# Story: Clickable Document Viewer **Phase:** `10_story_document_viewer.md` · **E2E:** `tests/e2e/test_document_viewer.py` ## Narrative As **a user**, I want to click any document Brain cites (the source chips under an answer) or any row in the Sources table and have the **actual document open in the browser** — in whatever format it is — so I can read the full context and verify the answer against my real notes. - **Given** an answer with source chips (`Homelab/active/container_gitlab/ gitlab.md`) or the Sources page table - **When** I click a chip or a path - **Then** a new tab opens the viewer: title, source/format/path meta, and the full content — markdown rendered with the existing local escape-first renderer, other formats as escaped monospace text. ## Acceptance criteria 1. `GET /api/documents/content?source=…&path=…` → 200 `{source, path, title, format, content, indexed_at, chunks}`; 404 `{detail}` when the pair is not indexed. Served **from the database only** — no filesystem access, hence no path-traversal surface. 2. `GET /document.html?source=…&path=…` renders the document: back link, title, meta row (source, format badge, mono path, indexed date, chunk count), content — md via the shared local renderer; other formats as escaped `
` (mono, horizontal scroll). 3. Chat source chips link to the viewer **in a new tab** (`target="_blank" rel="noopener"`), accessible name intact. 4. Sources table: the path cell is a link to the viewer (new tab). 5. XSS-safe: document content is never injected as raw HTML (renderer escapes first; raw formats use text nodes). 6. The viewer inherits the dark tech theme; the no-CDN invariant is extended to `/document.html`; landmarks/labels/`aria-live` on the new page. 7. Missing doc → designed not-found state with a link to the Sources page. 8. Unit + integration green, `app/` coverage >90%, story E2E green in isolation, one `--no-gpg-sign` commit. ## UI Visualization & Structure - **Viewer page:** sticky header (back link + title + meta badges) + ``: markdown column centered at ≤46rem; raw content in a full-width `pre` with `overflow-x: auto`. Dark tokens from Phase 08. - **Chips/links:** source chips keep the mono pill; path links get hover/focus underline; `:focus-visible` ring; ≥44px touch targets on mobile. - **Not-found state:** centered card (no emoji — SVG or plain text), message + "Open Sources" link. ## Playwright Mapping Rule **Test Scenario → `tests/e2e/test_document_viewer.py`** (mock LLM, seeded KB): 1. `test_source_chip_opens_document` — on-topic question → click the `.source-chip` → a **new tab** at `/document.html?…` shows the fixture doc's title + a known content string + the format badge. 2. `test_sources_row_links_to_viewer` — `/sources.html` → click a path link for a `.yaml` fixture → viewer shows its raw content in a `pre`. 3. `test_markdown_renders_and_stays_xss_safe` — an md fixture containing `` renders as visible escaped text (no execution). 4. `test_missing_doc_shows_not_found` — unknown path → not-found state + Sources link; no console crash. 5. `test_viewer_theme_and_no_cdn` — dark background + every `script[src]` / `link[href]` local or `data:`.