4.0 KiB
Phase 36 — Document Summary Shown Together With the Original
Source: TODO.md L5 — "When I click on a document with a summary I should be able to see the summary and the original document together."
Story: .agent/user_stories/summary-in-viewer.md
Context: Phase 30 stores a lite-model summary on documents.summary for every non-markdown document (plus an indexed is_summary chunk) — but the viewer never shows it: GET /api/documents/content omits the field and the shared renderer renderDocument (frontend/assets/document.js, used by BOTH the full-page viewer document.html and the chat/sources modal, phase 26) only renders the raw content. Markdown documents carry no summary (phase 30) and must render exactly as before.
Objective
When a document has a summary, show it and the original content together — a labeled Summary panel above the content, on both viewer surfaces at once (shared renderer); documents without a summary are unchanged.
Dependencies
30_document_summaries(complete) — thedocuments.summarycolumn (migration 0004), the summarizer, thesummary_kbE2E fixture + the deterministic mockSUMMARY_MODEdigest.10_story_document_viewer+26_document_modal_viewer(complete) — the viewer page, the modal, and the sharedrenderDocument(doc, {titleEl, metaEl, contentEl})core both surfaces render through.16_admin_auth(complete) — the soft rule this phase must not touch: the content endpoint stays public + stateless (catalog gated, viewer public).
Tasks
01_content_api_summary_field.md—DocContent.summary+ the endpoint returns it; integration tests.02_viewer_summary_panel.md— the shared renderer draws the.doc-summarypanel (both surfaces) + theme-matched styles.03_e2e_and_regression.md— the story E2E suitetest_summary_in_viewer.py; regressions; full gate; commit.
Testing & Quality
- Unit/integration: the content endpoint returns the summary for a summarized non-markdown doc and
nullfor a markdown doc; anonymous access unchanged. - Coverage: >90% on
app/(the touched endpoint stays covered). - E2E (mandatory, A16):
tests/e2e/test_summary_in_viewer.py— the story gate, run in isolation.
Completion Criteria
GET /api/documents/contentreturnssummary(string or null); no auth/shape change beyond the added nullable field; the endpoint is still public.- A summarized document shows the labeled Summary panel above the original content in the full-page viewer AND the modal; the original content (including content the summary digest doesn't contain) is fully visible.
- A markdown document (no summary) renders exactly as before on both surfaces — no empty panel.
uv run pytestgreen;uv run pytest --cov=app --cov-report=term-missing>90%;uv run pytest tests/e2e/test_summary_in_viewer.py -v --no-covgreen in isolation; regressions (task 03 list) green.uv run ruff check . && uv run pyrightclean.- UI Structure Check (AGENTS.md rule 5): the panel is a labeled section, contrast ≥4.5:1, no CDN (rule 6).
- One
--no-gpg-signcommit; phase directory moved to.agent/phases/complete/.
Locked decisions
- A7 / A15 untouched — retrieval, context assembly, and the SSE contract are unchanged; this is a display + API-field phase.
- Phase 16 soft rule untouched —
GET /api/documents/contentstays public + stateless (anyone who can open a document sees its summary; the catalog stays admin-gated). - Phase 30 untouched — summaries are still generated at import, still markdown-excluded, still fail-soft (NULL possible); this phase only surfaces the existing field.
- Shared-renderer principle (phase 26) — the panel is drawn in
renderDocument, so the page and the modal can never drift. - A11 untouched — vanilla HTML/CSS/JS, no CDN, no new packages; summary text rendered with
textContent(XSS contract unchanged). - A16 / A17 honoured — one new story E2E suite + one atomic
--no-gpg-signcommit.