fix(ui): keep stream virtualized and bottom-anchored while loading

This commit is contained in:
Shantur Rathore
2026-03-02 22:47:21 +00:00
parent 4c5acefa07
commit b6bf58ea8f
2 changed files with 16 additions and 11 deletions

View File

@@ -558,7 +558,6 @@ export default function MessageSection(props: MessageSectionProps) {
getKeyFromAnchorId={getMessageIdFromAnchorId} getKeyFromAnchorId={getMessageIdFromAnchorId}
overscanPx={800} overscanPx={800}
scrollSentinelMarginPx={SCROLL_SENTINEL_MARGIN_PX} scrollSentinelMarginPx={SCROLL_SENTINEL_MARGIN_PX}
virtualizationEnabled={() => !props.loading}
suspendMeasurements={() => !isActive()} suspendMeasurements={() => !isActive()}
loading={() => Boolean(props.loading)} loading={() => Boolean(props.loading)}
isActive={isActive} isActive={isActive}

View File

@@ -340,7 +340,6 @@ export default function VirtualFollowList<T>(props: VirtualFollowListProps<T>) {
} }
function handleContentRendered() { function handleContentRendered() {
if (isLoading()) return
scheduleAnchorScroll() scheduleAnchorScroll()
} }
@@ -523,25 +522,32 @@ export default function VirtualFollowList<T>(props: VirtualFollowListProps<T>) {
createEffect(() => { createEffect(() => {
const loading = isLoading() const loading = isLoading()
if (loading) { if (loading) {
// Keep the initial scroll pending while loading so we can
// anchor to the bottom as soon as items appear.
pendingInitialScroll = true pendingInitialScroll = true
return
}
if (!pendingInitialScroll) {
return
} }
if (!pendingInitialScroll) return
const container = scrollElement() const container = scrollElement()
const sentinel = bottomSentinel() const sentinel = bottomSentinel()
if (!container || !sentinel || props.items().length === 0) { if (!container || !sentinel || props.items().length === 0) return
return
// Ensure we're in follow-to-bottom mode for the initial position.
if (anchorLock()) {
clearAnchorLock()
} }
setAutoScroll(true)
pendingInitialScroll = false pendingInitialScroll = false
requestScrollToBottom(true) // Scroll synchronously so the first paint prefers bottom content.
scrollToBottom(true)
}) })
let previousFollowToken: string | number | undefined let previousFollowToken: string | number | undefined
createEffect(() => { createEffect(() => {
const token = props.followToken?.() const token = props.followToken?.()
if (isLoading() || token === undefined) { if (token === undefined) {
previousFollowToken = token previousFollowToken = token
return return
} }
@@ -735,7 +741,7 @@ export default function VirtualFollowList<T>(props: VirtualFollowListProps<T>) {
scrollContainer={scrollElement} scrollContainer={scrollElement}
threshold={overscanPx} threshold={overscanPx}
placeholderClass="message-stream-placeholder" placeholderClass="message-stream-placeholder"
virtualizationEnabled={() => virtualizationEnabled() && !isLoading()} virtualizationEnabled={virtualizationEnabled}
suspendMeasurements={suspendMeasurements} suspendMeasurements={suspendMeasurements}
onHeightChange={(nextHeight, previousHeight) => { onHeightChange={(nextHeight, previousHeight) => {
const delta = nextHeight - previousHeight const delta = nextHeight - previousHeight