FN-6718: surface session counts in Command Center overview

Surface selected-range session totals in the Command Center overview stat grid.

- Add a Sessions stat card backed by existing ActivityAnalytics data.
- Keep Overview populated when sessions are the only activity and default missing session values to zero.
- Cover populated, zero, omitted, and empty Overview session states in tests.
- Document the Overview sessions metric source in the dashboard guide.

Files changed:
 docs/dashboard-guide.md                            |  2 +-
 .../components/command-center/CommandCenter.tsx    | 10 +++-
 .../__tests__/CommandCenter.test.tsx               | 53 ++++++++++++++++++++++
 3 files changed, 62 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-6718

Fusion-Task-Lineage: 5e41d3dc-2f69-48e0-aceb-848cb111ea3b
This commit is contained in:
gsxdsm
2026-06-19 12:30:47 -07:00
parent 62033e5ef7
commit 25ba732598
3 changed files with 62 additions and 3 deletions

View File

@@ -663,7 +663,7 @@ Navigation:
Features:
- Global date-range picker in the header scopes the analytics tabs; **Mission Control** remains live rather than historical.
- **Overview** summarizes token usage/cost, autonomy, active nodes, agent runs, tasks done, model breadth, and real open signals, and includes the SDLC throughput funnel for the selected range at the bottom of the Overview content in loading, error, empty, and populated states. Its token total and Live activity snapshot token metric refresh on a bounded live cadence and animate number changes while preserving reduced-motion preferences. The Live activity snapshot also shows the current board-state count for tasks in progress, independent of the selected analytics date range. Overview includes a graph-rich software-factory snapshot with the existing tokens-by-model bar, tool-category bar, real recharts token-share pie, and the daily activity multi-series line chart placed before the daily activity sparkline/trend so the richer line graph sits higher in the chart grid. These reuse the already-loaded tokens, tools, activity, and signals analytics; the signals count comes from `/api/command-center/signals` and renders unavailable (`—`) while the incidents-backed response is loading or unavailable. The chart reveal/glow accents are decorative and disabled when reduced-motion preferences are active. The SDLC completion rate is shown as a radial gauge and is calculated as cohort conversion from in-range triage entrants, so the rate is capped at 100% even when older tasks finish during the range.
- **Overview** summarizes token usage/cost, autonomy, active nodes, sessions, agent runs, tasks done, model breadth, and real open signals, and includes the SDLC throughput funnel for the selected range at the bottom of the Overview content in loading, error, empty, and populated states. Its token total and Live activity snapshot token metric refresh on a bounded live cadence and animate number changes while preserving reduced-motion preferences. The sessions card uses the selected-range `ActivityAnalytics.sessions` value already loaded for the overview. The Live activity snapshot also shows the current board-state count for tasks in progress, independent of the selected analytics date range. Overview includes a graph-rich software-factory snapshot with the existing tokens-by-model bar, tool-category bar, real recharts token-share pie, and the daily activity multi-series line chart placed before the daily activity sparkline/trend so the richer line graph sits higher in the chart grid. These reuse the already-loaded tokens, tools, activity, and signals analytics; the signals count comes from `/api/command-center/signals` and renders unavailable (`—`) while the incidents-backed response is loading or unavailable. The chart reveal/glow accents are decorative and disabled when reduced-motion preferences are active. The SDLC completion rate is shown as a radial gauge and is calculated as cohort conversion from in-range triage entrants, so the rate is capped at 100% even when older tasks finish during the range.
- **Tokens** breaks down token totals, estimated cost, tasks, and per-model usage. Per-model and per-provider breakdowns use the task's analytics-only actually-used model snapshot when available, so usage from settings-resolved runs appears under the real runtime model instead of `(unknown)` without changing future model resolution; estimated cost uses the same snapshot-first, legacy-fallback model identity so those resolved runs price normally when the model is in the pricing table. It includes the existing token-usage-over-time chart, an additive recharts multi-series line graph, and a token-share pie backed by the same grouped token analytics; use the granularity control to switch the time-series request between hourly, daily, and weekly buckets. The token total and charts poll on a bounded cadence, keep the previous data visible during refresh, animate decorative count/bar transitions, and disable those animations for reduced-motion users.
- **Tools** shows autonomy ratio, tool-call volume, intervention counts, sessions, and tool categories. The area keeps the existing category bar and adds a recharts category-share pie from `ToolAnalytics.byCategory`. There is intentionally no tools line chart yet because `ToolAnalytics` does not expose a per-day tool trend; the dashboard does not fabricate one or call a new endpoint.
- **Activity** tracks sessions, messages, active nodes, active agents, agent heartbeat runs, and stickiness. Agent-run sheets show total, active, completed, and failed runs for the selected range, and the Agent runs/day sparkline trends runs by `agentRuns.startedAt`. The area keeps the existing live animated line charts for messages/day, active agents/day, active nodes/day, and combined throughput/day (`messages + active agents + active nodes`), and adds a recharts multi-series line graph for messages, active agents, and agent runs plus an agent-run outcome pie from the existing `agentRuns` split. These charts reuse the existing activity analytics endpoint, refresh on a bounded 15-second cadence while mounted, keep the previous data visible during refreshes, and disable decorative draw-on motion for reduced-motion users.

View File

