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]>
61 lines
3.1 KiB
Markdown
61 lines
3.1 KiB
Markdown
# 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.
|