diff --git a/BUILD-LOG.md b/BUILD-LOG.md index 8c6e319d..93bc8e8e 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -1987,3 +1987,15 @@ quoted an incomplete tmux command) is fixed. Receipt: Still open by design: "waiting" cannot tell a question from a completion report; a wording hint or an agent-side "Waiting on:" convention are the next candidates if the Seen button is not enough. Next: Jason keeps using it. + +## 2026-09-12 — Control board step 3, second refinement (#1503) + +Before: after marking two rows Seen, Jason could not find them again; they +were only visible as a small tag inside their project tables. + +After: a collapsed "Seen (N)" section sits between "Waiting on you" and "By +project", listing every marked row with Unsee. Open/closed state survives +the refresh because only the section body is re-rendered. Page-only change; +one static test added (control-board 64/64, registry 69/69). Independent +review APPROVED with no findings. Verified in Chrome against the two marks +Jason had made (resume, filbert). Next: Jason keeps using it. diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index 1fb1a76f..ee50ba86 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -215,3 +215,4 @@ are never rewritten or removed; corrections are new entries. - 2026-09-12 — dewey (Claude Code; takeover of pi session 01a08111) — WUI Phase 2 D01-D04 done, D05 delivered: five dashboard mockups (Rail, Deck, Console, Board, Stream) plus comparison at agents/dewey/work/wui/mockups/index.html; verifier 31 checks, 0 failed, 2 not verified (forced colours, screen reader); records in VERIFY.md, README.md, TASKS.md, HISTORY.md, DECISIONS.md. Stopped at Jason's design-selection gate. No v1 inspection, backend, credentials, commit or push. - 2026-09-12 — jarvis (Claude Code, coordinator) — Control board MVP step 2 (#1503, D-001): `packages/control-board` gains `serve` (loopback-only local server, `/api/board` re-runs the scanner) and a single-file page (waiting-on-you first, per-project groups, hide-offline, expandable rows, 10s auto-refresh with pause). Suites: control-board 33/33, registry 69/69. Static review (sonnet) found no defects; live browser check by coordinator. Receipt docs/plans/reviews/2026-09-12_control-board-step2-review.md. CURRENT next action: step 3, daily use by Jason. - 2026-09-12 — jarvis (Claude Code, coordinator) — Control board step 3, first refinement (#1503): Jason reported killed pi sessions still "waiting" and most waiting rows being completion reports. Liveness now requires a tmux pane running pi; "Seen" marks (seen.json, POST /api/seen, Seen/Unsee on the page) drop read rows out of "Waiting on you" until the agent writes again. Tests 63/63 (30 new by sonnet helper), registry 69/69; sonnet review APPROVED (README command fixed). Receipt docs/plans/reviews/2026-09-12_control-board-step3-seen-marks.md. +- 2026-09-12 — jarvis (Claude Code, coordinator) — Control board step 3, second refinement (#1503): Jason asked for a way to recall Seen rows; added a collapsed "Seen (N)" section with Unsee per row. Page-only change plus one static test (64/64); sonnet review APPROVED; live check in Chrome against Jason's two real marks. diff --git a/docs/plans/2026-09-12_control-board-mvp.md b/docs/plans/2026-09-12_control-board-mvp.md index 4e87766a..45a03cc0 100644 --- a/docs/plans/2026-09-12_control-board-mvp.md +++ b/docs/plans/2026-09-12_control-board-mvp.md @@ -133,3 +133,9 @@ node --test packages/control-board/tests/ - No changes to `packages/mosaic`. - This plan does not authorize push or merge beyond whatever the existing refactor-branch plan already allows. + +**2026-09-12 — a place to find what you marked Seen.** After using the +button, Jason noted the board had no way to show seen rows again. The page +now has a collapsed "Seen (N)" section between "Waiting on you" and "By +project" that lists every marked row with an Unsee button. It stays open or +closed across refreshes. diff --git a/packages/control-board/README.md b/packages/control-board/README.md index 4bca6a02..7141d562 100644 --- a/packages/control-board/README.md +++ b/packages/control-board/README.md @@ -95,6 +95,8 @@ only when Jason clicks "Seen" or "Unsee" on the page (via `POST the agent's newest message still has that exact `lastActivity` timestamp — as soon as the agent writes anything new, `lastActivity` changes, the mark no longer matches, and the row falls back into "Waiting on you" on its own. +Marked rows are listed under a collapsed "Seen (N)" section on the page, +each with an "Unsee" button, so nothing marked is ever out of reach. If `seen.json` exists but is not valid JSON (or not an object of string values), the scan refuses rather than silently dropping every mark. diff --git a/packages/control-board/src/page.html b/packages/control-board/src/page.html index ef856ba5..867665ad 100644 --- a/packages/control-board/src/page.html +++ b/packages/control-board/src/page.html @@ -35,6 +35,11 @@ .error-banner{margin:12px 0;padding:10px 14px;border-radius:var(--radius);border:1px solid var(--danger); background:color-mix(in srgb,var(--danger) 10%,var(--surface));color:var(--danger);font-size:.9rem} .empty{color:var(--muted);font-style:italic} + #seenDetails summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:8px} + #seenDetails summary::-webkit-details-marker{display:none} + #seenDetails summary::before{content:"\25B8";color:var(--muted);font-size:.9em} + #seenDetails[open] summary::before{content:"\25BE"} + #seenDetails summary h2{display:inline;margin:0} .table-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--radius-lg);background:var(--surface)} table{width:100%;border-collapse:collapse;font-size:.88rem} th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:top} @@ -84,6 +89,12 @@

