Fix build-breaking ident rewrite, blank-iframe grafting, and layout-gate regressions
Round-3 fixes from gate validation + human visual review of fresh cropin.com and ooni.com clones: - app.ts: section data-var rename used an unanchored substring replace, so Tile2_data matched inside MediaTile2_data and emitted a corrupted Mediatile2Data usage (ReferenceError at prerender). Word-boundary regex; every map site now shares one derivation. - graft.ts: about:blank iframes were blanket-skipped, missing the Klaviyo newsletter form (mounted into a blank same-origin frame via JS) — the audit's #1 complaint. Blank frames now graft; the visibility gate still excludes 0-size tracking pixels. - walker.ts: isVisible() now rejects boxes wholly outside the viewport (off-left always; off-right only when the page isn't horizontally scrollable; fixed boxes fully above/below), so a closed slide-in drawer's contents no longer count as expected text (91.8% -> 93.4%). - css.ts: four sizing-regime corrections — pin captured px for circular shrink-0 slides (Splide slide chain collapsed 0x0); flex-basis:100% for full-width shrink-0 slides; keep fixed-px grid templates for scrolling track lists (repeat(N,1fr) squished a 50-track carousel); single full-bleed fixed track -> minmax(0,1fr); keep authored heights whose in-flow children are fill children (aspect-video heroes inflated 240 -> 720px); width:100% for inset-spanned aspect boxes. Gate scores: ooni home 88.7 -> 93.3 (responsive 32 fails -> pass), pizza-ovens 90.8 -> 97.6 (perceptual 46.7% -> 17.2%). 107/107 compiler tests, all workspaces green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
199359d0a6
commit
7f99b76f66
@@ -64,3 +64,97 @@ describe("walker whitespace-only text nodes", () => {
|
||||
assert.deepEqual(section.children, []);
|
||||
});
|
||||
});
|
||||
|
||||
function findByClass(root: RawNode, cls: string): RawNode | null {
|
||||
if ((root.attrs?.class ?? "").split(/\s+/).includes(cls)) return root;
|
||||
for (const c of root.children) {
|
||||
if (isText(c)) continue;
|
||||
const hit = findByClass(c, cls);
|
||||
if (hit) return hit;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
describe("walker off-screen visibility", () => {
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
before(async () => {
|
||||
browser = await chromium.launch();
|
||||
page = await browser.newPage();
|
||||
await page.setViewportSize({ width: 375, height: 768 });
|
||||
});
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
const capture = async (html: string) => {
|
||||
await page.setContent(html);
|
||||
await page.evaluate("globalThis.__name = globalThis.__name || ((fn) => fn);");
|
||||
return page.evaluate(collectPage);
|
||||
};
|
||||
|
||||
it("marks a fixed off-screen-left drawer's un-hidden inner content invisible", async () => {
|
||||
// Real case (ooni.com): a slide-in search drawer is a position:fixed box parked at
|
||||
// x:-375 (right edge at 0) with visibility:hidden; its inner content sets
|
||||
// visibility:visible (so getComputedStyle reports it visible) but the whole box is
|
||||
// still off the left edge. The drawer must not leak into the clone's DOM text.
|
||||
const snap = await capture(`
|
||||
<div class="drawer" style="position:fixed;left:0;top:0;width:375px;height:768px;
|
||||
transform:translateX(-100%);visibility:hidden;">
|
||||
<div class="inner" style="visibility:visible;width:343px;">
|
||||
<h2 class="drawer-title">Popular Searches</h2>
|
||||
</div>
|
||||
</div>
|
||||
<p class="onscreen">Hello</p>`);
|
||||
const title = findByClass(snap.root, "drawer-title")!;
|
||||
assert.equal(title.visible, false, "off-screen-left drawer title should be invisible");
|
||||
// The genuinely on-screen paragraph is still visible (sanity that the gate isn't over-broad).
|
||||
const onscreen = findByClass(snap.root, "onscreen")!;
|
||||
assert.equal(onscreen.visible, true, "on-screen content stays visible");
|
||||
});
|
||||
|
||||
it("keeps a partially-overlapping negative-margin decoration visible", async () => {
|
||||
// A decoration pulled left by a negative margin so it straddles the left edge
|
||||
// (x:-40, width:200 -> right edge +160) still paints and must stay visible.
|
||||
const snap = await capture(`
|
||||
<div style="overflow:hidden;width:375px;">
|
||||
<div class="deco" style="width:200px;height:40px;margin-left:-40px;background:red;">peek</div>
|
||||
</div>`);
|
||||
const deco = findByClass(snap.root, "deco")!;
|
||||
assert.ok(deco.bbox.x < 0, "decoration starts off-screen left");
|
||||
assert.ok(deco.bbox.x + deco.bbox.width > 0, "but its right edge is on-screen");
|
||||
assert.equal(deco.visible, true, "partially-overlapping decoration stays visible");
|
||||
});
|
||||
|
||||
it("keeps normal in-flow content below the fold visible", async () => {
|
||||
// The page scrolls vertically, so content below the viewport is reachable and must
|
||||
// NOT be marked invisible (only position:fixed boxes are pinned).
|
||||
const snap = await capture(`
|
||||
<div style="height:2000px;"></div>
|
||||
<p class="belowfold" style="height:40px;">Below the fold</p>`);
|
||||
const below = findByClass(snap.root, "belowfold")!;
|
||||
assert.ok(below.bbox.y > 768, "content is below the 768px viewport");
|
||||
assert.equal(below.visible, true, "below-fold in-flow content stays visible");
|
||||
});
|
||||
|
||||
it("marks a computed-visibility:hidden subtree invisible", async () => {
|
||||
// A subtree whose computed visibility is actually hidden (and does not set
|
||||
// visibility:visible on any descendant) is not painted.
|
||||
const snap = await capture(`
|
||||
<div style="visibility:hidden;width:300px;">
|
||||
<span class="hidden-child">secret</span>
|
||||
</div>`);
|
||||
const child = findByClass(snap.root, "hidden-child")!;
|
||||
assert.equal(child.visible, false, "computed-hidden child is invisible");
|
||||
});
|
||||
|
||||
it("rejects a fixed box parked entirely below the viewport", async () => {
|
||||
// A position:fixed banner pinned below the viewport bottom never scrolls into view.
|
||||
const snap = await capture(`
|
||||
<div class="fixedlow" style="position:fixed;left:0;top:900px;width:375px;height:60px;">
|
||||
<span>hidden banner</span>
|
||||
</div>`);
|
||||
const fixed = findByClass(snap.root, "fixedlow")!;
|
||||
assert.equal(fixed.visible, false, "fixed box below the viewport is invisible");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user