Signals' words: what matters out of the noise of your feeds (#254)

The dialog asks what to listen for, the page says what it picked out of the noise of your feeds,
and a new Signal says nothing has come through yet instead of showing an empty list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 16:58:27 +00:00
parent 70990c70c7
commit 13cad18857
5 changed files with 15 additions and 13 deletions

View File

@@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Signals, under the places in the sidebar: New signal makes one from a name and keywords, and its page lists the items from your feeds that are about them, read and unread as in their own feeds. Settings takes your TypeSafe key, which Signals need (#254).
- Signals, under the places in the sidebar, pick what matters out of the noise of every feed you subscribe to: New signal takes a name and what to listen for, 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).
- 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

View File

@@ -444,9 +444,9 @@ async function prefsModal(){
<div class="inline"><input type="password" id="stskey" autocomplete="off" spellcheck="false"
placeholder="${S.me?.typesafe_key_set?'Saved. Paste a new one to replace it':'Paste your key'}">
<button class="btn ico" id="stsforget" title="Forget the key" aria-label="Forget the key"${S.me?.typesafe_key_set?'':' hidden'}>${ICON.trash}</button></div>
<span class="hint">Signals are picked by TypeSafe's Jev on your own key, from
<a href="https://typesafe.ai" target="_blank" rel="noopener">typesafe.ai</a>. It is kept on this
server and never shown again.</span></div>
<span class="hint">Signals pick what matters out of everything you subscribe to, using TypeSafe's
Jev on your own key, from <a href="https://typesafe.ai" target="_blank" rel="noopener">typesafe.ai</a>.
The key is kept on this server and never shown again.</span></div>
<div class="field boxed"><label for="tokname">API tokens</label>
<div id="tokens"></div>
<div class="inline"><input type="text" id="tokname" placeholder="What it is for">

View File

@@ -133,6 +133,8 @@ function renderEntries(){
if(!S.entries.length){
// Said only for a search, where the words typed are why; an empty feed or tab speaks for itself.
if(S.q) box.innerHTML='<p class="empty">Nothing matches that search.</p>';
// A new Signal is empty while Jev reads, which would otherwise look broken.
else if(sg&&S.filter==='all') box.innerHTML='<p class="empty">Nothing has come through yet. Jev reads new items as your feeds are checked.</p>';
return;
}
for(const e of S.entries) box.appendChild(epEl(e));

View File

@@ -38,8 +38,8 @@ function signalHead(sg: Signal){
<div class="art">${ICON.signal}</div>
<div class="meta">
<h2>${esc(sg.name)}</h2>
<div class="sub">Items from your feeds about ${esc(sg.keywords.join(', '))} · ${sg.unread} unread</div>
${nokey?'<div class="sub" style="color:var(--warn)">Nothing is picked until you add your TypeSafe key in Settings.</div>':''}
<div class="sub">Picked out of the noise of your ${plural(S.feeds.length,'feed')}: anything about ${esc(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>':''}
</div>
<div class="acts">
<button class="btn ico" data-a="settings" title="Edit this signal" aria-label="Edit this signal">${ICON.settings}</button>
@@ -65,12 +65,12 @@ function signalModal(sg?: Signal){
openModal(`<h3>${sg?'Edit signal':'New signal'}</h3>
<div class="field"><label for="sgname">Name</label>
<input type="text" id="sgname" value="${esc(sg?.name||'')}" placeholder="Formula 1"></div>
<div class="field"><label for="sgkwin">About any of</label>
<div class="field"><label for="sgkwin">Listen for</label>
<div class="chips" id="sgkw">${chipsHTML(sg?.keywords||[],'sgkwin')}</div>
<span class="hint">TypeSafe's Jev reads each item in the feeds you subscribe to, the last 30 days
of them and every new one, and puts the ones about any of these here as well as in their own
feed. Each item is one call on your TypeSafe key.${sg?' Changing these reads them all again.':''}</span></div>
${nokey?'<p class="hint" style="color:var(--warn)">Add your TypeSafe key in Settings first: nothing is picked without one.</p>':''}
<span class="hint">TypeSafe's Jev reads every item in the feeds you subscribe to, the last 30
days of them and each new one, and pulls out the ones about any of these. The rest stays
noise, in its own feed. Each item is one call on your TypeSafe key.${sg?' Changing these 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>':''}
<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>`);
wireChips($('#sgkw'));
@@ -83,7 +83,7 @@ function signalModal(sg?: Signal){
try{
const r=sg ? await api(`/api/signals/${sg.id}`,{method:'PATCH',body})
: await api('/api/signals',{method:'POST',body});
closeModal(); toast(sg?'Saved':'Made. Items show up here as Jev reads them');
closeModal(); toast(sg?'Saved. Jev reads everything again':'Listening. What Jev picks out shows up here');
await loadFeeds(true); selectFeed(':signal:'+(sg?sg.id:r.id));
}catch(e){ toast(why(e),true); }
};