phase: 158_thinking_timeline
All verification complete. Every gate is green and the docs check out. Final report:
## Phase 158 final verification pass — COMPLETE
**Verified (all tasks pre-completed; this pass re-ran the full gate):**
- Unit + integration: `uv run pytest --cov=app --cov-report=term-missing` → **3618 passed, 99% coverage on `app/`** (>90% gate)
- Phase E2E in isolation: `uv run pytest tests/e2e/test_thinking_timeline.py -v --no-cov` → **6/6 passed** (order pin, fold-at-rest, live-open, restore+`segments` array, shared page, no-thinking single disclosure)
- Regression E2E, each in isolation: `test_thinking_display` 5✓, `test_tool_call_compact` 4✓, `test_turn_progress_loader` 3✓ (per-run pin updated), `test_big_read_progress` 4✓, `test_agent_document_tools` 4✓, `test_share_chat` 4✓, `test_chat_persistence` 4✓, `test_smoke` 3✓
- Lint/types: `uv run ruff check . && uv run pyright` → **clean (0 errors)**
**Completion criteria:** all 9 met — timeline E2E pins ✓, restore/shared `segments` ✓, no-thinking single-disclosure + the three UNCHANGED regression suites green ✓, pre-phase byte-identical round-trip (integration pin: key absent, never null; >50 / >32 000 / empty text / unknown kind all 422) ✓, full suite + coverage + E2E + lint ✓, `git diff --stat` footprint limited to `app/schemas.py`, `frontend/assets/{app,shared}.js`, `tests/`, README, phase files (a pre-existing owner `TODO.md` edit is out of scope, not staged by the phase commit) ✓, D15→per-run supersession recorded in the updated pin ✓. (Commit + phase move are the harness's job; task dirs already in `complete/`.)
**Documentation:** README.md — Thinking section correctly describes the phase-158 timeline; config reference matches `.env.example` exactly; page table matches registered routes; no stale single-block wording. AGENTS.md — rules and quick-reference commands all accurate. **Verified consistent — no changes needed.**
**Decisions/deviations:** none. `test_tool_call_compact.py` carries a one-line docstring-only update (referenced the removed `closeToolCalls`); its test logic is byte-identical to the phase-117 contract and green.
**Next pending phase:** none — `todo/` contains only 158.
This commit is contained in:
@@ -135,32 +135,42 @@ def test_thinking_event_is_a_first_class_turn_branch() -> None:
|
||||
assert "sawThinking = true" in branch
|
||||
assert "clearTurnTimeout()" in branch, "first thinking frame clears the 300s guard"
|
||||
assert "removeTyping()" in branch, "the live block replaces the typing dots"
|
||||
assert "ensureThinkingBlock(wrap)" in branch
|
||||
assert "renderMarkdown(thinkingAcc)" in branch, "escape-first renderer (XSS-safe)"
|
||||
assert "ensureThinkingRun(wrap)" in branch
|
||||
assert "renderMarkdown(currentRun.run.text)" in branch, (
|
||||
"escape-first renderer (XSS-safe) — the RUN's text (phase 158, D1)"
|
||||
)
|
||||
assert "textEl.scrollTop = textEl.scrollHeight" in branch, "bottom-pinned while open"
|
||||
|
||||
|
||||
def test_thinking_block_helpers_are_idempotent() -> None:
|
||||
"""ensureThinkingBlock returns the existing `.thinking` details or
|
||||
creates it OPEN above the .bubble; closeThinkingBlock is a no-op
|
||||
without a block and never reopens one once the answer started."""
|
||||
def test_thinking_run_helpers_are_idempotent() -> None:
|
||||
"""Phase 158 (D1/D5 — the phase-109 D15 pin, replaced by owner
|
||||
direction 2026-10-04): ``ensureThinkingRun`` returns the active
|
||||
run's block or creates a NEW one OPEN above the .bubble (a frame
|
||||
of a new kind closes the previous run first); ``closeCurrentRun``
|
||||
is a no-op without a run and folds the active one (the D15
|
||||
re-open of the shared block is gone — a post-delta thinking frame
|
||||
opens a NEW block)."""
|
||||
js = _js()
|
||||
fn = js.find("function ensureThinkingBlock")
|
||||
assert fn != -1, "ensureThinkingBlock must exist (near addTyping/removeTyping)"
|
||||
fn = js.find("function ensureThinkingRun")
|
||||
assert fn != -1, "ensureThinkingRun must exist (near addTyping/removeTyping)"
|
||||
body = js[fn : js.find("\n}\n", fn)]
|
||||
assert "block.open = true" in body, "created open — the stream is the show"
|
||||
assert "closeCurrentRun()" in body, ("a frame of a new kind closes the "
|
||||
"previous run (D1)")
|
||||
assert "block.open = true" in body, "D5: created open — the stream is the show"
|
||||
assert "insertBefore" in body
|
||||
assert 'querySelector(".bubble")' in body, "the block sits ABOVE the bubble"
|
||||
fn2 = js.find("function closeThinkingBlock")
|
||||
assert fn2 != -1, "closeThinkingBlock must exist"
|
||||
fn2 = js.find("function closeCurrentRun")
|
||||
assert fn2 != -1, "closeCurrentRun must exist"
|
||||
body2 = js[fn2 : js.find("\n}\n", fn2)]
|
||||
assert "block.open = false" in body2
|
||||
assert "currentRun.el.open = false" in body2
|
||||
assert "currentRun = null" in body2, "a no-op without a run (idempotent)"
|
||||
|
||||
|
||||
def test_delta_branch_collapses_block_and_transitions_to_streaming() -> None:
|
||||
"""The first answer delta transitions thinking → streaming (even when
|
||||
thinking created the wrap first) and auto-collapses the block —
|
||||
idempotent, and it never reopens once the answer started."""
|
||||
thinking created the wrap first) and folds the active run — phase 158
|
||||
(D5): ``closeCurrentRun()`` (a later thinking frame opens a NEW run;
|
||||
the D15 re-open of the shared block is gone)."""
|
||||
js = _js()
|
||||
delta_idx = js.find('ev.type === "delta"')
|
||||
done_idx = js.find('ev.type === "done"')
|
||||
@@ -168,19 +178,20 @@ def test_delta_branch_collapses_block_and_transitions_to_streaming() -> None:
|
||||
branch = js[delta_idx:done_idx]
|
||||
assert "uiState === UI_STATE.thinking" in branch
|
||||
assert "setUiState(UI_STATE.streaming)" in branch
|
||||
assert "closeThinkingBlock(wrap)" in branch
|
||||
assert "closeCurrentRun()" in branch
|
||||
|
||||
|
||||
def test_done_branch_sets_sawdone_and_closes_block() -> None:
|
||||
"""On `done` the turn marks itself complete (sawDone — the stream-drop
|
||||
guard keys off it) and settles the thinking block closed."""
|
||||
guard keys off it) and settles EVERY open run closed (phase 158, D5:
|
||||
``closeAllRuns``)."""
|
||||
js = _js()
|
||||
done_idx = js.find('ev.type === "done"')
|
||||
error_idx = js.find('ev.type === "error"')
|
||||
assert -1 < done_idx < error_idx
|
||||
branch = js[done_idx:error_idx]
|
||||
assert "sawDone = true" in branch
|
||||
assert "closeThinkingBlock(wrap)" in branch
|
||||
assert "closeAllRuns(wrap)" in branch
|
||||
|
||||
|
||||
def test_stream_drop_guard_reports_severed_stream() -> None:
|
||||
@@ -282,10 +293,11 @@ def test_stop_turn_is_the_user_abort() -> None:
|
||||
def test_stop_branch_keeps_partial_and_persists_stopped() -> None:
|
||||
"""The stop path in handleSend's catch: no error state, no error
|
||||
banner; when answer text streamed the partial is kept on screen
|
||||
(thinking block closed, Tune + Stopped note appended — admin parity
|
||||
with the restore path) and persisted with the owner-locked optional
|
||||
`stopped: true` marker (+ optional thinking/tools); a pre-token stop
|
||||
persists nothing brain-side (phase-20 convention). The "Answer
|
||||
(every open run folded — phase 158, D5 — Tune + Stopped note
|
||||
appended — admin parity with the restore path) and persisted with
|
||||
the owner-locked optional `stopped: true` marker (+ optional
|
||||
thinking/tools/segments); a pre-token stop persists nothing
|
||||
brain-side (phase-20 convention). The "Answer
|
||||
stopped." live-region confirmation is set in the finally, AFTER the
|
||||
single settle, so setUiState(idle) can't overwrite it."""
|
||||
js = _js()
|
||||
@@ -301,7 +313,7 @@ def test_stop_branch_keeps_partial_and_persists_stopped() -> None:
|
||||
assert "if (wrap && acc && !persistedOnLeave)" in branch, (
|
||||
"only a partial WITH answer text is persisted (phase-20 dedupe)"
|
||||
)
|
||||
assert "closeThinkingBlock(wrap)" in branch
|
||||
assert "closeAllRuns(wrap)" in branch
|
||||
assert "appendTuneButton(wrap)" in branch, "admin parity with the restore path"
|
||||
assert "appendStoppedNote(wrap)" in branch
|
||||
assert "stopped: true" in branch, "the owner-locked optional marker"
|
||||
|
||||
Reference in New Issue
Block a user