From 126fa800ff2bda8200dddafbecf6c7f46e220cd6 Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Thu, 16 Jul 2026 09:51:59 +0500 Subject: [PATCH 1/2] fix(desktop): scroll chat to the latest reply on session resume A smooth programmatic scrollToBottom emitted intermediate scroll events that handleScroll mistook for a manual scroll-up, disabling auto-follow before late-loading content (images, syntax highlighting) finished growing, leaving the conversation parked at an earlier point on resume. Use an instant scroll for programmatic auto-scroll (no intermediate events to misread) and add a ResizeObserver that re-pins to the bottom while following, covering async height growth that doesn't trigger a React re-render. Closes #10483 Signed-off-by: Seydi Charyyev --- ui/desktop/src/components/ui/scroll-area.tsx | 31 ++++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/ui/desktop/src/components/ui/scroll-area.tsx b/ui/desktop/src/components/ui/scroll-area.tsx index 75900fb302a4..0fc5b86041f8 100644 --- a/ui/desktop/src/components/ui/scroll-area.tsx +++ b/ui/desktop/src/components/ui/scroll-area.tsx @@ -39,6 +39,7 @@ const ScrollArea = React.forwardRef( const rootRef = React.useRef>(null); const viewportRef = React.useRef(null); const viewportEndRef = React.useRef(null); + const contentRef = React.useRef(null); const [isFollowing, setIsFollowing] = React.useState(true); const [isScrolled, setIsScrolled] = React.useState(false); const userScrolledUpRef = React.useRef(false); @@ -60,9 +61,12 @@ const ScrollArea = React.forwardRef( const scrollToBottom = React.useCallback(() => { if (viewportRef.current) { + // Jump instantly rather than animating: a smooth programmatic scroll + // emits intermediate scroll events that handleScroll mistakes for a + // manual scroll-up, which disables auto-follow mid-animation. viewportRef.current.scrollTo({ top: viewportRef.current.scrollHeight, - behavior: 'smooth', + behavior: 'auto', }); // When explicitly scrolling to bottom, reset the following state setIsFollowing(true); @@ -169,7 +173,7 @@ const ScrollArea = React.forwardRef( if (viewportRef.current && !isActivelyScrollingRef.current) { viewportRef.current.scrollTo({ top: viewportRef.current.scrollHeight, - behavior: 'smooth', + behavior: 'auto', }); } }); @@ -178,6 +182,24 @@ const ScrollArea = React.forwardRef( lastScrollHeightRef.current = currentScrollHeight; }, [children, autoScroll, isFollowing]); + // Keep pinned to the bottom when content grows from async media (images, + // syntax highlighting) that resizes after paint without a React re-render, + // so it isn't covered by the [children] effect above. + React.useEffect(() => { + if (!autoScroll) return; + const viewport = viewportRef.current; + const content = contentRef.current; + if (!viewport || !content || typeof ResizeObserver === 'undefined') return; + + const observer = new ResizeObserver(() => { + if (isFollowing && !userScrolledUpRef.current) { + viewport.scrollTo({ top: viewport.scrollHeight, behavior: 'auto' }); + } + }); + observer.observe(content); + return () => observer.disconnect(); + }, [autoScroll, isFollowing]); + // Add scroll event listener React.useEffect(() => { const viewport = viewportRef.current; @@ -204,7 +226,10 @@ const ScrollArea = React.forwardRef( ref={viewportRef} className="h-full w-full rounded-[inherit] [&>div]:!block" > -
+
{children} {autoScroll &&
}
From 7b7ac7763c5c6194f6db7a47db04ca38b8da8948 Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Thu, 16 Jul 2026 10:07:01 +0500 Subject: [PATCH 2/2] fix(desktop): don't fight active scroll-up during resize re-pin Guard the ResizeObserver re-pin with isActivelyScrolling, matching the [children] auto-scroll effect, so streamed or late content growth doesn't yank the viewport to the bottom while the user is scrolling up within the bottom threshold. Signed-off-by: Seydi Charyyev --- ui/desktop/src/components/ui/scroll-area.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/desktop/src/components/ui/scroll-area.tsx b/ui/desktop/src/components/ui/scroll-area.tsx index 0fc5b86041f8..40d00f62f7c0 100644 --- a/ui/desktop/src/components/ui/scroll-area.tsx +++ b/ui/desktop/src/components/ui/scroll-area.tsx @@ -192,7 +192,9 @@ const ScrollArea = React.forwardRef( if (!viewport || !content || typeof ResizeObserver === 'undefined') return; const observer = new ResizeObserver(() => { - if (isFollowing && !userScrolledUpRef.current) { + // Mirror the [children] effect's guards, including isActivelyScrolling, so + // the re-pin doesn't fight a user scrolling up while content is still growing. + if (isFollowing && !userScrolledUpRef.current && !isActivelyScrollingRef.current) { viewport.scrollTo({ top: viewport.scrollHeight, behavior: 'auto' }); } });