Show task, active project and workspace per control board row (#1503)

Gate A fix asked by the professor session on Jason's behalf. Each row
now carries three derived fields, shown as "unknown" when the log and
tmux do not hold them:

- task: the session's first user message (pi logs have no task envelope)
- workspace: the live pane path of the pane running pi, else session cwd
- activeProject: basename of the nearest git checkout above the workspace

tmuxInspect replaces the bare liveness call in the CLI and returns
{ alive, workspace }; tmuxIsAlive stays as a wrapper. The grouping column
and seen.json keys are unchanged. Fixture test per field, tmux parse
tests, page test; missing launcher signals are recorded in the plan page.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-12 09:52:45 -05:00
co-authored by Claude Fable 5.1
parent 6ec253de20
commit 4a7e16c3ec
9 changed files with 321 additions and 26 deletions
@@ -572,3 +572,19 @@ test("page.html: a project header reads \"N of N\" only while a checkbox hides r
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");
});
test("page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail", () => {
const html = readFileSync(join(pkgRoot, "src", "page.html"), "utf8");
const m = html.match(/function buildRowPair\(rec, showProject\) \{[\s\S]*?\n \}/);
assert.ok(m, "buildRowPair() must exist in page.html");
const body = m[0];
assert.match(body, /rec\.task\s*\?[\s\S]*esc\(rec\.task\)[\s\S]*:\s*'<span class="unknown">unknown<\/span>'/, "task is escaped, or reads unknown when null");
assert.match(body, /rec\.activeProject\s*\?[\s\S]*esc\(rec\.activeProject\)[\s\S]*unknown/, "active project is escaped, or reads unknown when null");
assert.match(body, /<dt>Task<\/dt>/, "detail lists the task");
assert.match(body, /<dt>Active project<\/dt>/, "detail lists the active project");
assert.match(body, /<dt>Workspace<\/dt><dd>" \+ \(rec\.workspace \? esc\(rec\.workspace\) : "unknown"\)/, "detail lists the workspace, escaped, or unknown");
assert.match(body, /var span = showProject \? 7 : 6/, "the detail row spans the two new columns");
const heads = html.match(/<th scope=\\"col\\">Task<\/th><th scope=\\"col\\">Active project<\/th>/g) || [];
assert.equal(heads.length, 3, "all three tables carry the two new headers");
assert.match(html, /<td colspan="6" class="empty">No agents\.<\/td>/, "the empty project row spans every column");
});