Skip to content

feat(ui,rest,observability): make the dashboard chart functional — real type picker, multi-series, tooltip, a11y - #574

Merged
smunini merged 11 commits into
mainfrom
feat/555-dashboard-chart
Aug 19, 2026
Merged

feat(ui,rest,observability): make the dashboard chart functional — real type picker, multi-series, tooltip, a11y#574
smunini merged 11 commits into
mainfrom
feat/555-dashboard-chart

Conversation

@angela-helios

Copy link
Copy Markdown
Contributor

Closes #555. The "FHIR resources over time" card goes from decorative to functional, following the hi-fi mockup's resource picker design.

Stacked on #572 (base fix/543-stylesheet-unification; GitHub retargets to main when it merges).

The provider learns what a tenant actually stores

DashboardSnapshot now carries every type with at least one stored resource (via the existing count_all_types, largest first), and the caller passes the set to chart. Empty selection = the tenant's three largest types, skipping definitional/infrastructure resources (a server that seeds its own SearchParameters would otherwise chart those instead of clinical data — they stay available in the picker). Selections are capped at three (the mockup's choice; also bounds the per-type delta queries) and join the snapshot cache key. The hardcoded eight-type list is gone.

Every control does something

  • The type pill is the picker (mockup resPick): a details menu of checkbox rows — every stored type with its count, a typeahead filter pinned above a scrolling list, each option a plain link that toggles the type. Selecting past the cap swaps the oldest series out. The pill label stays one name wide (Encounter +2).
  • Expand works: a link that re-renders the taller plot server-side (520 viewBox) and collapses back.
  • Multi-series: up to three series on one shared y scale, each with its own --series-N palette slot; the legend entries carry the color dot and the total, and double as remove links while more than one is plotted.
  • Hover tooltip: a guide line plus a per-series readout of the nearest bucket (date, name, value). The data rides in an inert application/json carrier with server-computed SVG coordinates — the script does no chart math.
  • Windows (1h/24h/30d) keep the charted set and expansion across switches.

Honesty and access

  • The SVG has an accessible name (title + plotted types) instead of aria-hidden, and the same numbers render as text: a details-folded table of the sampled buckets, one column per series.
  • An explicit empty state replaces the silent bare axes.
  • The placeholder snapshot (no provider registered) renders under a visible "sample data" notice instead of posing as real.
  • Everything except the tooltip works with JavaScript disabled — picker, windows, expand, legend, and the table are links and native disclosure.
  • All strings in en/es/de.

Tests

  • dashboard.spec.ts: 2 assertions → the whole card (accessible name, window switching, picker toggling with the cap, legend removal, filter, tooltip in/out, expand round-trip, tabular alternative), plus a nojs spec walking the same controls with JavaScript off.
  • Provider unit tests updated (empty store → empty state; the observability cache tests catch the new trait shape).
  • Full hermetic suite locally: 128 passed, including axe (both themes) over the new card and the design-system guard over the new classes.

Demo seeding

Verified against a seeded store: ~1.2k resources across 8 types created through ordinary batch bundles, with their history rows backdated across 30 days so every window shows real growth curves. The two scripts (batch seeder + SQLite backdater) are local dev tooling — happy to commit them under a dev/ path if we want them in-repo.

…ype set

