Files
brain-of-reese/.agent/user_stories/no-reply-autoscroll.md
T
ducoterra 02c76ad328 chore(agent): phase roadmap from TODO.md — 8 phases (40–47), 24 tasks
Converts the 9 TODO items into an executable phase roadmap (Protocol B,
appended after phase 39):

- 40 tuning toggle anonymous flash (TODO L3)
- 41 sync fail-fast + modal when a model is down (TODO L4)
- 42 no reply autoscroll (TODO L5)
- 43 thinking scroll back — user scroll + gated autoscroll (TODO L7)
- 44 markdown tables (TODO L6)
- 45 agent unlimited tool calls behind BOR_AGENT_MAX_ROUNDS (TODO L8)
- 46 mobile hamburger nav (TODO L9)
- 47 quadlet + jinja import formats, A9 revision (TODO L10–L11)

Each phase carries a user story, a dedicated Playwright E2E suite plan,
and owner-locked decisions (R1 A9 format extension, R2 phase-37 budget
revision, A1–A5 scope decisions) confirmed 2026-08-27.

Also records the completed phases 30–39 todo/ -> complete/ moves that
were pending in the working tree. TODO.md is cleared (items now live in
.agent/phases/todo/).
2026-08-27 18:25:53 -04:00

92 lines
4.7 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: No reply autoscroll
**Phase:** `42_no_reply_autoscroll` · **Source:** `TODO.md` L5 ·
**E2E:** `tests/e2e/test_no_reply_autoscroll.py`
## Bug report (verbatim, `TODO.md` L5)
> "Get rid of the chat reply autoscroll, it's breaking things like
> making it impossible for the user to scroll while a reply generates."
## Narrative
As **a user reading a long answer**, I want full control of the
viewport while Brain replies. Today the page auto-scrolls to follow the
stream (phase 18 "follow-the-bottom"): while I'm in the 200px
near-bottom band the page is yanked down on every thinking / tool /
delta frame, which fights my own scrolling mid-answer. The reply
autoscroll is being **removed** — the page only scrolls when I
explicitly cause it.
- **Given** a reply is streaming (thinking, tool calls, or answer text)
- **When** I scroll up to read earlier context
- **Then** the viewport stays exactly where I put it for the rest of the
turn — no frame yanks it back.
## Acceptance criteria
1. **No streaming autoscroll:** during a long thinking stream, a tool
call, and a long answer, the page never auto-scrolls — sampled
`window.scrollY` is stable (within 1px) across frames while the
viewport is away from the bottom.
2. **Submit reveals my message:** sending a question while scrolled up
still scrolls the viewport down so my own message is visible
(user-initiated — kept by owner decision).
3. **Restore landing kept:** reloading a persisted conversation
(phase 14) still lands one-shot on the latest message.
4. **The phase-18 gate is gone:** `NEAR_BOTTOM_PX` /
`isNearBottom()` and the per-frame `scrollReveal` calls in the
thinking / tool / delta handlers are removed from `app.js`; the unit
pin (`tests/unit/test_frontend_scroll.py`) is rewritten for the new
contract (scrolls happen only on submit + restore landing).
5. **Everything else unchanged:** the thinking window's *internal*
bottom-pin (phase 17 — `textEl.scrollTop`, not the page) is untouched
in this phase (phase 43 reworks it separately); message rendering,
persistence, UI states, and the 120 s guard are unchanged.
## Owner-confirmed (2026-08-27, roadmap A1)
1. **"Reply autoscroll" = the phase-18 follow-the-bottom auto-follow on
thinking / tool / delta frames.** Removed.
2. **Kept:** scroll-on-submit (reveal the user's own message) and the
one-shot restore landing on page load.
## UI Visualization & Structure
- **The functional change is in `frontend/assets/app.js` only:**
- delete `export const NEAR_BOTTOM_PX = 200`, `isNearBottom()`, and
the `force`-optional gating in `scrollReveal` — the helper becomes an
unconditional `scrollIntoView` (still smooth, still still under
`prefers-reduced-motion` via the existing `SCROLL` constant);
- `addMessage(...)` gains an explicit "scroll" intent: the **user
submit** path scrolls (shows my message), brain bubble creation and
the typing indicator do **not**;
- the thinking / tool / delta handlers drop their `scrollReveal(wrap)`
calls (the thinking handler keeps its `textEl.scrollTop` window-pin —
phase 17, reworked in phase 43);
- the phase-14 restore landing keeps its one-shot forced scroll;
- module docstrings updated (the phase-18 contract block is replaced
by the new "no reply autoscroll (owner direction 2026-08-27)"
contract).
- **Non-goals:** no new UI element, no "↓ new content" pill (the owner
wants silence, not a substitute affordance), no change to the
composer / safe-area layout.
## Playwright Mapping Rule
**Test Scenario → `tests/e2e/test_no_reply_autoscroll.py`** (mock LLM;
DB up; the phase-18 suite `tests/e2e/test_follow_bottom_scroll.py` is
**deleted** in this phase — its behavior is intentionally removed):
1. `test_no_autoscroll_during_long_answer` — `LONG_ANSWER_TRIGGER`
question; once the answer starts, scroll the viewport up ~2× the
answer height; sample `window.scrollY` across ≥10 streaming frames:
stable within 1px; after `done` the viewport is still where it was.
2. `test_no_autoscroll_during_thinking` — `THINKING_TRIGGER` question;
scroll up during the ~4.5 s thinking stream; the viewport stays
pinned (no per-chunk page follow).
3. `test_submit_reveals_user_message` — scroll to the very top of a
populated conversation, send a question; the viewport ends with the
user's message visible (bottom in view).
4. `test_restore_landing_one_shot` (phase 14 regression) — settle a
conversation, reload; the page lands on the latest message one-shot
and stays there while no stream is active.
5. `test_answer_content_intact` (regression) — the long answer streams
to completion with sources and (for thinking) the collapsed block,
persisted and restorable.