From b7277e20f1c4e87eef32b5d72b3036a0796f740b Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 31 May 2026 08:10:45 -0700 Subject: [PATCH] perf: move file explorer reveal cleanup to shell ref (#4204) --- .../components/right-sidebar/FileExplorer.tsx | 18 ++++++++++++--- .../right-sidebar/useFileExplorerReveal.ts | 23 ++++++++----------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/renderer/src/components/right-sidebar/FileExplorer.tsx b/src/renderer/src/components/right-sidebar/FileExplorer.tsx index 38e83e2f9a7..67c8eabaf6d 100644 --- a/src/renderer/src/components/right-sidebar/FileExplorer.tsx +++ b/src/renderer/src/components/right-sidebar/FileExplorer.tsx @@ -120,7 +120,7 @@ function FileExplorerInner(): React.JSX.Element { const [bgMenuPoint, setBgMenuPoint] = useState({ x: 0, y: 0 }) const scrollRef = useRef(null) /** Includes Radix scroll viewport + scrollbar (scrollbar is not a child of the viewport). */ - const explorerShellRef = useRef(null) + const explorerShellRef = useRef(null) const flashTimeoutRef = useRef(null) const isMac = useMemo(() => navigator.userAgent.includes('Mac'), []) const isWindows = useMemo(() => navigator.userAgent.includes('Windows'), []) @@ -280,7 +280,7 @@ function FileExplorerInner(): React.JSX.Element { } }) - useFileExplorerReveal({ + const cancelRevealTimers = useFileExplorerReveal({ activeWorktreeId, worktreePath, pendingExplorerReveal, @@ -296,6 +296,18 @@ function FileExplorerInner(): React.JSX.Element { flashTimeoutRef, virtualizer }) + const setExplorerShellRef = useCallback( + (node: HTMLDivElement | null): void => { + explorerShellRef.current = node + if (node !== null) { + return + } + // Why: reveal flash/scroll timers target the explorer shell; clear them + // when that owner detaches instead of keeping a passive unmount Effect. + cancelRevealTimers() + }, + [cancelRevealTimers] + ) useFileExplorerAutoReveal({ activeFileId, @@ -424,7 +436,7 @@ function FileExplorerInner(): React.JSX.Element { return ( <>
void { const revealScrollFrameRef = useRef(null) const revealScrollTimeoutRef = useRef(null) @@ -57,18 +57,13 @@ export function useFileExplorerReveal({ } }, []) - // Why: reveal owns the flash and scroll timers it schedules; keep cleanup on - // hook unmount so no-worktree renders preserve the existing timeout behavior. - useEffect( - () => () => { - cancelRevealScroll() - if (flashTimeoutRef.current !== null) { - window.clearTimeout(flashTimeoutRef.current) - flashTimeoutRef.current = null - } - }, - [cancelRevealScroll, flashTimeoutRef] - ) + const cancelRevealTimers = useCallback((): void => { + cancelRevealScroll() + if (flashTimeoutRef.current !== null) { + window.clearTimeout(flashTimeoutRef.current) + flashTimeoutRef.current = null + } + }, [cancelRevealScroll, flashTimeoutRef]) const pendingRevealAncestorDirs = useMemo(() => { if ( @@ -225,4 +220,6 @@ export function useFileExplorerReveal({ virtualizer, worktreePath ]) + + return cancelRevealTimers }