From eb8e446d7bbab8b0e59ba73b2c84474bf9dba455 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Tue, 12 May 2026 16:14:01 -0700 Subject: [PATCH] fix(studio): break iframe ref update loop causing flickering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes an infinite update loop between handlePreviewIframeRef and NLELayout's onIframeRef effect — the callback identity change triggered the effect which re-called the callback, creating a render loop visible as "Maximum update depth exceeded" in dev mode and intermittent flickering in production. - Guard setPreviewIframe with identity check (skip if same iframe) - Use ref for onIframeRef in NLELayout effect to avoid re-firing on callback identity change Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/studio/src/App.tsx | 1 + packages/studio/src/components/nle/NLELayout.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/studio/src/App.tsx b/packages/studio/src/App.tsx index 1d49671fa..fff17410e 100644 --- a/packages/studio/src/App.tsx +++ b/packages/studio/src/App.tsx @@ -261,6 +261,7 @@ export function StudioApp() { const { syncPreviewTimelineHotkey, syncPreviewHistoryHotkey } = appHotkeys; const handlePreviewIframeRef = useCallback( (iframe: HTMLIFrameElement | null) => { + if (previewIframeRef.current === iframe) return; previewIframeRef.current = iframe; setPreviewIframe(iframe); syncPreviewTimelineHotkey(iframe); diff --git a/packages/studio/src/components/nle/NLELayout.tsx b/packages/studio/src/components/nle/NLELayout.tsx index a452de73c..1c3902474 100644 --- a/packages/studio/src/components/nle/NLELayout.tsx +++ b/packages/studio/src/components/nle/NLELayout.tsx @@ -234,9 +234,11 @@ export const NLELayout = memo(function NLELayout({ const currentLevel = compositionStack[compositionStack.length - 1]; const directUrl = compositionStack.length > 1 ? currentLevel.previewUrl : undefined; + const onIframeRefRef = useRef(onIframeRef); + onIframeRefRef.current = onIframeRef; useEffect(() => { - onIframeRef?.(iframeRef.current); - }, [compositionStack.length, onIframeRef, refreshKey, iframeRef]); + onIframeRefRef.current?.(iframeRef.current); + }, [compositionStack.length, refreshKey, iframeRef]); // Save master seek position before drilling down so we can restore it on back-navigation. // saveSeekPosition() sets pendingSeekRef in useTimelinePlayer which onIframeLoad reads.