A Signal with no key links to typesafe.ai and to the key's field in Settings (#266)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 19:32:57 +00:00
parent f34423735a
commit f1a156c974
3 changed files with 30 additions and 3 deletions

View File

@@ -2115,3 +2115,15 @@ test('a Signal holds the items Jev says fit its keywords, read where they are re
await page.request.delete(`/api/feeds/${encodeURIComponent(feed)}`); await page.request.delete(`/api/feeds/${encodeURIComponent(feed)}`);
await page.request.patch('/api/me', { data: { typesafe_key: null } }); await page.request.patch('/api/me', { data: { typesafe_key: null } });
}); });
test('a Signal with no key to judge it links to where one is got and where it goes', async ({ page }) => {
await page.request.patch('/api/me', { data: { typesafe_key: null } });
const { id } = await (await page.request.post('/api/signals', { data: { name: 'Keyless', keywords: ['x'] } })).json();
await page.reload();
await page.evaluate(id => selectFeed(':signal:' + id), id);
await expect(page.locator('#content a[href="https://typesafe.ai"]')).toBeVisible();
await page.locator('#content [data-keylink]').click();
await expect(page.locator('#stskey')).toBeFocused();
await page.keyboard.press('Escape');
await page.request.delete(`/api/signals/${id}`);
});

View File

@@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Signals pick what matters out of the noise of every feed you subscribe to: New signal takes a name, the words to listen for and, if those are not enough, a sentence saying what you mean, and its page lists the items from your feeds that are about it, read and unread as in their own feeds. Settings takes your TypeSafe key, which Signals need (#254). They are kept in a Signals folder first in the feed list, under All Subscriptions, closed or open as an OPML is, with its own page listing them and a + there for a new one (#255, #256, #265). Each can have an icon of its own, searched for among all of Font Awesome Free, in place of its initials (#261), or one picked for it with Auto, which a Signal made without an icon gets by itself; each Signal's row counts its items and unread as a feed's does (#263). - Signals pick what matters out of the noise of every feed you subscribe to: New signal takes a name, the words to listen for and, if those are not enough, a sentence saying what you mean, and its page lists the items from your feeds that are about it, read and unread as in their own feeds. Settings takes your TypeSafe key, which Signals need, and a Signal without one links to typesafe.ai to get one and to the key's place in Settings (#254, #266). They are kept in a Signals folder first in the feed list, under All Subscriptions, closed or open as an OPML is, with its own page listing them and a + there for a new one (#255, #256, #265). Each can have an icon of its own, searched for among all of Font Awesome Free, in place of its initials (#261), or one picked for it with Auto, which a Signal made without an icon gets by itself; each Signal's row counts its items and unread as a feed's does (#263).
- A list opens at once when you have seen it before or the page fetched it ahead: All Subscriptions and the feeds with something unread are fetched when the page loads, and a feed or filter as the pointer or a finger reaches it, then refreshed behind once shown. A list that has to be waited for shows a spinner instead of nothing (#247). Currently Listening and the Directory's front page are fetched ahead the same way and open at once (#248). - A list opens at once when you have seen it before or the page fetched it ahead: All Subscriptions and the feeds with something unread are fetched when the page loads, and a feed or filter as the pointer or a finger reaches it, then refreshed behind once shown. A list that has to be waited for shows a spinner instead of nothing (#247). Currently Listening and the Directory's front page are fetched ahead the same way and open at once (#248).
### Changed ### Changed

View File

@@ -109,7 +109,7 @@ function signalHead(sg: Signal){
<div class="meta"> <div class="meta">
<h2>${esc(sg.name)}</h2> <h2>${esc(sg.name)}</h2>
<div class="sub">Picked out of the noise of your ${plural(S.feeds.length,'feed')}: ${esc(sg.description||sg.keywords.join(', '))} · ${sg.unread} unread</div> <div class="sub">Picked out of the noise of your ${plural(S.feeds.length,'feed')}: ${esc(sg.description||sg.keywords.join(', '))} · ${sg.unread} unread</div>
${nokey?'<div class="sub" style="color:var(--warn)">No TypeSafe key yet, so nothing is being picked out. Add one in Settings.</div>':''} ${nokey?`<div class="sub" style="color:var(--warn)">No TypeSafe key yet, so nothing is being picked out. ${KEY_LINKS}</div>`:''}
</div> </div>
<div class="acts"> <div class="acts">
<button class="btn ico" data-a="settings" title="Edit this signal" aria-label="Edit this signal">${ICON.settings}</button> <button class="btn ico" data-a="settings" title="Edit this signal" aria-label="Edit this signal">${ICON.settings}</button>
@@ -151,7 +151,7 @@ function signalModal(sg?: Signal){
and each new one, and the ones about this subject are pulled out: its name, these words and and each new one, and the ones about this subject are pulled out: its name, these words and
your sentence together. The rest stays noise, in its own feed. Each item is one call on your your sentence together. The rest stays noise, in its own feed. Each item is one call on your
TypeSafe key.${sg?' A change here reads everything again.':''}</span></div> TypeSafe key.${sg?' A change here reads everything again.':''}</span></div>
${nokey?'<p class="hint" style="color:var(--warn)">Add your TypeSafe key in Settings first: without one, nothing is picked out.</p>':''} ${nokey?`<p class="hint" style="color:var(--warn)">Without a TypeSafe key, nothing is picked out. ${KEY_LINKS}</p>`:''}
<div class="cardacts"><button class="btn ico" onclick="closeModal()" title="Cancel" aria-label="Cancel">${ICON.close}</button> <div class="cardacts"><button class="btn ico" onclick="closeModal()" title="Cancel" aria-label="Cancel">${ICON.close}</button>
<button class="btn ico primary" id="sgsave" title="${sg?'Save':'Make signal'}" aria-label="${sg?'Save':'Make signal'}">${sg?ICON.check:ICON.plus}</button></div>`); <button class="btn ico primary" id="sgsave" title="${sg?'Save':'Make signal'}" aria-label="${sg?'Save':'Make signal'}">${sg?ICON.check:ICON.plus}</button></div>`);
wireChips($('#sgkw')); wireChips($('#sgkw'));
@@ -272,3 +272,18 @@ async function jevIcon(name: string, keywords: string[], description: string|nul
const r=await api('/api/signals/icon',{method:'POST',body:JSON.stringify({name,keywords,description,candidates})}); const r=await api('/api/signals/icon',{method:'POST',body:JSON.stringify({name,keywords,description,candidates})});
return r.icon; return r.icon;
} }
/// Where to get a TypeSafe key and where it goes, wherever a Signal says it has none: one click to
/// each, rather than "in Settings" left to be found.
const KEY_LINKS=`<a href="https://typesafe.ai" target="_blank" rel="noopener">Get one at typesafe.ai</a>, then
<button type="button" class="dlink" data-keylink>add it in Settings</button>.`;
/// Settings, open at the key's field.
async function keySetting(){
closeModal();
await prefsModal();
const k=$<HTMLInputElement>('#stskey'); k.scrollIntoView({block:'center'}); k.focus();
}
document.addEventListener('click',(e: MouseEvent)=>{
if(!(e.target as Element).closest?.('[data-keylink]')) return;
e.preventDefault(); keySetting().catch(err=>toast(why(err),true));
});