Files
stack/packages/webui/README.md
T
jason.woltjeandClaude Opus 5.5 08b428ecf1 feat(webui,conversation): S5 WebUI views and CHAT-03 follow-ups (row 40, #1522)
Dewey's round 3 candidate, manifest
agents/dewey/work/queue-40/candidate-manifest-r3.sha256 (d0aa0ded,
27 files, checked OK in the canonical tree).

- WebUI inbox, tasks, agents and trail views, read-only over /api/bus.
  The README says the bus proof ends at the Console process.
- CHAT-03 seal: the engine command is fixed, the engine environment is
  explicit, SEAL_FLAGS has --no-approve, escalating is cleared on throw.
- Terminal input typed after Ctrl-T or Ctrl-O is held. Only the run whose
  own parse set held drains it (T1), and #run catches errors per action.
- DEFERRED keeps N2 and moves F2 to done, citing T1.

Reviews: Filbert approve (comment 27011, rev 260), Darkwing approve
(27013, rev 264). Landing gate on 8cad7722 plus the candidate: webui 22,
conversation 161, control-board 124, every scripts/test-*.sh green,
test-task 98/0. Mutant Mr survives; its flows test is the first
follow-up row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 22:05:43 -05:00

185 lines
11 KiB
Markdown

# Mosaic Console
Piece 4, #1507. The control board is the first and only WebUI screen.
## Run
Start the existing board in one terminal, then the WebUI in another:
```sh
node packages/control-board/src/cli.mjs serve
node packages/webui/src/cli.mjs serve
```
Open http://127.0.0.1:7330/. Optional `--port N` and
`--board http://127.0.0.1:7331` select another port or loopback board origin.
`--business ID` picks the business the bus views read; without it they read
the running bus host's business (`mosaic bus start <business>`).
Port 0 picks a free port. Ctrl-C stops each foreground server. No daemon,
installation, account, authentication or deployment is added.
Only HTTP loopback board origins are accepted. The WebUI binds to IPv4 loopback
by default, rejects nonlocal Host and cross-origin requests, sends no CORS
headers, refuses redirects and accepts only JSON object POST bodies up to 4096
bytes. Do not expose either unauthenticated server through a public proxy.
## Use
- Waiting on you uses the board's `waitingOnYou` flag across all projects.
- Select a project on the left to filter the session table. Counts show visible
rows out of the total when Hide offline or Hide seen hides anything.
- Select an agent to open its inspector. Arrow keys move between table agent
buttons, Enter opens, and Close or Escape in the inspector returns focus.
- Seen clears the row from Waiting on you until the board detects new activity.
The collapsed Seen section keeps those rows available; Unsee returns them.
- Reply appears only where the board's registration permits it. A failure shows
the board's stderr and keeps the draft. Transport success is the board's
`delivered` receipt, not proof that the seat processed the message.
- Refresh runs the existing board scan. Automatic refresh uses the board page's
ten-second interval; Pause stops it. Failed refreshes keep the last snapshot
with a warning. There are no automatic action retries.
- Palette and appearance use the existing brand tokens and persist in this
browser when local storage is available. No settings screen is added.
- History opens a read-only conversation view for a seat, from its Waiting
card, its table row or its inspector (#1507, CHAT-02). It shows the whole
branch with nothing clipped. Tool calls, tool results and thinking start
collapsed. Session text is always shown as text: Markdown stays as source,
and terminal controls, bidi controls and marks show as visible symbols. Reply in the view
uses the same board reply path as the inspector.
- The view checks for new entries on the same ten-second refresh, and Pause
stops it. It never switches on its own. A fork, a newer session for the seat
or a rewritten file each shows a marker with a button to open the other
history. Reloading after a rewrite keeps the branch the view was on; if that
branch is gone, the view opens the latest one and says so. Session lists
older sessions for the seat. Only repository Pi seats
have history; other harnesses say so.
Drafts and receipts stay in page memory, including across refresh, inspector
changes and a stale registration. Reloading or closing the page loses them.
A pending send stays disabled across refresh; new text typed during a send is
not cleared by the earlier send's success. If the proxy loses the response,
delivery may be unknown: inspect the seat before sending again.
## Inbox, tasks, agents and trails (slice 1 S5, #1522)
The Console sidebar adds Inbox, Tasks and Agents next to the control board,
which stays at `#/`. Routes: `#/inbox`, `#/inbox/<decision>`, `#/tasks`,
`#/tasks/<vikunja:project/task>`, `#/agents`, `#/trail/task/<ref>` and
`#/trail/decision/<id>`, with `?kind=` filters on a trail. The design
note is `agents/dewey/work/wui/SLICE1-VIEWS.md`.
- Every view reads the four verbs of the Q1 module (`packages/bus/src/views.mjs`,
lead decision 56): `inbox`, `tasks`, `agents` and `trail`. The CLI reads the
same functions, so both show the same broker data.
- Nothing writes. A decision shows `mosaic decide <id> <key>` for each choice,
with Copy, which only puts the command on the clipboard. It is answered in a
terminal (REQ-DEC-3, Q4). Seen is not set from the browser either. The
command has no `--business`, so `mosaic decide` uses the running bus host's
business, as the Console does by default. From a Console started with
`--business` for another business the copied command refuses (no bus host,
or no such decision) rather than answering elsewhere.
- What no Q1 verb returns is labelled where it would show, for example
"Bot names: not in the Q1 module" (lead decision 63). Nothing is guessed.
- A task's list row can only say its current state came from a Vikunja read,
since the list has no trail. The task page reads the trail and tells a task
the stack never created from one changed in Vikunja after the stack wrote it.
A stale poll read is shown in the snapshots and never as current.
- All bus- and agent-authored text is rendered inert: controls show as control
pictures or `[U+XXXX]`, and Markdown stays as source.
- Views refresh on the board's ten-second interval and stop on Pause. Focus,
open sections and the copy status survive a refresh.
- A failed read keeps the last good answer for that read and says it is old,
with the time it was read. A page never read shows the failure instead,
titled "Bus refused the read" (403: the bus refused the Console's read, for
example `human-required` from a Console started inside an agent run), "No bus to read" (no system
config, or no bus host and no `--business`) or "The read failed".
## Data and boundaries
GET `/api/board`, `/api/conversations` and `/api/conversation`, and POST
`/api/seen` and `/api/reply` proxy only those board paths. Only the two
conversation routes carry their query string; the board validates it. POST
bytes and upstream status/JSON are preserved. GET `/api/config` returns the
configured board URL for the page's error message. No scanner, registration,
session reader or transport is implemented here. The session reader is
`packages/conversation`, served by the board.
GET `/api/bus/inbox`, `/api/bus/tasks`, `/api/bus/agents` and
`/api/bus/trail?subject=<id>` are the bus reads (`src/bus.mjs`). Any other
method is 405. The subject must match the broker's identifier rule, else 400.
Each read runs the S4 human transport (`packages/bus/src/human-cli.mjs`) as
a child process, so a slow read doesn't stall the server. The bus checks
that child's ancestry, which ends at the Console process; it never sees the
browser or any other client of the port. So while the Console runs,
anything that can connect to its port (7330 by default) reads what a reader
capability reads ("Human and reader paths" in `packages/bus/README.md`):
the inbox, tasks, agents and trails. That includes a T3 seat using `curl`
and a managed S6 session, since S6 doesn't confine the network. No route
writes (Q4), so the exposure is reads only. Slice 1 doesn't change this
(Filbert F1 on #1522). Answers are `{ rows, at }`; failures are `{ error, message }`
with 403 for a refusal, 503 for no bus or no answer, and 502 for an answer
that can't be used. The bus needs the system config
(`~/.config/mosaic-dev/config.json`); without it the board still serves and
the bus routes answer `not-configured`.
Console's shared CSS, Console CSS, brand.js and local Manrope fonts were copied
unchanged from `agents/dewey/work/wui/`. Font license and source URLs accompany
the files under `src/public/assets/fonts/`. `live.css` contains the live-page
adaptations; original mockups and unrelated pending design work stay untouched.
Unsupported mockup fields and controls are listed in `docs/plans/DEFERRED.md`.
The source tree is a project filter; no invented workspace registration tree
or mockup session hierarchy is presented as live data.
## Verify
```sh
node --test packages/webui/tests/
node --test packages/control-board/tests/ packages/seat/tests/ packages/ledger/tests/ packages/mosaic/tests/
WEBUI_EVIDENCE=/tmp/webui-evidence node --test packages/webui/tests/browser.test.mjs
WEBUI_EVIDENCE=/tmp/webui-evidence node --test packages/webui/tests/conversation.test.mjs
WEBUI_EVIDENCE=/tmp/webui-evidence node --test packages/webui/tests/bus-browser.test.mjs
```
Node's test runner and installed `/usr/bin/chromium` are required. Set `CHROMIUM`
to another installed Chromium path. No npm download is needed. The copied CDP
helper starts a separate temporary browser profile and removes it on exit.
Tests use temporary board session/registration files and stub the board's
transport. They never send to real seats or read real session logs.
Browser tests exercise the rendered real board fixture, exact counts, project
filtering, keyboard return focus, Seen/Unsee, success/failure receipts, draft and
caret preservation, pending-send exclusion, storage denial, stale registration,
loading/empty/malformed/unreachable states and hostile text. Contrast is measured
on 330 rendered samples across ten palettes and three modes. Layouts are checked
at 320, 390, 768, 1440 and 2560px. Horizontal scrolling is intentional within the
dense table; the page itself must not overflow.
Conversation tests run the real board routes over temporary repository-layout
session files. They cover full-length answers, collapsed tools and thinking,
hostile content rendered inert, malformed-line and reconcile markers, forks, and
the return flow: a send from the view, a tool call and a delayed result while a
draft is typed, a peer message, a 4.5-million-character answer split into
continuation parts, and a relaunch mid-turn.
Bus tests (`bus.test.mjs`, `bus-browser.test.mjs`) run the routes and the
views against an in-process broker read through its reader session, so every
row has the broker's own shape. `humanCall` runs against a stub CLI script.
They cover each view, the gap labels, inert hostile text, Copy, the stale
fallback and each failure title, and that no route writes. They never start
the human CLI against a live bus.
No root CI workflow is configured for this package. These local tests are not a
claim of CI, deployment, live-seat delivery or user acceptance.
## User test and rollback
Gate E belongs to Jason on Monday 2026-09-14. Use Console to find who is waiting,
open a registered repo seat, reply and see its next answer after a scan. Mark a
completion Seen and find it again in Seen. Record any reason to open the board's
own page or a repo seat terminal in DEFERRED.md. Pass is Jason's end-of-day
say-so, not a test result. Keep #1507 open pending that ruling.
Rollback requires only stopping the WebUI and using the unchanged board at 7331.
Revert the scoped WebUI commit to remove it; there is no data migration to undo.
Seen and reply actions already taken belong to the board and are not rolled back.