chore(agent): phase roadmap from TODO.md — 3 phases (138–140)
Build and Push Containers / build-and-push-app (push) Successful in 2m37s
Build and Push Containers / build-and-push-db (push) Successful in 12s

This commit is contained in:
2026-09-30 09:35:07 -04:00
parent 36ccf64f6a
commit 0db6ab5df0
13 changed files with 376 additions and 0 deletions
@@ -0,0 +1,45 @@
# Phase 138 — Markdown blockquotes render: `>` quotes become styled `<blockquote>`s
**Source:** `TODO.md` L3 — "Markdown formatted ">" quotes aren't rendering"
**Story:** n/a (TODO-derived phase, Protocol B append).
**Context:** `frontend/assets/markdown.js` — the shared escape-first renderer (loaded by all three pages: `index.html` chat, `document.html` viewer, `shared.html`): step 1 fence protection (`\u0000CODEn\u0000`), step 1b table protection (`\u0000TABLEn\u0000`, phase 44), step 2 `inline(escapeHtml(text))` (the inline chain: code → strong → em → h3/h4 → ul → ol), step 3 the paragraph pass (split on `\n{2,}`; the block-element guard `/^<(h\d|ul|ol|pre|li)/`), step 4 restore. Today a `>` line is escaped to `&gt;` and renders as literal text in a `<p>` (verified: `renderMarkdown("> quoted")` → `<p>&gt; quoted</p>`). `frontend/assets/styles.css` — the `:root` palette (`--ink-soft` 8.2:1 on `--surface`, `--line`, the `--brand-*` family; the `:root`-drift unit test parses the built-ins from this file — no second copy) and the chat-bubble / viewer typography. `tests/unit/test_markdown_tables.py` — the frontend source-pin + node-behavior unit pattern (`HAVE_NODE` skip); `tests/e2e/test_markdown_tables.py` — the phase-44 E2E pattern (mock flow with table output + the document viewer).
## Objective
`>`-prefixed lines in any rendered markdown — a chat answer, the thinking block, a saved/shared chat (restore), the document viewer — render as a semantic `<blockquote class="md-quote">` with theme-safe styling; every non-quote input renders byte-identical to today (the phase-44 table suite and the renderer-dependent unit suites stay green unedited).
## Dependencies
- `137_pdf_image_context` (complete) — pipeline order only; the code dependencies are the shared renderer + `styles.css` on `main`.
## Design (shared by all tasks — the executor reads this, not the chat)
- **The pass (task 01):** a new line-block pass in `markdown.js` BETWEEN step 2 (`inline(escapeHtml(text))`) and step 3 (paragraphs) — at that point the text is escaped (a quote line starts with `&gt;`) and already inline-transformed (bold/emphasis/code work inside quotes for free — the pass never re-transforms). Group the maximal run of consecutive lines matching `^\s*&gt;+ ?` into ONE `<blockquote class="md-quote">…</blockquote>`: strip the leading `&gt;` run plus ONE optional space from each member (nested `>>` / `>>>` collapses to one level — ASSUMPTION A1), join members with `<br>` (the paragraph convention). A BLANK line terminates the group (a re-quoted line starts a new `<blockquote>` — simple, predictable). The step-3 guard extends to `/^<(h\d|ul|ol|pre|li|blockquote)/` so the paragraph pass leaves the block alone (no `<p>` wrap).
- **Precedence is untouched:** fences protect first (step 1 — a `>` inside a code block never reaches the pass), tables protect second (step 1b — cell content is escaped inside the `<td>` markup and restored in step 4, AFTER the pass). No other pass changes.
- **In-quote structure (documented limit):** a markdown STRUCTURE inside a quote (a `- list` line, a `# heading` line) renders as plain text inside the blockquote — the inline chain runs before the pass, so its line-start transforms never see the post-strip text. Inline (code/strong/em) still works. Acceptable — the scope is quote rendering (owner L3).
- **The CSS (task 02):** `.md-quote` in `styles.css` — theme-safe from the EXISTING `:root` palette only: left border + horizontal padding + vertical margin + a muted text color carrying a ≥4.5:1 contrast comment on the bubble surface (the house comment convention, cf. `--ink-soft`'s `/* 8.2:1 on --surface */`). ONE rule set serves all three contexts (chat `.bubble`, `.thinking-text`, the document viewer) — a scoped override only if the E2E proves a clash.
- **Byte-identity (task 03):** any input without a quote line renders byte-identical to the pre-phase renderer — pinned by the node behavior check and by the unedited `test_markdown_tables.py` / `test_thinking_scroll.py` / `test_document_viewer.py` suites.
- **E2E (task 03):** dedicated `tests/e2e/test_markdown_blockquotes.py`, run in isolation (AGENTS.md rules 4/9), mock-only.
## Tasks
1. `01_blockquote_pass.md` — the blockquote pass in `markdown.js` + the paragraph-guard extension.
2. `02_blockquote_css.md` — the `.md-quote` styling in `styles.css` (theme-safe, WCAG AA contrast).
3. `03_unit_and_e2e.md` — unit source-pins + node behavior + the dedicated Playwright E2E.
## Testing & Quality
- Unit: `tests/unit/test_markdown_blockquotes.py` (new — the phase-44 pattern): source pins (pass order fences → tables → escape+inline → **blockquotes** → paragraphs → restore; the guard regex includes `blockquote`; the exact `<blockquote class="md-quote">` marker; the `.md-quote` CSS rule references palette vars only) + node behavior (single-line quote, multi-line group, nested collapse, XSS quote inert, fence-wins, a table cell containing `>` untouched, non-quote input byte-identical).
- E2E: `tests/e2e/test_markdown_blockquotes.py` (new), in isolation.
- Coverage: **>90%** on `app/` (the gate runs the full suite — this phase adds no `app/` code, so the suite staying green IS the gate).
## Completion Criteria
- [ ] `>` quotes render as a styled `<blockquote class="md-quote">` in the chat answer, the thinking block, saved/shared chats (restore), and the document viewer
- [ ] nested `>>` collapses to one level; a `>` inside a code fence or a table cell is untouched
- [ ] non-quote input renders byte-identical (the phase-44 / renderer-dependent suites pass unedited)
- [ ] `uv run pytest` green; `uv run pytest --cov=app --cov-report=term-missing` TOTAL >90%; `uv run pytest tests/e2e/test_markdown_blockquotes.py -v --no-cov` green in isolation; `uv run ruff check . && uv run pyright` clean
- [ ] one `--no-gpg-sign` Conventional Commit; phase dir moved to `.agents/phases/complete/` by the pipeline gate
## Locked decisions
- **A1 — nested quotes (`>>`, `>>>`) collapse to a single `<blockquote>` level (owner-confirmed 2026-09-30, roadmap confirmation).**
- **A2 — the quote style is a left border + padding + muted text color (NO background fill), built from the existing `:root` palette only (owner-confirmed 2026-09-30, roadmap confirmation).**
## Commit
```bash
git add frontend/ tests/ .agents/phases/ && git commit --no-gpg-sign -m "feat(ui): render markdown blockquotes in chat, viewer, and shared pages"
```