From 378f11d6dcecc8b27ac66b9117051207f4cf039f Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 30 Mar 2026 15:36:42 +0300 Subject: [PATCH] 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. --- site/src/pages/AgentsPage/AgentChatPageView.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentChatPageView.tsx b/site/src/pages/AgentsPage/AgentChatPageView.tsx index fa22354f01..a739069a93 100644 --- a/site/src/pages/AgentsPage/AgentChatPageView.tsx +++ b/site/src/pages/AgentsPage/AgentChatPageView.tsx @@ -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<{
{hasMoreMessages && (