[1134] feature: local-usage-stats (4/4) - #53
Closed
myk1yt wants to merge 116 commits into
Closed
Conversation
…cit-any Add new test file to eslint-suppressions.json with count of 26 no-explicit-any suppressions. These are standard test patterns (mock objects, private property access via 'as any') consistent with other test files in the suppressions list. Fixes CI lint failure in PR #25 compile (lint) job.
…cing - Remove UTF-8 BOM (U+FEFF) from costRecalculation.ts and costRecalculation.spec.ts - Fix qwenCodeModels pricing: qwen3-coder-plus inputPrice 0->1.0, outputPrice 0->5.0 - Fix qwenCodeModels pricing: qwen3-coder-flash inputPrice 0->0.3, outputPrice 0->1.5 Fixes invisible-chars CI check and 3 failing costRecalculation tests
…exactly-once recorder - UsageRecorder: per-task exactly-once usage event recording with endpoint domain extraction - costRecalculation: compute effective cost from token deltas and model pricing - Provider usage deltas: moonshot, openai, openai-codex, vscode-lm yield cumulative usage; Task diffs and records - Task finalization: flush pending usage events on abort/complete - ClineProvider: initialize UsageStatsService, expose getUsageStatsService, forward usageStatsChanged to webview - types: add usage-stats schemas and usageStatsChanged ExtensionMessage type
…proper types, fix run->start renames, add UsageEventStore import
The B15 usage-capture cherry-pick was authored against an older base and reverted newer upstream/base behavior in several files, causing e2e-mock subtask timeouts (7 tests) and unit-test failures. Restore clobbered base behavior while keeping B15's genuine usage/cost capture additions: - Task.ts: restore run() + _runPromise/_isHistoryTask, safeEnsureModelFetched (def + 3 call sites), abort-aware ask wait, resume_completed_task via initialStatus, and t() i18n in sayAndCreateMissingParamError. - ClineProvider.ts: scheduler gates on task.run() (completion promise) instead of fire-and-forget task.start(). This is the root cause of the subtask/resume e2e timeouts. - openai-codex.ts: restore service-tier feature alongside cost capture. - moonshot.ts, vscode-lm.ts, vscode-lm-format.ts, eslint-suppressions.json: revert to base (pure clobber, no genuine B15 content). - task-run-dispatch.spec.ts: bind run() (not start()). - openai-usage-tracking.spec.ts: assert totalCost from cost capture.
…StatsView, UsageAggregator
clearStats() only cleared the NDJSON store, so the dashboard kept showing cleared data from the SQLite projection. After the store clear, reset the stream generation via the coordinator (which pushes a reset snapshot to subscribers) or clear the database generation directly when no coordinator exists. Projection failures are logged, never thrown to the caller. Also replace two pre-existing 'as any' catalog doubles in the service spec with typed doubles (lint gate).
Both terminal finalize paths in Task (completed and failed/cancelled) built the UsageRecordingContext without rootTaskId even though the field exists on Task and is documented on the context, so sub-task usage was never grouped into the parent session. Pass this.rootTaskId at both sites.
The updateRollup fallback recorded uncached input as all-or-nothing (inputTokens when no cache reads, 0 otherwise), so events with cache reads left no base for the dashboard cacheRatio simulation. Compute the uncached input per event from its inclusion semantics — input minus cacheRead/cacheWrite when those are included in input (OpenAI-style), full input when excluded or unknown (Anthropic-style) — and thread it through every rollup path (live append, bulk append, v2/v3/rebuild). Also make upsertSession keep last_activity_ms monotonic (MAX of existing and new) so a backfilled older event no longer moves a session's last activity backward.
doInitialize() runs database.initialize() (whose v4 migration flips timezone_offset_minutes for rows already in SQLite) before the NDJSON migration copies legacy events verbatim with the old inverted sign, so pre-fix NDJSON events stayed wrong forever. Apply the same sign correction to migrated events. Post-fix events are unaffected: they were dual-written to SQLite by UsageEventStore and are skipped by INSERT OR IGNORE. Documented in code why no per-event discriminator exists.
…eam sink on webview cleanup
… labels from b14 merge
…ures, and detail The Dashboard preset/custom range flowed into the stats subscription but the Tasks section ignored it: pages came from the full History catalog, per-task totals were all-time (task_usage_metadata), and task details returned every event. - Add statsQueryRange module: single source for StatsQuery -> half-open [fromMs, toMs) bounds (presets via startOfDayInTimezone, custom from/to ISO, "all" unbounded); UsageStatsService.filterEventsByQuery now uses it too so export and task bounds cannot drift - DashboardTaskCatalog.getPage: optional range filters membership on HistoryItem.ts; totalEstimate becomes the filtered count; (ts DESC, id DESC) revision-tagged cursor semantics unchanged - UsageStatsDatabase: queryTaskUsageByTaskIds/queryEventsByTaskIds take an optional range; bounded aggregation reads usage_events with ms bounds and mirrors upsertTaskUsage semantics (cancelled included, getEffectiveCost, model/provider from the latest in-range event); unbounded keeps the metadata fast path - DashboardTaskProjection: computeTaskPage/computeTaskSummaries/ computeTaskDetail thread the range (membership by creation ts, figures and detail events by occurredAt) - UsageStatsStreamCoordinator: resolves the range per subscription for snapshot pages and drain upserts; new getSubscription(sink) lets the message handler align one-off task page/detail reads with the active stream subscription (unbounded fallback) - DashboardView: drop the range-bound task detail cache on preset/custom range change so expansions refetch against the new range
…task range resolution
…d height With only maxHeight set, the Virtuoso scroller's height:100% resolves against an auto-height parent, collapses to 0px, and deadlocks (zero viewport -> zero rendered items -> zero content height), so the Tasks header showed a count but no rows ever rendered. Drive an explicit height from totalListHeightChanged (capped at 400px) and bootstrap measurement with initialItemCount clamped to the task count (a larger fixed value crashes itemContent with undefined items). Adds Playwright CT regression tests (jsdom mocks Virtuoso and cannot catch this) and switches the dashboard i18n imports to the @src spelling so the CT harness can stub the TranslationContext.
Clicking the active range preset re-armed the resyncing banner without triggering a resubscription, so no snapshot ever arrived to clear it and the indicator spun forever (e.g. on double-click). Gate the banner on an actual preset change and clear it on the custom-range early return.
…ubtasks The Tasks list paged every History task, so subtasks appeared as sibling rows even though each parent row already aggregates its whole subtree (double-counted visually, detached from the summary cards). - Catalog pages root tasks only; bounded-range membership is subtree-based (a root is listed when the root or any descendant was created in range), orphans promote to roots. - DashboardTaskSummary gains childTaskIds; DashboardTaskPage gains childTasks carrying direct children of the page's roots. - Reducer keeps childTasks/subtask upserts out of the visible root order while storing them in the normalized map. - TaskList renders roots; expanding a root with subtasks shows an indented subtask list, and each subtask toggles its own API-call detail. Childless roots expand directly into their detail. - Adds Playwright CT coverage for the expand interaction (jsdom mocks react-virtuoso and cannot exercise it).
The root-only task page reads childrenByParentId for childTasks; the handler/routing specs' catalog stubs predated that index.
… coverage - Add coverage tests for UsageStatsService, UsageStatsStreamCoordinator, UsageStatsMigration, UsageStatsProjection, DashboardTaskProjection, UsageAggregator, costRecalculation, and UsageRecorder. - Add new safeWriteJson spec to cover rollback and failure paths. - Add dashboard webview tests for TaskList, AnimatedNumber, SessionDetail, dashboardStreamReducer, and useDashboardStatsStream edge cases. - Fix vscode mock so RelativePattern is constructible for file watcher tests.
- playwright-ct.config.ts: alias '@/i18n/TranslationContext' to the CT mock. DashboardSummary and UsageHeatmap import useAppTranslation via '@/i18n/...' which bypassed the existing '@src/i18n/...' alias, pulling the real TranslationContext -> ExtensionStateContext -> @roo-code/types barrel (zod) into the CT bundle and throwing 'ReferenceError: z is not defined' at mount. Fixes all 3 webview visual snapshot failures (bundle error, not pixel mismatch). - useDashboardStatsStream.spec.tsx: add coverage for the 10s loading timeout ERROR dispatch and the requestTaskPage no-cursor guard to close the patch-coverage shortfall.
…compatibility Root cause: DashboardSummary and UsageHeatmap components use StandardTooltip which requires a Radix TooltipProvider in the component tree. Playwright CT mounts components without the app's provider hierarchy, causing silent render failures (elements not found). Additionally, the ProviderBreakdownFixture was defined inline in the test file, which Playwright CT cannot mount (requires test story or external fixture). Fix: Extract all fixtures into StatsPanel.visual.fixture.tsx with proper TranslationContext.Provider and TooltipProvider wrappers. Run: https://github.com/Zoo-Code-Org/Zoo-Code/actions/runs/31237627413
…ov coverage gap - Move data-testid in ProviderBreakdownFixture from outer div to table (Playwright CT cannot find data-testid on root-level mount element) - Add CI-generated baseline screenshots for StatsPanel visual tests - Add 5 coverage tests for handleRebuildUsageStats and handleGetDashboardTaskDetail error branches (79.86% -> 80%+)
- mimo.spec.ts: replace manual 'any[]' stream-collection loop with the typed collectStream() helper. The merge in b4c997b introduced an extra 'any[]' that pushed the file to 19 no-explicit-any violations, exceeding the eslint-suppressions budget of 18 and failing the compile job. - UsageStatsMigration.spec.ts: reduce checkpoint-resume test from 2500 to 1200 events (still crosses the 1000-event batch boundary). The 2500-event run exceeded the 60s test timeout on windows-latest under parallel load; the per-event db.prepare() hot path makes SQLite writes the dominant cost.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack Position
feature/local-usage-statsDescription
https://www.youtube.com/shorts/UHnnOCM1_f0
Full Feature Description
feature/local-usage-statsusage-stats.ts,src/services/stats, the provider/task capture pathsTask.ts, the stats IPCusageStatsMessageHandler.ts, and the UIDashboardView.tsxanduseDashboardStatsStream.ts.Why Split Into 17 PRs
Instead of submitting this feature as a single unified PR, it was split into individual PRs because as code size grows, safely reviewing a PR becomes very difficult. The feature was broken into mutually exclusive individual PRs so that each can be reviewed independently.
What This PR Specifically Changes
Adds SQLite projection, transactional/idempotent migration, local-day rollup, rebuild/query/stream IPC, epoch guard, dashboard summary/session/heatmap/loading/retry UI, and visual coverage. Removes other feature files.
Included Files
src/services/stats/UsageStatsDatabase.tssrc/services/stats/UsageStatsMigration.tssrc/services/stats/UsageStatsProjection.tssrc/services/stats/UsageStatsStreamCoordinator.tssrc/core/webview/usageStatsMessageHandler.tswebview-ui/src/components/dashboard/DashboardView.tsxwebview-ui/src/components/dashboard/useDashboardStatsStream.tswebview-ui/src/components/dashboard/DashboardView.visual.tsxExclusion Scope
Upstream PR: Zoo-Code-Org#1134