review(53): Darkwing round 3, approve (#1542 comment 27171)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -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
@@ -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
@@ -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.
|
||||||
Reference in New Issue
Block a user