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; }); }, []);