Signals judge one subject, with a sentence of your own, not any one word (#259)

Jev was asked "Is this item about any of these: ai, development, programming?", so any one broad
word was enough and the Signal's name, which says what is meant, was never sent. AI Development
held 54 items: Sega pulling a game remaster (0.90), a Surface Laptop review (0.95), a C++ tutorial
(0.99). It is now asked about one subject, called by the Signal's name, described by its words
and, when those are not enough, its owner's sentence. Scored against TypeSafe over those 54 with
"AI tools for writing software: coding agents, AI dev tools, vibe coding, AI in code review.":
5 stay, Linux kernel AI code review highest at 0.94; the remaster 0.06, the C++ tutorial 0.02.

signals.description is new, and its first start deletes every verdict, which answered the old
question, so everything in the window is asked again. Any change to a Signal's name, words or
sentence drops its verdicts, since each is part of the question. The queue query names the
Signal's sentence apart from the item's description, which share a row; the test caught the
item's winning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 18:12:29 +00:00
parent f8371f255d
commit ce485a82ac
10 changed files with 150 additions and 60 deletions

View File

@@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Signals 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). They are kept in a Signals folder first in the feed list, under All Subscriptions, closed or open as an OPML is, with a + on its row for a new one and its own page listing them (#255, #256).
- 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 a + on its row for a new one and its own page listing them (#255, #256).
- 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

@@ -98,7 +98,7 @@ function signalHead(sg: Signal){
<div class="art">${ICON.signal}</div>
<div class="meta">
<h2>${esc(sg.name)}</h2>
<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>
<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>':''}
</div>
<div class="acts">
@@ -126,10 +126,13 @@ function signalModal(sg?: Signal){
<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">Listen for</label>
<div class="chips" id="sgkw">${chipsHTML(sg?.keywords||[],'sgkwin')}</div>
<div class="chips" id="sgkw">${chipsHTML(sg?.keywords||[],'sgkwin')}</div></div>
<div class="field"><label for="sgdesc">In your own words (optional)</label>
<input type="text" id="sgdesc" value="${esc(sg?.description||'')}" placeholder="AI tools for writing software: coding agents, vibe coding">
<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>
days of them and each new one, and pulls out the ones about this subject: its name, these
words and 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>
${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>`);
@@ -139,7 +142,7 @@ function signalModal(sg?: Signal){
const name=$<HTMLInputElement>('#sgname').value.trim(), keywords=chipWords($('#sgkw'));
if(!name){ $<HTMLInputElement>('#sgname').focus(); return; }
if(!keywords.length){ $<HTMLInputElement>('#sgkwin').focus(); return; }
const body=JSON.stringify({name,keywords});
const body=JSON.stringify({name,keywords,description:$<HTMLInputElement>('#sgdesc').value.trim()||null});
try{
const r=sg ? await api(`/api/signals/${sg.id}`,{method:'PATCH',body})
: await api('/api/signals',{method:'POST',body});

View File

@@ -91,6 +91,8 @@ interface Signal {
id: number;
name: string;
keywords: string[];
/// What its owner means by it, in their words (#259).
description: string | null;
unread: number;
}