From 399f49104e24ff716e2ca51f024dd8863d9df61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sun, 4 Oct 2026 14:05:18 +0000 Subject: [PATCH] fix(studio): a first animation in a full-document sub-composition plays (#5008) * fix(studio): a first animation in a full-document sub-composition plays * fix(studio): keep the comment share of the two touched files at their base * fix(studio): a first animation also plays when carries the composition id * fix(studio): a root whose id sits on keeps its first animation in * fix(studio): a composition id on the template tag keeps the first animation inside it --- .../core/src/compiler/htmlDocument.test.ts | 45 +++++++++++++++++++ packages/core/src/compiler/htmlDocument.ts | 29 ++++++++++++ .../studio-server/src/routes/files.test.ts | 35 +++++++++++++-- packages/studio-server/src/routes/files.ts | 12 +++-- 4 files changed, 115 insertions(+), 6 deletions(-) diff --git a/packages/core/src/compiler/htmlDocument.test.ts b/packages/core/src/compiler/htmlDocument.test.ts index 615fd6c66c..3f1138e453 100644 --- a/packages/core/src/compiler/htmlDocument.test.ts +++ b/packages/core/src/compiler/htmlDocument.test.ts @@ -5,6 +5,7 @@ import { injectScriptsIntoHtml, injectTagsAtHeadStart, insertBeforeCloseTag, + hasCompositionOutsideTemplates, isFullHtmlDocument, parseHTMLContent, stripEmbeddedRuntimeScripts, @@ -272,3 +273,47 @@ describe("injectTagsAtHeadStart on long adversarial input", () => { expect(performance.now() - started).toBeLessThan(1500); }); }); + +describe("hasCompositionOutsideTemplates", () => { + it.each([ + ["a root in ", '
', true], + [ + "a root in beside an inline template", + '
', + true, + ], + [ + "a full document whose composition is in its template", + '', + false, + ], + ["a bare template", '', false], + [ + "an id on and on the "); + expect(html.indexOf('window.__timelines["sub"]')).toBeGreaterThan(-1); + expect(html.lastIndexOf("")).toBeLessThan(close); + expect(html.slice(close)).toBe("\n"); + }); + + it("a first animation lands inside the template when carries the composition id", async () => { + const html = await addFirstAnimation( + '\n', + ); + const close = html.indexOf(""); + expect(html.lastIndexOf("")).toBeLessThan(close); + expect(html.slice(close)).toBe("\n"); + }); + + it("a first animation lands inside the template when the "); + expect(html.lastIndexOf("")).toBeLessThan(close); + expect(html.slice(close)).toBe("\n"); + }); + + it("a first animation in a file whose composition is in lands before ", async () => { + const html = await addFirstAnimation( + '
\n', + ); + expect(html.indexOf('window.__timelines["main"]')).toBeGreaterThan(html.indexOf("")); expect(html.lastIndexOf("")).toBeLessThan(html.indexOf("")); }); diff --git a/packages/studio-server/src/routes/files.ts b/packages/studio-server/src/routes/files.ts index afc7f2e03a..03476dc847 100644 --- a/packages/studio-server/src/routes/files.ts +++ b/packages/studio-server/src/routes/files.ts @@ -105,7 +105,10 @@ import { } from "../helpers/compositionInsertion.js"; import { resolveGsapWriter } from "./gsapMutationCapabilities.js"; import { requestSubPath } from "../helpers/requestSubPath.js"; -import { insertBeforeCloseTag } from "@hyperframes/core/compiler/html-document"; +import { + hasCompositionOutsideTemplates, + insertBeforeCloseTag, +} from "@hyperframes/core/compiler/html-document"; // ── Server cutover flag ───────────────────────────────────────────────────── @@ -1457,9 +1460,12 @@ async function prepareGsapMutationScript( `window.__timelines["${compId}"] = tl;`, "", ].join("\n"); + const [first, second] = hasCompositionOutsideTemplates(html) + ? (["body", "template"] as const) + : (["template", "body"] as const); html = - insertBeforeCloseTag(html, "body", `${bootstrap}\n`) ?? - insertBeforeCloseTag(html, "template", `${bootstrap}\n`) ?? + insertBeforeCloseTag(html, first, `${bootstrap}\n`) ?? + insertBeforeCloseTag(html, second, `${bootstrap}\n`) ?? `${html}\n${bootstrap}`; block = extractGsapScriptBlock(html); }