4.3 KiB
4.3 KiB
Phase 26 — Document Modal Viewer
Source: TODO.md L4 — "New documents should open in an almost-fullscreen modal, not in a new page"
Story: .agent/user_stories/document-modal.md
Context: Phase 10 added the separate /document.html viewer page; phase 19 added the shared header bar that now lives on every page. The document content is served by the stateless GET /api/documents/content endpoint (PLAN §4).
Objective
Stop opening cited documents in a new page/tab. Clicking a source chip or a Sources-table path link now opens the document in an almost-fullscreen modal overlay on the current page, fed by the same /api/documents/content endpoint. The existing /document.html page stays as the no-JS / direct-link fallback and its behaviour is unchanged.
Dependencies
10_story_document_viewer(complete) — the/document.htmlpage, thedocument.jsrenderer, therenderMarkdownescape-first renderer inmarkdown.js, and the#doc-content/.doc-md/.doc-rawmarkup this phase reuses inside the modal.19_shared_header(complete) — the shared header bar the modal sits under; the modal must not disturb the header.08_story_dark_tech_theme(complete) — the Phase-08 tokens and the ≥4.5:1 contrast /prefers-reduced-motioncontract the modal must honour.
Tasks
01_modal_css_and_html.md— the modal CSS (overlay, backdrop, close button, scrollable content area) + inject the modal skeleton intoindex.html02_app_js_modal_intercept.md— intercept document links inapp.js+sources.js, fetch content via/api/documents/content, render inside the modal03_document_js_modal_mode.md— adaptdocument.jsto optionally render in modal mode (reuse the same API call) for the direct-link fallback path04_e2e_regression_suite.md— updatetest_document_viewer.pyto verify modal behaviour; the story gate, run in isolation
Testing & Quality
- Unit/integration: none required for the modal itself (frontend-only); the
/api/documents/contentendpoint is unchanged (noapp/change → no coverage delta). - Coverage: frontend-only; the >90%
app/gate is unaffected. - E2E:
tests/e2e/test_document_viewer.pyrewritten for the modal contract (task 4), green in isolation (prereqpodman compose up -d db).
Completion Criteria
- Clicking a source chip (chat) or a Sources-table path link opens the document in an almost-fullscreen modal on the same page (no new tab, no navigation).
- The modal renders the same content the
/document.htmlpage renders: md/markdown via the shared renderer (.doc-md), other formats in<pre class="doc-raw">, source/format/path/indexed/chunks meta. - The modal has a visible close control, closes on Escape, closes on backdrop click, and keeps the dark theme + a11y frame (skip-link, focus trap,
:focus-visible, aria-label). - The existing
/document.htmlpage still works unchanged (direct link, back button, XSS-safe rendering, not-found state). - No CDN tags on any touched page; every asset reference is same-origin or
data:. uv run pytestgreen;uv run pytest --cov=app --cov-report=term-missingTOTAL ≥ pre-change number (gate >90%).uv run pytest tests/e2e/test_document_viewer.py -v --no-covgreen in isolation.uv run ruff check . && uv run pyrightclean.- UI Structure Check (AGENTS.md rule 5): modal content uses the standard centered column width for md; backdrop behind content; no 360px overflow.
.agent/user_stories/document-modal.mdexists.- One
--no-gpg-signcommit staging only this phase's files;.agent/phases/todo/26_document_modal_viewer/moved to.agent/phases/complete/.
Locked decisions
- No backend change — the modal reuses
GET /api/documents/contentunchanged (A10 untouched: the API stays stateless). - A11 untouched — vanilla HTML/CSS/JS, no CDN, zero new packages, no new assets, system font stack; the modal is pure CSS + JS.
- No anchor revised — this is a UI-behaviour change (PLAN §7.5 gains
#doc-modal,#doc-modal-backdrop,#doc-modal-close,#doc-modal-content); the/document.htmlpage and its story are unchanged. - A16 honoured — one story E2E suite (rewritten) + adapted regressions.
- A17 honoured — one atomic
--no-gpg-signcommit.