From a61f7de8d79255da5a79730959a8ac5614415db9 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Fri, 10 Jul 2026 17:36:09 -0700 Subject: [PATCH] fix(sdk): align template script traversal --- packages/parsers/src/hfIds.ts | 43 +++++++++++++++--- packages/parsers/src/htmlParser.test.ts | 55 ++++++++++++++++++++++++ packages/parsers/src/htmlParser.ts | 28 +++++++----- packages/sdk/src/document.ts | 39 +++++++++++------ packages/sdk/src/engine/model.ts | 36 ++++++---------- packages/sdk/src/session.timings.test.ts | 20 +++++++++ packages/sdk/src/session.ts | 37 ++++++++-------- 7 files changed, 186 insertions(+), 72 deletions(-) diff --git a/packages/parsers/src/hfIds.ts b/packages/parsers/src/hfIds.ts index f2020c61d..0e4329f3f 100644 --- a/packages/parsers/src/hfIds.ts +++ b/packages/parsers/src/hfIds.ts @@ -114,15 +114,49 @@ export function mintHfId(el: Element, assigned: Set): string { * inner id would be duplicated across every clone. Form B is distinguished from * a clone-source by the presence of a direct `[data-composition-id]` child. */ +function getChildElements(parent: Element): Element[] { + const directChildren = Array.from(parent.children); + if (directChildren.length || parent.tagName.toLowerCase() !== "template") return directChildren; + const content = (parent as HTMLTemplateElement).content; + if (content?.children.length) return Array.from(content.children); + return directChildren; +} + export function isCompositionTemplate(el: Element): boolean { if (el.tagName.toLowerCase() !== "template") return false; if (el.getAttribute("data-composition-id") !== null) return true; - for (const child of Array.from(el.children)) { + for (const child of getChildElements(el)) { if (child.getAttribute("data-composition-id") !== null) return true; } return false; } +/** + * Walk document-order descendants, descending through composition templates + * while keeping plain templates inert. linkedom's querySelectorAll does not + * expose template contents, so callers that model the served composition use + * this traversal instead. + */ +export function walkCompositionDescendants( + root: Document | Element, + visit: (el: Element) => void, +): void { + const rootElement: Element | null = + root.nodeType === 9 ? (root as Document).documentElement : (root as Element); + if (!rootElement) return; + + const walk = (parent: Element): void => { + for (const child of getChildElements(parent)) { + const isTemplate = child.tagName.toLowerCase() === "template"; + if (isTemplate && !isCompositionTemplate(child)) continue; + visit(child); + walk(child); + } + }; + + walk(rootElement); +} + /** * Document-order walk of every element under `root`, descending into * composition `