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:
@@ -21,17 +21,18 @@ shape (list → drill → read → the quoted answer) with a DISTINCT
|
||||
``reasoning_content`` run streamed AHEAD of every tool call and ahead
|
||||
of the final answer — the ``Step N`` markers make each run
|
||||
individually assertable. The client renders each CONTIGUOUS run in its
|
||||
own ``details.thinking`` / ``details.tool-calls-disclosure`` (D1), all
|
||||
above the answer bubble in arrival order (D2 — Option B,
|
||||
owner-chosen), open while the run's frames stream and folded at rest
|
||||
(D5), and persists the run order in the record's ``segments`` array
|
||||
(D4). Phase 161 (the documented supersession, owner TODO direction
|
||||
2026-10-05): the per-run blocks are NESTED inside ONE
|
||||
own per-run block (D1) in arrival order (D2 — Option B,
|
||||
owner-chosen) and persists the run order in the record's ``segments``
|
||||
array (D4). Phase 161 (the documented supersession, owner TODO
|
||||
direction 2026-10-05): the per-run blocks are NESTED inside ONE
|
||||
``details.reasoning`` container (``.reasoning-body``) that sits
|
||||
before the bubble — open during the turn, folded at done / on
|
||||
restore / on the shared page; the per-run open/fold behavior itself
|
||||
is unchanged. The sequence pin is shape-unchanged, read from the
|
||||
container's ``.reasoning-body`` children (the container is the one
|
||||
restore / on the shared page. The D5 refinement (owner 2026-10-06):
|
||||
the per-run blocks are plain divs — NOT collapsible (the container
|
||||
is the ONE disclosure; a nested fold inside the fold used to hide
|
||||
the live reasoning mid-turn), always visible while the turn is live.
|
||||
The sequence pin is shape-unchanged, read from the container's
|
||||
``.reasoning-body`` children (the container is the one
|
||||
new parent — the pre-bubble ``.msg-body`` children are exactly
|
||||
``["details.reasoning"]``).
|
||||
|
||||
@@ -54,15 +55,17 @@ Test → phase mapping (Playwright Mapping Rule):
|
||||
one lump); each disclosure summary is ``Tool call (1)``; the
|
||||
answer bubble carries the ``Read <source/path>`` echo.
|
||||
2. ``test_runs_fold_at_rest`` — D5 (phase 161 D2): after ``done``
|
||||
the container AND every ``.thinking`` /
|
||||
``.tool-calls-disclosure`` inside it has NO ``open`` attribute
|
||||
(the settle folded the runs + the container); the at-rest visible
|
||||
line is the container's ``Reasoning`` summary (``7 steps``);
|
||||
opening it reveals the folded run summaries.
|
||||
the container has NO ``open`` attribute (the settle folded it) and
|
||||
the inner runs are NOT collapsible (zero ``<details>`` inside the
|
||||
container — the D5 refinement); the at-rest visible line is the
|
||||
container's ``Reasoning`` summary (``7 steps``); opening it
|
||||
reveals the always-visible run blocks.
|
||||
3. ``test_live_runs_open_while_streaming`` — D5 (the live half):
|
||||
DURING the turn (the mock's pacing window — the 0.1 s tool frames +
|
||||
the thinking stream, the answer bubble still EMPTY) the active run's
|
||||
block is ``open`` (the live feedback the owner keeps).
|
||||
the thinking stream, the answer bubble still EMPTY) the container
|
||||
is ``open`` with a run visible in its ``.reasoning-body`` (the
|
||||
live feedback the owner keeps — the container is the ONE open
|
||||
disclosure).
|
||||
4. ``test_restored_timeline_and_record`` — the restore path + D4: a
|
||||
same-context RELOAD re-renders the same 7-element sequence in ONE
|
||||
collapsed reasoning container (``7 steps``, phase 161 D5/D6), and
|
||||
@@ -75,8 +78,9 @@ Test → phase mapping (Playwright Mapping Rule):
|
||||
5. ``test_shared_page_timeline`` — the shared-page parity (phase 51
|
||||
save + share flow): a FRESH context on ``/shared/<token>`` renders
|
||||
the same 7-element sequence in ONE collapsed reasoning container
|
||||
(``addSegments``), the ``Step N`` markers visible on container +
|
||||
summary click (expand ONE each), the answer bubble carries the
|
||||
(``addSegments``), the ``Step N`` markers visible on container
|
||||
click (the D5 refinement — the inner blocks are always visible
|
||||
once the container opens), the answer bubble carries the
|
||||
``Read <source/path>`` echo.
|
||||
6. ``test_no_thinking_turn_single_disclosure`` — the run-grouping
|
||||
contract for no-thinking turns (the phase-117 behavior preserved,
|
||||
@@ -192,13 +196,13 @@ STEP_MARKERS = ("Step 1", "Step 2", "Step 3", "Step 4")
|
||||
#: sequence is shape-unchanged — the container is the one new parent)
|
||||
#: — tests 1/4 (chat page) and 5 (shared page) pin it.
|
||||
TIMELINE_SEQ = [
|
||||
"details.thinking",
|
||||
"details.tool-calls-disclosure",
|
||||
"details.thinking",
|
||||
"details.tool-calls-disclosure",
|
||||
"details.thinking",
|
||||
"details.tool-calls-disclosure",
|
||||
"details.thinking",
|
||||
"div.thinking",
|
||||
"div.tool-calls-disclosure",
|
||||
"div.thinking",
|
||||
"div.tool-calls-disclosure",
|
||||
"div.thinking",
|
||||
"div.tool-calls-disclosure",
|
||||
"div.thinking",
|
||||
]
|
||||
|
||||
SHARE_URL_RE = re.compile(r"^/shared/[0-9a-f-]{36}$")
|
||||
@@ -325,18 +329,23 @@ BODY_SEQUENCE_JS = """
|
||||
}
|
||||
"""
|
||||
|
||||
#: D5's live-open predicate — the active run's block is ``open`` while
|
||||
#: the answer bubble is STILL EMPTY (the turn is in flight; the mock's
|
||||
#: pacing window — the 0.1 s tool frames + the thinking stream — keeps
|
||||
#: the window comfortably open for a 100 ms poll).
|
||||
#: D5's live predicate (the D5 refinement — the container is the ONE
|
||||
#: open disclosure): the container is ``open`` with a per-run block
|
||||
#: nested in its ``.reasoning-body`` while the answer bubble is STILL
|
||||
#: EMPTY (the turn is in flight; the mock's pacing window — the 0.1 s
|
||||
#: tool frames + the thinking stream — keeps the window comfortably
|
||||
#: open for a 100 ms poll).
|
||||
LIVE_OPEN_JS = """
|
||||
() => {
|
||||
const wrap = document.querySelector('#messages .msg.brain');
|
||||
if (!wrap) return false;
|
||||
const bubble = wrap.querySelector('.msg-body .bubble');
|
||||
if (!bubble || bubble.textContent !== '') return false;
|
||||
return !!wrap.querySelector(
|
||||
'details.thinking[open], details.tool-calls-disclosure[open]');
|
||||
const cont = wrap.querySelector('details.reasoning');
|
||||
if (!cont || !cont.hasAttribute('open')) return false;
|
||||
return !!cont.querySelector(
|
||||
'.reasoning-body div.thinking, ' +
|
||||
'.reasoning-body div.tool-calls-disclosure');
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -540,25 +549,23 @@ def test_runs_fold_at_rest(
|
||||
page.locator("#messages .msg.brain .reasoning-status")
|
||||
).to_have_text("7 steps")
|
||||
|
||||
# D5: at rest NOTHING inside is open — the ``done`` settle
|
||||
# (closeAllRuns) folded every run (a closed <details> has no
|
||||
# ``open`` attribute).
|
||||
# D5 refinement (owner 2026-10-06): the inner runs are NOT
|
||||
# collapsible — ZERO <details> inside the container (the
|
||||
# per-run blocks are plain divs, always visible while the
|
||||
# container is open).
|
||||
expect(
|
||||
page.locator("#messages .msg.brain details.thinking[open]")
|
||||
).to_have_count(0)
|
||||
expect(
|
||||
page.locator(
|
||||
"#messages .msg.brain details.tool-calls-disclosure[open]"
|
||||
)
|
||||
page.locator("#messages .msg.brain details.reasoning details")
|
||||
).to_have_count(0)
|
||||
|
||||
# The inner runs stay in the DOM inside the fold — opening the
|
||||
# container (the native toggle) reveals the 4 "Thinking" + 3
|
||||
# "Tool call (1)" lines (the timeline stays readable at rest).
|
||||
# "Tool call (1)" labels (the timeline stays readable at rest;
|
||||
# the D5 refinement — no second toggle: the blocks are always
|
||||
# visible once the container opens).
|
||||
page.locator("#messages .msg.brain .reasoning-summary").click()
|
||||
expect(container).to_have_attribute("open", "")
|
||||
expect(
|
||||
page.locator("#messages .msg.brain details.thinking > summary").first
|
||||
page.locator("#messages .msg.brain .thinking .thinking-summary").first
|
||||
).to_be_visible()
|
||||
expect(
|
||||
page.locator("#messages .msg.brain .tool-calls-summary").first
|
||||
@@ -652,12 +659,7 @@ def test_restored_timeline_and_record(
|
||||
f"the restored in-container sequence is not the timeline: {seq}"
|
||||
)
|
||||
expect(
|
||||
page.locator("#messages .msg.brain details.thinking[open]")
|
||||
).to_have_count(0)
|
||||
expect(
|
||||
page.locator(
|
||||
"#messages .msg.brain details.tool-calls-disclosure[open]"
|
||||
)
|
||||
page.locator("#messages .msg.brain details.reasoning details")
|
||||
).to_have_count(0)
|
||||
# The restored answer (the restore is synchronous at boot).
|
||||
expect(
|
||||
@@ -778,24 +780,17 @@ def test_shared_page_timeline(
|
||||
assert seq == TIMELINE_SEQ, (
|
||||
f"the shared in-container sequence is not the timeline: {seq}"
|
||||
)
|
||||
# D5 refinement (owner 2026-10-06): the inner runs are NOT
|
||||
# collapsible — ZERO <details> inside the container (the
|
||||
# per-run blocks are plain divs).
|
||||
expect(
|
||||
anon.locator("#messages .msg.brain details.thinking[open]")
|
||||
).to_have_count(0)
|
||||
expect(
|
||||
anon.locator(
|
||||
"#messages .msg.brain details.tool-calls-disclosure[open]"
|
||||
)
|
||||
anon.locator("#messages .msg.brain details.reasoning details")
|
||||
).to_have_count(0)
|
||||
|
||||
# The ``Step N`` markers are visible on a summary click (expand
|
||||
# ONE — the native <summary> is a real focusable toggle; phase
|
||||
# 161: the container's own summary first — the inner runs'
|
||||
# summaries sit inside its closed fold).
|
||||
# The ``Step N`` markers are visible on the container's summary
|
||||
# click (the D5 refinement — ONE toggle: the inner blocks are
|
||||
# always visible once the container opens; no second toggle).
|
||||
anon.locator("#messages .msg.brain .reasoning-summary").click()
|
||||
anon.locator("#messages .msg.brain details.thinking > summary").first.click()
|
||||
expect(
|
||||
anon.locator("#messages .msg.brain details.thinking").first
|
||||
).to_have_attribute("open")
|
||||
first_text = anon.locator("#messages .msg.brain .thinking-text").first
|
||||
expect(first_text).to_be_visible()
|
||||
expect(first_text).to_contain_text(STEP_MARKERS[0])
|
||||
|
||||
Reference in New Issue
Block a user