diff --git a/src/renderer/src/components/stats/ClaudeUsageDailyChart.tsx b/src/renderer/src/components/stats/ClaudeUsageDailyChart.tsx index ed6f40d22f8..3f99f7e435f 100644 --- a/src/renderer/src/components/stats/ClaudeUsageDailyChart.tsx +++ b/src/renderer/src/components/stats/ClaudeUsageDailyChart.tsx @@ -11,18 +11,26 @@ function formatTokens(value: number): string { return value.toLocaleString() } +function getDailyTotal(entry: ClaudeUsageDailyPoint): number { + return entry.inputTokens + entry.outputTokens + entry.cacheReadTokens + entry.cacheWriteTokens +} + +function getMaxDailyTotal(daily: ClaudeUsageDailyPoint[]): number { + let max = 1 + // Why: all-time usage histories can exceed V8's argument limit if spread + // into Math.max, even though the chart only renders the last 10 days. + for (const entry of daily) { + max = Math.max(max, getDailyTotal(entry)) + } + return max +} + type ClaudeUsageDailyChartProps = { daily: ClaudeUsageDailyPoint[] } export function ClaudeUsageDailyChart({ daily }: ClaudeUsageDailyChartProps): React.JSX.Element { - const maxDailyTotal = Math.max( - 1, - ...daily.map( - (entry) => - entry.inputTokens + entry.outputTokens + entry.cacheReadTokens + entry.cacheWriteTokens - ) - ) + const maxDailyTotal = getMaxDailyTotal(daily) return (
@@ -34,8 +42,7 @@ export function ClaudeUsageDailyChart({ daily }: ClaudeUsageDailyChartProps): Re
{daily.slice(-10).map((entry) => { - const total = - entry.inputTokens + entry.outputTokens + entry.cacheReadTokens + entry.cacheWriteTokens + const total = getDailyTotal(entry) const segments = [ { key: 'cache-write', diff --git a/src/renderer/src/components/stats/CodexUsageDailyChart.tsx b/src/renderer/src/components/stats/CodexUsageDailyChart.tsx index ca5bcb9296c..88f3613047d 100644 --- a/src/renderer/src/components/stats/CodexUsageDailyChart.tsx +++ b/src/renderer/src/components/stats/CodexUsageDailyChart.tsx @@ -11,12 +11,22 @@ function formatTokens(value: number): string { return value.toLocaleString() } +function getMaxDailyTotal(daily: CodexUsageDailyPoint[]): number { + let max = 1 + // Why: all-time usage histories can exceed V8's argument limit if spread + // into Math.max, even though the chart only renders the last 10 days. + for (const entry of daily) { + max = Math.max(max, entry.totalTokens) + } + return max +} + type CodexUsageDailyChartProps = { daily: CodexUsageDailyPoint[] } export function CodexUsageDailyChart({ daily }: CodexUsageDailyChartProps): React.JSX.Element { - const maxDailyTotal = Math.max(1, ...daily.map((entry) => entry.totalTokens)) + const maxDailyTotal = getMaxDailyTotal(daily) return (
diff --git a/src/renderer/src/components/stats/usage-daily-chart.test.tsx b/src/renderer/src/components/stats/usage-daily-chart.test.tsx new file mode 100644 index 00000000000..dc6bb5e600a --- /dev/null +++ b/src/renderer/src/components/stats/usage-daily-chart.test.tsx @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest' +import type { ClaudeUsageDailyPoint } from '../../../../shared/claude-usage-types' +import type { CodexUsageDailyPoint } from '../../../../shared/codex-usage-types' +import { ClaudeUsageDailyChart } from './ClaudeUsageDailyChart' +import { CodexUsageDailyChart } from './CodexUsageDailyChart' + +function makeDay(index: number): string { + return new Date(Date.UTC(2026, 0, 1 + index)).toISOString().slice(0, 10) +} + +describe('usage daily charts', () => { + it('renders Codex/OpenCode daily charts for very large histories', () => { + const daily: CodexUsageDailyPoint[] = Array.from({ length: 130_000 }, (_, index) => ({ + day: makeDay(index), + inputTokens: index + 1, + cachedInputTokens: 0, + outputTokens: 0, + reasoningOutputTokens: 0, + totalTokens: index + 1 + })) + + expect(() => CodexUsageDailyChart({ daily })).not.toThrow() + }) + + it('renders Claude daily charts for very large histories', () => { + const daily: ClaudeUsageDailyPoint[] = Array.from({ length: 130_000 }, (_, index) => ({ + day: makeDay(index), + inputTokens: index + 1, + outputTokens: 0, + cacheReadTokens: 0, + cacheWriteTokens: 0 + })) + + expect(() => ClaudeUsageDailyChart({ daily })).not.toThrow() + }) +})