chore(agent): phase roadmap from TODO.md (phases 73-75), clear the file
Convert the two unchecked TODO items into executable phases (Protocol B, appended after the 72 completed phases): - 73_hidden_tab_stream (TODO L3): a merely-hidden browser tab must never stop a generating answer; repro/root-cause decision tree + the pagehide partial-correlation fix + the hidden-tab E2E pin. - 74_llm_chat_history (TODO L4, history): client-provided history in POST /api/chat (stateless, A10) mapped through both the deflected and grounded agent paths, prior thinking blocks preserved via reasoning_content, capped oldest-first; mock echo marker + E2E. - 75_save_doc_full_session (TODO L4, save-as-doc): the Save-as-doc draft body becomes the full session transcript; edit-out happens in the existing doc-edit body; multi-turn git-verified E2E. Owner-confirmed assumptions A1-A7 are recorded as ASSUMPTION lines in the task files. TODO.md is cleared (items now live in .agents/phases/todo/).
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Phase 73 — Hidden tab never stops a generating answer
|
||||
|
||||
**Source:** `TODO.md` L3 — "Clicking on another tab while an answer is generating stops that answer from being generated. Reponses should continue to generate unless you outright close the tab."
|
||||
**Story:** n/a (TODO-derived)
|
||||
**Context:** `frontend/assets/app.js` (the SSE turn machine: `runTurn` ~L1859, the `pagehide` partial-persist handler ~L2162, the 120s pre-token guard `TURN_TIMEOUT_MS` ~L287 / `armTurnTimeout` ~L982, `readSSE` ~L1044, the settle paths `done` ~L2011 / stop ~L2079), `app/api/chat.py` (the `finally` "turn cancelled" log line — the server-side signal that the SSE consumer really went away), phase-48 teardown contract (a REAL consumer departure — tab closed, navigation, Stop — still cancels the fetch and stops the model: that behavior is correct and must survive this phase).
|
||||
|
||||
## Objective
|
||||
An in-flight answer keeps generating while the browser tab is merely hidden (switched away from) and completes when the user returns; only closing the tab, navigating away, or clicking Stop aborts the turn. Also fixes the latent record-corruption on that path (a `pagehide` partial persist can leave a duplicated brain turn in the saved conversation, which makes the answer *look* truncated on restore).
|
||||
|
||||
## Dependencies
|
||||
— (none)
|
||||
|
||||
## Tasks
|
||||
1. `01_repro_root_cause.md` — bounded repro with an instrumentation decision tree; pin down WHICH mechanism stops the answer on tab switch (no permanent code changes).
|
||||
2. `02_fix_hidden_tab.md` — the fix: correlate the pagehide partial with the turn's settle so `done`/stop *replaces* it (never appends a second brain turn); if the repro implicates the 120s guard, make hidden time not count toward it; keep phase-48 teardown for real departures.
|
||||
3. `03_e2e_hidden_tab_stream.md` — Playwright regression: synthetic `pagehide` mid-stream → the answer completes exactly once, the record has one brain turn, reload restores it; regressions + commit.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration: frontend-only phase — no `app/` changes expected (coverage floor unaffected, must stay **>90%** on `app/`).
|
||||
- E2E: new story suite `tests/e2e/test_hidden_tab_stream.py`, run in isolation (`uv run pytest tests/e2e/test_hidden_tab_stream.py -v --no-cov`) against the deterministic mock LLM (long/slow deterministic streams give a guaranteed mid-stream window).
|
||||
- Regression runs in isolation: `test_chat_rag.py`, `test_chat_persistence.py`, `test_chat_history.py` (phase 50), `test_stop_generation.py` (phase 48 contract: real Stop/cancel still tears down), `test_retry_answer.py`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] The repro's root cause is named in the phase-73 commit message body (one line: which candidate from task 01 fired, or "none of C1–C3 — <finding>").
|
||||
- [ ] `uv run pytest tests/e2e/test_hidden_tab_stream.py -v --no-cov` green in isolation: a tab switch (synthetic `pagehide`) mid-turn never stops the answer, and the persisted conversation holds exactly one brain turn for that question.
|
||||
- [ ] Real departures unchanged: Stop button, tab close, and navigation still cancel the fetch (phase-48 `test_stop_generation.py` + `test_chat_persistence.py` green).
|
||||
- [ ] `uv run pytest` green; `uv run pytest --cov=app --cov-report=term-missing` >90%; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] One atomic `--no-gpg-sign` Conventional-Commits commit (e.g. `fix(chat): keep generating while the tab is hidden`); phase dir moved to `.agents/phases/complete/`.
|
||||
Reference in New Issue
Block a user