Files
brain-of-reese/tests/unit/test_tool_call_compact.py
T
ducoterra 051f62deb7
Build and Push Containers / build-and-push-app (push) Successful in 1m55s
Build and Push Containers / build-and-push-db (push) Successful in 14s
phase: 161_reasoning_bubble
All gates verified. Final check — AGENTS.md rules against the project (commands, gates, protocols) — the quick-reference commands all exist and the rules (PLAN §2/§7/§9 references, phase protocol, No CDN, Debugpy, git gates) accurately describe the project; the reasoning bubble is a pure UI change with no new commands or env vars, so no AGENTS.md change is needed.

**Verification complete — full results:**

- **Verified** (implemented by prior task passes, all in working tree): `createReasoningWrap`/`ensureReasoningWrap` live container (D1), `settleReasoning` fold at the 3 settle sites + not at delta (D2), 320px `.reasoning-body` window + pinned-tail follow (D3), zero persistence change (D4), collapsed restore + shared-page parity with D6 step counts, `styles.css` family treatment + reduced-motion still, mock `REASONING_MIDROUND_TRIGGER` flow, 6-test story suite, phase-158 pin updates
- **Full suite:** `uv run pytest` → **3738 passed, 0 failed** · `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99% (>90%)** · `uv run ruff check .` → all checks passed · `uv run pyright` → **0 errors, 0 warnings**
- **E2E in isolation** (DB up; port-8123 collision with an unrelated `undercooked` service → `E2E_APP_PORT=8125`/`E2E_MOCK_PORT=8902`, no live change made): `test_reasoning_bubble.py` **6/6** · `test_thinking_timeline.py` ✓ · `test_thinking_display.py` ✓ · `test_tool_call_compact.py` ✓ · `test_big_read_progress.py` ✓ · `test_turn_progress_loader.py` ✓ · `test_share_chat.py` ✓ · `test_chat_persistence.py` ✓ · `test_smoke.py` ✓ (all exit 0, zero failures)
- **Completion criteria:** all 9 checked against the code + test outcomes — container open/live/nested ✓, stays open past mid-round delta ✓, folds at done ✓, restore + shared collapsed with "3 steps" ✓, plain turn = no container ✓, regression suites green ✓, suite/coverage/lint green ✓, diff scope limited to `frontend/assets/{app.js,shared.js,styles.css}` + `tests/` + `README.md` + phase files (no `app/` — D4) ✓
- **Documentation:** README.md "Thinking" section names the Reasoning bubble accurately (320px window, open-until-settle incl. mid-round deltas, "N steps", byte-identical no-container, collapsed on restore/shared) — verified consistent, no changes needed; AGENTS.md — verified consistent, no changes needed; all `.env.example` vars documented
- **Decisions:** commit + phase-dir move left to the harness (per pass rules — no git index/history commands executed); `undercooked` dev server on port 8123 untouched, E2E used the env port overrides
- **Next pending phase:** none — `161_reasoning_bubble` is the last in `todo/`
2026-10-06 06:36:09 -04:00

297 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Unit: the phase-117 tool-calls DISCLOSURE contract (task 01).
The owner's visual-glitch report (2026-09-15): one per-call tool "card"
per ``tool`` frame — 6+ stacked full-width bordered cards on a phone —
swamped a completed answer, and the flex row broke the label
mid-word. The fix (phase 117, owner decisions D1–D6) wraps the
existing ``.tool-calls`` list in a native ``details`` disclosure with a
plain-text "Tool call(s) (N)" summary — phase 158 (D1): one
disclosure per CONTIGUOUS tool-call run, created OPEN on the first
live ``tool`` frame of the run, folded at rest (delta folds the
active run via ``closeCurrentRun``; done/stop/finalizeFailedTurn fold
every run via ``closeAllRuns``), and rendered CLOSED on the restore
path and the shared page (D3). The count is per run (the per-run
"Tool call(s) (N)", D3). The line bytes, the four label literals, and
the ``<code>`` textContent arguments stay byte-identical (D6), so the
phase-37/70/87/95 pins stay green.
No ``app/`` logic (frontend-only, D1) — like
``test_frontend_tool_states.py``, this module pins the JS/CSS markers
at source level: the disclosure/summary build (task 01) and the
debox/dechip + disclosure CSS contract (task 02). The E2E story suite
(task 03) completes the phase.
"""
from __future__ import annotations
import re
from pathlib import Path
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
APP_JS = FRONTEND / "assets" / "app.js"
SHARED_JS = FRONTEND / "assets" / "shared.js"
STYLES_CSS = FRONTEND / "assets" / "styles.css"
#: The four pinned tool-line label literals (D6) — the emoji guard and
#: the phase-37/70 pins key off these exact bytes.
PINNED_LABELS = (
'line.textContent = "📄 Reading "',
'line.textContent = "🔎 Searching for "',
'line.textContent = "🔎 Listing documents in "',
'line.textContent = "🔎 Listing documents"',
)
def _js() -> str:
return APP_JS.read_text(encoding="utf-8")
def _shared_js() -> str:
return SHARED_JS.read_text(encoding="utf-8")
def _css() -> str:
return STYLES_CSS.read_text(encoding="utf-8")
def _fn(src: str, name: str) -> str:
"""The flat function body (house extraction: first ``\n}\n`` after
the definition — the function must stay flat, no nested
declarations)."""
fn = src.find(f"function {name}")
assert fn != -1, f"{name} must exist"
return src[fn : src.find("\n}\n", fn)]
def test_tool_run_builds_an_open_details_disclosure() -> None:
"""Phase 158 (D1/D5) + 161 (D1): ``ensureToolRun`` creates a
native ``details.tool-calls-disclosure`` via
``createToolDisclosure`` — a ``summary.tool-calls-summary``
(first child) + the existing ``.tool-calls`` list (role=list +
aria-label "Tool calls", second child) — appended into the
phase-161 reasoning container's ``.reasoning-body`` in arrival
order (D1 — the container sits above the bubble; the per-run
bubble anchor is superseded). It starts OPEN while the run is
live; every append re-renders the plain-text per-run "Tool
call(s) (N)" count on the summary through textContent (no
innerHTML anywhere)."""
js = _js()
disc = _fn(js, "createToolDisclosure")
assert "document.createElement(\"details\")" in disc
assert "className = \"tool-calls-disclosure\"" in disc
assert "document.createElement(\"summary\")" in disc
assert "className = \"tool-calls-summary\"" in disc
# The list keeps its exact phase-37 shape, nested in the disclosure.
assert "className = \"tool-calls\"" in disc
assert 'setAttribute("role", "list")' in disc
assert 'setAttribute("aria-label", "Tool calls")' in disc
assert "innerHTML" not in disc, (
"the tool builder stays createElement/textContent (phase 117 "
"style — innerHTML ABSENT)"
)
run = _fn(js, "ensureToolRun")
assert "createToolDisclosure()" in run
assert "disc.open = true", (
"D5: created OPEN on the first live tool frame of the run — the "
"calls and the phase-87 .tool-elapsed suffix stay visible during "
"the turn"
)
assert 'insertBefore(disc, body.querySelector(".bubble"))' not in run, (
"the per-run bubble anchor is GONE (phase 161 D1)"
)
assert (
'ensureReasoningWrap(wrap).querySelector(".reasoning-body")'
".appendChild(disc)" in run
), (
"phase 161 (D1): appended into the container's body — the "
"container sits above the answer, arrival order preserved"
)
# D5: the count rides the summary — the template fragment itself
# (singular for one call, plural otherwise), textContent-built.
append = _fn(js, "appendToolLine")
assert "Tool call${n === 1 ? \"\" : \"s\"} (${n})" in append, (
"the per-run plain-text count template (D3) on every append"
)
assert "innerHTML" not in append, (
"no HTML injection surface on tool lines — textContent only"
)
def test_append_tool_line_keeps_the_four_pinned_labels() -> None:
"""D6: the four ``line.textContent`` label literals and the
``<code>`` textContent arguments stay byte-identical — the emoji
guard, the phase-37/70 unit pins, and the E2E text assertions all
key off these exact bytes."""
body = _fn(_js(), "appendToolLine")
for label in PINNED_LABELS:
assert label in body, f"pinned label literal changed: {label!r}"
assert "code.textContent = argument" in body, (
"the path/pattern/scope is data — textContent, never innerHTML"
)
def test_close_all_runs_defined_and_called_at_every_rest_site() -> None:
"""Phase 158 (D5): ``closeAllRuns`` — the terminal settle (the
phase-117 ``closeToolCalls`` + the D15 block close, merged):
closes EVERY open ``details.thinking`` +
``details.tool-calls-disclosure`` in the wrap (defensive; a late
double-close is a no-op), called at ALL rest sites — the done
handler (the turn ended), the stop/abort settle (the turn was
stopped), and the failed-turn funnel — so the definition + ≥3
calls. The `delta` handler folds only the active run
(``closeCurrentRun``)."""
js = _js()
body = _fn(js, "closeAllRuns")
assert "details.thinking, details.tool-calls-disclosure" in body, (
"targets both run kinds in the given wrap"
)
assert "el.open = false" in body, "fold: open=false (idempotent)"
assert js.count("closeAllRuns(wrap)") >= 3, (
"the definition + the done handler + the stop/abort settle + "
"the failed-turn funnel — the runs settle exactly where the "
"Thinking blocks settle"
)
delta_idx = js.find('ev.type === "delta"')
done_idx = js.find('ev.type === "done"')
delta_branch = js[delta_idx:done_idx]
assert "closeCurrentRun()" in delta_branch, (
"the delta folds the ACTIVE run (not every run — a later "
"thinking frame opens a new one)"
)
def test_restore_path_folds_the_disclosure() -> None:
"""renderStoredMessage (D3): a restored brain record with `tools`
re-renders through the SAME appendToolLine (byte-identical — the
caller passes the run's disclosure, phase 158), and the
disclosure is FOLDED right after the loop (``restoreDisc.open =
false``) — a completed turn from storage is one compact
"Tool calls (N)" line, not N stacked cards (the space fix;
mirrors the thinking restore rendering collapsed)."""
js = _js()
fn = js.find("function renderStoredMessage")
assert fn != -1
body = js[fn : js.find("function restoreConversation", fn)]
assert "appendToolLine(restoreDisc, t.name, arg, tOffset, tLimit, tPath)" in body
assert "restoreDisc.open = false" in body
assert (
body.find("appendToolLine(restoreDisc, t.name, arg, tOffset, tLimit, tPath)")
< body.find("restoreDisc.open = false")
), "the fold sits AFTER the tool-line restore loop"
def test_run_anchors_are_the_container_in_arrival_order() -> None:
"""Phase 158 (D2) + 161 (D1): the anchor moved to the REASONING
CONTAINER for both run kinds (the phase-161 documented
supersession — the container sits before the bubble; the runs
append into its ``.reasoning-body``) — the old
``.tool-calls-disclosure ?? .tool-calls ?? .bubble`` anchor chain
is GONE (with per-run disclosures, "above the tool lines" was the
wrong order — arrival order is the contract), and the runs still
open while active (D5)."""
js = _js()
run = _fn(js, "ensureThinkingRun")
assert (
'ensureReasoningWrap(wrap).querySelector(".reasoning-body")'
".appendChild(block)" in run
), "phase 161 (D1): the thinking run anchors on the container"
assert "block.open = true" in run
assert "??" not in run, "the old anchor chain is GONE"
d = _fn(js, "ensureToolRun")
assert (
'ensureReasoningWrap(wrap).querySelector(".reasoning-body")'
".appendChild(disc)" in d
), "phase 161 (D1): the tool run anchors on the container"
assert "disc.open = true" in d
def test_shared_page_add_tool_lines_builds_a_closed_disclosure() -> None:
"""shared.js addToolLines (D3 parity): the SAME
``details.tool-calls-disclosure`` + summary + ``.tool-calls`` list —
but created CLOSED (a pure render has no live turn to open it),
with the count set once at the end. The four label literals, the
three textContent-only argument lines, and the phase-95 marker stay
byte-identical (no innerHTML anywhere)."""
body = _fn(_shared_js(), "addToolLines")
assert "document.createElement(\"details\")" in body
assert "className = \"tool-calls-disclosure\"" in body
assert "document.createElement(\"summary\")" in body
assert "open = false" in body, (
"D3: the shared page renders the disclosure CLOSED — never an "
"auto-expanded record"
)
assert "className = \"tool-calls\"" in body
for label in PINNED_LABELS:
assert label in body, f"pinned label literal changed: {label!r}"
assert "Tool call${n === 1 ? \"\" : \"s\"} (${n})" in body, (
"the same plain-text count template (D5), set once at the end"
)
assert body.count("code.textContent = argument") == 3, (
"all three argument-bearing lines (read / grep / ls) stay "
"textContent-only"
)
assert "innerHTML" not in body, (
"no HTML injection surface on shared tool lines — textContent only"
)
def test_tool_call_rule_is_deboxed_inline_flow() -> None:
"""D4: the ``.tool-call`` rule is DEBOXED — no ``display: flex``,
no card (no background / border / radius / padding), and no
``var(--accent-line)`` left border. The accent now rides the TEXT
(``var(--accent-ink)``, ≈10.4:1 on the surface), and the line keeps
the compact status metrics + ``overflow-wrap: anywhere`` so the
label + inline ``code`` flow as ONE continuous run — a long path
wraps to the left edge and the label never breaks mid-word (the
"Rea/ding" glitch is gone)."""
m = re.search(r"\.tool-call \{([^}]*)\}", _css())
assert m, "the .tool-call rule must exist"
row = m.group(1)
assert "display: flex" not in row, (
"deboxed: no flex — the label + code are one inline run"
)
assert "var(--accent-line)" not in row, "deboxed: no accent left border"
assert "border" not in row, "deboxed: no card borders at all"
assert "background" not in row, "deboxed: no card background"
assert "padding" not in row, "deboxed: no card padding"
assert "var(--accent-ink)" in row, (
"the accent now rides the text color (≈10.4:1 on the surface)"
)
assert "font-size: 0.8rem" in row, "the compact status metrics stay"
assert "overflow-wrap: anywhere" in row, "the path wraps inside the run"
def test_tool_call_code_rule_is_dechipped() -> None:
"""D4: the ``.tool-call code`` rule KEEPS ``var(--mono)`` +
``var(--ink)`` (≈11.5:1 on the surface — AA) but LOSES the chip
(no background, no padding, no radius) — the path is inline text
in the line's run now."""
m = re.search(r"\.tool-call code \{([^}]*)\}", _css())
assert m, "the .tool-call code rule must exist"
code = m.group(1)
assert "var(--mono)" in code
assert "var(--ink)" in code
assert "background" not in code, "dechipped: no chip background"
assert "padding" not in code, "dechipped: no chip padding"
assert "border-radius" not in code, "dechipped: no chip radius"
assert "overflow-wrap: anywhere" in code
def test_tool_calls_disclosure_and_summary_rules_exist() -> None:
"""Phase 117 (D2/D4): the NEW ``.tool-calls-disclosure`` and
``.tool-calls-summary`` rules exist — the summary is a native
focusable toggle carrying the accent TEXT (``var(--accent-ink)``,
≈10.4:1 on the surface; text + color, never color alone, B5),
modeled on the details.thinking family, with no new color literal
(the phase-92 zero-literal invariant)."""
css = _css()
assert re.search(r"\.tool-calls-disclosure \{", css), (
"the .tool-calls-disclosure rule must exist"
)
m = re.search(r"\.tool-calls-summary \{([^}]*)\}", css)
assert m, "the .tool-calls-summary rule must exist"
summary = m.group(1)
assert "var(--accent-ink)" in summary, (
"the accent rides the summary text (≈10.4:1 on the surface)"
)
assert "cursor: pointer" in summary, "a toggle affordance"