# Navigation, record workflows, and private runtime smoke

Use this reference for dashboard correction releases where automated tests were green but real user paths exposed routing, filter, document, or calendar defects.

## Exact metric drill-down

- Treat `metric_id` as the navigation contract. Never derive a localized search term with string splitting and then hope the catalog search resolves the intended metric.
- Load the approved catalog and select by exact `metric.id`, or add an exact server-side `id` filter.
- Replace the current selection for a card drill-down unless multi-select is explicitly requested; otherwise an old metric can create a misleading extra series.
- Await session readiness, exact catalog resolution, series loading, and chart render before reporting success.
- On success, focus the chart heading or status. On failure or an empty range, show a focused human-readable status instead of an empty explorer.
- Acceptance must inspect the ECharts instance, exact selected ID, label, dimensions, non-null points, and absence of Chart.js—not merely a canvas count.

## History and route ownership

- Before pushing a detail/day route, make the current view representable in the current history entry. If a tab was selected while the URL still represented the root view, `replaceState` the current entry with that tab before `pushState` for the detail route.
- Suppress duplicate pushes when the current parsed route already equals the requested route. Event and parent-cell handlers can otherwise create two identical history entries and make one Back operation appear broken.
- Give each route namespace one owner. Generic `popstate` handlers must not overwrite day, calendar, or record handlers after those handlers restore their view.
- Verify Back and Forward from every originating surface, not only deep-link reload.

## Per-tab record filters

Maintain a state object shaped like `filtersByTab = {overview, labs, medications, appointments, documents, report}`.

- Sanitize each tab with a local allowlist before every request and before writing history state.
- Switching tabs uses only the destination tab’s saved filters; foreign keys never migrate into the destination request or destination filter object.
- Back/Forward restores the complete map, then sanitizes each tab again.
- Regression tests should set filters in at least two tabs, open documents through real user controls, record the actual request URL, and prove HTTP 200 plus absence of foreign parameters.
- Do not `page.route(...fulfill...)` the endpoint whose query contract is under test. A one-shot `abort()` followed by `continue()` is acceptable for retry behavior because it does not fabricate the successful response.
- Register `waitForResponse` before the click that triggers the request; registering afterward races fast local responses.

## Real document and lab acceptance

Use a disposable synthetic DB with enough reviewed and unreviewed documents for real cursor pagination and a fixture-only FTS table. This is test data support, not a product schema migration.

Cover:
- newest-first ordering, filters, FTS, cursor continuation, metadata for unreviewed documents, content/original actions, match-to-section focus, and retry cleanup;
- no visible filesystem path, Drive URL, stored filename, or numeric/internal ID;
- canonical lab IDs such as `lab.crp`, range widening that includes the selected observation, and an understandable one-point chart;
- patient-facing primary labels, with technical provenance confined to collapsed details.

## Calendar text and mobile evidence

- Render category text explicitly with `eventContent` or equivalent; do not rely on a colored dot or pseudo-element.
- Keep full desktop labels and category-first compact mobile labels so truncation preserves meaning.
- Add category/count to `aria-label` and full text to `title`; retain clickable day cells and explicit event-click routing.
- Test computed width/height, visibility, contrast, non-dot content, correct day navigation, and 390 px overflow.

## Private preview deployment and smoke

1. Back up only the runtime assets being replaced and the generated preview.
2. Atomically install source assets, regenerate the enabled profile, verify source hashes, ensure no synthetic marker, and preserve the fallback checksum.
3. Restart the private service.
4. Do **not** equate `systemctl is-active` with readiness, and do not assume the service binds loopback. Read the unit/environment, then inspect the actual listening socket after restart. Build the smoke URL from the verified private bind address and route; a loopback `ERR_CONNECTION_REFUSED` can coexist with a healthy service intentionally bound only to a private interface.
5. Poll that bound socket or private HTTP route with a timeout before starting Playwright.
6. Run a browser smoke over real data while logging only statuses and counts: required cards, documents HTTP status/count, canonical lab drill-down, task routing, visible calendar-title count, console errors, Chart.js requests, and external requests.
7. Keep credentials, health values, bind addresses, and document names out of the final report; represent the endpoint as `[PRIVATE ROUTE]`.

### Data-dependent positive actions

A live dataset may legitimately contain no currently eligible row for an optional action such as “Original öffnen.” Do not fail the whole release merely because a page-local `available` filter returns zero, and do not create or mutate production data to manufacture eligibility.

- Search only through the existing bounded pagination contract, under an explicit page cap.
- If no eligible real row exists, navigate back to the unfiltered live list and verify the negative contract: the workspace loads and the unavailable action is absent.
- Verify the positive action separately with the real server stack plus a disposable safe fixture where eligibility is explicit.
- Report the split honestly: live negative-path PASS and synthetic positive-path PASS. Never claim a live positive action occurred when it did not.
- Smoke harnesses must not assume page 1 contains an eligible item; log only aggregate match/next-page booleans while diagnosing.

A smoke failure caused by checking an async UI immediately should be diagnosed before changing product code: wait for the visible target or response. Preserve the failure if the target still does not appear.

## Evidence hygiene

Full browser matrices may regenerate older tracked screenshot artifacts. After the matrix, restore unrelated legacy evidence and retain/force-add only the newly approved synthetic screenshots. This cleanup does not invalidate source-level test results, but it must happen before commit and final clean-tree verification.
