From c7a784fdad2bacbc9be035cc14e00376061112b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 24 Sep 2026 11:23:30 -0400 Subject: [PATCH 1/3] fix(studio-server): preview keeps its base URL when a script mentions a tag --- .../src/helpers/subComposition.ts | 7 ++++++- .../studio-server/src/routes/preview.test.ts | 19 +++++++++++++++++++ packages/studio-server/src/routes/preview.ts | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/studio-server/src/helpers/subComposition.ts b/packages/studio-server/src/helpers/subComposition.ts index d9e90315000..0a5c4937884 100644 --- a/packages/studio-server/src/helpers/subComposition.ts +++ b/packages/studio-server/src/helpers/subComposition.ts @@ -334,7 +334,7 @@ export function buildSubCompositionHtml( } // Inject for relative asset resolution (before other tags) - if (baseHref && !headContent.includes("\n${headContent}`; } @@ -375,3 +375,8 @@ ${rewrittenContent} `; } + +/** True for a real `` tag; the text "|/gi, "")); +} diff --git a/packages/studio-server/src/routes/preview.test.ts b/packages/studio-server/src/routes/preview.test.ts index 6150dbd4fd9..cfff79fbbfe 100644 --- a/packages/studio-server/src/routes/preview.test.ts +++ b/packages/studio-server/src/routes/preview.test.ts @@ -82,6 +82,25 @@ async function getPreviewSignature(projectDir: string): Promise { } describe("registerPreviewRoutes", () => { + it("adds its even when a script mentions one, and keeps an authored ", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerPreviewRoutes(app, createAdapter(projectDir)); + writeFileSync( + join(projectDir, "index.html"), + ``, + ); + const injected = await (await app.request("http://localhost/projects/demo/preview")).text(); + expect(injected).toContain(''); + + writeFileSync( + join(projectDir, "index.html"), + ``, + ); + const authored = await (await app.request("http://localhost/projects/demo/preview")).text(); + expect(authored).not.toContain(''); + }); + it("injects Studio GSAP motion manifest runtime into project preview", async () => { const projectDir = createProjectDir(); writeFileSync( diff --git a/packages/studio-server/src/routes/preview.ts b/packages/studio-server/src/routes/preview.ts index 8baa1666df4..e1d2ee8bc99 100644 --- a/packages/studio-server/src/routes/preview.ts +++ b/packages/studio-server/src/routes/preview.ts @@ -12,7 +12,7 @@ import { isWithinProjectRoot } from "@hyperframes/parsers/asset-resolution"; import type { StudioApiAdapter } from "../types.js"; import { resolveWithinProject } from "../helpers/safePath.js"; import { getMimeType } from "../helpers/mime.js"; -import { buildSubCompositionHtml } from "../helpers/subComposition.js"; +import { buildSubCompositionHtml, hasBaseElement } from "../helpers/subComposition.js"; import { resolveProjectAndSignature, resolveProjectSignature, @@ -383,7 +383,7 @@ export function registerPreviewRoutes(api: Hono, adapter: PreviewApiAdapter): vo // Inject for relative asset resolution const baseHref = `/api/projects/${project.id}/preview/`; - if (!bundled.includes("/i, ``); } From 9672b1a556506e031d194bbcff3570a99455d00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 24 Sep 2026 11:36:15 -0400 Subject: [PATCH 2/3] test(studio-server): pin the sub-composition preview's base check --- .../src/helpers/subComposition.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/studio-server/src/helpers/subComposition.test.ts b/packages/studio-server/src/helpers/subComposition.test.ts index ac82d980828..4ab151e6e26 100644 --- a/packages/studio-server/src/helpers/subComposition.test.ts +++ b/packages/studio-server/src/helpers/subComposition.test.ts @@ -16,6 +16,20 @@ function makeTempProject(files: Record): string { } describe("buildSubCompositionHtml", () => { + it("adds the preview base even when the project head's script mentions a ", () => { + const dir = makeTempProject({ + "index.html": ``, + "compositions/scene.html": `
`, + }); + const html = buildSubCompositionHtml( + dir, + "compositions/scene.html", + "/api/runtime.js", + "/api/projects/demo/preview/", + ); + expect(html).toContain(''); + }); + it("handles full HTML document compositions without nesting in ", () => { const dir = makeTempProject({ "index.html": ` From 3a4354f76e680a51f4b123149b6c2de7ac6e1d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 24 Sep 2026 12:07:01 -0400 Subject: [PATCH 3/3] fix(studio-server): find a with the HTML parser, not a regex --- .../src/helpers/subComposition.test.ts | 18 +++++++++++++++++- .../src/helpers/subComposition.ts | 4 ++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/studio-server/src/helpers/subComposition.test.ts b/packages/studio-server/src/helpers/subComposition.test.ts index 4ab151e6e26..2e08e0798e5 100644 --- a/packages/studio-server/src/helpers/subComposition.test.ts +++ b/packages/studio-server/src/helpers/subComposition.test.ts @@ -3,7 +3,7 @@ import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { buildSubCompositionHtml } from "./subComposition"; +import { buildSubCompositionHtml, hasBaseElement } from "./subComposition"; function makeTempProject(files: Record): string { const dir = mkdtempSync(join(tmpdir(), "hf-subcomp-preview-")); @@ -430,3 +430,19 @@ describe("buildSubCompositionHtml", () => { expect(html).toContain('src="assets/logo.png"'); }); }); + +describe("hasBaseElement", () => { + it("counts a real element and nothing that only mentions one", () => { + expect(hasBaseElement(``)).toBe(true); + expect(hasBaseElement(``)).toBe(true); + for (const html of [ + ``, + ``, + ``, + ``, + ``, + ]) { + expect(hasBaseElement(html)).toBe(false); + } + }); +}); diff --git a/packages/studio-server/src/helpers/subComposition.ts b/packages/studio-server/src/helpers/subComposition.ts index 0a5c4937884..c2eb2ec5c70 100644 --- a/packages/studio-server/src/helpers/subComposition.ts +++ b/packages/studio-server/src/helpers/subComposition.ts @@ -376,7 +376,7 @@ ${rewrittenContent} `; } -/** True for a real `` tag; the text "` element; the text "|/gi, "")); + return parseHTML(html).document.querySelector("base") !== null; }