From 6652fae2437d97122e5601346adc5a23173c7ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Tue, 19 May 2026 16:07:24 -0400 Subject: [PATCH] fix(#969): producer path propagates data-hf-authored-id to host element When the producer inlines a sub-composition with compId match, it takes innerRoot.innerHTML which strips the wrapper div and its id attribute. CSS/GSAP selectors rewritten from #ID to [data-hf-authored-id=ID] then match nothing. Fix: after innerHTML injection, copy the inner root's id as data-hf-authored-id on the host element. This makes #ID selectors work identically in both preview (bundler) and render (producer) paths. Closes #969 --- .../compiler/inlineSubCompositions.test.ts | 34 ++++--------------- .../src/compiler/inlineSubCompositions.ts | 6 ++++ 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/packages/core/src/compiler/inlineSubCompositions.test.ts b/packages/core/src/compiler/inlineSubCompositions.test.ts index dd3e084ce..7e91f165e 100644 --- a/packages/core/src/compiler/inlineSubCompositions.test.ts +++ b/packages/core/src/compiler/inlineSubCompositions.test.ts @@ -131,46 +131,24 @@ describe("inlineSubCompositions – #ID selector scoping divergence", () => { expect(scopedCss).toContain('[data-hf-authored-id="intro"]'); }); - /** - * Known divergence: the producer path strips the inner root element via - * innerHTML (line 307 of inlineSubCompositions.ts), losing the id attribute. - * The bundler path preserves it via flattenInnerRoot + data-hf-authored-id. - * - * Both paths rewrite CSS and GSAP selectors from `#intro` to - * `[data-hf-authored-id="intro"]`, but only the bundler path actually adds - * that attribute to an element. In the producer path, no element carries - * `data-hf-authored-id`, so the rewritten selectors match nothing. - * - * Workaround: use [data-composition-id="name"] as scope in sub-compositions - * instead of #name. - * - * Proper fix (follow-up): make the producer path add data-hf-authored-id - * to the host element when the inner root has an id attribute. - */ - // FIXME(#969): flip these assertions once the producer path adds - // data-hf-authored-id to the host element. See PR #965 "Proper fix (follow-up)". - it("documents the divergence: producer path lacks data-hf-authored-id element", () => { + it("producer path propagates data-hf-authored-id to host when inner root has id", () => { const document = makeHostDocument("intro"); const host = document.querySelector('[data-composition-src="intro.html"]')!; - // Producer path: no flattenInnerRoot inlineSubCompositions(document, [host], { resolveHtml: () => SUB_COMP_HTML, parseHtml: (html) => parseHTML(html).document, }); - // After producer inlining, no element inside the host has - // data-hf-authored-id="intro". The rewritten CSS/GSAP selectors - // targeting [data-hf-authored-id="intro"] will match nothing. - const authoredIdElement = host.querySelector('[data-hf-authored-id="intro"]'); - expect(authoredIdElement).toBeNull(); + // The inner root's id="intro" is stripped (innerHTML), but the producer + // now propagates it as data-hf-authored-id on the host element so that + // rewritten #ID selectors ([data-hf-authored-id="intro"]) resolve. + expect(host.getAttribute("data-hf-authored-id")).toBe("intro"); - // The original #intro element is gone — innerHTML stripped it. + // The original #intro element is still gone — innerHTML stripped it. const introById = host.querySelector("#intro"); expect(introById).toBeNull(); - // Only the host itself has data-composition-id="intro", which is the - // correct workaround scope to use. expect(host.getAttribute("data-composition-id")).toBe("intro"); }); }); diff --git a/packages/core/src/compiler/inlineSubCompositions.ts b/packages/core/src/compiler/inlineSubCompositions.ts index 54096cecb..3ff1a437a 100644 --- a/packages/core/src/compiler/inlineSubCompositions.ts +++ b/packages/core/src/compiler/inlineSubCompositions.ts @@ -305,6 +305,12 @@ export function inlineSubCompositions( hostEl.innerHTML = prepared.outerHTML || ""; } else { hostEl.innerHTML = compId ? innerRoot.innerHTML || "" : innerRoot.outerHTML || ""; + // When the producer path strips the inner root (innerHTML), the + // authored id attribute is lost. Propagate it to the host so that + // rewritten #ID selectors ([data-hf-authored-id="X"]) still resolve. + if (compId && authoredRootId) { + hostEl.setAttribute("data-hf-authored-id", authoredRootId); + } } } else { for (const child of [...contentDoc.querySelectorAll("style, script")]) child.remove();