Files
stack/docs/plans/reviews/2026-09-12_control-board-step3-seen-marks.md
T
jason.woltjeandClaude Fable 5.1 88d21defde Check pi liveness per tmux pane and add "Seen" marks to the control board (#1503)
First step-3 refinement from Jason's daily use. Liveness now lists the
panes of the agent's tmux session and counts it alive only if a pane runs
pi, so killed pi sessions whose tmux session still exists show offline
instead of waiting. A "Seen" button on waiting and error rows stores the
row's lastActivity in <dataRoot>/board/seen.json (clicks only, never
rewritten by a scan, fail closed if corrupt) and drops the row from
"Waiting on you" until the agent writes anything newer; "Unsee" reverses
it. New POST /api/seen route: JSON only, 4 KB limit, 400 on bad input.

Tests: control-board 63/63 (30 new), registry 69/69. Review APPROVED;
receipt docs/plans/reviews/2026-09-12_control-board-step3-seen-marks.md.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-12 08:24:49 -05:00

3.4 KiB

Control board step 3, first refinement: real liveness and "Seen" marks

Date: 2026-09-12. Recorded by jarvis (Claude Code, coordinator). Tracking: #1503. Plan: docs/plans/2026-09-12_control-board-mvp.md (Step 3 log).

What Jason reported

After using the page: "most of the sessions are actually just in a complete state rather than waiting on me". Also, he had terminated the darkwing and dewey pi sessions, and both still showed "waiting".

Cause

  1. Liveness only asked tmux whether a session by that name existed. Both tmux sessions still existed (now running bash and Claude), so the scanner read the old pi session log as live status.
  2. Pi records that the agent finished a reply, not whether that reply was a question or a completion report. Both landed in "waiting".

What was built

  • tmuxIsAlive now lists the panes of the named tmux session and reports alive only if one runs pi. A session that exists but runs something else is offline. The tmux check is injectable for tests.
  • "Seen" marks: <dataRoot>/board/seen.json maps project/agent to the lastActivity timestamp Jason marked. A marked row keeps its state but leaves "Waiting on you" until the agent writes anything newer, which changes the timestamp and clears the mark by itself. "Unsee" clears it by hand. The file is written only by clicks (atomic, mode 0600); a scan reads it and never rewrites it; a corrupt file refuses the scan (fail closed).
  • Server: POST /api/seen with a JSON body; requires Content-Type: application/json (so a page from another site cannot post it without a CORS preflight, which this server does not answer); body limit 4096 bytes; 400 on bad input; 500 if the rescan fails. Other methods 405.
  • Page: Seen / Unsee button beside the state badge, "seen" tag, footer count. CLI scan --print shows s for seen rows and a seen count.

Tests

node --test packages/control-board/tests/: 63 pass, 0 fail (30 new, written by a Claude Sonnet helper against the spec; no src change was needed). node --test packages/mosaic/tests/: 69 pass, 0 fail, unchanged.

Live check

Real tmux, full fleet, Chrome via DevTools on port 7332. Before: 18 rows waiting on Jason including darkwing and dewey. After the liveness fix: 16, both now offline (their tmux sessions run bash). Clicking Seen on zane wrote {"fleet/zane": "<timestamp>"} (mode 600), the row left "Waiting on you" and showed a seen tag with Unsee in its project table; the API reported 15 waiting and 1 seen. Unsee emptied the file and restored 16. Console: no messages. Screenshot /tmp/control-board-seen.png (not committed). The board was left with no marks.

Independent review

Reviewer: code-review agent (Claude Sonnet), read-only. It checked the pi runtime source to confirm a live pi pane reports pi as its command (pi sets its process title, and tool subprocesses do not take over the pane), traced the CORS argument for the POST route, the escaping in the new markup, the single-response paths in the server, and the stale-mark fail-safe. Verdict: APPROVED with one finding: the README quoted the tmux command without the -s -t '=<session>' flags the code uses. Fixed in README and plan page.

Boundaries kept

Loopback only, no auth, no daemon, no launcher changes, no new root files, no run records touched. seen.json is Jason's input, not derived data, and is the only board file a scan does not regenerate.