## Outcome - Audited the repository read-only at the exact requested base: `019c6c09721844ea6e08b2cd975fc543d424e617` - Working tree remained clean; no files were created or modified. - No database rows, originals, reports, or PHI were opened. Inspection was limited to source, documentation, and synthetic tests. ## Current document workflow map ### UI routes | Workflow | Current route | Implementation | |---|---|---| | Document list | `?view=record&tab=documents` | `dashboard-v5-record.js:98-123,269-322` | | Reviewed detail | `?view=record&tab=documents&document=api-document-<24hex>` | `dashboard-v5-record.js:212-245` | | Extracted preview | Same route plus `&preview=extracted` | `dashboard-v5-record.js:247-261` | | Original | `/api/v1/documents//original` in a new tab | `dashboard-v5-record.js:224-226,314` | | Matches | No separate URL; transient in-page result | `dashboard-v5-record.js:324-354` | | Document → day | `?view=day&date=YYYY-MM-DD` | `dashboard-v5-record.js:316`; day router at `dashboard-v5-day-controller.js:39-42,143-166` | | Day documents | Reviewed-only metadata, currently plain text with no document action | API: `read_api.py:1173-1197`; UI: `dashboard-v5-day-controller.js:135` | Routing is strict: - Allowed record parameters are only `view`, `tab`, `document`, `preview`. - Duplicate/unknown parameters and malformed opaque IDs fail closed. - Search/filter text deliberately stays out of URLs. - Existing route/history restoration is at `dashboard-v5-record.js:575-594`. ### List/filter/status behavior `dashboard-v5-record.js:269-321` currently provides: - Full-text query - Date range - Category - Institution - Type - Review status - Sort - Four independent status displays: - original - extraction - human review - verified-search eligibility Actions are correctly gated at `dashboard-v5-record.js:311-316`: - Reviewed extracted content: `Inhalt anzeigen` - Extracted content, including pending: clearly labelled preview - Technically available original: descriptor-backed original link - Matches: reviewed-searchable only - Day navigation: valid document date only ### Pagination Backend pagination is already suitable and should remain unchanged: - Signed, filter/query-bound opaque cursor: `read_api.py:1802-1851` - Page size validation, default 25/max 50: `read_api.py:1854-1861` - SQL filtering and keyset predicate before `LIMIT page_size + 1`: `read_api.py:1935-2046` - Search pagination uses rank plus stable ID: `read_api.py:1976-1994,2010-2037` - Cursor generation: `read_api.py:2107-2124` Current UI issue: **“Weitere Dokumente laden” replaces the first page instead of appending it** because it reruns `render()` with only the cursor page: - `dashboard-v5-record.js:320` Detail and preview independently page chunks in batches of 20, but automatically stop after 20 pages/400 chunks: - Detail: `dashboard-v5-record.js:216-219` - Preview: `dashboard-v5-record.js:251-253` ## Safe API boundary No API semantic change is needed for B1. | Endpoint | Policy | |---|---| | `GET /api/v1/documents` | Reviewed and pending safe metadata; strict filters and cursor | | `GET /api/v1/documents/` | Reviewed-only detail | | `GET /api/v1/documents//matches?q=…` | Reviewed-only FTS matches | | `GET /api/v1/documents//extracted-preview` | Bounded read-only preview; does not confer review/search status | | `GET/HEAD /api/v1/documents//original` | Authenticated descriptor-backed streaming | | `GET /api/v1/day/` | Reviewed-only document metadata for the day | Dispatch and strict query allowlists are at `read_api.py:2728-2801,2833-2839`. Security properties already present: - SQLite `mode=ro`, `query_only=ON`, `trusted_schema=OFF`: `read_api.py:161-168` - Current review-state join for FTS: `read_api.py:1976-1986,2250-2266` - Reviewed-only detail: `read_api.py:2142-2168` - Preview remains review-neutral and bounded: `read_api.py:2211-2247` - Original authentication/origin/session gate: `health_dashboard_server.py:967-1004` - Component-by-component no-follow open and pinned descriptor: `health_dashboard_server.py:303-357` - `no-store`, `nosniff`, `no-referrer`, generic filename, streamed chunks: `health_dashboard_server.py:1005-1028` - API methods other than the browser-session bootstrap are read-only: `health_dashboard_server.py:690-707,727-740` Important legacy boundary: numeric `/health-doc/` and `/health-report/` routes remain at `health_dashboard_server.py:888-905`. B1 should create **no links or dependencies** to these legacy routes. ## Minimal B1 implementation proposal No Python/API/schema/extraction changes are required. ### 1. `dashboard-v5-record.js` **Change `269-322`: document list renderer** - Wrap filters/coverage and results in a `.document-workspace`. - Add: - `.document-master` containing list and pagination - `.document-detail` with labelled empty state - Extract the current row construction at `303-317` into a small `renderDocumentEntry()` helper. - Make “Weitere Dokumente laden” fetch the next cursor directly and append unique rows to the existing master list; do not place cursor in durable history/filter state. - Mark the selected row/action with `aria-current="true"`. **Refactor `212-261`: detail and preview** - Convert `openDocument()` and `openExtractedPreview()` from whole-page `content.replaceChildren(...)` operations into detail-pane renderers. - Preserve the existing exact route contract: - reviewed: `document=` - preview: `document=&preview=extracted` - Keep the master list and active filters mounted while detail/preview loads. - Focus the detail heading after pointer, keyboard, reload, Back, and Forward. - On narrow screens, scroll the detail heading into view rather than introducing a second router. **Adjust `575-594`: restore path** - Restore the documents workspace first, then resolve its selected detail/preview. - Do not add URL parameters or expose filter/search text. - Keep original links on the existing safe API route. ### 2. `dashboard-v5-day-controller.js` **Change `67-77` and `135` only** - Add a dedicated safe document-list renderer instead of generic `setSection()`. - Render reviewed day documents as text plus an `Öffnen` button. - Call existing `window.healthRecordOpenDocument(item.id)`. - Do not display the opaque ID or add new API fields. - Back must restore the exact day route. This uses the already reviewed-only day contract from `read_api.py:1173-1197`; no backend change is needed. ### 3. `dashboard-v5.css` Add B1 styles after the existing document block at approximately `417-439`: - Desktop master-detail grid, e.g. bounded master column plus flexible detail. - Sticky detail only where it cannot be obscured by record navigation. - Selected-row state independent of color. - Master list remains vertically scrollable without page-width overflow. - At existing breakpoints near `446-447`, collapse to one column. - Preserve 44×44 controls, print behavior, privacy behavior, and `overflow-wrap:anywhere`. No changes are needed in `render.py`; the workspace can remain dynamically constructed in the existing `[data-record-content]` container. ## Focused browser tests Create one synthetic-only file: `tests/browser/dashboard_v5_sprint6g_b1.spec.js:1` Recommended cases: 1. **Desktop master-detail** - Open document list. - Select a reviewed row. - Assert list and detail remain simultaneously visible. - Assert selected row state, detail focus, and exact URL. 2. **Pagination appends** - Mock 55 opaque documents over three cursor pages. - Assert counts progress `25 → 50 → 55`. - Assert no duplicate rows and the first page remains mounted. - Verify filter/sort parameters remain bound to every request. 3. **Filter/status preservation** - Apply category, review status, and sort. - Open detail, Back, Forward, and reload. - Assert filters, selected row, and status chips remain coherent. 4. **Pending preview boundary/no status writes** - Pending row must have no reviewed viewer or match action. - Preview warning and original action remain available when technically safe. - Capture document-workflow requests and require only `GET`/`HEAD`; the only allowed POST in the session is `/api/v1/browser-session`. - Re-fetch the list after preview/original access and assert review/search status is unchanged. 5. **Day → document → Back** - Open `?view=day&date=`. - Activate a reviewed document. - Assert master-detail record route. - Back restores the exact day and heading focus. 6. **Direct links/history** - Direct reviewed detail and pending preview. - Reload, Back, Forward. - Invalid/duplicate route parameters fail closed. 7. **Security and accessibility** - XSS payload remains text-only. - No path, stored filename, URL, Drive-like token, or numeric DB ID in visible DOM. - No external requests or console errors. - At `390×844`, no horizontal overflow and all visible controls are at least 44×44. Existing focused coverage to retain: - API pagination/review/FTS: `tests/test_dashboard_v5_sprint6e_record.py:82-219` - Original attack matrix: `tests/test_dashboard_v5_sprint6e1_original.py:71-261` - Preview and unchanged DB review status: `tests/test_dashboard_v5_sprint6g_a3.py:56-90` - Existing record routes/browser behavior: `tests/browser/dashboard_v5_record_6e.spec.js:21-210` - Real synthetic original/preview behavior: `tests/browser/dashboard_v5_record_original_6e4b.spec.js:19-75` and `dashboard_v5_sprint6g_a3.spec.js:43-63` ## Issues / cautions - Current pagination label promises loading more but replaces the page. - Current detail/preview replaces the entire document list, so it is not yet a master-detail workspace. - Day documents have no reverse link into the record workspace. - The legacy numeric document/report routes remain a separate trust boundary and must not be reused. - Keep B1 strictly UI-only: **no review-status controls, no status mutation requests, no new POST endpoint, and no API contract change.**