feat(web): console-style redesign on shadcn-svelte components#136
Note
🤖 GPT-6.1-Sol on behalf of Oliver
Why
Oliver asked for a more modern dashboard built from familiar shared components, and picked option A ("Console", in the Vercel/Geist style) from three mockups. This PR stacks on #135. It restyles the app on shadcn-svelte (bits-ui) primitives and removes the hand-rolled rail controls. Chart rendering is unchanged apart from theme tokens.
What changed
Shell. The top bar now reads Logo / dataset switcher (a Popover with a searchable Command list), then a ⌘K command menu and the theme toggle on the right. Below it is a row of underline tabs: Dashboard, Files, Alerts. On the home page the tabs are Datasets and Files.
One filter toolbar instead of the left rail. The toolbar holds:
- Date range: a Popover with a Range Calendar, presets, and typed
YYYY-MM-DDfields. The presets are Last day, Last 7 days, Last 30 days and All available. - Sources: a Command checklist with search and select all/clear.
- Interval and Direction: Selects.
- MAAD: a Popover with measure and address-family segmented toggles.
- Reset.
Each control shows its current value in muted text and wraps at narrow widths.
- Date range: a Popover with a Range Calendar, presets, and typed
KPI row. Four cards show totals for the selected window: Flows (with TCP share), Packets (per flow), Bytes (per packet), and Complete buckets (with partial/unknown counts). They're computed from the
/api/netflow/statswindow the traffic card already uses. No new query and no "vs prior" deltas. Unique-IP counts aren't summed.Chart cards. Each header has the drag handle and title on the left. The right side holds segmented ToggleGroups, a Metrics/Ranges checklist popover, and a
⋯DropdownMenu (Move up/down, Taller/Shorter). These replace the old title-bar buttons and keep drag reorder and native resize. Cards stay in the packed single column.Visual system. Neutral Geist-like greys, 1px borders, an 8px radius, a small card shadow and tabular numbers. Blue
#2563ebis used only for selection, focus and data. Light and dark themes both got full token sets, and the chart grid/axis tokens were retuned for the new surfaces.Other pages use the same shell:
- Home: a datasets table.
- Files: a form with a NativeSelect that keeps
?datasetin sync with the top bar. - File detail: a header, inline MAAD toggles, and per-source summary tables with badges.
- Alerts: segmented filters, a Live/Idle badge, Table and Empty states.
Removed: AnalysisLayout, FilterSheet, ControlPopover, SegmentedControl, CardLayoutControls, DragGrip, PrimaryFilters, RouterFilter, DateRangeFilter, MetricSelector, Direction/MAAD filters and DatasetTabs. Filter and metric semantics still live in the existing modules (
schemas.ts,chart-utils.ts, thetypes.tsoptions).
Logic and accessibility review
GPT-6.1-Sol reviewed the full diff against feat/redesign and hardened the following paths:
- Reversed traffic windows are rejected before the window cache can normalize them. Cached filter changes hide the previous request's totals until the selected request settles.
- The KPI sums additive API fields and counts the API's coverage states. Complete buckets with zero-valued data still count as complete. Unknown coverage is labeled "unknown", rather than "missing".
- Card menus use bits-ui's normal Tab and Shift+Tab handling. Their visible order matches their DOM order at narrow widths. The persistent card slot still restores focus after moving a card, including an unmounted placeholder that activates during the move.
- Lazy source and dataset lists focus their search only while focus remains on the opening popover. They preserve focus if the user has already interacted with another control. The dataset popover has the accessible name "Choose dataset" and both lists announce loading.
- The unused app-wide Tooltip provider was removed. Dashboard initial JS is now 201.6 KiB gzip, compared with 202.0 KiB before review and 182.7 KiB on the base branch. The remaining bits-ui cost was left in place to preserve the requested components.
The shared page-load stats request and date-preset decisions are retained. Immediate KPIs need that request, and activating the Traffic card reuses it. Selected-end-date presets support historical captures. "All available" keeps the existing configured dataset-start-to-today window; dataset summaries currently provide no last-available date.
Regression coverage in console-accessibility.spec.ts and console-window.spec.ts checks menu Tab order, slow list loading, dialog naming and cancellation, focus after placeholder activation, reversed windows and cached-window totals. Keyboard command-menu focus was also checked at 390px and 1280px using the fixture dataset. No deployment was changed during the review.
Design screenshots (Playwright fixture data, viewport captures)
Each sheet shows Before (#135) next to the console design before the logic review, in light and dark. The review moves the narrow card menu after its controls so visual order matches keyboard order.
Human review guide
Setup: bun install, then bun run dev:web against any dataset. Alternatively, bun run test:e2e seeds the Playwright fixture (playwright and playwright-external-maad). The perf stage on :8090 runs the initial console design on uoregon-v5 (read-only). The review commits have not been redeployed there.
Flows to try
- Dashboard toolbar:
- Pick a preset, then a calendar range, then type an invalid date such as
2025-02-31(the field is flagged and the URL is unchanged). - Use the Sources search with Enter to toggle, then Clear and Select all.
- Change the Interval, Direction and MAAD options. Reload and use back/forward; the URL state should survive.
- Reset.
- Pick a preset, then a calendar range, then type an invalid date such as
- KPI row: change the window or sources, and compare the numbers with the traffic chart tooltip totals.
⋯menu on several cards: move up/down and taller/shorter by keyboard. Focus should return to the menu trigger after a move or resize, and the order should persist across reloads. Tab and Shift+Tab should leave the menu normally, in the same order as the visible controls. Drag reorder and the native resize corner should still work.- Dataset switcher and ⌘K: switch datasets from the dashboard and from alerts (you stay on the same tab), jump to Files, and toggle the theme.
- Files → open
202503010200→ file detail: change the MAAD toggles and use Next File. - Alerts: change tail, horizon and sort, use show more, and check the copy-command empty state.
Edge cases
- Reversed dates show the alert inside the toolbar.
- With no sources selected or reversed dates, the KPIs read "Unavailable". Reversed windows do not issue a stats request.
- A deferred (not yet loaded) card still has a working
⋯menu, and moving a card mounts it. - Coarse pointers get 44px toolbar and menu targets.
- At 320px the header fits; the dataset name truncates.
Decisions to review
- The KPI row requests the stats window up front, even when the traffic card is deferred or ordered later. It's still exactly one request, shared with the card (
netflow-stats-data.svelte.ts, asserted indashboard-lazy-charts). - Window presets end on the selected end date ("Last 7 days" = end−6 … end), because the data is date-granular and may be historical. "All available" spans dataset start … today.
- Interval and Direction are Selects rather than segmented controls. The MAAD options are in a popover on the dashboard but inline on file detail.
- Card titles now match the visible chart titles ("Unique IP Counts", "Unique Protocol Counts", "Spectrum").
- The command palette, the dataset/source command lists and the range calendar load on first open to keep route JS down.
Verification
Automated (all passing)
bun run format,bun run lint(eslint, infra and clippy) andbun run typecheck(svelte-check: 0 errors, 0 warnings).- CI run 37908423730 passed Web Checks, Rust Pipeline Checks and Playwright E2E on 3962184.
bun run test:web: 246 tests. New:tests/lib/console-ui.test.tscovers date presets and validation, window totals and shell routing. The stats route test also covers exclusive end bounds, complete zero-valued buckets, partial observations and unknown coverage.bun run test:e2erun serially (--workers=1) against the production build: 110 passed.- New
console-ui.spec.tscovers:- KPIs matched to the
/api/netflow/statsresponse; - presets, calendar and typed fields;
- Sources search;
- the
⋯menu move and resize with focus retained; - packed panels;
- placeholder menus;
- the dataset switcher and ⌘K;
- toolbar wrapping;
- coarse-pointer targets;
- the file layout.
- KPIs matched to the
- The existing specs were updated to the new locators through
tests/e2e/toolbar-helpers.ts.redesign.spec.ts(which tested the old rail and sheet) was replaced.
- New
bun run perf:bundlefor #135 and this branch, measured withmeasureBundles(gzip):
| Route initial JS (gzip) | #135 | This PR | Δ |
|---|---|---|---|
/ | 97.7 KiB | 107.6 KiB | +10.1% |
/datasets/[dataset] | 182.7 KiB | 201.6 KiB | +10.4% |
/datasets/[dataset]/alerts | 102.2 KiB | 113.5 KiB | +11.1% |
/netflow/files | 98.9 KiB | 108.4 KiB | +9.7% |
/netflow/files/[slug] | 159.2 KiB | 174.4 KiB | +9.6% |
| All emitted JS | 204.0 KiB | 255.6 KiB | +25.3% |
| CSS | 12.6 KiB | 15.7 KiB | +24.4% |
The cost is bits-ui:
- Popover, Select, DropdownMenu and ToggleGroup, plus floating-ui positioning, account for the eager component cost. The unused Tooltip provider has been removed.
- Command, RangeCalendar and
@internationalized/dateare split into chunks that load on first open.
The first cut was +21.5 KiB on / and +45 KiB on the dashboard. Lazy-loading the palette, the lists and the calendar brought that down to about +10 KiB and +19 KiB. The CSS growth comes from component utility classes.
Manual checks done
- Fixture screenshots at 1280 and 390, light and dark, on the dashboard, Files and file detail (above).
- Real-data QA on the redeployed perf stage (:8090, uoregon-v5 read-only): KPIs, all eight cards, both themes and 390px. These screenshots are private and not included. The
teststage container was verified unchanged.
Remaining manual checks
- Screen-reader pass on the Range Calendar and Command lists, and Safari/Firefox checks; only Chromium was tested.
- Bundle increase sign-off.
Design and implementation by Claude Opus 5.5 in Claude Code. Review fixes by GPT-6.1-Sol through the Codex harness in T3 Code.
Checks
- SuccessfulPlaywright E2EGitHub
- SuccessfulRust Pipeline ChecksGitHub
- SuccessfulWeb ChecksGitHub
Added 9 commits
- perf(web): load the sources and dataset command lists on first open2338bcd
- chore(web): add shadcn-svelte popover, command, range calendar, menu, toggle group, sheet and form primitives5172eed
- test(web): cover console toolbar, KPI totals, card menu and shell navigation6eaeb68
- feat(web): console shell, filter toolbar, KPI row and card menus on shadcn-svelte8200107
- feat(web): console styling for datasets, files, file detail and alerts pages; remove rail controlsae94138
- fix(web): lazy-load calendar and command palette, keep card menu focus across activationf8cb1fc
- fix(web): reject reversed traffic windows and hide stale totalsa41aba7
- fix(web): preserve keyboard focus in console controlsab8e387
- perf(web): remove the unused app tooltip provider3962184