review(53): Darkwing round 2, request changes (#1542 comment 27165)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-10 14:05:29 -05:00
co-authored by Claude Opus 5.5
parent e2b4e6f5da
commit d277234237
100 changed files with 3894 additions and 0 deletions
@@ -0,0 +1,16 @@
2c1c65b477f03a9474ca13022eb511363ffef0b75b932d404a0098b7f838594a packages/webui/README.md
02bbcb68335e80f8822aeea44bc8be49532cd702b325c1ea09ffe95b361a121c packages/webui/src/public/app.js
a9cb1cd82332b23a47e3a1239d25d13c86d16c4220695e34b243effa999f45f2 packages/webui/src/public/assets/fonts/jetbrains-mono-400.woff2
086c48dfbea9ddaff1320f7e09399b8e2924e88ce67453721255db3bdbb5a353 packages/webui/src/public/assets/fonts/jetbrains-mono-500.woff2
c503cc5ec5f8b2c7666b7ecda1adf44bd45f2e6579b2eba0fc292150416588a2 packages/webui/src/public/assets/fonts/jetbrains-mono-700.woff2
30f0c136e3c88e422d0791acd97238870f9054a9729bc34cf2ff0d4ed8cac4ad packages/webui/src/public/assets/fonts/jetbrains-mono-OFL.txt
f6f89aaaa6a02e9a16020de26fb6a664f1da152e0b0413248da2d9f6b68395cc packages/webui/src/public/assets/fonts/jetbrains-mono-sources.txt
d8bc0fc7ad10fa188a6f68b925449476af4ca56ba343b24b089fb48cca296295 packages/webui/src/public/bus.js
01b456c1ca685e69e2974d66a615b7b8736abbd51aef7a3f014bca3783710430 packages/webui/src/public/icons.svg
fbbb51e5669a1f559015e0fdebf6f450b63383070472d2461f0bce9b9723d890 packages/webui/src/public/index.html
d7b4d0ca32f07a63265d89131847cbb72c7e532101e693b31b0f38839b22f7e6 packages/webui/src/public/shell.css
4fdb72ec24c7245584c89280415e10113dba1c4cb03d98f5570ac745ba2e95cb packages/webui/src/public/tokens.css
5a65fea95e6225f812c196b0199f3f6562a60e523da685b255f749f63b480c64 packages/webui/src/serve.mjs
6f68f67e99d5e832c0ba219911201b8adf08505e234647f2d9eac7292d55f50f packages/webui/tests/browser.mjs
713c86bd6b1d1b27949bbae071f1bdf1f7d744f84b723ad5a1bdbd1e8adc5fb6 packages/webui/tests/serve.test.mjs
c68acea3c81c1ac67347bae8139632c13dbcfe91207645aa545caa848887be05 packages/webui/tests/shell.test.mjs
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# Row 53 gate: packages/webui node suite, build-tokens --check, then every scripts/test-*.sh with Docker unreachable.
export TMPDIR=~/darkwing-scratch/r53b/tmp DOCKER_HOST=unix:///nonexistent.sock
cd ~/darkwing-scratch/r53b/cand
O=~/darkwing-scratch/r53b/out
: > $O/summary.txt
echo "start $(date -u +%FT%TZ)" >> $O/summary.txt
(cd packages/webui && env -u NODE_TEST_CONTEXT node --test 'tests/*.test.mjs') > $O/node-webui.txt 2>&1; e=$?
echo "node-webui exit=$e $(grep -E '^ℹ (pass|fail)' $O/node-webui.txt | tr '\n' ' ')" >> $O/summary.txt
node docs/design/tools/build-tokens.mjs --check > $O/build-tokens.txt 2>&1; e=$?
echo "build-tokens --check exit=$e $(tail -1 $O/build-tokens.txt)" >> $O/summary.txt
for f in scripts/test-*.sh; do
s=$(basename $f .sh)
$f > $O/$s.txt 2>&1; e=$?
echo "$s exit=$e $(grep -E 'passed, [0-9]+ failed' $O/$s.txt | tail -1)" >> $O/summary.txt
done
echo "end $(date -u +%FT%TZ)" >> $O/summary.txt
echo DONE >> $O/summary.txt
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..f0261468 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -368,7 +368,7 @@
}
const failTitle = err => err.status === 403 ? 'Bus refused the read' : ['not-configured', 'no-bus-host'].includes(err.code) ? 'No bus to read' : 'The read failed';
// A refusal fails closed: no kept data, the code in mono. Any other failure keeps the last read, labelled with its time.
- const refusal = err => err.status === 403 || ['not-configured', 'no-bus-host'].includes(err.code);
+ const refusal = err => err.status === 403;
// After a refusal nothing read before is shown anywhere: not in a view, the palette or the Inbox count.
function forget() { last.clear(); inboxCount = null; }
const failedView = err => `${head(failTitle(err))}<div class="banner ${refusal(err) ? 'ref' : 'err'}" role="alert"><p>${refusal(err) ? `<b>refused: <code>${txt(err.code)}</code>.</b> ` : ''}${why(err)} Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>${empty('Nothing to show.', refusal(err) ? 'The view stays empty until the problem above is fixed.' : 'Read again, or come back when the bus answers.')}`;
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5795.443344ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6268.860495ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8228.406461ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (1675.044775ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (122.850827ms)
✔ bus refusals map to statuses and never carry a path or a stack (41.87758ms)
✔ humanCall speaks the human transport protocol without blocking the server (1966.703581ms)
✔ busReader takes --business, else the live bus host, else refuses (202.62174ms)
✔ humanCall kills a transport that runs past its timeout (529.245643ms)
✔ serve refuses a --business value that is not a business id (191.893201ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4996.198675ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3473.451013ms)
✔ conversation view: a newer session with no readable history keeps the marker (2317.799437ms)
✔ conversation view: seats without history say so and offer no reply (1821.002855ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5805.992267ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56174.229792ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4942.660505ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23675.677859ms)
✔ loopback host and board origin fail closed (10.046586ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (124.822555ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (172.437494ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1879.898ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.519064ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.976932ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (4.470229ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7510.926876ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (7040.739548ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56775.652439
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (7040.739548ms)
Error: Uncaught (in promise) Error: Condition timed out: !!document.querySelector("#bus-view .banner.ref"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:378:9)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..836dfbc2 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -304,7 +304,7 @@
// everything drawn from it: the inspector, the project tree, the counts and the footer. Any other
// failure keeps the last data with its scan time.
function error(err) {
- const refused = err.status === 403;
+ const refused = false;
failed = refused ? 'refused' : true; $('error').hidden = false;
if (refused) {
data = null; selected = null; project = null;
@@ -0,0 +1,59 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5097.83917ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5306.446774ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7791.246228ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (398.914569ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (470.407043ms)
✔ bus refusals map to statuses and never carry a path or a stack (175.165999ms)
✔ humanCall speaks the human transport protocol without blocking the server (1816.384458ms)
✔ busReader takes --business, else the live bus host, else refuses (186.59124ms)
✔ humanCall kills a transport that runs past its timeout (528.932291ms)
✔ serve refuses a --business value that is not a business id (174.235634ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5245.016323ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3422.707286ms)
✔ conversation view: a newer session with no readable history keeps the marker (4796.44785ms)
✔ conversation view: seats without history say so and offer no reply (5298.920714ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5290.556185ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55325.794121ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4859.496965ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (25971.502398ms)
✔ loopback host and board origin fail closed (10.250345ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (134.111283ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (108.160287ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1137.333748ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.13576ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.175629ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.598383ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4929.714237ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3800.054159ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55800.902436
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4929.714237ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'banner err'
- 'banner ref'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:148:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'banner err',
expected: 'banner ref',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..4f2b2625 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -315,7 +315,7 @@
inspect(); convForm();
}
$('error').className = refused ? 'banner ref' : 'banner err';
- $('board-refused').hidden = !refused; document.body.classList.toggle('board-refused', refused);
+ $('board-refused').hidden = !refused;
$('error').innerHTML = refused
? `<p><b>The board refused the read.</b> refused: <code>${esc(err.message)}</code> Board: ${esc(board)}. Console shows nothing rather than a guess. Use Refresh to try again.</p>`
: `<p><b>${esc(err.message)}</b> Board: ${esc(board)}. ${data ? `Showing last known data.${data.generatedAt ? ` Scanned ${esc(data.generatedAt)}.` : ''}` : 'No board data loaded.'} Use Refresh to try again.</p>`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5712.8413ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6470.833098ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7936.181163ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (301.533225ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (494.643207ms)
✔ bus refusals map to statuses and never carry a path or a stack (231.746191ms)
✔ humanCall speaks the human transport protocol without blocking the server (2469.733966ms)
✔ busReader takes --business, else the live bus host, else refuses (652.021248ms)
✔ humanCall kills a transport that runs past its timeout (530.392475ms)
✔ serve refuses a --business value that is not a business id (165.143296ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (6095.538773ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2939.706831ms)
✔ conversation view: a newer session with no readable history keeps the marker (1245.419871ms)
✔ conversation view: seats without history say so and offer no reply (1025.221336ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (6516.684216ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56090.939503ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (6486.190874ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23562.619035ms)
✔ loopback host and board origin fail closed (9.883696ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (126.346727ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (111.624361ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1913.882882ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.422763ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.680236ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.722365ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6346.854574ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3733.402259ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56623.460421
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6346.854574ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'block' !== 'none'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:150:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'block',
expected: 'none',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..9bd147b6 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -371,7 +371,7 @@
const refusal = err => err.status === 403 || ['not-configured', 'no-bus-host'].includes(err.code);
// After a refusal nothing read before is shown anywhere: not in a view, the palette or the Inbox count.
function forget() { last.clear(); inboxCount = null; }
- const failedView = err => `${head(failTitle(err))}<div class="banner ${refusal(err) ? 'ref' : 'err'}" role="alert"><p>${refusal(err) ? `<b>refused: <code>${txt(err.code)}</code>.</b> ` : ''}${why(err)} Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>${empty('Nothing to show.', refusal(err) ? 'The view stays empty until the problem above is fixed.' : 'Read again, or come back when the bus answers.')}`;
+ const failedView = err => `${head(failTitle(err))}<div class="banner ${refusal(err) ? 'ref' : 'err'}" role="alert"><p>${refusal(err) ? `<b>refused: ${txt(err.code)}.</b> ` : ''}${why(err)} Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>${empty('Nothing to show.', refusal(err) ? 'The view stays empty until the problem above is fixed.' : 'Read again, or come back when the bus answers.')}`;
const staleBanner = err => `<div class="banner err s1-stale" role="alert"><p><b>The last read failed.</b> ${why(err)} This is what was read before, ${when(readAt)}. Nothing was changed.</p>${retry}</div>`;
// Freshness line in the command bar for bus pages.
const stamp = iso => { const t = Date.parse(iso); return Number.isFinite(t) ? `${new Date(t).toISOString().slice(11, 19)} UTC` : 'unknown'; };
@@ -0,0 +1,58 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5462.633703ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6016.227844ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7161.229825ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (459.662074ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (198.328355ms)
✔ bus refusals map to statuses and never carry a path or a stack (128.375723ms)
✔ humanCall speaks the human transport protocol without blocking the server (2011.124303ms)
✔ busReader takes --business, else the live bus host, else refuses (330.452814ms)
✔ humanCall kills a transport that runs past its timeout (529.363963ms)
✔ serve refuses a --business value that is not a business id (257.002802ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5921.250904ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2583.408385ms)
✔ conversation view: a newer session with no readable history keeps the marker (1776.121129ms)
✔ conversation view: seats without history say so and offer no reply (842.197628ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5884.809548ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56135.561582ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4993.429078ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24999.455651ms)
✔ loopback host and board origin fail closed (8.987038ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (151.065885ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (150.806081ms)
✔ unreachable board reports URL; CLI rejects unsupported options (858.291021ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.308606ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.824401ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.562959ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8401.194007ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (1483.639423ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56554.628294
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (1483.639423ms)
AssertionError [ERR_ASSERTION]: #/inbox
+ actual - expected
+ '--business'
- 'no-bus-host'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:379:16)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: '--business',
expected: 'no-bus-host',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..0737320a 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -377,7 +377,7 @@
const stamp = iso => { const t = Date.parse(iso); return Number.isFinite(t) ? `${new Date(t).toISOString().slice(11, 19)} UTC` : 'unknown'; };
function freshBus(state) {
$('fresh-bus').innerHTML = state === 'refused' ? 'Bus <span class="warn">not read: refused</span>' : state === 'failed' ? 'Bus <span class="warn">not read: the read failed</span>'
- : `Bus read <b>${esc(stamp(readAt))}</b>${readAt ? ` (${esc(ago(readAt))})` : ''}${state === 'stale' ? ' · <span class="warn">stale: the last read failed</span>' : ''}`;
+ : `Bus read <b>${esc(stamp(readAt))}</b>${readAt ? ` (${esc(ago(readAt))})` : ''}`;
}
// ---------------------------------------------------------------- router
@@ -0,0 +1,89 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (8079.031307ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (95685.374707ms)
✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (39939.301906ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (399.29347ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (107.942253ms)
✔ bus refusals map to statuses and never carry a path or a stack (183.540352ms)
✔ humanCall speaks the human transport protocol without blocking the server (2475.56703ms)
✔ busReader takes --business, else the live bus host, else refuses (210.045959ms)
✔ humanCall kills a transport that runs past its timeout (528.605198ms)
✔ serve refuses a --business value that is not a business id (196.905686ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (64786.967758ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (27245.695481ms)
✔ conversation view: a newer session with no readable history keeps the marker (6126.989522ms)
✔ conversation view: seats without history say so and offer no reply (5118.460015ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (9195.480901ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (88101.20953ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (16229.53364ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (46498.641699ms)
✔ loopback host and board origin fail closed (9.766364ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (204.824833ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (368.503988ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1135.758555ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.436508ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.838218ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.916424ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (25445.008415ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (12241.439812ms)
ℹ tests 27
ℹ suites 0
ℹ pass 24
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 103416.270276
✖ failing tests:
test at tests/bus-browser.test.mjs:68:1
✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (39939.301906ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'BODY' !== 'H1'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/bus-browser.test.mjs:124:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'BODY',
expected: 'H1',
operator: 'strictEqual',
diff: 'simple'
}
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (25445.008415ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /stale: the last read failed/. Input:
'Bus read 18:29:18 UTC (1 s ago)'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:221:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Bus read 18:29:18 UTC (1 s ago)',
expected: /stale: the last read failed/,
operator: 'match',
diff: 'simple'
}
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (12241.439812ms)
AssertionError [ERR_ASSERTION]: #/inbox
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:372:14)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'Bus read 18:29:28 UTC (1 s ago)',
expected: /stale: the last read failed/,
operator: 'match',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..7c8f15db 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -41,7 +41,7 @@
palette = p.id;
if (!['system', 'light', 'dim', 'dark'].includes(mode)) mode = 'system';
document.documentElement.dataset.palette = palette;
- if (mode === 'system') delete document.documentElement.dataset.mode; else document.documentElement.dataset.mode = mode;
+ document.documentElement.dataset.mode = mode;
$('palette').value = palette; $('mode').value = mode;
try { localStorage.setItem('mosaic-console-appearance', JSON.stringify({ palette, mode })); } catch {}
}
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (15272.623465ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (31722.087198ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (30330.451829ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (2416.562027ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (37.29549ms)
✔ bus refusals map to statuses and never carry a path or a stack (18.416107ms)
✔ humanCall speaks the human transport protocol without blocking the server (1676.411784ms)
✔ busReader takes --business, else the live bus host, else refuses (250.959993ms)
✔ humanCall kills a transport that runs past its timeout (527.099896ms)
✔ serve refuses a --business value that is not a business id (180.793544ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (28900.406029ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4313.697934ms)
✔ conversation view: a newer session with no readable history keeps the marker (2351.575056ms)
✔ conversation view: seats without history say so and offer no reply (2446.325824ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (10421.525061ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (73032.592659ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (12366.957176ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (33923.140487ms)
✔ loopback host and board origin fail closed (8.196194ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (445.360351ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (152.315148ms)
✔ unreachable board reports URL; CLI rejects unsupported options (923.203685ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.929341ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.043473ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.771135ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5708.723686ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (19111.162225ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 73383.749377
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5708.723686ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
true !== false
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:108:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..c6273452 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -481,7 +481,7 @@
const t = $('toast'); clearTimeout(toastTimer);
if (text === null) { t.className = 'toast'; t.textContent = ''; return; }
t.className = `toast on${error ? ' err' : ''}`;
- t.innerHTML = `<span>${esc(text)}</span>${error ? '<button type="button" data-dismiss>Dismiss</button>' : ''}`;
+ t.innerHTML = `<span>${text}</span>${error ? '<button type="button" data-dismiss>Dismiss</button>' : ''}`;
if (!error) toastTimer = setTimeout(() => toast(null), 2600);
}
@@ -0,0 +1,36 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (9677.560125ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5238.868632ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (11020.061708ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (412.713989ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (210.605785ms)
✔ bus refusals map to statuses and never carry a path or a stack (68.144102ms)
✔ humanCall speaks the human transport protocol without blocking the server (2938.448863ms)
✔ busReader takes --business, else the live bus host, else refuses (506.983539ms)
✔ humanCall kills a transport that runs past its timeout (567.330888ms)
✔ serve refuses a --business value that is not a business id (810.105696ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (7229.729607ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (8811.289186ms)
✔ conversation view: a newer session with no readable history keeps the marker (4399.608573ms)
✔ conversation view: seats without history say so and offer no reply (1755.37456ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (7441.830627ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (58785.923958ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (9615.513932ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24892.390944ms)
✔ loopback host and board origin fail closed (12.008815ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (182.542287ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (212.873578ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1994.917068ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.662111ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.799647ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (3.174412ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (13373.047364ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (6417.896432ms)
ℹ tests 27
ℹ suites 0
ℹ pass 27
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 59334.66732
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..846b6f36 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -500,7 +500,7 @@
const q = $('cmdk-input').value.trim().toLowerCase();
pkShown = pkAll.filter(i => !q || `${i.label} ${i.group}`.toLowerCase().includes(q)).slice(0, 50);
pkAt = Math.min(pkAt, Math.max(0, pkShown.length - 1));
- $('cmdk-list').innerHTML = pkShown.length ? pkShown.map((i, n) => `<li role="option" id="cmdk-opt-${n}" data-href="${esc(i.href)}" aria-selected="${n === pkAt}"><span>${txt(i.label)}</span><span class="g">${txt(i.group)}</span></li>`).join('')
+ $('cmdk-list').innerHTML = pkShown.length ? pkShown.map((i, n) => `<li role="option" id="cmdk-opt-${n}" data-href="${esc(i.href)}" aria-selected="${n === pkAt}"><span>${i.label}</span><span class="g">${txt(i.group)}</span></li>`).join('')
: '<li class="pk-none" role="option" aria-disabled="true" aria-selected="false">No view, decision or task matches.</li>';
if (pkShown.length) { $('cmdk-input').setAttribute('aria-activedescendant', `cmdk-opt-${pkAt}`); $(`cmdk-opt-${pkAt}`)?.scrollIntoView({ block: 'nearest' }); }
else $('cmdk-input').removeAttribute('aria-activedescendant');
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (6262.461685ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (8323.877976ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (11564.402673ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (2264.208911ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (86.487989ms)
✔ bus refusals map to statuses and never carry a path or a stack (69.631861ms)
✔ humanCall speaks the human transport protocol without blocking the server (3169.117616ms)
✔ busReader takes --business, else the live bus host, else refuses (343.785283ms)
✔ humanCall kills a transport that runs past its timeout (532.902486ms)
✔ serve refuses a --business value that is not a business id (400.71332ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (6321.331007ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4518.74495ms)
✔ conversation view: a newer session with no readable history keeps the marker (2848.536998ms)
✔ conversation view: seats without history say so and offer no reply (1705.987269ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (6589.386693ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (60212.056998ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5627.527147ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24935.535973ms)
✔ loopback host and board origin fail closed (10.838303ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (707.851249ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (335.465899ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1717.022266ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.259551ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.791564ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (3.433592ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (9944.951011ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (3695.850599ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 60869.027066
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (3695.850599ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:413:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 2,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css
index ed6d2d96..099ad74c 100644
--- a/packages/webui/src/public/shell.css
+++ b/packages/webui/src/public/shell.css
@@ -18,7 +18,7 @@ body{font:400 14px/1.5 var(--font);color:var(--text);background:var(--canvas)}
code,kbd,pre,.mono{font-family:var(--mono);font-size:12.5px}
kbd{border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:0 .35em;background:var(--surface);color:var(--strong);white-space:nowrap;font-size:11.5px}
.count,td,.s1-twhen,time,.fresh,.kv dd{font-variant-numeric:tabular-nums}
-:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
+:focus-visible{outline:2px solid var(--focus);outline-offset:0}
table.sessions tbody tr:focus-visible,table.s1-table tbody tr:focus-visible{outline:2px solid var(--focus);outline-offset:-2px}
svg.i{width:18px;height:18px;flex:none;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (7609.074415ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (11452.724025ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (12309.820512ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (2304.099495ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (94.109243ms)
✔ bus refusals map to statuses and never carry a path or a stack (42.272421ms)
✔ humanCall speaks the human transport protocol without blocking the server (2371.801182ms)
✔ busReader takes --business, else the live bus host, else refuses (410.928224ms)
✔ humanCall kills a transport that runs past its timeout (531.203001ms)
✔ serve refuses a --business value that is not a business id (306.449847ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (7788.383155ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4662.276458ms)
✔ conversation view: a newer session with no readable history keeps the marker (1912.219392ms)
✔ conversation view: seats without history say so and offer no reply (1496.764289ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (8619.000837ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (59599.785751ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (7467.265369ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (25221.11973ms)
✔ loopback host and board origin fail closed (11.544617ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (1719.852203ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (185.65663ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1065.79537ms)
✔ tokens.css and icons.svg are byte copies of docs/design (6.317784ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (14.868056ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (5.161292ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5535.667757ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (6509.811784ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 60160.369576
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5535.667757ms)
AssertionError [ERR_ASSERTION]: #cmdk outline offset
0 !== 2
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:181:125)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 0,
expected: 2,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css
index ed6d2d96..cc401c3b 100644
--- a/packages/webui/src/public/shell.css
+++ b/packages/webui/src/public/shell.css
@@ -18,7 +18,7 @@ body{font:400 14px/1.5 var(--font);color:var(--text);background:var(--canvas)}
code,kbd,pre,.mono{font-family:var(--mono);font-size:12.5px}
kbd{border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:0 .35em;background:var(--surface);color:var(--strong);white-space:nowrap;font-size:11.5px}
.count,td,.s1-twhen,time,.fresh,.kv dd{font-variant-numeric:tabular-nums}
-:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
+:focus-visible{outline:1px solid var(--focus);outline-offset:2px}
table.sessions tbody tr:focus-visible,table.s1-table tbody tr:focus-visible{outline:2px solid var(--focus);outline-offset:-2px}
svg.i{width:18px;height:18px;flex:none;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
@@ -0,0 +1,53 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (10029.216ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (11567.747539ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (12394.616919ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (2135.731454ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (106.029912ms)
✔ bus refusals map to statuses and never carry a path or a stack (43.317817ms)
✔ humanCall speaks the human transport protocol without blocking the server (2315.691295ms)
✔ busReader takes --business, else the live bus host, else refuses (428.29625ms)
✔ humanCall kills a transport that runs past its timeout (532.121068ms)
✔ serve refuses a --business value that is not a business id (200.505373ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (10797.128621ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3575.186349ms)
✔ conversation view: a newer session with no readable history keeps the marker (1850.44415ms)
✔ conversation view: seats without history say so and offer no reply (1664.190106ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (8184.327825ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (59155.373445ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (9016.166527ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (27198.316759ms)
✔ loopback host and board origin fail closed (10.638221ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (1333.045642ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (516.069031ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1227.942831ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.552045ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.997839ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (3.263111ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8865.020042ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (5262.681363ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 59903.296319
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8865.020042ms)
AssertionError [ERR_ASSERTION]: #cmdk outline 1px
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:181:69)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: '==',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css
index ed6d2d96..a07327e6 100644
--- a/packages/webui/src/public/shell.css
+++ b/packages/webui/src/public/shell.css
@@ -166,7 +166,7 @@ body.on-bus #board-refused{display:none}
.cmd-right label{flex:1 1 0;min-width:0}
.cmd-right .btn{flex:none}
/* The section list is one strip that scrolls inside itself (IMPLEMENTING.md), so more sections never add rows. */
- .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:auto;flex-basis:100%;min-width:0;max-width:100%;gap:2px;padding-bottom:2px;scrollbar-width:thin}
+ .s1-sections{display:flex;flex-wrap:wrap;overflow-x:visible;flex-basis:100%;min-width:0;max-width:100%;gap:2px;padding-bottom:2px;scrollbar-width:thin}
.s1-sections>li{flex:none}
.s1-section{border-radius:var(--r);padding:5px 8px;white-space:nowrap;flex:none}
.inspector{inset:0;top:0;width:100%;z-index:40;box-shadow:none}
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (6072.849226ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (7648.73759ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (10575.99292ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (2314.72617ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (71.737636ms)
✔ bus refusals map to statuses and never carry a path or a stack (26.592189ms)
✔ humanCall speaks the human transport protocol without blocking the server (1969.155253ms)
✔ busReader takes --business, else the live bus host, else refuses (346.614032ms)
✔ humanCall kills a transport that runs past its timeout (529.001693ms)
✔ serve refuses a --business value that is not a business id (204.313294ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5650.641432ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4927.264395ms)
✔ conversation view: a newer session with no readable history keeps the marker (3456.642762ms)
✔ conversation view: seats without history say so and offer no reply (3245.69597ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (6179.814923ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56099.101119ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (6232.410476ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24265.087834ms)
✔ loopback host and board origin fail closed (11.345162ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (383.542915ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (858.113627ms)
✔ unreachable board reports URL; CLI rejects unsupported options (926.123488ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.490977ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.558934ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (4.898223ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6025.485868ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (5394.399273ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56657.087449
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6025.485868ms)
AssertionError [ERR_ASSERTION]: one row at 360px
2 !== 1
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:204:14)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..046ff49e 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -209,7 +209,7 @@
const items = [['/', 'Board', '', 'board'], ['/inbox', 'Inbox', inboxCount ? `<span class="count" aria-label="${inboxCount.open} open for you">${inboxCount.open}</span>${inboxCount.blocking ? ` ${blockChip(`${inboxCount.blocking} blocking`)}` : ''}` : '', 'inbox'], ['/tasks', 'Tasks', '', 'tasks'], ['/agents', 'Agents', '', 'agents']];
$('sections').innerHTML = items.map(([h, l, extra, icon]) => {
const cur = h === '/' ? route === '/' : route.startsWith(h) || (h === '/tasks' && route.startsWith('/trail/task')) || (h === '/inbox' && route.startsWith('/trail/decision'));
- return `<li><a href="#${h}" class="s1-section"${cur ? ' aria-current="page"' : ''}><svg class="i" aria-hidden="true"><use href="/icons.svg#i-${icon}"/></svg>${l}${extra ? ` ${extra}` : ''}</a></li>`;
+ return `<li><a href="#${h}" class="s1-section"${cur ? ' aria-current="page"' : ''}>${l}${extra ? ` ${extra}` : ''}</a></li>`;
}).join('');
}
function countInbox(rows) { inboxCount = { open: rows.length, blocking: rows.filter(d => d.blocking).length }; }
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5401.316742ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5276.112592ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8044.16258ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (1584.166767ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (105.247657ms)
✔ bus refusals map to statuses and never carry a path or a stack (53.793812ms)
✔ humanCall speaks the human transport protocol without blocking the server (2250.629516ms)
✔ busReader takes --business, else the live bus host, else refuses (269.127594ms)
✔ humanCall kills a transport that runs past its timeout (530.269905ms)
✔ serve refuses a --business value that is not a business id (219.283891ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4958.475554ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3630.443988ms)
✔ conversation view: a newer session with no readable history keeps the marker (1294.07507ms)
✔ conversation view: seats without history say so and offer no reply (1115.599978ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5574.722064ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56570.885372ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5176.575671ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23510.05973ms)
✔ loopback host and board origin fail closed (10.17656ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (137.753736ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (88.553236ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1859.386694ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.686138ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.846105ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (4.440777ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5245.349397ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3503.212069ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 57151.799923
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5245.349397ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 4
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:196:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 4,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..39477043 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -296,7 +296,6 @@
function accept(value) {
if (!value || !Array.isArray(value.sessions) || value.sessions.some(r => !r || typeof r.project !== 'string' || typeof r.agent !== 'string')) throw new Error('Invalid board session data');
data = value; failed = false; $('error').hidden = true; $('error').className = 'banner err';
- $('board-refused').hidden = true; document.body.classList.remove('board-refused');
for (const id of awaiting.keys()) { const r = row(id); if (r && !pending(r)) awaiting.delete(id); }
render();
}
@@ -0,0 +1,55 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5471.314194ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (7878.85227ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8907.311012ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (944.751298ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (84.28557ms)
✔ bus refusals map to statuses and never carry a path or a stack (63.12128ms)
✔ humanCall speaks the human transport protocol without blocking the server (2026.814322ms)
✔ busReader takes --business, else the live bus host, else refuses (240.833469ms)
✔ humanCall kills a transport that runs past its timeout (527.244614ms)
✔ serve refuses a --business value that is not a business id (169.201231ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5690.430019ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3845.901684ms)
✔ conversation view: a newer session with no readable history keeps the marker (1522.881981ms)
✔ conversation view: seats without history say so and offer no reply (957.590092ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5842.696786ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56214.274595ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5702.561978ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (26371.006181ms)
✔ loopback host and board origin fail closed (7.080199ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (125.853509ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (99.128202ms)
✔ unreachable board reports URL; CLI rejects unsupported options (957.323889ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.322481ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.617784ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.454045ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5828.413235ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (5116.480007ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56613.263322
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5828.413235ms)
AssertionError [ERR_ASSERTION]: Expected "actual" to be strictly unequal to:
'none'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:161:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'none',
expected: 'none',
operator: 'notStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..4eb91e82 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -532,7 +532,7 @@
const a = e.target.closest?.('#bus-view table.s1-table td.s1-title a');
if (!a || (e.key !== 'ArrowDown' && e.key !== 'ArrowUp')) return;
const links = [...a.closest('table').querySelectorAll('td.s1-title a')], i = links.indexOf(a);
- e.preventDefault(); links[i + (e.key === 'ArrowDown' ? 1 : -1)]?.focus();
+ e.preventDefault(); links[i + (e.key === 'ArrowDown' ? -1 : 1)]?.focus();
});
window.addEventListener('hashchange', () => render(true));
$('refresh').addEventListener('click', () => render(false));
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (6928.118795ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (8209.259569ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (12049.913654ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (2984.527328ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (45.725005ms)
✔ bus refusals map to statuses and never carry a path or a stack (25.241585ms)
✔ humanCall speaks the human transport protocol without blocking the server (1825.996919ms)
✔ busReader takes --business, else the live bus host, else refuses (694.772009ms)
✔ humanCall kills a transport that runs past its timeout (528.374368ms)
✔ serve refuses a --business value that is not a business id (184.930858ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (7523.995068ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4770.585661ms)
✔ conversation view: a newer session with no readable history keeps the marker (1243.339041ms)
✔ conversation view: seats without history say so and offer no reply (1088.351142ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (7949.559455ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56700.906036ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (7259.807748ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23891.089464ms)
✔ loopback host and board origin fail closed (7.728754ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (866.179772ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (126.918795ms)
✔ unreachable board reports URL; CLI rejects unsupported options (575.777593ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.024718ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.983577ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.447936ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7103.550163ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (5312.733709ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 57103.536658
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7103.550163ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /Second task/. Input:
'#7 Kept task'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:224:38)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: '#7 Kept task',
expected: /Second task/,
operator: 'match',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/serve.mjs b/packages/webui/src/serve.mjs
index 280a228f..93a1958f 100644
--- a/packages/webui/src/serve.mjs
+++ b/packages/webui/src/serve.mjs
@@ -22,7 +22,7 @@ const files = new Map([
['/', ['index.html', 'text/html; charset=utf-8']],
...['app.js', 'brand.js', 'bus.js'].map(f => ['/' + f, [f, 'text/javascript; charset=utf-8']]),
...['shared/app.css', 'tokens.css', 'console.css', 'live.css', 'bus.css', 'shell.css'].map(f => ['/' + f, [f, 'text/css; charset=utf-8']]),
- ['/icons.svg', ['icons.svg', 'image/svg+xml']],
+ ['/icons.svg', ['icons.svg', 'text/plain']],
...[400, 500, 600, 700].map(w => [`/assets/fonts/manrope-${w}.woff2`, [`assets/fonts/manrope-${w}.woff2`, 'font/woff2']]),
...[400, 500, 700].map(w => [`/assets/fonts/jetbrains-mono-${w}.woff2`, [`assets/fonts/jetbrains-mono-${w}.woff2`, 'font/woff2']]),
]);
@@ -0,0 +1,53 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5776.919786ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5899.647375ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7817.95423ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (304.00469ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (92.68169ms)
✔ bus refusals map to statuses and never carry a path or a stack (59.725948ms)
✔ humanCall speaks the human transport protocol without blocking the server (2162.640231ms)
✔ busReader takes --business, else the live bus host, else refuses (417.956067ms)
✔ humanCall kills a transport that runs past its timeout (529.93184ms)
✔ serve refuses a --business value that is not a business id (154.497633ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5253.517004ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3024.904581ms)
✔ conversation view: a newer session with no readable history keeps the marker (1715.174597ms)
✔ conversation view: seats without history say so and offer no reply (1329.071054ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5838.646156ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56097.068788ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5288.871253ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (24117.940082ms)
✔ loopback host and board origin fail closed (9.101418ms)
✖ real board fixture passes through WebUI; assets and isolated seen/reply work (100.398615ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (96.109877ms)
✔ unreachable board reports URL; CLI rejects unsupported options (879.497882ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.300521ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.702869ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.21885ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8260.236526ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3414.935311ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56499.507114
✖ failing tests:
test at tests/serve.test.mjs:22:1
✖ real board fixture passes through WebUI; assets and isolated seen/reply work (100.398615ms)
AssertionError [ERR_ASSERTION]: /icons.svg
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/serve.test.mjs:41:101)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: '==',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css
index ed6d2d96..7fb39c59 100644
--- a/packages/webui/src/public/shell.css
+++ b/packages/webui/src/public/shell.css
@@ -17,7 +17,7 @@ body{font:400 14px/1.5 var(--font);color:var(--text);background:var(--canvas)}
.tree-title,.label{font:700 11px/1.3 var(--font);letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
code,kbd,pre,.mono{font-family:var(--mono);font-size:12.5px}
kbd{border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:0 .35em;background:var(--surface);color:var(--strong);white-space:nowrap;font-size:11.5px}
-.count,td,.s1-twhen,time,.fresh,.kv dd{font-variant-numeric:tabular-nums}
+
:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
table.sessions tbody tr:focus-visible,table.s1-table tbody tr:focus-visible{outline:2px solid var(--focus);outline-offset:-2px}
svg.i{width:18px;height:18px;flex:none;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
@@ -0,0 +1,58 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (7032.157582ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5974.415759ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8270.130573ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (480.132084ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (215.284099ms)
✔ bus refusals map to statuses and never carry a path or a stack (202.050789ms)
✔ humanCall speaks the human transport protocol without blocking the server (2541.451512ms)
✔ busReader takes --business, else the live bus host, else refuses (286.306148ms)
✔ humanCall kills a transport that runs past its timeout (528.640541ms)
✔ serve refuses a --business value that is not a business id (250.072087ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5934.362349ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3261.510465ms)
✔ conversation view: a newer session with no readable history keeps the marker (1883.827199ms)
✔ conversation view: seats without history say so and offer no reply (1497.152757ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (6942.276403ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (57525.879316ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (6010.051378ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22782.377069ms)
✔ loopback host and board origin fail closed (9.765723ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (126.74125ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (141.695296ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1790.575205ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.988386ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.255569ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.41268ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6915.352548ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3708.441788ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 58096.23649
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6915.352548ms)
AssertionError [ERR_ASSERTION]: #fresh
+ actual - expected
+ 'normal'
- 'tabular-nums'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:197:60)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'normal',
expected: 'tabular-nums',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..8ccceb1f 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -43,7 +43,7 @@
document.documentElement.dataset.palette = palette;
if (mode === 'system') delete document.documentElement.dataset.mode; else document.documentElement.dataset.mode = mode;
$('palette').value = palette; $('mode').value = mode;
- try { localStorage.setItem('mosaic-console-appearance', JSON.stringify({ palette, mode })); } catch {}
+ try { localStorage.setItem('mosaic-console-look', JSON.stringify({ palette, mode })); } catch {}
}
$('palette').innerHTML = Brand.palettes.map(p => `<option value="${p.id}">${p.name}</option>`).join('');
try { const saved = JSON.parse(localStorage.getItem('mosaic-console-appearance')); if (saved) { palette = saved.palette; mode = saved.mode; } } catch {}
@@ -0,0 +1,48 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (7219.196692ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (7659.592024ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8632.286954ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (282.571341ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (122.635688ms)
✔ bus refusals map to statuses and never carry a path or a stack (75.618193ms)
✔ humanCall speaks the human transport protocol without blocking the server (2211.018612ms)
✔ busReader takes --business, else the live bus host, else refuses (397.540081ms)
✔ humanCall kills a transport that runs past its timeout (566.728797ms)
✔ serve refuses a --business value that is not a business id (174.980802ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (7757.890315ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2813.782157ms)
✔ conversation view: a newer session with no readable history keeps the marker (1649.062025ms)
✔ conversation view: seats without history say so and offer no reply (1061.913097ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (7800.58257ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (57377.700776ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (7773.93166ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (25903.694289ms)
✔ loopback host and board origin fail closed (10.020241ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (131.068377ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (140.661963ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1249.439995ms)
✔ tokens.css and icons.svg are byte copies of docs/design (2.059447ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.730261ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.702814ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7911.76887ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3765.63761ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 57861.215013
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7911.76887ms)
Error: Uncaught: TypeError: Cannot read properties of null (reading 'mode')
at <anonymous>:1:62
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:121:18)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css
index ed6d2d96..9b515c06 100644
--- a/packages/webui/src/public/shell.css
+++ b/packages/webui/src/public/shell.css
@@ -97,7 +97,7 @@ table.s1-table td code{font-size:12.5px;white-space:nowrap}
.skel{display:block;height:12px;border-radius:4px;background:var(--raised);animation:skel 1.4s ease-in-out infinite}
.skel:nth-child(odd){width:82%}
@keyframes skel{50%{opacity:.45}}
-body.board-refused #board-view{display:none}
+
body.on-bus #board-refused{display:none}
/* Toast: one line, polite, bottom centre. */
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3927.278203ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4657.75327ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5524.823411ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (257.569233ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (197.475849ms)
✔ bus refusals map to statuses and never carry a path or a stack (62.384222ms)
✔ humanCall speaks the human transport protocol without blocking the server (2352.562117ms)
✔ busReader takes --business, else the live bus host, else refuses (180.530201ms)
✔ humanCall kills a transport that runs past its timeout (533.606041ms)
✔ serve refuses a --business value that is not a business id (165.095593ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4242.583993ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2828.148222ms)
✔ conversation view: a newer session with no readable history keeps the marker (1663.099588ms)
✔ conversation view: seats without history say so and offer no reply (1125.53899ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4488.22519ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53962.334042ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4220.745217ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23229.025406ms)
✔ loopback host and board origin fail closed (11.407687ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (184.096852ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (295.763247ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1230.782963ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.468688ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.116895ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (4.445183ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4761.914043ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3437.488223ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54409.271455
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4761.914043ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'block' !== 'none'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:150:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'block',
expected: 'none',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..de1a56b8 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -433,7 +433,7 @@
try { html = await view(live, path, new URLSearchParams(query)); readAt = live.oldest(); }
catch (err) {
const kept = reader(false);
- try { if (refusal(err)) throw err; html = await view(kept, path, new URLSearchParams(query)); readAt = kept.oldest(); html = staleBanner(err) + html; state = 'stale'; }
+ try { html = await view(kept, path, new URLSearchParams(query)); readAt = kept.oldest(); html = staleBanner(err) + html; state = 'stale'; }
catch { html = failedView(err); state = refusal(err) ? 'refused' : 'failed'; }
if (refusal(err)) forget();
}
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4453.411945ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5264.477386ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6343.176422ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (555.570309ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (154.111616ms)
✔ bus refusals map to statuses and never carry a path or a stack (80.416177ms)
✔ humanCall speaks the human transport protocol without blocking the server (2073.643449ms)
✔ busReader takes --business, else the live bus host, else refuses (267.731498ms)
✔ humanCall kills a transport that runs past its timeout (530.094794ms)
✔ serve refuses a --business value that is not a business id (169.493923ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4369.121592ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2242.808825ms)
✔ conversation view: a newer session with no readable history keeps the marker (1227.780012ms)
✔ conversation view: seats without history say so and offer no reply (917.943994ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4861.316701ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55068.800905ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4414.74079ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22879.258682ms)
✔ loopback host and board origin fail closed (8.430657ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (122.99485ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (126.993216ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1042.823066ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.949745ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.846898ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.237079ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8600.36373ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (5870.56617ms)
ℹ tests 27
ℹ suites 0
ℹ pass 25
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55530.663629
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8600.36373ms)
Error: Uncaught (in promise) Error: Condition timed out: !!document.querySelector("#bus-view .banner.ref"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:230:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (5870.56617ms)
Error: Uncaught (in promise) Error: Condition timed out: !!document.querySelector("#bus-view .banner.ref"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:378:9)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..e9f550a1 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -314,7 +314,7 @@
$('status').textContent = 'Not read: the board refused the read';
inspect(); convForm();
}
- $('error').className = refused ? 'banner ref' : 'banner err';
+ $('error').className = 'banner err';
$('board-refused').hidden = !refused; document.body.classList.toggle('board-refused', refused);
$('error').innerHTML = refused
? `<p><b>The board refused the read.</b> refused: <code>${esc(err.message)}</code> Board: ${esc(board)}. Console shows nothing rather than a guess. Use Refresh to try again.</p>`
@@ -0,0 +1,59 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3790.126485ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4070.339367ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6162.782857ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (326.590311ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (396.862759ms)
✔ bus refusals map to statuses and never carry a path or a stack (110.608731ms)
✔ humanCall speaks the human transport protocol without blocking the server (2385.844223ms)
✔ busReader takes --business, else the live bus host, else refuses (254.94543ms)
✔ humanCall kills a transport that runs past its timeout (529.223498ms)
✔ serve refuses a --business value that is not a business id (220.172057ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4138.868392ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2853.917903ms)
✔ conversation view: a newer session with no readable history keeps the marker (1258.099466ms)
✔ conversation view: seats without history say so and offer no reply (812.16505ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4183.301896ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54639.801943ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4094.058594ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23424.360921ms)
✔ loopback host and board origin fail closed (16.59858ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (188.870931ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (278.788458ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1419.616783ms)
✔ tokens.css and icons.svg are byte copies of docs/design (4.12186ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.602968ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (5.209893ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4052.812383ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2860.652705ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55419.923852
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4052.812383ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'banner err'
- 'banner ref'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:148:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'banner err',
expected: 'banner ref',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..dc273736 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -370,7 +370,7 @@
// A refusal fails closed: no kept data, the code in mono. Any other failure keeps the last read, labelled with its time.
const refusal = err => err.status === 403 || ['not-configured', 'no-bus-host'].includes(err.code);
// After a refusal nothing read before is shown anywhere: not in a view, the palette or the Inbox count.
- function forget() { last.clear(); inboxCount = null; }
+ function forget() {}
const failedView = err => `${head(failTitle(err))}<div class="banner ${refusal(err) ? 'ref' : 'err'}" role="alert"><p>${refusal(err) ? `<b>refused: <code>${txt(err.code)}</code>.</b> ` : ''}${why(err)} Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>${empty('Nothing to show.', refusal(err) ? 'The view stays empty until the problem above is fixed.' : 'Read again, or come back when the bus answers.')}`;
const staleBanner = err => `<div class="banner err s1-stale" role="alert"><p><b>The last read failed.</b> ${why(err)} This is what was read before, ${when(readAt)}. Nothing was changed.</p>${retry}</div>`;
// Freshness line in the command bar for bus pages.
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3567.402844ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4006.199314ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5272.110688ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (304.233276ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (107.385725ms)
✔ bus refusals map to statuses and never carry a path or a stack (58.355469ms)
✔ humanCall speaks the human transport protocol without blocking the server (2413.175616ms)
✔ busReader takes --business, else the live bus host, else refuses (143.018572ms)
✔ humanCall kills a transport that runs past its timeout (529.454661ms)
✔ serve refuses a --business value that is not a business id (225.520972ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3440.64227ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2063.812556ms)
✔ conversation view: a newer session with no readable history keeps the marker (1146.976215ms)
✔ conversation view: seats without history say so and offer no reply (996.933872ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3432.385645ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53753.61663ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3517.316598ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22592.030121ms)
✔ loopback host and board origin fail closed (9.548699ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (116.171793ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (100.990811ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1426.440429ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.085029ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.918929ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.183322ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6763.556991ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (1226.800369ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54139.811347
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (1226.800369ms)
AssertionError [ERR_ASSERTION]: #/inbox drops the Inbox count on a human-required refusal
2 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:383:16)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 2,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..ac5ee574 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -423,7 +423,7 @@
// The board page keeps its own title; the Inbox count still follows the bus.
if (shown !== null) { shown = null; $('bus-view').innerHTML = ''; document.title = 'Mosaic Console'; if (navigated) $('main').focus(); }
try { const r = await fetchRead('inbox'); last.set('inbox', r); if (g === gen) { countInbox(r.rows); sections(path); } }
- catch (err) { if (refusal(err)) { forget(); if (g === gen) sections(path); } }
+ catch {}
if (g === gen) schedule();
return;
}
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3839.150823ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4398.773937ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5161.938687ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (257.233571ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (75.691859ms)
✔ bus refusals map to statuses and never carry a path or a stack (70.807746ms)
✔ humanCall speaks the human transport protocol without blocking the server (2116.92695ms)
✔ busReader takes --business, else the live bus host, else refuses (193.29717ms)
✔ humanCall kills a transport that runs past its timeout (549.215635ms)
✔ serve refuses a --business value that is not a business id (175.964255ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4205.85625ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2062.291696ms)
✔ conversation view: a newer session with no readable history keeps the marker (1227.269696ms)
✔ conversation view: seats without history say so and offer no reply (1221.050447ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4210.391938ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53870.242716ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3848.544339ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22700.192342ms)
✔ loopback host and board origin fail closed (8.406871ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (124.60245ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (103.540624ms)
✔ unreachable board reports URL; CLI rejects unsupported options (893.165438ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.457484ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.707206ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.687255ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7388.987757ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (7624.873854ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54287.706116
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (7624.873854ms)
Error: Uncaught (in promise) Error: Condition timed out: !document.querySelector("#sections .count"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:418:68)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..76758e26 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -435,7 +435,6 @@
const kept = reader(false);
try { if (refusal(err)) throw err; html = await view(kept, path, new URLSearchParams(query)); readAt = kept.oldest(); html = staleBanner(err) + html; state = 'stale'; }
catch { html = failedView(err); state = refusal(err) ? 'refused' : 'failed'; }
- if (refusal(err)) forget();
}
if (g !== gen) return;
freshBus(state);
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3690.442184ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3849.350418ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5179.717435ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (231.525972ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (137.616984ms)
✔ bus refusals map to statuses and never carry a path or a stack (62.325904ms)
✔ humanCall speaks the human transport protocol without blocking the server (2171.007692ms)
✔ busReader takes --business, else the live bus host, else refuses (141.854219ms)
✔ humanCall kills a transport that runs past its timeout (528.99615ms)
✔ serve refuses a --business value that is not a business id (160.592221ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3644.327268ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2456.357644ms)
✔ conversation view: a newer session with no readable history keeps the marker (1296.77647ms)
✔ conversation view: seats without history say so and offer no reply (1069.763404ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3612.07748ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53490.878162ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3605.370048ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22582.383388ms)
✔ loopback host and board origin fail closed (9.211601ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (128.119652ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (107.85881ms)
✔ unreachable board reports URL; CLI rejects unsupported options (995.745599ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.903771ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.53098ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.427432ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7142.657614ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (1130.262824ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53881.267549
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (1130.262824ms)
AssertionError [ERR_ASSERTION]: #/inbox drops the Inbox count on a human-required refusal
2 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:383:16)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 2,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..49e54847 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -511,7 +511,7 @@
pkBack = document.activeElement; pkAt = 0; $('cmdk-input').value = '';
pkItems(); pkFill(); d.showModal(); $('cmdk-input').focus();
Promise.allSettled(['inbox', 'tasks'].map(async v => last.set(v, await fetchRead(v)))).then(rs => {
- if (rs.some(r => r.status === 'rejected' && refusal(r.reason))) { forget(); sections(routeOf().split('?')[0]); }
+
if (d.open) { pkItems(); pkFill(); }
});
}
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3094.868838ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4479.963024ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5207.878049ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (253.189333ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (166.673421ms)
✔ bus refusals map to statuses and never carry a path or a stack (250.543874ms)
✔ humanCall speaks the human transport protocol without blocking the server (2711.339811ms)
✔ busReader takes --business, else the live bus host, else refuses (124.064744ms)
✔ humanCall kills a transport that runs past its timeout (528.361869ms)
✔ serve refuses a --business value that is not a business id (179.900947ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2787.993908ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2713.629338ms)
✔ conversation view: a newer session with no readable history keeps the marker (1517.334847ms)
✔ conversation view: seats without history say so and offer no reply (1262.349937ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2789.431642ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53580.275598ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2782.596251ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22064.392292ms)
✔ loopback host and board origin fail closed (7.027509ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (107.473266ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (83.115149ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1860.131739ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.88699ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.960146ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.992866ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6498.656891ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (7332.608632ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54368.682085
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (7332.608632ms)
Error: Uncaught (in promise) Error: Condition timed out: document.querySelector("#cmdk-dialog").open && [...document.querySelectorAll("#cmdk-list [data-href]")].map(e=>e.dataset.href).length===4 && !document.querySelector("#sections .count"): undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:405:34)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..510aea43 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -307,7 +307,7 @@
const refused = err.status === 403;
failed = refused ? 'refused' : true; $('error').hidden = false;
if (refused) {
- data = null; selected = null; project = null;
+ data = null; project = null;
$('projects').innerHTML = '<p class="muted">Not read: the board refused the read.</p>';
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = 'Board not read: refused';
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3086.813365ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3278.687202ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5638.664355ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (239.637465ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (197.053056ms)
✔ bus refusals map to statuses and never carry a path or a stack (168.899142ms)
✔ humanCall speaks the human transport protocol without blocking the server (2952.702683ms)
✔ busReader takes --business, else the live bus host, else refuses (114.932507ms)
✔ humanCall kills a transport that runs past its timeout (528.008507ms)
✔ serve refuses a --business value that is not a business id (168.452683ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3069.725175ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2812.549981ms)
✔ conversation view: a newer session with no readable history keeps the marker (1122.606966ms)
✔ conversation view: seats without history say so and offer no reply (769.203298ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2877.518379ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53655.195021ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2938.919633ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22116.008047ms)
✔ loopback host and board origin fail closed (6.758241ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (112.880418ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (110.760082ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1924.942791ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.902975ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.804197ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.006071ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2930.528977ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3482.984298ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54060.857983
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2930.528977ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:152:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..2de73dc9 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -282,7 +282,7 @@
// Freshness line in the command bar: when the board last scanned, and warning words when the last refresh failed.
function fresh() {
const at = Date.parse(data?.generatedAt), ago = Number.isFinite(at) ? age(Math.max(0, Math.round((Date.now() - at) / 1000))) : null;
- if (!data) { $('fresh-board').innerHTML = failed === 'refused' ? 'Board <span class="warn">not read: refused</span>' : failed ? 'Board <span class="warn">not read: the read failed</span>' : 'Board not read yet'; return; }
+ if (!data) { $('fresh-board').innerHTML = failed ? 'Board <span class="warn">not read: the read failed</span>' : 'Board not read yet'; return; }
const scan = `Board scanned <b>${esc(Number.isFinite(at) ? `${new Date(at).toISOString().slice(11, 19)} UTC` : 'unknown')}</b>${ago ? ` (${ago} ago)` : ''}`;
$('fresh-board').innerHTML = failed ? `${scan} · <span class="warn">stale: the last refresh failed</span>` : scan;
}
@@ -0,0 +1,59 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2968.889817ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3226.573849ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5700.168779ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (266.368346ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (289.193528ms)
✔ bus refusals map to statuses and never carry a path or a stack (576.468911ms)
✔ humanCall speaks the human transport protocol without blocking the server (2511.116714ms)
✔ busReader takes --business, else the live bus host, else refuses (430.389135ms)
✔ humanCall kills a transport that runs past its timeout (528.253433ms)
✔ serve refuses a --business value that is not a business id (190.157217ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2961.036578ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2837.798308ms)
✔ conversation view: a newer session with no readable history keeps the marker (1272.634327ms)
✔ conversation view: seats without history say so and offer no reply (831.966075ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2864.021743ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53178.274663ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2862.922541ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22346.280842ms)
✔ loopback host and board origin fail closed (7.233985ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (103.43982ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (83.580754ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1875.027395ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.192019ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.359592ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.448547ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2864.793151ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3930.695527ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53561.128538
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2864.793151ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Board not read: the read failed'
- 'Board not read: refused'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:156:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Board not read: the read failed',
expected: 'Board not read: refused',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..e34ee5fd 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -310,7 +310,6 @@
data = null; selected = null; project = null;
$('projects').innerHTML = '<p class="muted">Not read: the board refused the read.</p>';
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
- $('footer').textContent = 'Board not read: refused';
$('status').textContent = 'Not read: the board refused the read';
inspect(); convForm();
}
@@ -0,0 +1,58 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3246.245856ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3674.93145ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5179.540346ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (239.681337ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (196.812069ms)
✔ bus refusals map to statuses and never carry a path or a stack (44.758167ms)
✔ humanCall speaks the human transport protocol without blocking the server (2933.216401ms)
✔ busReader takes --business, else the live bus host, else refuses (107.108112ms)
✔ humanCall kills a transport that runs past its timeout (529.400306ms)
✔ serve refuses a --business value that is not a business id (161.249002ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3218.875059ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2575.330787ms)
✔ conversation view: a newer session with no readable history keeps the marker (1147.311364ms)
✔ conversation view: seats without history say so and offer no reply (804.925523ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2689.75633ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53854.571559ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2860.421809ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22255.535822ms)
✔ loopback host and board origin fail closed (8.336514ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (118.861441ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (95.830197ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1834.666373ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.986879ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.98463ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.208649ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2883.477769ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2756.953859ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54274.036903
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2883.477769ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'waiting 1 · working 1'
- 'Board not read: refused'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:155:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'waiting 1 · working 1',
expected: 'Board not read: refused',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..169e8e0c 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -311,7 +311,6 @@
$('projects').innerHTML = '<p class="muted">Not read: the board refused the read.</p>';
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = 'Board not read: refused';
- $('status').textContent = 'Not read: the board refused the read';
inspect(); convForm();
}
$('error').className = refused ? 'banner ref' : 'banner err';
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3550.51874ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5312.307526ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5616.759717ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (260.854664ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (91.58931ms)
✔ bus refusals map to statuses and never carry a path or a stack (159.936555ms)
✔ humanCall speaks the human transport protocol without blocking the server (2972.254326ms)
✔ busReader takes --business, else the live bus host, else refuses (165.485236ms)
✔ humanCall kills a transport that runs past its timeout (547.85034ms)
✔ serve refuses a --business value that is not a business id (162.092336ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3539.736538ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3160.834001ms)
✔ conversation view: a newer session with no readable history keeps the marker (1301.74645ms)
✔ conversation view: seats without history say so and offer no reply (826.83122ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2888.780959ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54197.380733ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3020.912636ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22115.319317ms)
✔ loopback host and board origin fail closed (9.401662ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (119.854295ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (321.466276ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1709.391542ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.258316ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.899559ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.650272ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2910.736129ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (4240.183759ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54846.778865
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2910.736129ms)
AssertionError [ERR_ASSERTION]: The input was expected to not match the regular expression /Scanned/. Input:
'Scanned 2026-10-10T18:51:40.678Z · refresh every 10s'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:157:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Scanned 2026-10-10T18:51:40.678Z · refresh every 10s',
expected: /Scanned/,
operator: 'doesNotMatch',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..9a94ec51 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -308,7 +308,6 @@
failed = refused ? 'refused' : true; $('error').hidden = false;
if (refused) {
data = null; selected = null; project = null;
- $('projects').innerHTML = '<p class="muted">Not read: the board refused the read.</p>';
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = 'Board not read: refused';
$('status').textContent = 'Not read: the board refused the read';
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3943.764665ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4161.06461ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5544.885357ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (621.447301ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (179.59438ms)
✔ bus refusals map to statuses and never carry a path or a stack (130.608711ms)
✔ humanCall speaks the human transport protocol without blocking the server (2724.299629ms)
✔ busReader takes --business, else the live bus host, else refuses (110.308126ms)
✔ humanCall kills a transport that runs past its timeout (528.747596ms)
✔ serve refuses a --business value that is not a business id (188.683838ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3892.153976ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2405.469187ms)
✔ conversation view: a newer session with no readable history keeps the marker (1162.017836ms)
✔ conversation view: seats without history say so and offer no reply (811.669296ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2843.374378ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54334.298298ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2861.443553ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22345.735665ms)
✔ loopback host and board origin fail closed (6.544532ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (105.279642ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (306.595913ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1904.82471ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.160351ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.292111ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.341858ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2793.054072ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3257.19315ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54777.426057
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (2793.054072ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:154:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..33702149 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -309,7 +309,6 @@
if (refused) {
data = null; selected = null; project = null;
$('projects').innerHTML = '<p class="muted">Not read: the board refused the read.</p>';
- for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = 'Board not read: refused';
$('status').textContent = 'Not read: the board refused the read';
inspect(); convForm();
@@ -0,0 +1,36 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4287.251342ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4257.078572ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (4787.813726ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (248.673864ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (108.850367ms)
✔ bus refusals map to statuses and never carry a path or a stack (57.034728ms)
✔ humanCall speaks the human transport protocol without blocking the server (2123.171508ms)
✔ busReader takes --business, else the live bus host, else refuses (181.909869ms)
✔ humanCall kills a transport that runs past its timeout (528.763166ms)
✔ serve refuses a --business value that is not a business id (158.778448ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4196.796291ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1947.944484ms)
✔ conversation view: a newer session with no readable history keeps the marker (1219.575356ms)
✔ conversation view: seats without history say so and offer no reply (966.856189ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4154.261341ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53503.07169ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3990.479246ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22660.736326ms)
✔ loopback host and board origin fail closed (8.965093ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (126.518024ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (104.109094ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1039.219515ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.241848ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.50003ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.17149ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6799.567456ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2563.79395ms)
ℹ tests 27
ℹ suites 0
ℹ pass 27
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53936.307319
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..bfe9075b 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -376,7 +376,7 @@
// Freshness line in the command bar for bus pages.
const stamp = iso => { const t = Date.parse(iso); return Number.isFinite(t) ? `${new Date(t).toISOString().slice(11, 19)} UTC` : 'unknown'; };
function freshBus(state) {
- $('fresh-bus').innerHTML = state === 'refused' ? 'Bus <span class="warn">not read: refused</span>' : state === 'failed' ? 'Bus <span class="warn">not read: the read failed</span>'
+ $('fresh-bus').innerHTML = state === 'failed' ? 'Bus <span class="warn">not read: the read failed</span>'
: `Bus read <b>${esc(stamp(readAt))}</b>${readAt ? ` (${esc(ago(readAt))})` : ''}${state === 'stale' ? ' · <span class="warn">stale: the last read failed</span>' : ''}`;
}
@@ -0,0 +1,80 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4019.454985ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4218.465523ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5722.957436ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (288.58676ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (146.939199ms)
✔ bus refusals map to statuses and never carry a path or a stack (263.376856ms)
✔ humanCall speaks the human transport protocol without blocking the server (2922.82782ms)
✔ busReader takes --business, else the live bus host, else refuses (244.943803ms)
✔ humanCall kills a transport that runs past its timeout (530.490728ms)
✔ serve refuses a --business value that is not a business id (308.685115ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4008.930441ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2357.061135ms)
✔ conversation view: a newer session with no readable history keeps the marker (1100.14734ms)
✔ conversation view: seats without history say so and offer no reply (799.501081ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2671.451659ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53877.624865ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2890.013136ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22280.584605ms)
✔ loopback host and board origin fail closed (9.459608ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (150.74983ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (98.568173ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1760.354168ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.048301ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.876693ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.620976ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4024.870689ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (2076.962299ms)
ℹ tests 27
ℹ suites 0
ℹ pass 25
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54355.140712
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4024.870689ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Bus read 18:54:25 UTC (0 s ago)'
- 'Bus not read: refused'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:234:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Bus read 18:54:25 UTC (0 s ago)',
expected: 'Bus not read: refused',
operator: 'strictEqual',
diff: 'simple'
}
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (2076.962299ms)
AssertionError [ERR_ASSERTION]: #/inbox
+ actual - expected
+ 'Bus read 18:54:28 UTC (0 s ago)'
- 'Bus not read: refused'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:382:16)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'Bus read 18:54:28 UTC (0 s ago)',
expected: 'Bus not read: refused',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css
index ed6d2d96..14821b7b 100644
--- a/packages/webui/src/public/shell.css
+++ b/packages/webui/src/public/shell.css
@@ -103,7 +103,7 @@ body.on-bus #board-refused{display:none}
/* Toast: one line, polite, bottom centre. */
.toast{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);display:flex;align-items:center;gap:10px;background:var(--strong);color:var(--surface);padding:6px 8px 6px 14px;border-radius:var(--r);font-size:13px;z-index:60;max-width:calc(100% - 24px);box-shadow:var(--shadow)}
/* Off, the toast stays in the accessibility tree, empty and visually hidden, so the first copy is announced. */
-.toast:not(.on){width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;box-shadow:none}
+.toast:not(.on){display:none}
.toast span{min-width:0;overflow-wrap:anywhere}
.toast.err{background:var(--danger)}
.toast button{font:inherit;font-weight:600;border:1px solid currentColor;background:none;color:inherit;border-radius:4px;padding:1px 8px;cursor:pointer}
@@ -0,0 +1,63 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3328.993527ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3531.78405ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5447.140363ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (269.185034ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (125.783547ms)
✔ bus refusals map to statuses and never carry a path or a stack (145.763412ms)
✔ humanCall speaks the human transport protocol without blocking the server (2793.230461ms)
✔ busReader takes --business, else the live bus host, else refuses (120.744303ms)
✔ humanCall kills a transport that runs past its timeout (588.228776ms)
✔ serve refuses a --business value that is not a business id (158.710131ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3282.87396ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2349.879429ms)
✔ conversation view: a newer session with no readable history keeps the marker (1255.661234ms)
✔ conversation view: seats without history say so and offer no reply (834.227184ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2771.824123ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53870.749227ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3156.662852ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22133.981151ms)
✔ loopback host and board origin fail closed (8.165648ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (133.174467ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (104.076118ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1812.887702ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.313076ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.501953ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.280554ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (3339.870267ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3129.879391ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54285.916301
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (3339.870267ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
+ false,
- true,
'visible',
'',
true
]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:199:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [ false, 'visible', '', true ],
expected: [ true, 'visible', '', true ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..4ceace5b 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -370,7 +370,7 @@
// A refusal fails closed: no kept data, the code in mono. Any other failure keeps the last read, labelled with its time.
const refusal = err => err.status === 403 || ['not-configured', 'no-bus-host'].includes(err.code);
// After a refusal nothing read before is shown anywhere: not in a view, the palette or the Inbox count.
- function forget() { last.clear(); inboxCount = null; }
+ function forget() { last.clear(); }
const failedView = err => `${head(failTitle(err))}<div class="banner ${refusal(err) ? 'ref' : 'err'}" role="alert"><p>${refusal(err) ? `<b>refused: <code>${txt(err.code)}</code>.</b> ` : ''}${why(err)} Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>${empty('Nothing to show.', refusal(err) ? 'The view stays empty until the problem above is fixed.' : 'Read again, or come back when the bus answers.')}`;
const staleBanner = err => `<div class="banner err s1-stale" role="alert"><p><b>The last read failed.</b> ${why(err)} This is what was read before, ${when(readAt)}. Nothing was changed.</p>${retry}</div>`;
// Freshness line in the command bar for bus pages.
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3126.358489ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4118.593367ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5006.994875ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (293.322998ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (179.622323ms)
✔ bus refusals map to statuses and never carry a path or a stack (244.651528ms)
✔ humanCall speaks the human transport protocol without blocking the server (2463.711647ms)
✔ busReader takes --business, else the live bus host, else refuses (126.115149ms)
✔ humanCall kills a transport that runs past its timeout (528.077583ms)
✔ serve refuses a --business value that is not a business id (153.121325ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2776.188695ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2558.872677ms)
✔ conversation view: a newer session with no readable history keeps the marker (1138.046042ms)
✔ conversation view: seats without history say so and offer no reply (1204.449823ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2668.08359ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53852.495895ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2670.260808ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22057.59907ms)
✔ loopback host and board origin fail closed (8.782534ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (130.541717ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (140.203928ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1610.160969ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.884644ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.761476ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.839968ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6358.781073ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (1322.190764ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54329.286857
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (1322.190764ms)
AssertionError [ERR_ASSERTION]: #/inbox drops the Inbox count on a human-required refusal
2 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:383:16)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 2,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/bus.js b/packages/webui/src/public/bus.js
index 93df4371..c2eacbe1 100644
--- a/packages/webui/src/public/bus.js
+++ b/packages/webui/src/public/bus.js
@@ -370,7 +370,7 @@
// A refusal fails closed: no kept data, the code in mono. Any other failure keeps the last read, labelled with its time.
const refusal = err => err.status === 403 || ['not-configured', 'no-bus-host'].includes(err.code);
// After a refusal nothing read before is shown anywhere: not in a view, the palette or the Inbox count.
- function forget() { last.clear(); inboxCount = null; }
+ function forget() { inboxCount = null; }
const failedView = err => `${head(failTitle(err))}<div class="banner ${refusal(err) ? 'ref' : 'err'}" role="alert"><p>${refusal(err) ? `<b>refused: <code>${txt(err.code)}</code>.</b> ` : ''}${why(err)} Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>${empty('Nothing to show.', refusal(err) ? 'The view stays empty until the problem above is fixed.' : 'Read again, or come back when the bus answers.')}`;
const staleBanner = err => `<div class="banner err s1-stale" role="alert"><p><b>The last read failed.</b> ${why(err)} This is what was read before, ${when(readAt)}. Nothing was changed.</p>${retry}</div>`;
// Freshness line in the command bar for bus pages.
@@ -0,0 +1,65 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3007.173261ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3855.294086ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5615.842981ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (261.282923ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (93.524702ms)
✔ bus refusals map to statuses and never carry a path or a stack (152.594397ms)
✔ humanCall speaks the human transport protocol without blocking the server (3222.083926ms)
✔ busReader takes --business, else the live bus host, else refuses (80.345244ms)
✔ humanCall kills a transport that runs past its timeout (575.740134ms)
✔ serve refuses a --business value that is not a business id (190.165655ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2882.103601ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2486.2508ms)
✔ conversation view: a newer session with no readable history keeps the marker (1177.664134ms)
✔ conversation view: seats without history say so and offer no reply (1284.697782ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2706.579171ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53381.0835ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2781.75392ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22306.183997ms)
✔ loopback host and board origin fail closed (7.394334ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (107.914025ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (97.224984ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1885.353532ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.899596ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.989552ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.889945ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6673.23278ms)
✖ five states on Board, Inbox, Tasks, Agents and Trail (2369.587424ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53793.045051
✖ failing tests:
test at tests/shell.test.mjs:316:1
✖ five states on Board, Inbox, Tasks, Agents and Trail (2369.587424ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
'#/',
'#/inbox',
'#/tasks',
'#/agents',
+ '#/inbox/dddddddddddddddddddddddddddddddd',
+ '#/tasks/vikunja%3A32%2F7',
+ '#/tasks/vikunja%3A32%2F8'
]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:397:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [ '#/', '#/inbox', '#/tasks', '#/agents', '#/inbox/dddddddddddddddddddddddddddddddd', '#/tasks/vikunja%3A32%2F7', '#/tasks/vikunja%3A32%2F8' ],
expected: [ '#/', '#/inbox', '#/tasks', '#/agents' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/app.js b/packages/webui/src/public/app.js
index 2084ff63..5ca3d420 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -283,7 +283,7 @@
function fresh() {
const at = Date.parse(data?.generatedAt), ago = Number.isFinite(at) ? age(Math.max(0, Math.round((Date.now() - at) / 1000))) : null;
if (!data) { $('fresh-board').innerHTML = failed === 'refused' ? 'Board <span class="warn">not read: refused</span>' : failed ? 'Board <span class="warn">not read: the read failed</span>' : 'Board not read yet'; return; }
- const scan = `Board scanned <b>${esc(Number.isFinite(at) ? `${new Date(at).toISOString().slice(11, 19)} UTC` : 'unknown')}</b>${ago ? ` (${ago} ago)` : ''}`;
+ const scan = `Board scanned <b>${esc(Number.isFinite(at) ? new Date(at).toLocaleTimeString([], { hour12: false }) : 'unknown')}</b>${ago ? ` (${ago} ago)` : ''}`;
$('fresh-board').innerHTML = failed ? `${scan} · <span class="warn">stale: the last refresh failed</span>` : scan;
}
setInterval(() => { if (data) fresh(); }, 5000);
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3672.88716ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3626.292542ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6049.955031ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (298.139781ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (384.361372ms)
✔ bus refusals map to statuses and never carry a path or a stack (95.057268ms)
✔ humanCall speaks the human transport protocol without blocking the server (2630.859361ms)
✔ busReader takes --business, else the live bus host, else refuses (153.24859ms)
✔ humanCall kills a transport that runs past its timeout (530.057319ms)
✔ serve refuses a --business value that is not a business id (149.60264ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3646.12638ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2657.957896ms)
✔ conversation view: a newer session with no readable history keeps the marker (1187.981349ms)
✔ conversation view: seats without history say so and offer no reply (824.394033ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3514.633496ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54861.537569ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3495.154258ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22156.108066ms)
✔ loopback host and board origin fail closed (6.632866ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (109.752328ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (116.522219ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1874.392891ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.924477ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.681684ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.146795ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (3244.793375ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3494.281686ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55222.326952
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (3244.793375ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^Board scanned \d\d:\d\d:\d\d UTC/. Input:
'Board scanned 13:57:59 (2s ago)'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:128:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Board scanned 13:57:59 (2s ago)',
expected: /^Board scanned \d\d:\d\d:\d\d UTC/,
operator: 'match',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css
index ed6d2d96..c3c3d718 100644
--- a/packages/webui/src/public/shell.css
+++ b/packages/webui/src/public/shell.css
@@ -166,7 +166,7 @@ body.on-bus #board-refused{display:none}
.cmd-right label{flex:1 1 0;min-width:0}
.cmd-right .btn{flex:none}
/* The section list is one strip that scrolls inside itself (IMPLEMENTING.md), so more sections never add rows. */
- .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:auto;flex-basis:100%;min-width:0;max-width:100%;gap:2px;padding-bottom:2px;scrollbar-width:thin}
+ .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:visible;flex-basis:100%;min-width:0;max-width:100%;gap:2px;padding-bottom:2px;scrollbar-width:thin}
.s1-sections>li{flex:none}
.s1-section{border-radius:var(--r);padding:5px 8px;white-space:nowrap;flex:none}
.inspector{inset:0;top:0;width:100%;z-index:40;box-shadow:none}
@@ -0,0 +1,53 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5211.322674ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5406.834792ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7967.747109ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (291.194633ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (167.355846ms)
✔ bus refusals map to statuses and never carry a path or a stack (301.032325ms)
✔ humanCall speaks the human transport protocol without blocking the server (2370.843823ms)
✔ busReader takes --business, else the live bus host, else refuses (511.36435ms)
✔ humanCall kills a transport that runs past its timeout (528.862393ms)
✔ serve refuses a --business value that is not a business id (163.431821ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5370.262527ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4365.992749ms)
✔ conversation view: a newer session with no readable history keeps the marker (2791.289399ms)
✔ conversation view: seats without history say so and offer no reply (1223.433429ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5354.865178ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56830.63505ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5434.62527ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22694.896098ms)
✔ loopback host and board origin fail closed (9.937835ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (137.366904ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (141.117982ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1498.910339ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.431161ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.986511ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.729744ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5309.354326ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (6092.066387ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 57366.349902
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5309.354326ms)
AssertionError [ERR_ASSERTION]: strip scrolls at 360px
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:205:14)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'visible',
expected: /^(auto|scroll)$/,
operator: 'match',
diff: 'simple'
}
@@ -0,0 +1,13 @@
diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css
index ed6d2d96..77fc8ff8 100644
--- a/packages/webui/src/public/shell.css
+++ b/packages/webui/src/public/shell.css
@@ -166,7 +166,7 @@ body.on-bus #board-refused{display:none}
.cmd-right label{flex:1 1 0;min-width:0}
.cmd-right .btn{flex:none}
/* The section list is one strip that scrolls inside itself (IMPLEMENTING.md), so more sections never add rows. */
- .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:auto;flex-basis:100%;min-width:0;max-width:100%;gap:2px;padding-bottom:2px;scrollbar-width:thin}
+ .s1-sections{display:flex;flex-wrap:wrap;overflow-x:auto;flex-basis:100%;min-width:0;max-width:100%;gap:2px;padding-bottom:2px;scrollbar-width:thin}
.s1-sections>li{flex:none}
.s1-section{border-radius:var(--r);padding:5px 8px;white-space:nowrap;flex:none}
.inspector{inset:0;top:0;width:100%;z-index:40;box-shadow:none}
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5754.769823ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5803.927596ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8809.823038ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (355.250288ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (113.044848ms)
✔ bus refusals map to statuses and never carry a path or a stack (74.932958ms)
✔ humanCall speaks the human transport protocol without blocking the server (2169.690829ms)
✔ busReader takes --business, else the live bus host, else refuses (700.614062ms)
✔ humanCall kills a transport that runs past its timeout (529.626355ms)
✔ serve refuses a --business value that is not a business id (157.707334ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5687.132751ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4440.235802ms)
✔ conversation view: a newer session with no readable history keeps the marker (2682.046974ms)
✔ conversation view: seats without history say so and offer no reply (2581.852312ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5716.272685ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (57961.778739ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5798.600313ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23168.713365ms)
✔ loopback host and board origin fail closed (10.029846ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (119.87584ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (116.102285ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1062.369733ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.26065ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.576684ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.555527ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5642.483696ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (5046.143675ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 58384.246613
✖ failing tests:
test at tests/shell.test.mjs:88:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5642.483696ms)
AssertionError [ERR_ASSERTION]: one row at 360px
2 !== 1
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53b/mutwt/packages/webui/tests/shell.test.mjs:204:14)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,16 @@
packages/webui/README.md: OK
packages/webui/src/public/app.js: OK
packages/webui/src/public/assets/fonts/jetbrains-mono-400.woff2: OK
packages/webui/src/public/assets/fonts/jetbrains-mono-500.woff2: OK
packages/webui/src/public/assets/fonts/jetbrains-mono-700.woff2: OK
packages/webui/src/public/assets/fonts/jetbrains-mono-OFL.txt: OK
packages/webui/src/public/assets/fonts/jetbrains-mono-sources.txt: OK
packages/webui/src/public/bus.js: OK
packages/webui/src/public/icons.svg: OK
packages/webui/src/public/index.html: OK
packages/webui/src/public/shell.css: OK
packages/webui/src/public/tokens.css: OK
packages/webui/src/serve.mjs: OK
packages/webui/tests/browser.mjs: OK
packages/webui/tests/serve.test.mjs: OK
packages/webui/tests/shell.test.mjs: OK
@@ -0,0 +1,48 @@
# mutate.py <id>: apply one named mutant to the candidate (exact text, must match once). Run from the tree root.
import sys
B, A, C, S, H = "packages/webui/src/public/bus.js", "packages/webui/src/public/app.js", "packages/webui/src/public/shell.css", "packages/webui/src/serve.mjs", "packages/webui/src/public/index.html"
M = {
"M01": [(B, "err.status === 403 || ['not-configured', 'no-bus-host'].includes(err.code);", "err.status === 403;")],
"M02": [(A, "const refused = err.status === 403;", "const refused = false;")],
"M03": [(A, "document.body.classList.toggle('board-refused', refused);", "")],
"M04": [(B, "<b>refused: <code>${txt(err.code)}</code>.</b> ", "<b>refused: ${txt(err.code)}.</b> ")],
"M05": [(B, "${state === 'stale' ? ' · <span class=\"warn\">stale: the last read failed</span>' : ''}", "")],
"M06": [(A, "if (mode === 'system') delete document.documentElement.dataset.mode; else document.documentElement.dataset.mode = mode;", "document.documentElement.dataset.mode = mode;")],
"M07": [(B, "t.innerHTML = `<span>${esc(text)}</span>", "t.innerHTML = `<span>${text}</span>")],
"M08": [(B, "<span>${txt(i.label)}</span><span class=\"g\">", "<span>${i.label}</span><span class=\"g\">")],
"M09": [(C, ":focus-visible{outline:2px solid var(--focus);outline-offset:2px}", ":focus-visible{outline:2px solid var(--focus);outline-offset:0}")],
"M10": [(C, ":focus-visible{outline:2px solid var(--focus);outline-offset:2px}", ":focus-visible{outline:1px solid var(--focus);outline-offset:2px}")],
"M11": [(C, " .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:auto;", " .s1-sections{display:flex;flex-wrap:wrap;overflow-x:visible;")],
"M21": [(B, "function forget() { last.clear(); inboxCount = null; }", "function forget() {}")],
"M22": [(B, "catch (err) { if (refusal(err)) { forget(); if (g === gen) sections(path); } }", "catch {}")],
"M23": [(B, " if (refusal(err)) forget();\n", "")],
"M24": [(B, "if (rs.some(r => r.status === 'rejected' && refusal(r.reason))) { forget(); sections(routeOf().split('?')[0]); }", "")],
"M25": [(A, "data = null; selected = null; project = null;", "data = null; project = null;")],
"M26": [(A, "failed === 'refused' ? 'Board <span class=\"warn\">not read: refused</span>' : ", "")],
"M27": [(A, " $('footer').textContent = 'Board not read: refused';\n", "")],
"M28": [(A, " $('status').textContent = 'Not read: the board refused the read';\n", "")],
"M29": [(A, " $('projects').innerHTML = '<p class=\"muted\">Not read: the board refused the read.</p>';\n", "")],
"M30": [(A, " for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';\n", "")],
"M31": [(B, "state === 'refused' ? 'Bus <span class=\"warn\">not read: refused</span>' : ", "")],
"M32": [(C, ".toast:not(.on){width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;box-shadow:none}", ".toast:not(.on){display:none}")],
"M33": [(B, "function forget() { last.clear(); inboxCount = null; }", "function forget() { last.clear(); }")],
"M34": [(B, "function forget() { last.clear(); inboxCount = null; }", "function forget() { inboxCount = null; }")],
"M35": [(A, "`${new Date(at).toISOString().slice(11, 19)} UTC`", "new Date(at).toLocaleTimeString([], { hour12: false })")],
"M36": [(C, " .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:auto;", " .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:visible;")],
"M37": [(C, " .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:auto;", " .s1-sections{display:flex;flex-wrap:wrap;overflow-x:auto;")],
"M12": [(B, "<svg class=\"i\" aria-hidden=\"true\"><use href=\"/icons.svg#i-${icon}\"/></svg>", "")],
"M13": [(A, " $('board-refused').hidden = true; document.body.classList.remove('board-refused');\n", "")],
"M14": [(B, "links[i + (e.key === 'ArrowDown' ? 1 : -1)]?.focus();", "links[i + (e.key === 'ArrowDown' ? -1 : 1)]?.focus();")],
"M15": [(S, "['/icons.svg', ['icons.svg', 'image/svg+xml']],", "['/icons.svg', ['icons.svg', 'text/plain']],")],
"M16": [(C, ".count,td,.s1-twhen,time,.fresh,.kv dd{font-variant-numeric:tabular-nums}", "")],
"M17": [(A, "try { localStorage.setItem('mosaic-console-appearance',", "try { localStorage.setItem('mosaic-console-look',")],
"M18": [(C, "body.board-refused #board-view{display:none}", "")],
"M19": [(B, "if (refusal(err)) throw err; html = await view(kept,", "html = await view(kept,")],
"M20": [(A, "$('error').className = refused ? 'banner ref' : 'banner err';", "$('error').className = 'banner err';")],
}
for f, old, new in M[sys.argv[1]]:
s = open(f).read()
n = s.count(old)
if n != 1: sys.exit(f"{sys.argv[1]}: {n} matches in {f}")
open(f, "w").write(s.replace(old, new))
print(sys.argv[1], "applied")
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# Each mutant against the packages/webui suite in a candidate worktree, then restores from the index.
export TMPDIR=~/darkwing-scratch/r53b/tmp
S=~/darkwing-scratch/r53b; M=$S/mut; W=$S/mutwt
: > $M/summary.txt
echo "start $(date -u +%FT%TZ)" >> $M/summary.txt
for v in $(python3 -c 'import re;print(" ".join(sorted(set(re.findall(r"\"(M\d\d)\"", open("'$M'/mutate.py").read())))))'); do
(cd $W && git checkout -q -- packages/webui && python3 $M/mutate.py $v > /dev/null && git diff > $M/$v.diff) || { echo "$v APPLY-FAILED" >> $M/summary.txt; continue; }
(cd $W/packages/webui && env -u NODE_TEST_CONTEXT node --test 'tests/*.test.mjs' > $M/$v.txt 2>&1); e=$?
r=killed; [ $e = 0 ] && r=SURVIVED
echo "$v $r exit=$e $(grep -E '^ℹ (pass|fail)' $M/$v.txt | tr '\n' ' ')" >> $M/summary.txt
done
(cd $W && git checkout -q -- packages/webui && sha256sum -c $S/pkt/candidate-manifest.sha256) > $M/manifest-after.txt 2>&1
echo "end $(date -u +%FT%TZ)" >> $M/summary.txt
echo DONE >> $M/summary.txt
@@ -0,0 +1,40 @@
start 2026-10-10T18:25:08Z
M01 killed exit=1 ℹ pass 26 ℹ fail 1
M02 killed exit=1 ℹ pass 26 ℹ fail 1
M03 killed exit=1 ℹ pass 26 ℹ fail 1
M04 killed exit=1 ℹ pass 26 ℹ fail 1
M05 killed exit=1 ℹ pass 24 ℹ fail 3
M06 killed exit=1 ℹ pass 26 ℹ fail 1
M07 SURVIVED exit=0 ℹ pass 27 ℹ fail 0
M08 killed exit=1 ℹ pass 26 ℹ fail 1
M09 killed exit=1 ℹ pass 26 ℹ fail 1
M10 killed exit=1 ℹ pass 26 ℹ fail 1
M11 killed exit=1 ℹ pass 26 ℹ fail 1
M12 killed exit=1 ℹ pass 26 ℹ fail 1
M13 killed exit=1 ℹ pass 26 ℹ fail 1
M14 killed exit=1 ℹ pass 26 ℹ fail 1
M15 killed exit=1 ℹ pass 26 ℹ fail 1
M16 killed exit=1 ℹ pass 26 ℹ fail 1
M17 killed exit=1 ℹ pass 26 ℹ fail 1
M18 killed exit=1 ℹ pass 26 ℹ fail 1
M19 killed exit=1 ℹ pass 25 ℹ fail 2
M20 killed exit=1 ℹ pass 26 ℹ fail 1
M21 killed exit=1 ℹ pass 26 ℹ fail 1
M22 killed exit=1 ℹ pass 26 ℹ fail 1
M23 killed exit=1 ℹ pass 26 ℹ fail 1
M24 killed exit=1 ℹ pass 26 ℹ fail 1
M25 killed exit=1 ℹ pass 26 ℹ fail 1
M26 killed exit=1 ℹ pass 26 ℹ fail 1
M27 killed exit=1 ℹ pass 26 ℹ fail 1
M28 killed exit=1 ℹ pass 26 ℹ fail 1
M29 killed exit=1 ℹ pass 26 ℹ fail 1
M30 SURVIVED exit=0 ℹ pass 27 ℹ fail 0
M31 killed exit=1 ℹ pass 25 ℹ fail 2
M32 killed exit=1 ℹ pass 26 ℹ fail 1
M33 killed exit=1 ℹ pass 26 ℹ fail 1
M34 killed exit=1 ℹ pass 26 ℹ fail 1
M35 killed exit=1 ℹ pass 26 ℹ fail 1
M36 killed exit=1 ℹ pass 26 ℹ fail 1
M37 killed exit=1 ℹ pass 26 ℹ fail 1
end 2026-10-10T19:00:50Z
DONE
@@ -0,0 +1 @@
tokens.css is current
@@ -0,0 +1,36 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5523.436991ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (8766.561351ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8827.954235ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (277.316884ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (201.217171ms)
✔ bus refusals map to statuses and never carry a path or a stack (73.461855ms)
✔ humanCall speaks the human transport protocol without blocking the server (2813.609766ms)
✔ busReader takes --business, else the live bus host, else refuses (175.887468ms)
✔ humanCall kills a transport that runs past its timeout (529.046089ms)
✔ serve refuses a --business value that is not a business id (197.251891ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (6431.465011ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3853.631876ms)
✔ conversation view: a newer session with no readable history keeps the marker (3922.98223ms)
✔ conversation view: seats without history say so and offer no reply (3482.629009ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5496.850194ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (58053.664671ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5499.821805ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (25062.757457ms)
✔ loopback host and board origin fail closed (9.306713ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (134.500699ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (127.67224ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1474.550223ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.345771ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.627495ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.591051ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8724.52559ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (7349.679734ms)
ℹ tests 27
ℹ suites 0
ℹ pass 27
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 58535.525593
@@ -0,0 +1,391 @@
diff -ru /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/README.md packages/webui/README.md
--- /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/README.md 2026-10-10 12:23:48.000000000 -0500
+++ packages/webui/README.md 2026-10-10 13:15:05.000000000 -0500
@@ -91,7 +91,9 @@
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 refusal (403, `not-configured` or `no-bus-host`)
- never shows kept rows (row 53). A page never read, or refused, shows the failure instead,
+ never shows kept rows (row 53), and it drops every kept bus read: the Inbox
+ count and the palette's decisions and tasks go too, until a read succeeds.
+ A page never read, or refused, 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".
@@ -120,12 +122,16 @@
and tasks; arrows move, Enter opens, Esc closes and returns focus. Arrow
keys also move between task links in the bus tables.
- Copy reports in a toast. If the clipboard is unavailable, the toast stays
- until dismissed and the command is selected for Ctrl+C.
-- The command bar shows when the board was scanned and the bus read, and
- says when the last attempt failed. A board refusal hides the board rather
- than showing the last scan.
+ until dismissed and the command is selected for Ctrl+C. When no toast shows,
+ the toast is an empty live region, visually hidden but still in the
+ accessibility tree, so the first copy is announced.
+- The command bar shows, in UTC, when the board was scanned and the bus read,
+ and says when the last attempt failed. A board refusal drops the last scan
+ and everything drawn from it: the table, the inspector, the project tree,
+ the counts, the footer and the scan line all say the board was not read.
- From 760px the footer is a one-line status bar and the side column fits
- between it and the command bar. Below 760px the section list wraps.
+ between it and the command bar. Below 760px the section list is one strip
+ that scrolls inside itself.
## Data and boundaries
@@ -204,11 +210,15 @@
Shell tests (`shell.test.mjs`) check the copies, the font hashes and the kept
strings, and in the browser at 400px: System mode under an emulated dark and
-light OS, the empty, stale and refused states of the board and the bus views,
-not found, the palette, the toast, keyboard and visible focus, no sideways
+light OS, the empty, stale and refused states of the board and the bus views
+(a board refusal with the inspector open), not found, the palette, the toast,
+keyboard and visible focus, the section strip at 360 and 400px, no sideways
scroll, no script error and no request other than GET. A second test walks
the Board, Inbox, Tasks, Agents and a decision trail through loading (a held
-read), normal, a failed read over kept rows, and a refusal.
+read), normal, a failed read over kept rows, and a refusal by the bus
+(`human-required`) and for want of one (`no-bus-host`). It then checks that a
+refusal empties the palette and the Inbox count, from a view, from the
+palette's own reads and from the board page, and that palette labels are text.
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.
diff -ru /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/src/public/app.js packages/webui/src/public/app.js
--- /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/src/public/app.js 2026-10-10 12:03:18.000000000 -0500
+++ packages/webui/src/public/app.js 2026-10-10 13:15:05.000000000 -0500
@@ -282,8 +282,8 @@
// Freshness line in the command bar: when the board last scanned, and warning words when the last refresh failed.
function fresh() {
const at = Date.parse(data?.generatedAt), ago = Number.isFinite(at) ? age(Math.max(0, Math.round((Date.now() - at) / 1000))) : null;
- if (!data) { $('fresh-board').innerHTML = failed ? 'Board <span class="warn">not read: the read failed</span>' : 'Board not read yet'; return; }
- const scan = `Board scanned <b>${esc(Number.isFinite(at) ? new Date(at).toLocaleTimeString([], { hour12: false }) : 'unknown')}</b>${ago ? ` (${ago} ago)` : ''}`;
+ if (!data) { $('fresh-board').innerHTML = failed === 'refused' ? 'Board <span class="warn">not read: refused</span>' : failed ? 'Board <span class="warn">not read: the read failed</span>' : 'Board not read yet'; return; }
+ const scan = `Board scanned <b>${esc(Number.isFinite(at) ? `${new Date(at).toISOString().slice(11, 19)} UTC` : 'unknown')}</b>${ago ? ` (${ago} ago)` : ''}`;
$('fresh-board').innerHTML = failed ? `${scan} · <span class="warn">stale: the last refresh failed</span>` : scan;
}
setInterval(() => { if (data) fresh(); }, 5000);
@@ -300,10 +300,20 @@
for (const id of awaiting.keys()) { const r = row(id); if (r && !pending(r)) awaiting.delete(id); }
render();
}
- // A refusal (403) fails closed: the last data is hidden, not shown as current. Any other failure keeps the last data with its scan time.
+ // A refusal (403) fails closed: the last data is dropped, not kept or shown as current, and so is
+ // everything drawn from it: the inspector, the project tree, the counts and the footer. Any other
+ // failure keeps the last data with its scan time.
function error(err) {
- failed = true; $('error').hidden = false;
const refused = err.status === 403;
+ failed = refused ? 'refused' : true; $('error').hidden = false;
+ if (refused) {
+ data = null; selected = null; project = null;
+ $('projects').innerHTML = '<p class="muted">Not read: the board refused the read.</p>';
+ for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
+ $('footer').textContent = 'Board not read: refused';
+ $('status').textContent = 'Not read: the board refused the read';
+ inspect(); convForm();
+ }
$('error').className = refused ? 'banner ref' : 'banner err';
$('board-refused').hidden = !refused; document.body.classList.toggle('board-refused', refused);
$('error').innerHTML = refused
diff -ru /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/src/public/bus.js packages/webui/src/public/bus.js
--- /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/src/public/bus.js 2026-10-10 11:58:46.000000000 -0500
+++ packages/webui/src/public/bus.js 2026-10-10 13:15:05.000000000 -0500
@@ -369,12 +369,14 @@
const failTitle = err => err.status === 403 ? 'Bus refused the read' : ['not-configured', 'no-bus-host'].includes(err.code) ? 'No bus to read' : 'The read failed';
// A refusal fails closed: no kept data, the code in mono. Any other failure keeps the last read, labelled with its time.
const refusal = err => err.status === 403 || ['not-configured', 'no-bus-host'].includes(err.code);
+ // After a refusal nothing read before is shown anywhere: not in a view, the palette or the Inbox count.
+ function forget() { last.clear(); inboxCount = null; }
const failedView = err => `${head(failTitle(err))}<div class="banner ${refusal(err) ? 'ref' : 'err'}" role="alert"><p>${refusal(err) ? `<b>refused: <code>${txt(err.code)}</code>.</b> ` : ''}${why(err)} Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>${empty('Nothing to show.', refusal(err) ? 'The view stays empty until the problem above is fixed.' : 'Read again, or come back when the bus answers.')}`;
const staleBanner = err => `<div class="banner err s1-stale" role="alert"><p><b>The last read failed.</b> ${why(err)} This is what was read before, ${when(readAt)}. Nothing was changed.</p>${retry}</div>`;
// Freshness line in the command bar for bus pages.
const stamp = iso => { const t = Date.parse(iso); return Number.isFinite(t) ? `${new Date(t).toISOString().slice(11, 19)} UTC` : 'unknown'; };
function freshBus(state) {
- $('fresh-bus').innerHTML = state === 'failed' ? 'Bus <span class="warn">not read: the read failed</span>'
+ $('fresh-bus').innerHTML = state === 'refused' ? 'Bus <span class="warn">not read: refused</span>' : state === 'failed' ? 'Bus <span class="warn">not read: the read failed</span>'
: `Bus read <b>${esc(stamp(readAt))}</b>${readAt ? ` (${esc(ago(readAt))})` : ''}${state === 'stale' ? ' · <span class="warn">stale: the last read failed</span>' : ''}`;
}
@@ -420,7 +422,8 @@
if (board) {
// The board page keeps its own title; the Inbox count still follows the bus.
if (shown !== null) { shown = null; $('bus-view').innerHTML = ''; document.title = 'Mosaic Console'; if (navigated) $('main').focus(); }
- try { const r = await fetchRead('inbox'); last.set('inbox', r); if (g === gen) { countInbox(r.rows); sections(path); } } catch {}
+ try { const r = await fetchRead('inbox'); last.set('inbox', r); if (g === gen) { countInbox(r.rows); sections(path); } }
+ catch (err) { if (refusal(err)) { forget(); if (g === gen) sections(path); } }
if (g === gen) schedule();
return;
}
@@ -431,7 +434,8 @@
catch (err) {
const kept = reader(false);
try { if (refusal(err)) throw err; html = await view(kept, path, new URLSearchParams(query)); readAt = kept.oldest(); html = staleBanner(err) + html; state = 'stale'; }
- catch { html = failedView(err); state = 'failed'; }
+ catch { html = failedView(err); state = refusal(err) ? 'refused' : 'failed'; }
+ if (refusal(err)) forget();
}
if (g !== gen) return;
freshBus(state);
@@ -506,7 +510,10 @@
if (d.open) return;
pkBack = document.activeElement; pkAt = 0; $('cmdk-input').value = '';
pkItems(); pkFill(); d.showModal(); $('cmdk-input').focus();
- Promise.allSettled(['inbox', 'tasks'].map(async v => last.set(v, await fetchRead(v)))).then(() => { if (d.open) { pkItems(); pkFill(); } });
+ Promise.allSettled(['inbox', 'tasks'].map(async v => last.set(v, await fetchRead(v)))).then(rs => {
+ if (rs.some(r => r.status === 'rejected' && refusal(r.reason))) { forget(); sections(routeOf().split('?')[0]); }
+ if (d.open) { pkItems(); pkFill(); }
+ });
}
function pkGo(href) {
pkBack = null; $('cmdk-dialog').close();
diff -ru /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/src/public/shell.css packages/webui/src/public/shell.css
--- /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/src/public/shell.css 2026-10-10 12:14:49.000000000 -0500
+++ packages/webui/src/public/shell.css 2026-10-10 13:15:05.000000000 -0500
@@ -101,8 +101,9 @@
body.on-bus #board-refused{display:none}
/* Toast: one line, polite, bottom centre. */
-.toast{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);display:none;align-items:center;gap:10px;background:var(--strong);color:var(--surface);padding:6px 8px 6px 14px;border-radius:var(--r);font-size:13px;z-index:60;max-width:calc(100% - 24px);box-shadow:var(--shadow)}
-.toast.on{display:flex}
+.toast{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);display:flex;align-items:center;gap:10px;background:var(--strong);color:var(--surface);padding:6px 8px 6px 14px;border-radius:var(--r);font-size:13px;z-index:60;max-width:calc(100% - 24px);box-shadow:var(--shadow)}
+/* Off, the toast stays in the accessibility tree, empty and visually hidden, so the first copy is announced. */
+.toast:not(.on){width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;box-shadow:none}
.toast span{min-width:0;overflow-wrap:anywhere}
.toast.err{background:var(--danger)}
.toast button{font:inherit;font-weight:600;border:1px solid currentColor;background:none;color:inherit;border-radius:4px;padding:1px 8px;cursor:pointer}
@@ -164,8 +165,9 @@
.cmd-right{margin-left:0;flex:1 1 100%;flex-wrap:nowrap;min-width:0}
.cmd-right label{flex:1 1 0;min-width:0}
.cmd-right .btn{flex:none}
- /* The section list wraps rather than scrolling, so a blocking count never hides a section. */
- .s1-sections{display:flex;flex-wrap:wrap;overflow-x:visible;flex-basis:100%;gap:2px;padding-bottom:2px}
+ /* The section list is one strip that scrolls inside itself (IMPLEMENTING.md), so more sections never add rows. */
+ .s1-sections{display:flex;flex-wrap:nowrap;overflow-x:auto;flex-basis:100%;min-width:0;max-width:100%;gap:2px;padding-bottom:2px;scrollbar-width:thin}
+ .s1-sections>li{flex:none}
.s1-section{border-radius:var(--r);padding:5px 8px;white-space:nowrap;flex:none}
.inspector{inset:0;top:0;width:100%;z-index:40;box-shadow:none}
}
diff -ru /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/tests/shell.test.mjs packages/webui/tests/shell.test.mjs
--- /home/jwoltje/darkwing-scratch/r53a/snap/packages/webui/tests/shell.test.mjs 2026-10-10 12:23:48.000000000 -0500
+++ packages/webui/tests/shell.test.mjs 2026-10-10 13:15:05.000000000 -0500
@@ -35,17 +35,21 @@
});
test('shipped strings are unchanged, and the web keeps its write boundary', () => {
- // Markup in the source (<code>, <b>) is not part of the string.
+ // Markup in the source (<code>, <b>) is not part of the string. Each string is counted in the
+ // file that ships it, so dropping one of two copies fails too.
const src = ['app.js', 'bus.js', 'index.html'].map(f => read(f).toString()).join('\n');
- const plain = src.replace(/<\/?(code|b)>/g, '');
- for (const s of [
- 'Copied: ${text}',
- 'The clipboard is unavailable. The command is selected; press Ctrl+C to copy it.',
- 'Decisions are answered in a terminal with mosaic decide; Console only shows them.',
- 'No task ${txt(ref)} in this business.', 'No decision with id ${txt(id)}.', '${txt(b)} is not a decision id.', '${txt(b)} is not a task reference.',
- 'No page at this address.', 'Nothing is waiting on you.', 'No tasks in this business yet.', 'Tasks appear after the PM creates them or the poller reads them from Vikunja.',
- 'No open tasks.', 'No role is held right now.', 'Nothing of this kind in the trail.', 'Console shows nothing rather than a guess. Nothing was written.',
- ]) assert.ok(plain.includes(s), s);
+ const plain = Object.fromEntries(['app.js', 'bus.js'].map(f => [f, read(f).toString().replace(/<\/?(code|b)>/g, '')]));
+ for (const [file, s, n = 1] of [
+ ['bus.js', 'Copied: ${text}'],
+ ['bus.js', 'The clipboard is unavailable. The command is selected; press Ctrl+C to copy it.'],
+ ['bus.js', 'Decisions are answered in a terminal with mosaic decide; Console only shows them.'],
+ ['bus.js', 'No task ${txt(ref)} in this business.', 2], ['bus.js', 'No decision with id ${txt(id)}.'],
+ ['bus.js', '${txt(b)} is not a decision id.'], ['bus.js', '${txt(b)} is not a task reference.'],
+ ['bus.js', 'No page at this address.'], ['app.js', 'Nothing is waiting on you.'], ['bus.js', 'Nothing is waiting on you.'],
+ ['bus.js', 'No tasks in this business yet.'], ['bus.js', 'Tasks appear after the PM creates them or the poller reads them from Vikunja.'],
+ ['bus.js', 'No open tasks.'], ['bus.js', 'No role is held right now.'], ['bus.js', 'Nothing of this kind in the trail.'],
+ ['bus.js', 'Console shows nothing rather than a guess. Nothing was written.'],
+ ]) assert.equal(plain[file].split(s).length - 1, n, `${file}: ${s}`);
// No Resolve control and no bus write: the only POSTs are the board's Seen and reply.
assert.doesNotMatch(src, /<button[^>]*>[^<]*(Resolve|Decide|Approve)/i);
assert.doesNotMatch(read('bus.js').toString(), /method:\s*['"](POST|PUT|PATCH|DELETE)/);
@@ -121,7 +125,7 @@
// Board, empty: what would appear and where it comes from.
assert.match(await text('#sessions .empty'), /No sessions match these filters\.Sessions appear here when the board scan finds/);
assert.match(await text('#waiting .empty'), /Nothing is waiting on you\./);
- assert.match(await text('#fresh-board'), /^Board scanned \d\d:\d\d:\d\d/);
+ assert.match(await text('#fresh-board'), /^Board scanned \d\d:\d\d:\d\d UTC/);
await flat(); await shot('board-empty-400');
// Board, a failed read over kept data: the data stays, labelled with its scan time, and the bar says stale.
@@ -135,16 +139,30 @@
assert.match(await text('#fresh-board'), /stale: the last refresh failed/);
await flat(); await shot('board-stale-400');
- // Board, a refusal: fails closed, the kept rows are hidden, not shown as current.
+ // Board, a refusal: fails closed. The kept rows are hidden, and so is everything drawn from
+ // them: the open inspector, the project tree's counts and the footer.
+ await b.evaluate('document.querySelector("table.sessions [data-open]").click()'); await wait('!document.querySelector("#inspector").hidden');
+ assert.equal(await text('#footer'), 'waiting 1 · working 1');
+ assert.equal(await b.evaluate('document.querySelectorAll("#projects .count").length'), 2);
brd.refuse(); await refresh();
assert.equal(await b.evaluate('document.querySelector("#error").className'), 'banner ref');
assert.match(await text('#error'), /The board refused the read\. refused: board-refused/);
assert.equal(await b.evaluate('getComputedStyle(document.querySelector("#board-view")).display'), 'none');
assert.equal(await b.evaluate('document.querySelector("#board-refused").hidden'), false);
+ assert.equal(await b.evaluate('document.querySelector("#inspector").hidden'), true);
+ assert.equal(await b.evaluate('document.body.classList.contains("has-inspector")'), false);
+ assert.equal(await b.evaluate('document.querySelectorAll("#projects .count, #projects [data-project]").length'), 0);
+ assert.equal(await text('#footer'), 'Board not read: refused');
+ assert.equal(await text('#fresh-board'), 'Board not read: refused');
+ assert.doesNotMatch(await text('#status'), /Scanned/);
await flat(); await shot('board-refused-400');
brd.one(); await refresh();
assert.equal(await b.evaluate('document.querySelector("#error").hidden'), true);
assert.notEqual(await b.evaluate('getComputedStyle(document.querySelector("#board-view")).display'), 'none');
+ assert.equal(await text('#footer'), 'waiting 1 · working 1');
+ // A good read after a failed one drops the stale words.
+ brd.fail(); await refresh(); assert.match(await text('#fresh-board'), /stale/);
+ brd.one(); await refresh(); assert.match(await text('#fresh-board'), /^Board scanned \d\d:\d\d:\d\d UTC/); assert.doesNotMatch(await text('#fresh-board'), /stale/);
// Keyboard on the board: down moves between rows, Enter opens, Esc closes and returns focus.
await b.evaluate('document.querySelector("table.sessions [data-open]").focus()');
@@ -159,12 +177,16 @@
await b.evaluate('document.querySelector(".skip").focus();document.querySelector(".skip").blur()');
for (const sel of ['#cmdk', '#sections a', '#refresh']) {
await b.evaluate(`document.querySelector(${JSON.stringify(sel)}).focus()`);
- const ring = await b.evaluate(`(()=>{const c=getComputedStyle(document.activeElement);return [c.outlineStyle, parseFloat(c.outlineWidth)]})()`);
- assert.notEqual(ring[0], 'none', `${sel} focus ring`); assert.ok(ring[1] >= 2, `${sel} outline ${ring[1]}px`);
+ const ring = await b.evaluate(`(()=>{const c=getComputedStyle(document.activeElement);return [c.outlineStyle, parseFloat(c.outlineWidth), parseFloat(c.outlineOffset)]})()`);
+ assert.notEqual(ring[0], 'none', `${sel} focus ring`); assert.ok(ring[1] >= 2, `${sel} outline ${ring[1]}px`); assert.equal(ring[2], 2, `${sel} outline offset`);
}
// Bus pages, empty: each says what would appear and where it comes from.
- for (const [hash, h1, words] of [['#/inbox', 'Inbox', /Nothing is waiting on you\./], ['#/tasks', 'Tasks', /No tasks in this business yet\./], ['#/agents', 'Agents', /No role is held right now\./]]) {
+ for (const [hash, h1, words] of [
+ ['#/inbox', 'Inbox', /^Nothing is waiting on you\.A decision shows here when an agent raises one routed to you through the bus\.$/],
+ ['#/tasks', 'Tasks', /^No tasks in this business yet\.Tasks appear after the PM creates them or the poller reads them from Vikunja\.$/],
+ ['#/agents', 'Agents', /^No role is held right now\.A role shows here while a run holds its claim on the bus\.$/],
+ ]) {
await go(hash, h1);
assert.match(await text('#bus-view .empty'), words, hash);
assert.match(await text('#fresh-bus'), /^Bus read \d\d:\d\d:\d\d UTC/, hash);
@@ -172,11 +194,23 @@
}
assert.equal(await b.evaluate('getComputedStyle(document.querySelector("#fresh-board")).display'), 'none');
assert.equal(await b.evaluate('document.querySelectorAll("#sections svg use").length'), 4);
+ for (const sel of ['#fresh', '#session-count']) assert.equal(await b.evaluate(`getComputedStyle(document.querySelector(${JSON.stringify(sel)})).fontVariantNumeric`), 'tabular-nums', sel);
+ // Before any copy the toast is an empty live region still in the accessibility tree, out of sight.
+ assert.deepEqual(await b.evaluate('(()=>{const t=document.querySelector("#toast"),c=getComputedStyle(t),r=t.getBoundingClientRect();return [c.display!=="none",c.visibility,t.textContent,r.width<=1&&r.height<=1]})()'), [true, 'visible', '', true]);
+ // Below 760px the section list is one strip that scrolls inside itself: every link on one
+ // line, no label wrapped, and still no sideways scroll on the page. 360px, then back to 400px.
+ for (const w of [360, 400]) {
+ await b.viewport(w, 900);
+ assert.equal(await b.evaluate('new Set([...document.querySelectorAll("#sections a")].map(a=>a.offsetTop)).size'), 1, `one row at ${w}px`);
+ assert.match(await b.evaluate('getComputedStyle(document.querySelector("#sections")).overflowX'), /^(auto|scroll)$/, `strip scrolls at ${w}px`);
+ assert.deepEqual(await b.evaluate('[...document.querySelectorAll("#sections a")].filter(a=>a.getClientRects().length>1||a.scrollWidth>a.clientWidth+1).map(a=>a.textContent)'), [], `no wrapped label at ${w}px`);
+ await flat();
+ }
// Bus, a failed read over kept data: the kept rows stay under a stale banner; the bar says stale.
s.rows.tasks = [{ task_ref: 'vikunja:32/7', fields: { title: 'Kept task', bucket: 2, done: false, assignees: [] }, source: 'self' }, { task_ref: 'vikunja:32/8', fields: { title: 'Second task', bucket: 2, done: false, assignees: [] }, source: 'self' }];
await go('#/trail/task/vikunja%3A32%2F7', 'Trail for task #7');
- assert.match(await text('#bus-view .empty'), /Nothing of this kind in the trail\./);
+ assert.match(await text('#bus-view .empty'), /^Nothing of this kind in the trail\.Every step the stack takes on this subject writes a trail row\.$/);
await flat(); await shot('bus-empty-trail-400');
await go('#/tasks', 'Tasks');
assert.equal(await b.evaluate('document.querySelectorAll("#bus-view td.s1-title a").length'), 2);
@@ -197,7 +231,7 @@
assert.equal(await b.evaluate('document.querySelector("#bus-view .banner.ref code").textContent'), 'human-required');
assert.equal(await b.evaluate('document.querySelectorAll("#bus-view td.s1-title a").length'), 0);
assert.match(await text('#bus-view .empty'), /Nothing to show\./);
- assert.match(await text('#fresh-bus'), /not read: the read failed/);
+ assert.equal(await text('#fresh-bus'), 'Bus not read: refused');
await flat(); await shot('bus-refused-400');
s.fail(null);
@@ -209,6 +243,8 @@
// Ctrl+K: opens on the views and the kept tasks, filters, Enter goes, Esc restores focus.
await go('#/inbox', 'Inbox');
await b.evaluate('document.querySelector("#refresh").focus()');
+ await b.key('k', 'KeyK', 3); await new Promise(r => setTimeout(r, 200));
+ assert.equal(await b.evaluate('document.querySelector("#cmdk-dialog").open'), false, 'Ctrl+Alt+K opens nothing');
await b.key('k', 'KeyK', 2); await wait('document.querySelector("#cmdk-dialog").open');
assert.equal(await b.evaluate('document.activeElement.id'), 'cmdk-input');
await wait('[...document.querySelectorAll("#cmdk-list [data-href]")].some(e=>e.dataset.href==="#/tasks/vikunja%3A32%2F7")');
@@ -251,6 +287,8 @@
await wait('document.querySelector("#toast").classList.contains("err")');
assert.equal(await text('#toast span'), 'The clipboard is unavailable. The command is selected; press Ctrl+C to copy it.');
assert.equal(await b.evaluate('getSelection().toString()'), `mosaic decide ${'d'.repeat(32)} yes`);
+ await new Promise(r => setTimeout(r, 3000));
+ assert.equal(await b.evaluate('document.querySelector("#toast").className'), 'toast on err', 'the error toast stays until dismissed');
await b.evaluate('document.querySelector("#toast [data-dismiss]").click()');
assert.equal(await b.evaluate('document.querySelector("#toast").classList.contains("on")'), false);
@@ -293,6 +331,7 @@
const count = sel => b.evaluate(`document.querySelectorAll(${JSON.stringify(sel)}).length`);
const flat = async () => assert.equal(await b.evaluate('document.documentElement.scrollWidth<=document.documentElement.clientWidth'), true, 'sideways scroll at 400px');
const refresh = async () => { await b.evaluate('document.querySelector("#refresh").click()'); await wait('!document.querySelector("#refresh").disabled'); };
+ const go = async (hash, h1) => { await b.evaluate(`location.hash=${JSON.stringify(hash)}`); await wait(`document.querySelector("#bus-view h1")?.textContent.startsWith(${JSON.stringify(h1)}) && !document.querySelector("#bus-view [aria-busy]")`); };
try {
await b.call('Page.addScriptToEvaluateOnNewDocument', { source: 'window.errors=[];addEventListener("error",e=>errors.push(e.message));addEventListener("unhandledrejection",e=>errors.push(String(e.reason)));window.requests=[];const f=window.fetch;window.fetch=(u,o={})=>{requests.push([(o.method||"GET").toUpperCase(),String(u)]);return f(u,o)};' });
await b.viewport(400, 900);
@@ -332,17 +371,55 @@
assert.equal(await count(rows), n, `${hash} keeps its rows`);
assert.match(await text('#fresh-bus'), /stale: the last read failed/, hash);
await flat(); await shot(`${name}-stale-400`);
- // A refusal: no kept rows, the code shown, nothing current.
- s.fail('human-required'); await refresh();
- await wait('!!document.querySelector("#bus-view .banner.ref")');
- assert.equal(await text('#bus-view .banner.ref code'), 'human-required', hash);
- assert.equal(await count(rows), 0, `${hash} hides kept rows on a refusal`);
- assert.match(await text('#bus-view .empty'), /Nothing to show\./, hash);
- await flat(); await shot(`${name}-refused-400`);
- s.fail(null); await refresh();
- await wait(`!document.querySelector("#bus-view .banner") && document.querySelectorAll(${JSON.stringify(rows)}).length===${n}`);
+ // A refusal, by the bus (403) or for want of one (no-bus-host): no kept rows, the code
+ // shown, nothing current, and no count in the section list.
+ for (const code of ['human-required', 'no-bus-host']) {
+ s.fail(code); await refresh();
+ await wait('!!document.querySelector("#bus-view .banner.ref")');
+ assert.equal(await text('#bus-view .banner.ref code'), code, hash);
+ assert.equal(await count(rows), 0, `${hash} hides kept rows on a ${code} refusal`);
+ assert.match(await text('#bus-view .empty'), /Nothing to show\./, hash);
+ assert.equal(await text('#fresh-bus'), 'Bus not read: refused', hash);
+ assert.equal(await count('#sections .count, #sections .tag-block'), 0, `${hash} drops the Inbox count on a ${code} refusal`);
+ if (code === 'human-required') { await flat(); await shot(`${name}-refused-400`); }
+ s.fail(null); await refresh();
+ await wait(`!document.querySelector("#bus-view .banner") && document.querySelectorAll(${JSON.stringify(rows)}).length===${n}`);
+ }
}
+ // After a refusal the palette lists only the views: no kept decision or task.
+ const views = ['#/', '#/inbox', '#/tasks', '#/agents'];
+ const palette = '[...document.querySelectorAll("#cmdk-list [data-href]")].map(e=>e.dataset.href)';
+ await b.evaluate('location.hash="#/inbox"'); await wait('document.querySelectorAll("#sections .count").length===1');
+ assert.match(await text('#sections'), /Inbox 1 1 blocking/);
+ s.fail('human-required'); await refresh(); await wait('!!document.querySelector("#bus-view .banner.ref")');
+ await b.key('k', 'KeyK', 2); await wait('document.querySelector("#cmdk-dialog").open');
+ assert.deepEqual(await b.evaluate(palette), views);
+ await b.key('Escape'); await wait('!document.querySelector("#cmdk-dialog").open');
+ s.fail(null);
+ // The palette's own reads: kept rows show until a refused refetch, then go, with the count.
+ await go('#/tasks', 'Tasks'); await b.evaluate('location.hash="#/inbox"'); await wait('document.querySelectorAll("#sections .count").length===1');
+ await b.key('k', 'KeyK', 2); await wait(`${palette}.some(h=>h.startsWith("#/tasks/"))`);
+ await b.key('Escape'); await wait('!document.querySelector("#cmdk-dialog").open');
+ s.fail('human-required');
+ await b.key('k', 'KeyK', 2); await wait(`document.querySelector("#cmdk-dialog").open && ${palette}.length===${views.length} && !document.querySelector("#sections .count")`);
+ assert.deepEqual(await b.evaluate(palette), views);
+ await b.key('Escape'); await wait('!document.querySelector("#cmdk-dialog").open');
+ // Palette labels are bus text: markup in a question or a task title shows as text.
+ s.fail(null);
+ s.rows.inbox = [{ ...s.rows.inbox[0], question: '<img src=x onerror="errors.push(1)" id="pk-q">Push?' }];
+ s.rows.tasks = [{ ...s.rows.tasks[0], fields: { ...s.rows.tasks[0].fields, title: '<b id="pk-t">Bold</b> task' } }, s.rows.tasks[1]];
+ await b.key('k', 'KeyK', 2); await wait(`${palette}.length===${views.length + 3}`);
+ assert.equal(await count('#cmdk-list #pk-q, #cmdk-list #pk-t, #cmdk-list img, #cmdk-list b'), 0);
+ assert.deepEqual(await b.evaluate('[...document.querySelectorAll("#cmdk-list [data-href] span:first-child")].slice(4).map(e=>e.textContent)'), ['<img src=x onerror="errors.push(1)" id="pk-q">Push?', '#7 <b id="pk-t">Bold</b> task', '#8 Second task']);
+ await b.key('Escape'); await wait('!document.querySelector("#cmdk-dialog").open');
+ // The board page's Inbox read: a refusal there drops the count too.
+ s.fail(null); await refresh(); await wait('document.querySelectorAll("#sections .count").length===1');
+ s.fail('no-bus-host'); await b.evaluate('location.hash="#/"'); await wait('!document.querySelector("#sections .count")');
+ await b.key('k', 'KeyK', 2); await wait('document.querySelector("#cmdk-dialog").open');
+ assert.deepEqual(await b.evaluate(palette), views);
+ await b.key('Escape'); s.fail(null);
+
assert.deepEqual(await b.evaluate('window.errors'), []);
assert.deepEqual(await b.evaluate('window.requests.filter(([m])=>m!=="GET")'), []);
} finally { await b.close(); await close(web); await close(brd.server); }
@@ -0,0 +1,9 @@
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2559.882854ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2631.856328
@@ -0,0 +1,9 @@
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2599.851202ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2668.022259
@@ -0,0 +1,9 @@
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2438.997108ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2506.864306
@@ -0,0 +1,14 @@
start 2026-10-10T18:21:44Z
node-webui exit=0 ℹ pass 27 ℹ fail 0
build-tokens --check exit=0 tokens.css is current
test-auth exit=0 selftest: 15 passed, 0 failed
test-conductor exit=0 selftest: 17 passed, 0 failed
test-config exit=0 selftest: 24 passed, 0 failed
test-discord exit=0 discord suite: 66 passed, 0 failed
test-extension-package exit=0 extension package selftest: 18 passed, 0 failed
test-foundation exit=0 selftest: 44 passed, 0 failed
test-queue exit=0 queue suite: 27 passed, 0 failed
test-release exit=0 selftest: 4 passed, 0 failed
test-task exit=0 selftest: 26 passed, 0 failed
end 2026-10-10T18:24:57Z
DONE
@@ -0,0 +1,17 @@
OK status with missing harness credential exits 3 and still lists accounts
OK status reports harness credential (read-only) + mosaic accounts
OK api key material never reaches output
OK oauth token material never reaches output
OK unparseable credential file exits 2
OK symlinked credential file exits 4
OK env-side credential names reported
OK env var values never reach output
OK accounts without an accounts dir reports none and creates nothing
OK accounts lists files and marks the active one
OK loose account perms flagged in listing
OK agent --auth with missing account file refuses (exit 4)
OK agent --auth with non-0600 account file refuses
OK agent --auth with invalid account name refuses
OK auth.sh without valid config refuses
selftest: 15 passed, 0 failed
@@ -0,0 +1,55 @@
Note: switching to '2d4d4e7dbc65767b1e65426ca5fadb9136a8be5b'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
Not currently on any branch.
nothing to commit, working tree clean
Note: switching to '2d4d4e7dbc65767b1e65426ca5fadb9136a8be5b'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
OK apply: attribution in commit subject
OK apply: target tree clean after commit
OK disallowed path refused (exit 1)
OK disallowed path: target untouched
OK syntax gate refused broken .mjs (exit 1)
OK syntax gate: target untouched
OK suite failure refused (exit 1)
OK suite failure: target reverted to clean
OK disabled policy refused (exit 2)
OK disabled policy: target untouched
OK failed run refused (exit 1)
OK failed run: target untouched
OK missing run exits 4 (exit 4)
OK invalid policy exits 2 (exit 2)
selftest: 17 passed, 0 failed
@@ -0,0 +1,26 @@
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK failed validation modified nothing
selftest: 24 passed, 0 failed
@@ -0,0 +1,70 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 178)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 66 passed, 0 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 2558265 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
OK sync succeeds after interruption recovery
OK unlocked stale lock file does not block
OK active lock refuses a concurrent sync
OK source symlink fails closed
OK nested second entrypoint fails closed
extension package selftest: 18 passed, 0 failed
@@ -0,0 +1,53 @@
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
OK syntax: scripts/foundation-inspect.mjs
OK syntax: scripts/foundation/strict-json.mjs
OK syntax: scripts/foundation/canonical.mjs
OK syntax: scripts/foundation/resolve.mjs
OK syntax: scripts/foundation/validate-record.mjs
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
OK syntax: scripts/foundation/canonical.test.mjs
OK syntax: scripts/foundation/cli.test.mjs
OK syntax: scripts/foundation/fixtures.test.mjs
OK syntax: scripts/foundation/resolve.test.mjs
OK syntax: scripts/foundation/strict-json.test.mjs
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
OK fixture generator runs
OK checked-in fixtures/bundles equal a fresh generation
OK checked-in fixtures/raw equal a fresh generation
OK checked-in fixtures/index.json equal a fresh generation
OK checked-in demo bundles equal a fresh generation
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test scripts/foundation/ (ℹ pass 80)
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
OK oracle: zero schema-column disagreements with the pinned checker
OK oracle: strict-only profile refusals are counted and asserted
OK demo: permitted read preview exits 0 (exit 0)
OK demo: permitted file.change preview exits 0 (exit 0)
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
OK demo: revoked registration is refused (exit 3) (exit 3)
OK demo: message is not authority (exit 3) (exit 3)
OK usage: no arguments exits 2 (exit 2)
OK io: missing file exits 4 (exit 4)
OK io: directory exits 4 (exit 4)
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
OK bound: oversize fixture exits 2 (exit 2)
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
OK text output starts with the disclaimer
OK json output is valid JSON with result allowed and exactly the charter §7 fields
OK json golden matches byte-for-byte
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
OK canary never printed (bundle run and credential-file run)
OK a non-bundle JSON file is refused at the shape gate, not read into output
OK no field of the non-bundle file is echoed
selftest: 44 passed, 0 failed
@@ -0,0 +1,35 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 148
ℹ pass 148
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/home/jwoltje/darkwing-scratch/r53b/cand) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed
@@ -0,0 +1,16 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
OK status safe on empty state (exit 0)
OK status created no pointer
OK fault-injected activation refuses (exit 1)
OK refused activation wrote no pointer
OK refusal logged exactly once with valid fields
OK healthy activation succeeds (exit 0)
OK pointer written with valid fields
OK repeat activation succeeds (log grows) (exit 0)
OK log is append-only across activations
OK rollback without previous refuses (exit 1)
selftest: 14 passed, 0 failed
@@ -0,0 +1,7 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
skip state-machine cases (docker daemon unavailable)
selftest: 4 passed, 0 failed
@@ -0,0 +1,32 @@
OK valid task validates (exit 0)
OK unknown task key exits 2 (exit 2)
OK unsupported taskVersion exits 2 (exit 2)
OK invalid task id exits 2 (exit 2)
OK empty prompt exits 2 (exit 2)
OK NUL in expectExact exits 2 (exit 2)
OK out-of-range timeout exits 2 (exit 2)
OK missing mission file exits 4 (exit 4)
OK task with valid mission validates (exit 0)
OK invalid mission exits 2 (exit 2)
OK validate missing task exits 4 (exit 4)
OK validation does not modify the task file
OK prune dry-run exits 0 (exit 0)
OK dry-run deleted nothing
OK prune --keep=2 --yes removes oldest (exit 0)
OK kept exactly 2 newest runs
OK newest run kept, oldest pruned
OK append-only receipt written (3 entries)
OK sessions/workspaces untouched by prune
OK prune with invalid keep exits 4 (exit 4)
skip adapter seam cases (docker daemon unavailable)
skip workspace/capability cases (docker daemon unavailable)
skip live task cases (docker unavailable)
OK onboard without name exits 4 (non-interactive) (exit 4)
OK onboard --name renders profile (exit 0)
OK profile written
OK canon structure: required filled, optional placeholdered
OK canon sections present
skip live recall pair: user recall run, recalled user name (docker daemon unavailable)
OK no agent identity on headless run
selftest: 26 passed, 0 failed
@@ -0,0 +1,118 @@
// Darkwing row 53 round 2 probes: the round 1 probes again, then a board 403 followed by another
// failure (P9), and a board 403 with a conversation open (P10).
// Run from a worktree's packages/webui: node --test <this file> with WT set to that worktree.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createServer, request } from 'node:http';
import { pathToFileURL } from 'node:url';
const WT = process.env.WT;
const u = p => pathToFileURL(`${WT}/packages/webui/${p}`).href;
const { browser } = await import(u('tests/browser.mjs'));
const { close } = await import(u('tests/fixture.mjs'));
const { startServer } = await import(u('src/serve.mjs'));
const { BusReadError } = await import(u('src/bus.mjs'));
const H = await import(u('tests/history-fixture.mjs'));
const out = [];
const note = (k, v) => { out.push(`${k}: ${JSON.stringify(v)}`); };
test('probes, fake board and bus', { timeout: 120000 }, async () => {
let reply = null;
const one = { agent: 'agent1', project: 'secretproj', state: 'waiting', waitingOnYou: true, task: 'Kept fixture task', lastActivity: '2026-10-10T00:00:00Z', lastAssistantText: 'Input needed: kept text', registered: null };
const ok = () => { reply = { status: 200, body: { sessions: [one, { ...one, agent: 'agent2', state: 'working', waitingOnYou: false }], counts: { waiting: 1, working: 1 }, generatedAt: new Date().toISOString() } }; };
ok();
const brd = createServer(async (req, res) => { for await (const c of req) void c; res.writeHead(reply.status, { 'content-type': 'application/json' }); res.end(JSON.stringify(reply.body)); });
await new Promise(r => brd.listen(0, '127.0.0.1', r));
const rows = { inbox: [{ id: 'd'.repeat(32), question: 'Kept question?', class: 'gated', action: 'git.push.protected', blocking: true, options: [{ key: 'yes', text: 'Yes' }], at: new Date().toISOString(), raised_by: 'coder' }], tasks: [{ task_ref: 'vikunja:32/7', fields: { title: 'Kept task title', bucket: 2, done: false, assignees: [] }, source: 'self' }], agents: [], trail: [] };
let fail = null;
const verb = n => async () => { if (fail) throw new BusReadError(fail, `fixture ${fail}`); return structuredClone(rows[n]); };
const web = await startServer({ port: 0, board: `http://127.0.0.1:${brd.address().port}`, bus: { inbox: verb('inbox'), tasks: verb('tasks'), agents: verb('agents'), trail: verb('trail') } });
const base = `http://127.0.0.1:${web.address().port}/`, b = await browser();
const wait = expr => b.evaluate(`(async()=>{for(let i=0;i<100;i++){if(${expr})return true;await new Promise(r=>setTimeout(r,50))}throw new Error('timed out: '+${JSON.stringify(expr)})})()`);
const vis = sel => b.evaluate(`(()=>{const e=document.querySelector(${JSON.stringify(sel)});if(!e)return null;return {shown:!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)&&!e.closest('[hidden]'),text:e.innerText.slice(0,200)}})()`);
const refresh = async () => { await b.evaluate('document.querySelector("#refresh").click()'); await wait('!document.querySelector("#refresh").disabled'); };
try {
await b.viewport(1440, 900);
await b.navigate(base); await wait('document.querySelectorAll("table.sessions tbody tr").length===2');
await b.evaluate('document.querySelector("table.sessions [data-open]").click()');
await wait('document.querySelector("#inspector-title")?.textContent==="agent1"');
reply = { status: 403, body: { error: 'non-local Host refused' } };
await refresh();
await wait('document.querySelector("#error").className==="banner ref"');
note('P1 board refused: board-view display', await b.evaluate('getComputedStyle(document.querySelector("#board-view")).display'));
note('P1 board refused: inspector', await vis('#inspector'));
note('P2 board refused: projects tree', await vis('#projects'));
note('P2 board refused: footer', await vis('#footer'));
note('P2 board refused: #status', await vis('#status'));
note('P2 board refused: #fresh-board', await vis('#fresh-board'));
// P9: still no good read, now a 500. Do the rows from before the refusal come back?
reply = { status: 500, body: { error: 'scan failed' } };
await refresh(); await wait('document.querySelector("#error").className==="banner err"');
note('P9 403 then 500: board-view display', await b.evaluate('getComputedStyle(document.querySelector("#board-view")).display'));
note('P9 403 then 500: session rows in the DOM and shown', await b.evaluate('[document.querySelectorAll("table.sessions tbody tr").length, !!document.querySelector("table.sessions")?.offsetHeight]'));
note('P9 403 then 500: #waiting', await vis('#waiting'));
note('P9 403 then 500: #sessions', await vis('#sessions'));
note('P9 403 then 500: projects, footer, status, fresh', [await vis('#projects'), await vis('#footer'), await vis('#status'), await vis('#fresh-board')]);
note('P9 403 then 500: banner', await vis('#error'));
ok(); await refresh(); await wait('document.querySelector("#error").hidden');
note('P9b recovered: rows, footer', [await b.evaluate('document.querySelectorAll("table.sessions tbody tr").length'), await vis('#footer')]);
// P3/P4: bus pages read once, then the bus refuses.
await b.evaluate('location.hash="#/tasks"'); await wait('document.querySelectorAll("#bus-view td.s1-title a").length===1');
await b.evaluate('location.hash="#/inbox"'); await wait('!!document.querySelector("#bus-view .s1-dec-open")');
fail = 'human-required'; await refresh(); await wait('!!document.querySelector("#bus-view .banner.ref")');
note('P4 bus refused: section list', await vis('#sections'));
await b.evaluate('document.querySelector("#cmdk").click()'); await wait('document.querySelector("#cmdk-dialog").open');
await new Promise(r => setTimeout(r, 500));
note('P3 bus refused: palette list', await b.evaluate('[...document.querySelectorAll("#cmdk-list li")].map(l=>l.innerText.replace(/\\n/g," | "))'));
await b.key('Escape');
await b.evaluate('location.hash="#/"'); await wait('document.querySelectorAll("table.sessions tbody tr").length===2');
await new Promise(r => setTimeout(r, 500));
note('P4b board page, bus refused: section list', await vis('#sections'));
fail = 'not-configured'; await b.evaluate('location.hash="#/tasks"'); await wait('!!document.querySelector("#bus-view .banner")');
note('P6 not-configured: banner class, task links', await b.evaluate('[document.querySelector("#bus-view .banner").className, document.querySelectorAll("#bus-view td.s1-title a").length]'));
fail = 'outcome-unknown'; await refresh(); await wait('!!document.querySelector("#bus-view .banner")');
note('P7 refusal then outcome-unknown: banner class, task links, banner text', await b.evaluate('[document.querySelector("#bus-view .banner").className, document.querySelectorAll("#bus-view td.s1-title a").length, document.querySelector("#bus-view .banner").innerText.slice(0,160)]'));
const ax = await b.call('Accessibility.getFullAXTree', {});
note('P8 status/live nodes before any toast', ax.nodes.filter(n => ['status'].includes(n.role?.value)).map(n => n.name?.value ?? ''));
note('P8 toast display, rect', await b.evaluate('(()=>{const t=document.querySelector("#toast"),r=t.getBoundingClientRect();return [getComputedStyle(t).display,r.width,r.height,t.querySelectorAll("button").length]})()'));
note('errors', await b.evaluate('window.errors||null'));
} finally {
await b.close(); await close(web); await new Promise(r => brd.close(r));
}
});
test('probe P10, a conversation open when the board refuses', { timeout: 120000 }, async () => {
const fx = await H.historyFixture();
const s = H.session(`${fx.sessionsDir}/2026-10-10T00-00-00-000Z_sess-1.jsonl`, fx.projectRoot);
s.add(H.user('Secret question from before the refusal')); s.add(H.assistant('Secret answer from before the refusal'));
// A proxy in front of the real board that can refuse every read with 403, as control-board's Host check does.
let refuse = false;
const up = new URL(`http://127.0.0.1:${fx.board.address().port}`);
const proxy = createServer((req, res) => {
if (refuse) { for (const _ of []) void _; res.writeHead(403, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'non-local Host refused' })); req.resume(); return; }
const p = request({ host: up.hostname, port: up.port, path: req.url, method: req.method, headers: { ...req.headers, host: up.host } }, r => { res.writeHead(r.statusCode, r.headers); r.pipe(res); });
req.pipe(p);
});
await new Promise(r => proxy.listen(0, '127.0.0.1', r));
const web = await startServer({ port: 0, board: `http://127.0.0.1:${proxy.address().port}` });
const b = await browser(), wait = H.waiter(b);
const vis = sel => b.evaluate(`(()=>{const e=document.querySelector(${JSON.stringify(sel)});if(!e)return null;return {shown:!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)&&!e.closest('[hidden]'),text:e.innerText.slice(0,200)}})()`);
try {
await b.viewport(1440, 900);
await b.navigate(`http://127.0.0.1:${web.address().port}/`);
await wait('!!document.querySelector("[data-history]")');
await b.evaluate('document.querySelector("[data-history]").click()');
await wait('document.querySelector("#conv-log")?.textContent.includes("Secret answer")');
refuse = true;
await b.evaluate('document.querySelector("#refresh").click()'); await wait('!document.querySelector("#refresh").disabled');
await wait('document.querySelector("#error").className==="banner ref"');
await new Promise(r => setTimeout(r, 500));
note('P10 conversation section', await vis('#conversation'));
note('P10 conv-log', await vis('#conv-log'));
note('P10 conv-meta, conv-title, conv-status', [await vis('#conv-meta'), await vis('#conv-title'), await vis('#conv-status')]);
note('P10 board-refused empty state', await vis('#board-refused'));
note('P10 conv-no-reply', await vis('#conv-no-reply'));
} finally {
console.log(out.join('\n'));
await b.close(); await close(web); await new Promise(r => proxy.close(r)); await fx.close();
}
});
@@ -0,0 +1,36 @@
✔ probes, fake board and bus (2848.393287ms)
P1 board refused: board-view display: "none"
P1 board refused: inspector: {"shown":false,"text":"agent1CloseStatewaitingProjectsecretprojTaskKept fixture taskTask sourceunknownTask set bynot applicable (task is not from a registration)Active projectunknownWorkspaceunknownModelunknownRegisteredNo "}
P2 board refused: projects tree: {"shown":true,"text":"Not read: the board refused the read."}
P2 board refused: footer: {"shown":true,"text":"Board not read: refused"}
P2 board refused: #status: {"shown":true,"text":"Not read: the board refused the read"}
P2 board refused: #fresh-board: {"shown":true,"text":"Board not read: refused"}
P9 403 then 500: board-view display: "block"
P9 403 then 500: session rows in the DOM and shown: [2,true]
P9 403 then 500: #waiting: {"shown":true,"text":"agent1\nwaiting\n\nsecretproj · unknown\n\nKept fixture task\n\nInput needed: kept text\n\nLast activity 2026-10-10T00:00:00Z\nHistorySeen"}
P9 403 then 500: #sessions: {"shown":true,"text":"AGENT\tSTATE\tTASK\tACTIVE PROJECT\tWORKSPACE\tMODEL\tREGISTERED\tLAST ACTIVITY\nagent1\nsecretproj\nHistory\twaiting\tKept fixture task\tunknown\tunknown\tunknown\n\tno\t2026-10-10T00:00:00Z\nagent2\nsecretproj\nHistory\t"}
P9 403 then 500: projects, footer, status, fresh: [{"shown":true,"text":"Not read: the board refused the read."},{"shown":true,"text":"Board not read: refused"},{"shown":true,"text":"Not read: the board refused the read"},{"shown":true,"text":"Board not read: the read failed"}]
P9 403 then 500: banner: {"shown":true,"text":"scan failed Board: http://127.0.0.1:46259. No board data loaded. Use Refresh to try again."}
P9b recovered: rows, footer: [2,{"shown":true,"text":"waiting 1 · working 1"}]
P4 bus refused: section list: {"shown":true,"text":"Board\nInbox\nTasks\nAgents"}
P3 bus refused: palette list: ["Board | view","Inbox | view","Tasks | view","Agents | view"]
P4b board page, bus refused: section list: {"shown":true,"text":"Board\nInbox\nTasks\nAgents"}
P6 not-configured: banner class, task links: ["banner ref",0]
P7 refusal then outcome-unknown: banner class, task links, banner text: ["banner err",0,"The bus did not answer in time. (outcome-unknown) Console shows nothing rather than a guess. Nothing was written.\n\nRead again"]
P8 status/live nodes before any toast: [""]
P8 toast display, rect: ["flex",1,1,0]
errors: null
P10 conversation section: {"shown":true,"text":"fixture conversation\n\nrepo · Secret question from before the refusal · started 2026-10-10T18:14:21.736Z\n\nSession\nSecret question from before the refusal · 2026-10-10T18:24:21.736Z\nBack to board\n\nHisto"}
P10 conv-log: {"shown":true,"text":"USER\n2026-10-10T18:24:21.736Z\nSecret question from before the refusal\nASSISTANT\n2026-10-10T18:24:21.736Z\nSecret answer from before the refusal"}
P10 conv-meta, conv-title, conv-status: [{"shown":true,"text":"repo · Secret question from before the refusal · started 2026-10-10T18:14:21.736Z"},{"shown":true,"text":"fixture conversation"},{"shown":true,"text":"History unavailable: non-local Host refused."}]
P10 board-refused empty state: {"shown":true,"text":"Nothing to show.\n\nThe board refused the read, so Console shows nothing rather than a guess. The reason is above."}
P10 conv-no-reply: {"shown":true,"text":"This session is no longer in the board scan."}
✔ probe P10, a conversation open when the board refuses (1888.587489ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 4949.888449
@@ -0,0 +1,236 @@
# Row 53, console tokens and shell restyle, round 2 review (Darkwing)
Issue #1542, request comment 27156, packet comment 27157, queue revs
362-364. Base `2d4d4e7d`. Candidate manifest sha256
`78c3aef963b7e35500c9f8afa6341057f1a6372ced70915d175e507b5fac8406`, the
same 16 files under `packages/webui`. The packet manifest and my snapshot
of the checkout both check 16 OK. `round2.patch` applied to my round 1
snapshot gives the round 2 files byte for byte, so the delta is those five
files and nothing else.
Verdict: **request changes**. All five of my round 1 items are fixed for
the cases I listed, and Sage's strip test is there and works. Every suite
is green and 35 of 37 mutants die. Two refusal gaps are left, and both
show data from before a board refusal:
- A board 403 followed by any other failed read brings the refused
session table and the "Waiting on you" card back on screen. Filbert
found the same thing (their C4, comment 27163).
- A board 403 while a conversation is open leaves the conversation and
its history on screen. I should have caught this in round 1. My probes
then never opened History, so they didn't.
Both fixes are a few lines each.
## Method
- Detached worktrees at `2d4d4e7d`, `cand` and `mutwt`, each with the 16
candidate files staged and checking 16 OK. `mutwt` still checks 16 OK
after the mutant runs (`r2/mut/manifest-after.txt`).
- `r2/gate.sh`: the `packages/webui` suite, `build-tokens.mjs --check`,
then every `scripts/test-*.sh` with `DOCKER_HOST=unix:///nonexistent.sock`,
18:21:44Z to 18:24:57Z.
- 37 mutants (`r2/mut/mutate.py`, `r2/mut/run.sh`), each against the full
webui suite, 18:25:08Z to 19:00:50Z. M01 to M20 are round 1's, with M11
rewritten for the strip. M21 to M37 are new and aim at the round 2 code.
- `test-release` with Docker, finished 19:01:30Z. Then the S5 browser test
three times on the unmutated candidate (`r2/out/s5-rep-*.txt`), see
note 3.
- Probes in Chromium through the candidate's `tests/browser.mjs`
(`r2/probes/probe2.test.mjs`, output `r2/probes/probes-cand.txt`). The
first test is my round 1 probe set again, a fake board and fake bus
verbs, plus P9. The second, P10, runs the real control-board from the
candidate's `tests/history-fixture.mjs` behind a small proxy that can
answer every request with 403, the way control-board's Host check does.
No tracker request.
- Read the five changed files against round 1's required list, Filbert's
comment 27143 and `IMPLEMENTING.md`.
Node v26.8.1, `TMPDIR=~/darkwing-scratch/r53b/tmp`.
## Suites
| Suite | Result |
|---|---|
| packages/webui (node) | 27/0 |
| build-tokens --check | rc 0, "tokens.css is current" |
| test-auth | 15/0 |
| test-conductor | 17/0 |
| test-config | 24/0 |
| test-discord | 66/0 |
| test-extension-package | 18/0 |
| test-foundation | 44/0 |
| test-queue | 27/0 |
| test-release | 4/0 without Docker, 14/0 with it |
| test-task, Docker unreachable | 26/0 |
As in round 1, I didn't run test-task with real Docker because it makes
live model calls. Dewey's run gave 98/0. Filbert's gate also ran the other
node packages, all green.
## Round 1 items
| Item | Result |
|---|---|
| Required 1, board refusal | Fixed for a single refusal. The inspector closes, the tree reads "Not read: the board refused the read.", the counts show "–", and the footer, `#status` and `#fresh-board` all say not read (P1, P2) |
| Required 2, bus refusal | Fixed. `forget()` clears `last` and the Inbox count from a view, from the board route and from `pkOpen`. The palette lists only the four views and the section list has no counts (P3, P4, P4b). P7 now shows no task rows |
| Required 3, tests | Done for the inspector. Not for an open conversation, see Required 2 below |
| Required 4, M01 | Done. `no-bus-host` runs over kept rows and M01 dies |
| Required 5, M08 | Done. Markup in a question and a task title, and M08 dies |
| Filbert C3 | Done. "Board not read: refused" |
The notes: the strip is now one row below 760px (note 1). The toast stays
in the accessibility tree while off, at 1×1 with `clip-path` (note 4, P8).
M09 and M16 are tested and die. Note 2 (freshness hidden below 760px) and
note 5 (Seen 403 wording) are unchanged, and that's fine.
## The strip test
Sage asked me to check it. `shell.test.mjs` sets 360px and then 400px and
asserts four things at each width:
- every `#sections a` has the same `offsetTop`;
- the list's computed `overflow-x` is `auto` or `scroll`;
- no label wraps (one client rect, `scrollWidth` within the box);
- the page doesn't scroll sideways.
All three of Sage's properties are pinned. Three mutants confirm it: M11
(back to wrap and visible) fails "one row at 360px", M37 (wrap with
`overflow-x:auto`) fails the same, and M36 (one row with
`overflow-x:visible`) fails "strip scrolls at 360px". M36 is caught only
by the computed-style check, because the fixture's labels fit at 360px
and the page never overflows. That check is the right one to have.
## Probes
Fixture as in round 1: one board with two sessions (project `secretproj`,
task "Kept fixture task"), one inbox decision and one task. P10 uses the
control-board fixture's session, whose history holds "Secret question from
before the refusal" and "Secret answer from before the refusal".
| Probe | Candidate |
|---|---|
| P1 board 403 with the inspector open | `#board-view` `display:none`, inspector hidden. Fixed |
| P2 board 403 | tree, footer, `#status` and `#fresh-board` say not read. Fixed |
| P3 bus `human-required` after a read | the palette lists only Board, Inbox, Tasks and Agents. Fixed |
| P4, P4b the same refusal | no Inbox count or BLOCKING chip, on a bus view and on the board page. Fixed |
| P6 `not-configured` after a good read | `banner ref`, no task rows |
| P7 refusal, then `outcome-unknown` | `banner err`, no task rows |
| P8 the toast before any copy | `display:flex`, 1×1, empty, in the tree |
| **P9** good read, board 403, then a 500 | `#board-view` is `display:block` again with both session rows and the waiting card ("agent1 … secretproj … Kept fixture task … Input needed: kept text"). The banner says "No board data loaded", `#fresh-board` "the read failed", the footer still "refused" |
| P9b then a good read | 2 rows, footer "waiting 1 · working 1". Recovery works |
| **P10** History open, then every board request 403 | `#conversation` stays shown with the title, the meta line and both secret messages in `#conv-log`. Beside it, `#board-refused` says "Nothing to show. The board refused the read, so Console shows nothing rather than a guess." |
P9: on a 403, `error()` sets `data = null` but leaves the markup in
`#sessions`, `#waiting` and `#seen`, and `render()` returns at once when
`!data`. The table hides only through `body.board-refused #board-view`.
The next `error()` that isn't a 403 removes that class and the old markup
shows again. The README line "A board refusal drops the last scan and
everything drawn from it" is not true after that second failure.
P10: `#conversation` sits outside `#board-view` (`index.html` line 17),
so the `board-refused` rule doesn't reach it. `convForm()` runs and the
reply form says "This session is no longer in the board scan", but the
history read before the refusal stays on screen. Filbert's T7 predicts
this from the code; P10 shows it with the real control-board.
## Mutants
35 of 37 killed (`r2/mut/summary.txt`). Every kill is an assertion or a
wait timeout, not a load error.
| Mutant | Change | Result |
|---|---|---|
| M01 | `refusal()` drops `not-configured` and `no-bus-host` | killed (was survived) |
| M02 | `error()`: `refused = false` | killed |
| M03 | no `body.board-refused` toggle | killed |
| M04 | the `refused:` code not in `<code>` | killed (was survived) |
| M05 | no stale words in the bus freshness line | killed |
| M06 | System sets `data-mode` | killed |
| M07 | toast text not escaped | **survived** |
| M08 | palette label not escaped | killed (was survived) |
| M09 | `:focus-visible` offset 0 | killed (was survived) |
| M10 | focus outline 1px | killed |
| M11 | strip back to wrap, `overflow-x` visible | killed (was survived) |
| M12 | no section icons | killed |
| M13 | `accept()` doesn't clear `board-refused` | killed |
| M14 | task-link ↑/↓ reversed | killed |
| M15 | `/icons.svg` served as `text/plain` | killed |
| M16 | no `tabular-nums` | killed (was survived) |
| M17 | storage key changed | killed |
| M18 | no `body.board-refused #board-view` rule | killed |
| M19 | refusal shows kept rows | killed |
| M20 | board refusal uses `banner err` | killed |
| M21 | `forget()` does nothing | killed |
| M22 | the board route's inbox read swallows a refusal | killed |
| M23 | a view's refusal doesn't call `forget()` | killed |
| M24 | `pkOpen` ignores a refused refetch | killed |
| M25 | a board 403 keeps `selected` | killed |
| M26 | `fresh()` loses the "refused" wording | killed |
| M27 | a board 403 keeps the footer | killed |
| M28 | a board 403 keeps `#status` | killed |
| M29 | a board 403 keeps the project tree | killed |
| M30 | a board 403 keeps the three heading counts | **survived** |
| M31 | `freshBus` loses the refused state | killed |
| M32 | `.toast:not(.on)` is `display:none` again | killed |
| M33 | `forget()` keeps the Inbox count | killed |
| M34 | `forget()` keeps `last` | killed |
| M35 | board freshness in local time | killed |
| M36 | strip one row, `overflow-x:visible` | killed |
| M37 | strip wraps, `overflow-x:auto` | killed |
M07 survives as in round 1. The toast text comes from validated refs, and
Dewey declined it with a reason. I accept that.
M30 survives because the three counts sit inside `#board-view`, which is
hidden while refused. Today that makes it close to equivalent. After P9
it isn't: the counts come back into view with the rows. Filbert's T7 asks
for the same assertion. A P9 test that checks the counts kills it.
## Required
1. **P9, Filbert's C4.** After a board 403, a later failure that isn't a
403 must not show the refused scan. Clear `#sessions`, `#waiting` and
`#seen` in `error()` on a refusal, or hide `#board-view` whenever
`data` is null. Test: a good read, a 403, then a 500, and assert no
session row, no waiting card and the three counts not numeric.
2. **P10.** A board 403 closes an open conversation, or hides
`#conversation` under `body.board-refused`. Closing is cleaner,
because the history then leaves the DOM. Test: open History, refuse
the board, and assert `#conversation` is hidden and `#conv-log` is
empty or hidden. `history-fixture.mjs` already gives you the
conversation; a proxy that answers 403 is about 20 lines (mine is in
`r2/probes/probe2.test.mjs`).
Then fix the README line on refusals if the wording no longer matches.
## Notes (not blocking)
1. I agree with Filbert's N4 to N7. N6 (a bus read in flight when
`forget()` runs puts its rows back) is real by the code, and it only
restores a read that succeeded, so it can wait.
2. P1 hides the inspector, but its text stays in the DOM. With the
inspector hidden that is fine; if you take the "clear on refusal"
route for Required 1, clearing it too costs one line.
3. In the M05 run, S5 also failed at `bus-browser.test.mjs` line 124
("BODY" !== "H1"). M05 only changes the bus freshness words, so that
failure isn't the mutant's doing. S5 passed in the gate, in the 36
other mutant runs and three times alone afterwards. The cause is a
race in `render()` that base `2d4d4e7d` has too. The 10 s refresh
timer isn't cleared when a navigation starts. If it fires during a
navigated read, the refresh (`navigated = false`) takes `gen`, the
navigation's own render returns early, and nobody focuses the new
`h1` or announces it. Under the mutant load a read took long enough
to hit that window. Not this row's regression and not blocking.
`clearTimeout(timer)` at the top of `render()`, or carrying
`navigated` into the next render, would close it. Worth its own small
row.
## Files
- `r2/candidate-manifest.sha256`: copy of Dewey's.
- `r2/gate.sh`, `r2/out/`: suite runs, `summary.txt`,
`test-release-docker.txt`, the S5 repeats and `r1-to-r2.diff`.
- `r2/mut/`: mutant definitions, runner, diffs, outputs, `summary.txt` and
the manifest check after the runs.
- `r2/probes/`: the probe test and its output.