phase: 161_reasoning_bubble
All gates verified. Final check — AGENTS.md rules against the project (commands, gates, protocols) — the quick-reference commands all exist and the rules (PLAN §2/§7/§9 references, phase protocol, No CDN, Debugpy, git gates) accurately describe the project; the reasoning bubble is a pure UI change with no new commands or env vars, so no AGENTS.md change is needed.
**Verification complete — full results:**
- **Verified** (implemented by prior task passes, all in working tree): `createReasoningWrap`/`ensureReasoningWrap` live container (D1), `settleReasoning` fold at the 3 settle sites + not at delta (D2), 320px `.reasoning-body` window + pinned-tail follow (D3), zero persistence change (D4), collapsed restore + shared-page parity with D6 step counts, `styles.css` family treatment + reduced-motion still, mock `REASONING_MIDROUND_TRIGGER` flow, 6-test story suite, phase-158 pin updates
- **Full suite:** `uv run pytest` → **3738 passed, 0 failed** · `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99% (>90%)** · `uv run ruff check .` → all checks passed · `uv run pyright` → **0 errors, 0 warnings**
- **E2E in isolation** (DB up; port-8123 collision with an unrelated `undercooked` service → `E2E_APP_PORT=8125`/`E2E_MOCK_PORT=8902`, no live change made): `test_reasoning_bubble.py` **6/6** · `test_thinking_timeline.py` ✓ · `test_thinking_display.py` ✓ · `test_tool_call_compact.py` ✓ · `test_big_read_progress.py` ✓ · `test_turn_progress_loader.py` ✓ · `test_share_chat.py` ✓ · `test_chat_persistence.py` ✓ · `test_smoke.py` ✓ (all exit 0, zero failures)
- **Completion criteria:** all 9 checked against the code + test outcomes — container open/live/nested ✓, stays open past mid-round delta ✓, folds at done ✓, restore + shared collapsed with "3 steps" ✓, plain turn = no container ✓, regression suites green ✓, suite/coverage/lint green ✓, diff scope limited to `frontend/assets/{app.js,shared.js,styles.css}` + `tests/` + `README.md` + phase files (no `app/` — D4) ✓
- **Documentation:** README.md "Thinking" section names the Reasoning bubble accurately (320px window, open-until-settle incl. mid-round deltas, "N steps", byte-identical no-container, collapsed on restore/shared) — verified consistent, no changes needed; AGENTS.md — verified consistent, no changes needed; all `.env.example` vars documented
- **Decisions:** commit + phase-dir move left to the harness (per pass rules — no git index/history commands executed); `undercooked` dev server on port 8123 untouched, E2E used the env port overrides
- **Next pending phase:** none — `161_reasoning_bubble` is the last in `todo/`
This commit is contained in:
@@ -682,6 +682,99 @@ details.thinking .thinking-text {
|
||||
details.thinking .thinking-text p,
|
||||
details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
|
||||
/* Phase 161 (owner TODO 2026-10-05, D3): the ONE reasoning bubble —
|
||||
the ENTIRE per-run timeline (phase-158 thinking + tool-call blocks,
|
||||
arrival order) nests INSIDE this details.reasoning container instead
|
||||
of stacking above the answer, so the live turn condenses to ONE
|
||||
bounded window (the .reasoning-body, 320px) and the runs no longer
|
||||
push the answer off-screen. Family treatment of details.thinking
|
||||
(phase 17): the SAME surface/line/brand-soft/radius tokens, so the
|
||||
two disclosures read as one block family. The summary is a native
|
||||
focusable toggle — >=44px target, the house 3px :focus-visible ring,
|
||||
the webkit marker suppressed, a CSS chevron ▸ that rotates 90° when
|
||||
open (the transition stills under prefers-reduced-motion — see the
|
||||
reduced-motion block below). Contrast: --brand-ink on --surface
|
||||
≈8.7:1, --ink-soft on --surface ≈6.9:1 (the status span) — both AA.
|
||||
overflow: hidden clips the scrollable body to the corner radius. */
|
||||
details.reasoning {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-left: 3px solid var(--brand-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
margin: 0 0 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
summary.reasoning-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
min-height: 44px; /* WCAG 2.1 AA touch target (the family pin) */
|
||||
color: var(--brand-ink); /* 8.7:1 on --surface */
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
summary.reasoning-summary::-webkit-details-marker { display: none; }
|
||||
/* CSS chevron: ▸ rotates 90° when open (the details.thinking
|
||||
convention; the transition stills under prefers-reduced-motion —
|
||||
see the reduced-motion block below). */
|
||||
summary.reasoning-summary::before {
|
||||
content: "▸";
|
||||
display: inline-block;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
details.reasoning[open] summary.reasoning-summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
summary.reasoning-summary:focus-visible {
|
||||
outline: 3px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
/* D6: the live status suffix ("Thinking…" / "Calling <name>…" /
|
||||
"N steps") rides the summary row AFTER the "Reasoning" literal —
|
||||
the flex row lays it out after the chevron + literal; ink-soft on
|
||||
surface ≈6.9:1 (AA); text + color, never color alone (B5). */
|
||||
.reasoning-status {
|
||||
color: var(--ink-soft); /* 6.9:1 on --surface */
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
/* D3: the ONE bounded live window — 320px (the phase-17 window
|
||||
convention), user-scrollable; app.js's pinned-tail follow
|
||||
(REASONING_NEAR_BOTTOM_PX) keeps the live tail in view while the
|
||||
user stays near the bottom — scrolling up pauses it, returning to
|
||||
the bottom resumes it. The per-run blocks nested inside are
|
||||
de-bordered below so THIS is the single scroll surface (no nested
|
||||
boxes). */
|
||||
.reasoning-body {
|
||||
padding: 0 0.75rem 0.75rem;
|
||||
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;
|
||||
}
|
||||
details.reasoning details.thinking .thinking-text {
|
||||
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; }
|
||||
|
||||
/* Phase 117: the tool-call record folds into a single disclosure line,
|
||||
reusing the Thinking block's native <details> convention. Open while
|
||||
the turn is live (phase 87's elapsed suffix stays visible), folded at
|
||||
@@ -1642,6 +1735,8 @@ a.answer-image:hover { text-decoration: underline; } /* flat — the caption und
|
||||
details.thinking summary::before { transition: none; }
|
||||
/* Phase 117 tool-calls disclosure: the same chevron, the same still. */
|
||||
.tool-calls-summary::before { transition: none; }
|
||||
/* Phase 161 reasoning bubble: the family chevron, the family still. */
|
||||
summary.reasoning-summary::before { transition: none; }
|
||||
}
|
||||
|
||||
/* Phase 117 (owner request 2026-09-15, live-mockup-confirmed — the
|
||||
|
||||
Reference in New Issue
Block a user