diff --git a/agents/dewey/work/queue-53/candidate-files.txt b/agents/dewey/work/queue-53/candidate-files.txt
new file mode 100644
index 00000000..52001987
--- /dev/null
+++ b/agents/dewey/work/queue-53/candidate-files.txt
@@ -0,0 +1,16 @@
+packages/webui/README.md
+packages/webui/src/public/app.js
+packages/webui/src/public/assets/fonts/jetbrains-mono-400.woff2
+packages/webui/src/public/assets/fonts/jetbrains-mono-500.woff2
+packages/webui/src/public/assets/fonts/jetbrains-mono-700.woff2
+packages/webui/src/public/assets/fonts/jetbrains-mono-OFL.txt
+packages/webui/src/public/assets/fonts/jetbrains-mono-sources.txt
+packages/webui/src/public/bus.js
+packages/webui/src/public/icons.svg
+packages/webui/src/public/index.html
+packages/webui/src/public/shell.css
+packages/webui/src/public/tokens.css
+packages/webui/src/serve.mjs
+packages/webui/tests/browser.mjs
+packages/webui/tests/serve.test.mjs
+packages/webui/tests/shell.test.mjs
diff --git a/agents/dewey/work/queue-53/evidence.md b/agents/dewey/work/queue-53/evidence.md
new file mode 100644
index 00000000..a044134d
--- /dev/null
+++ b/agents/dewey/work/queue-53/evidence.md
@@ -0,0 +1,348 @@
+# 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 `
`, `#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, `` 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 `Push?` and a task title `Bold 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: `, 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 ``) | 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 ``; 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 `