diff --git a/packages/ui/src/components/chat/MessageList.tsx b/packages/ui/src/components/chat/MessageList.tsx index cc3d58fa..755a61fd 100644 --- a/packages/ui/src/components/chat/MessageList.tsx +++ b/packages/ui/src/components/chat/MessageList.tsx @@ -1372,6 +1372,8 @@ const MessageList = React.forwardRef(({ scrollEl.scrollTop += prependedHeight; }); + const [virtualVersion, bumpVirtualVersion] = React.useReducer((v: number) => v + 1, 0); + const historyVirtualizer = useVirtualizer({ count: historyEntries.length, getScrollElement: resolveScrollContainer, @@ -1381,6 +1383,7 @@ const MessageList = React.forwardRef(({ useAnimationFrameWithResizeObserver: true, overscan: MESSAGE_LIST_OVERSCAN, enabled: shouldVirtualizeHistory, + onChange: bumpVirtualVersion, }); React.useLayoutEffect(() => { @@ -1437,7 +1440,7 @@ const MessageList = React.forwardRef(({ const historyVirtualRows = React.useMemo( () => (shouldVirtualizeHistory ? historyVirtualizer.getVirtualItems() : EMPTY_VIRTUAL_ROWS), - [historyVirtualizer, shouldVirtualizeHistory], + [historyVirtualizer, shouldVirtualizeHistory, virtualVersion], ); const allEntries = React.useMemo(() => { diff --git a/packages/ui/src/hooks/useChatAutoFollow.ts b/packages/ui/src/hooks/useChatAutoFollow.ts index 59c281bd..7b37e272 100644 --- a/packages/ui/src/hooks/useChatAutoFollow.ts +++ b/packages/ui/src/hooks/useChatAutoFollow.ts @@ -411,7 +411,9 @@ export const useChatAutoFollow = ({ }, [sessionIsWorking, startFollowLoop]); // Replay a deferred restoreSnapshot once ChatViewport mounts. - React.useEffect(() => { + // useLayoutEffect ensures scroll position is set before the browser paints, + // preventing a visible flash of content at the wrong scroll position. + React.useLayoutEffect(() => { if (!containerEl) return; if (pendingInitialRestoreRef.current && pendingInitialRestoreRef.current === currentSessionId) { void restoreSnapshot();