# Dashboard UX/accessibility release review

Use this for browser-backed acceptance reviews where approval is allowed only when no Important/Critical issue remains.

## Review sequence

1. **Read the stated acceptance criteria and deferrals first.** Treat explicitly deferred next-sprint features as a separate non-blocking section; do not mix them into release findings.
2. **Inspect implementation and repository tests.** Trace HTML semantics, CSS breakpoints/print/privacy rules, chart lifecycle, period filtering, view switching, empty states, and test coverage.
3. **Create synthetic-only runtime evidence.** Generate from a temporary fixture/database and serve through the real local route and asset policy. Never open production health data for UI review.
4. **Run existing unit and browser suites.** A green suite is evidence, not approval: compare what tests assert against every acceptance criterion.
5. **Add temporary deterministic probes for uncovered criteria.** Keep them outside the repo unless asked to implement tests.

## Required browser probes

At every required viewport (including exact phone/tablet/desktop dimensions):

- Assert `documentElement.scrollWidth <= clientWidth` after visiting every view containing long labels, not only the landing page.
- Enumerate visible interactive elements and record bounding boxes; targets must be at least 44×44 CSS px. Do not assume a styled label makes its nested checkbox semantic target compliant.
- Visit each mutually exclusive view and assert exactly one panel is visible. Test the separate More affordance and verify `aria-expanded` resets after leaving it.
- When a separate More/secondary view is outside the primary tablist, activate it by keyboard and assert the primary tablist still has exactly one `tabindex="0"` escape target. A view switch that sets every primary tab to `tabindex="-1"` strands keyboard users even if mouse-driven navigation tests pass.
- Exercise keyboard tab order, roving-tab arrow/Home/End behavior, skip link, visible focus, dialog open/close, and absence of traps. Record an actual Tab sequence from secondary views rather than inferring reachability from ARIA attributes or programmatic locator clicks.
- For every route-owning tab or control, require keyboard and pointer activation to use the same navigation/state function. After Arrow/Home/End navigation, assert the selected tab, visible panel, URL/query state, and history entry agree; then reload and require the same view to return. A roving-tab handler that only calls `selectView()` while the click handler separately updates `history` is an Important keyboard-equivalence defect.
- Exercise Back and Forward after both pointer and keyboard navigation. Inspect focus and restored sub-selection, not only the visible panel; a `popstate` handler that re-renders the destination but does not replay its route resolver can silently lose selected-card state and focus.
- Count cockpit cards and selected raw chart series from the runtime chart instance/data, not just DOM labels.
- Change the global period and assert visible chart labels/data-point counts actually change; checking only the period caption is insufficient.
- Verify charts are absent before first view activation, created on demand, updated/resized after period changes and viewport resize, and not duplicated on revisits.
- Verify events are a separate semantic track rather than an extra raw value series.
- Test a schema-only/empty fixture: every card/list/chart needs meaningful empty-state copy; a blank canvas is not an empty state.
- Activate privacy mode in every data-bearing view and inspect computed visibility of text and canvases. A partial `data-private` allowlist is not a reliable privacy mode.
- Emulate print media and inspect computed styles plus printable content. Navigation/actions should disappear, but required clinical dates, values, units, references, provenance, missingness, and disclaimers must remain printable. Avoid blanket print rules that make all private data transparent.
- Check long labels in the actual doctor/nutrition/medication views at narrow width, plus 200% text zoom/reflow where required.
- At 200% text, inspect every fixed-height chart panel for **vertical content compression**, not only horizontal overflow or panel-bound containment. Record the canvas `getBoundingClientRect()` dimensions and compare its visible CSS height with the Chart.js instance/canvas backing height, legend bounds, and chart area. A fixed-height grid can keep `canvas.bottom <= panel.bottom` while enlarged headings consume the panel and squeeze a 400px chart plus legend into an unreadable ~50px CSS strip. Require a usable visible plot/legend height or let the panel grow at text zoom, and confirm with a screenshot.
- Treat a large CSS canvas box as insufficient proof of readability. Chart.js can retain a much taller internal/backing height while CSS such as `height: 100% !important` forces the displayed canvas into a shorter grid track, vertically scaling all rendered content. Compute `scaleY = canvas.getBoundingClientRect().height / chart.height` and report effective visible dimensions for `chartArea.height`, `legend.height`, and legend hit-box/text rows. For example, a 340px CSS canvas backed by a ~760px Chart.js surface has `scaleY ≈ 0.45`; a nominal 12px legend row is visibly only ~5.4px and is an Important failure even though `boundingBox().height >= 280` and panel containment pass. Compare 100% and 200%, inspect date-label collisions, and capture a chart-centered screenshot rather than whichever controls currently hold focus.
- Regression tests for this issue must assert internal-to-visible scale/readability, not only the canvas bounding-box height. Include an upper bound on backing/CSS distortion or a minimum effective legend/tick size, plus a screenshot/visual check where appropriate.
- Capture console errors, failed assets, and external requests.

