diff --git a/.fallowrc.jsonc b/.fallowrc.jsonc index 700003a34b..3cef6d7b2e 100644 --- a/.fallowrc.jsonc +++ b/.fallowrc.jsonc @@ -893,7 +893,6 @@ "packages/studio/src/components/sidebar/BlocksTab.tsx", "packages/studio/src/components/sidebar/CompositionsTab.tsx", "packages/studio/src/components/sidebar/LeftSidebar.tsx", - "packages/studio/src/components/storyboard/StoryboardLoaded.tsx", "packages/studio/src/components/StudioRightPanel.tsx", "packages/studio/src/components/StudioToast.tsx", "packages/studio/src/components/ui/Tooltip.tsx", diff --git a/bun.lock b/bun.lock index 52b060cc06..07e9a09775 100644 --- a/bun.lock +++ b/bun.lock @@ -338,9 +338,7 @@ "@phosphor-icons/react": "^2.1.10", "@tanstack/react-virtual": "^3.14.6", "bpm-detective": "^2.0.5", - "dompurify": "^3.2.4", "gsap": "^3.13.0", - "marked": "^14.1.4", "mediabunny": "^1.45.3", }, "devDependencies": { @@ -1195,8 +1193,6 @@ "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], - "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], - "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], @@ -1441,8 +1437,6 @@ "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - "dompurify": ["dompurify@3.4.10", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w=="], - "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], @@ -1755,8 +1749,6 @@ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - "marked": ["marked@14.1.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg=="], - "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], diff --git a/docs/docs.json b/docs/docs.json index c12b0c5372..ea33cd5523 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -203,8 +203,7 @@ { "group": "Start in Studio", "pages": [ - "studio/index", - "studio/storyboard" + "studio/index" ] }, { diff --git a/docs/guides/faceless-explainer.mdx b/docs/guides/faceless-explainer.mdx index 03800be910..198d916f21 100644 --- a/docs/guides/faceless-explainer.mdx +++ b/docs/guides/faceless-explainer.mdx @@ -58,8 +58,7 @@ The visuals share one coherent language and develop with the explanation. A diag - The ending lands the sentence you wanted the viewer to remember. Ask the agent when the teaching sequence or evidence must change. Use -[Storyboard](/studio/storyboard) to inspect the sequence directly, or Studio -for a precise visual, timing, or animation correction. +Studio for a precise visual, timing, or animation correction. Check the final project, render through your preferred surface, and review the file. @@ -68,5 +67,4 @@ for a precise visual, timing, or animation correction. ## Related topics - [Prompt for explainers](/prompting/explainers) -- [Review the storyboard](/studio/storyboard) - [Create a product video from real screens](/guides/product-launch-video) diff --git a/docs/guides/general-video.mdx b/docs/guides/general-video.mdx index 11ebc4c023..cd76459e50 100644 --- a/docs/guides/general-video.mdx +++ b/docs/guides/general-video.mdx @@ -69,5 +69,4 @@ Before export, confirm: ## Related topics - [Choose a focused workflow](/workflows) -- [Review the story before polishing](/studio/storyboard) - [Edit the built project in Studio](/studio) diff --git a/docs/guides/pr-to-video.mdx b/docs/guides/pr-to-video.mdx index 81faff6aef..e6b26082e0 100644 --- a/docs/guides/pr-to-video.mdx +++ b/docs/guides/pr-to-video.mdx @@ -57,8 +57,8 @@ The video should explain the change, not read the pull request description aloud - Tests and refactoring are not presented as product features. - Contributor names and release details are accurate. -Ask the agent when the explanation or evidence must change. Use Storyboard to -inspect the sequence directly, or Studio for a precise timing, code-framing, or +Ask the agent when the explanation or evidence must change. Use +Studio for a precise timing, code-framing, or caption correction. @@ -68,5 +68,4 @@ caption correction. ## Related topics - [Prompt for code changes and pull requests](/prompting/code-and-prs) -- [Review the storyboard](/studio/storyboard) - [Create a custom video from several sources](/guides/general-video) diff --git a/docs/help.mdx b/docs/help.mdx index 64133f52b3..7a5aad2ef3 100644 --- a/docs/help.mdx +++ b/docs/help.mdx @@ -20,8 +20,7 @@ If the command cannot find a project, check that you are inside the project fold ## Preview is wrong or stale A change is missing, playback looks old, or the preview is blank. Preview's Code -editor and visual controls save automatically. Storyboard drafts show a Save -action when they still need one. If you edited the project in another +editor and visual controls save automatically. If you edited the project in another application, save that file first. 1. Wait for the project refresh, then reload Studio once. @@ -40,7 +39,6 @@ npx hyperframes check Selection, canvas controls, timing, or an Inspector field is unavailable. - Open **Layers** and select the element there when clicking the canvas is difficult. -- Confirm you are in **Preview**, not Storyboard. - Scrub to a time where the element is actually visible. - Check whether the visual lives inside a nested composition. - Ask the agent when the change affects source structure rather than one visible element. diff --git a/docs/studio/index.mdx b/docs/studio/index.mdx index c1848a1869..20eb83f30b 100644 --- a/docs/studio/index.mdx +++ b/docs/studio/index.mdx @@ -30,8 +30,6 @@ npx hyperframes preview /> -- **Storyboard** shows the planned sequence, direction, voiceover, status, and - comments. Use it to review the story. - **Preview** shows the built project. The live frame is in the middle, the timeline is below, project tools are on the left, and the Inspector is on the right. diff --git a/docs/studio/storyboard.mdx b/docs/studio/storyboard.mdx deleted file mode 100644 index 870c5f8a33..0000000000 --- a/docs/studio/storyboard.mdx +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: "Review the storyboard" -description: "Review planned frames, direction, voiceover, status, and feedback before or during the build." ---- - -The Storyboard view is for reviewing the story, not fine-tuning pixels. - -Each frame represents a planned moment and can include visual direction, voiceover, status, source details, and comments. - - - Storyboard with the video arc, frame statuses, visual sketches, and comments - - -## Use the frame statuses - -- **Outline** — the frame is still a plan. -- **Built** — a working scene exists. -- **Animated** — motion has been added and the scene is ready for closer review. - -Treat a status as progress information, not proof that the frame is approved. - -## Review one frame - -1. Open Storyboard. -2. Focus the frame you want to inspect. -3. Read its visual direction and voiceover together. -4. Check whether the frame supports the main message. -5. Leave a concrete comment. -6. Choose **Save & copy message**, then paste the message into your agent chat. - -Useful feedback describes the problem and desired outcome: - -```text -This frame repeats the previous claim. Use it to show the product -workflow instead, and keep the voiceover line unchanged. -``` - -## Board view and source view - -Use the board when you want to understand the sequence visually. - -Use the source view when you need to inspect or edit the storyboard text and structured direction more directly. - -The voiceover shown on the board is a guide. When the project has a `SCRIPT.md`, that file is the final source for generated narration. - -## When to move to Preview - -Built and Animated frames can include **Open in Preview**. Use it when the question becomes: - -- Is the timing right? -- Is the text readable? -- Does the animation feel correct? -- Is an element aligned? -- Does the media crop work? - -Those questions need the built composition rather than the plan alone. - -## Related topics - -- [Work on the built project in Studio](/studio) -- [Edit the visible frame](/studio/canvas) -- [Direct an agent more precisely](/prompting/anatomy) diff --git a/docs/studio/troubleshooting.mdx b/docs/studio/troubleshooting.mdx index edf0c7efd6..5715d146c9 100644 --- a/docs/studio/troubleshooting.mdx +++ b/docs/studio/troubleshooting.mdx @@ -26,8 +26,7 @@ Undo, zoom in, place the playhead more precisely, and retry. Check whether snapp ## The preview did not update -- Preview's Code editor and visual controls save automatically. Storyboard - narration and feedback drafts show their own Save action. If the change came +- Preview's Code editor and visual controls save automatically. If the change came from another editor or an agent, confirm that the external file was saved. - Wait for the project refresh. - Check the terminal running `hyperframes preview`. diff --git a/packages/cli/src/commands/preview.test.ts b/packages/cli/src/commands/preview.test.ts index 4d6c7528fd..885cdf0abf 100644 --- a/packages/cli/src/commands/preview.test.ts +++ b/packages/cli/src/commands/preview.test.ts @@ -1,4 +1,4 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import * as clack from "@clack/prompts"; @@ -17,7 +17,6 @@ import { reportPreviewShutdown, studioReadyUrl, studioDeepLink, - studioLandingSearch, studioSummaryUrls, waitForStudioChildClose, } from "./preview.js"; @@ -30,70 +29,25 @@ afterEach(() => { process.exitCode = undefined; }); -function projectWith(storyboard: string | null, frameFiles: string[] = []): string { - const dir = mkdtempSync(join(tmpdir(), "hf-preview-landing-")); +function tempProject(): string { + const dir = mkdtempSync(join(tmpdir(), "hf-preview-")); tempDirs.push(dir); - if (storyboard !== null) writeFileSync(join(dir, "STORYBOARD.md"), storyboard); - for (const file of frameFiles) { - mkdirSync(join(dir, file, ".."), { recursive: true }); - writeFileSync(join(dir, file), "
"); - } return dir; } -const FRAME = (n: number, status: string) => - `## Frame ${n} — F${n}\n- status: ${status}\n- src: compositions/frames/0${n}.html\n\nBeat.\n`; - -describe("studioLandingSearch", () => { - it("returns no search without a storyboard", () => { - expect(studioLandingSearch(projectWith(null))).toBe(""); - }); - - it("lands on the board while sketches are under review (any built frame)", () => { - const dir = projectWith(`${FRAME(1, "built")}${FRAME(2, "outline")}`, [ - "compositions/frames/01.html", - ]); - expect(studioLandingSearch(dir)).toBe("?view=storyboard"); - }); - - it("lands on the board during pure planning (srcs declared, none exist)", () => { - const dir = projectWith(`${FRAME(1, "outline")}${FRAME(2, "outline")}`); - expect(studioLandingSearch(dir)).toBe("?view=storyboard"); - }); - - it("lands on the timeline once frames exist without a built status", () => { - const dir = projectWith(`${FRAME(1, "outline")}`, ["compositions/frames/01.html"]); - expect(studioLandingSearch(dir)).toBe(""); - }); - - it("lands on the timeline for fully animated boards", () => { - const dir = projectWith(`${FRAME(1, "animated")}`, ["compositions/frames/01.html"]); - expect(studioLandingSearch(dir)).toBe(""); - }); -}); - describe("Studio handoff URLs", () => { it("hands off the exact timeline project route", () => { - const dir = projectWith(null); - expect(studioDeepLink("http://127.0.0.1:3002", "demo", dir)).toBe( + expect(studioDeepLink("http://127.0.0.1:3002", "demo")).toBe( "http://127.0.0.1:3002/#project/demo", ); - expect(studioSummaryUrls("demo", "http://127.0.0.1:3002", dir)).toEqual({ + expect(studioSummaryUrls("demo", "http://127.0.0.1:3002")).toEqual({ serverUrl: "http://127.0.0.1:3002", studioUrl: "http://127.0.0.1:3002/#project/demo", }); }); - it("hands off the exact storyboard route while a project is still planning", () => { - const dir = projectWith(FRAME(1, "outline")); - expect(studioDeepLink("http://127.0.0.1:3002", "demo", dir)).toBe( - "http://127.0.0.1:3002/?view=storyboard#project/demo", - ); - }); - it("URL-encodes project names that have hash-route metacharacters", () => { - const dir = projectWith(null); - expect(studioDeepLink("http://127.0.0.1:3002", "Launch #1? 50%", dir)).toBe( + expect(studioDeepLink("http://127.0.0.1:3002", "Launch #1? 50%")).toBe( "http://127.0.0.1:3002/#project/Launch%20%231%3F%2050%25", ); }); @@ -365,7 +319,7 @@ describe("preview lifecycle JSON failures", () => { }); it("wraps managed-start validation failures in one JSON document", async () => { - const dir = projectWith(null); + const dir = tempProject(); writeFileSync(join(dir, "index.html"), ""); const log = vi.spyOn(console, "log").mockImplementation(() => {}); @@ -423,7 +377,7 @@ describe("preview lifecycle JSON failures", () => { describe("foreground preview JSON", () => { it("emits the same ready session contract before remaining attached", () => { - const dir = projectWith(null); + const dir = tempProject(); expect(foregroundPreviewReadyPayload("Launch #1", "http://localhost:4567", dir, 4321)).toEqual({ schemaVersion: 1, operation: "start", diff --git a/packages/cli/src/commands/preview.ts b/packages/cli/src/commands/preview.ts index f1f3858cff..cafafc4752 100644 --- a/packages/cli/src/commands/preview.ts +++ b/packages/cli/src/commands/preview.ts @@ -33,16 +33,7 @@ export const examples: Example[] = [ "hyperframes preview --lint-verbose", ], ]; -import { - existsSync, - lstatSync, - mkdirSync, - readFileSync, - readlinkSync, - symlinkSync, - unlinkSync, -} from "node:fs"; -import { parseStoryboard, STORYBOARD_FILENAME } from "@hyperframes/core/storyboard"; +import { existsSync, lstatSync, mkdirSync, readlinkSync, symlinkSync, unlinkSync } from "node:fs"; import { resolve, dirname, basename, join } from "node:path"; import { fileURLToPath } from "node:url"; import { createRequire } from "node:module"; @@ -361,7 +352,7 @@ export default defineCommand({ ); return; } - printStudioSummary(project.name, previewBaseUrl(status.port), project.dir, { + printStudioSummary(project.name, previewBaseUrl(status.port), { details: [`Background preview running (PID ${status.pid}).`, `Log: ${status.logPath}`], }); return; @@ -547,7 +538,7 @@ export default defineCommand({ ); } else { clack.intro(c.bold("hyperframes preview")); - printStudioSummary(projectName, url, dir, { + printStudioSummary(projectName, url, { details: [ background.type === "reused" ? "Reusing the background server already running for this project." @@ -557,7 +548,7 @@ export default defineCommand({ footer: `Stop with: hyperframes preview ${JSON.stringify(dir)} --stop`, }); } - openStudioBrowser(url, projectName, dir, { + openStudioBrowser(url, projectName, { noOpen, browserPath, userDataDir, @@ -1181,47 +1172,19 @@ function compactSelectionPayload(selection: StudioSelectionSnapshot): CompactSel }; } -// Land the browser on the Storyboard view while the project is still planning -// or sketching — the timeline only becomes the right landing once frames are -// animated (or the storyboard never tracked statuses at all, e.g. beat plans). -export function studioLandingSearch(projectDir: string): string { - const storyboardPath = join(projectDir, STORYBOARD_FILENAME); - if (!existsSync(storyboardPath)) return ""; - let frames; - try { - frames = parseStoryboard(readFileSync(storyboardPath, "utf8")).frames; - } catch { - return ""; - } - // Sketch review in progress — the board is the review surface. - if (frames.some((f) => f.status === "built")) return "?view=storyboard"; - // Pure planning stage: frames declare src paths but none are built yet. - const srcs = frames - .map((f) => f.src) - .filter((s): s is string => typeof s === "string" && s.length > 0); - const planning = - frames.length > 0 && - frames.every((f) => f.status === "outline") && - srcs.length > 0 && - !srcs.some((s) => existsSync(join(projectDir, s))); - return planning ? "?view=storyboard" : ""; -} - -// The full Studio URL to open or hand to the user: status-aware landing view -// plus the project hash route. `url` never carries a trailing slash (both the -// embedded server and the Vite `Local:` match strip it). -export function studioDeepLink(url: string, projectName: string, projectDir: string): string { - return `${url}/${studioLandingSearch(projectDir)}#project/${encodeURIComponent(projectName)}`; +// The full Studio URL to open or hand to the user. `url` never carries a +// trailing slash (both the embedded server and the Vite `Local:` match strip it). +export function studioDeepLink(url: string, projectName: string): string { + return `${url}/#project/${encodeURIComponent(projectName)}`; } export function studioSummaryUrls( projectName: string, serverUrl: string, - projectDir: string, ): { serverUrl: string; studioUrl: string } { return { serverUrl, - studioUrl: studioDeepLink(serverUrl, projectName, projectDir), + studioUrl: studioDeepLink(serverUrl, projectName), }; } @@ -1266,20 +1229,15 @@ function previewLifecycleSession(options: { port: options.port, pid: options.pid, serverUrl, - studioUrl: studioDeepLink(serverUrl, options.projectName, options.projectDir), + studioUrl: studioDeepLink(serverUrl, options.projectName), ready: true, ...(options.logPath ? { logPath: options.logPath } : {}), }; } -function openStudioBrowser( - url: string, - projectName: string, - projectDir: string, - options?: BrowserLaunchOptions, -): void { +function openStudioBrowser(url: string, projectName: string, options?: BrowserLaunchOptions): void { if (options?.noOpen) return; - openBrowser(studioDeepLink(url, projectName, projectDir), { + openBrowser(studioDeepLink(url, projectName), { browserPath: options?.browserPath, userDataDir: options?.userDataDir, remoteDebuggingPort: options?.remoteDebuggingPort, @@ -1290,10 +1248,9 @@ function openStudioBrowser( function printStudioSummary( projectName: string, serverUrl: string, - projectDir: string, opts: { details?: string[]; footer?: string } = {}, ): void { - const urls = studioSummaryUrls(projectName, serverUrl, projectDir); + const urls = studioSummaryUrls(projectName, serverUrl); console.log(); console.log(` ${c.dim("Project")} ${c.accent(projectName)}`); console.log(` ${c.dim("Studio")} ${c.accent(urls.studioUrl)}`); @@ -1406,11 +1363,11 @@ function attachStudioReadyHandler( ); } else { spinner.stop(c.success("Studio running")); - printStudioSummary(projectName, url, projectDir, { + printStudioSummary(projectName, url, { footer: "Press Ctrl+C to stop", }); } - openStudioBrowser(url, projectName, projectDir, options); + openStudioBrowser(url, projectName, options); child.stdout.removeListener("data", handleOutput); child.stderr.removeListener("data", handleOutput); } @@ -1631,11 +1588,11 @@ async function runEmbeddedMode( ); } else { s.stop(c.success("Already running")); - printStudioSummary(pName, url, dir, { + printStudioSummary(pName, url, { details: ["Reusing existing server. Use --force-new to start a fresh instance."], }); } - openStudioBrowser(url, pName, dir, options); + openStudioBrowser(url, pName, options); return; } @@ -1649,7 +1606,7 @@ async function runEmbeddedMode( console.log(` ${c.warn(`Port ${startPort} is in use, using ${result.port} instead`)}`); console.log(); } - printStudioSummary(pName, url, dir, { + printStudioSummary(pName, url, { details: [ "Edit with your AI agent — it has HyperFrames skills installed.", "Changes reload automatically in the studio.", @@ -1657,7 +1614,7 @@ async function runEmbeddedMode( footer: "Press Ctrl+C to stop", }); } - openStudioBrowser(url, pName, dir, options); + openStudioBrowser(url, pName, options); // Block until Ctrl+C. Node would normally exit on SIGINT, but the listening // HTTP server keeps handles open, so the event loop stays alive after the diff --git a/packages/core/src/storyboard/types.ts b/packages/core/src/storyboard/types.ts index 6e783250ef..9e7cf144ae 100644 --- a/packages/core/src/storyboard/types.ts +++ b/packages/core/src/storyboard/types.ts @@ -4,7 +4,7 @@ * A storyboard is the plan for a video before any animation work happens: an * ordered set of frames (key moments) plus their narrative/script. It is * authored as a single canonical markdown file (`STORYBOARD.md`) and parsed - * into this normalized shape for the Studio's storyboard view and for agents. + * into this normalized shape for agents. * * See PRD: "Storyboarding in HyperFrames". The markdown stays canonical; this * is the derived structure the parser produces. diff --git a/packages/studio-server/src/createStudioApi.ts b/packages/studio-server/src/createStudioApi.ts index 5976ced33a..cf5e676097 100644 --- a/packages/studio-server/src/createStudioApi.ts +++ b/packages/studio-server/src/createStudioApi.ts @@ -1,7 +1,6 @@ import { Hono } from "hono"; import type { StudioApiAdapter } from "./types.js"; import { registerProjectRoutes } from "./routes/projects.js"; -import { registerStoryboardRoutes } from "./routes/storyboard.js"; import { registerFileRoutes } from "./routes/files.js"; import { registerPreviewRoutes } from "./routes/preview.js"; import { registerLintRoutes } from "./routes/lint.js"; @@ -24,7 +23,6 @@ export function createStudioApi(adapter: StudioApiAdapter): Hono { const api = new Hono(); registerProjectRoutes(api, adapter); - registerStoryboardRoutes(api, adapter); registerFileRoutes(api, adapter); registerPreviewRoutes(api, adapter); registerLintRoutes(api, adapter); diff --git a/packages/studio-server/src/routes/projects.ts b/packages/studio-server/src/routes/projects.ts index b0ca6ad2a5..a0a3580cb4 100644 --- a/packages/studio-server/src/routes/projects.ts +++ b/packages/studio-server/src/routes/projects.ts @@ -40,9 +40,7 @@ export function registerProjectRoutes(api: Hono, adapter: StudioApiAdapter): voi return c.json(result); }); - // Current content signature for a project — a cheap poll target for clients - // that refresh themselves when files change on disk (the storyboard board - // re-fetches when this differs from the signature its data was loaded with). + // Current content signature for a project: a cheap poll target for clients that refresh when files change on disk. api.get("/projects/:id/signature", async (c) => { const project = await adapter.resolveProject(c.req.param("id")); if (!project) return c.json({ error: "not found" }, 404); diff --git a/packages/studio-server/src/routes/storyboard.test.ts b/packages/studio-server/src/routes/storyboard.test.ts deleted file mode 100644 index a2fd535235..0000000000 --- a/packages/studio-server/src/routes/storyboard.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { Hono } from "hono"; -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { registerStoryboardRoutes } from "./storyboard.js"; -import type { StudioApiAdapter } from "../types.js"; - -const tempDirs: string[] = []; - -afterEach(() => { - for (const dir of tempDirs.splice(0)) { - rmSync(dir, { recursive: true, force: true }); - } -}); - -function makeProject(): string { - const dir = mkdtempSync(join(tmpdir(), "storyboard-route-")); - tempDirs.push(dir); - return dir; -} - -function makeApp(projectDir: string): Hono { - const adapter = { - resolveProject: (id: string) => (id === "p" ? { id: "p", dir: projectDir } : null), - } as unknown as StudioApiAdapter; - const app = new Hono(); - registerStoryboardRoutes(app, adapter); - return app; -} - -/** Request the storyboard for project "p" and return status + parsed JSON body. */ -async function getStoryboard(projectDir: string) { - const res = await makeApp(projectDir).request("/projects/p/storyboard"); - return { status: res.status, body: await res.json() }; -} - -describe("GET /projects/:id/storyboard", () => { - it("returns exists:false with empty frames when STORYBOARD.md is absent", async () => { - const { status, body } = await getStoryboard(makeProject()); - expect(status).toBe(200); - expect(body.exists).toBe(false); - expect(body.frames).toEqual([]); - }); - - it("404s for an unknown project", async () => { - const res = await makeApp(makeProject()).request("/projects/nope/storyboard"); - expect(res.status).toBe(404); - }); - - it("parses the manifest and resolves frame src existence on disk", async () => { - const dir = makeProject(); - mkdirSync(join(dir, "compositions", "frames"), { recursive: true }); - writeFileSync(join(dir, "compositions", "frames", "01-hook.html"), "
"); - writeFileSync( - join(dir, "STORYBOARD.md"), - `--- -message: Hello world ---- - -## Frame 1 — Hook -- status: built -- src: compositions/frames/01-hook.html - -Opening line. - -## Frame 2 — Missing -- status: outline -- src: compositions/frames/02-missing.html - -Not built yet. -`, - ); - - const { status, body } = await getStoryboard(dir); - expect(status).toBe(200); - expect(body.exists).toBe(true); - expect(body.globals.message).toBe("Hello world"); - expect(body.frames).toHaveLength(2); - expect(body.frames[0]).toMatchObject({ title: "Hook", status: "built", srcExists: true }); - expect(body.frames[1]).toMatchObject({ title: "Missing", status: "outline", srcExists: false }); - }); - - it("surfaces the companion SCRIPT.md when present", async () => { - const dir = makeProject(); - writeFileSync(join(dir, "STORYBOARD.md"), "## Frame 1\n\nHi.\n"); - writeFileSync(join(dir, "SCRIPT.md"), "# Script\n\nLine 1.\n"); - const { body } = await getStoryboard(dir); - expect(body.script).toMatchObject({ exists: true, path: "SCRIPT.md" }); - expect(body.script.content).toContain("Line 1."); - }); - - it("reports script.exists=false when there is no SCRIPT.md", async () => { - const dir = makeProject(); - writeFileSync(join(dir, "STORYBOARD.md"), "## Frame 1\n\nHi.\n"); - const { body } = await getStoryboard(dir); - expect(body.script.exists).toBe(false); - }); - - it("does not resolve src paths that escape the project", async () => { - const dir = makeProject(); - writeFileSync( - join(dir, "STORYBOARD.md"), - "## Frame 1\n- src: ../../etc/passwd\n\nEscape attempt.\n", - ); - const { body } = await getStoryboard(dir); - expect(body.frames[0].srcExists).toBe(false); - }); - - it("carries a project signature in both the absent and present branches", async () => { - const dir = makeProject(); - const absent = await getStoryboard(dir); - expect(absent.body.exists).toBe(false); - expect(absent.body.signature).toMatch(/^[0-9a-f]{24}$/); - - writeFileSync(join(dir, "STORYBOARD.md"), "## Frame 1\n\nHi.\n"); - const present = await getStoryboard(dir); - expect(present.body.exists).toBe(true); - expect(present.body.signature).toMatch(/^[0-9a-f]{24}$/); - expect(present.body.signature).not.toBe(absent.body.signature); - }); -}); diff --git a/packages/studio-server/src/routes/storyboard.ts b/packages/studio-server/src/routes/storyboard.ts deleted file mode 100644 index 519463b295..0000000000 --- a/packages/studio-server/src/routes/storyboard.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { existsSync, readFileSync } from "node:fs"; -import type { Hono } from "hono"; -import type { StudioApiAdapter } from "../types.js"; -import { resolveWithinProject } from "../helpers/safePath.js"; -import { resolveProjectAndSignature } from "../helpers/projectSignature.js"; -import { - parseStoryboard, - SCRIPT_FILENAME, - STORYBOARD_FILENAME, - type StoryboardFrame, -} from "@hyperframes/core/storyboard"; - -/** A frame enriched with disk-resolution info the Studio needs to render tiles. */ -interface ResolvedStoryboardFrame extends StoryboardFrame { - /** Whether `src` resolves to an existing file inside the project. */ - srcExists: boolean; -} - -function resolveFrames(projectDir: string, frames: StoryboardFrame[]): ResolvedStoryboardFrame[] { - return frames.map((frame) => { - let srcExists = false; - if (frame.src) { - const abs = resolveWithinProject(projectDir, frame.src); - srcExists = abs ? existsSync(abs) : false; - } - return { ...frame, srcExists }; - }); -} - -/** Read the companion SCRIPT.md narration doc if it exists alongside the storyboard. */ -function readScript(projectDir: string): { exists: boolean; path: string; content: string } { - const abs = resolveWithinProject(projectDir, SCRIPT_FILENAME); - if (abs && existsSync(abs)) { - try { - return { exists: true, path: SCRIPT_FILENAME, content: readFileSync(abs, "utf-8") }; - } catch { - /* fall through to absent */ - } - } - return { exists: false, path: SCRIPT_FILENAME, content: "" }; -} - -export function registerStoryboardRoutes(api: Hono, adapter: StudioApiAdapter): void { - // Parsed storyboard manifest for a project. Markdown (STORYBOARD.md) stays - // canonical on disk; this returns the derived, normalized structure. When the - // file is absent we return `exists: false` with empty frames rather than 404, - // so the Studio can render an opt-in empty state. - api.get("/projects/:id/storyboard", async (c) => { - // The signature lets the board bust poster caches and lets the client tell - // whether this payload is already current (see /projects/:id/signature). - const resolved = await resolveProjectAndSignature(adapter, c.req.param("id")); - if (!resolved) return c.json({ error: "not found" }, 404); - const { project, signature } = resolved; - - const abs = resolveWithinProject(project.dir, STORYBOARD_FILENAME); - if (!abs || !existsSync(abs)) { - return c.json({ - exists: false, - path: STORYBOARD_FILENAME, - globals: { extra: {} }, - frames: [], - warnings: [], - script: readScript(project.dir), - signature, - }); - } - - let source: string; - try { - source = readFileSync(abs, "utf-8"); - } catch { - return c.json({ error: "failed to read storyboard" }, 500); - } - - const manifest = parseStoryboard(source); - return c.json({ - exists: true, - path: STORYBOARD_FILENAME, - globals: manifest.globals, - frames: resolveFrames(project.dir, manifest.frames), - warnings: manifest.warnings, - script: readScript(project.dir), - signature, - }); - }); -} diff --git a/packages/studio-server/src/routes/thumbnail.test.ts b/packages/studio-server/src/routes/thumbnail.test.ts index c45b94491b..cef84b0c2b 100644 --- a/packages/studio-server/src/routes/thumbnail.test.ts +++ b/packages/studio-server/src/routes/thumbnail.test.ts @@ -94,13 +94,9 @@ describe("registerThumbnailRoutes", () => { const previewResponse = await app.request( "http://localhost/projects/demo/thumbnail/index.html?t=1.2&output=preview", ); - const storyboardResponse = await app.request( - "http://localhost/projects/demo/thumbnail/index.html?t=1.2&output=storyboard", - ); expect(sourceResponse.status).toBe(200); expect(previewResponse.status).toBe(200); - expect(storyboardResponse.status).toBe(200); expect(adapter.generateThumbnail).toHaveBeenNthCalledWith( 1, expect.objectContaining({ @@ -113,30 +109,6 @@ describe("registerThumbnailRoutes", () => { 2, expect.objectContaining({ outputWidth: 135, outputHeight: 135 }), ); - expect(adapter.generateThumbnail).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ outputWidth: 1080, outputHeight: 1080 }), - ); - }); - - it("caps storyboard output at a 1080px longest side", async () => { - const adapter = createAdapter(); - const app = new Hono(); - registerThumbnailRoutes(app, adapter); - await writeComposition(adapter, 7680, 4320); - - const response = await app.request( - "http://localhost/projects/demo/thumbnail/index.html?t=1.2&output=storyboard", - ); - - expect(response.status).toBe(200); - expect(adapter.generateThumbnail).toHaveBeenCalledWith( - expect.objectContaining({ - format: "jpeg", - outputWidth: 1080, - outputHeight: 608, - }), - ); }); it("deduplicates concurrent generation and writes one complete cache entry", async () => { diff --git a/packages/studio-server/src/routes/thumbnail.ts b/packages/studio-server/src/routes/thumbnail.ts index ab1a482fe7..fd94338cac 100644 --- a/packages/studio-server/src/routes/thumbnail.ts +++ b/packages/studio-server/src/routes/thumbnail.ts @@ -21,7 +21,6 @@ import { thumbnailGenerationCoordinator } from "./thumbnailGenerationCoordinator const THUMBNAIL_CACHE_VERSION = "v4"; const THUMBNAIL_MAX_OUTPUT_WIDTH = 240; const THUMBNAIL_MAX_OUTPUT_HEIGHT = 135; -const STORYBOARD_MAX_OUTPUT_DIMENSION = 1080; const THUMBNAIL_CACHE_MAX_BYTES = 512 * 1024 * 1024; const THUMBNAIL_CACHE_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000; const prunedCacheDirs = new Set(); @@ -101,13 +100,9 @@ export function registerThumbnailRoutes(api: Hono, adapter: StudioApiAdapter): v // PNG is the legacy source-density capture contract. Callers can opt either // format into the bounded preview contract explicitly. const outputMode = - requestedOutput === "source" + requestedOutput === "source" || (requestedOutput !== "preview" && format === "png") ? "source" - : requestedOutput === "storyboard" - ? "storyboard" - : requestedOutput !== "preview" && format === "png" - ? "source" - : "preview"; + : "preview"; const rawSelectorIndex = Number.parseInt(url.searchParams.get("selectorIndex") || "0", 10); const selectorIndex = Number.isFinite(rawSelectorIndex) && rawSelectorIndex > 0 ? rawSelectorIndex : undefined; @@ -168,13 +163,7 @@ export function registerThumbnailRoutes(api: Hono, adapter: StudioApiAdapter): v const outputScale = outputMode === "source" ? 1 - : outputMode === "storyboard" - ? Math.min( - 1, - STORYBOARD_MAX_OUTPUT_DIMENSION / compW, - STORYBOARD_MAX_OUTPUT_DIMENSION / compH, - ) - : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH); + : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH); const outputWidth = Math.max(1, Math.round(compW * outputScale)); const outputHeight = Math.max(1, Math.round(compH * outputScale)); const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${projectSignatureKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${outputMode}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${outputWidth}x${outputHeight}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`; diff --git a/packages/studio/fixtures/storyboard-sample/README.md b/packages/studio/fixtures/storyboard-sample/README.md deleted file mode 100644 index cf76a5b140..0000000000 --- a/packages/studio/fixtures/storyboard-sample/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# storyboard-sample (Studio fixture) - -A small, self-contained project for dogfooding the Studio **Storyboard** view. -Not a registry catalog item — it lives here purely as test content for the -storyboard UI/UX work. - -It exercises the storyboard contract end to end: - -- `STORYBOARD.md` — structured manifest (frontmatter + 5 frames) in the canonical - format the parser reads. -- `compositions/frames/0{1..4}-*.html` — live HTML frame sub-compositions - (`built` / `animated` statuses) the contact-sheet tiles render. -- Frame 5 (`05-cta.html`) is intentionally **absent** and `status: outline`, so - the grid has an outline placeholder to render. - -Preview the storyboard view: - -```bash -npx hyperframes preview packages/studio/fixtures/storyboard-sample -``` - -Inspect just the parsed manifest the Studio consumes: - -```bash -curl localhost:/api/projects//storyboard | jq -``` diff --git a/packages/studio/fixtures/storyboard-sample/SCRIPT.md b/packages/studio/fixtures/storyboard-sample/SCRIPT.md deleted file mode 100644 index c58be55209..0000000000 --- a/packages/studio/fixtures/storyboard-sample/SCRIPT.md +++ /dev/null @@ -1,52 +0,0 @@ -# SCRIPT — storyboard-sample - -**Voice:** Rachel (ElevenLabs) -**Voice settings:** stability 0.35 · similarity 0.75 · style 0.20 -**Voice direction:** Confident, warm, a little playful. Sounds like a person, never a reader. - ---- - -## Line 1 — Hook (Frame 1) - -**Time:** 0.0 – 3.0s -**Delivery:** Land the promise on the beat. Slight smile. - -``` -Ship a launch video in an afternoon. -``` - -## Line 2 — The problem (Frame 2) - -**Time:** 3.0 – 7.0s -**Delivery:** Wry, a touch tired — the old grind. - -``` -The old way? Prompt, wait twenty minutes, get something that misses. -``` - -## Line 3 — The feature (Frame 3) - -**Time:** 7.0 – 12.0s -**Delivery:** Lift into confidence on "then it animates." - -``` -You lock the story first — then it animates. -``` - -## Line 4 — Proof (Frame 4) - -**Time:** 12.0 – 16.0s -**Delivery:** Punchy, two clean beats. - -``` -Fewer re-renders. More finished videos. -``` - -## Line 5 — CTA (Frame 5) - -**Time:** 16.0 – 19.0s -**Delivery:** Calm, inviting. Let the command land. - -``` -Try it: npx hyperframes. -``` diff --git a/packages/studio/fixtures/storyboard-sample/STORYBOARD.md b/packages/studio/fixtures/storyboard-sample/STORYBOARD.md deleted file mode 100644 index 45f3b79b62..0000000000 --- a/packages/studio/fixtures/storyboard-sample/STORYBOARD.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -format: 1920x1080 -message: "Ship a launch video in an afternoon" -arc: Hook → Problem → Solution → Proof → CTA -audience: indie devs on X -voice: Rachel — 5 lines from SCRIPT.md ---- - -## Frame 1 — Hook - -- scene: Big type punches in on the beat -- duration: 3s -- poster: 2s -- transition_in: cut -- status: animated -- voiceover: "Ship a launch video in an afternoon." -- src: compositions/frames/01-hook.html - -Open cold on the promise. Big type punches in on the beat: "Ship a launch -video in an afternoon." This is the thesis — everything after pays it off. - -## Frame 2 — The problem - -- scene: A 20-minute timer spins on a stack of rejected takes -- duration: 4s -- poster: 2.6s -- transition_in: crossfade -- status: animated -- voiceover: "The old way? Prompt, wait twenty minutes, get something that misses." -- src: compositions/frames/02-problem.html - -The old way: prompt, wait twenty minutes, get something that misses. A timer -spins while a stack of rejected takes piles up. Establish the pain we remove. - -## Frame 3 — The feature in action - -- scene: Frames line up as a contact sheet -- duration: 5s -- poster: 3.3s -- transition_in: crossfade -- status: built -- voiceover: "You lock the story first — then it animates." -- src: compositions/frames/03-feature.html - -Cut to the storyboard view itself. Frames line up as a contact sheet and the -narration says "...you lock the story first, then it animates." Show, don't tell. - -## Frame 4 — Proof - -- scene: A stat counts up to the payoff number -- duration: 4s -- poster: 2.6s -- transition_in: wipe -- status: built -- voiceover: "Fewer re-renders. More finished videos." -- src: compositions/frames/04-proof.html - -A real metric lands: fewer re-renders per finished video. Number counts up -while a caption credits the storyboard-first flow. - -## Frame 5 — Call to action - -- scene: End card with the wordmark and a one-line CTA -- duration: 3s -- transition_in: crossfade -- status: outline -- voiceover: "Try it: npx hyperframes." -- src: compositions/frames/05-cta.html - -End card, still an outline. "Try it: npx hyperframes" with the wordmark. Not -built yet — this frame should render as an outline placeholder in the grid. diff --git a/packages/studio/fixtures/storyboard-sample/compositions/frames/01-hook.html b/packages/studio/fixtures/storyboard-sample/compositions/frames/01-hook.html deleted file mode 100644 index c67e1bda12..0000000000 --- a/packages/studio/fixtures/storyboard-sample/compositions/frames/01-hook.html +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/packages/studio/fixtures/storyboard-sample/compositions/frames/02-problem.html b/packages/studio/fixtures/storyboard-sample/compositions/frames/02-problem.html deleted file mode 100644 index af5dc65a36..0000000000 --- a/packages/studio/fixtures/storyboard-sample/compositions/frames/02-problem.html +++ /dev/null @@ -1,62 +0,0 @@ - diff --git a/packages/studio/fixtures/storyboard-sample/compositions/frames/03-feature.html b/packages/studio/fixtures/storyboard-sample/compositions/frames/03-feature.html deleted file mode 100644 index 8aae2f51b8..0000000000 --- a/packages/studio/fixtures/storyboard-sample/compositions/frames/03-feature.html +++ /dev/null @@ -1,84 +0,0 @@ - diff --git a/packages/studio/fixtures/storyboard-sample/compositions/frames/04-proof.html b/packages/studio/fixtures/storyboard-sample/compositions/frames/04-proof.html deleted file mode 100644 index d02ad55a12..0000000000 --- a/packages/studio/fixtures/storyboard-sample/compositions/frames/04-proof.html +++ /dev/null @@ -1,68 +0,0 @@ - diff --git a/packages/studio/fixtures/storyboard-sample/index.html b/packages/studio/fixtures/storyboard-sample/index.html deleted file mode 100644 index 3d23dc6d60..0000000000 --- a/packages/studio/fixtures/storyboard-sample/index.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - Storyboard Demo - - - - -
-
-
-
-
-
- - - - diff --git a/packages/studio/package.json b/packages/studio/package.json index 6dc522f050..ea98f3b954 100644 --- a/packages/studio/package.json +++ b/packages/studio/package.json @@ -76,9 +76,7 @@ "@phosphor-icons/react": "^2.1.10", "@tanstack/react-virtual": "^3.14.6", "bpm-detective": "^2.0.5", - "dompurify": "^3.2.4", "gsap": "^3.13.0", - "marked": "^14.1.4", "mediabunny": "^1.45.3" }, "devDependencies": { diff --git a/packages/studio/src/App.tsx b/packages/studio/src/App.tsx index cd7ac40bbc..1877673daf 100644 --- a/packages/studio/src/App.tsx +++ b/packages/studio/src/App.tsx @@ -53,8 +53,6 @@ import { StudioRightPanel } from "./components/StudioRightPanel"; import { TimelineToolbar } from "./components/TimelineToolbar"; import { StudioPlaybackProvider, StudioShellProvider } from "./contexts/StudioContext"; import { PanelLayoutProvider } from "./contexts/PanelLayoutContext"; -import { ViewModeProvider, useViewModeState } from "./contexts/ViewModeContext"; -import { StoryboardView } from "./components/storyboard/StoryboardView"; import { FileManagerProvider } from "./contexts/FileManagerContext"; import { DomEditProvider } from "./contexts/DomEditContext"; import { StudioSplash } from "./components/StudioSplash"; @@ -68,7 +66,6 @@ const getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementI export function StudioApp() { const { projectId, resolving, waitingForServer } = useServerConnection(); const initialUrlStateRef = useRef(readStudioUrlStateFromWindow()); - const viewModeValue = useViewModeState(); useStudioSessionStart(projectId, resolving, waitingForServer); const [compIdToSrc, setCompIdToSrc] = useState>(new Map()); const [previewIframe, setPreviewIframe] = useState(null); @@ -441,144 +438,135 @@ export function StudioApp() { return ( - - - - -
- { - void (async () => { - await previewPersistence.waitForPendingDomEditSaves(); - await renderQueue.startRender(undefined); - })(); - }} + + + +
+ { + void (async () => { + await previewPersistence.waitForPendingDomEditSaves(); + await renderQueue.startRender(undefined); + })(); + }} + /> + {previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && ( + - {previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && ( - - )} - - {viewModeValue.viewMode === "storyboard" && ( - + - )} -
-
-
-
- + ) + } + timelineToolbar={timelineToolbar} + renderClipContent={renderClipContent} + handleTimelineElementDelete={timelineEditing.handleTimelineElementDelete} + handleTimelineAssetDrop={timelineEditing.handleTimelineAssetDrop} + handleTimelineBlockDrop={handleTimelineBlockDrop} + handleTimelineCompositionDrop={timelineEditing.handleTimelineCompositionDrop} + handlePreviewBlockDrop={handlePreviewBlockDrop} + handleTimelineFileDrop={timelineEditing.handleTimelineFileDrop} + handleTimelineElementMove={timelineEditing.handleTimelineElementMove} + handleTimelineElementsMove={handleTimelineElementsMove} + handleTimelineElementResize={timelineEditing.handleTimelineElementResize} + handleTimelineGroupResize={timelineEditing.handleTimelineGroupResize} + handleToggleTrackHidden={timelineEditing.handleToggleTrackHidden} + setAudioGroupAttribute={timelineEditing.setAudioGroupAttribute} + handleGroupClips={timelineEditing.handleAutoGroupCarveSources} + setElementFxAttribute={timelineEditing.setElementFxAttribute} + handleBlockedTimelineEdit={timelineEditing.handleBlockedTimelineEdit} + handleTimelineElementSplit={timelineEditing.handleTimelineElementSplit} + handleRazorSplit={timelineEditing.handleRazorSplit} + handleRazorSplitAll={timelineEditing.handleRazorSplitAll} + onCopyClip={handleCopy} + onPasteClip={handlePaste} + onDuplicateClip={handleDuplicate} + canPasteClip={canPaste} + setCompIdToSrc={setCompIdToSrc} + setCompositionLoading={setCompositionLoading} + shouldShowMotionPath={shouldShowMotionPath} + shouldShowSelectedDomBounds={shouldShowSelectedDomBounds} + isGestureRecording={gestureState === "recording"} + recordingState={gestureState} + onToggleRecording={handleToggleRecording} + blockPreview={blockPreview} + gestureOverlay={ + gestureState === "recording" && previewIframe ? ( + + ) : undefined + } + /> + setConsoleErrors(null)} + domEditSession={domEditSession} + activeCompPath={activeCompPath} + toasts={toasts} + dismissToast={dismissToast} + /> +
+
+
+
); diff --git a/packages/studio/src/captions/components/CaptionOverlayUtils.ts b/packages/studio/src/captions/components/CaptionOverlayUtils.ts index c4d5bb9f5e..214b351ac0 100644 --- a/packages/studio/src/captions/components/CaptionOverlayUtils.ts +++ b/packages/studio/src/captions/components/CaptionOverlayUtils.ts @@ -221,9 +221,8 @@ export function registerCaptionIframe(ref: React.RefObject