fix(site/src/pages/AgentsPage): fix scroll-to-bottom pin starvation in agents chat (#23778)

scheduleBottomPin() cancelled any in-flight pin and restarted the
double-RAF chain on every ResizeObserver notification. When content
height changes on consecutive frames (e.g. during streaming where
SmoothText reveals characters each frame and markdown re-rendering
occasionally changes block height), the inner RAF that actually sets
scrollTop is perpetually cancelled before it fires. The scroll falls
behind the growing content.

Two fixes:

1. Make scheduleBottomPin() idempotent: if a pin is already in-flight,
   skip. The inner RAF reads scrollHeight at execution time so it
   always targets the latest bottom. User-interrupt paths (wheel,
   touch) still cancel via cancelPendingPins().

2. Add overscroll-behavior:contain to the scroll container. Prevents
   elastic overscroll from generating extra scroll events that could
   flip autoScrollRef to false.
This commit is contained in:
Mathias Fredriksson
2026-03-30 12:36:42 +00:00
committed by GitHub
parent f2845f6622
commit 378f11d6dc
@@ -792,7 +792,16 @@ const ScrollAnchoredContainer: FC<{
};
const scheduleBottomPin = () => {
cancelPendingPins();
// If a pin is already in-flight, let it complete. The
// inner RAF reads scrollHeight at execution time so it
// always targets the latest bottom. Cancelling and
// rescheduling on every ResizeObserver notification
// starves the pin on Safari, where sticky-element
// repositioning generates extra resize events that
// perpetually restart the double-RAF chain.
if (pinOuterRafId !== null || pinInnerRafId !== null) {
return;
}
pendingWheelPinRef.current = false;
isRestoringScrollRef.current = true;
// Double-RAF lets React's commit phase and the browser's
@@ -1149,7 +1158,7 @@ const ScrollAnchoredContainer: FC<{
<div
ref={scrollContainerRef}
data-testid="scroll-container"
className="flex min-h-0 flex-1 flex-col overflow-y-auto [overflow-anchor:none] [scrollbar-gutter:stable] [scrollbar-width:thin] [scrollbar-color:hsl(var(--surface-quaternary))_transparent]"
className="flex min-h-0 flex-1 flex-col overflow-y-auto [overflow-anchor:none] [overscroll-behavior:contain] [scrollbar-gutter:stable] [scrollbar-width:thin] [scrollbar-color:hsl(var(--surface-quaternary))_transparent]"
>
<div ref={contentRef}>
{hasMoreMessages && (