Add control board web page and local server (#1503)

Step 2 of the control board MVP (MOSAIC-STACK-D-001): `serve` command starts
a loopback-only local server that serves one self-contained page and re-runs
the status scanner on each /api/board request. The page lists sessions
waiting on Jason first (errors on top), then one table per project with
plain-word states, ages, last messages, expandable detail rows, per-project
hide-offline, and a 10-second auto-refresh with pause.

Tests: control-board 33/33 (10 new: loopback rules, host refusal, all routes,
per-request rescan, 500 path, CLI refusals, live serve, page escaping guard);
registry 69/69 unchanged. Receipt:
docs/plans/reviews/2026-09-12_control-board-step2-review.md.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-12 07:58:38 -05:00
co-authored by Claude Fable 5.1
parent b9f59a5903
commit ebedd1281e
10 changed files with 914 additions and 26 deletions
+2
View File
@@ -212,3 +212,5 @@ are never rewritten or removed; corrections are new entries.
- 2026-09-12 — dewey (Claude Code; owner-directed takeover of dewey pi session 01a08111) — Resumed WUI Phase 2 from agents/dewey/work/wui/TASKS.md: D01 source-linked feature inventory, then D02-D05 five dashboard mockups and comparison page, folding in MOSAIC-STACK-D-001 control board as the landing view. Prototype files only under agents/dewey/work/wui; no v1 inspection, backend, credentials, commit or push.
- 2026-09-12 — jarvis (Claude Code, coordinator) — Control board MVP step 1 (#1503, D-001): plan page, ticket, `packages/control-board` scanner + CLI, 23/23 tests, live scan 42 sessions/18 waiting on Jason; independent review CHANGES REQUIRED then APPROVED after three fixes (length→error, unknown liveness state, secrets-boundary test). Receipt docs/plans/reviews/2026-09-12_control-board-step1-review.md. CURRENT next action: step 2, the page.
- 2026-09-12 — dewey (Claude Code; takeover of pi session 01a08111) — WUI Phase 2 D01-D04 done, D05 delivered: five dashboard mockups (Rail, Deck, Console, Board, Stream) plus comparison at agents/dewey/work/wui/mockups/index.html; verifier 31 checks, 0 failed, 2 not verified (forced colours, screen reader); records in VERIFY.md, README.md, TASKS.md, HISTORY.md, DECISIONS.md. Stopped at Jason's design-selection gate. No v1 inspection, backend, credentials, commit or push.
- 2026-09-12 — jarvis (Claude Code, coordinator) — Control board MVP step 2 (#1503, D-001): `packages/control-board` gains `serve` (loopback-only local server, `/api/board` re-runs the scanner) and a single-file page (waiting-on-you first, per-project groups, hide-offline, expandable rows, 10s auto-refresh with pause). Suites: control-board 33/33, registry 69/69. Static review (sonnet) found no defects; live browser check by coordinator. Receipt docs/plans/reviews/2026-09-12_control-board-step2-review.md. CURRENT next action: step 3, daily use by Jason.
+9 -1
View File
@@ -83,12 +83,20 @@ Board files are derived. They can be deleted and rebuilt at any time by
running the scanner again. They are NOT run records and they are not
evidence under the repository's write-once rules.
## Command
## Commands
Scan once and print the table:
```
node packages/control-board/src/cli.mjs scan --print
```
Start the page (step 2), then open http://127.0.0.1:7331/ in a browser:
```
node packages/control-board/src/cli.mjs serve
```
Tests:
```
+13 -13
View File
@@ -10,20 +10,20 @@ update this file to the next action). No ambiguity, no re-planning.
Jason decided on 2026-09-12 (MOSAIC-STACK-D-001) that the MVP is the control
board: one web page listing running agent sessions across projects, showing each
one's status, and flagging which ones are waiting on him. Plan page:
`docs/plans/2026-09-12_control-board-mvp.md`. Tracking: #1503. Step 1 (status
scanner `packages/control-board`, plan page, ticket) is done in this checkout;
running `node packages/control-board/src/cli.mjs scan --print` writes one status
file per agent under `<dataRoot>/board/`. The registry line (increment 3,
headless identity-env leak) stays parked; #1500 is closed.
`docs/plans/2026-09-12_control-board-mvp.md`. Tracking: #1503. Steps 1 and 2
are done in this checkout: `packages/control-board` scans every Pi agent
(repo `.pi/state/*` and `~/.mosaic/fleet`) and serves the page.
Next action: step 2 of #1503 — the web page. One local page that reads
`<dataRoot>/board/index.json`, groups sessions by project, shows the plain-word
state per agent, puts a "waiting on you" section at the top, and refreshes
itself (re-running the scanner on each refresh is acceptable). Reuse Dewey's
control-board mockups under `agents/dewey/work/wui/` for layout where they fit.
Acceptance: Jason opens the page in a browser and sees the same rows the
`--print` output shows, with no manual steps beyond starting the page. No auth,
no hooks/plugins, no comms, no launcher changes, no new root files.
Start it with `node packages/control-board/src/cli.mjs serve` and open
http://127.0.0.1:7331/ (loopback only, no auth, no daemon; Ctrl-C stops it).
Next action: step 3 of #1503 — daily use. Jason opens the page while he works
and reports what is wrong, missing or confusing; each report becomes a small
fix on this package with a test, committed and pushed to `refactor`. Close
#1503 when Jason says the page answers "who is waiting on me" without him
opening a terminal. Out of scope until he asks: auth, hooks/plugins, comms,
memory, launcher changes, non-Pi harnesses, new root files. The registry line
(increment 3, headless identity-env leak) stays parked; #1500 is closed.
## Completed checkpoint: #1500 increment 2 (historical)
@@ -0,0 +1,60 @@
# Control board MVP step 2: the page, implementation and review receipt
Date: 2026-09-12. Recorded by jarvis (Claude Code, coordinator). Tracking: #1503.
Plan: `docs/plans/2026-09-12_control-board-mvp.md`. Step 1 receipt:
`2026-09-12_control-board-step1-review.md`.
## What was built
- `packages/control-board/src/serve.mjs`: a small local web server with no
dependencies and no auth. Routes: `/` (the page), `/api/board` (re-runs the
scanner and returns `index.json`), `/healthz`, `/favicon.ico` (empty). It
refuses to bind to anything but a loopback address.
- `packages/control-board/src/page.html`: one self-contained page. "Waiting on
you" at the top (error rows first), then one table per project with a
plain-word state badge, age, and last message. Rows expand to a detail panel;
open panels and the per-project "hide offline" choice survive the 10-second
auto-refresh. Pause/Resume and a manual Refresh button. All API text is
escaped before it reaches the page.
- `src/cli.mjs`: new `serve` command (`--port`, `--host`).
## Tests
`node --test packages/control-board/tests/` — 33 tests, 33 pass, 0 fail
(23 scanner tests from step 1, 10 new: loopback rules, host refusal without a
socket, every route, per-request rescan, 500 path, CLI refusal/exit codes, a
live spawned `serve`, and a static check that the page's esc() helper escapes
every HTML-significant character and that no API value is joined into markup
without it).
## Live check in a real browser
Server started with the full fleet and real tmux liveness on port 7332; page
opened in Chrome through DevTools. Seen: 18 rows under "Waiting on you" with
the three fleet 429-error rows first; two project groups (mosaic-stack 4,
fleet 38) with "24 offline hidden"; unticking "Hide offline" showed the
offline rows; Pause changed the header to "auto-refresh paused"; a row toggle
opened its detail panel and stayed open across the next refresh. Console:
only a favicon 404 (a `/favicon.ico` route was added after; no page errors).
Screenshot: `/tmp/control-board-review.png` (not committed).
## Independent review
Reviewer: in-session code-review agent (Claude Sonnet), static read-only.
Findings: none at reportable confidence for XSS, host-check bypass, request
handling, fail-closed behaviour, or doc drift. It could not run the browser
check itself (no shell or browser tools in its session), so it withheld a
final approval on that ground; the browser check above was done by the
coordinator instead. Two minor notes acted on: a stale test comment, and
adding a test for the page's escaping. A separate test-writing helper flagged
that `startServer` threw synchronously for a bad host while other failures
rejected; it is now `async` so every failure is a rejection.
Corrections during this step: open detail panels originally closed on every
refresh (fixed, keyed by section/project/agent); the escaping guard test
initially flagged my own key-building line and a comment (reworded).
## Boundaries kept
No launcher changes, no new root files, no secrets, no auth (loopback only),
no daemon, no changes to `packages/mosaic`. Next: step 3, daily use by Jason.