From a35df2afb29fe5855262e3202d1597edd2673669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 25 Aug 2026 08:02:34 +0000 Subject: [PATCH] dispatch is anything that leaves, and the deeplink now goes somewhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the definition was "doing something with a result beyond keeping it", which does not obviously cover a notification ABOUT classification failing. it should: classification decided where the item goes and it went to unclassified/. telling somebody is a different step, and its rule belongs in DISPATCHING.md. the test is never what the message is about — it is whether anything left. and the commander notification pointed at officer://command-hq/{id} where {id} is the DIRECTORY name, while /command-hq/:sessionId addresses a commander by its transcript id. so the tap landed nowhere. the two want different ids and cannot be reconciled today: threading wants the directory name, stable from creation; the route wants the transcript id, which does not exist when the commander is created. rather than make a commander fetch its own transcript id before it can speak — an errand it would rightly skip, as the language rule already proved — the tap opens Command HQ and the owner picks. one tap worse, and correct. the honest fix is a route that accepts a directory name. noted where whoever does the mobile deep-link work will find it. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- src/servers/api/command-hq/directory.ts | 11 ++++++++--- src/servers/notifications/catalogue.ts | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/servers/api/command-hq/directory.ts b/src/servers/api/command-hq/directory.ts index db4bd40a..06f72cd4 100644 --- a/src/servers/api/command-hq/directory.ts +++ b/src/servers/api/command-hq/directory.ts @@ -228,9 +228,14 @@ export function commanderClaudeMd(params: CommanderClaudeMdParams): string { ' subdirectory of `data/` named for that category. It changes where the item IS, never what it is.', ' Optional, and most commanders do not need it: it earns its place only when there are enough items', ' that finding one matters. It is a STEP, not a place — the place is always `data/`.', - '- **Dispatch** — doing something with a result beyond keeping it. Telling the owner, handing it to', - ' another agent, sending it somewhere. Optional, off until the owner arms it, and the only one of the', - ' four that acts outside this directory.', + '- **Dispatch** — anything that LEAVES this commander. Telling the owner, waking another conversation,', + ' handing work to an agent, sending something somewhere. Optional, off until the owner arms it, and the', + ' only one of the four that acts outside this directory.', + '', + ' Note what that includes: a notification saying classification could not place an item is DISPATCH,', + ' not classification. Classification decided where the item goes and it went to `unclassified/`.', + ' Telling somebody about it is a different step and its rule lives in `DISPATCHING.md`. The test is', + ' never what the message is about — it is whether anything left.', '', 'Storing the result is NOT dispatch. Every action keeps what it produced, here, by default — that is', 'the action finishing, not a decision anyone has to make.', diff --git a/src/servers/notifications/catalogue.ts b/src/servers/notifications/catalogue.ts index 905e3bc8..ed2faabc 100644 --- a/src/servers/notifications/catalogue.ts +++ b/src/servers/notifications/catalogue.ts @@ -127,8 +127,18 @@ const CORE_ENTRIES: Record = { A commander with something to say. ONE entry rather than one per commander, and the commander travels as `{id}` — so arming a new - commander needs no catalogue change and no app resync. `{id}` is the commander's row id, which is what - `officer://command-hq/{id}` opens. + commander needs no catalogue change and no app resync. `{id}` is the commander's DIRECTORY name, which + is what threads its notifications together. + + The route is the LIST, deliberately, and not `officer://command-hq/{id}`. That is what it was, and it + was wrong: `/command-hq/:sessionId` addresses a commander by its TRANSCRIPT id, so a deeplink built + from the directory name lands nowhere. Threading wants the directory name (stable from creation, before + any transcript exists); the route wants the transcript id (which does not exist yet when the commander + is created). Rather than make a commander fetch its own transcript id to send a notification, the tap + opens Command HQ and the owner picks — one tap worse, and correct. + + Worth revisiting when the app handles deep links properly: the honest fix is a route that accepts a + directory name, not a commander doing a lookup before it can speak. This entry is what a commander sees when it sends nothing but a code and an id: "Voice Memos" would be a lie without knowing which one, so the generic is honest and the deeplink does the rest. In practice a @@ -163,7 +173,7 @@ const CORE_ENTRIES: Record = { title: 'Commander: {title}', body: '{body}', target: 'mobile', - route: 'officer://command-hq/{id}', + route: 'officer://command-hq', thread: 'commander:{id}', }, };