feat(dashboard): fixed-stage layout, activity pulse chart and tile navigation - #8
Merged
Merged
Conversation
…vigation The dashboard was a long scrolling column. An operational view is something you glance at, so anything below the fold was something nobody would read. It is now a fixed stage that never scrolls. Panels that can grow without limit — the feed table, the activity list — scroll inside themselves, and an end-to-end test asserts the page itself has no scrollable overflow so the property cannot quietly regress. The height came from two places. A line of descriptive prose under the heading became the dashboard's own bar, which carries the section nav, the window filter, the last-updated time and the live indicator in the row the sentence occupied. And the panels split into four sections — overview, feeds, clients, traffic — reached from that bar. Navigation is also the numbers themselves: each overview tile is a button that opens the section explaining it, so a reader who wants the detail behind a figure clicks the figure instead of hunting for the right tab. The activity pulse is the new visualisation: requests and errors over the window, bucketed in SQL rather than by reading rows and counting them in JavaScript, because after a load test a single window holds tens of thousands of rows. Two series, so a legend is always present and identity never rests on colour alone; errors take the reserved status colour rather than a third categorical hue. Hovering gives a crosshair snapped to the nearest bucket and one tooltip listing every series, and every value it shows is also in a table rendered for screen readers, so the interaction enhances rather than gates. The series colours were computed rather than chosen: run through a palette validator against each theme's panel surface, giving colourblind separation of 23.5 dark and 29.8 light against a target of 8. The first dark pair failed the lightness band for its surface and was re-stepped. Four bugs found by looking at the running page and re-measuring, not by reasoning about the change: - Bucketing silently produced an empty chart. SQLite binds a JavaScript number as REAL, so dividing by a bound parameter gave floating-point bucket ids that matched no integer bucket. It needed an explicit CAST. - The series was anchored on the start of the window, which left the newest traffic one bucket past the end of the axis — the counters moved while the chart appeared not to. It anchors on the current bucket now. - The chart collapsed to a strip at the top of an empty panel: it asks for the full height of its container, and a block parent with auto height resolves that against its content. - The feed status table pushed its state column past the panel edge, where it read as a truncated word rather than as something scrollable. And two regressions caught by re-running Lighthouse rather than assuming a redesign is neutral: - label-content-name-mismatch was failing again, on the new KPI tiles. Each tile carried an aria-label to say where it navigates, and aria-label replaces the accessible name rather than adding to it, so the tile's detail line was visible on screen and absent from the name. This is the same defect already fixed twice here. The hint is now visually hidden text inside the button, which is additive. - Performance fell 97 to 86 with 360 ms of blocking. The server answered in 130 ms against 62,000 rows; the cost was toLocaleTimeString, which builds a fresh Intl formatter per call, across about a hundred timestamps, every ten seconds. Two formatters constructed once in lib/format.ts took blocking time to 20 ms and the score to 96. Verified: 12 end-to-end tests pass, Lighthouse reports accessibility 100 and performance 96 on the dashboard with no label/name violations, and all four sections were checked by hand in both themes. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds the dashboard around three asks: no vertical scrolling, a second bar holding the controls, and real visualisations with widgets that navigate.
Layout
The activity pulse
Requests and errors over the window, 48 buckets computed in SQL (a window can hold tens of thousands of rows after a load test). Crosshair snaps to the nearest bucket; one tooltip lists every series; a screen-reader table carries the same values so the hover layer enhances rather than gates.
Series colours were computed, not picked — validated against each theme's panel surface: CVD separation ΔE 23.5 dark / 29.8 light (target 8). The first dark pair failed the lightness band and was re-stepped.
Bugs found by running it, not reasoning about it
Two regressions caught by re-measuring
label-content-name-mismatchfailed on the new KPI tiles: anaria-labelreplaces the accessible name, so the tiles' visible detail line vanished from it. Now visually-hidden text inside the button, which is additive.toLocaleTimeStringbuilds a fresh Intl formatter per call, ~100 timestamps, every 10s. Cached inlib/format.ts: blocking 360 ms → 20 ms, score → 96.Verified
12/12 e2e passing · dashboard a11y 100, perf 96, no label/name violations · all four sections checked by hand in light and dark.
🤖 Generated with Claude Code