fix(rag): stop the per-run blocks from folding — the container is the one disclosure
Build and Push Containers / build-and-push-app (push) Successful in 1m43s
Build and Push Containers / build-and-push-db (push) Successful in 15s

The owner refinement (2026-10-06): the per-run thinking and tool-call
blocks inside the Reasoning bubble were individually collapsible
details elements — a fold inside the fold. A tool frame closed the
previous thinking block, so the visible thinking vanished mid-turn;
and the pinned-tail follow died on the append of every run after the
first (the pin was captured AFTER the new block's height growth
contaminated the 32px reading).

- app.js: the in-container runs are now plain divs —
  createThinkingRunEl (div.thinking + static .thinking-summary label +
  .thinking-text) and createToolRunEl (div.tool-calls-disclosure +
  static .tool-calls-summary label + the .tool-calls list); they never
  open or fold (the container is the ONE open/fold control, open
  through the turn, folded at settle — unchanged); the pin capture
  moves BEFORE ensureThinkingRun/ensureToolRun append (plus the
  first-frame edge: no container yet -> always pinned);
  closeCurrentRun just ends the run; closeAllRuns stays the defensive
  legacy guard; restoreTimeline/addSegments (shared.js) use the div
  builders; the legacy no-segments restore path keeps the old
  details-based blocks byte-identical
- styles.css: the in-container runs are distinct by label + rail
  (brand-ink Thinking label on a brand-soft rail; accent-ink
  Tool call(s) (N) label on a neutral rail); the interactive
  treatment (cursor + chevron) is scoped to the legacy <summary>; the
  inner windows stay disabled (the container's .reasoning-body is the
  ONE live scroll surface)
- unit + E2E: the D5-refinement contract is pinned — one
  details.reasoning disclosure, per-run blocks as plain divs, zero
  nested <details> inside the container, the container as the only
  open/fold control, the .reasoning-body window auto-follows the
  tail while streaming

unit+integration 3738 passed (coverage 99%), ruff + pyright clean, all
five affected E2E suites pass in isolation (reasoning_bubble 6,
thinking_timeline 6, thinking_display 5, thinking_scroll 8,
tool_call_compact 4), verified live: the follow holds through a long
thinking stream, the tool-frame transition, and the second thinking
run; restore renders the div structure without client-side errors

