Files
brain-of-reese/.agents/phases/complete/130_chat_answer_clusters/00_phase.md
T
ducoterra c88c65641a
Build and Push Containers / build-and-push-app (push) Successful in 2m27s
Build and Push Containers / build-and-push-db (push) Successful in 12s
phase: 130_chat_answer_clusters
**Phase 130 — final verification pass: 100% green, complete.**

- **Verified:** chips-only row (no chip for image docs) → images-only row (`.msg-images`) → related row → `.msg-actions` last; `getActionsRow` used by all 3 buttons + stopped/failed notes; `markLastRetryable` save-re-append hack gone; in-place chip fallback on image-load failure; CSS right-align with per-button auto margins removed — implementation matches the design in every detail.
- **Completion criteria:** (1) mixed citation renders chips→images→actions-last, DOM-asserted ✅ (2) all 3 buttons in one right-aligned `.msg-actions`; non-admin sees Retry-only; restore path identical ✅ (3) 404-bytes figure → plain chip, unit-pinned ✅ (4) E2E isolated ✅ (5) no behavior change (modal affordances, related tier, ARIA names) ✅
- **Tests/lint/coverage:** `uv run pytest tests/e2e/test_chat_answer_clusters.py -v --no-cov` → 4 passed; `uv run pytest --cov=app --cov-report=term-missing` → 3061 passed, **99%** app coverage; the 4 E2E files this phase modified (test_answer_images, test_image_documents, test_pdf_support, test_responsive_polish) → 22 passed in isolation; `uv run ruff check .` clean, `uv run pyright` → 0 errors.
- **Docs:** README.md — verified consistent in full (page table vs routes, env-var coverage vs `.env.example`, phase-130 cluster wording already in tree); no changes needed. AGENTS.md — fixed one pre-existing inconsistency: rule 5 still cited the retired "centered 46rem chat column"; now states the 72rem container contract (phase 100).
- **Decisions:** PLAN.md §7.1 still says 46rem, but it's owner-managed (phase 100 records the supersession pending owner sign-off) and out of my editable scope — AGENTS.md now cross-references the supersession.
- **Next pending phase:** `131_read_line_ranges` (todo/).
2026-09-28 07:49:37 -04:00

6.9 KiB

Phase 130 — Chat answer clusters: chips row, images row, bottom-right actions cluster

