From da766d899d4ba0005e271d8c6dbb9f978ac424c6 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Tue, 12 May 2026 14:24:31 -0700 Subject: [PATCH] fix(studio): prevent composition switch loop on sub-composition navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Circular state update between activeCompPath and compositionStack caused the preview to flicker when navigating to sub-compositions and scrubbing. The cycle: activeCompPath change → useEffect updates compositionStack → onCompositionChange fires → setActiveCompPath + refreshPreviewDocumentVersion → re-render → effect re-evaluates → repeat. Fixed by guarding both sides: onCompositionChange skips if path unchanged, updateCompositionStack skips notification if top-of-stack ID unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/studio/src/components/StudioPreviewArea.tsx | 8 ++++++-- packages/studio/src/components/nle/NLELayout.tsx | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/studio/src/components/StudioPreviewArea.tsx b/packages/studio/src/components/StudioPreviewArea.tsx index b5a4883f7..74911d067 100644 --- a/packages/studio/src/components/StudioPreviewArea.tsx +++ b/packages/studio/src/components/StudioPreviewArea.tsx @@ -108,8 +108,12 @@ export function StudioPreviewArea({ onCompositionChange={(compPath) => { // Sync activeCompPath when user drills down via timeline double-click // or navigates back via breadcrumb — keeps sidebar + thumbnails in sync. - setActiveCompPath(compPath); - refreshPreviewDocumentVersion(); + // Guard against no-op updates to prevent circular refresh cascades + // between activeCompPath → compositionStack → onCompositionChange. + if (compPath !== activeCompPath) { + setActiveCompPath(compPath); + refreshPreviewDocumentVersion(); + } }} onIframeRef={handlePreviewIframeRef} previewOverlay={ diff --git a/packages/studio/src/components/nle/NLELayout.tsx b/packages/studio/src/components/nle/NLELayout.tsx index 1e5f65714..a452de73c 100644 --- a/packages/studio/src/components/nle/NLELayout.tsx +++ b/packages/studio/src/components/nle/NLELayout.tsx @@ -203,8 +203,11 @@ export const NLELayout = memo(function NLELayout({ const updateCompositionStack: typeof setCompositionStack = useCallback((action) => { setCompositionStack((prev) => { const next = typeof action === "function" ? action(prev) : action; - const id = next[next.length - 1]?.id; - queueMicrotask(() => onCompositionChangeRef.current?.(id === "master" ? null : id)); + const prevId = prev[prev.length - 1]?.id; + const nextId = next[next.length - 1]?.id; + if (prevId !== nextId) { + queueMicrotask(() => onCompositionChangeRef.current?.(nextId === "master" ? null : nextId)); + } return next; }); }, []);