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.7 KiB
3.7 KiB
Story: Sources Mid-Stream — an in-flight answer is not lost on navigation
Phase: 20_sources_midstream_bug · E2E: tests/e2e/test_sources_midstream_bug.py
Bug report (TODO.md L3, verbatim)
"Clicking "sources" while chat is generating clears chat and result will never show up"
Narrative
As a user with a question in flight, I want to click "Sources" while Brain is still generating without losing the answer — when I come back to the chat, what had already streamed should be waiting for me.
- Given I asked a question and Brain's answer is still streaming
- When I click the "Sources" nav link (or otherwise leave the chat page) mid-stream
- Then the stream is aborted by the navigation (no server-side
resume — A10 stays stateless), but returning to
/shows my question and the partial answer that had already streamed, rendered exactly like any brain message (thinking block included, if any had streamed).
Owner-confirmed (2026-08-24, roadmap A1)
- A partial answer is persisted as a plain brain message — no "(partial)" marker, no sources/suggestions (the turn is dead; the user can re-ask for the full answer).
- Navigation before the first answer token (pure thinking) persists nothing brain-side: the question is restored, no empty/partial bubble.
- The deliberate New Chat
clearChatStorage()(sources/viewer pages) is untouched — that clear is by design (phase 14/19). - No server-side resume (A10 stays stateless) and no "leave page?" confirmation dialog.
Acceptance criteria
pagehide(navigate-away / bfcache store) with an in-flight turn (thinkingorstreaming) and non-empty streamed text persists the partial raw answer via the existingrememberBrainTurnsave-point helper (idempotent per turn —persistedOnLeave).- Returning to
/restores the question and the partial brain message through the existing restore path (escape-first markdown, optional collapsed thinking block) — no new UI surface, no error banner. - Navigation during pure thinking (no answer tokens) persists nothing brain-side: exactly the user message is restored.
- A completed turn persists exactly as before (done metadata intact; no duplicate brain message).
- New Chat from the sources page still clears the conversation
(
clearChatStorage, phase 14/19 — untouched). - Unit + integration green,
app/coverage ≥ today's number (>90% gate), story E2E green in isolation, one--no-gpg-signcommit.
Playwright Mapping Rule
Test Scenario → tests/e2e/test_sources_midstream_bug.py (mock LLM,
seeded KB):
test_partial_answer_survives_sources_nav_midstream— admin (login,next=/), send the ~9s long-answer question, wait for the first streamed frames to render, click#nav-sources(lands on/sources.html),page.goto("/")→ question + partial answer rendered, norole="alert",bor.chat.v1holds the partial brain message (starts with the first streamed chunk, shorter than the full answer, no done metadata).test_no_orphan_brain_message_when_navigated_before_first_token— the mock streams the phase-17 thinking scratchpad, then a 4s pre-token pause; navigate during the pause, return to/→ exactly one user message, zero brain messages, in the DOM and in storage.test_completed_turn_unaffected— a fully settled turn → trip to Sources and back → full answer, source chip, and a byte-identicalbor.chat.v1payload (done metadata intact, no duplicate).test_new_chat_still_clears_conversation— completed turn →/sources.html→ click the sources-page New Chat button → lands on/with the empty state andbor.chat.v1removed.