Add a collapsed "Seen" section to the control board page (#1503)
Jason asked for a way to recall rows he marked Seen. The page now lists them under a collapsed "Seen (N)" section between "Waiting on you" and "By project", each with Unsee. Open/closed state survives the 10-second refresh because only the section body is re-rendered. Page-only change plus one static test. Tests: control-board 64/64, registry 69/69. Review APPROVED, no findings. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 @@
|
||||
<h2 id="waiting-h">Waiting on you</h2>
|
||||
<div id="waitingBody"></div>
|
||||
</section>
|
||||
<section aria-labelledby="seen-h">
|
||||
<details id="seenDetails">
|
||||
<summary><h2 id="seen-h">Seen</h2></summary>
|
||||
<div id="seenBody"></div>
|
||||
</details>
|
||||
</section>
|
||||
<section aria-labelledby="projects-h">
|
||||
<h2 id="projects-h">By project</h2>
|
||||
<div id="projectsBody"></div>
|
||||
@@ -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 = '<p class="empty">Nothing is marked seen. Rows you mark come back here, and return to "Waiting on you" on their own when the agent writes again.</p>';
|
||||
return;
|
||||
}
|
||||
var rows = list.map(function (r) { return buildRowPair(r, true); }).join("");
|
||||
seenBody.innerHTML =
|
||||
'<div class="table-wrap"><table><thead><tr>' +
|
||||
"<th scope=\"col\">Project</th><th scope=\"col\">Agent</th><th scope=\"col\">State</th><th scope=\"col\">Age</th><th scope=\"col\">Last message</th>" +
|
||||
"</tr></thead><tbody>" + rows + "</tbody></table></div>";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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, /<details id="seenDetails">(?![^>]*\sopen)/, "the Seen section must start collapsed");
|
||||
assert.match(html, /<h2 id="seen-h">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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user