### Exact mobile 200% chart re-review probe

For a narrow post-fix approval focused on mobile chart geometry/readability:

1. Use the exact required CSS viewport (for example, `390×844`) and set the root font to the explicit computed target (`32px` when the baseline is `16px`), then wait for the chart's resize observer/layout pass.
2. Record both visible and internal geometry: canvas CSS width/height, `chart.width/height`, `scaleY`, effective chart-area height, legend height and hit-box rows, tick font sizes/rotation, chart-frame/panel bounds, document client/scroll width, fixed-navigation bounds, and console/request failures.
3. Scroll the **canvas** into the viewport center and quantify `nav.top - canvas.bottom`; require the complete canvas to be visible above the fixed navigation. A locator screenshot of a panel taller than the viewport can pin the fixed navigation over the image and falsely suggest chart occlusion even when the chart is independently scrollable and unobstructed.
4. Capture a normal viewport screenshot after that centering step. Visually confirm all legend rows, plot area, date labels, and the fixed navigation together. Do not use an oversized element/locator screenshot as the sole occlusion verdict.
5. Separately center every visible semantic control and measure it against the fixed-navigation rectangle; verify at least `44×44` CSS px and no overlap. Chart readability and control reachability are distinct checks.
6. Exercise real Home/End or arrow-key navigation at 200% text and record focus, selected tab, roving `tabindex="0"` count, and visible-panel count. A default-font keyboard pass does not by itself prove the zoomed mobile shell remains coherent.

## Severity and verdict

- **Critical:** exposure or mutation of sensitive/production data, unsafe clinical interpretation, executable injection, or destructive behavior.
- **Important:** a stated acceptance criterion is observably unmet; privacy/print mode is functionally incomplete; required accessibility targets/keyboard semantics fail; empty data is misleading; or tests claim coverage while only asserting labels rather than behavior.
- **Minor:** stale ARIA state, silent-limit feedback, maintainability debt without current acceptance failure.

Return `APPROVED` only when there are no Important/Critical findings. Structure the report as: verdict, Important/Critical findings with file/line and runtime evidence, Minor findings, positively verified criteria, test commands/results, and separately listed deferrals.

## Focused More/primary-tab keyboard blocker re-review

For a narrowly scoped final approval of a separate **More** control outside the primary `tablist`, do not rely only on the default first tab:

1. Select a non-default primary tab (ideally the last tab), then activate **More** by keyboard.
2. While the More panel is visible, assert that all primary tabs have `aria-selected="false"`, exactly one primary tab remains `tabindex="0"`, and that escape target is the previously selected primary tab. Confirm More remains a button outside the tablist with `aria-controls` targeting its panel and `aria-expanded="true"`.
3. Starting from the focused More button, record the real Tab sequence through intervening controls until focus reaches that preserved primary-tab escape target.
4. From the preserved tab, exercise Arrow/Home/End navigation. Assert focus, the single `aria-selected="true"` primary tab, the single `tabindex="0"` tab, visible panel, and More `aria-expanded="false"` all update together.
5. Keep the release response to the user’s exact requested verdict token when they explicitly request `APPROVE` or `CHANGES REQUIRED` only; retain detailed evidence internally rather than violating the output contract.

When starting a temporary review server, prove the process owns the chosen port before testing (or choose a fresh dynamic port). A synthetic sentinel proves safe fixture data, but it does not prove that the just-launched process is the server being exercised.

## Browser-suite reproducibility and coverage audit

