@@ -844,7 +844,6 @@ export default function ChatView({ threadId }: ChatViewProps) {
844844 } | null > ( null ) ;
845845 const pendingInteractionAnchorFrameRef = useRef < number | null > ( null ) ;
846846 const lastContentHeightRef = useRef ( 0 ) ;
847- const lastSmoothScrollTimestampRef = useRef ( 0 ) ;
848847 const composerEditorRef = useRef < ComposerPromptEditorHandle > ( null ) ;
849848 const composerFormRef = useRef < HTMLFormElement > ( null ) ;
850849 const composerFormHeightRef = useRef ( 0 ) ;
@@ -2300,13 +2299,18 @@ export default function ChatView({ threadId }: ChatViewProps) {
23002299
23012300 // Auto-scroll on new messages
23022301 const messageCount = timelineMessages . length ;
2303- const scrollMessagesToBottom = useCallback ( ( behavior : ScrollBehavior = "auto" ) => {
2304- const scrollContainer = messagesScrollRef . current ;
2305- if ( ! scrollContainer ) return ;
2306- scrollContainer . scrollTo ( { top : scrollContainer . scrollHeight , behavior } ) ;
2307- lastKnownScrollTopRef . current = scrollContainer . scrollTop ;
2308- shouldAutoScrollRef . current = true ;
2309- } , [ ] ) ;
2302+ const scrollMessagesToBottom = useCallback (
2303+ ( behavior : ScrollBehavior = "auto" , { enableAutoScroll = false } = { } ) => {
2304+ const scrollContainer = messagesScrollRef . current ;
2305+ if ( ! scrollContainer ) return ;
2306+ scrollContainer . scrollTo ( { top : scrollContainer . scrollHeight , behavior } ) ;
2307+ lastKnownScrollTopRef . current = scrollContainer . scrollTop ;
2308+ if ( enableAutoScroll ) {
2309+ shouldAutoScrollRef . current = true ;
2310+ }
2311+ } ,
2312+ [ ] ,
2313+ ) ;
23102314 const cancelPendingStickToBottom = useCallback ( ( ) => {
23112315 const pendingFrame = pendingAutoScrollFrameRef . current ;
23122316 if ( pendingFrame === null ) return ;
@@ -2323,6 +2327,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
23232327 if ( pendingAutoScrollFrameRef . current !== null ) return ;
23242328 pendingAutoScrollFrameRef . current = window . requestAnimationFrame ( ( ) => {
23252329 pendingAutoScrollFrameRef . current = null ;
2330+ if ( ! shouldAutoScrollRef . current ) return ;
23262331 if ( pendingUserScrollUpIntentRef . current || isPointerScrollActiveRef . current ) return ;
23272332 scrollMessagesToBottom ( ) ;
23282333 } ) ;
@@ -2409,19 +2414,18 @@ export default function ChatView({ threadId }: ChatViewProps) {
24092414 pendingUserScrollUpIntentRef . current = false ;
24102415 } else if ( shouldAutoScrollRef . current && pendingUserScrollUpIntentRef . current ) {
24112416 const scrolledUp = currentScrollTop < lastKnownScrollTopRef . current - 1 ;
2412- if ( scrolledUp && ! isNearBottom ) {
2417+ if ( scrolledUp ) {
24132418 shouldAutoScrollRef . current = false ;
24142419 pendingUserScrollUpIntentRef . current = false ;
24152420 } else if ( ! scrolledUp ) {
24162421 pendingUserScrollUpIntentRef . current = false ;
24172422 }
24182423 } else if ( shouldAutoScrollRef . current && isPointerScrollActiveRef . current ) {
24192424 const scrolledUp = currentScrollTop < lastKnownScrollTopRef . current - 1 ;
2420- if ( scrolledUp && ! isNearBottom ) {
2425+ if ( scrolledUp ) {
24212426 shouldAutoScrollRef . current = false ;
24222427 }
24232428 } else if ( shouldAutoScrollRef . current && ! isNearBottom ) {
2424- // Catch-all for keyboard/assistive scroll interactions.
24252429 const scrolledUp = currentScrollTop < lastKnownScrollTopRef . current - 1 ;
24262430 if ( scrolledUp ) {
24272431 shouldAutoScrollRef . current = false ;
@@ -2434,6 +2438,10 @@ export default function ChatView({ threadId }: ChatViewProps) {
24342438 const onMessagesWheel = useCallback ( ( event : React . WheelEvent < HTMLDivElement > ) => {
24352439 if ( event . deltaY < 0 ) {
24362440 pendingUserScrollUpIntentRef . current = true ;
2441+ const scrollContainer = messagesScrollRef . current ;
2442+ if ( scrollContainer ) {
2443+ scrollContainer . scrollTo ( { top : scrollContainer . scrollTop } ) ;
2444+ }
24372445 }
24382446 } , [ ] ) ;
24392447 const onMessagesPointerDown = useCallback ( ( _event : React . PointerEvent < HTMLDivElement > ) => {
@@ -2586,14 +2594,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
25862594 if ( pendingUserScrollUpIntentRef . current || isPointerScrollActiveRef . current ) return ;
25872595 cancelPendingStickToBottom ( ) ;
25882596 pendingAutoScrollFrameRef . current = null ;
2589- const heightDelta = nextHeight - previousHeight ;
2590- const now = performance . now ( ) ;
2591- const recentlySmoothed = now - lastSmoothScrollTimestampRef . current < 400 ;
2592- const useSmoothScroll = heightDelta > 100 && ! recentlySmoothed ;
2593- if ( useSmoothScroll ) {
2594- lastSmoothScrollTimestampRef . current = now ;
2595- }
2596- scrollMessagesToBottom ( useSmoothScroll ? "smooth" : "auto" ) ;
2597+ scrollMessagesToBottom ( ) ;
25972598 } ) ;
25982599
25992600 observer . observe ( contentElement ) ;
@@ -4750,7 +4751,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
47504751 < div className = "pointer-events-none absolute bottom-1 left-1/2 z-30 flex -translate-x-1/2 justify-center py-1.5" >
47514752 < button
47524753 type = "button"
4753- onClick = { ( ) => scrollMessagesToBottom ( "smooth" ) }
4754+ onClick = { ( ) => scrollMessagesToBottom ( "smooth" , { enableAutoScroll : true } ) }
47544755 className = "pointer-events-auto flex items-center gap-1.5 rounded-full border border-border/60 bg-card px-3 py-1 text-muted-foreground text-xs shadow-sm transition-colors hover:border-border hover:text-foreground hover:cursor-pointer"
47554756 >
47564757 < ChevronDownIcon className = "size-3.5" />
0 commit comments