Band coverage for never-visible occupying items, page-height trust guard, font materialization integrity

- Items never visible at the canonical viewport (carousel slides off-screen
  at every captured width) now fall through to per-viewport geometry bands
  when they occupy layout, instead of freezing canonical geometry
- The explicit-height probe trust excludes body/html/main and any wrapper
  whose height matches the page scrollHeight - page-height nodes keep
  flowing, and the layout height gate stays meaningful
- @font-face and style-rule url() harvesting resolves against the OWNING
  stylesheet's href (walking @import nesting), not document.baseURI;
  font bytes are magic-byte validated at the storage choke point (HTML
  bodies from catch-all 200 routes are rejected); font-face dedup prefers
  the variant whose source actually resolved, deterministically

415 tests pass (16 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-04 04:29:50 -07:00
co-authored by Claude Fable 5
parent 1d68f16f96
commit 994d2bc21e
7 changed files with 491 additions and 28 deletions
+61
View File
@@ -269,6 +269,31 @@ describe("generateCss hidden-node banded geometry", () => {
assert.ok(mobile.includes("visibility:hidden"), `child should carry the hide, got: ${mobile}`);
assert.ok(!mobile.includes("left:10px"), `ancestor-hidden child must not emit geometry, got: ${mobile}`);
});
it("emits per-band geometry for an occupying slide off-viewport at EVERY width, base included", () => {
// A carousel slide 612: a shrink-0 flex item off-screen RIGHT at every captured width — 375,
// the 1280 canonical base, AND 1920 — so it is never ownHidden, never ancestor-hidden, and never
// visibleByVp[base]=true (the walker marks `bbox.x >= vpW` invisible). It is NOT suppressed
// (no opacity:0 / visibility:hidden) and stays IN FLOW, so it still sets its flex track's
// cross-size. Without a band the base's canonical desktop width freezes across every viewport and
// inflates the mobile track. The band must carry THIS width's measured geometry.
const slide = nodeAt("n2", "div", {
375: { cs: { display: "block", position: "static", opacity: "1", width: "220px" }, bbox: { x: 400, y: 0, width: 220, height: 432 }, visible: false },
1280: { cs: { display: "block", position: "static", opacity: "1", width: "285px" }, bbox: { x: 1500, y: 0, width: 285, height: 532 }, visible: false },
1920: { cs: { display: "block", position: "static", opacity: "1", width: "285px" }, bbox: { x: 2200, y: 0, width: 285, height: 532 }, visible: false },
});
const track = nodeAt("n1", "div", {
375: { cs: { display: "flex", position: "relative" }, bbox: { x: 0, y: 0, width: 375, height: 432 } },
1280: { cs: { display: "flex", position: "relative" }, bbox: { x: 0, y: 0, width: 1280, height: 532 } },
1920: { cs: { display: "flex", position: "relative" }, bbox: { x: 0, y: 0, width: 1920, height: 532 } },
}, [slide]);
const root = nodeAt("n0", "body", {}, [track]);
const css = generateCss(ir3(root), new Map());
assert.ok(baseRule(css, "n2").includes("width:285px"), `base bakes canonical width, got: ${baseRule(css, "n2")}`);
const mobile = bandRule(css, /max-width/, "n2");
assert.ok(mobile.includes("width:220px"), `mobile band must carry the measured 220px width, got: ${mobile}`);
assert.ok(!mobile.includes("display:none"), "an occupying off-viewport slide must stay in layout");
});
});
// Fix 1 — mobile nav chip-strip overlap. A horizontally-scrollable flex strip (overflow-x:auto
@@ -924,6 +949,42 @@ describe("generateCss authored-height kept via own probe (T3)", () => {
});
});
// R2 — the own-probe height trust (T3) must NEVER bake a document-height wrapper. A body/html/main
// landmark, or any node whose captured box IS the page scroll height, probes hAuto:false/hFill:false
// as document-level re-measure noise (min-h-screen + %-height chain), not an authored height. Baking
// it pins the page to a fixed height — which, under an overflow:hidden main, hard-clips content drift
// and blinds the layout gate's heightDelta. Such wrappers must keep flowing (auto height).
describe("generateCss own-probe height trust never bakes a page-height wrapper (R2)", () => {
const explicit = (): RawSizing => ({ wAuto: false, wFill: false, hAuto: false, hFill: false });
// xIr sets perViewport.scrollHeight = 800 at every viewport.
function pageWrapper(tag: string, h: number) {
// A non-leaf wrapper with an in-flow child, whose own probe reads authored (hAuto/hFill false).
const child = xNode("n2", "div", Object.fromEntries(XVPS.map((vp) => [vp, { cs: { display: "block", position: "static" }, bbox: { x: 0, y: 0, width: vp, height: h } }])));
const wrap = xNode("n1", tag, Object.fromEntries(XVPS.map((vp) => [vp, { cs: { display: "block", position: "static", height: `${h}px` }, bbox: { x: 0, y: 0, width: vp, height: h }, sizing: explicit() }])), [child]);
return xNode("n0", "body", Object.fromEntries(XVPS.map((vp) => [vp, { bbox: { x: 0, y: 0, width: vp, height: h } }])), [wrap]);
}
it("does NOT bake a <main> landmark height even when its own probe is authored", () => {
const css = generateCss(xIr(pageWrapper("main", 800)), new Map());
const all = allRulesX(css, "n1");
assert.ok(!/height:800px/.test(all), `main landmark must keep flowing, got: ${all}`);
});
it("does NOT bake a wrapper whose captured height IS the page scroll height (±2%)", () => {
// 792 is within 2% of the 800px scrollHeight — a page-height wrapper, not authored content.
const css = generateCss(xIr(pageWrapper("div", 792)), new Map());
const all = allRulesX(css, "n1");
assert.ok(!/height:792px/.test(all), `near-page-height wrapper must keep flowing, got: ${all}`);
});
it("still bakes a genuinely authored height well below the page height", () => {
// 300px is far from the 800px scrollHeight — a real authored box (the T3 case) still survives.
const css = generateCss(xIr(pageWrapper("div", 300)), new Map());
const all = allRulesX(css, "n1");
assert.ok(/height:300px/.test(all), `authored sub-page height must still survive, got: ${all}`);
});
});
// C1 (css side) — a computed `grid-template-rows: subgrid` is a STRUCTURAL keyword, not a baked px
// regime. It must NEVER be dropped by the reflow-mode dropGridRows path (which strips px row tracks).
describe("collectNodeRules never drops a subgrid rows template (C1)", () => {
+152
View File
@@ -0,0 +1,152 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { looksLikeFontFile } from "../src/capture/capture.js";
import { buildFontGraph } from "../src/infer/fonts.js";
import type { FontFace } from "../src/capture/walker.js";
import type { AssetGraph, AssetEntry } from "../src/infer/assets.js";
// --- Magic-byte validator table -------------------------------------------------------------
function head(tag: string, len = 64): Buffer {
return Buffer.concat([Buffer.from(tag, "latin1"), Buffer.alloc(Math.max(0, len - tag.length))]);
}
describe("looksLikeFontFile (font container magic)", () => {
it("accepts every real font-container signature", () => {
assert.equal(looksLikeFontFile(head("wOF2")), true, "woff2");
assert.equal(looksLikeFontFile(head("wOFF")), true, "woff");
assert.equal(looksLikeFontFile(head("OTTO")), true, "CFF OpenType (otf)");
assert.equal(looksLikeFontFile(head("true")), true, "TrueType 'true' sfnt");
assert.equal(looksLikeFontFile(head("ttcf")), true, "TrueType collection");
assert.equal(
looksLikeFontFile(Buffer.concat([Buffer.from([0x00, 0x01, 0x00, 0x00]), Buffer.alloc(60)])),
true,
"TrueType \\x00\\x01\\x00\\x00 sfnt (ttf)",
);
// EOT: 0x504C ("LP") marker at byte offset 34.
const eot = Buffer.alloc(80);
eot[34] = 0x4c;
eot[35] = 0x50;
assert.equal(looksLikeFontFile(eot), true, "eot");
});
it("rejects HTML/text impostor bodies (the SPA-router 200 shell)", () => {
assert.equal(looksLikeFontFile(Buffer.from("<!DOCTYPE html><html><head></head></html>")), false);
assert.equal(looksLikeFontFile(Buffer.from("<!doctype html>")), false);
assert.equal(looksLikeFontFile(Buffer.from("<html><body>not a font</body></html>")), false);
assert.equal(looksLikeFontFile(Buffer.from("<?xml version=\"1.0\"?>")), false);
assert.equal(looksLikeFontFile(Buffer.from("just some plain text body")), false);
});
it("rejects truncated/empty buffers", () => {
assert.equal(looksLikeFontFile(Buffer.alloc(0)), false);
assert.equal(looksLikeFontFile(Buffer.from([0x77, 0x4f])), false); // 2 bytes of "wO"
});
});
// --- Validity-aware first-insert preference in buildFontGraph -------------------------------
function fontEntry(sourceUrl: string, localPath: string | null): AssetEntry {
return {
sourceUrl,
type: "font",
classification: localPath ? "downloaded" : "skipped",
localPath,
storedFile: localPath ? localPath.split("/").pop()! : null,
bytes: localPath ? 45_000 : 0,
reason: localPath ? null : "font_file_unavailable",
impact: null,
via: [],
};
}
function graphOf(entries: AssetEntry[]): AssetGraph {
const byUrl = new Map<string, AssetEntry>();
for (const e of entries) byUrl.set(e.sourceUrl, e);
return { entries, byUrl };
}
const DOC = "https://host.example/page";
// The genuine subset lives under the css file's sibling media dir; only THIS url is downloaded.
const GOOD_URL = "https://host.example/marketing-static/_next/static/media/good.woff2";
// The wrongly-based (document-relative) url that the SPA router answered with HTML and that was
// therefore rejected at store time — it never becomes a downloaded asset.
const BAD_URL = "https://host.example/media/impostor.woff2";
describe("buildFontGraph validity-aware preference (first-insert race)", () => {
it("prefers the face whose src resolves to a downloaded file even when it was inserted SECOND", () => {
const graph = graphOf([fontEntry(GOOD_URL, "/assets/cloned/fonts/good.woff2")]);
// Insertion order mirrors the real race: the mis-based CSSOM face lands first, the correctly
// based css-text face second. Only the good url is a downloaded asset.
const faces: FontFace[] = [
{ family: "Gothic", weight: "400", style: "normal", src: `url("${BAD_URL}") format("woff2")` },
{ family: "Gothic", weight: "400", style: "normal", src: `url("${GOOD_URL}") format("woff2")` },
];
const { entries, css } = buildFontGraph(faces, graph, DOC);
const gothic = entries.filter((e) => e.family === "Gothic");
assert.equal(gothic.length, 1, "deduped to a single face");
assert.equal(gothic[0]!.status, "resolved");
assert.deepEqual(gothic[0]!.localPaths, ["/assets/cloned/fonts/good.woff2"]);
assert.match(css, /good\.woff2/);
assert.doesNotMatch(css, /impostor/);
});
it("keeps the FIRST face when neither resolves (fallback, ties hold order)", () => {
const graph = graphOf([]); // nothing downloaded
const faces: FontFace[] = [
{ family: "Gothic", weight: "400", style: "normal", src: `url("${BAD_URL}") format("woff2")` },
{ family: "Gothic", weight: "400", style: "normal", src: `url("${GOOD_URL}") format("woff2")` },
];
const { entries } = buildFontGraph(faces, graph, DOC);
const gothic = entries.filter((e) => e.family === "Gothic");
assert.equal(gothic.length, 1);
assert.equal(gothic[0]!.status, "fallback");
});
it("keeps the FIRST resolving face when BOTH resolve (ties hold insertion order)", () => {
const alt = "https://host.example/marketing-static/_next/static/media/alt.woff2";
const graph = graphOf([
fontEntry(GOOD_URL, "/assets/cloned/fonts/good.woff2"),
fontEntry(alt, "/assets/cloned/fonts/alt.woff2"),
]);
const faces: FontFace[] = [
{ family: "Gothic", weight: "400", style: "normal", src: `url("${GOOD_URL}") format("woff2")` },
{ family: "Gothic", weight: "400", style: "normal", src: `url("${alt}") format("woff2")` },
];
const { entries } = buildFontGraph(faces, graph, DOC);
const gothic = entries.filter((e) => e.family === "Gothic");
assert.equal(gothic.length, 1);
assert.deepEqual(gothic[0]!.localPaths, ["/assets/cloned/fonts/good.woff2"], "first resolving wins");
});
});
// --- baseHref-driven relative resolution in buildFontGraph ----------------------------------
describe("buildFontGraph resolves a face's relative src against its owning sheet (baseHref)", () => {
it("uses baseHref, not the document url, so ../media climbs from the css file", () => {
// The sheet lives at /marketing-static/_next/static/css/sheet.css; `../media/x` from THERE is
// /marketing-static/_next/static/media/x.woff2, which is what actually downloaded. Resolved
// against the document (/page) it would clamp to /marketing-static/_next/static/media only if
// the css path were the base — the point of baseHref.
const sheetUrl = "https://host.example/marketing-static/_next/static/css/sheet.css";
const resolved = "https://host.example/marketing-static/_next/static/media/x.woff2";
const graph = graphOf([fontEntry(resolved, "/assets/cloned/fonts/x.woff2")]);
const faces: FontFace[] = [
{ family: "Gothic", weight: "400", style: "normal", src: 'url("../media/x.woff2") format("woff2")', baseHref: sheetUrl },
];
const { entries } = buildFontGraph(faces, graph, DOC);
assert.equal(entries[0]!.status, "resolved");
assert.deepEqual(entries[0]!.localPaths, ["/assets/cloned/fonts/x.woff2"]);
});
it("falls back to the document url when baseHref is absent (inline <style> / out-of-band parse)", () => {
// An out-of-band parse bakes the absolute url into src, so document fallback is harmless.
const abs = "https://host.example/fonts/y.woff2";
const graph = graphOf([fontEntry(abs, "/assets/cloned/fonts/y.woff2")]);
const faces: FontFace[] = [
{ family: "Mono", weight: "400", style: "normal", src: `url("${abs}") format("woff2")` },
];
const { entries } = buildFontGraph(faces, graph, DOC);
assert.equal(entries[0]!.status, "resolved");
});
});
+136
View File
@@ -0,0 +1,136 @@
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import { createServer, type Server } from "node:http";
import { chromium, type Browser, type Page } from "playwright";
import { collectPage, type PageSnapshot } from "../src/capture/walker.js";
// A relative url() inside a stylesheet must resolve against THAT sheet's url, not the document.
// These tests serve real CSS files at a NESTED path so `../media/x` climbs differently depending
// on the base: against the css file it lands under the nested dir, against the page it clamps to
// the site root. We assert the harvested url (cssUrls) and the face's baseHref carry the sheet
// base — the exact fix for the "HTML saved as woff2" font-materialization cluster.
// The nested layout mirrors a real bundler output:
// /page (document)
// /marketing-static/_next/static/css/x.css (external sheet)
// /marketing-static/_next/static/media/f.woff2 ← where ../media/f.woff2 SHOULD resolve
const CSS_PATH = "/marketing-static/_next/static/css/x.css";
const EXPECTED_MEDIA = "/marketing-static/_next/static/media/f.woff2";
// The WRONG (document-relative) resolution the old code produced:
const WRONG_MEDIA = "/media/f.woff2";
function serveText(body: string, type: string, res: import("node:http").ServerResponse): void {
res.writeHead(200, { "content-type": type });
res.end(body);
}
describe("font url() resolution against the owning stylesheet", () => {
let browser: Browser;
let page: Page;
let server: Server;
let origin = "";
before(async () => {
server = createServer((req, res) => {
const url = (req.url || "/").split("?")[0];
if (url === "/page") {
return serveText(
`<!doctype html><html><head><link rel="stylesheet" href="${CSS_PATH}"></head><body><p>hi</p></body></html>`,
"text/html",
res,
);
}
if (url === "/import-host") {
// A same-origin sheet that @imports the nested sheet; the imported sheet's own url must be
// the base for its faces, not the host sheet or the page.
return serveText(
`<!doctype html><html><head><link rel="stylesheet" href="/top.css"></head><body><p>hi</p></body></html>`,
"text/html",
res,
);
}
if (url === "/top.css") {
return serveText(`@import url("${CSS_PATH}");`, "text/css", res);
}
if (url === CSS_PATH) {
return serveText(
`@font-face{font-family:"Gothic";font-style:normal;font-weight:400;` +
`src:url("../media/f.woff2") format("woff2");}` +
`.hero{background:url("../media/bg.png")}`,
"text/css",
res,
);
}
res.writeHead(404).end();
});
await new Promise<void>((r) => server.listen(0, "127.0.0.1", r));
const addr = server.address();
if (addr && typeof addr === "object") origin = `http://127.0.0.1:${addr.port}`;
browser = await chromium.launch();
page = await browser.newPage();
});
after(async () => {
await browser.close();
await new Promise<void>((r) => server.close(() => r()));
});
const snap = async (path: string): Promise<PageSnapshot> => {
await page.goto(`${origin}${path}`, { waitUntil: "networkidle" });
await page.evaluate("globalThis.__name = globalThis.__name || ((fn) => fn);");
return page.evaluate(collectPage) as Promise<PageSnapshot>;
};
it("resolves a font-face src against the external sheet's url, not the document", async () => {
const s = await snap("/page");
assert.ok(
s.cssUrls.includes(`${origin}${EXPECTED_MEDIA}`),
`expected sheet-relative ${EXPECTED_MEDIA} in cssUrls, got ${JSON.stringify(s.cssUrls)}`,
);
assert.ok(
!s.cssUrls.includes(`${origin}${WRONG_MEDIA}`),
"must NOT produce the document-relative (wrong) resolution",
);
const face = s.fontFaces.find((f) => f.family === "Gothic");
assert.ok(face, "font face captured");
assert.equal(face!.baseHref, `${origin}${CSS_PATH}`, "face carries its owning sheet's base");
});
it("resolves ordinary style-rule url() against the sheet too", async () => {
const s = await snap("/page");
assert.ok(
s.cssUrls.includes(`${origin}/marketing-static/_next/static/media/bg.png`),
`expected sheet-relative bg.png, got ${JSON.stringify(s.cssUrls)}`,
);
});
it("resolves an @import-nested sheet's face against the IMPORTED sheet's url", async () => {
const s = await snap("/import-host");
// top.css @imports x.css; the face lives in x.css, so ../media resolves from x.css's dir.
assert.ok(
s.cssUrls.includes(`${origin}${EXPECTED_MEDIA}`),
`@import base must be the imported sheet, got ${JSON.stringify(s.cssUrls)}`,
);
const face = s.fontFaces.find((f) => f.family === "Gothic");
assert.equal(face?.baseHref, `${origin}${CSS_PATH}`);
});
it("falls back to the document base for an inline <style> (no sheet href)", async () => {
await page.goto(`${origin}/page`, { waitUntil: "networkidle" });
await page.evaluate("globalThis.__name = globalThis.__name || ((fn) => fn);");
await page.evaluate(() => {
const st = document.createElement("style");
st.textContent =
'@font-face{font-family:"Inline";src:url("./inline/g.woff2") format("woff2")}';
document.head.appendChild(st);
});
const s = (await page.evaluate(collectPage)) as PageSnapshot;
// document base is /page → ./inline/g.woff2 resolves to /inline/g.woff2 (page-relative).
assert.ok(
s.cssUrls.includes(`${origin}/inline/g.woff2`),
`inline <style> resolves against the document, got ${JSON.stringify(s.cssUrls)}`,
);
const face = s.fontFaces.find((f) => f.family === "Inline");
assert.equal(face?.baseHref, undefined, "inline face has no sheet href");
});
});