refactor(agents): migrate .agent/ planning tree to .agents/

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.
This commit is contained in:
2026-09-05 10:57:07 -04:00
parent 766702c750
commit dbf2af26c6
1118 changed files with 664 additions and 664 deletions
+73
View File
@@ -0,0 +1,73 @@
# 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.