# 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:`.