@@ -70,6 +70,11 @@ export interface ChatViewRef {
7070
7171export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
7272
73+ // Viewport buffer constants for memory-efficient scroll behavior
74+ const VIEWPORT_BUFFER_AT_BOTTOM = 10_000 // Larger buffer when at bottom to maintain scroll lock
75+ const VIEWPORT_BUFFER_SCROLLED_UP = 1_000 // Smaller buffer when scrolled up to preserve memory efficiency
76+ const VIEWPORT_BUFFER_TOP = 3_000 // Top buffer for smooth scrolling
77+
7378const isMac = navigator . platform . toUpperCase ( ) . indexOf ( "MAC" ) >= 0
7479
7580const ChatViewComponent : React . ForwardRefRenderFunction < ChatViewRef , ChatViewProps > = (
@@ -174,6 +179,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
174179 const disableAutoScrollRef = useRef ( false )
175180 const [ showScrollToBottom , setShowScrollToBottom ] = useState ( false )
176181 const [ isAtBottom , setIsAtBottom ] = useState ( false )
182+ // Debounced version of isAtBottom to prevent rapid viewport buffer changes
183+ const [ debouncedIsAtBottom , setDebouncedIsAtBottom ] = useState ( false )
177184 const lastTtsRef = useRef < string > ( "" )
178185 const [ wasStreaming , setWasStreaming ] = useState < boolean > ( false )
179186 const [ showCheckpointWarning , setShowCheckpointWarning ] = useState < boolean > ( false )
@@ -1430,6 +1437,16 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14301437
14311438 useEvent ( "wheel" , handleWheel , window , { passive : true } ) // passive improves scrolling performance
14321439
1440+ // Debounce the isAtBottom state to prevent rapid viewport buffer changes
1441+ // This adds hysteresis to avoid performance issues when users quickly scroll between top and bottom
1442+ useEffect ( ( ) => {
1443+ const timer = setTimeout ( ( ) => {
1444+ setDebouncedIsAtBottom ( isAtBottom )
1445+ } , 300 ) // 300ms delay provides good balance between responsiveness and stability
1446+
1447+ return ( ) => clearTimeout ( timer )
1448+ } , [ isAtBottom ] )
1449+
14331450 // Effect to handle showing the checkpoint warning after a delay
14341451 useEffect ( ( ) => {
14351452 // Only show the warning when there's a task but no visible messages yet
@@ -1888,10 +1905,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
18881905 key = { task . ts }
18891906 className = "scrollable grow overflow-y-scroll mb-1"
18901907 increaseViewportBy = { {
1891- top : 3_000 ,
1892- // Use a dynamic bottom value: larger when at bottom to maintain scroll lock,
1893- // smaller when scrolled up to preserve memory efficiency
1894- bottom : isAtBottom ? 10_000 : 1000 ,
1908+ top : VIEWPORT_BUFFER_TOP ,
1909+ // Dynamic bottom buffer based on scroll position:
1910+ // - When at bottom: Use larger buffer to maintain scroll lock behavior
1911+ // - When scrolled up: Use smaller buffer to preserve memory efficiency
1912+ // This balances the memory leak fix from PR #6697 with proper scroll lock functionality
1913+ // Using debounced state to prevent rapid toggling during quick scrolling
1914+ bottom : debouncedIsAtBottom ? VIEWPORT_BUFFER_AT_BOTTOM : VIEWPORT_BUFFER_SCROLLED_UP ,
18951915 } }
18961916 data = { groupedMessages }
18971917 itemContent = { itemContent }
@@ -1904,6 +1924,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
19041924 } }
19051925 atBottomThreshold = { 10 }
19061926 initialTopMostItemIndex = { groupedMessages . length - 1 }
1927+ // followOutput='smooth' ensures smooth scrolling animation when new content arrives,
1928+ // working in conjunction with the dynamic viewport buffer to maintain scroll lock
1929+ // when the user is at the bottom of the chat
19071930 followOutput = "smooth"
19081931 />
19091932 </ div >
0 commit comments