Commit Graph

1 Commits

  • feat(usage): refine usage dashboard UI and date range picker (#2002)
    * feat(usage): enhance usage stats backend and query hooks
    
    * feat(usage): redesign calendar date range picker with auto-switch and simplified layout
    
    * refactor(usage): streamline dashboard layout and stats components
    
    * refactor(usage): compact request log table with merged cache/multiplier columns and centered layout
    
    * feat(i18n): add cache short labels and usage stats translations for zh/en/ja
    
    * Align usage dashboard stats with range boundaries
    
    The usage dashboard mixed second-precision detail rows with day-level rollups, which caused custom half-day ranges to overcount historical rollup data and left the request log paginator on stale pages after top-level filter changes.
    
    This change limits rollups to fully covered local days, aligns multi-day trend buckets with natural local days, and resets request log pagination when the dashboard range or app filter changes.
    
    Constraint: usage_daily_rollups stores only daily aggregates after pruning old detail rows
    Rejected: Include partial boundary rollups proportionally | historical intra-day detail is unavailable after pruning
    Rejected: Force RequestLogTable remount on range change | would discard local draft filters unnecessarily
    Confidence: high
    Scope-risk: moderate
    Reversibility: clean
    Directive: Keep summary, trends, provider stats, and model stats on the same rollup-boundary rules
    Tested: cargo test --manifest-path src-tauri/Cargo.toml usage_stats
    Tested: pnpm exec vitest run tests/components/RequestLogTable.test.tsx
    Tested: pnpm typecheck
    Not-tested: Manual UI validation in the Tauri app
    
    * Preserve full-day usage filters at minute precision
    
    The latest review surfaced two interaction bugs in the usage dashboard: rollup-backed stats undercounted end days selected via the minute-precision picker, and immediate select changes accidentally applied unsubmitted text drafts from the request log filters.
    
    This change treats 23:59 as a fully selected local end day for rollup inclusion and narrows select-side state syncing so app/status updates do not commit provider/model drafts.
    
    Constraint: The custom range picker emits minute-precision timestamps, while rollups are stored at day granularity
    Rejected: Require exact 23:59:59 end timestamps | unreachable from the current picker UI
    Rejected: Rebuild applied filters from the full draft state on select changes | silently commits unsaved text input
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: Keep request-log text fields on explicit apply semantics even when select filters remain immediate
    Tested: cargo test --manifest-path src-tauri/Cargo.toml usage_stats
    Tested: pnpm exec vitest run tests/components/RequestLogTable.test.tsx
    Tested: pnpm typecheck
    Not-tested: Manual Tauri dashboard interaction
    
    * refactor(usage): move range presets into date picker, single-row layout
    
    - UsageDateRangePicker: add preset shortcuts (今天/1d/7d/14d/30d) inside
      popover top; clicking a preset applies immediately and closes popover
    - UsageDashboard: collapse to single row (app filters + refresh + picker);
      remove standalone preset buttons and summary stats bar
    - RequestLogTable: replace static Calendar badge with interactive
      UsageDateRangePicker via onRangeChange prop; single filter row
    
    * Keep usage pagination regression coverage aligned with the rendered UI
    
    The new regression test was asserting a non-existent pagination label and page summary text, so it failed before it could verify the real page-reset behavior. This commit switches the assertions to the numbered pagination buttons that the component actually renders and validates the reset through the query hook arguments.
    
    Constraint: RequestLogTable exposes numbered pagination buttons, not a "Next page" label or "2 / 6" summary text
    Rejected: Add synthetic pagination labels solely for the test | would couple production markup to a test-only assumption
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: Prefer pagination assertions that follow the rendered controls or hook inputs instead of invented summary text
    Tested: pnpm vitest run tests/components/RequestLogTable.test.tsx; pnpm typecheck; pnpm test:unit
    
    * refactor(usage): clean up dead code and polish date range picker
    
    - Remove unused exports MAX_CUSTOM_USAGE_RANGE_SECONDS,
      timestampToLocalDatetime, and localDatetimeToTimestamp from
      usageRange.ts (replaced by the calendar picker)
    - Deduplicate getPresetLabel from UsageDashboard and
      UsageDateRangePicker into shared getUsageRangePresetLabel helper
    - Add aria-label, aria-current and aria-pressed to calendar day
      buttons so screen readers can disambiguate same-numbered days
      across adjacent months
    - Drop unused cacheReadShort and cacheWriteShort i18n keys (zh/en/ja);
      the request log table renders R/W prefixes inline
    - Align customRangeHint copy with the removed 30-day limit by
      dropping "up to 30 days" wording (zh/en/ja)
    
    * fix(usage): align rollup cutoff to local midnight to keep days complete
    
    `rollup_and_prune` previously used `Utc::now() - retain_days * 86400`
    as the cutoff. Because rollups are bucketed by *local* date and detail
    rows below the cutoff are pruned, an unaligned cutoff left the youngest
    rolled-up day half-rolled-up and half-pruned. Combined with the new
    `compute_rollup_date_bounds` boundary trimming (which excludes any
    rollup day not fully covered by the requested range), custom range
    queries that touch that day silently under-count summary, trend,
    provider, and model stats.
    
    Fix the invariant at the source: snap the cutoff to the next local
    midnight after `(now - retain_days)`. Every rollup row now reflects a
    complete local day, so the boundary trimmer's all-or-nothing assumption
    holds.
    
    Includes unit tests for the cutoff math (typical case + already-on-
    midnight case). DST gap is handled defensively by bumping forward by
    an hour.
    
    Addresses Codex P2 review finding on PR #2002.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>