# Phase 43 — Thinking scroll back (user scroll + generate-time autoscroll) **Source:** `TODO.md` L7 — "Add scrolling back to the thinking block, but have it autoscroll while thinking content is generating." **Story:** `.agent/user_stories/thinking-scroll-back.md` **Context:** Phase 17 streams reasoning into the collapsible `.thinking` block with a per-chunk bottom-pin (`textEl.scrollTop = textEl.scrollHeight` in the `thinking` SSE handler). Phase 21 (owner choice 2026-08-24) made `.thinking-text` a no-scroll live tail: `overflow-y: hidden` (the JS pin is the sole scroller). The owner now reverses phase 21: the window is user-scrollable again, and the pin becomes **gated** — follow the tail only while the user is pinned near the window's bottom. This is the window-level successor of the phase-18 pattern (the page-level one is removed in phase 42, which runs first and touches the same `thinking` handler line — order matters). ## Objective The Thinking block follows its live tail while reasoning is generating **and** the user is at the bottom; a scrolled-up user is never yanked down, and returning to the bottom resumes following. ## Dependencies - `42_no_reply_autoscroll` (todo) — must run **first**: it strips the page-level scroll from the same `thinking` handler; this phase then reworks the window pin in the cleaned-up handler. - `17_thinking_display` (complete) — the block, the pin, the auto-collapse on first delta. - `21_thinking_no_scroll` (complete) — the `overflow-y: hidden` + 320px window being reversed (the 320px clip is kept). ## Tasks 1. `01_window_user_scrollable.md` — CSS: `overflow-y: auto` back, comment replaced (owner direction 2026-08-27). 2. `02_gated_bottom_pin.md` — `app.js`: `THINKING_NEAR_BOTTOM_PX = 32` + gated pin; unit pin rewritten (phase-21 file replaced). 3. `03_thinking_scroll_e2e_and_commit.md` — replace the phase-21 E2E with the new-contract suite + regressions + commit. ## Testing & Quality - Unit: `tests/unit/test_thinking_no_scroll.py` **deleted**, replaced by `tests/unit/test_thinking_scroll.py` — pins: `overflow-y: auto` + `max-height: 320px` in the `.thinking-text` rule; the 2026-08-27 owner-direction comment; `export const THINKING_NEAR_BOTTOM_PX = 32`; the pin is gated on `isThinkingNearBottom(textEl)` (no unconditional pin). - Coverage: frontend-only — `app/` TOTAL unchanged, >90%. - E2E (mandatory, A16): `tests/e2e/test_thinking_scroll.py`, run in isolation. `tests/e2e/test_thinking_no_scroll.py` is **deleted** (behavior intentionally reversed). ## Completion Criteria - [ ] Wheel/drag/keyboard move `.thinking-text` (frozen-tail state); computed `overflow-y: auto`, `max-height: 320px`. - [ ] While pinned at the window bottom: each chunk re-pins to the tail (±1px). Scrolled up: no re-pin across chunks. Return to bottom: following resumes. - [ ] Auto-collapse on first delta, reduced-motion stillness, answer-bubble scroll (phase 11), restored-collapsed block (phase 17) all unchanged. - [ ] `uv run pytest` green; coverage TOTAL unchanged. - [ ] `uv run pytest tests/e2e/test_thinking_scroll.py -v --no-cov` green in isolation (DB up). - [ ] Regression E2E suites green in isolation: `test_thinking_display.py`, `test_chat_persistence.py`, `test_no_reply_autoscroll.py`, `test_smoke.py`. - [ ] `uv run ruff check . && uv run pyright` clean. - [ ] One `--no-gpg-sign` commit; phase dir moved to `.agent/phases/complete/`. ## Locked decisions - **Owner direction (2026-08-27, roadmap A2)** reverses the phase-21 owner choice (2026-08-24): the window is user-scrollable again; autoscroll only while pinned near the bottom (32px band). The 320px clip is kept (owner-confirmed). - **A15 unchanged** — SSE contract untouched; pure client-side. - **A16/A17 honoured** — one story E2E suite (replacing the removed one), one atomic commit.