Files
stack/agents/dewey/work/queue-53/evidence.md
T

26 KiB
Raw Blame History

Row 53 (#1542): Console tokens and shell restyle

Dewey, 2026-10-10. Brief: docs/plans/2026-10-10_design-implementation.md, section "Console tokens and shell restyle" (origin/refactor a81915e8), with docs/design/IMPLEMENTING.md sections "Tokens", "Icons and the mark", "Components", "Strings", "Boundaries" and "Acceptance". Lead decision 81 (Harbor following the system setting by default, the Relay placeholder mark stays, no Decision Seen from the web).

Built on 923957e2; no file under packages/webui, docs/design or the test scripts changed between it and the gate base 8a7871ff (only scripts/mosaic-task.mjs, row 56). Candidate: 16 files, uncommitted in the canonical checkout, listed in candidate-manifest.sha256 beside this file (round 1 manifest sha256 75569953c1a2cd7b7266208d6e6607290335f1bfe0cf94d96ed224e480a1dda8; rounds 2 and 3 below; round 3's is current).

Round 3 (answers #1542 comments 27163 and 27165)

Scope from Sage: two fixes, each with a test. 1: a board 403 followed by a 503 or 500 must not bring back the refused scan's rows or waiting card (Filbert C4, Darkwing P9). 2: a board 403 must clear an open conversation's history (Darkwing P10).

Round 3 candidate: the same 16 files, manifest sha256 2562c05d4a92f90096c0fe68ef8c2adfa7244fa547f3390155f5bee5cce64cba (candidate-manifest.sha256 beside this file, replacing round 2's 78c3aef9). Three files changed from round 2: README.md, src/public/app.js, tests/shell.test.mjs. The round 2 to round 3 delta is round3.patch beside this file. Applied to the round 2 files, it reproduces the manifest (16 OK, checked). Base for the gate: 03969219.

Fixes

Item Change Test
Filbert C4, Darkwing P9: a 403 then a 500 or 503 showed the refused scan's table and waiting card again error() now draws the empty board whenever it holds no data, not only on a refusal. It clears #waiting, #seen and #sessions, sets the three counts to –, closes the inspector, and says why in the tree, the footer and #status: "the board refused the read" or "the read failed". Shell browser test: after the refusal, brd.unavailable() (503) and a refresh. The banner is banner err with "No board data loaded."; #board-view shows again; no session row, open or history button, waiting card or seen card; the three counts are –; the footer and #fresh-board read "Board not read: the read failed", #status "Not read: the read failed", the tree "Not read: the read failed.". Screenshot board-refused-then-failed-400.
Darkwing P10: a 403 with History open left #conversation and the old #conv-log messages On a refusal with a conversation open, error() empties #conv-log and calls closeConversation(), which hides the panel and its reply form. A session with registered set; History open; the fixture message "Fixture history line" in #conv-log and the reply form shown. After a 403: #conversation hidden, no has-conversation on <body>, #conv-log has no child, #conv-form not visible, the board hidden and the refusal state shown. Then recovery.

Test fixtures in shell.test.mjs's stub board: one() adds a third session that is seen (so the seen card is tested), registered() answers one live session with a conversation, unavailable() answers 503, and with status 200 the stub answers /api/conversations and /api/conversation with one fixture page. The new assertions sit in the "shell in a browser" test, where the board refusal state already lives.

Also answered

Item Done
Filbert T7: the counts after a refusal Asserted: #waiting-count, #seen-count and #session-count are – after the 403 and after the 503 that follows it.
Filbert N7 (my call): the project filter on a refusal Tested, not changed: a project is picked before the refusal (aria-pressed true), and after recovery the "all" filter is the pressed one.
README The refusal line in the command bar section now names the cards, the project filter, the empty board on a later failure and the closed conversation. The shell tests line names the two new cases.

Changes in behaviour beyond the two fixes

  • A failed read before the first scan (no data yet, not a refusal) now draws the empty board with "Not read: the read failed" in the tree, footer and #status, where round 2 left the skeleton rows, "Loading board…" and "Loading projects…" under the error banner. This is the same code path as the C4 fix; the banner was already shown in both.
  • error() no longer calls convForm(). With P10, a refusal closes the conversation and convForm() does nothing without one; a non-refusal failure with no data never had an open conversation. The error-noconvform mutant survived for that reason and is dropped.

Round 3 mutants

Each applied to a scratch copy of the round 3 tree, then shell.test.mjs run (~/dewey-scratch/r53b/r3/mutants.py, outputs in r3/out/). 13 of 13 killed, every one by the "shell in a browser" test.

Mutant Change Killed at
c4-no-clear no-data branch leaves the cards and the table no rows or cards after the 503 (11, expected 0)
c4-keep-sessions the table kept same (7)
c4-keep-waiting the waiting cards kept same (2)
c4-keep-seen the seen cards kept same (2)
c4-refused-only the empty board only on a refusal (round 2) footer "refused" after the 503
c4-footer-refused footer always says "refused" footer after the 503
c4-status-refused #status always says "refused" #status after the 503
c4-tree-refused the tree always says "refused" tree after the 503
error-keeps-counts the counts left as they were #waiting-count after the 403
error-noinspect the inspector left open #inspector hidden after the 403
error-keeps-project a 403 keeps the project filter "all" filter pressed after recovery
p10-no-close the conversation left open #conversation hidden after the 403
p10-no-clear the panel closed but the history kept #conv-log empty after the 403

Round 3 gate

Worktree at 03969219 plus the 16 round 3 files (sha256sum -c of the manifest: 16 OK), node_modules linked from the checkout, TMPDIR in scratch, DOCKER_HOST pointed at a missing socket, suites sequential, 2026-10-10T19:13:43Z to 19:18:19Z. core.hooksPath unset. Script ~/dewey-scratch/r53b/r3/gate.sh, outputs ~/dewey-scratch/r53b/r3/gate/out/.

Suite Result
node --test 'packages/webui/tests/*.test.mjs' 27 pass, 0 fail
node suites of business, bus, cli, control-board, conversation, discord, ledger, mosaic, queue, runs, seat, tasks 60, 67, 66, 124, 182, 178, 78, 69, 148, 41, 19, 51 pass; 0 fail in each
node docs/design/tools/build-tokens.mjs --check rc 0, "tokens.css is current"
test-auth 15 passed, 0 failed
test-conductor 17 passed, 0 failed
test-config 24 passed, 0 failed
test-discord 66 passed, 0 failed
test-extension-package 18 passed, 0 failed
test-foundation 44 passed, 0 failed
test-queue 27 passed, 0 failed
test-release, no Docker 4 passed, 0 failed (state-machine cases skipped: daemon unavailable)
test-task, no Docker 26 passed, 0 failed (adapter seam, workspace and live cases skipped)

Round 2's gate had Docker for test-release and test-task (14 and 98). Round 3 changes nothing those Docker cases run; Sage's gate rerun covers them.

Not in this round

Darkwing note 3, the refresh-timer focus race in bus.js render(): it is in HEAD, so per Sage it goes to row 54 with its own test, named there as a fix to HEAD behaviour beside the H1 keep()/restore() fix.

Round 2 (answers #1542 comments 27143 and 27148)

Round 2 candidate: the same 16 files, manifest sha256 78c3aef963b7e35500c9f8afa6341057f1a6372ced70915d175e507b5fac8406 (candidate-manifest.sha256 beside this file, replacing round 1's 75569953). Five files changed from round 1: README.md, src/public/app.js, src/public/bus.js, src/public/shell.css, tests/shell.test.mjs. The round 1 to round 2 delta is round2.patch beside this file. Base for the gate: 2d4d4e7d (nothing under packages/webui, docs/design or scripts changed since 8a7871ff).

Blocking items

Item Change Test
Filbert C1, Darkwing 1: a board 403 left the inspector, project tree, counts, footer and #status "Scanned" line error() on a 403 drops data, selected and project, closes the inspector, and sets the tree to "Not read: the board refused the read.", the three counts to –, the footer to "Board not read: refused" and #status to "Not read: the board refused the read". The next good read restores them through accept() and render(). Shell browser test opens the inspector and checks the footer "waiting 1 · working 1" and two tree counts before the refusal; after it, the inspector is hidden, <body> has no has-inspector, the tree has no .count or [data-project], the footer and #fresh-board read "Board not read: refused", #status has no "Scanned". After recovery the footer is back.
Filbert C3: the freshness line called a refusal "stale" fresh() with no data: "Board not read: refused", "Board not read: the read failed" or "Board not read yet". Same test: #fresh-board equals "Board not read: refused".
Filbert C2, Darkwing 2 and note 6: a bus refusal left the Inbox count, BLOCKING chip and palette rows forget() clears last and the Inbox count on any refusal: in the view's read, in the board route's inbox read (was catch {}), and in pkOpen's refetch, which then redraws the section list. A refused read anywhere drops every kept bus read, so the pre-refusal rows can't come back as stale on a later failure either (Darkwing's P7). The bus freshness line reads "Bus not read: refused". Five-states test: #sections first reads "Inbox 1 1 blocking"; after the refusal it has no .count or .tag-block, and Ctrl+K lists only the four views. A second step refuses only pkOpen's own refetch and checks the kept tasks and the count are gone. On the board page a no-bus-host refusal of the board route's inbox read drops the count and the palette shows views only.
Filbert T1, Darkwing 4 (M01): only 403 was tested as a refusal none needed The five-states refusal step runs for human-required (403) and no-bus-host over kept rows: banner code, 0 rows, "Nothing to show.", "Bus not read: refused", no section counts, then recovery.
Darkwing 3: tests with the inspector open as C1 as C1
Darkwing 5 (M08): palette labels with markup none needed The inbox question is <img src=x onerror="errors.push(1)" id="pk-q">Push? and a task title <b id="pk-t">Bold</b> task. The palette lists views plus three items, no img or b in the list, the labels equal the literal text, and window.errors stays empty.
Sage: the section list below 760px The deviation is withdrawn. Below 760px .s1-sections is one strip, flex-wrap:nowrap; overflow-x:auto, items flex:none; it scrolls inside itself, so row 54's three sections add no rows. The command bar keeps its four controls on one row. At 360px and 400px: every section link has one offsetTop, the list's computed overflow-x is auto, no label wraps, and the page has no sideways scroll.

Non-blocking items

Item Done
Filbert T2 (accept() keeps failed) Test: a failed refresh, then a good one; #fresh-board no longer says "stale".
Filbert T3 (clipboard-denied toast stays) Test: after 3 s the error toast's class is still toast on err.
Filbert T4 (empty states' second line) Each bus empty state and the trail's is matched whole, both lines, anchored. The board's was already.
Filbert T5 (nav wrap untested) Covered by Sage's strip test.
Filbert T6 (Ctrl+Alt+K) Pinned: Ctrl+Alt+K opens nothing. Only Ctrl+K and Cmd+K open the palette.
Filbert N1 (local board time, UTC bus time) Both are UTC now: "Board scanned 18:01:02 UTC (4s ago)". Test matches ^Board scanned \d\d:\d\d:\d\d UTC.
Filbert N2 (strings counted across files) The strings test counts each string per file, with the expected count (No task ${txt(ref)} in this business. twice in bus.js; "Nothing is waiting on you." once in app.js and once in bus.js).
Filbert N3, round 1 pkback Unchanged: equivalent in Chromium; the listener stays for other browsers.
Darkwing note 3, M09 (focus offset) Test: #cmdk's outline-offset is 2px. Table rows keep their inset ring (-2px), unchanged and untested.
Darkwing note 4 (toast live region display:none before the first copy) The toast is always rendered; while off it is empty and visually hidden (clip-path: inset(50%), 1px box), so it stays in the accessibility tree. Test: before the first copy it is not display:none, is visible, is empty and is at most 1px.
Darkwing note 7, M16 (tabular numbers) Test: font-variant-numeric is tabular-nums on #fresh and #session-count.
Darkwing M07 (toast text escaping) Not added. The toast text is Copied: <command>, the commands come from validated refs, and toast() passes the text through esc().
Darkwing note 2 (freshness hidden below 760px) Unchanged. The command bar has no room at 360px, and the stale and refusal banners still state it in the view. Left for row 54 or the design session.
Darkwing note 5 (Seen 403 wording, prose in <code>) Unchanged. A Seen 403 now also drops the board data, the same as a read 403; since only control-board's Host/Origin check gives it, the read would be refused too.

Round 2 mutants

Each applied to a scratch copy of the round 2 webui, then shell.test.mjs run (mut/tools/mutants.py, run.sh). 20 of 20 killed.

Mutant Change Killed by
C1-keep-data a board 403 keeps data shell browser test
C1-keep-inspector a board 403 keeps selected and the inspector shell browser test
C1-keep-footer a board 403 leaves the footer shell browser test
C3-fresh-wording "stale" wording on a refusal shell browser test
C2-render-forget the view's refused read doesn't forget() five-states test
C2-pk-forget pkOpen's refused refetch doesn't forget() five-states test
C2-board-forget the board route's refused inbox read doesn't forget() five-states test
C2-forget-keeps-count forget() clears last but not the count five-states test
M01-refusal-403-only refusal() only for 403 five-states test
M08-pk-label-raw palette label set as HTML five-states test
nav-wraps the round 1 wrapping section list shell browser test (strip)
toast-display-none the round 1 display:none toast shell browser test
M09-offset-0 focus outline-offset: 0 shell browser test
M16-no-tabular no tabular-nums shell browser test
N1-local-time board time in local time shell browser test
N2-one-copy one copy of the duplicated task string changed strings test
T6-ctrl-alt-k Ctrl+Alt+K opens the palette shell browser test
T3-err-toast-hides the error toast closes after 2.6 s shell browser test
T2-stale-sticks accept() keeps failed shell browser test
bus-fresh-refused bus refusal shows the round 1 freshness wording shell browser test

Round 2 gate

Worktree at 2d4d4e7d plus the 16 round 2 files (sha256sum -c of the manifest: all OK), node_modules linked from the checkout, TMPDIR in scratch, suites sequential, 2026-10-10T18:15:17Z to 18:19:16Z. Outputs: ~/dewey-scratch/r53b/gate/out/.

Suite Result
node --test 'packages/webui/tests/*.test.mjs' 27 pass, 0 fail
node docs/design/tools/build-tokens.mjs --check rc 0, "tokens.css is current"
test-auth 15 passed, 0 failed
test-conductor 17 passed, 0 failed
test-config 24 passed, 0 failed
test-discord 66 passed, 0 failed
test-extension-package 18 passed, 0 failed
test-foundation 44 passed, 0 failed
test-queue 27 passed, 0 failed
test-release 14 passed, 0 failed
test-task, Docker present 98 passed, 0 failed

The round 1 packet follows unchanged, except where rounds 2 and 3 supersede it (the nav deviation is withdrawn; the manifest above replaces 75569953).

Round 1 packet

What changed

All in packages/webui. No new endpoint, read, write or dependency.

File Change
src/public/tokens.css New. Byte copy of docs/design/tokens.css.
src/public/icons.svg New. Byte copy of docs/design/icons.svg: 17 icons and the mark symbol.
src/public/shell.css New. The components on top of the tokens: command bar, freshness line, section list with icons, dense table with tabular numbers, inspector, status mark (glyph plus word), class chip and BLOCKING chip, copy command, toast, Ctrl+K palette, empty, not found, banners (err, ref), skeleton rows, @font-face for JetBrains Mono, --r 6px and --r-lg 10px use, focus rings, the 400px layout. Loaded last.
src/public/assets/fonts/jetbrains-mono-{400,500,700}.woff2, jetbrains-mono-OFL.txt, jetbrains-mono-sources.txt JetBrains Mono 2.304 from the official release archive (URL and archive sha256 in the sources file), each file's sha256, and the OFL text from the archive.
src/public/index.html Loads tokens.css after shared/app.css and shell.css last; no data-mode on <html>; the mark from the sprite in the wordmark; the Ctrl+K button, freshness line, System option, banner error element, board refusal empty state, board skeleton, toast and palette <dialog>.
src/public/app.js theme() sets no property inline: it sets data-palette, and data-mode unless the mode is System, which removes it. Default mode system. Same key mosaic-console-appearance. Status mark classes on the state badge. Empty states with "what would appear and where it comes from". Board freshness line. A 403 from the board fails closed (kept rows hidden, banner ref); any other failure keeps the rows with their scan time.
src/public/bus.js Class and BLOCKING chips, section icons, empty states, the skeleton loading view, not found that names the address and points to the Board and Ctrl K, freshness for bus pages, a refusal (403, not-configured, no-bus-host) never showing kept rows, the toast on copy, the Ctrl+K palette, ↑/↓ between task links.
src/serve.mjs The files map only: tokens.css, shell.css, icons.svg, the three mono fonts.
tests/shell.test.mjs New: copies, font digests, strings and the write boundary; two browser tests (below).
tests/serve.test.mjs The new static files are served with their types.
tests/browser.mjs ArrowUp and k key codes; no text insert when a modifier is held (so Ctrl+K doesn't type k).
README.md "Shell (row 53, #1542)" section; the stale/refusal rule; the verify lines.

Acceptance against the brief

Brief line Where it is checked
tokens.css served, theme() no inline properties serve.test (type); shell browser test: <html> has no style attribute; inline mutant fails
data-palette and data-mode stay; System removes data-mode; key kept; Harbor + System default shell browser test: fresh load has no data-mode, palette harbor, select system; dark and light OS give dark and light pages; picking dark sets it and stores it under mosaic-console-appearance; System removes it again. Mode mutant fails.
--r 6px, --r-lg 10px, type scale, tabular numbers computed --r and --r-lg asserted; type scale and font-variant-numeric: tabular-nums in shell.css
JetBrains Mono 400/500/700 with sha256 and OFL static test: shipped files equal the listed set, each digest matches, OFL heading present, @font-face for each weight; document.fonts.check in the browser
Icon sprite; mark in one component; Relay placeholder icons.svg copy test; the mark is drawn once, in the wordmark, from #mark
Components each rendered in the browser test and its screenshots: command bar, section list (4 icons asserted), dense table, inspector (opened by keyboard), status mark (◐ glyph asserted), class chip (code action, uppercase BLOCKING asserted), copy command and toast (success and clipboard-denied), palette, not found
Five states on Board, Inbox, Tasks, Agents, Trail second browser test walks each view through loading (a held read shows the skeleton), normal, error over kept rows (same row count under banner err s1-stale), refusal (banner ref, code in mono, no rows, "Nothing to show.") and back. Empty is seeded per view in the first test.
Browser: no script errors, no sideways scroll at 400px, keyboard ↑/↓, Enter, Esc returning focus, visible focus window.errors empty at the end of both tests; scrollWidth <= clientWidth after every state; board ↑/↓/Enter/Esc, task-link ↑/↓, palette ↑/↓/Enter/Esc with focus back on the opener; outline ≥2px on #cmdk, a section link and Refresh
String grep test static test: the kept strings from bus.js, app.js and index.html, no Resolve/Decide/Approve button, no non-GET method in bus.js, no script in the sprite
No writes both browser tests: every request the page made is a GET

Tests

Outputs in ~/dewey-scratch/r53/out/.

Run Result
node --test 'tests/*.test.mjs' in packages/webui, with WEBUI_EVIDENCE (full-4.txt) 27 pass, 0 fail
shell.test.mjs alone, three repeats after the last test change (shell-rep-{1,2,3}.txt) 5 pass each
Five-states test alone (states-1.txt) pass

Screenshots (out/evidence-4/, 400px unless named): shell-board-empty, -board-stale, -board-refused, -bus-empty-{inbox,tasks,agents,trail}, -bus-stale, -bus-refused, -bus-inbox, -palette, -toast, -bus-tasks-1440, -board-1440, and states-{board,inbox,tasks,agents,trail}-{loading,normal,stale,refused} (board: loading and normal). I looked at each.

Mutations

Each applied to a scratch copy of the candidate, then shell.test.mjs run (mutbin/run.sh, outputs out/mut-*.txt).

Mutant Result
tokens: a comment appended to the src/public/tokens.css copy fails: "tokens.css differs from docs/design/tokens.css"
inline: theme() sets one property inline again fails: <html> style is --canvas: #fff;
mode: System sets data-mode="light" instead of removing it fails: true !== false
refusal: drop if (refusal(err)) throw err (refusal shows kept rows) fails: no .banner.ref
focus: :focus-visible{outline:none} appended to shell.css fails: #cmdk focus ring
foot: the side column's top and height rule removed (760px and up) fails: column bottom 930 against 900
post: method: 'POST' in fetchRead fails: static check and the GET-only browser check
loading: no loading view on navigation fails: no #bus-view .skel-rows[aria-busy=true] (five-states test)
skel: no board skeleton fails: no #sessions .skel-rows[aria-busy=true] (five-states test)
pkback: drop the b.focus() in the palette's close listener survives: Chromium's native <dialog> close puts focus back on the opener, so the listener is redundant there and the mutant is equivalent in this browser. Kept for browsers that don't.

Gate

Worktree at 8a7871ff plus the 16 candidate files (sha256sum -c of the manifest: all OK), node_modules linked from the checkout, TMPDIR in scratch, suites sequential, 2026-10-10T17:24:05Z to 17:28:05Z. Outputs: ~/dewey-scratch/r53/gate/out/.

Suite Result
node --test 'packages/webui/tests/*.test.mjs' 27 pass, 0 fail
node docs/design/tools/build-tokens.mjs --check rc 0, "tokens.css is current"
test-auth 15 passed, 0 failed
test-conductor 17 passed, 0 failed
test-config 24 passed, 0 failed
test-discord 66 passed, 0 failed
test-extension-package 18 passed, 0 failed
test-foundation 44 passed, 0 failed
test-queue 27 passed, 0 failed (verify and render --check skip outside the canonical root)
test-release 14 passed, 0 failed
test-task, Docker present 98 passed, 0 failed

An earlier gate run on 3265383f with the manifest before the five-states test was also green (26 webui tests); it is superseded by this one.

Decisions and deviations from the design

  • Bus refusal fails closed. Before this row a 403 from the bus still showed kept rows under a stale banner. Now a refusal (403, not-configured, no-bus-host) shows only the failure: "Console shows nothing rather than a guess". Other failures keep the rows, labelled with their read time. The board does the same for its 403.
  • Footer is a sticky status bar from 760px. With the side column sticky under the command bar, a page taller than the viewport slid the column under the bar at 1440×900. The footer now sits at the bottom, the column fills the space between, and the browser test checks the edges on a short page and on a long one scrolled to the end.
  • Nav wraps below 760px (withdrawn in round 2: the section list is one strip that scrolls inside itself, per IMPLEMENTING.md) instead of scrolling sideways, and the command bar keeps Palette, Appearance, Refresh and Pause on one row down to 360px.
  • Manrope 800 renders as 700: only 400 to 700 are shipped (row 53 adds no Manrope weight). Visible on the large headings only.
  • No business pill in the command bar: there is one business and no business read yet; the Business view is row 54.
  • Not found keeps the existing string "No page at this address." and adds the address, a Board link and Ctrl K.
  • #copy-status lost role="status". The toast is the live region now; both announcing the same line would read it twice. The status line still shows the text.
  • Class chip colours use the design's four classes; an unknown class gets the plain chip.

Not done here (follow-ups)

  • Row 54 (#1543) adds Queue, Business and Settings and will reuse these components; both rows change app.js, so 54 starts after 53 lands.
  • The pkback mutant above: a test in a browser without native dialog focus restore would need another engine; none is in the repo.

Boundaries kept

Commits: none (Sage commits). No change outside packages/webui and this directory. docs/design/ read only. The Q14 frozen paths untouched. No request to the tracker from node. No new dependency. Scratch worktrees removed after use; core.hooksPath unset.