board-stream¶
The board's push channel — an SSE with two modes (bare change signals, or hash-chained incremental patches) fed by every freshness source the backend can see.
raw source¶
The dashboard kept its status/grouping fresh by re-fetching the whole board on a 4s timer, while the live terminal rode a WebSocket. So a session's status change felt laggy (up to the poll interval) even though the backend already knew it — two different freshness models on one screen. Give the board the terminal's model: the backend pushes and the dashboard follows, so status flips as fast as the characters do.
expanded spec¶
board-stream is the board's live-delivery channel: GET /api/board/stream, a server-sent-events stream a
dashboard opens once, server→client only, with a periodic keep-alive ping so an idle proxy never times it
out. It speaks two protocols on one route. Plain mode (no query) is the legacy contract, kept verbatim
for old clients: a bare board-changed signal, the client refetches /api/board on its ETag/304 path.
Delta mode (?mode=delta) inverts who fetches: the server sends a full snapshot on every (re)connect
(board-full {to, board}), then per change either the hash-chained patch (board-delta {from, to, set,
del}) or a fresh full when the patch wouldn't win — the algebra, and the proof that this renders exactly
what refetching would, is board-delta's contract.
Four event sources plus one explicit nudge, one debounced pipeline. (1) A recursive fs.watch on the
per-user session store
(runtime) — every lifecycle transition lands as a session.json write. (2) A watch on the shared git
dir's refs (loose refs recursively, packed-refs/HEAD beside them) — a commit or merge moves a ref the
moment it lands, so tree reshapes push instead of waiting out a poll. (3) A subscriber-gated ~2s poll of the
CHEAP tmux session signature (sessions) for the two signals that never touch a file: liveness (a crash /
going offline) and activity (the live self-summary headline). (4) A delta-gated ~15s cold tick — the
server-side replacement for every client's slow fallback poll — that rebuilds and diffs so what no watcher
sees (an uncommitted worktree spec edit, a forge refresh, a watch blind spot like a recursively-deleted
store dir) still lands, once per tick total instead of once per open dashboard. And (0) an exported
explicit nudge (notifyBoardChanged) for a server-side mutation the watchers structurally cannot see —
today just /rename, which writes the worktree's .session, the name's ONE home outside the watched
store (session-rename); the route nudges the stream instead of double-writing the name into the store,
so a notification problem gets a notification solution, never a second data home. All of them funnel into
one debounced fire that collapses a burst (a merge touches many records) into one signal.
Rebuilds are gated on someone listening. With no delta subscriber the pipeline never builds — plain subscribers get the zero-cost notify they always did, and a closed dashboard costs nothing (the polls stop with their last subscriber). With delta subscribers the debounced fire rebuilds ONCE, broadcasts the patch to every delta stream, and notifies plain streams only when the board's content tag actually moved — so a signature wiggle that changes nothing no longer triggers a fleet of pointless refetches. Every source and watch is best-effort and never throws: a source that can't start just leaves that path to the cold tick or the client's own fallback.
Reconnect is free. A backend hot-reload replaces the child and drops the stream; EventSource
auto-reconnects to the fresh child — and in delta mode the reconnect's board-full re-anchors the patch
chain with no client-side repair logic. An old backend without this route, a proxy that strips SSE, or a
server that ignores ?mode=delta all degrade to the plain protocol or the fallback poll — never to a stale
board. What stays deliberately unshrunk here is the full snapshot itself (first paint, resync): slimming
that payload is board-lean's ongoing cut (tracked as issue #26), composing with — not replaced by — the
delta path.