Files
routstrd/docs/plans/tui-history-charts.md
redshift b6caa2afb3 feat(tui): replace Today tab with window-driven History charts
Rename the second usage-monitor tab from Today to History and draw usage
over time as a bar chart instead of the text lists and horizontal hourly
bars.

- daemon: add a zero-filled `timeline` series to the windowed summary,
  bucketed per window (hours for 24h, days for 7d/30d, months for all).
  Months are folded from daily aggregates since the SDK has no month
  grouping.
- tui: renderHistory draws a vertical spend chart with a y-axis, x labels
  and an independently scaled token sparkline plus legend; the [W]
  selector now drives the tab (it was hidden on it before).
- renderBox truncates overlong lines so a large number can't push the
  right border out.
- docs: SKILL.md wording + docs/plans/tui-history-charts.md.

Tests: daemon timeline buckets; chart axis/labels/sparkline/legend,
empty state, and full-width alignment.
2026-10-13 14:02:00 +08:00

64 lines
2.8 KiB
Markdown

# TUI "History" tab with adaptive bar charts
> **Status:** implemented. Branch `feat/tui-history-charts`
> (worktree `.worktrees/tui-history-charts`, based on `main` @ `5069b2d`).
## Goal
Replace the old **Today** tab (a totals box + a text "Recent Days" list + a
horizontal hourly bar list) with a **History** tab that draws usage over time
as bar charts, with the bucketing driven by the existing `[W]` time-window
selector.
| `[W]` window | Bucket | Bars |
| ------------ | ------ | ---- |
| `24h` | hour | 24 |
| `7d` | day | 7 |
| `30d` | day | 30 |
| `All` | month | one per calendar month since first usage |
Y-axis is total spend; a token sparkline shares the x-axis (scaled
independently to peak tokens, with both totals in the legend). This also makes
the tab window-aware: the selector was previously hidden on it.
## Backend — `src/daemon/http/usage-summary.ts`
- New `TimelineBucket` (`hour` | `day` | `month`), `TimelinePoint` and
`Timeline` types; `WindowedStats` gains a required `timeline: Timeline`.
- `buildTimeline(driver, baseFilter, window, now, tzOffsetMinutes)`:
- `today` (24h) → `aggregate({ groupBy: "hour" })` over the last 24
local-aligned hours. The SDK's `hour` key is `HH` with no date, so the
window is aligned to whole hours (each hour-of-day appears at most once).
- `7d` / `30d` → `aggregate({ groupBy: "day" })` over the last N days.
- `all` → `aggregate({ groupBy: "day" })` with no `after`, folded into
`YYYY-MM` months. The SDK has no `month` grouping, hence the fold.
- Every series is zero-filled so the x-axis is evenly spaced.
- `buildTimeline` is called once per summary build (after `buildAggregates`)
and attached to both the all-time and windowed blocks. The zero-data
short-circuit returns an empty timeline.
## TUI
- `types.ts` — `TabId` `"today"` → `"history"`; re-export the timeline types.
- `constants.ts` — tab renamed to `History`; `tabSupportsWindow` now excludes
only Overview and Recent.
- `render.ts` — `renderToday` replaced by `renderHistory` +
`renderTimelineChart` (vertical bars, y-axis gutter, x labels, token
sparkline, legend). Long series are merged to fit narrow terminals.
- `data.ts` — the old-daemon fallback `windowFromAllTime` supplies an empty
timeline.
## Tests
- `src/daemon/http/usage-summary.test.ts` — hourly / 7-day / 30-day /
month-folded timelines, including zero-fill and window totals.
- `src/tui/usage/render.test.ts` — axis, labels, sparkline, legend, empty
state, and full-width alignment of every rendered line.
## Notes / limits
- Daemons too old to return `timeline` still render: the History tab falls
back to an empty chart rather than erroring.
- The 24h window is hour-aligned rather than a rolling `now - 24h`, so each
hour-of-day appears once and the `HH` keys stay unambiguous.