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/`
This commit is contained in:
@@ -78,12 +78,15 @@ def _branch(js: str, open_type: str, close_type: str) -> str:
|
||||
# ---------- the run builders (D1/D2/D3/D5) ----------
|
||||
|
||||
|
||||
def test_ensure_thinking_run_builds_a_new_open_block_before_the_bubble() -> None:
|
||||
"""D1/D2/D3: ``ensureThinkingRun`` returns the active thinking
|
||||
def test_ensure_thinking_run_builds_a_new_open_block_in_the_container() -> None:
|
||||
"""D1/D3/D5: ``ensureThinkingRun`` returns the active thinking
|
||||
run's block or — a NEW ``details.thinking`` (the phase-17 static
|
||||
structure via ``createThinkingBlock``) created OPEN (D5),
|
||||
inserted BEFORE the ``.bubble`` (D2 — the anchor is the bubble,
|
||||
in arrival order). The plain ``Thinking`` literal (D3 — the
|
||||
appended into the phase-161 reasoning container's
|
||||
``.reasoning-body`` (D1 — the container sits before the bubble,
|
||||
a new run appends after the previous run's block, so arrival
|
||||
order is preserved; the per-run bubble anchor is superseded). The
|
||||
plain ``Thinking`` literal (D3 — the
|
||||
phase-17 literal, not a numbered variant) rides the static
|
||||
template; the fresh run joins segRuns + currentRun."""
|
||||
js = _js()
|
||||
@@ -96,8 +99,15 @@ def test_ensure_thinking_run_builds_a_new_open_block_before_the_bubble() -> None
|
||||
)
|
||||
assert "createThinkingBlock()" in body, "the shared builder is reused"
|
||||
assert "block.open = true" in body, "D5: created OPEN while its frames stream"
|
||||
assert 'insertBefore(block, body.querySelector(".bubble"))' in body, (
|
||||
"D2: the anchor is the BUBBLE — above it, in arrival order"
|
||||
assert (
|
||||
'ensureReasoningWrap(wrap).querySelector(".reasoning-body")'
|
||||
".appendChild(block)" in body
|
||||
), (
|
||||
"phase 161 (D1): appended into the container's body — the "
|
||||
"container sits above the bubble, arrival order preserved"
|
||||
)
|
||||
assert "insertBefore" not in body, (
|
||||
"the per-run bubble anchor is GONE (phase 161 D1)"
|
||||
)
|
||||
assert 'kind: "thinking"' in body
|
||||
assert "segRuns.push(run)" in body
|
||||
@@ -129,22 +139,33 @@ def test_create_thinking_block_is_the_phase_17_static_template_without_data() ->
|
||||
)
|
||||
|
||||
|
||||
def test_ensure_tool_run_builds_a_new_open_disclosure_before_the_bubble() -> None:
|
||||
"""D1/D2/D5: ``ensureToolRun`` returns the active tool run's
|
||||
def test_ensure_tool_run_builds_a_new_open_disclosure_in_the_container() -> None:
|
||||
"""D1/D5: ``ensureToolRun`` returns the active tool run's
|
||||
disclosure or — a NEW ``details.tool-calls-disclosure`` (the
|
||||
phase-117 structure via ``createToolDisclosure`` —
|
||||
summary.tool-calls-summary + the .tool-calls list, role=list +
|
||||
aria-label "Tool calls") created OPEN (D5), inserted BEFORE the
|
||||
bubble (D2, in arrival order). One disclosure per CONTIGUOUS
|
||||
tool-call run; the per-run "Tool call(s) (N)" count rides
|
||||
appendToolLine's summary update."""
|
||||
aria-label "Tool calls") created OPEN (D5), appended into the
|
||||
phase-161 reasoning container's ``.reasoning-body`` (D1 — the
|
||||
container sits before the bubble, arrival order preserved; the
|
||||
per-run bubble anchor is superseded). One disclosure per
|
||||
CONTIGUOUS tool-call run; the per-run "Tool call(s) (N)" count
|
||||
rides appendToolLine's summary update."""
|
||||
js = _js()
|
||||
body = _fn(js, "ensureToolRun")
|
||||
assert 'currentRun?.kind === "tool"' in body
|
||||
assert "closeCurrentRun()" in body
|
||||
assert "createToolDisclosure()" in body
|
||||
assert "disc.open = true" in body, "D5: created OPEN while its frames stream"
|
||||
assert 'insertBefore(disc, body.querySelector(".bubble"))' in body
|
||||
assert (
|
||||
'ensureReasoningWrap(wrap).querySelector(".reasoning-body")'
|
||||
".appendChild(disc)" in body
|
||||
), (
|
||||
"phase 161 (D1): appended into the container's body — the "
|
||||
"container sits above the bubble, arrival order preserved"
|
||||
)
|
||||
assert "insertBefore" not in body, (
|
||||
"the per-run bubble anchor is GONE (phase 161 D1)"
|
||||
)
|
||||
assert 'kind: "tool"' in body
|
||||
assert "segRuns.push(run)" in body
|
||||
disc = _fn(js, "createToolDisclosure")
|
||||
@@ -566,8 +587,11 @@ def test_restore_timeline_builds_the_collapsed_per_run_structure() -> None:
|
||||
created closed), and CONSECUTIVE ``kind:"tool"`` entries group
|
||||
into ONE ``details.tool-calls-disclosure`` (the shared
|
||||
phase-117 builder — ``createToolDisclosure``, innerHTML ABSENT —
|
||||
created closed), each inserted BEFORE the bubble (D2, arrival
|
||||
order). The per-run count rides ``appendToolLine``'s summary
|
||||
created closed). Phase 161 (task 02, D5/D6): the per-run blocks
|
||||
nest inside ONE details.reasoning container — the shared
|
||||
task-01 builder, created CLOSED and inserted ONCE before the
|
||||
bubble (the per-run bubble anchor is superseded; arrival order
|
||||
preserved). The per-run count rides ``appendToolLine``'s summary
|
||||
update; a kind change seals the tool run (one disclosure per
|
||||
run, D1)."""
|
||||
body = _fn(_js(), "restoreTimeline")
|
||||
@@ -588,8 +612,29 @@ def test_restore_timeline_builds_the_collapsed_per_run_structure() -> None:
|
||||
assert "if (toolDisc) toolDisc = null" in body, (
|
||||
"a thinking entry seals the tool run — ONE disclosure per run (D1)"
|
||||
)
|
||||
assert "body.insertBefore(block, bubble)" in body, "D2: above the bubble"
|
||||
assert "body.insertBefore(toolDisc, bubble)" in body, "D2: above the bubble"
|
||||
# Phase 161 (task 02, D5/D6): the per-run timeline nests inside
|
||||
# ONE details.reasoning container — the shared task-01 builder,
|
||||
# created CLOSED and inserted ONCE before the bubble (the
|
||||
# per-run bubble anchor is superseded; arrival order preserved).
|
||||
assert "createReasoningWrap()" in body, "the shared task-01 builder is reused"
|
||||
assert "cont.open = false" in body, (
|
||||
"D5: the restore renders collapsed (explicit for the pin)"
|
||||
)
|
||||
assert "body.insertBefore(cont, bubble)" in body, (
|
||||
"the container sits above the bubble — the ONE anchor"
|
||||
)
|
||||
assert "runsEl.appendChild(block)" in body, (
|
||||
"D1: the per-run block nests into the container's body"
|
||||
)
|
||||
assert "runsEl.appendChild(toolDisc)" in body, (
|
||||
"D1: the per-run disclosure nests into the container's body"
|
||||
)
|
||||
assert "insertBefore(block" not in body, (
|
||||
"the per-run bubble anchor is GONE (phase 161 D5)"
|
||||
)
|
||||
assert "insertBefore(toolDisc" not in body, (
|
||||
"the per-run bubble anchor is GONE (phase 161 D5)"
|
||||
)
|
||||
assert "for (const entry of segments" in body, "iterate the runs in order"
|
||||
|
||||
|
||||
@@ -718,10 +763,12 @@ def test_add_segments_builds_the_collapsed_parity_timeline() -> None:
|
||||
LINE BYTES as ``addToolLines`` (the four pinned label literals
|
||||
+ the phase-70 legacy names + the phase-139 readRangeSuffix +
|
||||
the phase-95 truncation note), the per-run
|
||||
``Tool call(s) (N)`` count, and insertion BEFORE the bubble
|
||||
(D2, arrival order) — all created CLOSED (no ``open = true``
|
||||
anywhere; the addThinkingBlock / addToolLines collapsed
|
||||
convention)."""
|
||||
``Tool call(s) (N)`` count, and nesting inside the phase-161
|
||||
details.reasoning container (the local parity builder, created
|
||||
CLOSED and inserted ONCE before the bubble — the per-run bubble
|
||||
anchor is superseded, arrival order preserved) — all created
|
||||
CLOSED (no ``open = true`` anywhere; the addThinkingBlock /
|
||||
addToolLines collapsed convention)."""
|
||||
body = _fn(_js_shared(), "addSegments")
|
||||
assert "document.createElement(\"details\")" in body, "the structure is createElement"
|
||||
assert 'block.className = "thinking"' in body
|
||||
@@ -746,9 +793,29 @@ def test_add_segments_builds_the_collapsed_parity_timeline() -> None:
|
||||
assert '"truncated-note"' in body, "the phase-95 note parity"
|
||||
# the per-run count template.
|
||||
assert "Tool call${n === 1 ? \"\" : \"s\"} (${n})" in body
|
||||
# D2: above the bubble, in arrival order.
|
||||
assert "body.insertBefore(block, bubble)" in body
|
||||
assert "body.insertBefore(toolDisc, bubble)" in body
|
||||
# Phase 161 (task 02, D5): the per-run timeline nests inside ONE
|
||||
# details.reasoning container — the local parity builder, created
|
||||
# CLOSED and inserted ONCE before the bubble (the per-run bubble
|
||||
# anchor is superseded; arrival order preserved).
|
||||
assert "createReasoningWrap()" in body, "the local parity builder is reused"
|
||||
assert "cont.open = false" in body, (
|
||||
"D5: collapsed (the pure-render convention)"
|
||||
)
|
||||
assert "body.insertBefore(cont, bubble)" in body, (
|
||||
"inserted ONCE before the bubble"
|
||||
)
|
||||
assert "runsEl.appendChild(block)" in body, (
|
||||
"D1: the per-run block nests into the container's body"
|
||||
)
|
||||
assert "runsEl.appendChild(toolDisc)" in body, (
|
||||
"D1: the per-run disclosure nests into the container's body"
|
||||
)
|
||||
assert "insertBefore(block" not in body, (
|
||||
"the per-run bubble anchor is GONE (phase 161 D5)"
|
||||
)
|
||||
assert "insertBefore(toolDisc" not in body, (
|
||||
"the per-run bubble anchor is GONE (phase 161 D5)"
|
||||
)
|
||||
# the consecutive-tool grouping (one disclosure per run, D1).
|
||||
assert "if (toolDisc) toolDisc = null" in body, (
|
||||
"a thinking entry seals the tool run — ONE disclosure per run"
|
||||
|
||||
Reference in New Issue
Block a user