fix(chat): stop autoscrolling while a reply streams (owner direction)
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." Owner direction 2026-08-27 (roadmap A1) revises the
phase-18 follow-the-bottom choice: the page NEVER auto-scrolls while a
turn streams. Kept (owner decision): the submit reveal (the user's own
message) and the one-shot phase-14 restore landing.
- frontend/assets/app.js: delete NEAR_BOTTOM_PX + isNearBottom;
scrollReveal becomes the one unconditional scrollIntoView (still
smooth, still "auto" under prefers-reduced-motion via SCROLL);
addMessage(who, html, scroll = false) carries an explicit scroll
intent — only the submit (", true") and the two restore landings
scroll. The thinking/tool/delta handlers and the typing indicator
drop their page-scroll calls; the thinking block's INTERNAL
bottom-pin (textEl.scrollTop, phase 17 — reworked separately in
phase 43) and the turn-end focus({ preventScroll: true }) survive.
- tests/unit/test_frontend_scroll.py: rewritten pin for the new
contract — phase-18 gate absent, helper unconditional, explicit
intent at submit/restore, no page-scroll call in the streaming
handlers, typing bubble scroll-free, SCROLL reduced-motion intact.
- tests/unit/test_chat_persistence.py: restore-landing pin updated to
the new signature (the old forced "auto" is gone; the landing
rides the default SCROLL — noted at the call site).
- tests/e2e/test_no_reply_autoscroll.py (new, replaces the deleted
test_follow_bottom_scroll.py): no autoscroll across >=10 samples
(1px tolerance) during a long answer and during the thinking stream;
submit-from-the-top still reveals the user message; the restore
landing lands one-shot on the latest message and stays; long answer
+ sources and the collapsed thinking block persist and restore.
E2E (isolation): test_no_reply_autoscroll.py 5/5; regressions
test_chat_rag 3/3, test_thinking_display 5/5,
test_chat_persistence 4/4, test_long_answers 2/2, test_smoke 3/3;
unit+integration 723 passed, app/ coverage 99%; ruff + pyright clean.
This commit is contained in:
@@ -85,10 +85,13 @@ def test_raw_text_only_stored_and_re_rendered_on_restore() -> None:
|
||||
on restore) — no HTML is ever stored. Restore re-applies the full
|
||||
brain-message chrome: is-deflected styling, maybe-try chips, sources."""
|
||||
js = _js()
|
||||
# Phase 18: restore landings are forced ("auto" + force) one-shot
|
||||
# scrollReveal calls — the only forced scrolls in the app.
|
||||
assert 'addMessage("user", renderMarkdown(m.text), "auto", true)' in js
|
||||
assert 'addMessage("brain", renderMarkdown(m.text), "auto", true)' in js
|
||||
# Phase 42 (owner direction 2026-08-27): the reply autoscroll is gone;
|
||||
# restore landings keep their one-shot load-time scroll via the
|
||||
# explicit intent (scroll=true) — the new addMessage signature has no
|
||||
# per-call behavior override (default SCROLL instead of forced
|
||||
# "auto" — documented at the call site).
|
||||
assert 'addMessage("user", renderMarkdown(m.text), true)' in js
|
||||
assert 'addMessage("brain", renderMarkdown(m.text), true)' in js
|
||||
assert "wrap.classList.add(\"is-deflected\")" in js
|
||||
assert "appendMaybeTry(wrap, m.suggestions)" in js
|
||||
assert "appendSources(wrap, m.sources)" in js
|
||||
|
||||
Reference in New Issue
Block a user