mirror of
https://github.com/mvschwarz/openrig-studio.git
synced 2026-11-07 05:56:21 -10:00
FLOOR: finished work, and find by id (#1)
A person looking at a rig's floor can now see its finished work, with each item's answer first in plain words, and find an item by id. Coverage is honest: a bounded or unreadable read says so instead of claiming there's nothing. An artifact reference wraps without changing its text. Six files (+291/-8): the floor page and server, the runtime contract, and tests. Source-only entry; no tag, pin, release or deploy.
This commit is contained in:
1 parent
e7346bbdbd
commit
652d2980b5
6 files changed
+291
-8
No files matched your search
@@ -581,8 +581,16 @@ function factoryState() {
|
||||
const attachment = typeof d.attached === "boolean"
|
||||
? { attached: d.attached, reason: d.reason ?? null, detail: d.detail ?? null }
|
||||
: {};
|
||||
// Finished work, when the generator sends it (optional; see the contract). Absent stays absent ("this provider doesn't send
|
||||
// finished work"). Sent but not in the declared shape (rows not a list, a row that isn't an item with a string id) is carried as
|
||||
// unreadable, never as "none finished", and never at the cost of the open work beside it.
|
||||
const okRow = (r) => !!r && typeof r === "object" && !Array.isArray(r) && typeof r.id === "string";
|
||||
const done = d.done === undefined ? {}
|
||||
: d.done && typeof d.done === "object" && Array.isArray(d.done.rows) && d.done.rows.every(okRow)
|
||||
? { done: { rows: d.done.rows, coverage: d.done.coverage && typeof d.done.coverage === "object" && !Array.isArray(d.done.coverage) ? d.done.coverage : {} } }
|
||||
: { done: { rows: [], coverage: { unreadable: "the finished work this box sent wasn't in the declared shape" } } };
|
||||
return { ok: true, rig: d.rig || "fixture", generatedAt: new Date().toISOString(), ...attachment,
|
||||
seats: Array.isArray(d.seats) ? d.seats : [], queue: Array.isArray(d.queue) ? d.queue : [] };
|
||||
seats: Array.isArray(d.seats) ? d.seats : [], queue: Array.isArray(d.queue) ? d.queue : [], ...done };
|
||||
}
|
||||
|
||||
// ---- stream: SSE change-signal (signal-only contract) ----------------------
|
||||
|
||||
+83
-4
@@ -78,6 +78,16 @@
|
||||
.queue-title { color: var(--text); font-weight: 600; }
|
||||
.queue-state { color: var(--idle); font-size: 10px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; }
|
||||
.route { color: var(--muted); overflow-wrap: anywhere; }
|
||||
.item-id { display: block; margin-top: 3px; color: var(--muted); font: 10px ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; }
|
||||
.result { max-width: 40ch; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.artifact { color: var(--muted); overflow-wrap: break-word; }
|
||||
.unset { color: var(--muted); font-style: italic; }
|
||||
.coverage { margin: -4px 0 10px; color: var(--muted); font-size: 11px; }
|
||||
/* An element marked hidden stays hidden, whatever display a class gives it (the find box is display:flex). */
|
||||
[hidden] { display: none !important; }
|
||||
.find { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 0 0 18px; }
|
||||
.find input { flex: 1 1 260px; max-width: 420px; min-width: 0; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); color: var(--text); font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
.find output { color: var(--muted); font-size: 11px; }
|
||||
.notice { padding: 18px; color: var(--blocked); }
|
||||
footer { margin-top: 22px; color: var(--muted); font-size: 11px; }
|
||||
@media (max-width: 600px) {
|
||||
@@ -93,6 +103,7 @@
|
||||
<div><div class="eyebrow">Live activity</div><h1 id="rig">Rig floor</h1></div>
|
||||
<div id="connection" class="connection"><span class="pulse"></span><span>connecting</span></div>
|
||||
</header>
|
||||
<div class="find" id="findbox" role="search" hidden><input id="find" type="search" placeholder="Find work by id (qitem-…)" aria-label="Find work by id" autocomplete="off" spellcheck="false"><output id="found" aria-live="polite"></output></div>
|
||||
<div id="content"><div class="notice">Reading the runtime contract…</div></div>
|
||||
<footer id="updated"></footer>
|
||||
</main>
|
||||
@@ -156,13 +167,56 @@
|
||||
<td class="numeric">${seat.ageMinutes == null ? 'unknown' : `${escapeHtml(seat.ageMinutes)}m`}</td>
|
||||
<td class="numeric">${escapeHtml(seat.pendingWork)}</td>
|
||||
</tr>`).join('') || '<tr><td class="empty" colspan="6">No seats reported.</td></tr>';
|
||||
// Ids and the find box come with finished work: a generator that sends no `done` gets the floor exactly as before.
|
||||
const showIds = !!(state.done && Array.isArray(state.done.rows));
|
||||
document.querySelector('#findbox').hidden = !showIds;
|
||||
const queue = state.queue.map(item => `
|
||||
<tr>
|
||||
<td class="queue-title">${escapeHtml(item.title)}</td>
|
||||
<tr${showIds ? ` data-id="${escapeHtml(item.id)}"` : ''}>
|
||||
<td class="queue-title">${escapeHtml(item.title)}${showIds ? `<span class="item-id">${escapeHtml(item.id)}</span>` : ''}</td>
|
||||
<td class="queue-state">${escapeHtml(item.state)}</td>
|
||||
<td class="route">${escapeHtml(item.source)}</td>
|
||||
<td class="route">${escapeHtml(item.destination)}</td>
|
||||
</tr>`).join('') || '<tr><td class="empty" colspan="4">Queue is clear.</td></tr>';
|
||||
// While a find is on, a table with no matching row says so instead of standing empty under its count.
|
||||
const queueRows = queue + (showIds && state.queue.length ? '<tr id="nomatch-open" hidden><td class="empty" colspan="4">No match in the queue.</td></tr>' : '');
|
||||
|
||||
// Work that finished, from a provider that sends `done` (an older one doesn't, and then this section isn't drawn). The answer
|
||||
// a seat closed the work with is its result; a field the work doesn't carry reads "not recorded", and a result the provider
|
||||
// couldn't read says so instead of looking empty.
|
||||
const unset = (text) => `<span class="unset">${text}</span>`;
|
||||
// A closure in plain words; the queue's own word stays in the tooltip.
|
||||
const SAY = { 'no-follow-on': 'Done, nothing follows', handed_off_to: 'Handed off to', blocked_on: 'Blocked on', denied: 'Denied', canceled: 'Canceled', escalation: 'Escalated' };
|
||||
const decisionOf = (d) => !d?.reason ? unset('not recorded')
|
||||
: `<span title="${escapeHtml(d.reason)}">${escapeHtml(SAY[d.reason] || d.reason)}${d.target ? `${SAY[d.reason] && /to$|on$/.test(SAY[d.reason]) ? ' ' : ': '}${escapeHtml(d.target)}` : ''}</span>`;
|
||||
// An artifact reference may break after "@", ":", "/" or "#", not inside a word.
|
||||
// Split the raw text first and escape each part: inserting after escaping would cut an entity (') apart.
|
||||
const breakable = (t) => String(t).split(/(?<=[@:/#])/).map(escapeHtml).join('<wbr>');
|
||||
const done = state.done && Array.isArray(state.done.rows) ? state.done : null;
|
||||
const doneRows = done ? done.rows.map(item => {
|
||||
if (!item || typeof item !== 'object') return '';
|
||||
const decision = decisionOf(item.decision);
|
||||
const answer = item.result ?? item.resolution;
|
||||
const result = item.resultRead === false ? unset("couldn't be read") : typeof answer === 'string' && answer !== '' ? escapeHtml(answer) : unset('not recorded');
|
||||
const closed = Date.parse(item.closedAt || '');
|
||||
return `
|
||||
<tr data-id="${escapeHtml(item.id)}">
|
||||
<td class="queue-title">${escapeHtml(item.title)}<span class="item-id">${escapeHtml(item.id)}</span></td>
|
||||
<td class="result">${result}</td>
|
||||
<td class="route">${decision}</td>
|
||||
<td class="artifact">${item.artifact ? breakable(item.artifact) : unset('not recorded')}</td>
|
||||
<td class="numeric">${Number.isFinite(closed) ? escapeHtml(new Date(closed).toLocaleString(undefined, { timeZoneName: 'short' })) : unset('unknown')}</td>
|
||||
</tr>`; }).join('') || `<tr><td class="empty" colspan="5">None of this rig's work among the newest ${escapeHtml(done.coverage?.looked ?? 0)} finished items on this box.</td></tr>` : '';
|
||||
const cov = done?.coverage || {};
|
||||
const doneSection = !done ? '' : cov.unreadable
|
||||
? `<section><h2>Done recently</h2><div class="notice">Finished work couldn't be read: ${escapeHtml(cov.unreadable)}.</div></section>`
|
||||
: `<section>
|
||||
<h2>Done recently · ${done.rows.length}</h2>
|
||||
<p class="coverage">The newest ${escapeHtml(done.rows.length)} of this rig's finished work, from the newest ${escapeHtml(cov.looked ?? 0)} finished items on this box.</p>
|
||||
<div class="table-wrap"><table aria-label="Finished work">
|
||||
<thead><tr><th>Work</th><th>Result</th><th>Decision</th><th>Artifact</th><th class="numeric">Closed</th></tr></thead>
|
||||
<tbody>${doneRows}${done.rows.length ? '<tr id="nomatch-done" hidden><td class="empty" colspan="5">No match in finished work.</td></tr>' : ''}</tbody>
|
||||
</table></div>
|
||||
</section>`;
|
||||
|
||||
content.innerHTML = `
|
||||
${welcomeHtml()}
|
||||
@@ -182,12 +236,37 @@
|
||||
<h2>Queue · ${state.queue.length}</h2>
|
||||
<div class="table-wrap"><table aria-label="Rig queue">
|
||||
<thead><tr><th>Work</th><th>State</th><th>Source</th><th>Destination</th></tr></thead>
|
||||
<tbody>${queue}</tbody>
|
||||
<tbody>${queueRows}</tbody>
|
||||
</table></div>
|
||||
</section>`;
|
||||
</section>${doneSection}`;
|
||||
lastState = state;
|
||||
findWork();
|
||||
updated.textContent = `Runtime snapshot · ${new Date(state.generatedAt).toLocaleString()}`;
|
||||
}
|
||||
|
||||
// Find by id: only among the work this floor already holds (this rig's open work and its newest finished work); it reads
|
||||
// nothing else. Kept outside #content so a refresh doesn't clear what was typed, and applied again after every refresh.
|
||||
let lastState = null;
|
||||
const findInput = document.querySelector('#find'), found = document.querySelector('#found');
|
||||
// "This rig's" work is work with one of this floor's seats at either end: a generator that still sends other rigs' open work
|
||||
// can't make the find box call that ours.
|
||||
function findWork() {
|
||||
const q = findInput.value.trim();
|
||||
const seats = new Set((lastState?.seats || []).map(s => s.seat));
|
||||
const ours = item => item && (seats.has(item.source) || seats.has(item.destination));
|
||||
const hit = item => ours(item) && String(item.id).includes(q);
|
||||
const openHits = (lastState?.queue || []).filter(hit), doneHits = (lastState?.done?.rows || []).filter(hit);
|
||||
const shown = new Set([...openHits, ...doneHits].map(item => String(item.id)));
|
||||
for (const row of content.querySelectorAll('tr[data-id]')) row.hidden = !!q && !shown.has(row.dataset.id);
|
||||
for (const [sel, hits] of [['#nomatch-open', openHits], ['#nomatch-done', doneHits]]) { const r = document.querySelector(sel); if (r) r.hidden = !q || hits.length > 0; }
|
||||
if (!q) { found.textContent = ''; return; }
|
||||
const done = lastState?.done;
|
||||
found.textContent = openHits.length || doneHits.length ? `${openHits.length} open, ${doneHits.length} finished`
|
||||
: done?.coverage?.unreadable ? "Not in this rig's open work. Finished work couldn't be read, so this can't say whether it finished."
|
||||
: `No work with that id in this rig's open work or its newest ${done?.rows?.length ?? 0} finished items (older finished work isn't searched).`;
|
||||
}
|
||||
findInput.addEventListener('input', findWork);
|
||||
|
||||
// The box's own introduction, declared in its manifest. Absent on a box
|
||||
// that declares none, which is the normal case — this surface ships to
|
||||
// every install and must not assume it is anybody's landing page.
|
||||
|
||||
@@ -110,6 +110,27 @@ by this contract (declared here, field-by-field), not by any backing system.
|
||||
the nearest minute, or `null` when no activity timestamp is known. It
|
||||
measures quietness, not uptime: a `working` seat can have a nonzero age.
|
||||
- Degraded shape: `{ "ok": false, "degraded": "<why>", "rig": null, "seats": [], "queue": [] }`.
|
||||
- `done` (optional, provisional): finished work, so work handed to a seat stays findable after it closes. Absent means the
|
||||
generator doesn't send finished work; `rows: []` means none has finished. `queue` stays open work only.
|
||||
|
||||
```json
|
||||
"done": {
|
||||
"rows": [
|
||||
{ "id": "<item id>", "state": "done", "title": "<one line>",
|
||||
"source": "<session>", "destination": "<session>", "closedAt": "<ISO timestamp>",
|
||||
"decision": { "reason": "<closure reason> | null", "target": "<closure target> | null" },
|
||||
"result": "<the note the item was closed with> | null", "resultRead": true,
|
||||
"resolution": "<string> | null", "artifact": "<evidence reference> | null",
|
||||
"handedOffTo": "<item id> | null", "handedOffFrom": "<item id> | null" }
|
||||
],
|
||||
"coverage": { "looked": 100, "ours": 12, "shown": 10, "limit": 100 }
|
||||
}
|
||||
```
|
||||
|
||||
A field the item doesn't carry is `null`, never invented. `resultRead: false` means the result couldn't be read (not that
|
||||
there is none). `coverage` says how much was looked at; `{ "unreadable": "<why>" }` there means finished work couldn't be read.
|
||||
The runtime carries `done` through only in this shape: a `done` that is sent but isn't in it (rows not a list, a row without a
|
||||
string `id`) arrives as `{ "rows": [], "coverage": { "unreadable": "<why>" } }`, never as none, and the open work is kept. Its text is shown as written: this runtime has no private display mode.
|
||||
|
||||
## stream
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// FLOOR keeps finished work findable: the runtime carries the generator's optional `done` block in its declared shape
|
||||
// (contract/runtime-api.md), and the surface shows it as "Done recently" with "not recorded" where a field is missing and a
|
||||
// find-by-id box that looks only at what the floor already holds. Invented fixture data only.
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import net from "node:net";
|
||||
import path from "node:path";
|
||||
import vm from "node:vm";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const surface = fs.readFileSync(path.join(REPO, "app", "surfaces", "floor.html"), "utf8");
|
||||
const DONE = { rows: [
|
||||
{ id: "qitem-20261023070000-aaaa", state: "done", title: "Fix the README typo.", source: "assistant@lab", destination: "lab-impl@lab",
|
||||
closedAt: "2026-10-23T07:00:00.000Z", decision: { reason: "no-follow-on", target: null }, result: "Fixed <b>line 12</b>.", resultRead: true,
|
||||
resolution: null, artifact: "docs/README.md@1a2b3c4", handedOffTo: null, handedOffFrom: null },
|
||||
{ id: "qitem-20261023065900-bbbb", state: "done", title: "Answer a question", source: "lab-qa@lab", destination: "lab-impl@lab",
|
||||
closedAt: "2026-10-23T06:59:00.000Z", decision: { reason: null, target: null }, result: null, resultRead: true, resolution: null, artifact: null },
|
||||
{ id: "qitem-20261023065800-cccc", state: "done", title: "Unread", source: "lab-qa@lab", destination: "lab-impl@lab",
|
||||
closedAt: "2026-10-23T06:58:00.000Z", decision: { reason: "handed_off_to", target: "qitem-x" }, result: null, resultRead: false, artifact: null },
|
||||
], coverage: { looked: 40, ours: 3, shown: 3, limit: 100 } };
|
||||
const SEATS = [{ seat: "lab-impl@lab" }, { seat: "lab-qa@lab" }];
|
||||
const OPEN = [{ id: "qitem-20261023071000-dddd", state: "pending", destination: "lab-impl@lab", source: "assistant@lab", tags: [], updated: "2026-10-23T07:10:00.000Z", title: "Open work" }];
|
||||
|
||||
const freePort = () => new Promise((resolve, reject) => { const s = net.createServer(); s.once("error", reject); s.listen(0, "127.0.0.1", () => { const { port } = s.address(); s.close(() => resolve(port)); }); });
|
||||
async function stateFrom(fixture) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "floor-done-"));
|
||||
fs.cpSync(path.join(REPO, "app"), path.join(dir, "app"), { recursive: true });
|
||||
fs.cpSync(path.join(REPO, "fixtures"), path.join(dir, "fixtures"), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, "fixtures", "factory-state.json"), JSON.stringify(fixture));
|
||||
const port = await freePort(), proc = spawn(process.execPath, [path.join(dir, "app", "serve-studio.mjs"), "--port", String(port), "--fixtures", path.join(dir, "fixtures")], { cwd: dir, stdio: "ignore" });
|
||||
try {
|
||||
for (let i = 0; i < 100; i++) { try { return await (await fetch(`http://127.0.0.1:${port}/api/factory/state`)).json(); } catch { await new Promise((r) => setTimeout(r, 100)); } }
|
||||
throw new Error("runtime never answered");
|
||||
} finally { proc.kill(); fs.rmSync(dir, { recursive: true, force: true }); }
|
||||
}
|
||||
|
||||
test("the runtime carries `done` through in its declared shape; absent stays absent; a malformed one is carried as unreadable, never as none", async () => {
|
||||
const s = await stateFrom({ rig: "lab", attached: true, seats: [], queue: OPEN, done: DONE });
|
||||
assert.deepEqual(s.done, DONE); assert.deepEqual(s.queue, OPEN, "queue is unchanged");
|
||||
assert.equal("done" in await stateFrom({ rig: "lab", attached: true, seats: [], queue: [] }), false, "a generator that sends no done: absent, not empty");
|
||||
for (const bad of ["nope", { rows: [null] }, { rows: [{ title: "no id" }] }, { rows: "x" }, null]) {
|
||||
const s2 = await stateFrom({ rig: "lab", attached: true, seats: [], queue: OPEN, done: bad });
|
||||
assert.deepEqual(s2.done, { rows: [], coverage: { unreadable: "the finished work this box sent wasn't in the declared shape" } }, JSON.stringify(bad));
|
||||
assert.deepEqual(s2.queue, OPEN, "a bad finished-work block never costs the open work");
|
||||
}
|
||||
assert.deepEqual((await stateFrom({ rig: "lab", attached: true, seats: [], queue: [], done: { rows: [] } })).done, { rows: [], coverage: {} });
|
||||
});
|
||||
|
||||
// The surface's own script, run with just enough of a page for render() and the find box.
|
||||
function page() {
|
||||
const el = (sel) => ({ sel, innerHTML: "", textContent: "", value: "", className: "", lastElementChild: { textContent: "" }, hidden: false,
|
||||
addEventListener() {}, querySelectorAll() { return rowsOf(this.innerHTML); } });
|
||||
const els = { "#content": el("#content"), "#rig": el("#rig"), "#updated": el("#updated"), "#connection": el("#connection"), "#find": el("#find"), "#found": el("#found"), "#findbox": el("#findbox"), "#nomatch-open": el("#nomatch-open"), "#nomatch-done": el("#nomatch-done") };
|
||||
const rows = new Map();
|
||||
const rowsOf = (html) => [...html.matchAll(/<tr data-id="([^"]+)">/g)].map((m) => rows.get(m[1]) || rows.set(m[1], { dataset: { id: m[1] }, hidden: false }).get(m[1]));
|
||||
const ctx = { document: { querySelector: (s) => els[s] }, fetch: () => new Promise(() => {}), EventSource: function () {}, setInterval, clearInterval, setTimeout, clearTimeout, parent: { postMessage() {} }, console };
|
||||
vm.createContext(ctx);
|
||||
vm.runInContext(surface.match(/<script>([\s\S]*)<\/script>/)[1].replace(/\bboot\(\);\s*$/, ""), ctx);
|
||||
return { ctx, els, rowsOf };
|
||||
}
|
||||
|
||||
test("FLOOR draws 'Done recently': id, decision, result (escaped), artifact; 'not recorded' and 'couldn't be read' are said; the open count is open work only", () => {
|
||||
const { ctx, els } = page();
|
||||
ctx.render({ ok: true, rig: "lab", generatedAt: "2026-10-23T07:11:00.000Z", attached: true, seats: SEATS, queue: OPEN, done: DONE });
|
||||
const h = els["#content"].innerHTML;
|
||||
assert.match(h, /<b>1<\/b><span>queue items<\/span>/, "finished work doesn't count as queued");
|
||||
assert.match(h, /<h2>Done recently · 3<\/h2>/);
|
||||
assert.match(h, /The newest 3 of this rig's finished work, from the newest 40 finished items on this box\./);
|
||||
assert.match(h, /<span class="item-id">qitem-20261023070000-aaaa<\/span>/);
|
||||
assert.match(h, /<td class="route"><span title="no-follow-on">Done, nothing follows<\/span><\/td>/, "the decision in plain words, the queue's word in the tooltip");
|
||||
assert.match(h, /<th>Work<\/th><th>Result<\/th><th>Decision<\/th><th>Artifact<\/th><th class="numeric">Closed<\/th>/, "the answer right after the work (ux 07:42Z)");
|
||||
assert.match(h, /<td class="result">Fixed <b>line 12<\/b>\.<\/td>/, "result text is escaped, never markup");
|
||||
assert.match(h, /<td class="artifact">docs\/<wbr>README\.md@<wbr>1a2b3c4<\/td>/, "an artifact breaks after / @ : #, not inside a word");
|
||||
assert.equal((h.match(/<span class="unset">not recorded<\/span>/g) || []).length, 4, "row b: decision, result and artifact; row c: artifact only (its decision is recorded, its result couldn't be read)");
|
||||
assert.match(h, /<td class="result"><span class="unset">couldn't be read<\/span><\/td>/);
|
||||
assert.match(h, /<span title="handed_off_to">Handed off to qitem-x<\/span>/);
|
||||
ctx.render({ ok: true, rig: "lab", generatedAt: "x", attached: true, seats: [], queue: [] });
|
||||
assert.doesNotMatch(els["#content"].innerHTML, /Done recently/, "a provider that sends no done: no section");
|
||||
ctx.render({ ok: true, rig: "lab", generatedAt: "x", attached: true, seats: [], queue: [], done: { rows: [], coverage: { unreadable: "rig queue timed out" } } });
|
||||
assert.match(els["#content"].innerHTML, /Finished work couldn't be read: rig queue timed out\./);
|
||||
});
|
||||
|
||||
test("find by id looks only at the floor's own rows: matches are kept, others hidden, and a miss says so plainly", () => {
|
||||
const { ctx, els, rowsOf } = page();
|
||||
ctx.render({ ok: true, rig: "lab", generatedAt: "x", attached: true, seats: SEATS, queue: OPEN, done: DONE });
|
||||
els["#find"].value = "aaaa"; ctx.findWork();
|
||||
assert.equal(els["#nomatch-open"].hidden, false, "the queue says it has no match instead of standing empty"); assert.equal(els["#nomatch-done"].hidden, true);
|
||||
assert.match(els["#content"].innerHTML, /<tr id="nomatch-open" hidden><td class="empty" colspan="4">No match in the queue\.<\/td><\/tr>/);
|
||||
assert.match(els["#content"].innerHTML, /<tr id="nomatch-done" hidden><td class="empty" colspan="5">No match in finished work\.<\/td><\/tr>/);
|
||||
const rows = rowsOf(els["#content"].innerHTML);
|
||||
assert.deepEqual(rows.filter((r) => !r.hidden).map((r) => r.dataset.id), ["qitem-20261023070000-aaaa"]);
|
||||
assert.equal(els["#found"].textContent, "0 open, 1 finished");
|
||||
els["#find"].value = "qitem-from-another-rig"; ctx.findWork();
|
||||
assert.equal(els["#found"].textContent, "No work with that id in this rig's open work or its newest 3 finished items (older finished work isn't searched).");
|
||||
assert.ok(rowsOf(els["#content"].innerHTML).every((r) => r.hidden));
|
||||
els["#find"].value = ""; ctx.findWork(); assert.equal(els["#found"].textContent, ""); assert.ok(rowsOf(els["#content"].innerHTML).every((r) => !r.hidden));
|
||||
assert.doesNotMatch(surface.slice(surface.indexOf("function findWork"), surface.indexOf("findInput.addEventListener")), /fetch\(/, "the find box reads nothing");
|
||||
});
|
||||
|
||||
// Review fixes on the first candidate. Each fails on it.
|
||||
test("find counts only this floor's work: another rig's open item a generator still sends is neither found nor shown", () => {
|
||||
const { ctx, els, rowsOf } = page();
|
||||
const foreign = { id: "qitem-20261023071100-ffff", state: "pending", destination: "y@other", source: "x@other", tags: [], updated: "x", title: "theirs" };
|
||||
ctx.render({ ok: true, rig: "lab", generatedAt: "x", attached: true, seats: SEATS, queue: [...OPEN, foreign], done: DONE });
|
||||
els["#find"].value = "ffff"; ctx.findWork();
|
||||
assert.match(els["#found"].textContent, /^No work with that id in this rig's open work/);
|
||||
assert.ok(rowsOf(els["#content"].innerHTML).every((r) => r.hidden));
|
||||
els["#find"].value = "dddd"; ctx.findWork(); assert.equal(els["#found"].textContent, "1 open, 0 finished");
|
||||
});
|
||||
|
||||
test("bounded and unreadable finished work are said as such: no 'yet', and find doesn't claim a miss it can't know", () => {
|
||||
const { ctx, els } = page();
|
||||
ctx.render({ ok: true, rig: "lab", generatedAt: "x", attached: true, seats: SEATS, queue: OPEN, done: { rows: [], coverage: { looked: 100, ours: 0, shown: 0, limit: 100 } } });
|
||||
assert.match(els["#content"].innerHTML, /None of this rig's work among the newest 100 finished items on this box\./); assert.doesNotMatch(els["#content"].innerHTML, /yet/);
|
||||
ctx.render({ ok: true, rig: "lab", generatedAt: "x", attached: true, seats: SEATS, queue: OPEN, done: { rows: [], coverage: { unreadable: "rig queue timed out" } } });
|
||||
els["#find"].value = "qitem-anything"; ctx.findWork();
|
||||
assert.equal(els["#found"].textContent, "Not in this rig's open work. Finished work couldn't be read, so this can't say whether it finished.");
|
||||
});
|
||||
|
||||
test("a hidden element stays hidden whatever a class says: the stylesheet makes [hidden] win", () => {
|
||||
assert.match(surface, /\[hidden\] \{ display: none !important; \}/);
|
||||
assert.match(surface, /\.find \{ display: flex;/, "the rule [hidden] has to beat is still there (positive control)");
|
||||
assert.match(surface, /<div class="find" id="findbox" role="search" hidden>/);
|
||||
});
|
||||
|
||||
test("an artifact reference wraps without changing its text: an apostrophe or ampersand survives the break points", () => {
|
||||
const { ctx, els } = page();
|
||||
const ref = "folder/owner's-output#1 & more@v2:x";
|
||||
ctx.render({ ok: true, rig: "lab", generatedAt: "x", attached: true, seats: SEATS, queue: [], done: { rows: [{ ...DONE.rows[0], artifact: ref }], coverage: { looked: 1 } } });
|
||||
const cell = els["#content"].innerHTML.match(/<td class="artifact">([\s\S]*?)<\/td>/)[1];
|
||||
assert.doesNotMatch(cell, /&#<wbr>/, "no entity is cut");
|
||||
const text = cell.replace(/<wbr>/g, "").replace(/'/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"');
|
||||
assert.equal(text, ref, "the rendered text is the reference itself");
|
||||
assert.equal((cell.match(/<wbr>/g) || []).length, 4, "after /, #, @ and :");
|
||||
});
|
||||
@@ -10,7 +10,7 @@ const surface = fs.readFileSync(SURFACE, "utf8");
|
||||
|
||||
test("FLOOR renders seats and queue as semantic tables instead of cards", () => {
|
||||
assert.match(surface, /<title>Rig Floor<\/title>/, "positive control: loaded the FLOOR surface");
|
||||
assert.equal((surface.match(/<table\b/g) || []).length, 2);
|
||||
assert.equal((surface.match(/<table\b/g) || []).length, 3, "seats, queue and finished work");
|
||||
|
||||
for (const heading of ["Seat", "Pod / member", "State", "Lifecycle", "Age", "Pending"]) {
|
||||
assert.match(surface, new RegExp(`<th[^>]*>${heading.replace(" / ", " \\/ ")}<\\/th>`, "i"));
|
||||
|
||||
@@ -23,9 +23,21 @@ import net from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import vm from "node:vm";
|
||||
|
||||
const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
// What FLOOR's own script draws into #content from one state, run with just enough of a page (no network, no timers fired).
|
||||
function renderedFloor(html, state) {
|
||||
const el = () => ({ innerHTML: "", textContent: "", value: "", className: "", hidden: true, lastElementChild: { textContent: "" }, addEventListener() {}, querySelectorAll: () => [] });
|
||||
const els = {};
|
||||
const ctx = { document: { querySelector: (s) => (els[s] ??= el()) }, fetch: () => new Promise(() => {}), EventSource: function () {}, setInterval, clearInterval, setTimeout, clearTimeout, parent: { postMessage() {} }, console };
|
||||
vm.createContext(ctx);
|
||||
vm.runInContext(html.match(/<script>([\s\S]*)<\/script>/)[1].replace(/\bboot\(\);\s*$/, ""), ctx);
|
||||
ctx.render(state);
|
||||
return els["#content"].innerHTML;
|
||||
}
|
||||
|
||||
const git = (...args) => {
|
||||
const r = spawnSync("git", args, { cwd: REPO, encoding: "utf8" });
|
||||
assert.equal(r.status, 0, `git ${args.join(" ")} failed: ${r.stderr}`);
|
||||
@@ -120,8 +132,32 @@ test("zero-config: the served rail and its bytes are unchanged from before the c
|
||||
|
||||
assert.deepEqual(await after.json("/surfaces.json"), await before.json("/surfaces.json"),
|
||||
"the zero-config manifest projection changed");
|
||||
assert.equal(await after.text("/surfaces/floor.html"), await before.text("/surfaces/floor.html"),
|
||||
"the bytes of the SDK's own surface changed for a consumer who adopted nothing");
|
||||
// The SDK's own surface for a consumer who adopted nothing. Its bytes can't stay frozen forever (FLOOR gained finished work,
|
||||
// shown only to a generator that sends `done`), so what is held is what such a consumer SEES, styles included:
|
||||
// - the markup outside style and script is the same, apart from elements marked hidden, and those count as unseen ONLY because
|
||||
// the stylesheet says `[hidden] { display: none !important; }` (no class rule can then show them);
|
||||
// - no style rule was removed or changed, and every added rule names only classes and ids that page doesn't show;
|
||||
// - render() draws the same thing from the shipped fixture state.
|
||||
const [a, b] = [await after.text("/surfaces/floor.html"), await before.text("/surfaces/floor.html")];
|
||||
const styleOf = (h) => (h.match(/<style>([\s\S]*?)<\/style>/)?.[1] || "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/@media[^{]*\{/g, "");
|
||||
const rules = (h) => new Set([...styleOf(h).matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((m) => `${m[1].trim().replace(/\s+/g, " ")} { ${m[2].trim().replace(/\s+/g, " ")} }`));
|
||||
const [ra, rb] = [rules(a), rules(b)];
|
||||
assert.ok(rb.size > 10, `positive control: the baseline's rules were read (${rb.size})`);
|
||||
assert.deepEqual([...rb].filter((r) => !ra.has(r)), [], "a style rule the zero-config page uses was removed or changed");
|
||||
const hides = ra.has("[hidden] { display: none !important; }");
|
||||
const markup = (h) => h.replace(/<style>[\s\S]*?<\/style>/, "").replace(/<script>[\s\S]*?<\/script>/, "");
|
||||
const unhidden = (h) => (hides ? h.replace(/<(\w+)\b[^>]*\shidden\b[^>]*>[\s\S]*?<\/\1>\s*/g, "") : h);
|
||||
assert.equal(unhidden(markup(a)), markup(b), "the visible markup of the SDK's own surface changed for a consumer who adopted nothing");
|
||||
const fixture = await after.json("/api/factory/state");
|
||||
assert.equal("done" in fixture, false, "positive control: the shipped fixture sends no finished work");
|
||||
const drawn = renderedFloor(a, fixture);
|
||||
assert.equal(drawn, renderedFloor(b, fixture), "what FLOOR draws changed for a consumer who adopted nothing");
|
||||
const seen = unhidden(markup(a)) + drawn;
|
||||
for (const r of [...ra].filter((x) => !rb.has(x) && x !== "[hidden] { display: none !important; }")) {
|
||||
const names = r.split("{")[0].match(/[.#][A-Za-z_][\w-]*/g) || [];
|
||||
for (const n of names) assert.ok(!new RegExp(n[0] === "." ? `class="[^"]*\\b${n.slice(1)}\\b` : `id="${n.slice(1)}"`).test(seen),
|
||||
`the added rule "${r}" styles ${n}, which a consumer who adopted nothing sees`);
|
||||
}
|
||||
});
|
||||
|
||||
test("zero-config: the contract answer is additive — one field gained, none lost, version unmoved", async (t) => {
|
||||
|
||||
Reference in new issue
Block a user