fix(frontend): stop auto-scroll from hijacking scrollback during streaming - #152
Closed
lucastononro wants to merge 5 commits into
Closed
fix(frontend): stop auto-scroll from hijacking scrollback during streaming#152lucastononro wants to merge 5 commits into
lucastononro wants to merge 5 commits into
Conversation
…aming
bottomRef.scrollIntoView({behavior:'smooth'}) fired on every chatItems
change, including every agent_token during streaming, yanking the view
back to the bottom and making it impossible to read earlier output.
Track whether the user is pinned near the bottom via a scroll listener +
threshold and only auto-scroll while pinned; use behavior:'auto' while a
bubble is actively streaming, and re-pin when the user sends a message.
Closes #99
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4
- P1: use behavior 'auto' unconditionally in the auto-scroll effect. A smooth scroll after send re-pinned and then animated through intermediate positions whose scroll events un-pinned the view mid-animation, so fast first tokens could leave the stream unfollowed. - P2: hoist AUTO_SCROLL_PIN_THRESHOLD_PX to module level so the constant is created once and is stable for the scroll-handler closure. - Add a manual test tutorial for the scroll-pinning behavior (no frontend unit-test harness exists, and jsdom cannot reproduce smooth-scroll animation events anyway). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4
Owner
Author
|
Addressed Greptile review findings in 05cb539: Fixed
Dismissed: none — both findings were valid. Tests
Validation: |
lucastononro
added a commit
that referenced
this pull request
Jul 29, 2026
lucastononro
added a commit
that referenced
this pull request
Jul 29, 2026
Owner
Author
|
Merged into integration branch |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
bottomRef.current?.scrollIntoView({behavior:'smooth'})on everychatItemschange, including every singleagent_tokenduring streaming — many times per second. That yanked the view back to the bottom constantly and made it impossible to scroll up and read earlier output mid-run.Changes
src/app/page.tsx— addedchatScrollRef(ref to the actualoverflow-y-autoscrollable chat pane) andpinnedToBottomRef, updated by a newonScrollhandler (handleChatScroll) using a distance-from-bottom threshold (AUTO_SCROLL_PIN_THRESHOLD_PX = 96).behavior: 'auto'(no animation) while a bubble is actively streaming (streamingItemIdRef.currentset) vs.'smooth'once streaming settles.addItemre-pins to bottom whenever the item is ausermessage — if the user scrolled up to read history and then sends a new message, the view still follows them down, matching normal chat UX.resetSessionStateresets the pin ref totrueon session switch, so a freshly loaded session starts pinned like before.Test plan
Existing (must still work)
New
Closes #99
🤖 Generated with Claude Code
Greptile Summary
This PR fixes the chat pane's auto-scroll behaviour during streaming by replacing the unconditional
scrollIntoView({behavior:'smooth'})on everychatItemschange with a pin-to-bottom mechanism: apinnedToBottomRefflag gated by a 96px distance threshold, updated by ahandleChatScrolllistener, and reset totrueonly when the user sends a message or switches sessions.page.tsx): theuseEffectnow bails out immediately whenpinnedToBottomRef.currentisfalse, and usesbehavior: 'auto'unconditionally to avoid the mid-smooth-animation un-pin race that the previous thread identified.addItem):pinnedToBottomRef.currentis set totruebeforesetChatItemsforuser-type items, so the first streaming token always lands visible after a send.resetSessionState): resets the pin ref totrueso a freshly loaded session starts scrolled to the bottom.docs/manual-tests/chat-autoscroll-pinning.md): comprehensive checklist covering the baseline, scrollback, re-pin-by-scroll, send-re-pin, and threshold scenarios.Confidence Score: 5/5
Safe to merge — the change is well-scoped to the chat scroll behaviour and all edge cases (smooth-scroll race, session switch, send re-pin) are correctly handled.
The pin-to-bottom logic is implemented correctly:
behavior:'auto'avoids the mid-animation un-pin race,pinnedToBottomRefis reset in all the right places (user send, session switch), and thehandleChatScrolldistance formula is standard and accurate. The two issues flagged in the previous review round are both resolved in this PR. No other defects found.Files Needing Attention: No files require special attention.
Important Files Changed
chatScrollRef/pinnedToBottomRefrefs,handleChatScrollcallback, guardeduseEffect, and re-pin on user send and session reset. Both previously flagged issues (module-level constant,behavior:'auto'to avoid mid-animation un-pin) are correctly resolved.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["chatItems changes (new token / new item)"] --> B{pinnedToBottomRef.current?} B -- false --> C[bail out - view stays put] B -- true --> D["scrollIntoView behavior:'auto' instant jump to bottomRef"] D --> E[scroll event fires on chatScrollRef] E --> F["handleChatScroll: distanceFromBottom = scrollHeight - scrollTop - clientHeight"] F --> G{"distanceFromBottom <= 96px?"} G -- yes --> H[pinnedToBottomRef = true] G -- no --> I[pinnedToBottomRef = false] J[User scrolls up manually] --> F K["User sends message addItem type='user'"] --> L[pinnedToBottomRef = true then setChatItems] L --> A M[Session switch resetSessionState] --> N[pinnedToBottomRef = true chatItems cleared] N --> AReviews (3): Last reviewed commit: "merge: staging-v0.0.5 into fix/99-autosc..." | Re-trigger Greptile