tasks, webui: field table notes and tests, scroll hint label, row 88 (#1582)
Row 61 follow-up. Filbert approved round 1 in comment 28214; Sage's full gate passed 16:18:32-16:22:34Z. The public/bus.js change is the one aria-label line Mos released under decision 123. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -519,7 +519,7 @@ GET `/api/task-fields` is row 61's read (#1550): the task field table,
|
||||
request. It is repository data, so it answers without `--business` or a
|
||||
system config. The answer is the table itself, with no `at`. A table that
|
||||
fails its check is refused whole with 503 `read-failed` and the loader's
|
||||
message, which names the file and never a path. Any other method is 405.
|
||||
message, which names the file or the key at fault, never a path. Any other method is 405.
|
||||
|
||||
Console's shared CSS, Console CSS, brand.js and local Manrope fonts were copied
|
||||
unchanged from `agents/dewey/work/wui/`. Font license and source URLs accompany
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
if (!fields.length) return `<p class="small">${why(Object.assign(new ReadError('invalid-response', ''), { module: true }))}</p>`;
|
||||
const by = f => (Array.isArray(f.writes) ? f.writes : []).map(w => `${txt(WRITER[verbs[w.verb]] ?? 'unknown')} <span class="source"><code>${txt(w.verb)}</code></span>`).join('<br>');
|
||||
const rows = fields.map(f => `<tr><th scope="row">${txt(f.label)}${f.note ? `<span class="source">${txt(f.note)}</span>` : ''}</th><td>${by(f)}</td></tr>`).join('');
|
||||
return `<div class="table-wrap" tabindex="0" role="region" aria-label="Who may write each field"><table class="s1-table s1-writers"><thead><tr><th scope="col">Field</th><th scope="col">Writer and verb</th></tr></thead><tbody>${rows}</tbody></table></div>`;
|
||||
return `<div class="table-wrap" tabindex="0" role="region" aria-label="Who may write each field, scroll for all columns"><table class="s1-table s1-writers"><thead><tr><th scope="col">Field</th><th scope="col">Writer and verb</th></tr></thead><tbody>${rows}</tbody></table></div>`;
|
||||
}
|
||||
async function taskView(get, ref) {
|
||||
const row = (await get('tasks')).find(r => r.task_ref === ref);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// CLI or reaches a real broker; the seeding writes go straight to the broker.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { copyFileSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { copyFileSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { Store } from '../../bus/src/store.mjs';
|
||||
@@ -71,12 +71,33 @@ function seed(root) {
|
||||
return { store, bus, calls, second, fail: code => { fail = code; }, ids: { old: old.id, open: open.id, routed: routed.id, resolved: resolved.id } };
|
||||
}
|
||||
|
||||
// Row 61 (#1550): the README's account of a refused field table, pinned so the wording and the
|
||||
// behavior it describes move together. reads.test.mjs checks the messages themselves.
|
||||
test('the README says a refused field table names the file or the key at fault, never a path', () => {
|
||||
const readme = readFileSync(new URL('../README.md', import.meta.url), 'utf8');
|
||||
assert.match(readme, /the loader's\s+message, which names the file or the key at fault, never a path\./);
|
||||
});
|
||||
|
||||
test('S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes', { timeout: 180000 }, async () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'webui-bus-browser-'));
|
||||
const s = seed(root), f = await fixture(), b = await browser();
|
||||
// Row 61 (#1550): a copy of the field table, so the test can break it and read it again.
|
||||
const fieldsFile = join(root, 'fields.json');
|
||||
copyFileSync(FIELDS_FILE, fieldsFile);
|
||||
// Hostile text in the comments row, as the table's own label and note: the label is at most
|
||||
// 80 characters, so it carries an excerpt of HOSTILE; the note carries all of it.
|
||||
const hostile = JSON.parse(readFileSync(fieldsFile, 'utf8'));
|
||||
const comments = hostile.fields.find(x => x.field === 'comments');
|
||||
comments.label = `Comments ${HOSTILE.slice(34)}`;
|
||||
comments.note = `Append only. ${HOSTILE}`;
|
||||
// The label excerpt must keep each hostile class, so a later change to the slice cannot drop one
|
||||
// without failing here. These assert on the data copied in, not on the rendered page.
|
||||
assert.ok(comments.label.length <= 80, 'the label fits the 80-character cap');
|
||||
assert.ok(comments.label.includes('<img'), 'the label excerpt keeps the img tag');
|
||||
assert.ok(comments.label.includes('onerror'), 'the label excerpt keeps onerror');
|
||||
assert.ok(comments.label.includes('\u001b'), 'the label excerpt keeps the ESC byte');
|
||||
assert.ok(comments.label.includes('\u202e'), 'the label excerpt keeps the bidi character');
|
||||
writeFileSync(fieldsFile, JSON.stringify(hostile));
|
||||
const web = await startServer({ port: 0, board: f.boardURL, bus: s.bus, fieldsFile }), base = `http://127.0.0.1:${web.address().port}/`;
|
||||
const out = process.env.WEBUI_EVIDENCE;
|
||||
if (out) mkdirSync(out, { recursive: true });
|
||||
@@ -212,6 +233,10 @@ test('S5 pages in a browser: real broker rows, inert text, Copy, routes, failure
|
||||
assert.match(t7, /Percent donethe assigned role task\.update\.assigned/);
|
||||
assert.match(t7, /DoneCites a review verdict, which is recorded and not checked\.pm task\.close/);
|
||||
assert.equal(await b.evaluate('document.querySelectorAll("#bus-view table.s1-writers tbody tr").length'), 11);
|
||||
assert.equal(await b.evaluate('document.querySelector("#bus-view table.s1-writers").closest("[role=region]").getAttribute("aria-label")'), 'Who may write each field, scroll for all columns');
|
||||
// The hostile label and note are shown as text, and no element is made of them.
|
||||
assert.ok(t7.includes('Comments <img src=x onerror="window.injected=2">'), 'the label excerpt is shown');
|
||||
assert.ok(t7.includes(`Append only. ${HOSTILE.slice(0, 34)}`), 'the note is shown');
|
||||
await inertCheck('task'); await noOverflow('task');
|
||||
// A table that fails its check: the section says why, and the rest of the task page stays.
|
||||
writeFileSync(fieldsFile, '{"source":');
|
||||
|
||||
Reference in New Issue
Block a user