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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user