fix(desktop): scroll chat to the latest reply on session resume (#10501)

Signed-off-by: Seydi Charyyev <seydi.charyev@gmail.com>
This commit is contained in:
Seydi Charyyev
2026-07-16 11:56:49 +05:00
committed by GitHub
parent e7c33077cd
commit 8691970733
+30 -3
View File
@@ -39,6 +39,7 @@ const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
const rootRef = React.useRef<React.ElementRef<typeof ScrollAreaPrimitive.Root>>(null);
const viewportRef = React.useRef<HTMLDivElement>(null);
const viewportEndRef = React.useRef<HTMLDivElement>(null);
const contentRef = React.useRef<HTMLDivElement>(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<ScrollAreaHandle, ScrollAreaProps>(
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<ScrollAreaHandle, ScrollAreaProps>(
if (viewportRef.current && !isActivelyScrollingRef.current) {
viewportRef.current.scrollTo({
top: viewportRef.current.scrollHeight,
behavior: 'smooth',
behavior: 'auto',
});
}
});
@@ -178,6 +182,26 @@ const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
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(() => {
// 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' });
}
});
observer.observe(content);
return () => observer.disconnect();
}, [autoScroll, isFollowing]);
// Add scroll event listener
React.useEffect(() => {
const viewport = viewportRef.current;
@@ -204,7 +228,10 @@ const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
ref={viewportRef}
className="h-full w-full rounded-[inherit] [&>div]:!block"
>
<div className={cn(paddingX ? `px-${paddingX}` : '', paddingY ? `py-${paddingY}` : '')}>
<div
ref={contentRef}
className={cn(paddingX ? `px-${paddingX}` : '', paddingY ? `py-${paddingY}` : '')}
>
{children}
{autoScroll && <div ref={viewportEndRef} style={{ height: '1px' }} />}
</div>