- For a **post-fix final approval**, begin by checking `git status --short` for every claimed test/config artifact. If the request says the browser suite, package manifest, lockfile, or runner config is committed but Git reports any of them as untracked, treat clean-checkout reproducibility as an **Important** release-gate failure even when `npm ci` and the local suite pass. Report local execution success separately from committed evidence.
- Audit both focused selectors and the default/full matrix path. Adding `GROUP=new-sprint` support proves only opt-in execution; the ordinary release command must actually invoke the new spec and update any reported group count/list. A focused green run is not release integration when the default matrix can omit it silently.
- Verify the committed browser suite is runnable from a clean checkout: its package manifest/lockfile, runner configuration, and documented command must exist. If the test command requires a base-URL environment variable, the repository must also document or script the complete synthetic fixture → generated dashboard → hardened/local server → test sequence; a package script that only starts the runner is not a reproducible browser gate. A test file that imports an uncommitted or globally installed runner is not release evidence. If browser tooling or the server harness is intentionally external, run an isolated temporary probe and report it separately rather than claiming the committed suite passed.
- Distinguish local execution evidence from durable regression coverage. Temporary probes can establish the current behavior, but approval-critical lifecycle/event/period semantics should be encoded in the committed suite when the sprint claims them. Assertions for a static heading or caption do not prove that a marker, chart update, or date-derived position exists.
- Keep evidence provenance exact: CSS declarations are implementation evidence, not runtime measurement. Claim an actual 44×44 target only when a browser probe successfully records the element's own bounding box. Likewise, do not convert a blocked or failed temporary probe into a positive runtime claim.
- Measure controls while each relevant view is active. Collecting targets only after navigating to a final view can silently exclude hidden controls from every earlier view.
- Enumerate every semantic interactive element (`button`, checkbox/radio `input`, `select`, `textarea`, `summary`, and links), not buttons alone. Record the element's own bounding box as well as any associated label/hit area; a 44px label does not by itself prove that a nested 22px checkbox meets a stated semantic-target requirement.
- Audit assertions for behavioral depth: period tests must compare chart labels/data; privacy tests must inspect every data-bearing view; print tests must cover the complete required information set; overflow tests must visit every long-label view; and populated as well as empty Lag/Phase paths must be exercised.
- Treat synthetic future-dated observations as a distinct anomaly case. Confirm they are visibly identified as future/check and are not silently selected as the ordinary “latest” health value.

## Native form-submission and confirmation gate

For capture/check-in dialogs, do not approve from markup validity, `form.checkValidity()`, mocked redirects, or a client-side success-state replacement alone. Exercise one complete synthetic submission through the real browser navigation stack and record the POST request, response status, queue/outbox side effect, redirect, confirmation text, and cleaned URL.

- Inspect the browser's actual `Origin` header. A response such as `Referrer-Policy: no-referrer` can make a native form POST serialize `Origin: null` in Chromium; a server that simultaneously requires `urlparse(origin).netloc == Host` will then reject its own same-page form with 403. If this occurs, isolate causality with a temporary response-header override (for example `same-origin`) against the same synthetic runtime: success under the override plus failure under the shipped header is precise evidence, not a proposed production workaround.
- Treat a browser test that intercepts the dashboard response and replaces `data-queued=false` with `true` as presentation-only evidence. If it closes via Cancel and never observes a POST, it does not verify queueing, truthful confirmation, CSRF/cookie compatibility, redirect behavior, or URL cleanup after a real submission.
- Keep the probe synthetic-only. Snapshot the synthetic queue before and after, verify any created action file's expected permissions/shape, and remove only the action created by the review.
- When JavaScript disables or relabels the submit button in a `submit` handler, verify both invalid and valid paths: invalid submission must retain enabled controls and focus the first invalid field; valid submission must still navigate and enqueue successfully.
- Use **native keyboard activation**, not `locator.click()`, for the release-critical reproduction: focus the opener and press Enter, then focus the submit control and press Enter. Run the same browser context at the exact viewport, explicit 200% root text size, and `prefers-reduced-motion: reduce` when those conditions are in scope.
- Exercise the sequence in one controlled session: (1) open and confirm initial focus, (2) attempt an invalid keyboard submit and record the focused invalid field, native validation message, dialog-open state, and enabled submit state, (3) press Escape and verify focus restoration, (4) reopen, complete all required values, and perform the valid keyboard submit.
- Observe both the POST request and its response. Record the shipped `Referrer-Policy`, the request's actual `Origin`, response status (typically 303), final confirmation text, and final URL after `history.replaceState`; require the receipt query parameter to be absent while the confirmation anchor may remain.
- Measure every visible dialog control after 200% text is applied, plus document/dialog client-versus-scroll geometry. A correct narrow dialog may exceed its client height through internal `overflow-y: auto`; it must remain horizontally contained and all semantic controls must still be at least 44×44 CSS px.
- Use a unique non-medical sentinel in the synthetic note to identify exactly one newly created action. Inspect its canonical JSON and `0600` mode before deleting only that file in `finally`; verify the queue is back to its pre-probe state even when the probe fails.
- Capture a normal viewport screenshot of the invalid 200% dialog and the post-redirect confirmation. Native validation bubbles may temporarily overlay nearby content; judge persistent reflow/clipping separately from this browser-owned transient feedback.

