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
+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