Add a per-project "Hide seen" checkbox to the control board page (#1503)
Each project table now has "Hide seen" beside "Hide offline", both on by default, with a note saying how many rows each one hides. The choice survives the 10-second refresh. Static test pins the markup, the filter, the persistence guard, and the change handler. Sonnet review: APPROVED. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
@@ -1999,3 +1999,16 @@ 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.
|
||||
|
||||
## 2026-09-12 — Control board step 3, third refinement (#1503)
|
||||
|
||||
Before: seen rows still sat in the project tables with a small tag, which
|
||||
was noise now that the Seen section lists them.
|
||||
|
||||
After: each project header has "Hide offline" and "Hide seen" checkboxes,
|
||||
both on by default, and the note under the table reads
|
||||
"N offline hidden · N seen hidden". The choice survives refreshes. Page-only
|
||||
change, one static test (control-board 65/65, registry 69/69). Sonnet
|
||||
review: APPROVED, one FYI (the default-on test would still pass without the
|
||||
persistence guard), fixed by tightening the regex. Verified in Chrome against
|
||||
Jason's real marks. Next: Jason keeps using it.
|
||||
|
||||
@@ -216,3 +216,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
- 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.
|
||||
- 2026-09-12 — jarvis (Claude Code, coordinator) — Control board step 3, third refinement (#1503): Jason suggested a per-project "Hide seen" checkbox beside "Hide offline"; added, on by default, with a combined hidden-count note. Page-only change plus one static test (65/65); sonnet review APPROVED (one test-rigor FYI fixed); live check in Chrome.
|
||||
|
||||
@@ -139,3 +139,8 @@ 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.
|
||||
|
||||
**2026-09-12 — "Hide seen" per project.** Jason suggested a checkbox like
|
||||
"Hide offline" so seen rows stop cluttering the fleet table now that the
|
||||
Seen section exists. Each project header has both boxes, on by default,
|
||||
and the note under the table reads "N offline hidden · N seen hidden".
|
||||
|
||||
@@ -96,7 +96,9 @@ 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.
|
||||
each with an "Unsee" button, so nothing marked is ever out of reach. Each
|
||||
project table has "Hide offline" and "Hide seen" checkboxes (both on by
|
||||
default) with a note saying how many rows each one hides.
|
||||
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.
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
.detail-list dd{margin:0;overflow-wrap:anywhere}
|
||||
.project-group{margin-bottom:20px}
|
||||
.project-group-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px}
|
||||
.group-toggles{display:flex;flex-wrap:wrap;gap:14px}
|
||||
.project-group-head label{font-size:.85rem;color:var(--muted);display:flex;align-items:center;gap:6px}
|
||||
.offline-note{margin:6px 0 0;font-size:.82rem;color:var(--muted)}
|
||||
.page-footer{margin-top:24px;padding-top:12px;border-top:1px solid var(--line);color:var(--muted);font-size:.85rem}
|
||||
@@ -125,6 +126,7 @@
|
||||
var timerId = null;
|
||||
var secondsLeft = REFRESH_MS / 1000;
|
||||
var hideOfflineState = {};
|
||||
var hideSeenState = {};
|
||||
// Open detail panels survive a refresh. Keyed by section, project and agent
|
||||
// because the same agent can appear in both the waiting list and its group.
|
||||
var openDetails = {};
|
||||
@@ -279,16 +281,27 @@
|
||||
projectsBody.innerHTML = projects.map(function (project) {
|
||||
var group = byProject[project].slice().sort(function (a, b) { return a.agent.localeCompare(b.agent); });
|
||||
if (!(project in hideOfflineState)) hideOfflineState[project] = true;
|
||||
if (!(project in hideSeenState)) hideSeenState[project] = true;
|
||||
var hideOffline = hideOfflineState[project];
|
||||
var visible = group.filter(function (r) { return !(hideOffline && r.state === "offline"); });
|
||||
var hiddenCount = group.length - visible.length;
|
||||
var hideSeen = hideSeenState[project];
|
||||
var offlineHidden = 0, seenHidden = 0;
|
||||
var visible = group.filter(function (r) {
|
||||
if (hideOffline && r.state === "offline") { offlineHidden += 1; return false; }
|
||||
if (hideSeen && r.seen) { seenHidden += 1; return false; }
|
||||
return true;
|
||||
});
|
||||
var rows = visible.map(function (r) { return buildRowPair(r, false); }).join("");
|
||||
var note = hiddenCount > 0 ? '<p class="offline-note">' + hiddenCount + " offline hidden</p>" : "";
|
||||
var noteParts = [];
|
||||
if (offlineHidden > 0) noteParts.push(offlineHidden + " offline hidden");
|
||||
if (seenHidden > 0) noteParts.push(seenHidden + " seen hidden");
|
||||
var note = noteParts.length > 0 ? '<p class="offline-note">' + noteParts.join(" \u00b7 ") + "</p>" : "";
|
||||
return (
|
||||
'<div class="project-group">' +
|
||||
'<div class="project-group-head"><h3>' + esc(project) + ' <span>(' + group.length + ")</span></h3>" +
|
||||
'<div class="group-toggles">' +
|
||||
'<label><input type="checkbox" class="hide-offline-toggle" data-project="' + esc(project) + '" ' + (hideOffline ? "checked" : "") + "> Hide offline</label>" +
|
||||
"</div>" +
|
||||
'<label><input type="checkbox" class="hide-seen-toggle" data-project="' + esc(project) + '" ' + (hideSeen ? "checked" : "") + "> Hide seen</label>" +
|
||||
"</div></div>" +
|
||||
'<div class="table-wrap"><table><thead><tr>' +
|
||||
"<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 || '<tr><td colspan="4" class="empty">No agents.</td></tr>') + "</tbody></table></div>" +
|
||||
@@ -297,6 +310,7 @@
|
||||
}).join("");
|
||||
}
|
||||
|
||||
|
||||
function renderFooter(data) {
|
||||
var counts = data.counts || {};
|
||||
var parts = STATES.map(function (s) { return cap(s) + " " + (counts[s] || 0); }).join(" · ");
|
||||
@@ -420,9 +434,10 @@
|
||||
});
|
||||
|
||||
main.addEventListener("change", function (e) {
|
||||
var cb = e.target.closest(".hide-offline-toggle");
|
||||
var cb = e.target.closest(".hide-offline-toggle, .hide-seen-toggle");
|
||||
if (!cb) return;
|
||||
hideOfflineState[cb.dataset.project] = cb.checked;
|
||||
var stateMap = cb.classList.contains("hide-seen-toggle") ? hideSeenState : hideOfflineState;
|
||||
stateMap[cb.dataset.project] = cb.checked;
|
||||
renderProjects(lastData);
|
||||
});
|
||||
|
||||
|
||||
@@ -546,3 +546,15 @@ test("page.html: has a collapsed Seen section that lists seen rows with the shar
|
||||
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");
|
||||
});
|
||||
|
||||
test("page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note", () => {
|
||||
const html = readFileSync(join(pkgRoot, "src", "page.html"), "utf8");
|
||||
const m = html.match(/function renderProjects\(data\) \{[\s\S]*?\n \}/);
|
||||
assert.ok(m, "renderProjects() must exist in page.html");
|
||||
const body = m[0];
|
||||
assert.match(body, /if \(!\(project in hideSeenState\)\) hideSeenState\[project\] = true/, "Hide seen defaults to on and the choice survives re-render");
|
||||
assert.match(body, /class="hide-seen-toggle" data-project="' \+ esc\(project\)/, "the checkbox carries the escaped project name");
|
||||
assert.match(body, /hideSeen && r\.seen/, "seen rows are filtered when the box is ticked");
|
||||
assert.match(body, /" seen hidden"/, "the note reports how many seen rows are hidden");
|
||||
assert.match(html, /closest\("\.hide-offline-toggle, \.hide-seen-toggle"\)/, "one change handler serves both checkboxes");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user