Remove the blanket .agent/ gitignore so the phase roadmap, user stories, reports, and PLAN.md are versioned with the code. Only runtime artifacts (.agent/phase-sessions/, .agent/pipeline.log) remain ignored. Update AGENTS.md git protocol rule to match.
3.3 KiB
Phase 49 — Retry the Last Answer (Redo)
Source: TODO.md L4 — "Need a retry button to retry the last answer, like a redo button"
Story: n/a (TODO-derived — owner roadmap confirmation 2026-08-29)
Context: frontend/assets/app.js — handleSend owns the whole turn (user-bubble append + conversation push → fetch/SSE → save points), appendTuneButton is the house pattern for a per-bubble meta-row action (reuses the .msg-meta row, the role=list → role=listitem rule, the admin gate), conversation/rememberBrainTurn hold the persisted records, and phase 48's Stop button means the last brain bubble can be a stopped partial — the natural retry candidate.
Objective
A Retry button on the last brain answer re-asks the preceding question in place — the old answer is removed (DOM + persisted record), the fresh answer streams into its place, and the conversation never duplicates the question.
Dependencies
48_stop_generation(todo) — the button/state-machine work it builds on (the meta-row button family, the in-flight guard) and the stopped partials it makes retryable.14_chat_persistence(complete) — the conversation records the retry edits in place.
Tasks
01_retry_button.md— therunTurnextraction + the Retry button + redo-in-place semantics + CSS.02_e2e_retry_answer.md— the story Playwright suite + regressions + commit.
Testing & Quality
- Frontend source pins (house pattern): the
runTurn(text, {reask})extraction (reask skips the user append/push), last-bubble-only Retry management, the redo-in-place record edit, the in-flight no-op. - Coverage: >90% on
app/(unchanged — frontend-only phase). - E2E (mandatory, A16):
tests/e2e/test_retry_answer.py, run in isolation.
Completion Criteria
- The last brain bubble (completed, deflected, empty-fallback, or stopped partial — admin and anonymous alike) carries a Retry button; earlier brain bubbles do not.
- Clicking Retry removes the old answer (DOM +
bor.chat.v1record), re-sends the preceding question without duplicating it, and streams the fresh answer into its place; the new answer carries the Retry button (it is the new last). - While a turn is in flight, Retry does nothing (no double turn).
uv run pytestgreen; coverage TOTAL unchanged (>90%).uv run pytest tests/e2e/test_retry_answer.py -v --no-covgreen in isolation (DB up).- Regression E2E suites green in isolation:
test_chat_rag.py,test_chat_persistence.py,test_stop_generation.py. uv run ruff check . && uv run pyrightclean.- One
--no-gpg-signcommit; phase dir moved to.agent/phases/complete/.
Locked decisions
- Owner-locked (2026-08-29, roadmap confirmation): redo-in-place (the old answer is replaced, the question is not duplicated); only the last brain bubble is retryable; Retry is available to all visitors (chat is public — unlike Tune, which is admin-only); inert while a turn is in flight.
- A10 untouched — retry is a fresh
POST /api/chat(the API stays stateless). - A16/A17 honoured — one story E2E suite, one atomic commit.
Commit
git add -A .agent/ frontend/ tests/ && git commit --no-gpg-sign -m "feat(chat): retry the last answer — redo-in-place Retry button on the latest brain bubble"