From 03d14028745a086f080466fd5b96a8c0e2bfd1f8 Mon Sep 17 00:00:00 2001 From: func25 Date: Fri, 19 Jun 2026 15:35:36 +0700 Subject: [PATCH] fix(producer): preserve subcomposition wrappers --- .../compiler/inlineSubCompositions.test.ts | 35 +++++++++++++++++++ .../src/compiler/inlineSubCompositions.ts | 5 +++ .../src/services/htmlCompiler.test.ts | 28 +++++++++------ .../producer/src/services/htmlCompiler.ts | 19 +++------- 4 files changed, 63 insertions(+), 24 deletions(-) diff --git a/packages/core/src/compiler/inlineSubCompositions.test.ts b/packages/core/src/compiler/inlineSubCompositions.test.ts index 452d1e8e8f..b7256db669 100644 --- a/packages/core/src/compiler/inlineSubCompositions.test.ts +++ b/packages/core/src/compiler/inlineSubCompositions.test.ts @@ -37,6 +37,16 @@ function makeHostDocument(compId: string) { return document; } +function makeAnonymousHostDocument() { + const { document } = parseHTML(` + +
+
+
+`); + return document; +} + describe("inlineSubCompositions – #ID selector scoping divergence", () => { it("throws an actionable error when a resolved sub-composition file is empty", () => { const document = makeHostDocument("intro"); @@ -145,6 +155,31 @@ describe("inlineSubCompositions – #ID selector scoping divergence", () => { expect(scopedCss).toContain('[data-hf-authored-id="intro"]'); }); + it("preserves the inferred composition boundary for anonymous hosts", () => { + const document = makeAnonymousHostDocument(); + const host = document.querySelector('[data-composition-src="intro.html"]')!; + + function flattenInnerRoot(innerRoot: Element): Element { + const clone = innerRoot.cloneNode(true) as Element; + clone.removeAttribute("id"); + clone.removeAttribute("data-composition-id"); + clone.setAttribute("data-hf-inner-root", "true"); + return clone; + } + + inlineSubCompositions(document, [host], { + resolveHtml: () => SUB_COMP_HTML, + parseHtml: (html) => parseHTML(html).document, + flattenInnerRoot, + }); + + expect(host.getAttribute("data-composition-id")).toBeNull(); + const inferredRoot = host.querySelector('[data-composition-id="intro"]'); + expect(inferredRoot?.getAttribute("data-hf-inner-root")).toBe("true"); + expect(inferredRoot?.getAttribute("id")).toBeNull(); + expect(inferredRoot?.querySelector(".title")?.textContent).toBe("HELLO WORLD"); + }); + it("extracts elements from sub-composition with original rel and crossorigin", () => { const subCompWithLinks = ` diff --git a/packages/core/src/compiler/inlineSubCompositions.ts b/packages/core/src/compiler/inlineSubCompositions.ts index f6e0b6167e..f04e9baace 100644 --- a/packages/core/src/compiler/inlineSubCompositions.ts +++ b/packages/core/src/compiler/inlineSubCompositions.ts @@ -369,6 +369,11 @@ export function inlineSubCompositions( for (const child of [...innerRoot.querySelectorAll("style, script")]) child.remove(); if (flattenInnerRoot) { const prepared = flattenInnerRoot(innerRoot); + if (!compId && inferredCompId) { + // Anonymous hosts have no outer composition id, so keep the inferred + // boundary on the preserved inner root after flattenInnerRoot strips it. + prepared.setAttribute("data-composition-id", inferredCompId); + } hostEl.innerHTML = prepared.outerHTML || ""; } else { hostEl.innerHTML = compId ? innerRoot.innerHTML || "" : innerRoot.outerHTML || ""; diff --git a/packages/producer/src/services/htmlCompiler.test.ts b/packages/producer/src/services/htmlCompiler.test.ts index f10908a8f4..ec827e4225 100644 --- a/packages/producer/src/services/htmlCompiler.test.ts +++ b/packages/producer/src/services/htmlCompiler.test.ts @@ -596,13 +596,15 @@ describe("template-wrapped sub-composition media offsets", () => { join(compositionsDir, "scene.html"), `