Source: TODO.md L6 — "The chat output is getting messy. I think it's because the source-chip, source-image, tune-btn, retry-btn, and save-as-doc-btn end up in the same div which causes them to push each other in weird ways. Those three buttons should be in a button cluster in the bottom right. We don't need to display the source-chip for images, just the image itself is fine. source-chips should show up before images in their own cluster, then images in their own cluster to keep things organized." Story: n/a (TODO-derived phase, Protocol B append). Context: frontend/assets/app.js — appendSources (~L1561: builds the FIRST .msg-meta row, interleaving a .source-image figure after each chip for refs carrying image_url), appendRelated (~L1618: the "Nearby docs" row, appended LAST among meta rows), appendSourceImageFigure (~L1660: the compact figure; on img error it removes the figure and the chip remains), appendAnswerImage (~L1735: the .answer-image figure — appended by callers right BEFORE appendSources), appendTuneButton (~L484, admin-gated), appendRetryButton (~L598), appendSaveAsDocButton (~L793, admin + docs-repo gated; margin-inline-start: auto pushes it right), markLastRetryable (~L620: re-appends the Retry button on the last bubble and re-appends the save button after it — "the auto margins split the free space"), appendStoppedNote / appendFailedNote (~L520/~L560: join the first .msg-meta row); frontend/assets/styles.css — the .msg-meta, .source-chip, .source-image, .tune-btn, .retry-btn, .save-as-doc-btn rules (grep each — the auto-margin rules live here); frontend/index.html — no chat markup changes expected (the bubble DOM is JS-built); E2E image-KB fixture patterns in tests/e2e/test_chat_image_questions.py / test_image_documents.py (the mock's IMAGE_DESCRIPTION_MODE canned vision reply makes image docs retrievable).

Objective

A completed brain bubble's metadata reads as organized clusters, top to bottom: the answer text → the answer figure (phase 124, unchanged) → the source chips row (text documents only — an image document gets NO chip, just its figure) → the source images row (all retrieved image figures) → the "Nearby docs" row (unchanged) → the actions row (a right-aligned cluster holding Tune / Retry / Save-as-doc and the Stopped/Failed notes) — never one soup div where buttons push chips around.

Dependencies

  • 129_sync_chunk_progress (todo) — pipeline order only; code dependencies are the completed chat-render contract (phases 26/49/59/113/120/122/124).

Design (shared by all tasks — the executor reads this, not the chat)

  • Row structure per brain bubble (top → bottom): (1) answer text; (2) .answer-image figure — the phase-124 position is UNCHANGED (below the text, before the source rows — A3); (3) .msg-meta chips row (role=list, aria-label="Sources") — one .source-chip per NON-image ref; refs with image_url contribute NO chip (ASSUMPTION A3 — the figure is the citation for image docs); (4) .msg-meta.msg-images images row (role=list, aria-label="Source images") — one .source-image figure per image ref, in ref order; the row is absent when there are no image refs; (5) .msg-meta.related-docs — the phase-113 row, UNCHANGED in content, placed after the images row (ASSUMPTION A4); (6) .msg-meta.msg-actions actions row (role=list, aria-label="Answer actions") — the ONLY row the action buttons + the stopped/failed notes join (ASSUMPTION A7).
  • Call order at every render site (the done branch, the empty-answer fallback, the stop finalize, the phase-14 restore path): answer image → appendSources (now builds the chips row AND the images row) → appendRelated → the action buttons/notes. The current "buttons claim the FIRST .msg-meta row" behavior is REPLACED — the buttons get-or-create .msg-actions instead.
  • Image-load failure (ASSUMPTION A3): appendSourceImageFigure's img error handler replaces the figure IN PLACE with a plain .source-chip anchor for that document (same documentUrl href, same modal click behavior, the label as text) — the citation survives a lost byte copy; no broken-image icon.
  • Actions row: one get-or-create .msg-actions per bubble (the house pattern). DOM order = Tune, Retry, Save-as-doc (each present per its gate — non-admins see only Retry; a stopped bubble sees the note + Retry). The note (appendStoppedNote / appendFailedNote) joins this row. markLastRetryable's re-append logic moves the Retry button into .msg-actions and DROPS the save-button re-append hack (row-level alignment replaces the per-button margin-inline-start: auto — CSS task). ARIA: role=list + role=listitem on every child (the existing convention).
  • CSS: .msg-actions { justify-content: flex-end; } (the right-aligned cluster — "bottom right" = the bubble's last row, right edge); remove the per-button auto margins (.save-as-doc-btn's margin-inline-start: auto + the tune/retry auto margins the markLastRetryable comment names); the chips row keeps its wrap; .msg-images wraps with the .msg-meta gap; the row ORDER is DOM order (no positioning tricks); mobile: rows stack naturally, the actions row stays right-aligned.

Tasks

  1. 01_sources_clusters.md — appendSources/figure split: chips-only row + images-only row, no chip for image docs, the in-place chip fallback.
  2. 02_actions_cluster.md — the .msg-actions row: the three buttons + the notes, markLastRetryable rework, ARIA kept.
  3. 03_css_clusters.md — the cluster CSS (actions right-aligned, auto margins removed, images row).
  4. 04_e2e_clusters.md — dedicated Playwright E2E, run in isolation.

Testing & Quality

  • Unit: the frontend unit suite — the pinned behaviors that move (the button-row target, the no-chip-for-images rule, the fallback) — inspect tests/unit/test_frontend_*.py for the chat-render pins and update them.
  • E2E: one dedicated Playwright file, run in isolation, image-KB fixture (text doc + image doc both cited).
  • Coverage: >90% on new/modified code (frontend: unit pins + E2E; app/ untouched this phase).

Completion Criteria

  • a bubble citing a text doc + an image doc renders: chips row (text chip only, NO chip for the image) → images row (the figure) → actions row (bottom right) — asserted in the DOM, not the pixels
  • Tune/Retry/Save-as-doc sit in ONE .msg-actions row, right-aligned; non-admin sees only Retry; the restore path renders the identical structure
  • an image whose bytes 404 renders as its plain chip (the citation survives)
  • uv run pytest tests/e2e/test_chat_answer_clusters.py -v --no-cov green in isolation
  • no behavior change in completed phases (chips still open the modal, the related row, the answer figure's position, the ARIA names)