import { Fragment, useState } from 'react' import { CliErrorPanel, CliErrorText } from '../components/CliErrorPanel' import { EmptyNote } from '../components/EmptyState' import { ListRow } from '../components/ListRow' import { Panel } from '../components/Panel' import { Sankey } from '../components/Sankey' import { SectionSkeleton } from '../components/Skeleton' import { StackedBars } from '../components/StackedBars' import { StaleBanner } from '../components/StaleBanner' import { type Polled, usePolled } from '../hooks/usePolled' import { formatUsd } from '../lib/format' import { codeburn } from '../lib/ipc' import { contiguousDailyWindow, dataStartKey, localDateKey } from '../lib/period' import type { CliError, DateRange, MenubarPayload, Period, SpendFlow } from '../lib/types' type Project = MenubarPayload['current']['topProjects'][number] /** Date-only CLI strings ("2026-07-11") formatted at local noon so the calendar day never rolls across time zones. */ function formatProjectDay(date: string): string { const d = new Date(`${date}T12:00:00`) return Number.isNaN(d.getTime()) ? '—' : d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) } const SPEND_CHART_DAYS = 15 function providerLabel(provider: string): string { if (provider === 'all') return 'All models' return provider .split(/[-\s]+/) .filter(Boolean) .map(part => part.charAt(0).toUpperCase() + part.slice(1)) .join(' ') } export function Spend({ period, provider, range = null }: { period: Period; provider: string; range?: DateRange | null }) { const overview = usePolled( () => range ? codeburn.getOverview(period, provider, range) : codeburn.getOverview(period, provider), [period, provider, range?.from, range?.to], ) return } export function SpendContent({ period, provider, range = null, overview, refreshToken = 0, ready = true, }: { period: Period provider: string range?: DateRange | null overview: Polled refreshToken?: number ready?: boolean }) { // Gate on app-level readiness so boot hydrates the cache once (default true // keeps standalone renders/tests polling normally). const flow = usePolled( () => range ? codeburn.getSpendFlow(period, provider, range) : codeburn.getSpendFlow(period, provider), [period, provider, range?.from, range?.to, refreshToken], { enabled: ready, memoKey: `spendflow|${period}|${provider}|${range?.from ?? ''}-${range?.to ?? ''}` }, ) if (!overview.data) { if (overview.error) return return } const animateKey = `${period}|${provider}|${range?.from ?? ''}|${range?.to ?? ''}` return } function SpendPage({ data, flow, provider, range, staleError, animateKey, }: { data: MenubarPayload flow: ReturnType> provider: string range: DateRange | null staleError: CliError | null animateKey: string }) { // `history.daily` is SPARSE (active days only), so zero-fill a contiguous // calendar window client-side; date keys are localDateKey / the CLI dateKey, // which match exactly, so real days always land in place. const now = new Date() const chartDaily = range ? contiguousDailyWindow(data.history.daily, range.from, range.to) : contiguousDailyWindow( data.history.daily, localDateKey(new Date(now.getFullYear(), now.getMonth(), now.getDate() - (SPEND_CHART_DAYS - 1))), localDateKey(now), ) const chartHasSpend = chartDaily.some(day => day.cost > 0) const dataStart = dataStartKey(data.history.daily) const projects = data.current.topProjects const breakdowns = [ { title: 'Activity', rows: [ ...data.current.topActivities.map(row => ({ key: `activity-${row.name}`, title: row.name, sub: `${row.turns.toLocaleString('en-US')} turns`, value: formatUsd(row.cost), })), ...data.current.skills.map(row => ({ key: `skill-${row.name}`, title: row.name, sub: `${row.turns.toLocaleString('en-US')} turns · skill`, value: formatUsd(row.cost), })), ], }, { title: 'Tools', rows: data.current.tools.map(row => ({ key: row.name, title: row.name, sub: `${row.calls.toLocaleString('en-US')} calls`, value: undefined, })), }, { title: 'MCP', rows: data.current.mcpServers.map(row => ({ key: row.name, title: row.name, sub: `${row.calls.toLocaleString('en-US')} calls`, value: undefined, })), }, { title: 'Subagents', rows: data.current.subagents.map(row => ({ key: row.name, title: row.name, sub: `${row.calls.toLocaleString('en-US')} calls`, value: formatUsd(row.cost), })), }, ].filter(section => section.rows.length) return ( <> {staleError && }
{chartHasSpend ? : No model spend in this range yet.}
{flow.data && flow.data.links.length ? ( ) : flow.error ? ( ) : ( {flow.loading ? 'Loading cost flow…' : 'No model-project flow in this range yet.'} )}
{breakdowns.length ? ( breakdowns.map(section => ) ) : ( No activity, tool, MCP, or subagent data in this range yet. )}
) } function ProjectBreakdown({ projects }: { projects: Project[] }) { const [expanded, setExpanded] = useState(null) return ( {projects.length ? ( projects.map((project, i) => { const open = expanded === project.name return ( setExpanded(current => current === project.name ? null : project.name)} /> {open && (
{project.sessionDetails.length ? ( project.sessionDetails.map((session, j) => (
{formatProjectDay(session.date)} {session.models[0]?.name ?? '—'} {session.calls.toLocaleString('en-US')} calls {formatUsd(session.cost)}
)) ) : (
No session detail for this project.
)}
)}
) }) ) : ( No project spend in this range yet. )}
) } function RowsPanel({ title, rows, }: { title: string rows: Array<{ key: string; title: string; sub: string; value?: string }> }) { return ( {rows.map((row, i) => ( ))} ) }