The provider contract gains the two things the chart card needs to become
functional (#555): the snapshot now carries every resource type the tenant
actually stores (with totals, largest first) so the picker can offer real
data instead of a hardcoded list of eight, and the caller passes the set of
types to chart — an empty selection means the provider's default, the
tenant's three largest types. Selections are capped at six, bounding the
per-type delta queries and matching the palette. The charted set joins the
snapshot cache key: two selections are two snapshots.

DEFAULT_DASHBOARD_TYPES is gone; the console handler keeps its own copy and
the dashboard now derives its defaults from what is stored.
… expand, a11y

The chart card's controls now all do something (#555). The inert type pill is
a real picker: every stored type as a checkbox row, each a plain link that
toggles the type in or out of the charted set — same progressive-enhancement
contract as the window selector. The dead expand button is a link that
re-renders the taller plot server-side. Multiple series plot together on one
shared scale, each with its own palette slot, and the legend entries double as
remove links while more than one is plotted.

Accessibility: the SVG carries an accessible name instead of aria-hidden, and
the same numbers render as text — a details-folded table of the sampled
buckets, one column per series. An explicit empty state replaces the silent
bare axes, and the placeholder snapshot (no provider registered) is labeled
as sample data instead of posing as real. All strings in en/es/de.

The tooltip's data rides in an inert application/json carrier: bucket labels
plus per-series values and SVG coordinates, so the hover script does no chart
math of its own.
…r the picker

Manual review: several series at once got noisy and the pill grew with every
name. Three is the mockup's choice and stays legible — the cap now lives on
both sides (the provider truncates, and a picker link that would exceed it
swaps the oldest series out instead). The pill shows one name plus a +N
overflow count. The picker's option list scrolls on its own with the heading
and a typeahead filter pinned above it, so a tenant with a hundred stored
types still finds the one it wants.
dashboard.spec.ts grows from two assertions to the whole card: accessible
name on the SVG, window switching, the picker toggling types with the
three-series cap (a fourth selection swaps the oldest out), the legend
removing series, the typeahead filter, the hover tooltip appearing and
leaving, expand/collapse re-rendering the taller plot, and the tabular
alternative opening. A nojs spec walks the same controls with JavaScript
off — picker, window, expand, and the table are all plain links and native
disclosure. Specs seed through the ordinary FHIR API and outlast the 15s
snapshot cache by polling.

Also catches up the two observability cache tests the trait change missed.
Manual review round two: three was the right default but the wrong ceiling —
an explicit selection can now go to six, the palette's full width, with the
oldest series swapping out past that. The default stays at the tenant's three
largest types.

The dashboard demo tooling moves into the repo (crates/ui/dev): a batch-bundle
seeder that stores ~1.2k resources across eight types through the ordinary
FHIR API, and a SQLite backdater that spreads their history rows across the
past 30 days with a distinct curve shape per type — so every window on the
chart has something real to show. Local-only; nothing ships or runs in CI.
@angela-helios
angela-helios requested a review from smunini August 19, 2026 07:22
@angela-helios
angela-helios changed the base branch from fix/543-stylesheet-unification to main August 19, 2026 07:26
Conflicts were the dashboard data path (#565 job cards + #537 bulk export
landing on main while this branch reworked the chart) and the UI's CSS/class
vocabulary refactor.

Resolution:
- DashboardSnapshot carries both sides: `available`/`TypeCount` (#555) and
  `export_jobs`/`import_jobs_active` (#565). The provider keeps this branch's
  `distinct_types` (derived from `available`) instead of main's second
  `count_all_types` read, and gains main's job-store counts.
- Rest dashboard tests updated for the three-argument `snapshot(window,
  tenant, types)` signature.
- app.css: main's new rules folded into this branch's `@layer` structure.
- Main's new templates moved onto the renamed vocabulary this branch
  introduced (`.button` -> `.btn`, `.page-title` -> `.page-head__title`).
- Bulk-export status chips renamed `.chip*` -> `.status-chip*`: `.chip` is
  already the filter affordance on the search/registry pages, and the
  duplicate definition both collided visually and tripped the new
  design-system guard. The dead duplicate `.notice` block from #537 is
  dropped (the registry-viewer `.notice` won the cascade anyway).
- Dashboard e2e expects five stat cards (main's job cards) and keeps this
  branch's `waitForSeries()`.

Claude-Session: https://claude.ai/code/session_019E74WX1whW1NaT1SjUqXJG
…ashboard-chart

Combines the multi-series chart with main's #565 job cards: the snapshot
carries both the picker's available types and the job counts, the metrics
render real export/import figures inside the rewritten build_dashboard, and
the stat-card spec expects five cards with the chart's series wait.
Both integrations of main landed on this branch in parallel; this reconciles
them. Steve's merge answered the .chip collision by minting a status-chip
family — folded here into the existing .tag status-pill vocabulary instead
(one pill family, per #543), and the vestigial .backlink stays gone. The
export card keeps his structure otherwise.
@angela-helios

Copy link
Copy Markdown
Contributor Author

Heads-up on the parallel merges: your main catch-up and mine landed on this branch at the same time — reconciled in 83685f669+. One deliberate call: your status-chip family for the export job chips got folded into the existing .tag status-pill vocabulary instead (tag--in-progress/complete/failed/cancelled, defined next to the other status tags) — #572 exists to keep one pill family, and the design-system guard now enforces exactly that. Everything else from your merge is kept as-is. Full suite locally: 133 passed, including the guards over the export pages.

The dashboard chart's polyline series carried a `total` string that
nothing reads — the legend renders its own total from `LegendEntry`,
built separately from the same snapshot. Dead code failed the Linting
job under `-D warnings`.

Claude-Session: https://claude.ai/code/session_01ArLi5r8NFZ2vJJ7uJvwZWA
# Conflicts:
#	crates/ui/templates/layouts/base.html
Three `set_value` calls in the editor tests exceed the line limit and
`cargo fmt --check` rejects them, failing the Linting job. Arrived on
main with #573; purely mechanical reformat, no behavior change.

Claude-Session: https://claude.ai/code/session_01ArLi5r8NFZ2vJJ7uJvwZWA
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.71856% with 31 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/rest/src/dashboard.rs 28.00% 18 Missing ⚠️
crates/ui/src/lib.rs 95.20% 13 Missing ⚠️

📢 Thoughts on this report? Let us know!

@smunini
smunini merged commit 848306d into main Aug 19, 2026
20 checks passed
@smunini
smunini deleted the feat/555-dashboard-chart branch August 19, 2026 18:33
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.

Home dashboard: make the "FHIR resources over time" chart functional

2 participants