phase: 161_reasoning_bubble
Build and Push Containers / build-and-push-app (push) Successful in 1m55s
Build and Push Containers / build-and-push-db (push) Successful in 14s

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:
2026-10-06 06:36:09 -04:00
parent 8172956c15
commit 051f62deb7
48 changed files with 4194 additions and 166 deletions
@@ -0,0 +1,98 @@
# Task 03 — The container's CSS
**Phase:** `161_reasoning_bubble` · **Source:** `TODO.md:3 — "The reasoning process should be one collapsible section that **stays open** during the reasoning process and shows all thinking and tool calls in order. Right now the thinking, tool calling, thinking, tool calling blocks take up a ton of space and obscure the answer. We need to condense this while still letting the user watch the thinking process happen so the latency doesn't feel super low. This new thinking/tool calling bubble should only collapse once the final response has been returned and the session has stopped."`
**Story:** n/a
## Objective
The `details.reasoning` container's styling: the native
`details`/`summary` treatment of the house family (the
`details.thinking` convention — L633), the bounded 320px live window
(D3), the inner-run compacting (de-bordered, one scroll surface), the
reduced-motion stills — all AA-contrast, WCAG 2.1 AA basics
(focusable summary ≥44px, `:focus-visible`, chevron).
## Work
1. `frontend/assets/styles.css` — the `details.reasoning` block (a
NEW distinct comment block citing this phase + D3; placed
ADJACENT to the `details.thinking` family — after the
`details.thinking .thinking-text` margin rule ~L684, before the
phase-117 `.tool-calls-disclosure` comment ~L689 — verify the
actual layout in the file and keep the family blocks adjacent):
`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;` — the
family treatment of `details.thinking` (the same surface/line/
brand-soft/radius tokens; the comment documents the parity + the
D3 window).
2. `frontend/assets/styles.css` — `summary.reasoning-summary`:
`display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem
0.75rem; min-height: 44px;` (the WCAG ≥44px target — parity with
the `details.thinking` summary); `color: var(--brand-ink)` (8.7:1
on `--surface` — AA — the token the `details.thinking` summary
uses); `font-size: 0.9rem; cursor: pointer; list-style: none;`;
`summary.reasoning-summary::-webkit-details-marker { display: none
}`; the CSS chevron `summary.reasoning-summary::before { content:
"▸"; display: inline-block; transition: transform 0.15s ease; }` +
`details.reasoning[open] summary.reasoning-summary::before {
transform: rotate(90deg); }` (the `details.thinking` convention);
`summary.reasoning-summary:focus-visible { outline: 3px solid
var(--brand); outline-offset: 2px; }` (the house 3px ring).
3. `frontend/assets/styles.css` — `.reasoning-status`: `color:
var(--ink-soft)` (6.9:1 on `--surface` — AA); `font-size: 0.8rem;
line-height: 1.4; margin-left: 0.25rem;` (D6 — text + color, never
color alone — B5; the status is a span INSIDE the summary — the
flex row lays it out after the literal).
4. `frontend/assets/styles.css` — `.reasoning-body`: `padding: 0 0.75rem
0.75rem; max-height: 320px; overflow-y: auto;` (D3 — the ONE
bounded live window; a user-scrollable window — the phase-17
convention; the comment cites this phase + D3 + the pinned-tail
follow in app.js).
5. `frontend/assets/styles.css` — the inner-run compacting (D3 — the
nested-border weight removed; ONE scroll surface):
`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
inner 320px window disabled — the container's window is the single
scroll surface; the phase-17 per-block follow in app.js no-ops
accordingly — the task-01 comment) + `details.reasoning
.tool-calls-disclosure { margin: 0.25rem 0; }` (already compact —
the rule documents the intent, no visual change).
6. `frontend/assets/styles.css` — the reduced-motion block (L1499,
the existing `details.thinking summary::before { transition: none;
}` stills list): append `summary.reasoning-summary::before {
transition: none; }` (the chevron rotation stills — parity with
the existing stills).
7. No JS change in this task (the selectors the task-01/02 JS
creates — `reasoning` / `reasoning-summary` / `reasoning-status` /
`reasoning-body` — are the CSS contract).
## Testing & Quality
- Unit: `tests/unit/test_reasoning_bubble.py` (extended) — the source
pins: the `details.reasoning` rule exists with the family tokens +
`overflow: hidden`; the `summary.reasoning-summary` rule with
`min-height: 44px` + the `:focus-visible` ring + the webkit-marker
suppression + the chevron `::before` + the `[open]` rotation; the
`.reasoning-status` rule with the AA token; the `.reasoning-body`
rule with `max-height: 320px` + `overflow-y: auto`; the
inner-run compacting rules (the de-border + the
`max-height: none` on the inner `.thinking-text`); the
reduced-motion still appended.
- E2E (verified in task 04's suite): the computed-style pins —
`.reasoning-body`'s `max-height` ≤ 320px + `overflow-y: auto`
(the D3 contract — test 6 of `test_reasoning_bubble.py` per the
phase overview).
- Coverage: **>90%** on this task's new/modified code (frontend — the
`app/` gate is untouched; held by the suite).
## Completion Criteria
- [ ] `tests/unit/test_reasoning_bubble.py` green (`uv run pytest
tests/unit/test_reasoning_bubble.py -v --no-cov`)
- [ ] `uv run ruff check . && uv run pyright` clean (the CSS is
verified by the unit source pins — no CSS lint gate)
- [ ] the full test suite green (`uv run pytest`)
- [ ] no behavior change in completed work beyond the documented
inner-run compacting (the `details.thinking` /
`.tool-calls-disclosure` rules OUTSIDE the container are
byte-identical — the legacy no-container path renders
unchanged)