From 53b3621437670f12a0c4663dbae0da1252ab9c6b Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Fri, 17 Jul 2026 00:31:03 -0700 Subject: [PATCH] fix(studio): short-circuit slideshow-island detection with a substring check (review N2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit slideshowIslandRegex scanned the full file content on every editingFile change even for the common non-slideshow case. Gate it behind a plain substring check on SLIDESHOW_ISLAND_TYPE first — cheap, and avoids the full-content RegExp pass for files that plainly have no island. Added a test for the still-open behavior this preserves: a malformed island (invalid JSON) still trips the substring check and the regex, so the tab stays discoverable rather than silently disappearing. --- .../studio/src/hooks/useSlideshowTabState.test.ts | 7 +++++++ packages/studio/src/hooks/useSlideshowTabState.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/studio/src/hooks/useSlideshowTabState.test.ts b/packages/studio/src/hooks/useSlideshowTabState.test.ts index 7aed27cc7..be45f448b 100644 --- a/packages/studio/src/hooks/useSlideshowTabState.test.ts +++ b/packages/studio/src/hooks/useSlideshowTabState.test.ts @@ -69,6 +69,13 @@ describe("useSlideshowTabState", () => { harness.unmount(); }); + it("still detects a malformed island — presence-only, not full manifest validation", () => { + const malformed = ``; + const harness = renderHook({ editingFileContent: malformed, rightPanelTab: "design" }); + expect(harness.getState().isSlideshowComposition).toBe(true); + harness.unmount(); + }); + it("bounces rightPanelTab off 'slideshow' to 'renders' on a non-slideshow composition", () => { const harness = renderHook({ editingFileContent: PLAIN_HTML, rightPanelTab: "slideshow" }); expect(harness.setRightPanelTabCalls).toEqual(["renders"]); diff --git a/packages/studio/src/hooks/useSlideshowTabState.ts b/packages/studio/src/hooks/useSlideshowTabState.ts index 5e38d6ba3..df7d49ea6 100644 --- a/packages/studio/src/hooks/useSlideshowTabState.ts +++ b/packages/studio/src/hooks/useSlideshowTabState.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo, type MutableRefObject } from "react"; -import { slideshowIslandRegex } from "@hyperframes/core/slideshow"; +import { SLIDESHOW_ISLAND_TYPE, slideshowIslandRegex } from "@hyperframes/core/slideshow"; import type { SceneInfo } from "../components/panels/SlideshowPanel"; import type { IframeWindow } from "../player/lib/playbackTypes"; import type { RightPanelTab } from "../utils/studioHelpers"; @@ -27,11 +27,13 @@ export function useSlideshowTabState(params: { // Presence-only (not full manifest validation): a malformed island should // still surface the Slideshow tab so the user can see/fix it, rather than - // making the whole panel disappear. - const isSlideshowComposition = useMemo( - () => Boolean(editingFileContent && slideshowIslandRegex("i").test(editingFileContent)), - [editingFileContent], - ); + // making the whole panel disappear. The plain substring check short-circuits + // the regex scan on every non-slideshow file (the common case) without + // paying for a full-content RegExp pass. + const isSlideshowComposition = useMemo(() => { + if (!editingFileContent || !editingFileContent.includes(SLIDESHOW_ISLAND_TYPE)) return false; + return slideshowIslandRegex("i").test(editingFileContent); + }, [editingFileContent]); // Derive scene list from the live clip manifest in the preview iframe. const slideshowScenes = useMemo(() => {