From b6bf58ea8ff4cbe5763c6219d03331aec3c1fdb4 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Mon, 2 Mar 2026 22:47:21 +0000 Subject: [PATCH] fix(ui): keep stream virtualized and bottom-anchored while loading --- .../ui/src/components/message-section.tsx | 1 - .../ui/src/components/virtual-follow-list.tsx | 26 ++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/ui/src/components/message-section.tsx b/packages/ui/src/components/message-section.tsx index af0b2982..c4897842 100644 --- a/packages/ui/src/components/message-section.tsx +++ b/packages/ui/src/components/message-section.tsx @@ -558,7 +558,6 @@ export default function MessageSection(props: MessageSectionProps) { getKeyFromAnchorId={getMessageIdFromAnchorId} overscanPx={800} scrollSentinelMarginPx={SCROLL_SENTINEL_MARGIN_PX} - virtualizationEnabled={() => !props.loading} suspendMeasurements={() => !isActive()} loading={() => Boolean(props.loading)} isActive={isActive} diff --git a/packages/ui/src/components/virtual-follow-list.tsx b/packages/ui/src/components/virtual-follow-list.tsx index 901f841e..25c7cbe9 100644 --- a/packages/ui/src/components/virtual-follow-list.tsx +++ b/packages/ui/src/components/virtual-follow-list.tsx @@ -340,7 +340,6 @@ export default function VirtualFollowList(props: VirtualFollowListProps) { } function handleContentRendered() { - if (isLoading()) return scheduleAnchorScroll() } @@ -523,25 +522,32 @@ export default function VirtualFollowList(props: VirtualFollowListProps) { createEffect(() => { const loading = isLoading() if (loading) { + // Keep the initial scroll pending while loading so we can + // anchor to the bottom as soon as items appear. pendingInitialScroll = true - return - } - if (!pendingInitialScroll) { - return } + + if (!pendingInitialScroll) return + const container = scrollElement() const sentinel = bottomSentinel() - if (!container || !sentinel || props.items().length === 0) { - return + if (!container || !sentinel || props.items().length === 0) return + + // Ensure we're in follow-to-bottom mode for the initial position. + if (anchorLock()) { + clearAnchorLock() } + setAutoScroll(true) + pendingInitialScroll = false - requestScrollToBottom(true) + // Scroll synchronously so the first paint prefers bottom content. + scrollToBottom(true) }) let previousFollowToken: string | number | undefined createEffect(() => { const token = props.followToken?.() - if (isLoading() || token === undefined) { + if (token === undefined) { previousFollowToken = token return } @@ -735,7 +741,7 @@ export default function VirtualFollowList(props: VirtualFollowListProps) { scrollContainer={scrollElement} threshold={overscanPx} placeholderClass="message-stream-placeholder" - virtualizationEnabled={() => virtualizationEnabled() && !isLoading()} + virtualizationEnabled={virtualizationEnabled} suspendMeasurements={suspendMeasurements} onHeightChange={(nextHeight, previousHeight) => { const delta = nextHeight - previousHeight