## Interactive chart prototype evidence (ECharts and similar engines)

When a sprint claims a dense interactive chart prototype, distinguish option presence from exercised behavior:

- Verify the repository's documented/package test command actually includes the prototype spec. A manually run all-spec command can produce a larger green count while the durable package script silently runs only the legacy suite; treat that mismatch, or an untracked prototype spec, as an Important reproducibility blocker.
- Inspect the runtime option contract for every claimed primitive (series/day counts, renderer, tooltip, linked crosshair/axis pointer, zoom controllers, brush, ARIA, reference areas, and event markers), but do not stop there.
- Exercise real gestures. For pan, drag the plot and require the visible `dataZoom` start/end to change. For brush, drag a range and require the engine's selection event/result to expose both a deterministic ISO start/end range and a non-zero deterministic point/day count; a count alone can hide a missing or malformed range, while toggling a shell `data-*` flag proves only button wiring. For tooltip/crosshair, hover a known point and verify the rendered tooltip content/date plus the runtime option contract for trigger, crosshair type, and linked axes (and active pointer state where the engine exposes it reliably).
- Verify both halves of duplicate controls: zoom preset and reset/all-range, series toggle and solo/reset, brush and clear. Read legend selection from the chart instance, not only `aria-pressed` attributes maintained by application code.
- Keep the synthetic browser harness environment internally consistent. When the server writes to a disposable action inbox and the suite independently reads that inbox, export the same path through both the server variable and the runner-facing test variable. Explicitly override inherited test URLs, instance IDs, and inbox paths for the controlled run; otherwise a real POST can succeed while the assertion scans a stale foreign directory and reports a misleading failure. Verify server ownership and the synthetic identity header before running the package command.
- At exact mobile 200% text, wait for ResizeObserver/layout settling and record CSS versus internal chart dimensions, plot readability, labels, every visible control's 44×44 box, navigation overlap, and page overflow. An overflow-only assertion does not prove a usable chart.
- In print media, inspect the complete fallback, not merely whether the canvas remains visible. Beware global `button { display:none }` rules when accessible table row identifiers (for example ISO dates) are implemented as buttons: the printed table can retain values while losing every row date. Open `<details>` explicitly or provide print rules that expose its content and preserve textual identifiers.
- Make performance gates explicit about timer scope. A timer started after data synthesis/option construction, or one that excludes library fetch/parse, cannot support an end-to-end initialization claim. For a meaningful prototype gate, define included phases, warm/cold conditions, repetitions and percentile, hardware/browser context, and at least one interaction-latency measurement. If it is intentionally only a coarse local smoke threshold, label it that way and do not present it as a production budget.

## URL/history state coherence for explorers

When a chart selection is represented in the URL, test every state transition—not only deep-link load and reload:

