fix: clear scroll-to-citation setTimeouts on panel close/unmount in source-detail-panel

Stagger setTimeout calls (50ms, 150ms, 300ms, 600ms, 1000ms) and the
final state-update timer were never cleared when the panel was closed
or unmounted, causing setState on unmounted component warnings.

Added scrollTimersRef to track all timeout IDs and clear them when
'open' becomes false or on unmount.

Fixes #1092
This commit is contained in:
mac-agent 2026-04-04 07:05:28 -04:00
parent e5a6feb6f6
commit 6f6be19f9d

View file

@ -274,6 +274,9 @@ export function SourceDetailPanel({
[shouldReduceMotion]
);
// Track stagger scroll timers so they can be cleared on close/unmount
const scrollTimersRef = useRef<ReturnType<typeof setTimeout>[]>([]);
// Callback ref for the cited chunk - scrolls when the element mounts
const citedChunkRefCallback = useCallback(
(node: HTMLDivElement | null) => {
@ -312,32 +315,41 @@ export function SourceDetailPanel({
// Each subsequent scroll will correct for any layout shifts
const scrollAttempts = [50, 150, 300, 600, 1000];
// Track all timeout IDs so they can be cleared on close/unmount
scrollAttempts.forEach((delay) => {
setTimeout(() => {
const id = setTimeout(() => {
scrollToCitedChunk();
}, delay);
scrollTimersRef.current.push(id);
});
// After final attempt, mark state as scrolled
setTimeout(
const doneTimer = setTimeout(
() => {
setHasScrolledToCited(true);
setActiveChunkIndex(citedChunkIndex);
},
scrollAttempts[scrollAttempts.length - 1] + 50
);
scrollTimersRef.current.push(doneTimer);
}
},
[open, citedChunkIndex]
);
// Reset scroll state when panel closes
// Clear scroll timers and reset scroll state when panel closes
useEffect(() => {
if (!open) {
scrollTimersRef.current.forEach(clearTimeout);
scrollTimersRef.current = [];
hasScrolledRef.current = false;
setHasScrolledToCited(false);
setActiveChunkIndex(null);
}
return () => {
scrollTimersRef.current.forEach(clearTimeout);
scrollTimersRef.current = [];
};
}, [open]);
// Handle escape key