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
+61 -24
View File
@@ -753,27 +753,53 @@ summary.reasoning-summary:focus-visible {
max-height: 320px;
overflow-y: auto;
}
/* D3 inner-run compacting: the per-run blocks nest de-bordered inside
the container — the nested border weight is removed (a 2px line
indent keeps the family rhythm) and the inner 320px windows are
disabled (max-height: none) so the container's window is the single
scroll surface (the phase-17 per-block follow in app.js no-ops on
the now-unscrollable .thinking-text). */
details.reasoning details.thinking {
border: 0;
border-left: 2px solid var(--line);
border-radius: 0;
margin: 0.25rem 0;
background: transparent;
/* D3 inner-run compacting + the D5 refinement (owner 2026-10-06):
the per-run blocks nest as plain divs inside the container —
NEVER collapsible (the container is the ONE disclosure; a nested
fold inside the fold was the defect). The blocks are distinct
from each other by LABEL + RAIL: the thinking run carries the
family's brand-soft rail + the brand-ink "Thinking" label; the
tool run carries the neutral --line rail + the accent-ink
"Tool call(s) (N)" label (the phase-117 accent pairing). The
inner 320px windows are disabled (max-height: none) so the
container's window is the single scroll surface (the phase-17
per-block follow in app.js no-ops on the now-unscrollable
.thinking-text). */
details.reasoning .thinking {
border-left: 2px solid var(--brand-soft);
padding-left: 0.75rem;
margin: 0.5rem 0;
}
details.reasoning details.thinking .thinking-text {
/* The static (non-interactive) "Thinking" label — brand-ink on the
surface ≈8.7:1 (the family's summary pairing, AA); no cursor, no
focus target (it is not a control — the container's summary is).
*/
details.reasoning .thinking .thinking-summary {
display: block;
color: var(--brand-ink);
font-size: 0.8rem;
line-height: 1.4;
margin-bottom: 0.15rem;
}
details.reasoning .thinking .thinking-text {
padding: 0 0 0.75rem;
color: var(--ink-soft); /* 6.9:1 on --surface — the scratchpad pairing */
font-size: 0.875rem;
line-height: 1.55;
max-height: none;
overflow-y: visible;
}
/* The nested tool-calls disclosure is already compact — the rule
documents the intent (one window, one rhythm) without a visual
change. */
details.reasoning .tool-calls-disclosure { margin: 0.25rem 0; }
details.reasoning .thinking .thinking-text p,
details.reasoning .thinking .thinking-text ul { margin: 0 0 0.5rem; }
/* The tool run — the neutral rail (distinct from the thinking run's
brand-soft rail) + the phase-117 accent-ink label (≈10.4:1 on the
surface, AA). The .tool-calls rows keep the exact phase-37/117
treatment below (the shared rule — no new bytes). */
details.reasoning .tool-calls-disclosure {
border-left: 2px solid var(--line);
padding-left: 0.75rem;
margin: 0.5rem 0;
}
/* Phase 117: the tool-call record folds into a single disclosure line,
reusing the Thinking block's native <details> convention. Open while
@@ -787,24 +813,33 @@ details.reasoning .tool-calls-disclosure { margin: 0.25rem 0; }
label either way. */
.tool-calls-disclosure { margin: 0.25rem 0; }
.tool-calls-summary {
cursor: pointer;
color: var(--accent-ink);
font-size: 0.8rem;
line-height: 1.4;
padding: 0.15rem 0;
}
/* The legacy (out-of-container) disclosure's summary is a native
focusable toggle — the interactive treatment is scoped to the
<summary> element: the in-container run's label is a plain span
(the D5 refinement — the container is the ONE disclosure, so the
run's label is not a control). */
summary.tool-calls-summary {
cursor: pointer;
list-style: none; /* the native marker is redundant with the count text */
}
.tool-calls-summary::-webkit-details-marker { display: none; }
summary.tool-calls-summary::-webkit-details-marker { display: none; }
/* CSS chevron: ▸ rotates 90° when open — the same family treatment as
details.thinking summary::before (the transition stills under
prefers-reduced-motion — see the reduced-motion block below). */
.tool-calls-summary::before {
prefers-reduced-motion — see the reduced-motion block below). Scoped
to the <summary> element: the in-container run's label (a span with
the same class) carries no chevron. */
summary.tool-calls-summary::before {
content: "▸";
display: inline-block;
margin-right: 0.5rem;
transition: transform 0.15s ease;
}
.tool-calls-disclosure[open] .tool-calls-summary::before {
details.tool-calls-disclosure[open] summary.tool-calls-summary::before {
transform: rotate(90deg);
}
@@ -1733,8 +1768,10 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
.typing span.typing-elapsed { opacity: 1; }
/* Phase 17 thinking block: the chevron stills (no rotation motion). */
details.thinking summary::before { transition: none; }
/* Phase 117 tool-calls disclosure: the same chevron, the same still. */
.tool-calls-summary::before { transition: none; }
/* Phase 117 tool-calls disclosure: the same chevron, the same still
(scoped to the <summary> — the in-container span label has no
chevron to still). */
summary.tool-calls-summary::before { transition: none; }
/* Phase 161 reasoning bubble: the family chevron, the family still. */
summary.reasoning-summary::before { transition: none; }
}