fix(dashboard): restore accurate executing/total column headers
Show live agents over card count (e.g. 3/4) using the shared Running predicate so unpaused WIP counts without sessionFile, and document the badge semantics for operators.
This commit is contained in:
@@ -4,4 +4,4 @@
|
||||
|
||||
summary: Unify max concurrency across planning/execution/review and simplify board capacity indicators.
|
||||
category: feature
|
||||
dev: maxConcurrent caps all top-level working agents per project; maxTriageConcurrent removed from UI (Settings, Command Center, Engine Control) and admission; free slots admit oldest createdAt via per-project atomic admission coordinator across lanes; footer Waiting/Running/Blocked; column headers remain card totals; Running counts unpaused WIP membership (sessionFile is not a DB/board field — do not require it); nested runNested helpers remain parent-internal soft-breach by design.
|
||||
dev: maxConcurrent caps all top-level working agents per project; maxTriageConcurrent removed from UI (Settings, Command Center, Engine Control) and admission; free slots admit oldest createdAt via per-project atomic admission coordinator across lanes; footer Waiting/Running/Blocked; column headers show executing/total via shared Running predicate; Running counts unpaused WIP membership (sessionFile is not a DB/board field — do not require it); nested runNested helpers remain parent-internal soft-breach by design.
|
||||
|
||||
@@ -1258,7 +1258,7 @@ Features:
|
||||
<!-- FNXC:CommandCenter 2026-06-27-10:03: Tokens detail charts must show every model bucket returned by analytics for accurate spend attribution; Overview remains a compact top-model summary because its copy explicitly frames those cards as top consumers/share. -->
|
||||
<!-- FNXC:CommandCenterActivity 2026-06-30-00:00: Activity active-agent counts include both durable-agent usage events and ephemeral task-worker execution runs from agentRuns, because task execution can be visible without a matching usage_events row. -->
|
||||
<!-- FNXC:CommandCenterActivity 2026-07-01-00:00: Graph-owned workflow step sessions publish active-to-terminal agentRuns lifecycle rows with task lineage and step metadata, so daily activity and Activity throughput charts include new workflow execution without dashboard-side recounting. -->
|
||||
- **Overview controls dashboard** includes AI engine stop/start backed by `globalPause`, the shared Global Max Concurrent slider, and current-project **Max concurrency** plus **Max worktrees** controls. Max concurrency caps top-level working agents across planning, execution, and review/merge; free capacity is admitted oldest-first within the project. The footer reports **Waiting**, **Running (N/max)**, and **Blocked**; column headers report the card count in each lane. Nested helper agents remain parent-internal and may temporarily exceed the displayed top-level count.
|
||||
- **Overview controls dashboard** includes AI engine stop/start backed by `globalPause`, the shared Global Max Concurrent slider, and current-project **Max concurrency** plus **Max worktrees** controls. Max concurrency caps top-level working agents across planning, execution, and review/merge; free capacity is admitted oldest-first within the project. The footer reports **Waiting**, **Running (N/max)**, and **Blocked**; column headers report executing/total (live agents in the lane over card count). Nested helper agents remain parent-internal and may temporarily exceed the displayed top-level count.
|
||||
<!-- FNXC:TeamArea 2026-07-18-12:30: FN-8351 moves organization export and import to the Team tab so team-level portability controls are not presented as Overview dashboard controls. -->
|
||||
- **Team tab — Org export / import** lets an operator download a portable organization JSON bundle or paste one for a dry-run preview before confirming the apply step. Exports are secret-scrubbed by default: credentials and tokens are never included, while safe secret references can remain for setup in the destination project.
|
||||
- **Configuration versions** lives in **Settings → Project → Configuration Versions**. It lists recorded project-setting revisions newest first; select **Roll back** on any revision and confirm once to restore it. The restore is recorded as a new forward revision, so it can itself be undone without manually reconstructing settings.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useFlashOnIncrease } from "../hooks/useFlashOnIncrease";
|
||||
import { useConfirm } from "../hooks/useConfirm";
|
||||
import type { Task, TaskDetail, Column as ColumnType, ColumnId, TaskCreateInput, GithubIssueAction, MergeResult } from "@fusion/core";
|
||||
import { COLUMN_LABELS, COLUMN_DESCRIPTIONS, getErrorMessage } from "@fusion/core";
|
||||
import { enrichRunningAgentTaskShapeFromFlags, isRunningAgentTask } from "../../../core/src/live-agent-count";
|
||||
import { isNearDuplicateCanonicalInactive } from "../../../core/src/near-duplicate-canonical";
|
||||
import { TaskCard } from "./TaskCard";
|
||||
import { WorktreeGroup } from "./WorktreeGroup";
|
||||
@@ -287,10 +288,14 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, showWorktree
|
||||
*/
|
||||
const showWorktreeGroups = showWorktreeGrouping === true && isWipProcessingColumn;
|
||||
/*
|
||||
FNXC:BoardColumnCount 2026-07-21-18:47:
|
||||
Column headers show the card count in that lane, not live-agent active/total.
|
||||
FN-8453 briefly rendered isRunningAgentTask counts as N/total, which read as broken for Todo (always 0/N waiting) and often for In Progress when board slim rows lack durable session/checkout liveness. Capacity Running/Waiting stays on the footer status bar.
|
||||
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.
|
||||
*/
|
||||
const activeTaskCount = useMemo(
|
||||
() => tasks.filter((task) => isRunningAgentTask(enrichRunningAgentTaskShapeFromFlags(task, columnFlags))).length,
|
||||
[tasks, columnFlags],
|
||||
);
|
||||
// When search is active, skip pagination so all matching tasks are visible
|
||||
const shouldPaginate = !isArchived && !isSearchActive && !showWorktreeGroups && tasks.length > PAGINATED_COLUMN_THRESHOLD;
|
||||
|
||||
@@ -661,7 +666,12 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, showWorktree
|
||||
<div className="column-header">
|
||||
<div className={`column-dot dot-${column}`} />
|
||||
<h2>{workflowMode ? (columnDisplayName ?? COLUMN_LABELS[column] ?? column) : COLUMN_LABELS[column]}</h2>
|
||||
<span className={`column-count${countFlashing ? " count-flash" : ""}`}>{tasks.length}</span>
|
||||
<span
|
||||
className={`column-count${countFlashing ? " count-flash" : ""}`}
|
||||
aria-label={t("column.executingOfTotal", "{{active}} executing of {{total}}", { active: activeTaskCount, total: tasks.length })}
|
||||
>
|
||||
<span>{activeTaskCount}</span>/<span>{tasks.length}</span>
|
||||
</span>
|
||||
{(workflowMode ? isReviewColumn : column === "in-review") && onToggleAutoMerge && (
|
||||
<label className="auto-merge-toggle" title={autoMerge ? t("column.autoMergeEnabled", "Auto-merge enabled") : t("column.autoMergeDisabled", "Auto-merge disabled")}>
|
||||
{/*
|
||||
|
||||
@@ -109,9 +109,11 @@ describe("Column count-flash", () => {
|
||||
const tasks = [makeTask("FN-001")];
|
||||
render(<Column {...defaultProps} tasks={tasks} />);
|
||||
|
||||
const badge = screen.getByText("1");
|
||||
// Badge is active/total (0/1 for triage without a live planner).
|
||||
const badge = screen.getByText("1").parentElement!;
|
||||
expect(badge.className).toContain("column-count");
|
||||
expect(badge.className).not.toContain("count-flash");
|
||||
expect(badge).toHaveTextContent("0/1");
|
||||
});
|
||||
|
||||
it("applies count-flash class when task count increases", () => {
|
||||
@@ -121,8 +123,9 @@ describe("Column count-flash", () => {
|
||||
const moreTasks = [makeTask("FN-001"), makeTask("FN-002")];
|
||||
rerender(<Column {...defaultProps} tasks={moreTasks} />);
|
||||
|
||||
const badge = screen.getByText("2");
|
||||
const badge = screen.getByText("2").parentElement!;
|
||||
expect(badge.className).toContain("count-flash");
|
||||
expect(badge).toHaveTextContent("0/2");
|
||||
});
|
||||
|
||||
it("does not apply count-flash class when task count decreases", () => {
|
||||
@@ -132,9 +135,47 @@ describe("Column count-flash", () => {
|
||||
const fewerTasks = [makeTask("FN-001")];
|
||||
rerender(<Column {...defaultProps} tasks={fewerTasks} />);
|
||||
|
||||
const badge = screen.getByText("1");
|
||||
const badge = screen.getByText("1").parentElement!;
|
||||
expect(badge.className).not.toContain("count-flash");
|
||||
});
|
||||
|
||||
it("shows accurate executing/total for WIP (unpaused active over card total)", () => {
|
||||
const tasks = [
|
||||
{ ...makeTask("FN-001"), column: "in-progress" as ColumnType },
|
||||
{ ...makeTask("FN-002"), column: "in-progress" as ColumnType },
|
||||
{ ...makeTask("FN-003"), column: "in-progress" as ColumnType, paused: true },
|
||||
{ ...makeTask("FN-004"), column: "in-progress" as ColumnType, userPaused: true },
|
||||
];
|
||||
render(
|
||||
<Column
|
||||
{...defaultProps}
|
||||
column={"in-progress" as ColumnType}
|
||||
columnFlags={{ countsTowardWip: true }}
|
||||
tasks={tasks}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("2 executing of 4")).toHaveTextContent("2/4");
|
||||
});
|
||||
|
||||
it("counts only live planners as executing in todo (queued is not executing)", () => {
|
||||
const tasks = [
|
||||
{ ...makeTask("FN-001"), column: "todo" as ColumnType, status: "planning" as any },
|
||||
{ ...makeTask("FN-002"), column: "todo" as ColumnType, status: "queued" as any },
|
||||
{ ...makeTask("FN-003"), column: "todo" as ColumnType, status: "queued" as any },
|
||||
{ ...makeTask("FN-004"), column: "todo" as ColumnType, status: "queued" as any },
|
||||
];
|
||||
render(
|
||||
<Column
|
||||
{...defaultProps}
|
||||
column={"todo" as ColumnType}
|
||||
columnFlags={{ hold: true }}
|
||||
tasks={tasks}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("1 executing of 4")).toHaveTextContent("1/4");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Column workflow mode (U9)", () => {
|
||||
|
||||
Reference in New Issue
Block a user