From 66aecf20d1f7170fc940489a0301257084b25ca1 Mon Sep 17 00:00:00 2001 From: Code UX Date: Tue, 14 Jul 2026 01:33:36 +0000 Subject: [PATCH 1/2] feat(task T05): implement via codex --- .../__tests__/CostAllocationPanels.test.tsx | 234 +++++++++++ .../cost/CostAllocationPanels.module.css | 293 ++++++++++++++ .../components/cost/CostAllocationPanels.tsx | 380 ++++++++++++++++++ .../content/docs/user-dashboard-stats.mdx | 4 + docs-web/user/dashboard/stats.md | 2 + docs/dashboard/design-system-stats.md | 2 + 6 files changed, 915 insertions(+) create mode 100644 dashboard/src/v2/pages/stats/__tests__/CostAllocationPanels.test.tsx create mode 100644 dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.module.css create mode 100644 dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.tsx diff --git a/dashboard/src/v2/pages/stats/__tests__/CostAllocationPanels.test.tsx b/dashboard/src/v2/pages/stats/__tests__/CostAllocationPanels.test.tsx new file mode 100644 index 0000000000..622602965f --- /dev/null +++ b/dashboard/src/v2/pages/stats/__tests__/CostAllocationPanels.test.tsx @@ -0,0 +1,234 @@ +/** + * @vitest-environment jsdom + */ +import { cleanup, render, screen, within } from "@testing-library/preact"; +import * as matchers from "@testing-library/jest-dom/matchers"; +import { afterEach, describe, expect, it } from "vitest"; +import type { + CostAmount, + CostCoverageState, + CostDimensionRow, + CostModelRow, + CostProvenance, + CostSpendSegment, + CostTokenSegment, +} from "../cost-insights.js"; +import { CostAllocationPanels } from "../components/cost/CostAllocationPanels.js"; + +expect.extend(matchers); + +afterEach(() => cleanup()); + +function provenance(state: CostCoverageState, calls = 10): CostProvenance { + return { + state, + invocationCount: calls, + configuredPricingInvocationCount: state === "complete" || state === "partial" ? calls - (state === "partial" ? 2 : 0) : 0, + providerReportedCostInvocationCount: 0, + unpricedInvocationCount: state === "unpriced" ? calls : state === "partial" ? 2 : 0, + unknownInvocationCount: state === "unknown" ? calls : 0, + }; +} + +function amount(usd: number | null, state: CostCoverageState = "complete", calls = 10): CostAmount { + return { usd, provenance: provenance(state, calls) }; +} + +const tokenSegments: CostTokenSegment[] = [ + { id: "input", label: "Input", tokens: 40, share: 0.4 }, + { id: "cached_input", label: "Cached input", tokens: 20, share: 0.2 }, + { id: "output", label: "Output", tokens: 30, share: 0.3 }, + { id: "reasoning", label: "Reasoning", tokens: 10, share: 0.1 }, +]; + +function spendSegments(state: CostCoverageState = "complete"): CostSpendSegment[] { + const values = [1, 2, 4, 3]; + const ids: CostSpendSegment["id"][] = ["input", "cached_input", "output", "provider_reported"]; + const labels = ["Input", "Cached input", "Output", "Provider reported"]; + return ids.map((id, index) => ({ + id, + label: labels[index] ?? id, + amount: amount(values[index] ?? 0, state), + share: (values[index] ?? 0) / 10, + })); +} + +function dimensionRow(id: string, label: string, cost: number, tokens: number, calls = 2): CostDimensionRow { + return { + id, + label, + amount: amount(cost, "complete", calls), + spendShare: cost / 10, + tokenShare: tokens / 100, + calls, + costPerCall: amount(cost / calls, "complete", calls), + tokens, + }; +} + +function modelRow( + id: string, + provider: string, + model: string, + cost: number, + tokens: number, +): CostModelRow { + return { + ...dimensionRow(id, `${provider} / ${model}`, cost, tokens), + provider, + model, + }; +} + +function renderPanels(overrides: Partial[0]> = {}) { + const models = [ + modelRow("provider-a:model-alpha", "Provider A", "model-alpha", 6, 60), + modelRow("provider-b:model-alpha", "Provider B", "model-alpha", 4, 40), + ]; + const purposes = [ + dimensionRow("task_coding", "task_coding", 6, 60), + dimensionRow("quality-assurance", "quality-assurance", 4, 40), + ]; + + return render( + , + ); +} + +describe("CostAllocationPanels", () => { + it("reconciles exact token and spend segments and keeps provider-reported remainder distinct", () => { + renderPanels(); + + expect(screen.getByRole("region", { name: "Cost allocation" })).toBeInTheDocument(); + expect(screen.getByRole("img", { name: /Input 40; cached 20; output 30; reasoning 10; total 100/i })).toBeInTheDocument(); + expect(screen.getByRole("img", { name: /Spend allocation.*Provider reported: \$3\.00, 30\.0%.*Total: \$10\.00/i })).toBeInTheDocument(); + + const tokens = screen.getByRole("list", { name: "Exact token allocation values" }); + expect(within(tokens).getByText("40 tokens")).toBeInTheDocument(); + expect(within(tokens).getByText("20 tokens")).toBeInTheDocument(); + expect(within(tokens).getByText("30 tokens")).toBeInTheDocument(); + expect(within(tokens).getByText("10 tokens")).toBeInTheDocument(); + expect(within(tokens).getAllByText(/%$/).map((node) => node.textContent)).toEqual([ + "40.0%", "20.0%", "30.0%", "10.0%", + ]); + + const spend = screen.getByRole("list", { name: "Exact spend allocation values" }); + expect(within(spend).getByText("Provider reported")).toBeInTheDocument(); + expect(within(spend).getByText("$3.00")).toBeInTheDocument(); + expect(within(spend).getAllByText(/%$/).map((node) => node.textContent)).toEqual([ + "10.0%", "20.0%", "40.0%", "30.0%", + ]); + }); + + it("preserves deterministic view-model ranking and tie order while retaining provider/model identities", () => { + const tiedModels = [ + modelRow("a", "Provider A", "same-model", 5, 50), + modelRow("b", "Provider B", "same-model", 5, 50), + ]; + renderPanels({ models: tiedModels }); + + const ranking = screen.getByRole("list", { name: "Ranked model cost allocation" }); + const providerA = within(ranking).getByText("Provider A · same-model"); + const providerB = within(ranking).getByText("Provider B · same-model"); + expect(providerA.compareDocumentPosition(providerB) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect(within(ranking).getAllByText("same-model")).toHaveLength(2); + }); + + it("groups rows beyond the deterministic top six into a bounded Other row that reconciles totals", () => { + const purposes = Array.from({ length: 8 }, (_, index) => ( + dimensionRow(`purpose_${index + 1}`, `purpose_${index + 1}`, 1.25, 12.5, 1) + )); + renderPanels({ purposes }); + + const ranking = screen.getByRole("list", { name: "Ranked purpose cost allocation" }); + expect(within(ranking).getAllByRole("listitem")).toHaveLength(7); + const other = screen.getByLabelText("Other purpose entries, 2 rows, ranked 7"); + expect(within(other).getByText("Other (2)")).toBeInTheDocument(); + expect(within(other).getByText("$2.50")).toBeInTheDocument(); + expect(within(other).getByText("25")).toBeInTheDocument(); + expect(within(other).getAllByText("25.0%", { selector: "dd" })).toHaveLength(2); + expect(screen.queryByText("Purpose 7")).not.toBeInTheDocument(); + expect(screen.queryByText("Purpose 8")).not.toBeInTheDocument(); + }); + + it("keeps long labels intact, humanizes purpose identifiers, and exposes keyboard-scannable rows", () => { + const longModel = "model-with-an-intentionally-very-long-context-and-reasoning-variant-name"; + const longPurpose = "automated_security_review_and_dependency_validation"; + renderPanels({ + models: [modelRow("long", "Provider with a long regional deployment identity", longModel, 10, 100)], + purposes: [dimensionRow("long-purpose", longPurpose, 10, 100)], + }); + + expect(screen.getByText(longModel)).toBeInTheDocument(); + expect(screen.getByText(`Provider with a long regional deployment identity · ${longModel}`)).toBeInTheDocument(); + expect(screen.getByText("Automated security review and dependency validation")).toBeInTheDocument(); + expect(screen.getByLabelText(`${longModel} ranked 1`)).toHaveAttribute("tabindex", "0"); + }); + + it("shows an explicit empty state without presenting missing data as free usage", () => { + renderPanels({ + totalSpend: amount(null, "unavailable", 0), + totalTokens: 0, + tokenSegments: tokenSegments.map((segment) => ({ ...segment, tokens: 0, share: 0 })), + spendSegments: spendSegments().map((segment) => ({ + ...segment, + amount: amount(null, "unavailable", 0), + share: 0, + })), + models: [], + purposes: [], + }); + + expect(screen.getByText("Empty window — no calls or token usage were recorded.")).toBeInTheDocument(); + expect(screen.getAllByText("Unavailable").length).toBeGreaterThan(0); + expect(screen.queryByText("Configured free usage", { exact: false })).not.toBeInTheDocument(); + expect(screen.getByText("No model cost allocation is available for this window.")).toBeInTheDocument(); + expect(screen.getByText("No purpose cost allocation is available for this window.")).toBeInTheDocument(); + }); + + it("keeps unpriced usage visibly distinct from a configured zero-dollar total", () => { + const unpriced = amount(0, "unpriced", 4); + renderPanels({ + totalSpend: unpriced, + spendSegments: spendSegments("unpriced").map((segment) => ({ + ...segment, + amount: unpriced, + share: 0, + })), + }); + + expect(screen.getByText("Unpriced usage — 4 calls have usage telemetry but no usable price.")).toBeInTheDocument(); + expect(screen.getAllByText("Unpriced").length).toBeGreaterThan(0); + expect(screen.queryByText("Configured free usage", { exact: false })).not.toBeInTheDocument(); + }); + + it("marks partial coverage as a minimum rather than a complete total", () => { + renderPanels({ totalSpend: amount(5, "partial", 10) }); + + expect(screen.getByText("Partial cost coverage — 2 of 10 calls remain unpriced; shown spend is a minimum.")).toBeInTheDocument(); + expect(screen.getByText("$5.00+", { selector: "strong" })).toBeInTheDocument(); + }); + + it("identifies a covered zero total as configured free usage", () => { + renderPanels({ + totalSpend: amount(0, "complete", 3), + spendSegments: spendSegments().map((segment) => ({ + ...segment, + amount: amount(0, "complete", 3), + share: 0, + })), + }); + + expect(screen.getByText("Configured free usage — covered calls reconcile to $0.00 and are not unpriced.")).toBeInTheDocument(); + expect(screen.getAllByText("$0.00").length).toBeGreaterThan(0); + expect(screen.queryByText("Unpriced")).not.toBeInTheDocument(); + }); +}); diff --git a/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.module.css b/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.module.css new file mode 100644 index 0000000000..3aeed6eb40 --- /dev/null +++ b/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.module.css @@ -0,0 +1,293 @@ +.root { + display: grid; + min-width: 0; + gap: 1rem; +} + +.coverageNotice { + border-left: 3px solid var(--stats-accent-signal); + background: var(--stats-selection-fill); + padding: 0.75rem 1rem; + color: var(--stats-detail-color); + font-size: 0.8125rem; + line-height: 1.5; +} + +.allocationGrid, +.breakdownGrid { + display: grid; + min-width: 0; + gap: 1rem; +} + +.allocationPanel, +.breakdownPanel { + min-width: 0; +} + +.panelHeading { + min-width: 0; +} + +.eyebrow, +.metrics dt { + color: var(--stats-label-color); + font-size: 0.625rem; + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.panelTitle { + margin: 0.35rem 0 0; + color: var(--stats-value-color); + font-size: 1.25rem; + font-weight: 630; + line-height: 1.25; + letter-spacing: -0.02em; +} + +.panelDescription { + max-width: 44rem; + margin: 0.4rem 0 0; + color: var(--stats-detail-color); + font-size: 0.8125rem; + line-height: 1.55; +} + +.totalLine { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: 0.5rem 1rem; + margin: 1.5rem 0 0.75rem; + color: var(--stats-detail-color); + font-size: 0.75rem; +} + +.totalLine strong { + color: var(--stats-value-color); + font-family: var(--font-mono); + font-size: 1rem; +} + +.spendBar { + display: flex; + width: 100%; + height: 0.5rem; + overflow: hidden; + border-radius: 999px; + background: var(--stats-quiet-track); +} + +.spendSegment { + min-width: 2px; + height: 100%; + transition: width var(--stats-motion-standard); +} + +.allocationSummary { + margin: 0.75rem 0 0; + color: var(--stats-detail-color); + font-size: 0.75rem; + line-height: 1.45; +} + +.legend { + display: grid; + margin: 1rem 0 0; + padding: 0; + border-top: 1px solid var(--stats-border-hairline); + list-style: none; +} + +.legendRow { + display: grid; + min-width: 0; + grid-template-columns: auto minmax(0, 1fr) auto auto; + align-items: center; + gap: 0.6rem; + border-bottom: 1px solid var(--stats-border-hairline); + padding: 0.7rem 0; + outline: none; +} + +.legendRow:focus-visible, +.dimensionRow:focus-visible { + border-radius: var(--stats-control-radius); + box-shadow: 0 0 0 2px var(--stats-focus-ring); +} + +.swatch { + width: 0.75rem; + height: 0.75rem; + border: 1px solid var(--stats-border-strong); + border-radius: 0.2rem; + background-color: var(--stats-accent-signal); +} + +.segment_input { + background-color: var(--stats-signal-text); +} + +.segment_cached_input { + background-color: var(--stats-accent-cyan); + background-image: repeating-linear-gradient(135deg, transparent 0 2px, var(--stats-surface-panel) 2px 3px); +} + +.segment_output { + background-color: var(--stats-warning-text); + background-image: repeating-linear-gradient(90deg, transparent 0 3px, var(--stats-surface-panel) 3px 4px); +} + +.segment_reasoning { + background-color: var(--stats-negative-text); + background-image: repeating-linear-gradient(45deg, transparent 0 2px, var(--stats-surface-panel) 2px 3px); +} + +.segment_provider_reported { + background-color: var(--stats-positive-text); + background-image: repeating-linear-gradient(-45deg, transparent 0 2px, var(--stats-surface-panel) 2px 4px); +} + +.legendLabel, +.legendValue, +.legendShare { + min-width: 0; + overflow-wrap: anywhere; +} + +.legendLabel { + color: var(--stats-value-color); + font-size: 0.8125rem; + font-weight: 600; +} + +.legendValue, +.legendShare { + font-family: var(--font-mono); + font-size: 0.75rem; + text-align: right; +} + +.legendValue { + color: var(--stats-value-color); +} + +.legendShare { + min-width: 3.75rem; + color: var(--stats-detail-color); +} + +.dimensionList { + display: grid; + margin: 1rem 0 0; + padding: 0; + gap: 0.625rem; + list-style: none; +} + +.dimensionRow { + min-width: 0; + outline: none; +} + +.rowHeading { + display: grid; + min-width: 0; + grid-template-columns: auto minmax(0, 1fr); + align-items: start; + gap: 0.75rem; +} + +.rank { + color: var(--stats-label-color); + font-family: var(--font-mono); + font-size: 0.6875rem; + line-height: 1.5rem; +} + +.rowCopy { + min-width: 0; +} + +.rowCopy h3, +.rowCopy p { + overflow-wrap: anywhere; +} + +.rowCopy h3 { + margin: 0; + color: var(--stats-value-color); + font-size: 0.875rem; + font-weight: 650; + line-height: 1.5; +} + +.rowCopy p { + margin: 0.15rem 0 0; + color: var(--stats-detail-color); + font-family: var(--font-mono); + font-size: 0.6875rem; + line-height: 1.45; +} + +.metrics { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.65rem; + margin: 0.9rem 0 0; +} + +.metrics > div { + min-width: 0; + border-top: 1px solid var(--stats-border-hairline); + padding-top: 0.5rem; +} + +.metrics dt, +.metrics dd { + overflow-wrap: anywhere; +} + +.metrics dd { + margin: 0.2rem 0 0; + color: var(--stats-value-color); + font-family: var(--font-mono); + font-size: 0.75rem; + line-height: 1.35; +} + +@media (min-width: 52rem) { + .allocationGrid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (min-width: 70rem) { + .breakdownGrid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 34rem) { + .legendRow { + grid-template-columns: auto minmax(0, 1fr) auto; + } + + .legendShare { + grid-column: 2 / -1; + text-align: left; + } + + .metrics { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (prefers-reduced-motion: reduce) { + .spendSegment { + transition: none; + } +} diff --git a/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.tsx b/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.tsx new file mode 100644 index 0000000000..6e26857649 --- /dev/null +++ b/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.tsx @@ -0,0 +1,380 @@ +import type { FunctionComponent, JSX } from "preact"; +import type { + CostAmount, + CostDimensionRow, + CostModelRow, + CostProvenance, + CostSpendSegment, + CostTokenSegment, +} from "../../cost-insights.js"; +import { formatAdaptiveCurrency } from "../../cost-insights.js"; +import { NUMBER_FORMATTER } from "../../stats-utils.js"; +import { + DASHED_EMPTY_CLASS, + PANEL_CLASS, + SUBPANEL_CLASS, + TokenFlowBar, +} from "../stats-ui-primitives.js"; +import styles from "./CostAllocationPanels.module.css"; + +const TOP_ROW_COUNT = 6; + +export interface CostAllocationPanelsProps { + totalSpend: CostAmount; + totalTokens: number; + tokenSegments: CostTokenSegment[]; + spendSegments: CostSpendSegment[]; + models: CostModelRow[]; + purposes: CostDimensionRow[]; +} + +interface GroupedDimensionRow extends CostDimensionRow { + groupedRows?: CostDimensionRow[]; +} + +function formatShare(share: number): string { + const percentage = Math.max(0, share) * 100; + return `${percentage.toLocaleString("en-US", { + minimumFractionDigits: percentage > 0 && percentage < 0.1 ? 2 : 1, + maximumFractionDigits: percentage > 0 && percentage < 0.1 ? 2 : 1, + })}%`; +} + +function formatExactTokens(tokens: number): string { + return `${NUMBER_FORMATTER.format(tokens)} tokens`; +} + +function sumProvenance(rows: CostDimensionRow[]): CostProvenance { + const totals = rows.reduce((sum, row) => ({ + state: sum.state, + invocationCount: sum.invocationCount + row.amount.provenance.invocationCount, + configuredPricingInvocationCount: sum.configuredPricingInvocationCount + + row.amount.provenance.configuredPricingInvocationCount, + providerReportedCostInvocationCount: sum.providerReportedCostInvocationCount + + row.amount.provenance.providerReportedCostInvocationCount, + unpricedInvocationCount: sum.unpricedInvocationCount + + row.amount.provenance.unpricedInvocationCount, + unknownInvocationCount: sum.unknownInvocationCount + + row.amount.provenance.unknownInvocationCount, + }), { + state: "unavailable", + invocationCount: 0, + configuredPricingInvocationCount: 0, + providerReportedCostInvocationCount: 0, + unpricedInvocationCount: 0, + unknownInvocationCount: 0, + }); + + const covered = totals.configuredPricingInvocationCount + + totals.providerReportedCostInvocationCount; + if (totals.unknownInvocationCount > 0) totals.state = "unknown"; + else if (rows.some((row) => row.amount.provenance.state === "partial")) totals.state = "partial"; + else if (covered > 0 && totals.unpricedInvocationCount > 0) totals.state = "partial"; + else if (covered === 0 && totals.unpricedInvocationCount > 0) totals.state = "unpriced"; + else if (totals.invocationCount > 0) totals.state = "complete"; + return totals; +} + +function groupDimensionRows(rows: CostDimensionRow[]): GroupedDimensionRow[] { + if (rows.length <= TOP_ROW_COUNT) return rows; + + const groupedRows = rows.slice(TOP_ROW_COUNT); + const provenance = sumProvenance(groupedRows); + const hasUnavailableAmount = groupedRows.some((row) => ( + row.amount.usd === null || row.amount.provenance.state === "unavailable" + )); + const usd = hasUnavailableAmount + ? null + : groupedRows.reduce((sum, row) => sum + (row.amount.usd ?? 0), 0); + const calls = groupedRows.reduce((sum, row) => sum + row.calls, 0); + const amount: CostAmount = { usd, provenance }; + const costPerCall: CostAmount = calls > 0 && usd !== null + ? { usd: usd / calls, provenance } + : { usd: null, provenance: { ...provenance, state: "unavailable" } }; + + return [ + ...rows.slice(0, TOP_ROW_COUNT), + { + id: "__other__", + label: "Other", + amount, + spendShare: groupedRows.reduce((sum, row) => sum + row.spendShare, 0), + tokenShare: groupedRows.reduce((sum, row) => sum + row.tokenShare, 0), + calls, + costPerCall, + tokens: groupedRows.reduce((sum, row) => sum + row.tokens, 0), + groupedRows, + }, + ]; +} + +function humanizePurpose(label: string): string { + const normalized = label.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim(); + return normalized.length > 0 + ? `${normalized.charAt(0).toUpperCase()}${normalized.slice(1)}` + : "Unclassified purpose"; +} + +function coverageMessage(totalSpend: CostAmount, totalTokens: number): string { + const provenance = totalSpend.provenance; + if (provenance.invocationCount === 0 && totalTokens === 0) { + return "Empty window — no calls or token usage were recorded."; + } + if (provenance.state === "unpriced") { + return `Unpriced usage — ${NUMBER_FORMATTER.format(provenance.unpricedInvocationCount)} calls have usage telemetry but no usable price.`; + } + if (provenance.state === "partial") { + return `Partial cost coverage — ${NUMBER_FORMATTER.format(provenance.unpricedInvocationCount)} of ${NUMBER_FORMATTER.format(provenance.invocationCount)} calls remain unpriced; shown spend is a minimum.`; + } + if (provenance.state === "unknown") { + return `Coverage unknown — ${NUMBER_FORMATTER.format(provenance.unknownInvocationCount)} of ${NUMBER_FORMATTER.format(provenance.invocationCount)} calls lack cost-source metadata.`; + } + if (provenance.state === "unavailable") { + return "Spend unavailable — this window does not contain enough cost data to price usage."; + } + if (totalSpend.usd === 0) { + const configuredOnly = provenance.configuredPricingInvocationCount > 0 + && provenance.providerReportedCostInvocationCount === 0; + return configuredOnly + ? "Configured free usage — covered calls reconcile to $0.00 and are not unpriced." + : "Covered zero-cost usage — covered calls reconcile to $0.00 and are not unpriced."; + } + return `Complete cost coverage — all ${NUMBER_FORMATTER.format(provenance.invocationCount)} calls have a usable cost source.`; +} + +function AllocationHeading({ id, eyebrow, title, description }: { + id: string; + eyebrow: string; + title: string; + description: string; +}): JSX.Element { + return ( +
+
{eyebrow}
+

{title}

+

{description}

+
+ ); +} + +const TokenAllocation: FunctionComponent<{ + totalTokens: number; + segments: CostTokenSegment[]; +}> = ({ totalTokens, segments }) => { + const byId = new Map(segments.map((segment) => [segment.id, segment])); + const getTokens = (id: CostTokenSegment["id"]): number => byId.get(id)?.tokens ?? 0; + + return ( +
+ +
+ Total token volume + {formatExactTokens(totalTokens)} +
+ + {totalTokens === 0 ? ( +

Zero total tokens — no token allocation is available.

+ ) : ( +

+ {segments[0]?.label ?? "Token lanes"} accounts for {formatShare(segments[0]?.share ?? 0)} of recorded volume. +

+ )} +
    + {segments.map((segment) => ( +
  • +
  • + ))} +
+
+ ); +}; + +const SpendAllocation: FunctionComponent<{ + totalSpend: CostAmount; + totalTokens: number; + segments: CostSpendSegment[]; +}> = ({ totalSpend, totalTokens, segments }) => { + const hasVisualSpend = totalSpend.usd !== null && totalSpend.usd > 0; + const leadingSegment = segments.reduce((leader, segment) => ( + leader === null || segment.share > leader.share ? segment : leader + ), null); + const chartLabel = hasVisualSpend + ? `Spend allocation. ${segments.map((segment) => `${segment.label}: ${formatAdaptiveCurrency(segment.amount)}, ${formatShare(segment.share)}`).join("; ")}. Total: ${formatAdaptiveCurrency(totalSpend)}.` + : `Spend allocation unavailable. Total: ${formatAdaptiveCurrency(totalSpend)}.`; + + return ( +
+ +
+ Total recorded spend + {formatAdaptiveCurrency(totalSpend)} +
+
+ {hasVisualSpend ? segments.map((segment) => ( + segment.share > 0 ? ( +
+

+ {hasVisualSpend && leadingSegment + ? `${leadingSegment.label} accounts for ${formatShare(leadingSegment.share)} of recorded spend.` + : totalTokens > 0 + ? "No positive priced spend lanes are available; consult the coverage state above." + : "No spend allocation is available for this empty window."} +

+
    + {segments.map((segment) => ( +
  • +
  • + ))} +
+
+ ); +}; + +const DimensionMetrics: FunctionComponent<{ row: CostDimensionRow }> = ({ row }) => ( +
+
Total cost
{formatAdaptiveCurrency(row.amount)}
+
Spend share
{formatShare(row.spendShare)}
+
Tokens
{NUMBER_FORMATTER.format(row.tokens)}
+
Token share
{formatShare(row.tokenShare)}
+
Calls
{NUMBER_FORMATTER.format(row.calls)}
+
Cost / call
{formatAdaptiveCurrency(row.costPerCall)}
+
+); + +function modelIdentity(row: CostModelRow): string { + return `${row.provider} · ${row.model ?? "Model not reported"}`; +} + +const DimensionRow: FunctionComponent<{ + row: GroupedDimensionRow; + rank: number; + kind: "model" | "purpose"; +}> = ({ row, rank, kind }) => { + const model = kind === "model" && row.id !== "__other__" ? row as CostModelRow : null; + const title = row.id === "__other__" + ? `Other (${row.groupedRows?.length ?? 0})` + : kind === "purpose" ? humanizePurpose(row.label) : model?.model ?? "Model not reported"; + const identity = model ? modelIdentity(model) : null; + const content = ( + <> +
+ +
+

{title}

+ {identity ?

{identity}

: null} +
+
+ + + ); + + if (!row.groupedRows) { + return ( +
  • + {content} +
  • + ); + } + + return ( +
  • + {content} +
  • + ); +}; + +const DimensionBreakdown: FunctionComponent<{ + title: string; + description: string; + rows: CostDimensionRow[]; + kind: "model" | "purpose"; +}> = ({ title, description, rows, kind }) => { + const groupedRows = groupDimensionRows(rows); + const titleId = `cost-${kind}-breakdown-title`; + + return ( +
    + + {rows.length === 0 ? ( +
    + No {kind} cost allocation is available for this window. +
    + ) : ( +
      + {groupedRows.map((row, index) => ( + + ))} +
    + )} +
    + ); +}; + +export const CostAllocationPanels: FunctionComponent = ({ + totalSpend, + totalTokens, + tokenSegments, + spendSegments, + models, + purposes, +}) => ( +
    +
    + {coverageMessage(totalSpend, totalTokens)} +
    +
    + + +
    +
    + + +
    +
    +); diff --git a/docs-web/content/docs/user-dashboard-stats.mdx b/docs-web/content/docs/user-dashboard-stats.mdx index f38ce9ad46..5a305f5d2b 100644 --- a/docs-web/content/docs/user-dashboard-stats.mdx +++ b/docs-web/content/docs/user-dashboard-stats.mdx @@ -77,6 +77,10 @@ Cost data is visualized directly within the Usage Graph and Composition views, f - Cost coverage distinguishes configured-pricing calls, provider-reported fallback calls, and unpriced calls. A zero-dollar covered call is different from an invocation that has no usable pricing source, and an empty window reports zero calls in every category. - Current pricing settings recalculate historical Stats projections when the snapshot is requested; changing pricing does not rewrite stored invocation telemetry. - Cost analytics group multiple runs of the same conceptual sprint into one canonical sprint row. The existing Sprint Telemetry ledger remains run-oriented. +- Cost allocation separates input, cached input, output, reasoning, and provider-reported fallback lanes with exact values and percentages. Ranked model and execution-purpose rows include spend, tokens, shares, calls, and cost per call; dense collections keep six leaders visible and reconcile the remainder in one bounded `Other` group. +- Allocation patterns and text labels duplicate color meaning, rows support keyboard scanning and long provider/model names, and empty, unpriced, partial, covered zero-dollar, and unknown-coverage windows remain visibly different. +- Cost totals, per-call and per-token rates, task/sprint averages, breakdowns, and detail rows share one deterministic calculation model. Invalid negative or non-finite telemetry is treated as zero, breakdowns reconcile to the normalized totals, and equal rankings use stable labels and identities. +- Small proven costs retain sub-cent precision. Fully covered zero-price usage displays as `$0.00`; partially priced, unpriced, legacy unknown-coverage, and empty telemetry use distinct states so missing pricing is never presented as free usage. ## Underlying telemetry diff --git a/docs-web/user/dashboard/stats.md b/docs-web/user/dashboard/stats.md index a95f5eb9b7..5a305f5d2b 100644 --- a/docs-web/user/dashboard/stats.md +++ b/docs-web/user/dashboard/stats.md @@ -77,6 +77,8 @@ Cost data is visualized directly within the Usage Graph and Composition views, f - Cost coverage distinguishes configured-pricing calls, provider-reported fallback calls, and unpriced calls. A zero-dollar covered call is different from an invocation that has no usable pricing source, and an empty window reports zero calls in every category. - Current pricing settings recalculate historical Stats projections when the snapshot is requested; changing pricing does not rewrite stored invocation telemetry. - Cost analytics group multiple runs of the same conceptual sprint into one canonical sprint row. The existing Sprint Telemetry ledger remains run-oriented. +- Cost allocation separates input, cached input, output, reasoning, and provider-reported fallback lanes with exact values and percentages. Ranked model and execution-purpose rows include spend, tokens, shares, calls, and cost per call; dense collections keep six leaders visible and reconcile the remainder in one bounded `Other` group. +- Allocation patterns and text labels duplicate color meaning, rows support keyboard scanning and long provider/model names, and empty, unpriced, partial, covered zero-dollar, and unknown-coverage windows remain visibly different. - Cost totals, per-call and per-token rates, task/sprint averages, breakdowns, and detail rows share one deterministic calculation model. Invalid negative or non-finite telemetry is treated as zero, breakdowns reconcile to the normalized totals, and equal rankings use stable labels and identities. - Small proven costs retain sub-cent precision. Fully covered zero-price usage displays as `$0.00`; partially priced, unpriced, legacy unknown-coverage, and empty telemetry use distinct states so missing pricing is never presented as free usage. diff --git a/docs/dashboard/design-system-stats.md b/docs/dashboard/design-system-stats.md index bb24ae2928..4f21bbab1c 100644 --- a/docs/dashboard/design-system-stats.md +++ b/docs/dashboard/design-system-stats.md @@ -191,6 +191,8 @@ The `StatsPage` uses the `useStatsPageData` hook to coordinate visual modes. The `cost-insights.ts` is the pure frontend boundary for Cost calculations and display state. Cost components consume its normalized totals, rates, averages, reconciled spend/token segments, deterministic dimension rows, and task/canonical-sprint details instead of re-deriving values in JSX. Every monetary amount carries coverage provenance so complete zero-price usage, partial pricing, unpriced telemetry, legacy unknown coverage, and empty data remain distinct. +Cost allocation panels pair separate token and spend graphics with exact textual legends. Token lanes cover input, cached input, output, and reasoning; spend lanes keep token-priced input, cached input, output, and provider-reported fallback spend distinct. Ranked model and execution-purpose rows show spend, tokens, calls, both shares, and cost per call. The first six rows remain visible and all additional rows reconcile into one bounded `Other` summary so source collection size does not expand the rendered ledger. Patterns, labels, focusable rows, and full provider/model identities make the breakdown usable without relying on color or pointer input. + Task and canonical sprint averages use distinct rows that contain provider invocations. Covered zero-cost rows remain in the denominator, while an empty collection produces an unavailable amount. Canonical sprint rows come from `costAnalytics.sprints`; legacy snapshots fall back to the run-oriented `sprints` ledger only when the additive projection is absent. ## Responsive Behavior From ad166f66eae2d96a969e90e2322ca6d37e881b5a Mon Sep 17 00:00:00 2001 From: Code UX Date: Tue, 14 Jul 2026 02:14:20 +0000 Subject: [PATCH 2/2] feat(task T05): implement via codex --- .../__tests__/CostAllocationPanels.test.tsx | 40 +++++++++++++++ .../cost/CostAllocationPanels.module.css | 11 +--- .../components/cost/CostAllocationPanels.tsx | 50 +++++++++++++++---- 3 files changed, 82 insertions(+), 19 deletions(-) diff --git a/dashboard/src/v2/pages/stats/__tests__/CostAllocationPanels.test.tsx b/dashboard/src/v2/pages/stats/__tests__/CostAllocationPanels.test.tsx index 622602965f..c3b37d2b9e 100644 --- a/dashboard/src/v2/pages/stats/__tests__/CostAllocationPanels.test.tsx +++ b/dashboard/src/v2/pages/stats/__tests__/CostAllocationPanels.test.tsx @@ -217,6 +217,43 @@ describe("CostAllocationPanels", () => { expect(screen.getByText("$5.00+", { selector: "strong" })).toBeInTheDocument(); }); + it("does not announce unknown or unavailable coverage as zero spend", () => { + const { rerender } = renderPanels({ + totalSpend: amount(0, "unknown", 3), + spendSegments: spendSegments("unknown").map((segment) => ({ + ...segment, + amount: amount(0, "unknown", 3), + share: 0, + })), + }); + + expect(screen.getByText("Coverage unknown — 3 of 3 calls lack cost-source metadata.")).toBeInTheDocument(); + expect(screen.getByRole("img", { + name: /Spend allocation.*Input: Coverage unknown, 0\.0%.*Total: Coverage unknown\. No positive spend lanes\./i, + })).toBeInTheDocument(); + expect(screen.queryByText("$0.00")).not.toBeInTheDocument(); + + rerender( + ({ + ...segment, + amount: amount(null, "unavailable", 3), + share: 0, + }))} + models={[]} + purposes={[]} + />, + ); + + expect(screen.getByText("Spend unavailable — this window does not contain enough cost data to price usage.")).toBeInTheDocument(); + expect(screen.getByRole("img", { + name: /Spend allocation.*Provider reported: Unavailable, 0\.0%.*Total: Unavailable\. No positive spend lanes\./i, + })).toBeInTheDocument(); + }); + it("identifies a covered zero total as configured free usage", () => { renderPanels({ totalSpend: amount(0, "complete", 3), @@ -229,6 +266,9 @@ describe("CostAllocationPanels", () => { expect(screen.getByText("Configured free usage — covered calls reconcile to $0.00 and are not unpriced.")).toBeInTheDocument(); expect(screen.getAllByText("$0.00").length).toBeGreaterThan(0); + expect(screen.getByRole("img", { + name: /Spend allocation.*Provider reported: \$0\.00, 0\.0%.*Total: \$0\.00\. No positive spend lanes\./i, + })).toBeInTheDocument(); expect(screen.queryByText("Unpriced")).not.toBeInTheDocument(); }); }); diff --git a/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.module.css b/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.module.css index 3aeed6eb40..7e2f6d52d6 100644 --- a/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.module.css +++ b/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.module.css @@ -5,10 +5,8 @@ } .coverageNotice { - border-left: 3px solid var(--stats-accent-signal); - background: var(--stats-selection-fill); + border-left-width: 3px; padding: 0.75rem 1rem; - color: var(--stats-detail-color); font-size: 0.8125rem; line-height: 1.5; } @@ -78,7 +76,6 @@ height: 0.5rem; overflow: hidden; border-radius: 999px; - background: var(--stats-quiet-track); } .spendSegment { @@ -113,12 +110,6 @@ outline: none; } -.legendRow:focus-visible, -.dimensionRow:focus-visible { - border-radius: var(--stats-control-radius); - box-shadow: 0 0 0 2px var(--stats-focus-ring); -} - .swatch { width: 0.75rem; height: 0.75rem; diff --git a/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.tsx b/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.tsx index 6e26857649..4b04995fa5 100644 --- a/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.tsx +++ b/dashboard/src/v2/pages/stats/components/cost/CostAllocationPanels.tsx @@ -10,9 +10,13 @@ import type { import { formatAdaptiveCurrency } from "../../cost-insights.js"; import { NUMBER_FORMATTER } from "../../stats-utils.js"; import { + CHIP_CLASS, + CONTROL_FOCUS_CLASS, DASHED_EMPTY_CLASS, PANEL_CLASS, + STATUS_TONE_CLASS, SUBPANEL_CLASS, + TRACK_CLASS, TokenFlowBar, } from "../stats-ui-primitives.js"; import styles from "./CostAllocationPanels.module.css"; @@ -142,6 +146,19 @@ function coverageMessage(totalSpend: CostAmount, totalTokens: number): string { return `Complete cost coverage — all ${NUMBER_FORMATTER.format(provenance.invocationCount)} calls have a usable cost source.`; } +function coverageTone( + totalSpend: CostAmount, + totalTokens: number, +): keyof typeof STATUS_TONE_CLASS { + if (totalSpend.provenance.invocationCount === 0 && totalTokens === 0) return "neutral"; + if (totalSpend.provenance.state === "unpriced" || totalSpend.provenance.state === "partial") { + return "warning"; + } + if (totalSpend.provenance.state === "unknown") return "cyan"; + if (totalSpend.provenance.state === "unavailable") return "neutral"; + return totalSpend.usd === 0 ? "positive" : "signal"; +} + function AllocationHeading({ id, eyebrow, title, description }: { id: string; eyebrow: string; @@ -192,7 +209,11 @@ const TokenAllocation: FunctionComponent<{ )}
      {segments.map((segment) => ( -
    • +