Standardize on the .agents/ directory (shared with project skills): phases/, user_stories/, reports/, screenshots/, validate.sh, and phase-sessions/ + pipeline.log all move to .agents/ (git mv preserves history; runtime artifacts move alongside). Updates every reference in AGENTS.md, README.md, .gitignore, app docstrings, and test story headers. Historical KB content in data/ and the runtime pipeline.log transcript are left untouched.
74 lines
3.7 KiB
Markdown
74 lines
3.7 KiB
Markdown
# 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)
|
|
1. **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).
|
|
2. Navigation **before the first answer token** (pure thinking) persists
|
|
nothing brain-side: the question is restored, no empty/partial
|
|
bubble.
|
|
3. The deliberate **New Chat** `clearChatStorage()` (sources/viewer
|
|
pages) is untouched — that clear is by design (phase 14/19).
|
|
4. No server-side resume (A10 stays stateless) and no "leave page?"
|
|
confirmation dialog.
|
|
|
|
## Acceptance criteria
|
|
1. `pagehide` (navigate-away / bfcache store) with an in-flight turn
|
|
(`thinking` or `streaming`) and non-empty streamed text persists the
|
|
partial raw answer via the existing `rememberBrainTurn` save-point
|
|
helper (idempotent per turn — `persistedOnLeave`).
|
|
2. 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.
|
|
3. Navigation during pure thinking (no answer tokens) persists nothing
|
|
brain-side: exactly the user message is restored.
|
|
4. A completed turn persists exactly as before (done metadata intact;
|
|
no duplicate brain message).
|
|
5. New Chat from the sources page still clears the conversation
|
|
(`clearChatStorage`, phase 14/19 — untouched).
|
|
6. Unit + integration green, `app/` coverage ≥ today's number (>90%
|
|
gate), story E2E green in isolation, one `--no-gpg-sign` commit.
|
|
|
|
## Playwright Mapping Rule
|
|
**Test Scenario → `tests/e2e/test_sources_midstream_bug.py`** (mock LLM,
|
|
seeded KB):
|
|
1. `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, no `role="alert"`, `bor.chat.v1` holds the partial brain
|
|
message (starts with the first streamed chunk, shorter than the full
|
|
answer, no done metadata).
|
|
2. `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.
|
|
3. `test_completed_turn_unaffected` — a fully settled turn → trip to
|
|
Sources and back → full answer, source chip, and a byte-identical
|
|
`bor.chat.v1` payload (done metadata intact, no duplicate).
|
|
4. `test_new_chat_still_clears_conversation` — completed turn →
|
|
`/sources.html` → click the sources-page New Chat button → lands on
|
|
`/` with the empty state and `bor.chat.v1` removed.
|