Skip to content

feat(web): a usage window shorter than a day, and an axis fine enough to draw it (AGT-4296) - #615

Merged
unohee merged 1 commit into
mainfrom
feat/usage-hour-windows
Sep 10, 2026
Merged

feat(web): a usage window shorter than a day, and an axis fine enough to draw it (AGT-4296)#615
unohee merged 1 commit into
mainfrom
feat/usage-hour-windows

Conversation

@unohee

@unohee unohee commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

TL;DR

The dashboard's shortest window was 24h, and a 24h total is dominated by whatever ran before the last deploy. Measured on vela the morning after the durable draft cache shipped:

window draft-stage prompt cache
last 24h 34.1%
last 2h 66.8%

Only the second number described the code actually running. The first said the fix had not landed.

What was missing

parseUsageSince already accepted durations like 2h — the selector just never offered one. The real gap was somewhere to draw it: groupKeyOf bottomed out at day, so a one-hour window on the time axis is a single bar, which is not a series.

  • by=hour (record.ts.slice(0, 13)), advertised in --help and the README alongside the other axes
  • 1h / 2h / 6h in the window selector
  • The axis is chosen from the window — hour up to 48h, day beyond, because thirty days by hour is 720 bars, a texture rather than a reading
  • Hour buckets render on the reader's clock. Day keys deliberately do NOT convert: shifting one by the UTC offset relabels the day, and that alignment belongs to AGT-4293
  • The card heading follows the axis actually drawn. Left fixed it read 일별 over labels like 9. 10. 23시 — on first load for every reader, since the default window is 24h

Two test properties worth naming

process.env.TZ is pinned in the client test file. Local-time rendering is invisible where UTC and local coincide, and CI runners are UTC — the mutant that skips the conversion entirely passed there while failing on a laptop in KST. Only the non-zero offset is load-bearing, not Seoul specifically. The suite is run both ways below.

usage.css has a tripwire asserting every var(--…) it references exists in tokens.css. Three undefined names shipped into that file during this work (--surface-2, --text-1, --text-2; the real names are --surface2, --fg-primary, --fg-secondary). An undefined custom property is not an error — the declaration is dropped and the rule renders with an inherited colour, so it looks plausible, and neither the build, nor oxlint, nor jsdom sees it.

Why this is half a change

Cross-axis filters and the drill-down panel were split out to AGT-4297. Three review rounds on the combined change all returned REVISE, with the same two classes recurring — a fix trading one defect for another, and a fix re-breakable with a green suite. Every finding in round 3 was in the drill-down UI; this half drew none across all three rounds. The commit gate treats a repeating finding as a signal the change is too large.

The split line is drawn so each PR is a vertical slice: filters ship with their consumer in AGT-4297, rather than landing as an API nobody calls.

Review

Layer 2 (independent subagent), three rounds. Layer 1 (openswarm review) is not run on my own commits per standing policy.

Mutants against this half:

mutant tests killed
time axis always day 4
no local-time conversion 2
card heading fixed 2
hour key equal to day key 1
short windows removed 1

Gate

tsc --noEmit exit 0 · npm run build exit 0 · full suite 5984 passed / 10 skipped, and the same under TZ=UTC · oxlint 0 warnings on the changed files.

Closes AGT-4296.

🤖 Generated with Claude Code

… to draw it (AGT-4296)

The dashboard's shortest window was 24h, and a 24h total is dominated by
whatever ran before the last deploy. Measured on vela the morning after
the durable draft cache shipped: the draft stage read 34.1% prompt-cache
over 24h and 66.8% over the last two hours. Only the second number
described the code that was actually running — the first said the fix had
not landed.

`parseUsageSince` already accepted durations like `2h`; the selector just
never offered one. What was missing was somewhere to draw it. `groupKeyOf`
bottomed out at `day`, so a one-hour window rendered on the time axis is a
single bar, which is not a series.

So: a `by=hour` axis (`record.ts.slice(0, 13)`), 1h/2h/6h in the selector,
and the axis chosen from the window — hour up to 48h, day beyond, because
thirty days by hour is 720 bars, which is a texture rather than a reading.

Hour keys stay UTC on the wire, like day keys, and are rendered on the
reader's clock. Day keys are deliberately NOT converted: shifting one by
the UTC offset relabels the day, and that alignment is AGT-4293's
business. The card heading follows the axis that was actually drawn — left
fixed it read 일별 over labels like `9. 10. 23시`, on first load for every
reader, because the default window is 24h.

Two test properties worth naming, both learned the hard way here:

`process.env.TZ` is pinned in the client test file. Local-time rendering
is invisible where UTC and local coincide, and CI runners are UTC — the
mutant that skips the conversion entirely passed there while failing on a
laptop in KST. Only the non-zero offset is load-bearing, not Seoul.

`usage.css` now has a tripwire asserting every `var(--…)` it references is
defined in `tokens.css`. Three undefined names shipped into that file
during this work (`--surface-2`, `--text-1`, `--text-2`; the real names
are `--surface2`, `--fg-primary`, `--fg-secondary`). An undefined custom
property is not an error — the declaration is dropped and the rule renders
with an inherited colour, so it looks plausible, and neither the build,
nor oxlint, nor jsdom sees it.

Cross-axis filters and the drill-down panel were split out to AGT-4297.
Three review rounds all returned REVISE with the same two classes
recurring — a fix trading one defect for another, and a fix re-breakable
with a green suite — and every finding in the last round was in the
drill-down UI while this half drew none. Per the commit gate, a repeating
finding means the change is too large.

Review: layer 2 (independent subagent), three rounds on the combined
change; this half accumulated no findings across all three. Mutants:
always-day axis kills 4 tests, no local-time conversion 2, fixed heading
2, hour key equal to day key 1, short windows removed 1.

tsc --noEmit exit 0 · build exit 0 · full suite 5984 passed / 10 skipped,
and the same under TZ=UTC · oxlint 0 warnings on the changed files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@unohee
unohee merged commit e227b19 into main Sep 10, 2026
7 checks passed
@unohee
unohee deleted the feat/usage-hour-windows branch September 10, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant