Show "shown of total" in control board project headers while rows are hidden (#1503)

A project header now reads "fleet (13 of 38)" while Hide offline or Hide
seen hides at least one row, and "fleet (38)" when nothing is hidden. The
note under the table still says which filter hid how many. Numbers only,
so nothing new needs escaping. Static test pins the expression and the
removal of the raw-length header. Sonnet review: APPROVED, no findings.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-12 09:00:21 -05:00
co-authored by Claude Fable 5.1
parent e90d15dd70
commit bf1391a424
6 changed files with 39 additions and 2 deletions
+3 -1
View File
@@ -98,7 +98,9 @@ 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
project table has "Hide offline" and "Hide seen" checkboxes (both on by
default) with a note saying how many rows each one hides.
default) with a note saying how many rows each one hides. While a box hides
something the project header reads "N of N" (shown of total); otherwise it
shows the plain total.
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.
+2 -1
View File
@@ -295,9 +295,10 @@
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>" : "";
var headCount = visible.length < group.length ? visible.length + " of " + group.length : String(group.length);
return (
'<div class="project-group">' +
'<div class="project-group-head"><h3>' + esc(project) + ' <span>(' + group.length + ")</span></h3>" +
'<div class="project-group-head"><h3>' + esc(project) + ' <span>(' + headCount + ")</span></h3>" +
'<div class="group-toggles">' +
'<label><input type="checkbox" class="hide-offline-toggle" data-project="' + esc(project) + '" ' + (hideOffline ? "checked" : "") + "> Hide offline</label>" +
'<label><input type="checkbox" class="hide-seen-toggle" data-project="' + esc(project) + '" ' + (hideSeen ? "checked" : "") + "> Hide seen</label>" +
@@ -558,3 +558,17 @@ test("page.html: each project has a Hide seen checkbox (default on) beside Hide
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");
});
test("page.html: a project header reads \"N of N\" only while a checkbox hides rows", () => {
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,
/var headCount = visible\.length < group\.length \? visible\.length \+ " of " \+ group\.length : String\(group\.length\)/,
"shown-of-total only when something is hidden; the plain total otherwise",
);
assert.match(body, /<span>\(' \+ headCount \+ "\)<\/span>/, "the header uses headCount, not the raw group length");
assert.doesNotMatch(body, /<span>\(' \+ group\.length/, "the raw group length must no longer be rendered in the header");
});