LLM-Generated: true
This commit is contained in:
2026-10-06 14:44:41 -04:00
parent 051f62deb7
commit 690fd98036
15 changed files with 941 additions and 609 deletions
+87 -44
View File
@@ -935,27 +935,46 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
// is no longer "processing"; settle its "(Ns)" suffix (the
// visible indication moves to the thinking block).
settleToolLine();
// Phase 17: model reasoning — stream it live into the collapsible
// Thinking block. No setUiState here: the UI state stays
// Phase 17: model reasoning — stream it live into the per-run
// Thinking block (the D5 refinement, owner 2026-10-06 — a
// plain div, always visible; the container is the ONE
// collapsible). No setUiState here: the UI state stays
// "thinking" (the button stays the enabled "Stop" control —
// phase 48 — #send-status unchanged) — the live block simply
// replaces the typing dots as the visible feedback.
// Phase 158 (D1, owner direction 2026-10-04): PER-RUN blocks —
// the D15 "re-open the one shared block" is superseded. Each
// CONTIGUOUS thinking run streams into its OWN
// details.thinking (ensureThinkingRun: a frame of a new kind
// closes the previous run and opens a new block; D5 — the new
// block is OPEN while its frames stream). A post-delta
// thinking frame (the next agent round: the model answered,
// called a tool, then thinks again) lands in a NEW block above
// the answer — not a re-open of the block that sits on top of
// it (the owner's "thinking resumed above the answer" awkwardness
// is gone; the runs stay in arrival order, D2).
// CONTIGUOUS thinking run streams into its OWN block
// (ensureThinkingRun: a frame of a new kind ends the previous
// run and opens a new block — always visible inside the
// container; D5 refinement). A post-delta thinking frame (the
// next agent round: the model answered, called a tool, then
// thinks again) lands in a NEW block — not a re-open of the
// block that sits on top of it (the owner's "thinking resumed
// above the answer" awkwardness is gone; the runs stay in
// arrival order, D2).
thinkingAcc += ev.text || "";
sawThinking = true;
clearTurnTimeout(); // the stream is alive — as the first delta says
if (!wrap) wrap = addMessage("brain", "");
removeTyping(); // the live block replaces the dots as feedback
// Phase 161 (D3): the container window's PRE-update pin
// capture — BEFORE ensureThinkingRun appends the (new) block:
// the append's own height growth must not leak into the
// "pre" reading (an append taller than the 32px band would
// read as "the user scrolled up" and kill the follow at the
// first frame of every run after the first — the D5
// refinement, owner 2026-10-06, exposed the ordering defect:
// the old details collapse used to reset the window to a
// fitting state, masking it). The phase-17 pre-measure
// convention, verbatim.
// convention, verbatim. A turn's FIRST frame (the container
// did not exist yet — reasoningWrapEl is null) is always
// pinned: the fresh window starts at its bottom (st=0), and
// a first chunk taller than the 320px window must still
// land on the tail, not the top.
const rbody = reasoningWrapEl?.querySelector(".reasoning-body");
const rPinned = rbody ? isReasoningPinned(rbody) : true;
const block = ensureThinkingRun(wrap);
// Phase 161 (D6): the container's live status — "Thinking…"
// while a thinking frame streams (a tool frame overwrites it
@@ -970,18 +989,17 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
// grows the window's content below the old bottom, so measuring
// the distance AFTER the update reads the chunk's height, not
// the user's position, and the follow died at the first
// 2-newline gap. Pre-update, the distance is where the user
// actually is. Phase 158: the same pin logic, now operating on
// THIS block (per-run — each run's block pins its own tail;
// the pre-update capture is verbatim).
// 2-newline gap. The container capture above is taken BEFORE
// the block append for the same reason (the D5 refinement's
// exposed ordering defect). Phase 158: the same pin logic, now
// operating on THIS block (per-run — each run's block pins its
// own tail; the pre-update capture is verbatim). The D5
// refinement (owner 2026-10-06): the in-container block is a
// plain div (block.open is undefined → the gate is false) and
// its .thinking-text is not scrollable, so this capture
// no-ops there — the container window's follow below is the
// live scroll surface.
const pinned = block.open && isThinkingNearBottom(textEl);
// Phase 161 (D3): the container window's PRE-update pin
// capture — the phase-17 pre-measure convention, BEFORE the
// re-render grows the content (the inner per-block follow
// above stays — inside the container the .thinking-text is
// no longer scrollable, so it no-ops there; task 03).
const rbody = reasoningWrapEl?.querySelector(".reasoning-body");
const rPinned = !!rbody && isReasoningPinned(rbody);
textEl.innerHTML = renderMarkdown(currentRun.run.text); // escape-first, XSS-safe
if (pinned) {
// Follow the live tail only while the user was pinned to the
@@ -1063,24 +1081,37 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
?.setAttribute("aria-label", toolStatus);
}
// Phase 158 (D1): PER-RUN disclosure — each CONTIGUOUS tool-call
// run streams into its OWN details.tool-calls-disclosure
// (ensureToolRun: a frame of a new kind closes the previous run
// and opens a new disclosure; D5 — OPEN while its frames stream),
// inserted BEFORE the bubble in arrival order (D2). The record
// run streams into its OWN block (ensureToolRun: a frame of a
// new kind ends the previous run and opens a new disclosure —
// the D5 refinement, owner 2026-10-06: a plain div, always
// visible inside the container; the container is the ONE
// collapsible), inserted in arrival order (D2). The record
// rides the run's `records` too — the SAME toolAcc objects, so
// the phase-95 tool_result stamp (truncated / chars_shown /
// chars_total) flows into the segments payload with no extra
// wiring.
// Phase 161 (D3): the container window's PRE-update pin
// capture — BEFORE ensureToolRun appends the (new) block:
// the append's own height growth must not leak into the
// "pre" reading (an append taller than the 32px band would
// read as "the user scrolled up" and kill the follow — the
// D5 refinement, owner 2026-10-06, exposed the ordering
// defect: the old details collapse used to reset the window
// to a fitting state, masking it). The phase-17 pre-measure
// convention, verbatim.
// convention, verbatim. A turn's FIRST frame (the container
// did not exist yet — reasoningWrapEl is null) is always
// pinned: the fresh window starts at its bottom (st=0), and
// a first line taller than the 320px window must still land
// on the tail, not the top.
const tbody = reasoningWrapEl?.querySelector(".reasoning-body");
const tPinned = tbody ? isReasoningPinned(tbody) : true;
const disc = ensureToolRun(wrap);
currentRun.run.records.push(toolRecord);
// Phase 161 (D6): "Calling <name>…" — the display name the
// tool line already shows (the phase-70 legacy-name mapping
// produced `name` before this point).
setReasoningStatus(`Calling ${name}…`);
// Phase 161 (D3): the container window's PRE-update pin
// capture — before the line append grows the content.
const tbody = reasoningWrapEl?.querySelector(".reasoning-body");
const tPinned = !!tbody && isReasoningPinned(tbody);
const line = appendToolLine(
disc,
name,
@@ -1132,18 +1163,22 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
acc += ev.text || "";
if (uiState === UI_STATE.thinking) setUiState(UI_STATE.streaming);
if (!wrap) wrap = addMessage("brain", ""); // first token: live bubble in
// Phase 158 (D5): the first delta FOLDS the open run (the
// active block closes; a later thinking frame opens a NEW run
// above the bubble — the D15 re-open of the shared block is
// gone). Idempotent — a no-op without an open run.
// Phase 158 (D5): the first delta ENDS the active run (the
// D5 refinement, owner 2026-10-06 — the in-container runs are
// non-collapsible divs, so nothing folds; the run simply
// stops accumulating frames — a later thinking frame opens a
// NEW run in the container — the D15 re-open of the shared
// block is gone). Idempotent — a no-op without an active run.
closeCurrentRun();
wrap.querySelector(".bubble").innerHTML = renderMarkdown(acc);
// No page scroll (phase 42): the answer never follows the viewport.
} else if (ev.type === "done") {
sawDone = true;
// Phase 158 (D5): the turn is over — fold EVERY open run
// (defensive: by construction at most the last run is open; a
// late double-close is a no-op). Replaces the phase-117
// Phase 158 (D5): the turn is over — the D5 refinement (owner
// 2026-10-06) made the in-container runs non-collapsible divs,
// so this is the DEFENSIVE legacy guard (the pre-phase-161
// details blocks the restore path creates, already closed — a
// no-op in every live path). Replaces the phase-117
// closeToolCalls fold + the D15 block close.
closeAllRuns(wrap);
// Phase 161 (D2): the turn settled — fold the container too
@@ -1468,13 +1503,21 @@ def test_run_turn_is_pinned_at_phase_159_state() -> None:
the live container, like segRuns; the D6 status writes + the D3
pre/post follow in the thinking/tool frame branches; the
``settleReasoning(wrap)`` fold at the done / stop / failed
settle sites — the container's live-path contract, D1/D2/D3/D6):
the full source must match the pin — the ``reask`` path, the
turn-local resets, the SSE contract, the save points, and the
sanctioned additions (the task-01 ``lastUserWrap`` assignment +
the idle-settle mark pass, the task-03 aborted-settle mark pair,
the phase-160 done-settle pair, and the phase-161 live-container
additions) included."""
settle sites — the container's live-path contract, D1/D2/D3/D6)
and the D5 refinement's (owner 2026-10-06) sanctioned changes
(the per-run blocks are plain divs — ``ensureThinkingRun`` /
``ensureToolRun`` create ``div``s through the run builders, never
open/foldable; the container window's PRE-update pin capture
moves BEFORE the block append — the append's height growth must
not leak into the "pre" reading; the first-frame edge case — a
fresh window is always pinned; the legacy block-level pin
capture stays, no-op'ing on the divs): the full source must
match the pin — the ``reask`` path, the turn-local resets, the
SSE contract, the save points, and the sanctioned additions (the
task-01 ``lastUserWrap`` assignment + the idle-settle mark
pass, the task-03 aborted-settle mark pair, the phase-160
done-settle pair, the phase-161 live-container additions, and
the D5 refinement) included."""
assert _fn_source(_js(), "runTurn") == PINNED_RUN_TURN
+27 -15
View File
@@ -145,20 +145,25 @@ def test_thinking_event_is_a_first_class_turn_branch() -> None:
def test_thinking_run_helpers_are_idempotent() -> None:
"""Phase 158 (D1/D5 — the phase-109 D15 pin, replaced by owner
direction 2026-10-04): ``ensureThinkingRun`` returns the active
run's block or creates a NEW one OPEN (phase 161, D1: appended
into the reasoning container's ``.reasoning-body`` — the
per-run bubble anchor is superseded; a frame of a new kind
closes the previous run first); ``closeCurrentRun`` is a no-op
without a run and folds the active one (the D15 re-open of the
shared block is gone — a post-delta thinking frame opens a NEW
block)."""
run's block or creates a NEW one (the D5 refinement, owner
2026-10-06 — a plain ``div.thinking`` through the run builder,
NEVER open/foldable: the container is the ONE disclosure); it is
appended into the reasoning container's ``.reasoning-body`` (phase
161, D1 — the per-run bubble anchor is superseded; a frame of a
new kind ends the previous run first); ``closeCurrentRun`` is a
no-op without a run and ends the active one (nothing folds — the
D5 refinement; the D15 re-open of the shared block is gone — a
post-delta thinking frame opens a NEW block)."""
js = _js()
fn = js.find("function ensureThinkingRun")
assert fn != -1, "ensureThinkingRun must exist (near addTyping/removeTyping)"
body = js[fn : js.find("\n}\n", fn)]
assert "closeCurrentRun()" in body, ("a frame of a new kind closes the "
assert "closeCurrentRun()" in body, ("a frame of a new kind ends the "
"previous run (D1)")
assert "block.open = true" in body, "D5: created open — the stream is the show"
assert "createThinkingRunEl()" in body, (
"D5 refinement: a plain div — never collapsible (the container "
"is the ONE disclosure)"
)
assert (
'ensureReasoningWrap(wrap).querySelector(".reasoning-body")'
".appendChild(block)" in body
@@ -169,15 +174,20 @@ def test_thinking_run_helpers_are_idempotent() -> None:
fn2 = js.find("function closeCurrentRun")
assert fn2 != -1, "closeCurrentRun must exist"
body2 = js[fn2 : js.find("\n}\n", fn2)]
assert "currentRun.el.open = false" in body2
assert ".open" not in body2, (
"D5 refinement: nothing folds — the run simply ends (the "
"in-container runs are non-collapsible divs)"
)
assert "currentRun = null" in body2, "a no-op without a run (idempotent)"
def test_delta_branch_collapses_block_and_transitions_to_streaming() -> None:
"""The first answer delta transitions thinking → streaming (even when
thinking created the wrap first) and folds the active run — phase 158
(D5): ``closeCurrentRun()`` (a later thinking frame opens a NEW run;
the D15 re-open of the shared block is gone)."""
thinking created the wrap first) and ends the active run — phase 158
(D5): ``closeCurrentRun()`` (the D5 refinement, owner 2026-10-06 —
the in-container runs are non-collapsible divs, so nothing folds;
a later thinking frame opens a NEW run; the D15 re-open of the
shared block is gone)."""
js = _js()
delta_idx = js.find('ev.type === "delta"')
done_idx = js.find('ev.type === "done"')
@@ -190,8 +200,10 @@ def test_delta_branch_collapses_block_and_transitions_to_streaming() -> None:
def test_done_branch_sets_sawdone_and_closes_block() -> None:
"""On `done` the turn marks itself complete (sawDone — the stream-drop
guard keys off it) and settles EVERY open run closed (phase 158, D5:
``closeAllRuns``)."""
guard keys off it) and settles the runs (phase 158, D5:
``closeAllRuns`` — the D5 refinement, owner 2026-10-06 — the
in-container runs are non-collapsible divs, so the defensive
details query no-ops in every live path)."""
js = _js()
done_idx = js.find('ev.type === "done"')
error_idx = js.find('ev.type === "error"')
+12 -2
View File
@@ -300,8 +300,18 @@ def test_segments_keep_arrival_order_above_the_bubble() -> None:
)
assert "??" not in t, ("the old anchor chain is GONE — the container "
"append is the only anchor")
assert "block.open = true" in t
assert "disc.open = true" in d
assert "createThinkingRunEl()" in t, (
"D5 refinement: a plain div — never collapsible"
)
assert "createToolRunEl()" in d, (
"D5 refinement: a plain div — never collapsible"
)
assert ".open =" not in t, (
"D5 refinement: the in-container runs are non-collapsible divs"
)
assert ".open =" not in d, (
"D5 refinement: the in-container runs are non-collapsible divs"
)
def test_tool_call_style_is_accent_and_contrast_safe() -> None:
+75 -36
View File
@@ -211,9 +211,12 @@ def test_per_run_inserts_append_into_the_reasoning_body() -> None:
``.reasoning-body`` — ``ensureThinkingRun`` / ``ensureToolRun``
insert via ``ensureReasoningWrap(wrap).querySelector(
".reasoning-body").appendChild(…)`` (arrival order preserved: a
new run appends after the previous run's block). EVERYTHING ELSE
in both functions is unchanged (closeCurrentRun, ``open = true``
D5, the segRuns push, currentRun)."""
new run appends after the previous run's block). The D5
refinement (owner 2026-10-06): the per-run elements are plain
``div``s through the run builders (never open/foldable — the
container is the ONE disclosure); everything else in both
functions is unchanged (closeCurrentRun, the segRuns push,
currentRun)."""
js = _js()
t = _fn(js, "ensureThinkingRun")
assert (
@@ -221,7 +224,9 @@ def test_per_run_inserts_append_into_the_reasoning_body() -> None:
".appendChild(block)" in t
), "the thinking run appends into the container's body"
assert "closeCurrentRun()" in t
assert "block.open = true" in t, "D5: the per-run open/fold is unchanged"
assert "createThinkingRunEl()" in t, (
"D5 refinement: a plain div — never collapsible"
)
assert "segRuns.push(run)" in t
assert "currentRun =" in t
d = _fn(js, "ensureToolRun")
@@ -230,8 +235,16 @@ def test_per_run_inserts_append_into_the_reasoning_body() -> None:
".appendChild(disc)" in d
), "the tool run appends into the container's body"
assert "closeCurrentRun()" in d
assert "disc.open = true" in d, "D5: the per-run open/fold is unchanged"
assert "createToolRunEl()" in d, (
"D5 refinement: a plain div — never collapsible"
)
assert "segRuns.push(run)" in d
assert ".open =" not in t, (
"D5 refinement: the in-container runs are non-collapsible divs"
)
assert ".open =" not in d, (
"D5 refinement: the in-container runs are non-collapsible divs"
)
def test_the_per_run_bubble_anchor_is_gone() -> None:
@@ -484,10 +497,13 @@ def test_restore_per_run_blocks_append_into_the_reasoning_body() -> None:
append into the container's ``.reasoning-body`` — the per-run
``insertBefore(…, bubble)`` anchor is GONE from
``restoreTimeline`` (the container sits before the bubble; the
runs nest inside it, arrival order preserved). EVERYTHING ELSE
in the loop is unchanged (the per-run create, the closed state,
the tool-run seal, the well-formedness guards, the truncation
note)."""
runs nest inside it, arrival order preserved). The D5
refinement (owner 2026-10-06, parity with app.js): the
in-container runs are plain ``div``s through the run builders
(never collapsible — the container is the ONE disclosure,
created COLLAPSED by the restore); everything else in the loop
is unchanged (the tool-run seal, the well-formedness guards,
the truncation note)."""
body = _fn(_js(), "restoreTimeline")
assert "runsEl.appendChild(block)" in body, (
"the thinking block appends into the container's body"
@@ -501,12 +517,19 @@ def test_restore_per_run_blocks_append_into_the_reasoning_body() -> None:
assert "insertBefore(toolDisc" not in body, (
"the per-run bubble anchor is GONE from restoreTimeline (tool)"
)
# the per-run create + the closed state are unchanged.
assert 'summary.textContent = "Thinking"' in body, (
"the per-run thinking block structure is unchanged"
# the per-run create is the div builder (the D5 refinement).
assert "createThinkingRunEl()" in body, (
"D5 refinement: the in-container run is a plain div (never "
"collapsible — the container is the ONE disclosure)"
)
assert "createToolRunEl()" in body, (
"D5 refinement: the in-container run is a plain div (never "
"collapsible)"
)
assert "block.open = true" not in body, "the in-container runs never open"
assert "toolDisc.open" not in body, (
"D5 refinement: the in-container runs are non-collapsible divs"
)
assert "block.open = true" not in body, "D5: created COLLAPSED (unchanged)"
assert "toolDisc.open = false" in body
assert "if (toolDisc) toolDisc = null" in body, "the tool-run seal is unchanged"
@@ -768,15 +791,20 @@ def test_inner_html_is_absent_from_both_wrappers() -> None:
"innerHTML ABSENT from the shared.js builder (the house rule)"
)
# the ONLY HTML write in each timeline function is the
# escape-first renderMarkdown into .thinking-text.
# escape-first renderMarkdown into .thinking-text (the shared
# builder's local textEl; app.js's restore targets the div
# block's .thinking-text through querySelector).
for fn_name, js in (("restoreTimeline", _js()), ("addSegments", _js_shared())):
body = _fn(js, fn_name)
writes = re.findall(r"\b(\w+)\.innerHTML\s*=\s*([^;\n]+)", body)
writes = re.findall(r"([\w.$()\"'-]+\.?)\.innerHTML\s*=\s*([^;\n]+)", body)
assert len(writes) == 1, (
f"{fn_name}: exactly ONE innerHTML write (the structure is "
f"createElement): {writes}"
)
assert writes[0][0] == "textEl", "the single write is into .thinking-text"
assert (
writes[0][0].rstrip(".") == "textEl"
or 'querySelector(".thinking-text")' in writes[0][0]
), "the single write is into .thinking-text"
assert writes[0][1].strip() == "renderMarkdown(entry.text)", (
"the single write is the escape-first renderMarkdown"
)
@@ -889,29 +917,37 @@ def test_the_reasoning_body_is_the_one_bounded_live_window() -> None:
def test_the_inner_runs_are_compacted_to_one_scroll_surface() -> None:
"""Task 03 item 5: the inner-run compacting (D3 — ONE scroll
surface, the nested border weight removed):
``details.reasoning details.thinking`` is de-bordered (a 2px
``--line`` indent keeps the family rhythm, transparent
background, tight margins); the inner ``.thinking-text``
320px window is DISABLED (``max-height: none`` +
``overflow-y: visible``) so the container's window is the single
scroll surface; the nested ``.tool-calls-disclosure`` keeps its
compact margin (the documented-intent rule)."""
"""Task 03 item 5 + the D5 refinement (owner 2026-10-06): the
inner runs nest as PLAIN DIVS inside ONE scroll surface —
``details.reasoning .thinking`` (the family's brand-soft rail,
the 2px weight — distinct from the tool run's neutral rail) and
``details.reasoning .tool-calls-disclosure`` (the neutral
``--line`` rail), de-bordered (transparent background, no nested
box); the inner ``.thinking-text`` 320px window is DISABLED
(``max-height: none`` + ``overflow-y: visible``) so the
container's window is the single scroll surface. The runs are
distinct from each other by LABEL + RAIL: the brand-ink
"Thinking" label vs the accent-ink "Tool call(s) (N)" label.
"""
css = _css()
run = _rule(css, "details.reasoning details.thinking")
assert "border: 0" in run, ("the nested border weight is removed")
assert "border-left: 2px solid var(--line)" in run
assert "border-radius: 0" in run
assert "margin: 0.25rem 0" in run
assert "background: transparent" in run
inner_text = _rule(css, "details.reasoning details.thinking .thinking-text")
run = _rule(css, "details.reasoning .thinking")
assert "border-left: 2px solid var(--brand-soft)" in run, (
"the thinking run carries the brand-soft rail"
)
inner_text = _rule(css, "details.reasoning .thinking .thinking-text")
assert "max-height: none" in inner_text, (
"the inner 320px window is disabled"
)
assert "overflow-y: visible" in inner_text
disc = _rule(css, "details.reasoning .tool-calls-disclosure")
assert "margin: 0.25rem 0" in disc
assert "border-left: 2px solid var(--line)" in disc, (
"the tool run carries the neutral rail (distinct from the "
"thinking run's brand-soft rail)"
)
label = _rule(css, "details.reasoning .thinking .thinking-summary")
assert "var(--brand-ink)" in label, (
"the thinking label is brand-ink (the family pairing)"
)
def test_the_reasoning_still_is_in_the_house_reduced_motion_block() -> None:
@@ -949,8 +985,11 @@ def test_the_legacy_no_container_rules_are_untouched() -> None:
# line-anchored: the nested details.reasoning selector is a
# different rule).
assert len(re.findall(r"(?m)^\.tool-calls-disclosure \{", css)) == 1
disc = _rule(css, ".tool-calls-disclosure")
assert "margin: 0.25rem 0" in disc
disc = re.search(
r"(?m)^\.tool-calls-disclosure \{([^}]*)\}", css
)
assert disc, "styles.css must style .tool-calls-disclosure (base)"
assert "margin: 0.25rem 0" in disc.group(1)
chevron = _rule(css, ".tool-calls-summary::before")
assert 'content: "▸"' in chevron
assert "margin-right: 0.5rem" in chevron
+107 -76
View File
@@ -78,27 +78,34 @@ 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_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),
appended into the phase-161 reasoning container's
def test_ensure_thinking_run_builds_a_new_div_block_in_the_container() -> None:
"""D1/D3 + the D5 refinement (owner 2026-10-06):
``ensureThinkingRun`` returns the active thinking run's block or
— a NEW plain ``div.thinking`` (the run builder —
``createThinkingRunEl``: the static ``.thinking-summary``
"Thinking" literal + the ``.thinking-text``; NEVER open/foldable
— the container is the ONE disclosure, always visible while it
is open), 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."""
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 builder;
the fresh run joins segRuns + currentRun."""
js = _js()
body = _fn(js, "ensureThinkingRun")
assert 'currentRun?.kind === "thinking"' in body, (
"the active thinking run is extended, not recreated"
)
assert "closeCurrentRun()" in body, (
"a frame of a new kind CLOSES the previous run first (D1)"
"a frame of a new kind ends the previous run first (D1)"
)
assert "createThinkingRunEl()" in body, (
"the D5 refinement: a plain div — never collapsible"
)
assert ".open =" not in body, (
"the in-container runs are non-collapsible divs"
)
assert "createThinkingBlock()" in body, "the shared builder is reused"
assert "block.open = true" in body, "D5: created OPEN while its frames stream"
assert (
'ensureReasoningWrap(wrap).querySelector(".reasoning-body")'
".appendChild(block)" in body
@@ -139,13 +146,15 @@ def test_create_thinking_block_is_the_phase_17_static_template_without_data() ->
)
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), appended into the
phase-161 reasoning container's ``.reasoning-body`` (D1 — the
def test_ensure_tool_run_builds_a_new_div_disclosure_in_the_container() -> None:
"""D1 + the D5 refinement (owner 2026-10-06): ``ensureToolRun``
returns the active tool run's disclosure or — a NEW plain
``div.tool-calls-disclosure`` (the run builder —
``createToolRunEl``: the static ``.tool-calls-summary`` + the
.tool-calls list, role=list + aria-label "Tool calls"; NEVER
open/foldable — the container is the ONE disclosure, always
visible while it is open), 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
@@ -154,8 +163,12 @@ def test_ensure_tool_run_builds_a_new_open_disclosure_in_the_container() -> None
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 "createToolRunEl()" in body, (
"the D5 refinement: a plain div — never collapsible"
)
assert ".open =" not in body, (
"the in-container runs are non-collapsible divs"
)
assert (
'ensureReasoningWrap(wrap).querySelector(".reasoning-body")'
".appendChild(disc)" in body
@@ -321,14 +334,19 @@ def test_the_arm_follows_the_line_append_with_the_new_signature() -> None:
def test_close_current_run_and_close_all_runs_are_defined() -> None:
"""D5's fold side: ``closeCurrentRun`` folds the ACTIVE run
(idempotent; a no-op without a run) and ``closeAllRuns`` closes
EVERY open ``details.thinking`` + ``details.tool-calls-disclosure``
in the wrap (defensive — a late double-close is a no-op; a no-op
without a wrap)."""
"""The D5 refinement's (owner 2026-10-06) fold side:
``closeCurrentRun`` ENDS the ACTIVE run (nothing folds — the
in-container runs are non-collapsible divs; idempotent; a
no-op without a run) and ``closeAllRuns`` closes EVERY open
``details.thinking`` + ``details.tool-calls-disclosure`` in the
wrap (the DEFENSIVE legacy guard — the pre-phase-161
details blocks the restore path creates, already closed; a
no-op in every live path)."""
js = _js()
cur = _fn(js, "closeCurrentRun")
assert "currentRun.el.open = false" in cur
assert ".open" not in cur, (
"D5 refinement: nothing folds — the run simply ends"
)
assert "currentRun = null" in cur, "the run is cleared (a later close is a no-op)"
all_runs = _fn(js, "closeAllRuns")
assert "details.thinking, details.tool-calls-disclosure" in all_runs
@@ -581,34 +599,35 @@ def test_the_pre_phase_narrative_is_replaced() -> None:
def test_restore_timeline_builds_the_collapsed_per_run_structure() -> None:
"""D5: ``restoreTimeline`` renders the record's ``segments``
COLLAPSED — a ``kind:"thinking"`` entry gets its OWN
``details.thinking`` (createElement structure, the plain
``Thinking`` literal via textContent — D3; NO ``open = true`` —
created closed), and CONSECUTIVE ``kind:"tool"`` entries group
into ONE ``details.tool-calls-disclosure`` (the shared
phase-117 builder — ``createToolDisclosure``, innerHTML ABSENT —
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
inside the container COLLAPSED — a ``kind:"thinking"`` entry
gets its OWN plain ``div.thinking`` (the D5 refinement — the
run builder, never collapsible), and CONSECUTIVE
``kind:"tool"`` entries group into ONE plain
``div.tool-calls-disclosure`` (the run builder — the static
label + the .tool-calls list; innerHTML ABSENT). 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")
assert "document.createElement(\"details\")" in body, "the structure is createElement"
assert 'block.className = "thinking"' in body
assert "block.open = true" not in body, "D5: created COLLAPSED (no open state)"
assert 'summary.textContent = "Thinking"' in body, (
"the plain Thinking literal (D3 — the phase-17 literal; "
"position distinguishes the runs) via textContent"
assert "createThinkingRunEl()" in body, (
"the D5 refinement: the in-container run is a plain div "
"(never collapsible — the container is the ONE disclosure)"
)
assert "createToolRunEl()" in body, (
"the D5 refinement: the in-container run is a plain div "
"(never collapsible)"
)
assert "block.open = true" not in body, "the in-container runs never open"
assert "toolDisc.open" not in body, (
"the in-container runs are non-collapsible divs"
)
assert 'textEl.className = "thinking-text"' in body
# the consecutive-tool grouping (one disclosure per run — the
# grouping loop): create on the first entry, seal on a kind
# change, insert before the bubble.
assert "createToolDisclosure()" in body, "the shared phase-117 builder (innerHTML ABSENT)"
assert "toolDisc.open = true" not in body, "D5: created COLLAPSED (no open state)"
assert "toolDisc.open = false" in body
# change, append into the container's body.
assert "if (toolDisc) toolDisc = null" in body, (
"a thinking entry seals the tool run — ONE disclosure per run (D1)"
)
@@ -682,11 +701,14 @@ def _single_escape_first_write(body: str) -> None:
into ``.thinking-text`` (the existing restore convention;
renderMarkdown's escape-first contract keeps storage
XSS-safe)."""
writes = re.findall(r"\b(\w+)\.innerHTML\s*=\s*([^;\n]+)", body)
writes = re.findall(r"([\w.$()\"'-]+\.?)\.innerHTML\s*=\s*([^;\n]+)", body)
assert len(writes) == 1, (
f"exactly ONE innerHTML write (the structure is createElement): {writes}"
)
assert writes[0][0] == "textEl", "the single write is into .thinking-text"
assert (
writes[0][0].rstrip(".") == "textEl"
or 'querySelector(".thinking-text")' in writes[0][0]
), "the single write is into .thinking-text"
assert writes[0][1].strip() == "renderMarkdown(entry.text)", (
"the single write is the escape-first renderMarkdown"
)
@@ -694,19 +716,22 @@ def _single_escape_first_write(body: str) -> None:
def test_restore_timeline_structure_is_create_element_and_the_only_write_is_escape_first() -> None:
"""innerHTML ABSENT from the STRUCTURE — it is built via
createElement/textContent (the thinking block's summary is
textContent; the disclosure rides the innerHTML-absent
``createToolDisclosure`` — the task-02 pin). The ONLY HTML
createElement/textContent (the in-container runs ride the
innerHTML-absent D5-refinement run builders — the static
label via textContent; the .tool-calls list, role=list +
aria-label "Tool calls" — the task-02 pin). The ONLY HTML
write in the function is the escape-first renderMarkdown into
``.thinking-text`` (the existing restore convention — the
phase-17 pattern; renderMarkdown's escape-first contract keeps
storage XSS-safe)."""
body = _fn(_js(), "restoreTimeline")
_single_escape_first_write(body)
assert "document.createElement(\"summary\")" in body, (
"the summary is createElement + textContent"
assert "createThinkingRunEl()" in body, (
"the in-container run rides the innerHTML-absent run builder"
)
assert "createToolRunEl()" in body, (
"the in-container run rides the innerHTML-absent run builder"
)
assert "document.createElement(\"div\")" in body, "the .thinking-text is createElement"
def test_the_restore_dispatch_gives_segments_precedence_over_the_legacy_path() -> None:
@@ -756,31 +781,37 @@ def test_the_restore_dispatch_gives_segments_precedence_over_the_legacy_path() -
def test_add_segments_builds_the_collapsed_parity_timeline() -> None:
"""The shared-page parity copy (D5 — pure render, always
COLLAPSED): the same structure (a per-run details.thinking with
the plain ``Thinking`` literal via textContent + the phase-117
disclosure structure — summary.tool-calls-summary + the
.tool-calls list, role=list + aria-label "Tool calls"), the same
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 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)."""
COLLAPSED): the same structure (a per-run plain div.thinking
with the static ``.thinking-summary`` "Thinking" label via
textContent + the phase-117 disclosure structure as a plain
div — ``.tool-calls-summary`` + the .tool-calls list,
role=list + aria-label "Tool calls"; the D5 refinement —
NEVER collapsible, always visible while the container is
open), the same 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 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 COLLAPSED (the container is the ONE
disclosure; the addThinkingBlock / addToolLines collapsed
convention)."""
body = _fn(_js_shared(), "addSegments")
assert "document.createElement(\"details\")" in body, "the structure is createElement"
assert "document.createElement(\"div\")" in body, "the structure is createElement"
assert 'block.className = "thinking"' in body
assert "block.open = true" not in body, "D5: created COLLAPSED"
assert "block.open = false" in body
assert 'summary.textContent = "Thinking"' in body, "the plain Thinking literal (D3)"
assert "block.open" not in body, (
"D5 refinement: the in-container runs are non-collapsible divs"
)
assert 'label.textContent = "Thinking"' in body, "the plain Thinking literal (D3)"
assert 'textEl.className = "thinking-text"' in body
assert "renderMarkdown(entry.text)" in body, "the escape-first renderer (parity)"
# the phase-117 disclosure structure.
# the phase-117 disclosure structure (the D5 refinement — a plain
# div, never collapsible).
assert 'toolDisc.className = "tool-calls-disclosure"' in body
assert "toolDisc.open = true" not in body, "D5: created COLLAPSED"
assert "toolDisc.open = false" in body
assert "toolDisc.open" not in body, (
"D5 refinement: the in-container runs are non-collapsible divs"
)
assert 'summary.className = "tool-calls-summary"' in body
assert 'container.className = "tool-calls"' in body
assert 'setAttribute("role", "list")' in body
+53 -29
View File
@@ -62,23 +62,29 @@ def _fn(src: str, name: str) -> str:
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)."""
def test_tool_run_builds_a_div_disclosure_in_the_container() -> None:
"""Phase 158 (D1) + 161 (D1) + the D5 refinement (owner
2026-10-06): ``ensureToolRun`` creates a plain
``div.tool-calls-disclosure`` via ``createToolRunEl`` — a
``span.tool-calls-summary`` (first child — the static label, NOT
a control: the container is the ONE disclosure) + 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). The run is ALWAYS VISIBLE while the container is
open (never open/foldable — no ``.open`` anywhere); 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
disc = _fn(js, "createToolRunEl")
assert "document.createElement(\"div\")" in disc, (
"the D5 refinement: a plain div — never collapsible"
)
assert "className = \"tool-calls-disclosure\"" in disc
assert "document.createElement(\"summary\")" in disc
assert "document.createElement(\"span\")" in disc, (
"the label is a static span (not a <summary> control)"
)
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
@@ -89,11 +95,9 @@ def test_tool_run_builds_an_open_details_disclosure() -> None:
"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 "createToolRunEl()" in run
assert ".open =" not in run, (
"D5 refinement: the in-container runs are non-collapsible divs"
)
assert 'insertBefore(disc, body.querySelector(".bubble"))' not in run, (
"the per-run bubble anchor is GONE (phase 161 D1)"
@@ -186,22 +190,33 @@ def test_run_anchors_are_the_container_in_arrival_order() -> None:
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)."""
wrong order — arrival order is the contract), and the runs are
ALWAYS VISIBLE inside the open container (the D5 refinement —
never open/foldable)."""
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 "createThinkingRunEl()" in run, (
"the D5 refinement: a plain div — never collapsible"
)
assert ".open =" not in run, (
"the in-container runs are non-collapsible divs"
)
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
assert "createToolRunEl()" in d, (
"the D5 refinement: a plain div — never collapsible"
)
assert ".open =" not in d, (
"the in-container runs are non-collapsible divs"
)
def test_shared_page_add_tool_lines_builds_a_closed_disclosure() -> None:
@@ -277,12 +292,16 @@ def test_tool_call_code_rule_is_dechipped() -> None:
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)``,
"""Phase 117 (D2/D4) + the D5 refinement (owner 2026-10-06): the
NEW ``.tool-calls-disclosure`` and ``.tool-calls-summary`` rules
exist — the label carries 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)."""
(the phase-92 zero-literal invariant). The D5 refinement: the
INTERACTIVE treatment (cursor + the chevron) is scoped to the
legacy ``<summary>`` element — the in-container run's label is a
static span, not a control (the container is the ONE
disclosure)."""
css = _css()
assert re.search(r"\.tool-calls-disclosure \{", css), (
"the .tool-calls-disclosure rule must exist"
@@ -293,4 +312,9 @@ def test_tool_calls_disclosure_and_summary_rules_exist() -> None:
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"
toggle = re.search(r"summary\.tool-calls-summary \{([^}]*)\}", css)
assert toggle, (
"the D5 refinement: the toggle affordance is scoped to the "
"legacy <summary> (the in-container label is a static span)"
)
assert "cursor: pointer" in toggle.group(1), "a toggle affordance"