Waiting on you

+
+
+

Seen

+
+
+

By project

@@ -102,6 +113,8 @@ var pauseBtn = document.getElementById("pauseBtn"); var errorBanner = document.getElementById("errorBanner"); var waitingBody = document.getElementById("waitingBody"); + var seenBody = document.getElementById("seenBody"); + var seenHeading = document.getElementById("seen-h"); var projectsBody = document.getElementById("projectsBody"); var footer = document.getElementById("footer"); var main = document.getElementById("main"); @@ -204,6 +217,27 @@ return main + detail; } + // Rows you marked Seen, in one place, so they can be found and put back. + // Collapsed by default; the open/closed choice survives a refresh. + function renderSeen(data) { + var list = (data.sessions || []).filter(function (r) { return r.seen; }); + list.sort(function (a, b) { + var aa = a.ageSeconds === null || a.ageSeconds === undefined ? Infinity : a.ageSeconds; + var bb = b.ageSeconds === null || b.ageSeconds === undefined ? Infinity : b.ageSeconds; + return aa - bb; + }); + seenHeading.textContent = "Seen (" + list.length + ")"; + if (list.length === 0) { + seenBody.innerHTML = '

Nothing is marked seen. Rows you mark come back here, and return to "Waiting on you" on their own when the agent writes again.

'; + return; + } + var rows = list.map(function (r) { return buildRowPair(r, true); }).join(""); + seenBody.innerHTML = + '
' + + "" + + "" + rows + "
ProjectAgentStateAgeLast message
"; + } + function renderWaiting(data) { var list = (data.sessions || []).filter(function (r) { return r.waitingOnYou; }); list.sort(function (a, b) { @@ -273,6 +307,7 @@ function renderAll() { rowIdx = 0; renderWaiting(lastData); + renderSeen(lastData); renderProjects(lastData); renderFooter(lastData); } diff --git a/packages/control-board/tests/serve.test.mjs b/packages/control-board/tests/serve.test.mjs index a60af13b..ad70ab7f 100644 --- a/packages/control-board/tests/serve.test.mjs +++ b/packages/control-board/tests/serve.test.mjs @@ -535,3 +535,14 @@ test("page.html: seenControl() escapes rec.project/agent/lastActivity, and the P assert.match(body, /esc\(rec\.lastActivity\)/); assert.match(html, /headers:\s*\{\s*"content-type":\s*"application\/json"\s*\}/, "the seen POST must send content-type: application/json"); }); + +test("page.html: has a collapsed Seen section that lists seen rows with the shared row builder", () => { + const html = readFileSync(join(pkgRoot, "src", "page.html"), "utf8"); + assert.match(html, /
(?![^>]*\sopen)/, "the Seen section must start collapsed"); + assert.match(html, /

Seen<\/h2>/); + const m = html.match(/function renderSeen\(data\) \{[\s\S]*?\n \}/); + assert.ok(m, "renderSeen() must exist in page.html"); + assert.match(m[0], /filter\(function \(r\) \{ return r\.seen; \}\)/, "the Seen section lists exactly the rows the API marks seen"); + assert.match(m[0], /buildRowPair\(r, true\)/, "seen rows reuse the escaped row builder, project column included"); + assert.match(html, /renderWaiting\(lastData\);\n\s*renderSeen\(lastData\);/, "renderAll must render the Seen section on every refresh"); +});