fix sidebar virtual row stale measurement (#2101)

This commit is contained in:
Neil 2026-05-16 12:51:30 -07:00 committed by GitHub
parent 85ad942954
commit 803f060e08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 34 additions and 10 deletions

View File

@ -392,8 +392,42 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
const prCacheLen = useAppStore((s) => Object.keys(s.prCache).length)
const issueCacheLen = useAppStore((s) => Object.keys(s.issueCache).length)
const renderRowKeySignature = useMemo(
() => renderRows.map(getRenderRowKey).join('\n'),
[renderRows]
)
const renderRowsRef = useRef(renderRows)
renderRowsRef.current = renderRows
const totalSize = virtualizer.getTotalSize()
const virtualItems = virtualizer.getVirtualItems()
const measureMountedRows = useCallback(() => {
const currentRows = renderRowsRef.current
virtualizer.elementsCache.forEach((element) => {
const idx = parseInt(element.getAttribute('data-index') ?? '', 10)
const row = Number.isNaN(idx) ? undefined : currentRows[idx]
const expectedKey = row ? getRenderRowKey(row) : null
if (
!element.isConnected ||
expectedKey === null ||
element.getAttribute('data-worktree-virtual-row-key') !== expectedKey
) {
return
}
virtualizer.measureElement(element)
})
}, [virtualizer])
useLayoutEffect(() => {
// Why: after delete/collapse, TanStack may briefly retain the removed row's
// cached element. Measuring that disconnected node reports 0px and corrupts
// the next row's slot, so measure only elements whose DOM key still matches
// the row currently rendered at that index.
measureMountedRows()
const frameId = window.requestAnimationFrame(measureMountedRows)
return () => window.cancelAnimationFrame(frameId)
}, [prCacheLen, issueCacheLen, measureMountedRows, renderRowKeySignature])
useVirtualizedScrollAnchor({
anchorRef: scrollAnchorRef,
getRowKey: getRenderRowKey,
@ -404,16 +438,6 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
virtualizer
})
useLayoutEffect(() => {
virtualizer.elementsCache.forEach((element) => {
const idx = parseInt(element.getAttribute('data-index') ?? '', 10)
if (Number.isNaN(idx) || idx >= renderRows.length) {
return
}
virtualizer.measureElement(element)
})
}, [prCacheLen, issueCacheLen, virtualizer, renderRows.length])
const navigateWorktree = useCallback(
(direction: 'up' | 'down') => {
// Why: derive the cycling order from an all-expanded layout, not the