fix: resolve chat scroll anchoring and task-switch scroll race condit… #11385
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
Fixes scroll-anchoring race conditions in the chat view by consolidating scroll tracking into Virtuoso's own callbacks, eliminating stale-closure bugs, and removing direct DOM scroll manipulation from child components.
Related GitHub Issue
Closes: #
Description
isAtBottomstate → refisAtBottomwas React state captured in closures byhandleRowHeightChangeand the input area'sonHeightChange. Because React batches state updates asynchronously, these callbacks frequently read stale values — the scroll position had changed but the closure still held the old boolean. Converting to a ref (isAtBottomRef) ensures every read gets the current value synchronously. This also removesisAtBottomfromhandleRowHeightChange's dependency array, preventing unnecessary callback re-creation that cascaded into Virtuoso row re-renders.Consolidated scroll listeners
A
useEffectattached a rawscrollevent listener to the scroll container DOM element to detect when the user scrolled away from the bottom and clearstickyFollowRef. This duplicated logic already present in Virtuoso'satBottomStateChangecallback, creating a race between two sources of truth for the same state. The raw listener is removed.atBottomStateChangenow handles all three responsibilities: updatingisAtBottomRef, toggling the scroll-to-bottom button, and clearing sticky follow.New-task scroll anchoring
Starting a new task relied solely on Virtuoso's
initialTopMostItemIndexto position the view at the bottom. When message data arrived asynchronously after mount, the view could be left stranded mid-scroll. A new block in thetask?.tseffect now also setsstickyFollowRef = trueand issues arequestAnimationFrame→scrollTo({ top: MAX_SAFE_INTEGER })to cover the async race.Removed CodeBlock outer-scroll manipulation
CodeBlockwas independently tracking the outer Virtuoso scroller's position viadocument.querySelectorand a dedicated scroll listener, then manually settingscrollTopafter syntax highlighting completed. This fought Virtuoso's own scroll management. All of this is removed — outer container scrolling is now fully delegated to Virtuoso'sfollowOutputand ChatView'shandleRowHeightChange.What changed, concretely
isAtBottomatBottomStateChangecallbackinitialTopMostItemIndexonlyscrollTovia rAFscrollTopTest Procedure