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:
7
.changeset/board-header-active-chrome-count.md
Normal file
7
.changeset/board-header-active-chrome-count.md
Normal 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.
|
||||||
@@ -11,6 +11,8 @@ import { WorktreeGroup } from "./WorktreeGroup";
|
|||||||
import { QuickEntryBox } from "./QuickEntryBox";
|
import { QuickEntryBox } from "./QuickEntryBox";
|
||||||
import { PluginSlot } from "./PluginSlot";
|
import { PluginSlot } from "./PluginSlot";
|
||||||
import { groupByWorktree } from "../utils/worktreeGrouping";
|
import { groupByWorktree } from "../utils/worktreeGrouping";
|
||||||
|
import { isTaskAgentActive } from "../utils/taskActivity";
|
||||||
|
import { isTaskStuck } from "../utils/taskStuck";
|
||||||
import type { ToastType } from "../hooks/useToast";
|
import type { ToastType } from "../hooks/useToast";
|
||||||
import type { TaskContextMenuColumnMetadata } from "./TaskContextMenu";
|
import type { TaskContextMenuColumnMetadata } from "./TaskContextMenu";
|
||||||
import { ChevronDown, ChevronUp, MoreVertical } from "lucide-react";
|
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:
|
FNXC:BoardColumnCount 2026-07-21-19:30:
|
||||||
Column header is executing/total (e.g. 3/4). Executing uses the same Running predicate as the
|
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.
|
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(
|
const activeTaskCount = useMemo(
|
||||||
() => tasks.filter((task) => isRunningAgentTask(enrichRunningAgentTaskShapeFromFlags(task, columnFlags))).length,
|
() => tasks.filter((task) =>
|
||||||
[tasks, columnFlags],
|
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
|
// When search is active, skip pagination so all matching tasks are visible
|
||||||
const shouldPaginate = !isArchived && !isSearchActive && !showWorktreeGroups && tasks.length > PAGINATED_COLUMN_THRESHOLD;
|
const shouldPaginate = !isArchived && !isSearchActive && !showWorktreeGroups && tasks.length > PAGINATED_COLUMN_THRESHOLD;
|
||||||
|
|||||||
@@ -179,6 +179,44 @@ describe("Column count-flash", () => {
|
|||||||
|
|
||||||
expect(screen.getByLabelText("1 executing of 4")).toHaveTextContent("1/4");
|
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");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Reference in New Issue
Block a user