fix(rag): stop the per-run blocks from folding — the container is the one disclosure
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user