# 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.