From 60a72f2b295d7baa204ff1699b0bb4ce8c01f96f Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Mon, 13 Apr 2026 18:36:01 -0700 Subject: [PATCH] fix: re-measure virtualizer items when async card content loads (#605) PR/issue data arrives after worktree cards mount, changing their height. The virtualizer's ResizeObserver can miss the resize during React's batched rendering, leaving stale measurements and overlapping cards. Add a useLayoutEffect that re-measures all cached elements when the PR or issue cache grows, ensuring positions are corrected before paint. --- .../src/components/sidebar/WorktreeList.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/renderer/src/components/sidebar/WorktreeList.tsx b/src/renderer/src/components/sidebar/WorktreeList.tsx index 5a614c825..f3ac586a9 100644 --- a/src/renderer/src/components/sidebar/WorktreeList.tsx +++ b/src/renderer/src/components/sidebar/WorktreeList.tsx @@ -371,6 +371,25 @@ const WorktreeList = React.memo(function WorktreeList() { clearPendingRevealWorktreeId ]) + // ── Async content re-measurement ────────────────────────────── + // PR and issue data arrive asynchronously after cards mount. When a + // card gains an issue/PR row its height changes, and the virtualizer's + // ResizeObserver should reposition subsequent items. In practice the + // observer can miss the resize when it coincides with React's batched + // rendering — leaving stale measurements and overlapping cards. + // + // Why useLayoutEffect: runs after React commits the new DOM (cards now + // include the issue row) but before the browser paints, so the user + // never sees a frame of overlap. + const prCacheLen = useAppStore((s) => Object.keys(s.prCache).length) + const issueCacheLen = useAppStore((s) => Object.keys(s.issueCache).length) + + useLayoutEffect(() => { + virtualizer.elementsCache.forEach((element) => { + virtualizer.measureElement(element) + }) + }, [prCacheLen, issueCacheLen, virtualizer]) + const navigateWorktree = useCallback( (direction: 'up' | 'down') => { const worktreeRows = rows.filter(