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/).
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user