Composed-tree shadow DOM capture, sized invisible spacers, track re-anchoring, block fill-width slides, javascript: href sanitization

- Walker serializes the composed (flattened) tree: open shadow roots walk
  as host children with <slot> replaced by its assigned light nodes (or
  fallback), so custom-element content (product cards, web components) is
  captured instead of arriving childless
- In-flow visibility:hidden nodes with a nonzero border box survive the
  prune as sized invisible placeholders - ghost spacers carry load-bearing
  geometry
- When pruning drops leading in-flow children of a horizontally-translated
  track, the baked translateX is re-anchored by their aggregate margin-box
  width (settled carousel tracks return to origin instead of pushing real
  content off-screen); animation-owned transforms unaffected
- Circular-shrink slide guard counts block-level fill children as
  width-deriving regardless of wAuto (block auto-width IS fill), pinning
  library-sized slide widths uniformly
- javascript: hrefs emit as "#" and the link gate normalizes both sides

382 tests pass (18 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-04 02:41:00 -07:00
co-authored by Claude Fable 5
parent 6e4921884c
commit 762855664c
10 changed files with 509 additions and 13 deletions
+130
View File
@@ -182,3 +182,133 @@ describe("neutralizeAnimatedTransforms (Defect C)", () => {
assert.equal(byVp[375].transform, "matrix(1, 0, 0, 1, -100, 0)", "finite animations own a settled end state, not a perpetual phase");
});
});
/** A RawNode with an explicit computed style and bbox (same at every viewport). Unlike `raw()` this
* lets a test author invisible-but-laid-out boxes, transforms, margins, and positions directly. */
function rawS(
tag: string,
computed: Record<string, string>,
bbox: { x: number; y: number; width: number; height: number },
children: RawChild[] = [],
visible = true,
attrs: Record<string, string> = {},
): RawNode {
return { tag, attrs, computed, bbox, visible, children };
}
// FIX 2 — an in-flow `visibility:hidden` box with a nonzero border box is load-bearing geometry: it
// reserves the row height its absolutely-positioned siblings paint into. It is invisible (so the plain
// visibility prune would drop it), but dropping it collapses the column. It must survive as a sized
// placeholder; a genuinely empty display:none-everywhere box must still prune.
describe("IR prune keeps sized invisible (visibility:hidden) spacers (FIX 2)", () => {
it("keeps an in-flow visibility:hidden ghost column with a nonzero bbox", () => {
const ghost = rawS(
"div",
{ display: "flex", position: "static", visibility: "hidden" },
{ x: 0, y: 0, width: 650, height: 723 },
// its children are themselves invisible (they set no visibility:visible) — the box is a spacer
[rawS("img", { display: "block", position: "static", visibility: "hidden" }, { x: 0, y: 0, width: 650, height: 240 }, [], false)],
false,
);
// the real content is an absolutely-positioned sibling layered over the ghost's reserved space
const painted = rawS("img", { display: "block", position: "absolute", visibility: "visible" }, { x: 0, y: 0, width: 650, height: 723 }, [], true);
const column = rawS("div", { display: "block", position: "relative", visibility: "visible" }, { x: 0, y: 0, width: 650, height: 723 }, [ghost, painted], true);
const body = raw("body", {}, [column]);
const root = buildFixtureIR(body);
const kept = findByTag(root, "div");
assert.ok(kept, "the column survives");
// The ghost div (a second nested div) must still be present as a sized placeholder.
const divs: IRNode[] = [];
const collect = (n: IRNode): void => { if (n.tag === "div") divs.push(n); for (const c of n.children) if (!isTextChild(c)) collect(c as IRNode); };
collect(root);
// column + ghost = 2 divs (body is not a div)
assert.equal(divs.length, 2, "the visibility:hidden ghost column is retained, not pruned");
const ghostIR = divs.find((d) => d.computedByVp[1280]?.visibility === "hidden");
assert.ok(ghostIR, "the retained ghost carries visibility:hidden");
assert.ok(ghostIR!.bboxByVp[1280]!.height > 0, "the ghost keeps its load-bearing height");
});
it("still prunes a display:none-everywhere empty box (no resurrection)", () => {
const hidden = rawS("div", { display: "none", position: "static", visibility: "visible" }, { x: 0, y: 0, width: 0, height: 0 }, [], false);
const body = raw("body", {}, [rawS("section", { display: "block", position: "static", visibility: "visible" }, { x: 0, y: 0, width: 640, height: 100 }, [hidden], true)]);
const root = buildFixtureIR(body);
assert.equal(findByTag(root, "div"), null, "a display:none-everywhere box stays pruned");
});
it("does not keep an out-of-flow (absolute) visibility:hidden box as a spacer", () => {
// Absolute boxes take no space in flow, so an invisible absolute box is not load-bearing geometry.
const absHidden = rawS("div", { display: "block", position: "absolute", visibility: "hidden" }, { x: 0, y: 0, width: 300, height: 300 }, [], false);
const body = raw("body", {}, [rawS("section", { display: "block", position: "static", visibility: "visible" }, { x: 0, y: 0, width: 640, height: 100 }, [absHidden], true)]);
const root = buildFixtureIR(body);
assert.equal(findByTag(root, "div"), null, "an out-of-flow invisible box is not kept as a spacer");
});
});
// FIX 3 — a settled loop carousel parks its track with a baked translateX and prepends invisible clone
// slides that occupy exactly [translateX, 0]; the first REAL slide paints at x=0. Pruning drops the
// off-screen clones, but the baked translateX would survive verbatim and push every real slide
// offscreen-left. Re-anchor the track's translateX by the aggregate outer width of the dropped leading
// clones so the first kept slide lands at its captured position.
describe("IR prune re-anchors a translated track after dropping leading clones (FIX 3)", () => {
it("re-anchors translateX toward 0 by the dropped leading clone width", () => {
// Three off-screen leading loop clones, each 200px wide (translateX = -600 = -(3×200)); two reals.
// The clones are OFF-SCREEN (visible:false via the off-screen test), NOT visibility:hidden — so
// they prune and the FIX-2 sized-invisible-spacer carve-out (which requires visibility:hidden) does
// not resurrect them. This mirrors a real settled Splide loop's leading clones.
const clone = (i: number): RawNode =>
rawS("li", { display: "block", position: "static", visibility: "visible" }, { x: -600 + i * 200, y: 0, width: 200, height: 100 }, [], false);
const real = (i: number): RawNode =>
rawS("li", { display: "block", position: "static", visibility: "visible" }, { x: i * 200, y: 0, width: 200, height: 100 }, [{ text: `real${i}` }], true);
const track = rawS(
"ul",
{ display: "flex", position: "relative", visibility: "visible", transform: "matrix(1, 0, 0, 1, -600, 0)" },
{ x: 0, y: 0, width: 1000, height: 100 },
[clone(0), clone(1), clone(2), real(0), real(1)],
true,
);
const body = raw("body", {}, [track]);
const root = buildFixtureIR(body);
const ul = findByTag(root, "ul")!;
// The clones are pruned (invisible everywhere), leaving the two real slides.
const slides = ul.children.filter((c) => !isTextChild(c));
assert.equal(slides.length, 2, "only the real slides survive");
// The baked -600 translate is re-anchored to 0 (600 + 3×200).
assert.equal(ul.computedByVp[1280]!.transform, "matrix(1, 0, 0, 1, 0, 0)", "track translateX re-anchored to origin");
assert.equal(ul.computedByVp[375]!.transform, "matrix(1, 0, 0, 1, 0, 0)", "re-anchored at every viewport");
});
it("leaves a track untouched when no leading children are dropped", () => {
const real = (i: number): RawNode =>
rawS("li", { display: "block", position: "static", visibility: "visible" }, { x: i * 200, y: 0, width: 200, height: 100 }, [{ text: `real${i}` }], true);
const track = rawS(
"ul",
{ display: "flex", position: "relative", visibility: "visible", transform: "matrix(1, 0, 0, 1, -120, 0)" },
{ x: 0, y: 0, width: 400, height: 100 },
[real(0), real(1)],
true,
);
const body = raw("body", {}, [track]);
const root = buildFixtureIR(body);
const ul = findByTag(root, "ul")!;
assert.equal(ul.computedByVp[1280]!.transform, "matrix(1, 0, 0, 1, -120, 0)", "a track with no dropped leading children keeps its offset");
});
it("does not re-anchor when the dropped leading siblings are out of flow", () => {
const absClone = (i: number): RawNode =>
rawS("li", { display: "block", position: "absolute", visibility: "hidden" }, { x: -400 + i * 200, y: 0, width: 200, height: 100 }, [], false);
const real = (i: number): RawNode =>
rawS("li", { display: "block", position: "static", visibility: "visible" }, { x: i * 200, y: 0, width: 200, height: 100 }, [{ text: `real${i}` }], true);
const track = rawS(
"ul",
{ display: "flex", position: "relative", visibility: "visible", transform: "matrix(1, 0, 0, 1, -80, 0)" },
{ x: 0, y: 0, width: 400, height: 100 },
[absClone(0), absClone(1), real(0), real(1)],
true,
);
const body = raw("body", {}, [track]);
const root = buildFixtureIR(body);
const ul = findByTag(root, "ul")!;
assert.equal(ul.computedByVp[1280]!.transform, "matrix(1, 0, 0, 1, -80, 0)", "out-of-flow clones don't advance the track, so no re-anchor");
});
});