@@ -57,14 +57,35 @@ export function ConversationView({
5757 const items = useMemo ( ( ) => buildConversationItems ( events ) , [ events ] ) ;
5858 const lastTurn = items . filter ( ( i ) : i is Turn => i . type === "turn" ) . pop ( ) ;
5959
60- // Scroll to bottom on initial mount
61- const hasScrolledRef = useRef ( false ) ;
60+ const isNearBottomRef = useRef ( true ) ;
61+ const prevItemsLengthRef = useRef ( 0 ) ;
62+
63+ // Update isNearBottom on scroll
6264 useLayoutEffect ( ( ) => {
63- if ( hasScrolledRef . current ) return ;
6465 const el = scrollRef . current ;
65- if ( el && items . length > 0 ) {
66+ if ( ! el ) return ;
67+
68+ const handleScroll = ( ) => {
69+ const threshold = 100 ;
70+ const distanceFromBottom =
71+ el . scrollHeight - el . scrollTop - el . clientHeight ;
72+ isNearBottomRef . current = distanceFromBottom <= threshold ;
73+ } ;
74+
75+ el . addEventListener ( "scroll" , handleScroll ) ;
76+ return ( ) => el . removeEventListener ( "scroll" , handleScroll ) ;
77+ } , [ ] ) ;
78+
79+ // Scroll to bottom on first render and when new content arrives
80+ useLayoutEffect ( ( ) => {
81+ const el = scrollRef . current ;
82+ if ( ! el ) return ;
83+
84+ const isNewContent = items . length > prevItemsLengthRef . current ;
85+ prevItemsLengthRef . current = items . length ;
86+
87+ if ( isNearBottomRef . current || isNewContent ) {
6688 el . scrollTop = el . scrollHeight ;
67- hasScrolledRef . current = true ;
6889 }
6990 } , [ items ] ) ;
7091
0 commit comments