@@ -125,6 +125,7 @@ function OverviewTab({ range }: { range: DateRange }) {
const toolCalls = tools.data?.toolCalls ?? 0;
const activeNodes = activity.data?.activeNodes ?? 0;
const activeAgents = activity.data?.activeAgents ?? 0;
const sessionsCount = activity.data?.sessions ?? 0;
const agentRunsTotal = activity.data?.agentRuns?.total ?? 0;
const tasksDone = activity.data?.funnel?.doneInRange ?? 0;
/*
@@ -175,10 +176,10 @@ function OverviewTab({ range }: { range: DateRange }) {
const activityTrendValues =
dailyActivityValues.length > 0
? dailyActivityValues
: [activity.data?.sessions ?? 0, activity.data?.messages ?? 0, activeAgents, activeNodes, tasksDone];
: [sessionsCount, activity.data?.messages ?? 0, activeAgents, activeNodes, tasksDone];
const hasOverviewChartData = tokensByModelData.length > 0 || toolCategoryData.length > 0 || dailyActivityValues.length > 0;
const hasActivityData =
(activity.data?.sessions ?? 0) > 0 ||
sessionsCount > 0 ||
(activity.data?.messages ?? 0) > 0 ||
activeNodes > 0 ||
activeAgents > 0 ||
@@ -207,6 +208,11 @@ function OverviewTab({ range }: { range: DateRange }) {
},
{ id: "autonomy", label: t("commandCenter.overview.autonomy", "Autonomy ratio"), value: autonomyLabel },
{ id: "nodes", label: t("commandCenter.overview.activeNodes", "Active nodes"), value: formatCount(activeNodes) },
/*
FNXC:CommandCenter 2026-06-19-00:00:
Session counts were already present on ActivityAnalytics for the selected date range but missing from the Overview stat grid. Surface the existing value here without adding a new endpoint.
*/
{ id: "sessions", label: t("commandCenter.overview.sessions", "Sessions"), value: formatCount(sessionsCount) },
{ id: "agentRuns", label: t("commandCenter.overview.agentRuns", "Agent runs"), value: formatCount(agentRunsTotal) },
{ id: "tasksDone", label: t("commandCenter.overview.tasksDone", "Tasks done"), value: formatCount(tasksDone) },
{ id: "models", label: t("commandCenter.overview.uniqueModels", "Unique models"), value: formatCount(uniqueModels) },

View File

@@ -343,6 +343,7 @@ describe("CommandCenter shell", () => {
expect(screen.queryByTestId("command-center-overview-chart-activity")).toBeNull();
await screen.findByTestId("command-center-empty");
expectThroughputLastAfter("command-center-empty");
expect(screen.queryByTestId("command-center-stat-sessions")).toBeNull();
expect(screen.queryByTestId("command-center-overview-charts")).toBeNull();
expect(screen.queryByTestId("cc-overview-pie")).toBeNull();
expect(screen.queryByTestId("cc-overview-line")).toBeNull();
@@ -363,6 +364,57 @@ describe("CommandCenter shell", () => {
expect(statValue("command-center-stat-agentRuns")).toBe("5");
});
it("renders the date-range Sessions stat card when sessions exist", async () => {
mockOverviewApi({
tokens: tokenFixture(0),
tools: toolsFixture(0),
activity: activityFixture({ sessions: 3, messages: 0, activeNodes: 0, activeAgents: 0, agentRuns: 0, doneInRange: 0 }),
signals: signalsFixture(0),
live: liveFixture([{ column: "in-progress", count: 0 }]),
});
render(<CommandCenter />);
await screen.findByTestId("command-center-stat-sessions");
expect(statValue("command-center-stat-sessions")).toBe("3");
expect(screen.queryByTestId("command-center-empty")).toBeNull();
});
it("renders zero in the Sessions card when other activity keeps Overview populated", async () => {
mockOverviewApi({
tokens: tokenFixture(0),
tools: toolsFixture(0),
activity: activityFixture({ sessions: 0, messages: 4, activeNodes: 0, activeAgents: 0, agentRuns: 0, doneInRange: 0 }),
signals: signalsFixture(0),
live: liveFixture([{ column: "in-progress", count: 0 }]),
});
render(<CommandCenter />);
await screen.findByTestId("command-center-stat-sessions");
expect(statValue("command-center-stat-sessions")).toBe("0");
expect(screen.queryByTestId("command-center-empty")).toBeNull();
});
it("defaults the Sessions stat card to zero when activity payload omits sessions", async () => {
const { sessions: _omitted, ...activityWithoutSessions } = activityFixture({
messages: 5,
activeNodes: 0,
activeAgents: 0,
agentRuns: 0,
doneInRange: 0,
});
mockOverviewApi({
tokens: tokenFixture(0),
tools: toolsFixture(0),
activity: activityWithoutSessions,
signals: signalsFixture(0),
live: liveFixture([{ column: "in-progress", count: 0 }]),
});
render(<CommandCenter />);
await screen.findByTestId("command-center-stat-sessions");
expect(statValue("command-center-stat-sessions")).toBe("0");
});
it("renders live Overview headline values when analytics data exists", async () => {
mockOverviewApi();
render(<CommandCenter />);
@@ -374,6 +426,7 @@ describe("CommandCenter shell", () => {
expect(screen.getByTestId("command-center-stat-tokens").textContent).toContain("$12.50");
expect(statValue("command-center-stat-autonomy")).toBe("10.0:1");
expect(statValue("command-center-stat-nodes")).toBe("3");
expect(statValue("command-center-stat-sessions")).toBe("4");
expect(statValue("command-center-stat-agentRuns")).toBe("8");
expect(statValue("command-center-stat-tasksDone")).toBe("7");
expect(statValue("command-center-stat-models")).toBe("2");