* feat(dashboard): add granular usage timelines
* fix(dashboard): readable timeline legend and month-scale buckets
Session legend labels now use the real projectPath's last two segments
(app/web) instead of the sanitized project dir, which rendered as an
unreadable -Users-name-... dump and truncated in the legend.
Hourly buckets now cap at 8 days; longer ranges bucket daily. A 30-day
window rendered 720 overlapping hourly spikes, unreadable as a chart.
The browser no longer gives the backend session_other/model_other
aggregate a top-N slot, which rendered two identical Other legend
entries whenever the backend fold out-ranked a real series.
---------
Co-authored-by: AgentSeal <hello@agentseal.org>
* fix(web): reject invalid dashboard periods without exiting
* test(web): assert invalid periods return 400 without exiting; drop redundant /api/devices re-parse
- Add tests/web-dashboard.test.ts: boots the dashboard on an ephemeral port and asserts
/api/usage and /api/devices answer 400 (not process.exit) for a bad period, and that
the server keeps serving afterward. runWebDashboard now returns the http.Server so it
can be exercised in-process; callers that ignore the return value are unaffected.
- /api/devices: resolve periodInfo once instead of validating then re-parsing it inside
localGetUsage (pullDevices invokes localGetUsage with the same already-validated query).
---------
Co-authored-by: AgentSeal <hello@agentseal.org>