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]>
3.1 KiB
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 returnsindex.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: newservecommand (--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.