fix(dashboard): count visibly active cards (REVISING replans) in column header processing count

A Todo card parked in the durable needs-replan stage keeps its REVISING badge
and activity chrome (FN-8494), but the header only counted live agents, so it
read 0/2 under a glowing card. Union the shared Running predicate with the
card's own activity predicate (same globalPaused/stuck gates) so the header
equals the number of visibly active cards. Footer Running and admission keep
live-agent-only truth: a parked replan must not consume concurrency capacity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-22 18:01:34 -07:00
parent 295226ea64
commit 3f7c6e4848
3 changed files with 61 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Board column headers now count REVISING (replan) cards and other visibly active cards in the processing count.
category: fix
dev: Column header count = shared Running predicate ∪ card activity-chrome predicate (isTaskAgentActive); footer/admission keep live-agent-only semantics.

View File

@@ -11,6 +11,8 @@ import { WorktreeGroup } from "./WorktreeGroup";
import { QuickEntryBox } from "./QuickEntryBox";
import { PluginSlot } from "./PluginSlot";
import { groupByWorktree } from "../utils/worktreeGrouping";
import { isTaskAgentActive } from "../utils/taskActivity";
import { isTaskStuck } from "../utils/taskStuck";
import type { ToastType } from "../hooks/useToast";
import type { TaskContextMenuColumnMetadata } from "./TaskContextMenu";
import { ChevronDown, ChevronUp, MoreVertical } from "lucide-react";
@@ -291,10 +293,22 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, showWorktree
FNXC:BoardColumnCount 2026-07-21-19:30:
Column header is executing/total (e.g. 3/4). Executing uses the same Running predicate as the
footer (unpaused WIP, live planners, active review). Total is the card count in this lane.
FNXC:BoardColumnCount 2026-07-22-06:10:
The header must agree with the cards below it: a Todo card parked in the durable
`needs-replan` stage keeps its REVISING badge and activity chrome (FN-8494), so a header
that only counts live agents read 0/2 under a glowing card. Union the shared Running
predicate with the card's own activity-chrome predicate (isTaskAgentActive, same
globalPaused/stuck gates the card applies) so the count equals the number of visibly
active cards. Footer Running and admission intentionally keep the live-agent-only truth —
a parked replan must not consume top-level concurrency capacity.
*/
const activeTaskCount = useMemo(
() => tasks.filter((task) => isRunningAgentTask(enrichRunningAgentTaskShapeFromFlags(task, columnFlags))).length,
[tasks, columnFlags],
() => tasks.filter((task) =>
isRunningAgentTask(enrichRunningAgentTaskShapeFromFlags(task, columnFlags))
|| isTaskAgentActive(task, { globalPaused, isStuck: isTaskStuck(task, taskStuckTimeoutMs, lastFetchTimeMs) }),
).length,
[tasks, columnFlags, globalPaused, taskStuckTimeoutMs, lastFetchTimeMs],
);
// When search is active, skip pagination so all matching tasks are visible
const shouldPaginate = !isArchived && !isSearchActive && !showWorktreeGroups && tasks.length > PAGINATED_COLUMN_THRESHOLD;

View File

@@ -179,6 +179,44 @@ describe("Column count-flash", () => {
expect(screen.getByLabelText("1 executing of 4")).toHaveTextContent("1/4");
});
it("counts cards with active chrome — a REVISING (needs-replan) todo card and a live code-review gate", () => {
// Header must equal the number of visibly active cards (FN-8494 keeps REVISING chrome on
// parked replans; a live gate session runs with null status and a pending step lease).
const tasks = [
{ ...makeTask("FN-001"), column: "todo" as ColumnType, status: "needs-replan" as any },
{ ...makeTask("FN-002"), column: "todo" as ColumnType },
];
render(
<Column
{...defaultProps}
column={"todo" as ColumnType}
columnFlags={{ hold: true }}
tasks={tasks}
/>,
);
expect(screen.getByLabelText("1 executing of 2")).toHaveTextContent("1/2");
});
it("counts an in-review card whose code-review gate holds a pending step lease", () => {
const tasks = [
{
...makeTask("FN-001"),
column: "in-review" as ColumnType,
workflowStepResults: [{ workflowStepId: "code-review", workflowStepName: "Code Review", status: "pending" as const, startedAt: new Date().toISOString() }],
},
{ ...makeTask("FN-002"), column: "in-review" as ColumnType },
];
render(
<Column
{...defaultProps}
column={"in-review" as ColumnType}
columnFlags={{ mergeBlocker: true }}
tasks={tasks}
/>,
);
expect(screen.getByLabelText("1 executing of 2")).toHaveTextContent("1/2");
});
});
/*