fix(chat): keep the in-flight answer when navigating away mid-turn — partial answer restored on return
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
# Task 01 — app.js: persist the partial answer on navigate-away (pagehide)
|
||||
|
||||
**Phase:** `20_sources_midstream_bug` · **Source:** `TODO.md` L3 —
|
||||
*"Clicking "sources" while chat is generating clears chat and result will
|
||||
never show up"*
|
||||
|
||||
## Objective
|
||||
`frontend/assets/app.js` gains exactly one new save point: on `pagehide`
|
||||
(navigate-away / bfcache), if a turn is in flight and answer text has
|
||||
already streamed, the partial answer is persisted via the existing
|
||||
`rememberBrainTurn` helper — so returning to the chat restores the
|
||||
question **and** what had been generated.
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/app.js`
|
||||
- Next to the other turn-local state (around the `acc` /
|
||||
`thinkingAcc` / `sawThinking` / `aborted` declarations, ~line 920+),
|
||||
declare a turn-local flag:
|
||||
```js
|
||||
let persistedOnLeave = false; // pagehide partial-persist at most once
|
||||
```
|
||||
and reset it to `false` at the top of `runTurn` (where `acc`,
|
||||
`thinkingAcc`, `sawThinking`, `wrap` are initialized), so it is
|
||||
turn-scoped like the rest.
|
||||
- Register one handler at module boot (next to the other
|
||||
`window.addEventListener` calls):
|
||||
```js
|
||||
/* Navigate-away save point (phase 20, owner choice 2026-08-24 A1):
|
||||
* leaving the chat mid-turn would otherwise drop the in-flight
|
||||
* answer — the brain message persists only on `done`, and
|
||||
* navigation aborts the stream. On `pagehide`, if a turn is in
|
||||
* flight and answer text has streamed, persist the partial raw text
|
||||
* (reusing the save-point helper, so restore re-renders it exactly
|
||||
* like a completed answer — no "(partial)" marker, no sources).
|
||||
* Thinking-only (no answer tokens yet) persists nothing brain-side:
|
||||
* the question is already saved on send and the user can re-ask.
|
||||
* `persistedOnLeave` makes this idempotent across pagehide/bfcache
|
||||
* churn. */
|
||||
window.addEventListener("pagehide", () => {
|
||||
if (persistedOnLeave) return;
|
||||
if (uiState !== UI_STATE.thinking && uiState !== UI_STATE.streaming)
|
||||
return;
|
||||
if (!acc) return; // nothing brain-side to save yet
|
||||
persistedOnLeave = true;
|
||||
rememberBrainTurn(acc, { thinking: thinkingAcc || undefined });
|
||||
});
|
||||
```
|
||||
**ASSUMPTION (owner-confirmed A1):** the partial is a plain brain
|
||||
message (no marker, no sources); the variables `uiState`, `acc`,
|
||||
`thinkingAcc` are the turn's existing ones — match the names actually
|
||||
in scope (the file keeps `let acc = ""` / `let thinkingAcc = ""`
|
||||
turn-locals inside `runTurn`; if the handler needs them outside that
|
||||
scope, hoist the turn-locals to module scope *without* changing any
|
||||
behavior — smallest diff wins).
|
||||
- Update the phase-14 persistence block comment (~line 631): the
|
||||
"Save points:" sentence now lists three — the user message on send,
|
||||
the brain message on `done`, and the **partial** brain message on
|
||||
navigate-away (`pagehide`, phase 20).
|
||||
2. `tests/unit/test_sources_midstream.py` (new — follow the repo's
|
||||
source-level pin pattern used by `tests/unit/test_shared_header.py`):
|
||||
- `app.js` contains `addEventListener("pagehide"` exactly once.
|
||||
- The pagehide body is guarded by the in-flight states (`thinking`
|
||||
and `streaming`) and a non-empty `acc` check.
|
||||
- The pagehide body calls `rememberBrainTurn(acc,` with
|
||||
`thinking: thinkingAcc || undefined`.
|
||||
- `persistedOnLeave` is declared and reset in `runTurn`.
|
||||
- The persistence block comment lists the `pagehide` save point.
|
||||
- `clearChatStorage` (header.js) is untouched — still the only
|
||||
deliberate clear.
|
||||
|
||||
## Testing & Quality
|
||||
- `uv run pytest tests/unit/test_sources_midstream.py -v` green.
|
||||
- `uv run ruff check . && uv run pyright` clean.
|
||||
- Manual smoke (dev server, DEBUGPY optional): send a question against
|
||||
the real LLM (or any slow stream), click Sources mid-stream, return —
|
||||
the partial answer is visible.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] The `pagehide` handler exists, is turn-scoped and idempotent, and
|
||||
reuses `rememberBrainTurn` (no duplicated storage code).
|
||||
- [ ] No other save point changed: send + `done` behave byte-identically
|
||||
to before (existing persistence unit pins still pass).
|
||||
- [ ] Unit pins green; lint/types clean.
|
||||
Reference in New Issue
Block a user