Remove the blanket .agent/ gitignore so the phase roadmap, user stories, reports, and PLAN.md are versioned with the code. Only runtime artifacts (.agent/phase-sessions/, .agent/pipeline.log) remain ignored. Update AGENTS.md git protocol rule to match.
65 lines
3.3 KiB
Markdown
65 lines
3.3 KiB
Markdown
# 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 `<pre>` (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) +
|
|
`<main>`: 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
|
|
`<script>alert(1)</script>` 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:`.
|