# Health Dashboard: record-workflow hardening

Use for a feature-flagged, read-only health-record/dashboard slice with document search or originals.

## Release discipline

1. Freeze and verify the stated base SHA, remote SHA, and clean tree before edits.
2. Treat a broad UI/FTS/security request as one vertical contract: database query → API → safe DOM → router/history → browser evidence.
3. When the user gives an execution-to-RC instruction, keep one continuous tool round running until either (A) verified commit/push or (B) the explicitly defined blocker threshold. A progress message is not an end state: do not return a final/status-only answer and silently stop the worker.
4. Track identical failures by **root cause**, not by superficial assertion changes. Locator scoping or wording adjustments do not consume a cause-related repair attempt when the product action never fired. Reproduce, inspect trace/network/DOM evidence, repair the actual cause, then rerun the narrow case.
5. Do **not** commit or push when any acceptance gate is still merely smoke-tested. A passing legacy matrix is not evidence for newly requested flows.
6. Run focused gates first. Run the full Python suite and isolated browser matrix only after the focused contract is stable; any later productive code change makes those full-gate results stale and requires a deliberate re-evaluation rather than an unreported release claim.
7. Before final reporting, separately verify remote SHA, ahead/behind, clean tree, the exact browser group/case counts, and remove ignored Playwright artefacts.

## Query and FTS contracts

- Apply filters in SQL, then stable sort, then `limit + 1`; expose `truncated`, `next_cursor`, and a bounded page size. Never reject a valid targeted query merely because unrelated table rows exceed a global cap.
- Cursors and document references returned to a browser must be opaque. For real keyset pagination, sign or server-bind a cursor payload containing the primary sort key plus stable ID tie-breaker, and bind it to sort, direction, filters, and search query. Verify every public sort over >100 synthetic equal-key rows with no duplicates/omissions; tampered and cross-query cursors return controlled 400. Do not expose primary keys, paths, Drive IDs, filenames, or search text in the cursor.
- Active FTS search still needs keyset semantics: order by relevance plus stable ID and carry both values in the signed cursor; perform the relevance/review/filter predicate in SQL before `LIMIT`.
- FTS results must join the **current** `dokumente.review_status='geprueft'` state at query time; an index created before a review-status downgrade must not disclose content.
- Quote every user search token as an FTS literal. Test `OR`, `NOT`, `NEAR`, `AND`, umlauts, and ß; syntax-like input must be a controlled client error, never an availability error.
- Keep normalization and chunk boundaries in one shared module consumed by migration, detail/viewer, and match endpoints. Pagination must replace fixed content truncation.

## Original-document route

- Resolve only a reviewed opaque ID to a configured local root.
- Reject duplicate/unknown query parameters and all noncanonical paths.
- Open component-by-component beneath an allowlisted root using `openat`/`dir_fd`, `O_DIRECTORY`, and `O_NOFOLLOW` (or a proven equivalent). `resolve()` plus `O_NOFOLLOW` on only the leaf is insufficient because intermediate symlinks can be followed. Pin the descriptor, then validate regular-file type and size with `fstat`; stream chunks rather than loading the whole file.
- Test symlinks at leaf and parent levels, traversal, file replacement between lookup/open, auth, HEAD, MIME magic, generic download name, and `no-store`/`nosniff`/`no-referrer` headers.

## Router and DOM

- Extend the existing central view/day router. Do not introduce a competing panel-visibility state machine.
- Validate direct-link parameters exactly (`view=record`, allowed tab, optional opaque document); reject duplicates and unknown keys. Keep search text out of URLs.
- Use `textContent`/text nodes for every database-derived value. Rendering must use real controls, tables, cards, and links—not concatenated text summaries.
- Dynamic form pitfall: a shared button helper that always sets `type='button'` will never trigger a form's `submit` listener. Make submit controls `type='submit'` or call `form.requestSubmit()` explicitly, and prove the API request occurs; a test that merely sees pre-existing fixture text is a false positive.
- Browser tests must cover direct links, history, focus, session-bootstrap count, 390px overflow, XSS-as-text, filters/sorts/pagination, document viewer/match navigation, originals available/unavailable, and print view.
