Files
brain-of-reese/.agent/user_stories/thinking-no-scroll.md
T

100 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Story: Thinking No Scroll (live-tail Thinking window)
**Phase:** `21_thinking_no_scroll` · **Source:** `TODO.md` L4 ·
**E2E:** `tests/e2e/test_thinking_no_scroll.py`
## Bug report (verbatim, `TODO.md` L4)
> "Disable scroll in the thinking window. Users don't need to scroll back
> through thinking, just see it live."
## Narrative
As **a user**, the Thinking block is a *scratchpad, not a transcript*:
while Brain reasons I just want to watch the newest lines appear — not
scroll back through everything it already thought. The block should
always show the **live tail** of the reasoning stream, in its fixed
320px window, with no way to scroll it back.
- **Given** a turn streams a long reasoning body (longer than the 320px
window)
- **When** I try to scroll back through the window — mouse wheel, mouse
drag, or keyboard — while it streams (or while it sits open before the
first token)
- **Then** the window does not move: it stays glued to the live tail,
pinned there by the phase-17 per-chunk bottom-pin (the sole scroller).
## Acceptance criteria
1. With a long thinking stream, wheel / mouse-drag / keyboard on
`.thinking-text` do **not** move it; the visible content is always the
live tail (pinned at the bottom, within 1px, after each chunk).
2. Computed style of `.thinking-text`: `overflow-y: hidden`,
`max-height: 320px` — the 320px clip stays (owner-confirmed: no
auto-height growth, no "↓ more" affordance).
3. A long **answer** bubble is untouched: it keeps its existing scroll
behavior (phase 11) — phase 21 only changed `.thinking-text`.
4. Restored (collapsed) Thinking blocks are untouched (phase 14/17
restore path): they render collapsed with their text, where overflow
is moot.
5. No new surface: the block keeps its summary chevron, focus-visible
ring, aria contract, and the reduced-motion stillness (styles.css
`prefers-reduced-motion` block).
## Owner-confirmed (2026-08-24, roadmap A2)
1. **Keep the 320px clip** — "just see it live" means the window stays a
fixed 320px viewport showing the newest lines; no auto-height growth,
no "↓ more" affordance.
2. **The answer bubble is untouched** — final answers keep their existing
scroll behavior (phase 11 long answers).
3. **Restored (collapsed) Thinking blocks are untouched** — the phase-14
restore renders them collapsed, where overflow is moot.
## UI Visualization & Structure
- **The whole functional change is one CSS property**
(`frontend/assets/styles.css`, phase-17 thinking section):
`details.thinking .thinking-text` — `overflow-y: auto` →
`overflow-y: hidden` (with the owner-choice comment: *no user scroll
back (owner choice 2026-08-24): the window is a live tail only — the
JS bottom-pin is the sole scroller*). `max-height: 320px` and every
other declaration stay byte-identical.
- **Why this works:** `overflow: hidden` disables *user* scrolling
(wheel/drag/keyboard) but still permits *programmatic* scrolling — so
the phase-17 bottom-pin (`textEl.scrollTop = textEl.scrollHeight` on
every `thinking` chunk, `frontend/assets/app.js`) keeps the window
glued to the live tail with **no JS change**.
- **Non-goals:** no change to the answer bubble, the collapsed restore
state, the summary/chevron, or the auto-collapse on the first delta
(phase 17).
## Playwright Mapping Rule
**Test Scenario → `tests/e2e/test_thinking_no_scroll.py`** (mock LLM;
phase 21 lengthened `mock_llm.compose_thinking` to ~2 700 chars ≈ 4.5s
of paced `reasoning_content` frames so the scratchpad overflows the
320px window; tests 1–2 use the phase-20 `think out loud then hesitate`
trigger — after the stream ends there is a deterministic 4s pre-content
pause with the block still open and no further pin frames, a frozen live
tail where any (regressed) user scroll would persist and be observable):
1. `test_thinking_window_not_user_scrollable` — wait until the window
overflows and the thinking stream ends (text stable; block still
open, pre-token). Assert the pin left it at the tail; then focus
`.thinking-text`, wheel up ×2, `Home` + `ArrowUp`, mouse-drag up:
`scrollTop` must not move (within 1px) and the window must still show
the live tail.
2. `test_thinking_window_tracks_live_tail` — while the stream is live:
once the 2nd-to-last chunk has landed, the window is pinned to the
tail; after the last chunk (held by the 4s pause) it is still pinned,
the last chunk's text (the final text node) renders inside the
visible rectangle, and a hit-test at the box's bottom lands inside
`.thinking-text`.
3. `test_thinking_window_css_contract` — computed style: `overflow-y` is
`hidden`, `max-height` is `320px`, and the clip is real
(`scrollHeight > clientHeight` for the long scratchpad).
4. `test_answer_bubble_still_scrollable` (regression, phase 11) — the
long-answer mock question streams to completion; the answer bubble's
computed `overflow-y` is not `hidden`, the page is scrollable, and
wheel up/down over the answer moves the page scroll both ways.
5. `test_restored_collapsed_thinking_unaffected` (regression, phase 17)
— a settled thinking turn is reloaded; the restored conversation
renders the Thinking block **collapsed** with its full text (matching
the persisted raw text), and the answer bubble is intact.