phase: 158_thinking_timeline
Build and Push Containers / build-and-push-app (push) Successful in 8m38s
Build and Push Containers / build-and-push-db (push) Successful in 32s

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:
2026-10-05 02:52:55 -04:00
parent 3f752fb77f
commit 31bd903a93
49 changed files with 4280 additions and 482 deletions
+50 -27
View File
@@ -63,8 +63,14 @@ def test_tool_branch_is_a_first_class_turn_branch() -> None:
"label updates only while the state is still thinking"
)
assert (
"appendToolLine(wrap, name, argument, frameOffset, frameLimit, framePath)"
"const line = appendToolLine("
in branch
) and "armToolLineClock(wrap, line)" in branch, (
"the run's disclosure gets the line; the clock targets it"
)
assert "ensureToolRun(wrap)" in branch, (
"phase 158 (D1): the tool frame opens/extends the run's "
"disclosure"
)
# No setUiState in the branch: the state stays "thinking" (never stale).
assert "setUiState" not in branch, (
@@ -131,22 +137,31 @@ def test_calling_tool_label_strings() -> None:
def test_tool_lines_render_into_the_bubble_wrap() -> None:
"""appendToolLine: first frame creates the .tool-calls list (role=list
+ accessible name) BEFORE the bubble — below an existing Thinking
block — and each line is a .tool-call listitem with the exact marks:
🔎 Listing documents / 📄 Reading <code>path</code>. The path goes
through textContent (storage can never inject HTML)."""
"""Phase 158 (D1): the tool lines render into the run's
disclosure — ``createToolDisclosure`` builds the phase-117
structure (a ``.tool-calls`` list, role=list + accessible name
"Tool calls") and ``ensureToolRun`` inserts it BEFORE the bubble
(D2: above the answer, in arrival order); each line is a
.tool-call listitem with the exact marks: 🔎 Listing documents /
📄 Reading <code>path</code>. The path goes through textContent
(storage can never inject HTML)."""
js = _js()
fn = js.find("function createToolDisclosure")
disc = js[fn : js.find("\n}\n", fn)]
assert 'listEl.className = "tool-calls"' in disc
assert 'setAttribute("role", "list")' in disc
assert 'setAttribute("aria-label", "Tool calls")' in disc, (
"the list keeps its exact phase-37 shape"
)
fn2 = js.find("function ensureToolRun")
run = js[fn2 : js.find("\n}\n", fn2)]
assert 'insertBefore(disc, body.querySelector(".bubble"))' in run, (
"the disclosure sits ABOVE the answer, in arrival order"
)
fn = js.find("function appendToolLine")
assert fn != -1, "appendToolLine must exist"
body = js[fn : js.find("\n}\n", fn)]
assert 'querySelector(".tool-calls")' in body, "idempotent per wrap"
assert 'className = "tool-calls"' in body
assert 'setAttribute("role", "list")' in body
assert 'setAttribute("aria-label", "Tool calls")' in body
assert 'insertBefore(container, body.querySelector(".bubble"))' in body, (
"the list sits ABOVE the answer"
)
assert 'querySelector(".tool-calls")' in body, "the line appends to the run's list"
assert 'className = "tool-call"' in body
assert 'setAttribute("role", "listitem")' in body
assert 'line.textContent = "📄 Reading "' in body
@@ -237,10 +252,10 @@ def test_tool_lines_re_render_on_restore() -> None:
body = js[fn:end]
assert "Array.isArray(m.tools)" in body
assert (
"appendToolLine(wrap, t.name, arg, tOffset, tLimit, tPath)" in body
"appendToolLine(restoreDisc, t.name, arg, tOffset, tLimit, tPath)" in body
), (
"phase 139: the stored range + the phase-146 stored scope ride the "
"SAME helper — a pre-phase record (no fields) renders byte-identical"
"SAME helper (the caller passes the run's disclosure — phase 158)"
)
thinking_restore = body.find("if (m.thinking)")
tools_restore = body.find("Array.isArray(m.tools)")
@@ -250,20 +265,28 @@ def test_tool_lines_re_render_on_restore() -> None:
assert "typeof t.name !== \"string\"" in body, "malformed entries skipped"
def test_thinking_block_stays_on_top_of_tool_lines() -> None:
"""If a tool frame precedes the first thinking frame, the Thinking
block is still created ABOVE the tool lines (scratchpad on top),
not below them."""
def test_segments_keep_arrival_order_above_the_bubble() -> None:
"""Phase 158 (D2): both run builders insert their element
BEFORE the bubble — later insertions land after earlier ones, so
document order is ARRIVAL order (a tool frame preceding the
first thinking frame lands the scratchpad BELOW the tool lines —
the timeline order; the old "scratchpad on top" anchor chain is
gone)."""
js = _js()
fn = js.find("function ensureThinkingBlock")
assert fn != -1
body = js[fn : js.find("\n}\n", fn)]
assert 'querySelector(".tool-calls")' in body, (
"the thinking anchor must account for existing tool lines"
fn = js.find("function ensureThinkingRun")
t = js[fn : js.find("\n}\n", fn)]
fn2 = js.find("function ensureToolRun")
d = js[fn2 : js.find("\n}\n", fn2)]
assert 'insertBefore(block, body.querySelector(".bubble"))' in t, (
"the thinking run inserts before the bubble"
)
assert 'querySelector(".bubble")' in body
assert "insertBefore" in body
assert "block.open = true" in body
assert 'insertBefore(disc, body.querySelector(".bubble"))' in d, (
"the tool run inserts before the bubble"
)
assert "??" not in t, ("the old anchor chain is GONE — the bubble is "
"the only anchor")
assert "block.open = true" in t
assert "disc.open = true" in d
def test_tool_call_style_is_accent_and_contrast_safe() -> None: