fix(lanes): render undeclared lanes at their detected stage, quiet tmux polling noise

A lane whose sessions never call `ccam stage` had no declaration at all, so
its pipeline map stayed 100% pending even though detection was tracking real
progress. inferredDisplayStage renders such a lane AT its last detection
instead (display only — the DB's declared `stage` and detection-as-evidence
rules are untouched). Also stops tmux's own "no session" stderr noise from
spamming the journal on every poll.
This commit is contained in:
2026-09-03 11:28:06 +07:00
parent b27e679eac
commit 8711917d44
4 changed files with 78 additions and 11 deletions
+30 -6
View File
@@ -589,12 +589,12 @@ describe("stage detection", () => {
!payload.pipeline_nodes.some((n) => n.state === "done"),
"inference must never render a node done — only a declared stage carrying evidence may"
);
// lane.stage is still the default "idle", which is not itself a pipeline
// node/alias, so nodeStates() (unaware of detection) reports "pending" for
// every node here — detection only ever adds the `detected` flag alongside
// whatever nodeStates() already computed from the declared stage.
// lane.stage is still the default "idle" and `stages` is empty, so the lane
// has no declaration of its own to render and is displayed AT its detected
// node instead (inferredDisplayStage) — `current`, and flagged `detected`
// so it paints amber rather than as a declaration.
const review = payload.pipeline_nodes.find((n) => n.id === "review");
assert.equal(review.state, "pending");
assert.equal(review.state, "current");
assert.equal(review.detected, true);
const implement = payload.pipeline_nodes.find((n) => n.id === "implement");
assert.equal(implement.detected, true);
@@ -948,9 +948,10 @@ describe("stage detection", () => {
}
});
it("a stale detection stops rendering as `detected` in pipeline_nodes, even though the DB row still holds it", () => {
it("a stale detection stops rendering as `detected` on a lane that HAS declared a stage", () => {
const { db } = require("../db");
const l = lanes.createLane({ cwd: "/tmp/wt-detect-stale-render" });
lanes.setStage(l.id, { stage: "plan" });
lanes.recordDetection(l.id, { nodeId: "tests" });
const oldTime = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
db.prepare("UPDATE lanes SET detected_at = ? WHERE id = ?").run(oldTime, l.id);
@@ -965,6 +966,29 @@ describe("stage detection", () => {
!nodes.some((n) => n.detected),
"a detection older than the TTL must not paint any node amber — it is no longer current information"
);
// The declaration is what the lane still renders from.
assert.equal(nodes.find((n) => n.id === "plan").state, "current");
lanes.deleteLane(l.id);
});
it("a lane that never declared a stage keeps rendering its LAST detection, TTL or not", () => {
const { db } = require("../db");
const l = lanes.createLane({ cwd: "/tmp/wt-detect-undeclared-render" });
lanes.recordDetection(l.id, { nodeId: "tests" });
const oldTime = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
db.prepare("UPDATE lanes SET detected_at = ? WHERE id = ?").run(oldTime, l.id);
// With nothing declared, the last detection IS the lane's last known
// progress — blanking it left every non-CCAM repo's card 100% pending
// forever. Still amber (`detected`), still never `done`.
const payload = lanes.lanePayload(lanes.getLane(l.id));
const tests = payload.pipeline_nodes.find((n) => n.id === "tests");
assert.equal(tests.state, "current");
assert.equal(tests.detected, true);
assert.ok(!payload.pipeline_nodes.some((n) => n.state === "done"));
assert.ok(payload.progress > 0, "an inferred stage advances the progress bar");
// The declared stage the DB holds is reported untouched.
assert.equal(payload.stage, "idle");
lanes.deleteLane(l.id);
});
});