Files
brain-of-reese/.agent/phases/complete/26_document_modal_viewer/00_phase.md
T

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.html page, the document.js renderer, the renderMarkdown escape-first renderer in markdown.js, and the #doc-content / .doc-md / .doc-raw markup 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-motion contract the modal must honour.

Tasks

  1. 01_modal_css_and_html.md — the modal CSS (overlay, backdrop, close button, scrollable content area) + inject the modal skeleton into index.html
  2. 02_app_js_modal_intercept.md — intercept document links in app.js + sources.js, fetch content via /api/documents/content, render inside the modal
  3. 03_document_js_modal_mode.md — adapt document.js to optionally render in modal mode (reuse the same API call) for the direct-link fallback path
  4. 04_e2e_regression_suite.md — update test_document_viewer.py to verify modal behaviour; the story gate, run in isolation

Testing & Quality

  • Unit/integration: none required for the modal itself (frontend-only); the /api/documents/content endpoint is unchanged (no app/ change → no coverage delta).
  • Coverage: frontend-only; the >90% app/ gate is unaffected.
  • E2E: tests/e2e/test_document_viewer.py rewritten for the modal contract (task 4), green in isolation (prereq podman 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.html page 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.html page 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 pytest green; uv run pytest --cov=app --cov-report=term-missing TOTAL ≥ pre-change number (gate >90%).
  • uv run pytest tests/e2e/test_document_viewer.py -v --no-cov green in isolation.
  • uv run ruff check . && uv run pyright clean.
  • 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.md exists.
  • One --no-gpg-sign commit 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/content unchanged (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.html page and its story are unchanged.
  • A16 honoured — one story E2E suite (rewritten) + adapted regressions.
  • A17 honoured — one atomic --no-gpg-sign commit.