diff --git a/.changeset/board-header-active-chrome-count.md b/.changeset/board-header-active-chrome-count.md new file mode 100644 index 0000000000..f446373b13 --- /dev/null +++ b/.changeset/board-header-active-chrome-count.md @@ -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. diff --git a/packages/dashboard/app/components/Column.tsx b/packages/dashboard/app/components/Column.tsx index 46685c2225..ab577471af 100644 --- a/packages/dashboard/app/components/Column.tsx +++ b/packages/dashboard/app/components/Column.tsx @@ -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; diff --git a/packages/dashboard/app/components/__tests__/Column.test.tsx b/packages/dashboard/app/components/__tests__/Column.test.tsx index 47e1ce47a4..e0220e200f 100644 --- a/packages/dashboard/app/components/__tests__/Column.test.tsx +++ b/packages/dashboard/app/components/__tests__/Column.test.tsx @@ -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( + , + ); + 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( + , + ); + expect(screen.getByLabelText("1 executing of 2")).toHaveTextContent("1/2"); + }); }); /*