feat(status-bar): add Time Trends & Day Trends charts to the Claude usage popover#8406
feat(status-bar): add Time Trends & Day Trends charts to the Claude usage popover#8406jellychoco wants to merge 2 commits into
Conversation
… popover Add a chart panel to the right side of the status-bar Claude usage popover, porting the "Time Trends" hour-of-day overlay and daily trend charts from UsageScope. The popover previously showed only rate-limit bars (Session/Weekly/Fable); it now also charts locally scanned token usage history so users can see when and how much they use Claude. User-visible behavior: - Time Trends: hour-of-day overlay line chart (X = 00-23h). One line per day, today emphasized at 2px full opacity, historical days at 1px with per-window opacity (1D 0.9 / 7D 0.38 / 30D 0.2 / 6M 0.08) so overlap density reads as "hours I use most". Hover shows a per-hour tooltip: Today / Avg (active days) / Peak. - Day Trends: total-token bars per day (7D/30D) or per month (6M), hover tooltip with Total/Input/Output/Cache read/Cache write. A leading partial month is trimmed from the 6M view so month totals compare honestly. - Mode (Time/Day) and range (1D/7D/30D/6M) persist via localStorage. - Charts fill the panel height via ResizeObserver-driven SVG sizing; gridline density increases (2 -> 4 lines) on plots >= 200px tall. - Usage tracking stays opt-in: the panel shows an enable CTA until claude-usage scanning is turned on; data never leaves the device. - The panel is hidden while a remote runtime environment is active: the popover then shows the remote account (stablyai#7973) and pairing it with local-disk charts would misattribute the data. Data pipeline (main process): - claude-usage scanner now aggregates hour-of-day buckets (day x hour, project/model-free key to keep the cache small) alongside daily aggregates, merged across files with the same dedupe/ownership semantics (stablyai#8006 fork reclaim unaffected). - SCHEMA_VERSION 5 -> 6: old caches lack per-file hourly projections, so they are dropped and fully reparsed on next refresh; scanState.enabled is preserved across the bump (existing behavior). - New query getHourly(days): serves hourly points clamped to HOURLY_RETENTION_DAYS = 185 (6M window + slack) via new IPC channel claudeUsage:getHourly, exposed on window.api.claudeUsage. Per-file changes: | File | Change | | --- | --- | | src/main/claude-usage/types.ts | ClaudeUsageHourlyAggregate; hourlyAggregates on persisted state/file; hour on attributed turn | | src/main/claude-usage/scanner.ts | local day+hour bucketing, hourly aggregation/merge/sort, hourly in per-file reuse check and scan result | | src/main/claude-usage/store.ts | SCHEMA_VERSION 5->6, HOURLY_RETENTION_DAYS=185, getHourly/buildHourly, getDayCutoff extracted from getRangeCutoff | | src/main/ipc/claude-usage.ts | claudeUsage:getHourly handler | | src/preload/index.ts, api-types.ts | claudeUsage.getHourly bridge + ClaudeUsageHourlyResult typing | | src/shared/claude-usage-types.ts | ClaudeUsageHourlyPoint, ClaudeUsageHourlyResult DTOs | | src/renderer/.../ClaudeUsageTrendsPanel.tsx | new: panel shell, mode/range segmented controls, fetch + enable/error/empty/loading states | | src/renderer/.../claude-usage-trends-model.ts | new: pure model — hour-of-day matrix, day/month buckets, monotone-cubic path builder, tooltip stats | | src/renderer/.../claude-usage-trend-charts.tsx | new: TimeTrendsChart + DayTrendsChart SVG rendering and hover hit-testing | | src/renderer/.../claude-usage-trend-chart-frame.tsx | new: shared chart scaffolding — dims hook (ResizeObserver), Y axis, tooltip surface | | src/renderer/.../StatusBar.tsx | ProviderDetailsMenu sideContent prop (260px -> 588px two-column layout when present); Claude menu passes the panel, gated off remote runtimes | | src/main/claude-usage/*.test.ts, .../claude-usage-trends-model.test.ts | 13 new tests: hourly aggregation, window/185d clamp, day/month bucketing, partial-month trim, monotone path, tooltip stats | | src/renderer/src/i18n/locales/*.json | 28 new auto.* keys via pnpm run sync:localization-catalog | Design decisions: - Hourly aggregates are account-wide (no scope/model/project dimension) because the popover complements account-level rate limits; persisted size stays <= 24 rows per active day per file. - Retention is enforced at query time (matching the store's existing "no pruning, filter at query" convention); 185 days is the ceiling the API will serve regardless of requested window. - Charts are hand-rolled SVG with --chart-3 tokens (no chart library, matching the existing stats-pane convention of hand-rolled charts). Known limitation: controls inside the Radix DropdownMenu popover are not keyboard-reachable (Radix menus block Tab and rove only across menu items). This matches the pre-existing AccountRuntimeToggle in the same popover; usage tracking can also be enabled from Settings > Stats & Usage, which remains keyboard-accessible. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (7)
📝 WalkthroughWalkthroughAdds hourly Claude usage attribution, aggregation, persistence, retention-limited retrieval, and IPC/preload APIs. Introduces renderer models and SVG charts for hourly, daily, and monthly trends, with local preferences, loading and tracking states, tooltips, and localized labels. Integrates the trends panel into the Claude status-bar menu when no remote runtime is active. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/renderer/src/components/status-bar/ClaudeUsageTrendsPanel.tsx (2)
89-115: 🩺 Stability & Availability | 🔵 TrivialNo timeout on the
getHourlyIPC round trip.If the main-process scan (or the underlying
refresh()it triggers) stalls, the "Scanning local transcripts…" state can persist indefinitely with no fallback for the user. Consider wrapping this call with a bounded timeout so a stuck scan surfaces theloadFailedstate instead of hanging silently.
41-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the custom segmented control with the existing
ToggleGroupprimitive.src/renderer/src/components/ui/toggle-group.tsxalready exists, so reusing it keeps keyboard/focus behavior and styling aligned with the rest of the UI.Source: Path instructions
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0f4944b0-ddc4-4109-b39b-1e7010bea54b
📒 Files selected for processing (20)
src/main/claude-usage/scanner.test.tssrc/main/claude-usage/scanner.tssrc/main/claude-usage/store.test.tssrc/main/claude-usage/store.tssrc/main/claude-usage/types.tssrc/main/ipc/claude-usage.tssrc/preload/api-types.tssrc/preload/index.tssrc/renderer/src/components/status-bar/ClaudeUsageTrendsPanel.tsxsrc/renderer/src/components/status-bar/StatusBar.tsxsrc/renderer/src/components/status-bar/claude-usage-trend-chart-frame.tsxsrc/renderer/src/components/status-bar/claude-usage-trend-charts.tsxsrc/renderer/src/components/status-bar/claude-usage-trends-model.test.tssrc/renderer/src/components/status-bar/claude-usage-trends-model.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/shared/claude-usage-types.ts
Review: stablyai#8406 (CodeRabbit, 3 actionable + 2 nitpicks) Per-file changes: | File | Change | | --- | --- | | claude-usage-trend-charts.tsx | date axis/tooltip labels now use the active UI locale (i18n.language) instead of the OS locale (toLocaleDateString undefined -> i18n.language) | | ClaudeUsageTrendsPanel.tsx | SegmentedControl reimplemented on the ui/toggle-group shadcn primitive (guarding Radix's empty-value emit on re-click); Enable button disabled while loading to prevent overlapping setEnabled/getHourly calls; mode toggle gets its own aria-label 'Chart mode' (new key 53ce9bcf71) instead of duplicating the region label | | i18n/locales/*.json | 'Chart mode' key synced across 5 catalogs | Declined (with rationale in PR thread): client-side timeout on the getHourly IPC — first scans of large transcript histories legitimately run long and the IPC resolves when the scan completes; a fixed timeout would misreport in-progress scans as failures, and scan errors already surface via scanState.lastScanError. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed the CodeRabbit review in bd711c6:
Declining one nitpick with rationale — client-side timeout on the Re-ran |
What
Adds a chart panel to the right side of the status-bar Claude usage popover. The popover previously showed only the account rate-limit bars (Session / Weekly / Fable); it now also visualizes locally scanned Claude Code token usage:
ResizeObserver-driven SVG sizing and densify gridlines on tall plots.The charts are a port of the Time Trends / daily trend visualizations from my UsageScope app, adapted to Orca's per-provider popover (Claude-only series,
--chart-3token).Screenshots
How it works
src/main/claude-usagescanner now also aggregates hour-of-day buckets (day × hour, project/model-free so the persisted cache stays ≤ 24 rows per active day per file), with the same dedupe/fork-ownership semantics as daily aggregates ([Bug]: The stats and Usage #8006 unaffected).SCHEMA_VERSION5 → 6: old caches are dropped and reparsed on next refresh;scanState.enabledis preserved (existing bump behavior).claudeUsage:getHourlyIPC serves hourly points clamped to a 185-day ceiling (6M window + slack), following the store's existing query-time-filtering convention.ProviderDetailsMenugains an opt-insideContentprop (popover widens 260px → 588px into a two-column layout). Only the Claude menu uses it; Codex/Gemini/other provider popovers are untouched.Tests
13 new tests: hourly aggregation across projects/hours (scanner), window filtering + 185-day clamp (store), day/month bucketing, partial-month trim, zero-fill, monotone-cubic path edge cases, and tooltip stats (renderer model). All model logic lives in a pure
.tsmodule so the tests exercise the real chart math.AI code review summary (Claude)
An adversarial multi-agent review (3 reviewer dimensions, each finding independently re-verified against the code) ran on this diff:
Datemath consistent with the existing scanner; no keyboard shortcuts, no path handling beyond the existing scanner roots. Tests avoid timezone-dependent assertions.stat()on rescans. The schema bump costs one full reparse on first refresh (same as previous bumps).--chart-3,border,muted,popover, text tokens); works in light/dark; hover tooltips on both charts; loading/error/empty/consent states all render distinctly.Known limitation (pre-existing pattern): controls inside the Radix
DropdownMenupopover are not keyboard-reachable (Radix menus block Tab and rove only across menu items) — same as the existingAccountRuntimeTogglein this popover. Usage tracking can also be enabled from Settings → Stats & Usage, which is keyboard-accessible.Notes
pnpm run sync:localization-catalog; es/ja/ko/zh currently carry the English source values per the catalog bootstrap flow.pnpm lint,pnpm typecheck,pnpm test, andpnpm build:desktop(native build unchanged by this PR). The two full-suite test failures on my machine are pre-existing environment issues (missing node-pty spawn-helper binary; a locale-dependent assertion inautomation-schedules.test.tson a Korean-locale system) and reproduce on a clean checkout.X: @Command_CV
🤖 Generated with Claude Code