review(53): Darkwing round 3, approve (#1542 comment 27171)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-10 14:42:32 -05:00
co-authored by Claude Opus 5.5
parent f134348672
commit f5083132f5
51 changed files with 1948 additions and 0 deletions
@@ -0,0 +1,16 @@
eb355c2d22fbfcc14e973a1f793891b0caab04a50cf6fd83c64b9de73a84bfac packages/webui/README.md
6154b49628a613059bb35c26b577817e8ed5fd084c9dfb50aef11431578f14e7 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
da37a24308e51875502b81a5694d4e9c69dae24d36ee272a0b99b1ace7c4d317 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/r53c/tmp DOCKER_HOST=unix:///nonexistent.sock
cd ~/darkwing-scratch/r53c/cand
O=~/darkwing-scratch/r53c/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/app.js b/packages/webui/src/public/app.js
index efe7ba9e..112010bd 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -308,7 +308,7 @@
function error(err) {
const refused = err.status === 403;
failed = refused ? 'refused' : true; $('error').hidden = false;
- if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
+ if (refused) { data = null; selected = null; project = null; }
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4357.364683ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5606.369695ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7280.218799ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (285.542274ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (157.338486ms)
✔ bus refusals map to statuses and never carry a path or a stack (316.904847ms)
✔ humanCall speaks the human transport protocol without blocking the server (2487.061551ms)
✔ busReader takes --business, else the live bus host, else refuses (336.421998ms)
✔ humanCall kills a transport that runs past its timeout (528.263473ms)
✔ serve refuses a --business value that is not a business id (197.400597ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4373.974175ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3178.919122ms)
✔ conversation view: a newer session with no readable history keeps the marker (1638.653594ms)
✔ conversation view: seats without history say so and offer no reply (1022.655709ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5580.491856ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54785.556991ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5349.447192ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22901.932391ms)
✔ loopback host and board origin fail closed (10.225551ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (133.809594ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (90.434917ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1774.668623ms)
✔ tokens.css and icons.svg are byte copies of docs/design (5.724676ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (13.029263ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (3.292396ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4631.885541ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3637.934453ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55170.160307
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4631.885541ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:200: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 efe7ba9e..8636ad73 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -308,7 +308,7 @@
function error(err) {
const refused = err.status === 403;
failed = refused ? 'refused' : true; $('error').hidden = false;
- if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
+ if (refused) { data = null; selected = null; project = null; if (conv) { closeConversation(); } }
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4855.702517ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6320.269832ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6730.573327ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (331.32457ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (146.624553ms)
✔ bus refusals map to statuses and never carry a path or a stack (76.885706ms)
✔ humanCall speaks the human transport protocol without blocking the server (2060.096289ms)
✔ busReader takes --business, else the live bus host, else refuses (189.158327ms)
✔ humanCall kills a transport that runs past its timeout (529.637878ms)
✔ serve refuses a --business value that is not a business id (247.237132ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5389.609333ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2501.421482ms)
✔ conversation view: a newer session with no readable history keeps the marker (1310.369236ms)
✔ conversation view: seats without history say so and offer no reply (878.453526ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5669.785351ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55399.462805ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5444.715817ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23838.29648ms)
✔ loopback host and board origin fail closed (9.455093ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (117.609002ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (90.209362ms)
✔ unreachable board reports URL; CLI rejects unsupported options (935.047647ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.365172ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.577104ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.750164ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5432.797602ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3531.698004ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55797.162398
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5432.797602ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
1 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:202: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: 1,
expected: 0,
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 efe7ba9e..ecac6ea6 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -308,7 +308,7 @@
function error(err) {
const refused = err.status === 403;
failed = refused ? 'refused' : true; $('error').hidden = false;
- if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
+ if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); showConversation(false); } }
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4894.224914ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4840.674479ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6613.741507ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (664.975397ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (425.806763ms)
✔ bus refusals map to statuses and never carry a path or a stack (67.063325ms)
✔ humanCall speaks the human transport protocol without blocking the server (2077.716621ms)
✔ busReader takes --business, else the live bus host, else refuses (531.836271ms)
✔ humanCall kills a transport that runs past its timeout (530.052372ms)
✔ serve refuses a --business value that is not a business id (171.001006ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4861.432832ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2733.981528ms)
✔ conversation view: a newer session with no readable history keeps the marker (1341.539484ms)
✔ conversation view: seats without history say so and offer no reply (863.397661ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4864.411016ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55457.728647ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4831.449262ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22275.110746ms)
✔ loopback host and board origin fail closed (7.438572ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (135.245611ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (174.946225ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1438.695438ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.00061ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.83711ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.045232ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7781.958592ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2788.807591ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56053.212777
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (7781.958592ms)
Error: Uncaught (in promise) Error: Condition timed out: document.activeElement?.matches("[data-open]") && document.activeElement.textContent==="agent1": undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r53c/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/r53c/mutwt/packages/webui/tests/shell.test.mjs:215:5)
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 efe7ba9e..ea420eb4 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -309,7 +309,7 @@
const refused = err.status === 403;
failed = refused ? 'refused' : true; $('error').hidden = false;
if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
- if (!data) {
+ if (refused) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
@@ -0,0 +1,59 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4138.053527ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4310.599257ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5552.190765ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (273.704359ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (353.352341ms)
✔ bus refusals map to statuses and never carry a path or a stack (217.326745ms)
✔ humanCall speaks the human transport protocol without blocking the server (2545.275102ms)
✔ busReader takes --business, else the live bus host, else refuses (288.289302ms)
✔ humanCall kills a transport that runs past its timeout (529.2749ms)
✔ serve refuses a --business value that is not a business id (267.579967ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4310.725198ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2380.319162ms)
✔ conversation view: a newer session with no readable history keeps the marker (1108.514853ms)
✔ conversation view: seats without history say so and offer no reply (739.010404ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4211.527735ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53894.209091ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4017.881869ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22340.898892ms)
✔ loopback host and board origin fail closed (8.4696ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (113.976306ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (112.568935ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1664.496002ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.267374ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.514151ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.411806ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4017.126857ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3040.520103ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54278.918401
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4017.126857ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Board not read: refused'
- 'Board not read: the read failed'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:180: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: refused',
expected: 'Board not read: the read failed',
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 efe7ba9e..76b00755 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -312,7 +312,6 @@
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
- for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;
$('status').textContent = `Not read: ${why}`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4803.099952ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4688.540875ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6571.898793ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (292.441148ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (190.781931ms)
✔ bus refusals map to statuses and never carry a path or a stack (62.185517ms)
✔ humanCall speaks the human transport protocol without blocking the server (2433.800874ms)
✔ busReader takes --business, else the live bus host, else refuses (223.279532ms)
✔ humanCall kills a transport that runs past its timeout (530.182798ms)
✔ serve refuses a --business value that is not a business id (147.728257ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4805.500178ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2376.564491ms)
✔ conversation view: a newer session with no readable history keeps the marker (1107.914187ms)
✔ conversation view: seats without history say so and offer no reply (726.437678ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4771.118004ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55737.030014ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4789.0592ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22243.778003ms)
✔ loopback host and board origin fail closed (9.600543ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (108.618347ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (102.037953ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1561.753343ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.371336ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.094789ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.364592ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4749.995989ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3130.895181ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56141.922296
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4749.995989ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
11 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:178: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: 11,
expected: 0,
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 efe7ba9e..9238d4ad 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -312,7 +312,7 @@
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
- for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
+ for (const id of ['waiting', 'sessions']) $(id).replaceChildren();
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;
$('status').textContent = `Not read: ${why}`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4388.587757ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4415.26492ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6321.569047ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (320.319672ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (306.321478ms)
✔ bus refusals map to statuses and never carry a path or a stack (318.963185ms)
✔ humanCall speaks the human transport protocol without blocking the server (2194.687043ms)
✔ busReader takes --business, else the live bus host, else refuses (320.203199ms)
✔ humanCall kills a transport that runs past its timeout (554.353ms)
✔ serve refuses a --business value that is not a business id (167.646967ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4846.113484ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2027.977829ms)
✔ conversation view: a newer session with no readable history keeps the marker (1161.508504ms)
✔ conversation view: seats without history say so and offer no reply (760.511986ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4346.108258ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (45634.23458ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4849.4561ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22243.45107ms)
✔ loopback host and board origin fail closed (7.156936ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (111.873671ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (127.79059ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1492.129995ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.960519ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.943369ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.992483ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4352.755458ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2978.13126ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 46089.278953
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4352.755458ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:178: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/app.js b/packages/webui/src/public/app.js
index efe7ba9e..d4e0dbfb 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -312,7 +312,7 @@
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
- for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
+ for (const id of ['seen', 'sessions']) $(id).replaceChildren();
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;
$('status').textContent = `Not read: ${why}`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (6036.593035ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6100.894321ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (8128.82253ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (286.736519ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (64.674194ms)
✔ bus refusals map to statuses and never carry a path or a stack (57.136766ms)
✔ humanCall speaks the human transport protocol without blocking the server (2193.388754ms)
✔ busReader takes --business, else the live bus host, else refuses (274.849013ms)
✔ humanCall kills a transport that runs past its timeout (529.49086ms)
✔ serve refuses a --business value that is not a business id (180.054135ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (6103.960998ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2681.456786ms)
✔ conversation view: a newer session with no readable history keeps the marker (1287.650059ms)
✔ conversation view: seats without history say so and offer no reply (793.309869ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (6168.138254ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56488.219447ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (6051.844481ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23107.35492ms)
✔ loopback host and board origin fail closed (9.209811ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (121.057055ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (140.860249ms)
✔ unreachable board reports URL; CLI rejects unsupported options (879.49217ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.924842ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.7955ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.254758ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6081.58039ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3787.790968ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56843.035622
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6081.58039ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:178: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/app.js b/packages/webui/src/public/app.js
index efe7ba9e..9896d88d 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -312,7 +312,7 @@
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
- for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
+ for (const id of ['waiting', 'seen']) $(id).replaceChildren();
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;
$('status').textContent = `Not read: ${why}`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4900.207456ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5083.939777ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6572.822309ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (321.20192ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (232.17288ms)
✔ bus refusals map to statuses and never carry a path or a stack (199.358364ms)
✔ humanCall speaks the human transport protocol without blocking the server (2259.246413ms)
✔ busReader takes --business, else the live bus host, else refuses (673.781883ms)
✔ humanCall kills a transport that runs past its timeout (533.333407ms)
✔ serve refuses a --business value that is not a business id (193.758775ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4558.232742ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2276.697584ms)
✔ conversation view: a newer session with no readable history keeps the marker (1102.029166ms)
✔ conversation view: seats without history say so and offer no reply (908.417782ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4904.720077ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (45186.714012ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4899.11086ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23184.867229ms)
✔ loopback host and board origin fail closed (7.160896ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (126.69408ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (118.514677ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1188.266786ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.921274ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.667648ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (1.967926ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4871.93075ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3432.477449ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 45655.547661
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4871.93075ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
7 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:178: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: 7,
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 efe7ba9e..cf6acd3a 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -313,7 +313,6 @@
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
- for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;
$('status').textContent = `Not read: ${why}`;
inspect();
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5042.417022ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6255.981482ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7622.154685ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (835.304749ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (208.683705ms)
✔ bus refusals map to statuses and never carry a path or a stack (198.695171ms)
✔ humanCall speaks the human transport protocol without blocking the server (2332.630157ms)
✔ busReader takes --business, else the live bus host, else refuses (392.502506ms)
✔ humanCall kills a transport that runs past its timeout (529.641646ms)
✔ serve refuses a --business value that is not a business id (189.184677ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5305.067115ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2727.339279ms)
✔ conversation view: a newer session with no readable history keeps the marker (1309.474994ms)
✔ conversation view: seats without history say so and offer no reply (795.825407ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5156.419695ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56394.343322ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5665.906343ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22515.741357ms)
✔ loopback host and board origin fail closed (9.890577ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (145.760824ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (98.004231ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1653.18752ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.355435ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (3.020533ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.776918ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5878.107856ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3293.499639ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56836.962867
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5878.107856ms)
AssertionError [ERR_ASSERTION]: #waiting-count
'1' !== '–'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:170:82)
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: '1',
expected: '–',
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 efe7ba9e..d3776a01 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -314,7 +314,7 @@
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
- $('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;
+ $('footer').textContent = 'Board not read: refused';
$('status').textContent = `Not read: ${why}`;
inspect();
}
@@ -0,0 +1,59 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5050.385435ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5043.562016ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6603.09917ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (474.208971ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (267.476295ms)
✔ bus refusals map to statuses and never carry a path or a stack (174.709188ms)
✔ humanCall speaks the human transport protocol without blocking the server (2272.778823ms)
✔ busReader takes --business, else the live bus host, else refuses (174.265748ms)
✔ humanCall kills a transport that runs past its timeout (528.397088ms)
✔ serve refuses a --business value that is not a business id (179.433644ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5250.655315ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2378.948314ms)
✔ conversation view: a newer session with no readable history keeps the marker (1190.032821ms)
✔ conversation view: seats without history say so and offer no reply (808.96696ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4978.669482ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54992.098098ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4913.440869ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (23385.886205ms)
✔ loopback host and board origin fail closed (8.441654ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (125.381941ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (88.118654ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1520.733067ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.243298ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.589141ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.627381ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4768.175563ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3631.304572ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55438.777304
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4768.175563ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Board not read: refused'
- 'Board not read: the read failed'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:180: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: refused',
expected: 'Board not read: the read failed',
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 efe7ba9e..0dac0174 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -310,7 +310,7 @@
failed = refused ? 'refused' : true; $('error').hidden = false;
if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
if (!data) {
- const why = refused ? 'the board refused the read' : 'the read failed';
+ const why = 'the board refused the read';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
@@ -0,0 +1,59 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5650.912391ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6162.937461ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7308.837935ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (353.912872ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (146.299774ms)
✔ bus refusals map to statuses and never carry a path or a stack (100.39279ms)
✔ humanCall speaks the human transport protocol without blocking the server (2110.43097ms)
✔ busReader takes --business, else the live bus host, else refuses (256.398016ms)
✔ humanCall kills a transport that runs past its timeout (665.019901ms)
✔ serve refuses a --business value that is not a business id (160.743926ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5876.60575ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1901.922149ms)
✔ conversation view: a newer session with no readable history keeps the marker (1374.173262ms)
✔ conversation view: seats without history say so and offer no reply (888.075297ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5878.334661ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55888.208065ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (6156.340851ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22681.684546ms)
✔ loopback host and board origin fail closed (9.702895ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (122.969313ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (115.314798ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1242.745272ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.320161ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.215405ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.543711ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6262.777615ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3210.027001ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56300.428205
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (6262.777615ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Not read: the board refused the read'
- 'Not read: the read failed'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:182: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: 'Not read: the board refused the read',
expected: 'Not read: the read failed',
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 efe7ba9e..f08e1c6f 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -316,7 +316,6 @@
for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';
$('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;
$('status').textContent = `Not read: ${why}`;
- inspect();
}
$('error').className = refused ? 'banner ref' : 'banner err';
$('board-refused').hidden = !refused; document.body.classList.toggle('board-refused', refused);
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (5556.899402ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6058.035533ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6942.067417ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (253.97288ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (83.219593ms)
✔ bus refusals map to statuses and never carry a path or a stack (103.494196ms)
✔ humanCall speaks the human transport protocol without blocking the server (2173.393637ms)
✔ busReader takes --business, else the live bus host, else refuses (173.699979ms)
✔ humanCall kills a transport that runs past its timeout (527.897056ms)
✔ serve refuses a --business value that is not a business id (158.452896ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4719.166139ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (3027.465998ms)
✔ conversation view: a newer session with no readable history keeps the marker (1450.244115ms)
✔ conversation view: seats without history say so and offer no reply (806.679811ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4994.551896ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56326.781282ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4814.661956ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22241.515676ms)
✔ loopback host and board origin fail closed (6.963843ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (105.737467ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (99.870326ms)
✔ unreachable board reports URL; CLI rejects unsupported options (885.6488ms)
✔ tokens.css and icons.svg are byte copies of docs/design (0.953653ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (1.786699ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.145606ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4841.378827ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (4184.730508ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56742.461593
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4841.378827ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:164: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 efe7ba9e..a1df8aaf 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -308,7 +308,7 @@
function error(err) {
const refused = err.status === 403;
failed = refused ? 'refused' : true; $('error').hidden = false;
- if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
+ if (refused) { data = null; selected = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4958.436261ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5839.680736ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6752.747552ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (313.110002ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (209.181124ms)
✔ bus refusals map to statuses and never carry a path or a stack (132.196704ms)
✔ humanCall speaks the human transport protocol without blocking the server (2490.855076ms)
✔ busReader takes --business, else the live bus host, else refuses (914.332215ms)
✔ humanCall kills a transport that runs past its timeout (530.46568ms)
✔ serve refuses a --business value that is not a business id (165.845097ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5366.203786ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2292.540248ms)
✔ conversation view: a newer session with no readable history keeps the marker (1224.20263ms)
✔ conversation view: seats without history say so and offer no reply (920.065471ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5438.909685ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55064.68829ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5751.325816ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22542.858137ms)
✔ loopback host and board origin fail closed (8.112756ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (118.794689ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (98.229427ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1391.894302ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.045049ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.418334ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.203271ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5530.681465ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3243.523818ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55485.89588
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5530.681465ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'false' !== 'true'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:187: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 efe7ba9e..21c6f4ac 100644
--- a/packages/webui/src/public/app.js
+++ b/packages/webui/src/public/app.js
@@ -308,7 +308,7 @@
function error(err) {
const refused = err.status === 403;
failed = refused ? 'refused' : true; $('error').hidden = false;
- if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
+ if (refused) { data = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
if (!data) {
const why = refused ? 'the board refused the read' : 'the read failed';
$('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4617.694886ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4583.641477ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (6203.178815ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (425.84534ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (243.006036ms)
✔ bus refusals map to statuses and never carry a path or a stack (97.760263ms)
✔ humanCall speaks the human transport protocol without blocking the server (2112.252953ms)
✔ busReader takes --business, else the live bus host, else refuses (178.424797ms)
✔ humanCall kills a transport that runs past its timeout (528.349537ms)
✔ serve refuses a --business value that is not a business id (187.247791ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4591.731864ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1895.812928ms)
✔ conversation view: a newer session with no readable history keeps the marker (1115.788704ms)
✔ conversation view: seats without history say so and offer no reply (767.542558ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4850.217739ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (55022.437588ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4926.547646ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22233.744565ms)
✔ loopback host and board origin fail closed (9.651345ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (135.566789ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (110.732547ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1154.724864ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.451541ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.811802ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.799964ms)
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4922.844102ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (2893.4734ms)
ℹ tests 27
ℹ suites 0
ℹ pass 26
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55550.563039
✖ failing tests:
test at tests/shell.test.mjs:96:1
✖ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (4922.844102ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r53c/mutwt/packages/webui/tests/shell.test.mjs:164: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,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,26 @@
# Round 3 mutants: exact text, must match once. Run from the worktree root.
import sys
A = "packages/webui/src/public/app.js"
CLOSE = "if (conv) { $('conv-log').replaceChildren(); closeConversation(); }"
M = {
"R01": [(A, " " + CLOSE, "")],
"R02": [(A, CLOSE, "if (conv) { closeConversation(); }")],
"R03": [(A, CLOSE, "if (conv) { $('conv-log').replaceChildren(); showConversation(false); }")],
"R04": [(A, " if (!data) {\n const why", " if (refused) {\n const why")],
"R05": [(A, " for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();\n", "")],
"R06": [(A, "for (const id of ['waiting', 'seen', 'sessions'])", "for (const id of ['waiting', 'sessions'])")],
"R07": [(A, "for (const id of ['waiting', 'seen', 'sessions'])", "for (const id of ['seen', 'sessions'])")],
"R08": [(A, "for (const id of ['waiting', 'seen', 'sessions'])", "for (const id of ['waiting', 'seen'])")],
"R09": [(A, " for (const id of ['waiting-count', 'seen-count', 'session-count']) $(id).textContent = '–';\n", "")],
"R10": [(A, "$('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;", "$('footer').textContent = 'Board not read: refused';")],
"R11": [(A, "const why = refused ? 'the board refused the read' : 'the read failed';", "const why = 'the board refused the read';")],
"R12": [(A, " inspect();\n }\n $('error').className", " }\n $('error').className")],
"R13": [(A, "if (refused) { data = null; selected = null; project = null;", "if (refused) { data = null; selected = null;")],
"R14": [(A, "if (refused) { data = null; selected = null; project = null;", "if (refused) { data = null; project = null;")],
}
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/r53c/tmp
S=~/darkwing-scratch/r53c; 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"\"(R\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,17 @@
start 2026-10-10T19:26:06Z
R01 killed exit=1 ℹ pass 26 ℹ fail 1
R02 killed exit=1 ℹ pass 26 ℹ fail 1
R03 killed exit=1 ℹ pass 26 ℹ fail 1
R04 killed exit=1 ℹ pass 26 ℹ fail 1
R05 killed exit=1 ℹ pass 26 ℹ fail 1
R06 killed exit=1 ℹ pass 26 ℹ fail 1
R07 killed exit=1 ℹ pass 26 ℹ fail 1
R08 killed exit=1 ℹ pass 26 ℹ fail 1
R09 killed exit=1 ℹ pass 26 ℹ fail 1
R10 killed exit=1 ℹ pass 26 ℹ fail 1
R11 killed exit=1 ℹ pass 26 ℹ fail 1
R12 killed exit=1 ℹ pass 26 ℹ fail 1
R13 killed exit=1 ℹ pass 26 ℹ fail 1
R14 killed exit=1 ℹ pass 26 ℹ fail 1
end 2026-10-10T19:38: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 (6060.523918ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (6504.221202ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (7770.043591ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (309.254238ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (148.596287ms)
✔ bus refusals map to statuses and never carry a path or a stack (196.362869ms)
✔ humanCall speaks the human transport protocol without blocking the server (2435.021376ms)
✔ busReader takes --business, else the live bus host, else refuses (349.294707ms)
✔ humanCall kills a transport that runs past its timeout (532.169891ms)
✔ serve refuses a --business value that is not a business id (261.462336ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (5977.305191ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (4319.030475ms)
✔ conversation view: a newer session with no readable history keeps the marker (1875.816756ms)
✔ conversation view: seats without history say so and offer no reply (1635.252753ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (5813.776191ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (56573.703754ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (5744.431882ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22842.231747ms)
✔ loopback host and board origin fail closed (7.949761ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (145.46719ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (156.678394ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1280.951527ms)
✔ tokens.css and icons.svg are byte copies of docs/design (1.350765ms)
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.436856ms)
✔ shipped strings are unchanged, and the web keeps its write boundary (2.130262ms)
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (8204.713465ms)
✔ five states on Board, Inbox, Tasks, Agents and Trail (3668.031892ms)
ℹ tests 27
ℹ suites 0
ℹ pass 27
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 57021.687567
@@ -0,0 +1,163 @@
diff -ru r53b/snap/packages/webui/README.md r53c/snap/packages/webui/README.md
--- r53b/snap/packages/webui/README.md 2026-10-10 13:15:05.000000000 -0500
+++ r53c/snap/packages/webui/README.md 2026-10-10 14:18:51.000000000 -0500
@@ -127,8 +127,12 @@
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.
+ and everything drawn from it: the cards, the table, the inspector, the
+ project filter and tree, the counts, the footer and the scan line all say
+ the board was not read. A failed read with no scan held, after a refusal or
+ before the first scan, shows the board empty and says the read failed; it
+ never brings back rows from a refused scan. A refusal also closes an open
+ conversation and clears its history.
- 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 is one strip
that scrolls inside itself.
@@ -211,7 +215,8 @@
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
-(a board refusal with the inspector open), not found, the palette, the toast,
+(a board refusal with the inspector open and a project picked, then a failed
+read, and a refusal with a conversation 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
diff -ru r53b/snap/packages/webui/src/public/app.js r53c/snap/packages/webui/src/public/app.js
--- r53b/snap/packages/webui/src/public/app.js 2026-10-10 13:15:05.000000000 -0500
+++ r53c/snap/packages/webui/src/public/app.js 2026-10-10 14:18:51.000000000 -0500
@@ -301,18 +301,22 @@
render();
}
// 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.
+ // everything drawn from it: the cards, the table, the inspector, an open conversation and its
+ // history, the project tree, the counts and the footer. Any other failure keeps the last data with
+ // its scan time. With no data, from a refusal or before the first scan, the board is drawn empty
+ // and says why, so a later failure never brings back old rows.
function error(err) {
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>';
+ if (refused) { data = null; selected = null; project = null; if (conv) { $('conv-log').replaceChildren(); closeConversation(); } }
+ if (!data) {
+ const why = refused ? 'the board refused the read' : 'the read failed';
+ $('projects').innerHTML = `<p class="muted">Not read: ${why}.</p>`;
+ for (const id of ['waiting', 'seen', 'sessions']) $(id).replaceChildren();
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();
+ $('footer').textContent = `Board not read: ${refused ? 'refused' : 'the read failed'}`;
+ $('status').textContent = `Not read: ${why}`;
+ inspect();
}
$('error').className = refused ? 'banner ref' : 'banner err';
$('board-refused').hidden = !refused; document.body.classList.toggle('board-refused', refused);
diff -ru r53b/snap/packages/webui/tests/shell.test.mjs r53c/snap/packages/webui/tests/shell.test.mjs
--- r53b/snap/packages/webui/tests/shell.test.mjs 2026-10-10 13:15:05.000000000 -0500
+++ r53c/snap/packages/webui/tests/shell.test.mjs 2026-10-10 14:18:51.000000000 -0500
@@ -59,20 +59,28 @@
function board() {
let reply = { status: 200, body: { sessions: [], counts: {}, generatedAt: new Date().toISOString() } };
const one = { agent: 'agent1', project: 'proj', state: 'waiting', waitingOnYou: true, task: 'Fixture task', lastActivity: '2026-10-10T00:00:00Z', lastAssistantText: 'Input needed: fixture', registered: null };
+ // While the board answers, agent1 has one conversation with one message. A failure or refusal answers every path.
+ const catalogue = { conversations: [{ project: 'proj', seat: 'agent1', conversation: 'c1', availability: 'available', title: 'Fixture', conversationCreatedAt: '2026-10-10T00:00:00Z', lastActivityAt: '2026-10-10T00:00:00Z' }] };
+ const page = { page: { branch: 'b1', entries: [{ message: 'm1', role: 'assistant', createdAt: '2026-10-10T00:00:00Z', content: [{ block: 0, type: 'text', text: 'Fixture history line' }] }] }, view: { incomplete: false, defaultBranch: 'b1', branch: 'b1' }, cursor: null, follow: null };
let gate = null;
const server = createServer(async (req, res) => {
for await (const chunk of req) { void chunk; }
await gate;
- res.writeHead(reply.status, { 'content-type': 'application/json' }); res.end(JSON.stringify(reply.body));
+ const path = new URL(req.url, 'http://localhost').pathname;
+ const body = reply.status !== 200 ? reply.body : path === '/api/conversations' ? catalogue : path === '/api/conversation' ? page : reply.body;
+ res.writeHead(reply.status, { 'content-type': 'application/json' }); res.end(JSON.stringify(body));
});
return {
server,
// Holds every reply until the returned function is called.
hold: () => { let go; gate = new Promise(r => { go = r; }); return () => { gate = null; go(); }; },
empty: () => { reply = { status: 200, body: { sessions: [], counts: {}, generatedAt: new Date().toISOString() } }; },
- one: () => { reply = { status: 200, body: { sessions: [one, { ...one, agent: 'agent2', state: 'working', waitingOnYou: false }], counts: { waiting: 1, working: 1 }, generatedAt: new Date().toISOString() } }; },
+ one: () => { reply = { status: 200, body: { sessions: [one, { ...one, agent: 'agent2', state: 'working', waitingOnYou: false }, { ...one, agent: 'agent3', state: 'idle', waitingOnYou: false, seen: true }], counts: { waiting: 1, working: 1 }, generatedAt: new Date().toISOString() } }; },
fail: () => { reply = { status: 500, body: { error: 'board scan failed' } }; },
refuse: () => { reply = { status: 403, body: { error: 'board-refused' } }; },
+ // One seat a reply can reach: registered, alive, with a tmux session.
+ registered: () => { reply = { status: 200, body: { sessions: [{ ...one, registered: { alive: true, tmux: { session: 'fixture' } } }], counts: { waiting: 1 }, generatedAt: new Date().toISOString() } }; },
+ unavailable: () => { reply = { status: 503, body: { error: 'board unavailable' } }; },
};
}
@@ -95,6 +103,7 @@
const shot = async name => { if (out) await b.screenshot(join(out, `shell-${name}.png`)); };
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('Condition timed out: '+${JSON.stringify(expr)})})()`);
const text = sel => b.evaluate(`document.querySelector(${JSON.stringify(sel)})?.textContent ?? null`);
+ 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]")`); };
@@ -140,10 +149,13 @@
await flat(); await shot('board-stale-400');
// 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.
+ // them: the open inspector, the project filter, the project tree's counts and the footer.
+ await b.evaluate('document.querySelector("#projects [data-project=proj]").click()');
+ assert.equal(await b.evaluate('document.querySelector("#projects [data-project=proj]").getAttribute("aria-pressed")'), 'true');
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);
+ assert.equal(await count('#seen .wait-item'), 1);
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/);
@@ -155,14 +167,43 @@
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/);
+ for (const id of ['#waiting-count', '#seen-count', '#session-count']) assert.equal(await text(id), '–', id);
await flat(); await shot('board-refused-400');
+ // A failed read after the refusal shows the board again, empty: no row, card or count from
+ // the refused scan, and the footer says what the banner says.
+ brd.unavailable(); await refresh();
+ assert.equal(await b.evaluate('document.querySelector("#error").className'), 'banner err');
+ assert.match(await text('#error'), /board unavailable.*No board data loaded\./);
+ assert.notEqual(await b.evaluate('getComputedStyle(document.querySelector("#board-view")).display'), 'none');
+ assert.equal(await count('#sessions tr, #sessions [data-open], #waiting .wait-item, #seen .wait-item, #board-view [data-history]'), 0);
+ for (const id of ['#waiting-count', '#seen-count', '#session-count']) assert.equal(await text(id), '–', id);
+ assert.equal(await text('#footer'), 'Board not read: the read failed');
+ assert.equal(await text('#fresh-board'), 'Board not read: the read failed');
+ assert.equal(await text('#status'), 'Not read: the read failed');
+ assert.equal(await text('#projects'), 'Not read: the read failed.');
+ await flat(); await shot('board-refused-then-failed-400');
brd.one(); await refresh();
assert.equal(await b.evaluate('document.querySelector("#error").hidden'), true);
+ assert.equal(await b.evaluate('document.querySelector("#projects [data-project=\'\']").getAttribute("aria-pressed")'), '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/);
+ // A refusal with a conversation open closes it and clears its history and reply form: the
+ // session is in no scan the page holds.
+ brd.registered(); await refresh(); await wait('document.querySelectorAll("table.sessions tbody tr").length===1');
+ await b.evaluate('document.querySelector("table.sessions [data-history]").click()');
+ await wait('document.querySelector("#conv-log").textContent.includes("Fixture history line")');
+ assert.equal(await b.evaluate('document.querySelector("#conv-form").hidden'), false);
+ brd.refuse(); await refresh();
+ assert.equal(await b.evaluate('document.querySelector("#conversation").hidden'), true);
+ assert.equal(await b.evaluate('document.body.classList.contains("has-conversation")'), false);
+ assert.equal(await count('#conv-log > *'), 0);
+ assert.equal(await b.evaluate('document.querySelector("#conv-form").checkVisibility()'), false);
+ assert.equal(await b.evaluate('getComputedStyle(document.querySelector("#board-view")).display'), 'none');
+ assert.equal(await b.evaluate('document.querySelector("#board-refused").hidden'), false);
+ brd.one(); await refresh(); await wait('document.querySelectorAll("table.sessions tbody tr").length===2');
// 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()');
@@ -0,0 +1,14 @@
start 2026-10-10T19:23:02Z
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-10T19:26:06Z
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,23 @@
On branch refactor
Your branch is up to date with 'origin/refactor'.
nothing to commit, working tree clean
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: 3852416 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/r53c/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,163 @@
// Darkwing row 53 round 3 probes: round 2's probe set again (P1-P9, with P9 a 403 then a 500), then
// the conversation cases on the real control-board fixture: P10 (a 403 with History open), P11 (the
// session picker after a refusal, when the next catalogue read fails), P13 (a 403 while a history
// page is still in flight), and P12 (a first read that fails, before any scan).
// Run from a worktree's packages/webui: node --test <this file> with WT set to that worktree.
import { test, after } 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)}`); };
after(() => console.log(out.join('\n')));
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('probes P10, P11, P13 on the real control-board', { 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. refuse: every request 403. catFail: the catalogue 500.
// hold: history page requests wait until released, then go upstream as they were.
let refuse = false, catFail = false, hold = false; const held = [];
const up = new URL(`http://127.0.0.1:${fx.board.address().port}`);
const pass = (req, res) => { 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); };
const proxy = createServer((req, res) => {
const path = new URL(req.url, 'http://x').pathname;
if (hold && path === '/api/conversation') { held.push(() => pass(req, res)); return; }
if (refuse) { res.writeHead(403, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'non-local Host refused' })); req.resume(); return; }
if (catFail && path === '/api/conversations') { res.writeHead(500, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'catalogue failed' })); req.resume(); return; }
pass(req, res);
});
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)}})()`);
const refresh = async () => { await b.evaluate('document.querySelector("#refresh").click()'); await wait('!document.querySelector("#refresh").disabled'); };
const state = () => b.evaluate('({conversation:document.querySelector("#conversation").hidden, hasConv:document.body.classList.contains("has-conversation"), log:document.querySelector("#conv-log").children.length, logText:document.querySelector("#conv-log").textContent.slice(0,120), form:document.querySelector("#conv-form").checkVisibility(), active:document.activeElement.id||document.activeElement.tagName, boardView:getComputedStyle(document.querySelector("#board-view")).display})');
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")');
note('P10 before: conv-pick options', await b.evaluate('[...document.querySelectorAll("#conv-pick option")].map(o=>o.textContent)'));
refuse = true; await refresh(); await wait('document.querySelector("#error").className==="banner ref"');
await new Promise(r => setTimeout(r, 500));
note('P10 after 403: state', await state());
note('P10 after 403: #conversation', await vis('#conversation'));
note('P10 after 403: board-refused', await vis('#board-refused'));
note('P10 after 403: conv-pick and conv-meta in the DOM', await b.evaluate('[[...document.querySelectorAll("#conv-pick option")].map(o=>o.textContent), document.querySelector("#conv-meta").textContent]'));
// P11: the board answers again, History is opened again, and the catalogue read fails.
refuse = false; await refresh(); await wait('document.querySelector("#error").hidden && !!document.querySelector("[data-history]")');
catFail = true;
await b.evaluate('document.querySelector("[data-history]").click()');
await wait('/History unavailable/.test(document.querySelector("#conv-status").textContent)');
note('P11 catalogue fails after refusal: conv-pick', await vis('#conv-pick'));
note('P11 options', await b.evaluate('[...document.querySelectorAll("#conv-pick option")].map(o=>o.textContent)'));
note('P11 conv-meta, conv-status', [await vis('#conv-meta'), await vis('#conv-status')]);
// P13: History open again with the page held; the board refuses; then the held page answers.
catFail = false; await b.evaluate('document.querySelector("#conv-back").click()'); await wait('document.querySelector("#conversation").hidden');
hold = true;
await b.evaluate('document.querySelector("[data-history]").click()');
await wait('/Loading history/.test(document.querySelector("#conv-status").textContent)');
for (let i = 0; i < 100 && !held.length; i++) await new Promise(r => setTimeout(r, 50));
note('P13 held page requests', held.length);
refuse = true; await refresh(); await wait('document.querySelector("#error").className==="banner ref"');
hold = false; refuse = false; for (const go of held.splice(0)) go();
await new Promise(r => setTimeout(r, 1500));
note('P13 after the held page answered: state', await state());
note('errors', await b.evaluate('window.errors||null'));
} finally {
await b.close(); await close(web); await new Promise(r => proxy.close(r)); await fx.close();
}
});
test('probe P12, the first read fails', { timeout: 60000 }, async () => {
let reply = { status: 500, body: { error: 'scan failed' } };
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 web = await startServer({ port: 0, board: `http://127.0.0.1:${brd.address().port}` });
const 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)}})()`);
try {
await b.viewport(1440, 900);
await b.navigate(`http://127.0.0.1:${web.address().port}/`); await wait('!document.querySelector("#error").hidden');
note('P12 first read 500: board-view display', await b.evaluate('getComputedStyle(document.querySelector("#board-view")).display'));
note('P12 projects, footer, status, fresh, banner', [await vis('#projects'), await vis('#footer'), await vis('#status'), await vis('#fresh-board'), await vis('#error')]);
note('P12 waiting, sessions', [await vis('#waiting'), await vis('#sessions')]);
} finally {
await b.close(); await close(web); await new Promise(r => brd.close(r));
}
});
@@ -0,0 +1,46 @@
✔ probes, fake board and bus (2656.90098ms)
✔ probes P10, P11, P13 on the real control-board (3802.384625ms)
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: [0,false]
P9 403 then 500: #waiting: {"shown":true,"text":""}
P9 403 then 500: #sessions: {"shown":true,"text":""}
P9 403 then 500: projects, footer, status, fresh: [{"shown":true,"text":"Not read: the read failed."},{"shown":true,"text":"Board not read: the read failed"},{"shown":true,"text":"Not read: the read failed"},{"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:45189. 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 before: conv-pick options: ["Secret question from before the refusal · 2026-10-10T19:25:10.918Z"]
P10 after 403: state: {"conversation":true,"hasConv":false,"log":0,"logText":"","form":false,"active":"BODY","boardView":"none"}
P10 after 403: #conversation: {"shown":false,"text":"fixture conversationrepo · Secret question from before the refusal · started 2026-10-10T19:15:10.918ZSessionSecret question from before the refusal · 2026-10-10T19:25:10.918ZBack to board\n2 messages ·"}
P10 after 403: board-refused: {"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 after 403: conv-pick and conv-meta in the DOM: [["Secret question from before the refusal · 2026-10-10T19:25:10.918Z"],"repo · Secret question from before the refusal · started 2026-10-10T19:15:10.918Z"]
P11 catalogue fails after refusal: conv-pick: {"shown":true,"text":"Secret question from before the refusal · 2026-10-10T19:25:10.918Z"}
P11 options: ["Secret question from before the refusal · 2026-10-10T19:25:10.918Z"]
P11 conv-meta, conv-status: [{"shown":true,"text":""},{"shown":true,"text":"History unavailable: catalogue failed."}]
P13 held page requests: 1
P13 after the held page answered: state: {"conversation":true,"hasConv":false,"log":0,"logText":"","form":false,"active":"BODY","boardView":"none"}
errors: null
P12 first read 500: board-view display: "block"
P12 projects, footer, status, fresh, banner: [{"shown":true,"text":"Not read: the read failed."},{"shown":true,"text":"Board not read: the read failed"},{"shown":true,"text":"Not read: the read failed"},{"shown":true,"text":"Board not read: the read failed"},{"shown":true,"text":"scan failed Board: http://127.0.0.1:40057. No board data loaded. Use Refresh to try again."}]
P12 waiting, sessions: [{"shown":true,"text":""},{"shown":true,"text":""}]
✔ probe P12, the first read fails (1018.796797ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 7599.426941
@@ -0,0 +1,154 @@
# Row 53, console tokens and shell restyle, round 3 review (Darkwing)
Issue #1542, request comment 27168, packet comment 27169, queue revs
367-369. Base `ea080fd8`. Candidate manifest sha256
`2562c05d4a92f90096c0fe68ef8c2adfa7244fa547f3390155f5bee5cce64cba`, the
same 16 files under `packages/webui`. The packet manifest and my snapshot
of the checkout both check 16 OK. `round3.patch` applied to my round 2
snapshot gives the round 3 files byte for byte, so the delta is README,
`app.js` and `shell.test.mjs` and nothing else (`r3/out/r2-to-r3.diff`).
Verdict: **approve**. Both of my round 2 items are fixed, and the tests
pin them. Every suite is green, all 14 new mutants die, and my probes on
the real control-board show nothing from before a refusal. I have three
notes, none blocking. The worst is a stale session picker that this row
didn't introduce.
## Method
- Detached worktrees at `ea080fd8`, `cand`, `mutwt` and `pwt`, each with
the 16 candidate files staged and checking 16 OK. `mutwt` still checks
16 OK after the mutant runs (`r3/mut/manifest-after.txt`).
- `r3/gate.sh`: the `packages/webui` suite, `build-tokens.mjs --check`,
then every `scripts/test-*.sh` with `DOCKER_HOST=unix:///nonexistent.sock`,
19:23:02Z to 19:26:06Z.
- 14 mutants aimed at the round 3 code (`r3/mut/mutate.py`,
`r3/mut/run.sh`), each against the full webui suite, 19:26:06Z to
19:38:50Z.
- `test-release` with Docker, finished 19:39:38Z.
- Probes in Chromium through the candidate's `tests/browser.mjs`
(`r3/probes/probe3.test.mjs`, output `r3/probes/probes-cand.txt`). Test
one is round 2's fake board and bus set (P1 to P9). Test two runs the
real control-board from `tests/history-fixture.mjs` behind a proxy that
can answer 403, fail the catalogue with a 500, or hold history pages
(P10, P11, P13). Test three is a board whose first read is a 500 (P12).
No tracker request.
- Read the three changed files against my round 2 required list and
Filbert's C4 (comment 27163).
Node v26.8.1, `TMPDIR=~/darkwing-scratch/r53c/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 before, I didn't run test-task with real Docker because it makes live
model calls.
## Round 2 items
| Item | Result |
|---|---|
| Required 1, P9 and Filbert's C4 | Fixed. With no scan held, `error()` empties `#sessions`, `#waiting` and `#seen`, puts "–" in the three counts, and says "the read failed" in the tree, the footer and `#status`. P9 (403 then 500) and P12 (first read 500) both show an empty board. The test runs a 403, then a 503, and asserts no rows, no cards, no History buttons and the dashes |
| Required 2, P10 | Fixed. A 403 with a conversation open empties `#conv-log` and calls `closeConversation()`. P10 on the real control-board: `#conversation` hidden, no `has-conversation`, `#conv-log` empty, the form hidden |
| README wording | Fixed. The refusal line now names the cards, the project filter, the empty board after a failed read and the closed conversation. Each claim matches what the probes show |
| M30 (counts kept on refusal) | Now tested. R09 below drops the dashes and dies on `#waiting-count` |
Dewey's test doesn't run a 500, only a 503. Both go through the same
non-403 branch, and my P9 and P12 run the 500. That's enough.
## Probes
Fixtures as in round 2. P10, P11 and P13 use 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 |
| P2 board 403 | tree, footer, `#status` and `#fresh-board` say not read |
| P3, P4, P4b bus refusal | the palette and section list show the four views only |
| P6, P7 | `banner ref` then `banner err`, no task rows |
| P8 the toast before any copy | 1×1, empty, in the tree |
| **P9** good read, board 403, then a 500 | `#board-view` shown but empty, 0 rows, `#waiting` and `#sessions` empty, tree, footer, `#status` and `#fresh-board` say "the read failed". Was the refused rows. Fixed |
| P9b then a good read | 2 rows, footer "waiting 1 · working 1" |
| **P10** History open, then every board request 403 | `#conversation` hidden, `#conv-log` empty, form hidden, `#board-refused` shown. Was the full history on screen. Fixed |
| P11 refusal, recovery, History again with the catalogue failing | `#conv-pick` shown with the earlier conversation's title. See note 1 |
| P12 first read 500, no scan ever | empty board, "the read failed" everywhere, banner "No board data loaded" |
| P13 a 403 while a history page is in flight, then the page answers | `#conv-log` stays empty, `#conversation` stays hidden. The `convGen` bump in `closeConversation()` drops the late page |
## Mutants
14 of 14 killed (`r3/mut/summary.txt`). Every kill is an assertion or a
wait timeout in the shell browser test, not a load error.
| Mutant | Change | Killed at |
|---|---|---|
| R01 | no close block on a refusal | `#conversation` hidden, line 200 |
| R02 | close without clearing `#conv-log` | `#conv-log` empty, line 202 |
| R03 | `showConversation(false)` instead of `closeConversation()` | the keyboard focus wait, line 215 |
| R04 | `if (!data)` becomes `if (refused)` | footer "the read failed", line 180 |
| R05 | no `replaceChildren()` loop | no rows or cards, line 178 |
| R06 | the loop skips `seen` | line 178 |
| R07 | the loop skips `waiting` | line 178 |
| R08 | the loop skips `sessions` | line 178 |
| R09 | no count dashes | `#waiting-count`, line 170 |
| R10 | footer always "refused" | line 180 |
| R11 | `why` always "refused" | `#status`, line 182 |
| R12 | no `inspect()` in the no-data block | inspector hidden, line 164 |
| R13 | a refusal keeps `project` | "All projects" pressed after recovery, line 187 |
| R14 | a refusal keeps `selected` | inspector hidden, line 164 |
R03 passes the conversation assertions, because they check only that the
panel is hidden and the log empty. It dies at the keyboard step because
`conv` stays set. The next Enter on a row runs `closeConversation()`
first (line 347), which moves focus to the old History button before
`focusSnapshot()`. Escape then returns focus to History, not to the row.
A direct check that the conversation is closed would catch it sooner, but
the kill is real.
## Notes (not blocking)
1. **Stale session picker (P11), not from this row.** `openConversation()`
clears the title, the meta line and the log, but replaces the
`#conv-pick` options only after a good catalogue read. When the
catalogue fails, the picker keeps the previous conversation's titles,
shown under "History unavailable". That can be a conversation from
before a refusal or from another seat. Choosing one doesn't open the
wrong session: the next catalogue read doesn't find it under this row
and falls back. Base `03969219` has the same code. The fix is one line,
`$('conv-pick').replaceChildren()` next to the `conv-meta` clear. It
fits in row 54 or a later shell row.
2. **Focus after a refusal closes a conversation.** In `error()`,
`closeConversation()` runs before the no-data block.
`restore()` puts focus back on the History button, which is still in
the DOM at that point. The next lines empty `#sessions`, the button
goes, and focus falls to `<body>` (P10 `active: "BODY"`). The
`$('main').focus()` fallback in `closeConversation()` has already run
by then. A keyboard user ends up at the top of the document after a
refusal. Repeating the fallback at the end of `error()` fixes it.
3. Round 2's note 2 (the hidden inspector keeps its text in the DOM) and
Filbert's N4 to N7 still stand. M07 stays accepted. Round 2's note 3,
the refresh timer race, is now in row 54 with Dewey's test, and I'll
check it there.
## Files
- `r3/candidate-manifest.sha256`: copy of Dewey's.
- `r3/gate.sh`, `r3/out/`: suite runs, `summary.txt`,
`test-release-docker.txt` and `r2-to-r3.diff`.
- `r3/mut/`: mutant definitions, runner, diffs, outputs, `summary.txt` and
the manifest check after the runs.
- `r3/probes/`: the probe test and its output.