mirror of
https://github.com/Routstr/routstrd.git
synced 2026-10-30 07:37:40 +00:00
The windowed aggregates and the History timeline used different range boundaries, so a tab's totals did not match the sum of its chart bars: - today: totals covered the calendar day since local midnight, while the chart drew a rolling, hour-aligned last 24 hours. - 7d/30d: totals were exact rolling (now - N*24h), while the chart snapped to whole local days, leaving up to 24h of drift at the left edge. Extract the chart's bucket alignment into windowRangeStartUtc() and derive windowAfter() from it, so both sides cover exactly the same range: 24 whole local hours for today, 7/30 whole local days ending today. This also makes the today window match its '24h' label in the TUI. Tests: rename the calendar-day test, add a 24h boundary test (yesterday's hours included, 25h ago excluded), and assert bars sum to window totals for the today/7d/30d timelines.
3.2 KiB
3.2 KiB
TUI "History" tab with adaptive bar charts
Status: implemented. Branch
feat/tui-history-charts(worktree.worktrees/tui-history-charts, based onmain@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),TimelinePointandTimelinetypes;WindowedStatsgains a requiredtimeline: Timeline. buildTimeline(driver, baseFilter, window, now, tzOffsetMinutes):today(24h) →aggregate({ groupBy: "hour" })over the last 24 local-aligned hours. The SDK'shourkey isHHwith 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 noafter, folded intoYYYY-MMmonths. The SDK has nomonthgrouping, hence the fold.- Every series is zero-filled so the x-axis is evenly spaced.
buildTimelineis called once per summary build (afterbuildAggregates) 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 toHistory;tabSupportsWindownow excludes only Overview and Recent.render.ts—renderTodayreplaced byrenderHistory+renderTimelineChart(vertical bars, y-axis gutter, x labels, token sparkline, legend). Long series are merged to fit narrow terminals.data.ts— the old-daemon fallbackwindowFromAllTimesupplies 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
timelinestill 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 theHHkeys stay unambiguous. - The windowed aggregates use the same bucket-aligned boundary as the chart
(
windowRangeStartUtc), so a tab's totals always equal the sum of its bars: 24 whole hours for24h, whole local days for7d/30d. Before this, the24htotals were calendar-day (since local midnight) while the chart drew a rolling 24h, and7d/30dtotals were exact rolling windows while the chart snapped to whole days.