phase: 139_read_range_tool_lines
Build and Push Containers / build-and-push-app (push) Successful in 2m54s
Build and Push Containers / build-and-push-db (push) Successful in 17s

All verification is complete and green. Phase 139's three tasks (01–03) were already implemented in prior passes; this was the final verification pass, and every gate holds.

## Phase 139 — final verification report

**Verified in working tree (no code changes needed):**
- `readRangeSuffix` single-source-of-truth helper in `app.js` + byte-identical parity copy in `shared.js` (EN-dash ` (lines N–M)`, `(from line N)`, `(first N lines)`, `Number.isInteger(x) && x >= 1` guard mirroring the server's `_whole_line_number`)
- `appendToolLine(wrap, name, argument, offset = null, limit = null)` — `<span class="read-range">` sibling after `<code>`, only when suffix non-empty; `shared.js` `addToolLines` parity
- Live `tool` branch: guarded `ev.offset`/`ev.limit`, additive `toolAcc` records (non-range keeps pre-phase shape), status label carries the suffix (A2); restore path passes stored `t.offset`/`t.limit` through, phase-95 truncation note untouched
- E2E: `SAME_FILE_TRIGGER` mock flow (same file read twice, offsets 10/20 then 40/15) + 3-test Playwright suite

**Test / lint / coverage results:**
- `uv run pytest` (unit + integration): **3283 passed, 9 skipped, 0 failed**
- `uv run pytest --cov=app --cov-report=term-missing`: **TOTAL 99%** (>90% gate)
- `uv run pytest tests/e2e/test_read_range_tool_lines.py -v --no-cov`: **3 passed** in isolation
- `uv run ruff check .`: clean; `uv run pyright`: **0 errors, 0 warnings**

**Completion criteria:**
1. Range read renders `(lines N–M)` live; two same-file reads show DIFFERENT ranges — ✅ (E2E test 1)
2. Plain read byte-identical; phase-87/117 pins + emoji guard pass unedited — ✅ (E2E test 2 + full suite)
3. Suffix survives save → restore on chat page + shared page — ✅ (E2E test 3)
4. Full suite green, coverage >90%, E2E isolated green, ruff + pyright clean — ✅
5. Commit + phase move — left to the harness per executor rules (no `git add`/`commit` run)

**Notable:** the missing pytest summary line on one run was a transient output-buffering artifact, confirmed by re-run; DB was already up and healthy.

**Next pending phase:** `140_pictorial_pdf_pages`
This commit is contained in:
2026-09-30 12:55:23 -04:00
parent 1a657ce4ca
commit 9e7262a5c8
31 changed files with 2197 additions and 25 deletions
+13 -5
View File
@@ -50,17 +50,19 @@ def test_tool_branch_is_a_first_class_turn_branch() -> None:
"the turn handler must branch on tool frames"
)
branch = js[tool_idx:delta_idx]
assert "toolAcc.push({ name, argument })" in branch, (
assert "toolAcc.push(toolRecord)" in branch, (
"every tool frame is recorded for persistence — the record stays"
" {name, argument}-generic, no per-tool shape (phase 68: the"
" search tool rides the same accumulator)"
" search tool rides the same accumulator; phase 139: the range"
" fields join additively — a non-range record keeps the exact"
" pre-phase shape)"
)
assert "clearTurnTimeout()" in branch, "a tool frame proves the stream is alive"
assert 'addMessage("brain", "")' in branch, "first frame creates the brain wrap"
assert "uiState === UI_STATE.thinking" in branch, (
"label updates only while the state is still thinking"
)
assert "appendToolLine(wrap, name, argument)" in branch
assert "appendToolLine(wrap, name, argument, frameOffset, frameLimit)" in branch
# No setUiState in the branch: the state stays "thinking" (never stale).
assert "setUiState" not in branch, (
"the tool branch must keep uiState=thinking (button stays disabled)"
@@ -86,7 +88,10 @@ def test_calling_tool_label_strings() -> None:
branch = js[tool_idx:delta_idx]
assert "sendLabel" not in branch, "phase 48: the button keeps its Stop label"
assert "`${brand()} is listing documents`" in branch
assert "`${brand()} is reading ${argument}`" in branch
assert (
"`${brand()} is reading ${argument}${readRangeSuffix(frameOffset, frameLimit)}`"
in branch
), ("phase 139: the read status carries the requested-range suffix (A2)")
# Phase 70: the read status — new + legacy name, locked
# name+argument gate, first in the ternary.
assert 'name === "read" || name === "read_document") && argument' in branch, (
@@ -222,7 +227,10 @@ def test_tool_lines_re_render_on_restore() -> None:
end = js.find("function restoreConversation")
body = js[fn:end]
assert "Array.isArray(m.tools)" in body
assert "appendToolLine(wrap, t.name, arg)" in body
assert "appendToolLine(wrap, t.name, arg, tOffset, tLimit)" in body, (
"phase 139: the stored range rides the SAME helper — a pre-phase"
" record (no fields) renders byte-identical"
)
thinking_restore = body.find("if (m.thinking)")
tools_restore = body.find("Array.isArray(m.tools)")
assert -1 < thinking_restore < tools_restore, (