From eef937bfa438ff6c8a5991116e0d7cadd01ab3b4 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 22 Oct 2026 11:40:01 -0400 Subject: [PATCH 1/3] feat(app): notify @mentions of you, and the ones that arrived while you were away A new mention of you glows the window when it's focused on another channel and sends a desktop notification otherwise. Mentions found within ten seconds of Ace opening or a host reconnecting arrived while you were away and notify once as a summary. The count each channel had when you were last told is kept per host and profile, so reloads and reconnects never repeat a notice. Why: apps/app/src/attention.ts#function useAttention | mention counts are ignored until the person's GitHub login loads; with no names every count reads zero, and recording that would re-notify old mentions --- apps/app/src/attention.ts | 58 ++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/apps/app/src/attention.ts b/apps/app/src/attention.ts index ebe84f7d..53babb0a 100644 --- a/apps/app/src/attention.ts +++ b/apps/app/src/attention.ts @@ -10,14 +10,16 @@ import { host } from "./host"; /** * Run ends and messages this person has seen or been told about, by channel, and how many mentions - * of them each channel had when they last saw it. Anything before `since` predates this browser - * profile's first visit, so history never lights up. Profiles from before mentions have no `heard`. + * of them each channel had when they last saw it (`heard`) or were told (`called`). Anything before + * `since` predates this browser profile's first visit, so history never lights up. Profiles from + * before mentions have no `heard`, and from before mention notices no `called`. */ type State = { since: number; seen: Record; told: Record; heard?: Record; + called?: Record; }; export type Outcome = Exclude; @@ -37,6 +39,18 @@ const NOTICES: Record = { /** The pulse holds its glow this long, then keeps these props until it has faded out. */ export const glow = { hold: 1800, props: { glowOpacity: 0.6, glowFadeIn: 0.8, glowFadeOut: 1.3 } }; +// Listings outlive the socket and refresh just after each connect, behind other updates, so mentions +// found that soon after one arrived while this person was away. +const CATCH_UP = 10_000; +let opened = host.status === "open" ? Date.now() : 0; +let caught = false; +host.subscribe(() => { + if (host.status !== "open") opened = 0; + if (host.status !== "open" || opened) return; + opened = Date.now(); + caught = false; +}); + const seenAt = (channel: Listing, state: State) => Math.max(state.since, state.seen[channel.id] || 0); @@ -66,11 +80,22 @@ function baseline(channel: Listing, names: string[], state: State): number { ?? (channel.created > state.since ? 0 : mentioned(channel, names)); } +const times = (count: number) => count === 1 ? "Mentioned you" : `Mentioned you ${count} times`; + +/** One notice for every mention that arrived while this person was away, so a reconnect never floods. */ +function missed(found: { channel: Listing; count: number }[]): [string, string] { + const [{ channel, count }] = found; + if (found.length === 1) return [channel.name, `${times(count)} while you were away`]; + const list = found.map((value) => `${value.count} in ${value.channel.name}`).join(", "); + return ["Mentions while you were away", list]; +} + /** * Runs this person invoked that ended, or that wait on a question, while they were not watching the - * channel. Each pulses the window's glow or sends one desktop notification, then marks its row - * until they open it. Others' messages and @mentions of `names` (lowercased GitHub logins) mark the - * row too, without a notification. + * channel, and new @mentions of `names` (lowercased GitHub logins). Each pulses the window's glow or + * sends one desktop notification, then marks its row until they open it; mentions that arrived + * while Ace was closed or disconnected notify once together. Others' messages mark the row too, + * without a notification. */ export function useAttention( channels: Listing[], @@ -94,12 +119,21 @@ export function useAttention( const seen: Record = {}; const told: Record = {}; const heard: Record = {}; + const called: Record = {}; + const mentions: { channel: Listing; count: number }[] = []; + // Until the person's GitHub login loads, every count reads zero and would re-tell old mentions. + const named = names.length > 0; + const away = named && !caught && Date.now() - opened < CATCH_UP; for (const channel of channels) { const { id, ran, said } = channel; const looking = watching && id === open; // Mentions are counts, not times, so each channel keeps the count it was last seen at. const count = looking ? mentioned(channel, names) : baseline(channel, names, state); if (state.heard?.[id] !== count) heard[id] = count; + const total = mentioned(channel, names); + const before = state.called?.[id] ?? baseline(channel, names, state); + if (named && total !== before) called[id] = total; + if (named && total > before && !looking) mentions.push({ channel, count: total - before }); if (looking) { const last = Math.max(ran?.at || 0, said?.at || 0); if ((state.seen[id] || 0) < last) seen[id] = last; @@ -114,15 +148,25 @@ export function useAttention( desktop?.notify(channel.name, NOTICES[outcome]).catch(() => {}); } } - if (!Object.keys(seen).length && !Object.keys(told).length && !Object.keys(heard).length) { - return; + if (away && mentions.length) { + caught = true; + const [title, body] = missed(mentions); + desktop?.notify(title, body).catch(() => {}); + } else if (mentions.length && watching) setPulse("on"); + else { + for (const { channel, count } of mentions) { + desktop?.notify(channel.name, times(count)).catch(() => {}); + } } + const changes = [seen, told, heard, called].some((value) => Object.keys(value).length); + if (!changes) return; setState((value) => value && { ...value, seen: { ...value.seen, ...seen }, told: { ...value.told, ...told }, heard: { ...value.heard, ...heard }, + called: { ...value.called, ...called }, } ); }, [channels, user, names, state, setState, watching, open]); From db7a602244822d9464f36470163ef3c56d9ec0a8 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 22 Oct 2026 11:40:06 -0400 Subject: [PATCH 2/3] chore: update attention lane note --- ...QJSWS1YHDS9TRD27C41WG-told-persists-per-host-and-u.md | 0 ...49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md | 8 ++++++++ ...J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md | 9 +++++++++ 3 files changed, 17 insertions(+) rename .lane/{memory => attic}/apps/app/src/attention.ts/01M4XQJSWS1YHDS9TRD27C41WG-told-persists-per-host-and-u.md (100%) create mode 100644 .lane/memory/apps/app/src/attention.ts/01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md create mode 100644 .lane/memory/apps/app/src/attention.ts/01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md diff --git a/.lane/memory/apps/app/src/attention.ts/01M4XQJSWS1YHDS9TRD27C41WG-told-persists-per-host-and-u.md b/.lane/attic/apps/app/src/attention.ts/01M4XQJSWS1YHDS9TRD27C41WG-told-persists-per-host-and-u.md similarity index 100% rename from .lane/memory/apps/app/src/attention.ts/01M4XQJSWS1YHDS9TRD27C41WG-told-persists-per-host-and-u.md rename to .lane/attic/apps/app/src/attention.ts/01M4XQJSWS1YHDS9TRD27C41WG-told-persists-per-host-and-u.md diff --git a/.lane/memory/apps/app/src/attention.ts/01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md b/.lane/memory/apps/app/src/attention.ts/01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md new file mode 100644 index 00000000..19ba6e40 --- /dev/null +++ b/.lane/memory/apps/app/src/attention.ts/01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md @@ -0,0 +1,8 @@ +--- +id: 01M5J49W4DPK93Q9SVS23VW59A +anchor: function useAttention +created: 2026-10-22T15:40:01Z +norm: '1' +--- + +mention counts are ignored until the person's GitHub login loads; with no names every count reads zero, and recording that would re-notify old mentions diff --git a/.lane/memory/apps/app/src/attention.ts/01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md b/.lane/memory/apps/app/src/attention.ts/01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md new file mode 100644 index 00000000..d76db27e --- /dev/null +++ b/.lane/memory/apps/app/src/attention.ts/01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md @@ -0,0 +1,9 @@ +--- +id: 01M5J49ZNB9XYK6GFYV3SBXKAP +anchor: '@file' +created: 2026-10-22T15:40:05Z +norm: '1' +supersedes: 01M4XQJSWS1YHDS9TRD27C41WG +--- + +`told` and `called` persist per host and user so reloads and reconnects never re-notify the same run end or mention; `since` keeps history from lighting up on a first visit From 921157415532547b95b6fe6604df786c85d0adb2 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 22 Oct 2026 11:40:07 -0400 Subject: [PATCH 3/3] lane: sync fix-mentions memory --- ...01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md | 4 ++++ .../01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.lane/memory/apps/app/src/attention.ts/01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md b/.lane/memory/apps/app/src/attention.ts/01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md index 19ba6e40..49584821 100644 --- a/.lane/memory/apps/app/src/attention.ts/01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md +++ b/.lane/memory/apps/app/src/attention.ts/01M5J49W4DPK93Q9SVS23VW59A-mention-counts-are-ignored-u.md @@ -3,6 +3,10 @@ id: 01M5J49W4DPK93Q9SVS23VW59A anchor: function useAttention created: 2026-10-22T15:40:01Z norm: '1' +sig: 764bb872be391a4f +body_hash: 5ea3135588da2978 +raw_hash: b0b3e34b9fb9b707 +lines: 100-195 --- mention counts are ignored until the person's GitHub login loads; with no names every count reads zero, and recording that would re-notify old mentions diff --git a/.lane/memory/apps/app/src/attention.ts/01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md b/.lane/memory/apps/app/src/attention.ts/01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md index d76db27e..0a9dc39c 100644 --- a/.lane/memory/apps/app/src/attention.ts/01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md +++ b/.lane/memory/apps/app/src/attention.ts/01M5J49ZNB9XYK6GFYV3SBXKAP-told-and-called-persist-per.md @@ -3,6 +3,10 @@ id: 01M5J49ZNB9XYK6GFYV3SBXKAP anchor: '@file' created: 2026-10-22T15:40:05Z norm: '1' +sig: 9d222e7bbdcbbb9b +body_hash: a48e796f66372ad6 +raw_hash: 99a80a221071bf27 +lines: 1-195 supersedes: 01M4XQJSWS1YHDS9TRD27C41WG ---