- Open a metric from another view and verify URL, focus, chart/table content, Back, Forward, and reload.
- Clear the full selection and remove the last metric chip, then require the metric query parameter to disappear. Reload must remain empty rather than silently restoring the cleared metric.
- Replace or solo a metric and verify the URL represents the current durable selection, not the metric that originally opened the Explorer.
- Inspect the state API for a real clear operation. A setter that rejects `null`/empty values combined with UI code that only mutates an in-memory selection is a common stale-URL defect.
- After asynchronous navigation, wait for the final status text before judging focus; checking focus as soon as the destination shell becomes visible can record the source control while loading is still in progress.
- For deep links targeting a `<details>` card, assert `document.activeElement` is the card's interactive `<summary>`, not a programmatically focusable `<details tabindex="-1">` container. A test such as `expect(card).toBeFocused()` can bless the wrong accessibility target.
- After click, reload, Back, and Forward, independently assert all four restoration invariants: exactly one selected card, that exact card's `open === true`, focus on its `<summary>`, and scroll/viewport placement. Do not infer open state from focus or a selection marker.
- Beware asynchronous `<details>` `toggle` races when rendering several cards with default `open` values and then switching the selected card. A later queued toggle can close the restored card after the resolver initially opened or focused it. Wait at least one animation frame plus the toggle-task turn before measuring, and design the resolver/listeners so stale toggle events cannot undo the final route state.

## Mobile workflow and chart accessibility coverage

A narrow-width assertion must follow the complete workflow launched from the mobile card. Do not stop before the destination Explorer/document view:

- At the exact phone viewport, open the card action, wait for final rendering, expand relevant `<details>`, and measure page/card/chart/table geometry.
- Measure both width and height for every visible semantic control; `height >= 44` alone does not establish a `44×44` target.
- For single-point charts, require the accessible chart description or an explicitly associated status/table summary to identify the metric and explain that one point does not establish a trend. A generic ECharts `role="img"` label plus an unrelated live-region warning is weak coverage even when a collapsed fallback table exists.
- Keep document provenance checks behavioral: assert coverage values and scope, status labels, empty/filtered states, and narrow-width reflow—not merely that headings are present.
- Ensure any package/matrix command references committed specs and documentation. An untracked test can pass locally while making the clean-checkout browser gate non-reproducible.

### Mobile master-detail route integrity

For mapping, document, review-queue, and similar mobile master-detail workspaces:

- Treat visible list/detail mode as durable route state. A mobile “Back to list” control must remove the selected-item query parameter through the same route/history seam used to open the detail; merely hiding the detail and focusing the selected row leaves URL, reload, Back/Forward, and assistive-technology state contradictory.
- Probe the complete sequence: list → detail → in-workspace Back → assert list visible and selection absent from URL → reload → list remains visible → browser Back/Forward restores the expected semantic states. Repeat for direct detail deep links.
- Apply this independently to every master-detail implementation in the sprint. A correct mapping workspace does not establish correctness for a parallel document workspace.
- Render the production detail independently from the master row. Do not construct editors inside legacy closed `<details>` cards, detach those cards, and move the selected form into the detail pane. That hidden-backing-card pattern is still a closed-details dependency, can silently produce a detail without its editor, and makes focus/state restoration brittle.
- Tests must verify route and reload coherence after the in-workspace Back action, not only that the list became visible.

## Code-quality checks

Flag duplicated chart configuration/lifecycle code, monolithic UI controllers, selectors/styles that miss generic controls, and tests that cover only the happy landing view. Prefer small view renderers, one chart adapter/lifecycle seam, centralized control sizing/privacy policy, and parameterized viewport/view tests.

When an acceptance suite claims broad coverage, compare selector scope against every control/view named in the matrix. Typical false-confidence patterns include `button`-only target checks, one-view privacy checks, one-value print checks, and navigation tests that inspect only the initial and one destination panel.

## Fail-closed CLI and synthetic-evidence boundary

For health or similarly sensitive dashboards, compare operational documentation against the actual argument parser and environment defaults—not only the data-provider implementation. If documentation says a fixture/database path is mandatory but the CLI still supplies a canonical or production default, treat that as an **Important** release-gate failure: an accidental no-argument invocation can cross the synthetic-only review boundary even when the reviewed run used a temporary database and its digest stayed unchanged. Require the sensitive input flag in the parser and add a regression test proving omission fails before any database is opened.

A deliberate exclusion of the pre-commit/untracked-file gate excludes only that repository-state gate. It does not waive functional safety, privacy, reproducibility, or code-quality findings. Report the exclusion narrowly rather than converting it into general approval.

For browser reviews that install dependencies or create traces/results, clean up only artifacts created by the review (`node_modules`, test results, temporary fixture/output directories, and the temporary server process) before final reporting. Re-run repository status afterward so review-generated noise is not attributed to the implementation.