perf(ui): start streams at newest

Reverse the message stream scroll layout so the viewport naturally starts at the newest messages and keeps older content virtualized. Use sentinel-based edge chasing to make jump-to-top/bottom land reliably despite VirtualItem mounts.
This commit is contained in:
Shantur Rathore
2026-03-01 12:40:18 +00:00
parent ca2b3c232f
commit 13802537b4
5 changed files with 227 additions and 258 deletions

View File

@@ -2,7 +2,7 @@ import { JSX, Accessor, children as resolveChildren, createEffect, createMemo, c
const sizeCache = new Map<string, number>()
const DEFAULT_MARGIN_PX = 600
const MIN_PLACEHOLDER_HEIGHT = 32
const MIN_PLACEHOLDER_HEIGHT = 100
const VISIBILITY_BUFFER_PX = 48
type ObserverRoot = Element | Document | null
@@ -156,10 +156,12 @@ interface VirtualItemProps {
export default function VirtualItem(props: VirtualItemProps) {
const resolved = resolveChildren(() => props.children)
const cachedHeight = sizeCache.get(props.cacheKey)
// Default to hidden until we can determine visibility.
// This avoids keeping heavy DOM alive when IntersectionObserver
// doesn't fire (common for hidden/zero-sized scroll roots).
const [isIntersecting, setIsIntersecting] = createSignal(false)
const [hasWrapper, setHasWrapper] = createSignal(false)
const [measuredHeight, setMeasuredHeight] = createSignal(cachedHeight ?? 0)
const [hasMeasured, setHasMeasured] = createSignal(cachedHeight !== undefined)
let hasReportedMeasurement = Boolean(cachedHeight && cachedHeight > 0)
@@ -191,6 +193,9 @@ export default function VirtualItem(props: VirtualItemProps) {
const shouldHideContent = createMemo(() => {
if (props.forceVisible?.()) return false
if (!virtualizationEnabled()) return false
// Avoid mounting everything on first paint; wait until the wrapper ref is
// attached so we can run the rect pre-check and/or observer.
if (!hasWrapper()) return true
return !isIntersecting()
})
@@ -200,6 +205,8 @@ export default function VirtualItem(props: VirtualItemProps) {
let resizeObserver: ResizeObserver | undefined
let intersectionCleanup: (() => void) | undefined
let precheckRetryFrame: number | null = null
let precheckRetryCount = 0
function cleanupResizeObserver() {
if (resizeObserver) {
@@ -208,6 +215,13 @@ export default function VirtualItem(props: VirtualItemProps) {
}
}
function cleanupPrecheckRetry() {
if (precheckRetryFrame !== null) {
cancelAnimationFrame(precheckRetryFrame)
precheckRetryFrame = null
}
}
function cleanupIntersectionObserver() {
if (intersectionCleanup) {
intersectionCleanup()
@@ -267,10 +281,20 @@ export default function VirtualItem(props: VirtualItemProps) {
function refreshIntersectionObserver(targetRoot: Element | Document | null) {
cleanupIntersectionObserver()
cleanupPrecheckRetry()
if (!wrapperRef) {
setIsIntersecting(false)
return
}
// If the caller provided an explicit scroll root but it isn't available yet
// (common during pane/session switches), don't fall back to viewport-based
// visibility. Hidden/display:none panes can yield 0x0 wrapper rects which
// would incorrectly mark every item as visible and mount heavy DOM.
if (props.scrollContainer && !targetRoot) {
setIsIntersecting(false)
return
}
if (typeof IntersectionObserver === "undefined") {
setIsIntersecting(true)
return
@@ -299,9 +323,25 @@ export default function VirtualItem(props: VirtualItemProps) {
? (targetRoot as Element).getBoundingClientRect()
: null
const bounds = rootRect ? { top: rootRect.top, bottom: rootRect.bottom } : getViewportRect()
setIsIntersecting(
shouldRenderByRects({ wrapperRect: wrapperRef.getBoundingClientRect(), rootRect: bounds, margin }),
)
const wrapperRect = wrapperRef.getBoundingClientRect()
// During display toggles (e.g. session switches), layout can momentarily
// report 0x0 rects even though the root is renderable. Treat that as
// unknown visibility and defer to the observer (or a retry) instead of
// marking everything visible and mounting heavy DOM.
if (wrapperRect.width === 0 && wrapperRect.height === 0) {
setIsIntersecting(false)
if (precheckRetryCount < 3 && typeof requestAnimationFrame !== "undefined") {
precheckRetryCount += 1
precheckRetryFrame = requestAnimationFrame(() => {
precheckRetryFrame = null
refreshIntersectionObserver(targetRoot)
})
}
return
}
precheckRetryCount = 0
const visibleNow = shouldRenderByRects({ wrapperRect, rootRect: bounds, margin })
setIsIntersecting(visibleNow)
} catch {
// Ignore measurement failures; IntersectionObserver will correct us.
}
@@ -314,6 +354,7 @@ export default function VirtualItem(props: VirtualItemProps) {
function setWrapperRef(element: HTMLDivElement | null) {
wrapperRef = element ?? undefined
setHasWrapper(Boolean(wrapperRef))
const root = props.scrollContainer ? props.scrollContainer() : null
refreshIntersectionObserver(root ?? null)
}
@@ -343,6 +384,7 @@ export default function VirtualItem(props: VirtualItemProps) {
}
})
createEffect(() => {
const key = props.cacheKey
@@ -375,6 +417,7 @@ export default function VirtualItem(props: VirtualItemProps) {
onCleanup(() => {
cleanupResizeObserver()
cleanupIntersectionObserver()
cleanupPrecheckRetry()
flushVisibility()
})