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
@@ -36,10 +36,22 @@ const FRAME_SKIP_RE =
|
||||
const FRAME_STILL_RE =
|
||||
/(?:youtube(?:-nocookie)?\.com\/embed|player\.vimeo\.com|\bwistia\b|fast\.wistia|players?\.brightcove|open\.spotify\.com|w\.soundcloud\.com|google\.com\/maps\/embed)/i;
|
||||
|
||||
/** How to materialize a frame's content, from its URL alone (deterministic). */
|
||||
/**
|
||||
* How to materialize a frame's content, from its URL alone (deterministic).
|
||||
*
|
||||
* NOTE on blank/empty-src frames: a frame with no `src` (or `about:blank`/`javascript:`)
|
||||
* is NOT inert — many form/widget embeds (Klaviyo lightbox signup, loyalty popups) mount a
|
||||
* same-origin blank iframe and inject their rendered DOM into it via script, so the element
|
||||
* carries real, sized content with no navigable URL. Those must GRAFT (the frame document is
|
||||
* same-origin, so collectPage evaluates in it directly). The dead pixels that also use a blank
|
||||
* src (analytics sandboxes, 0×0 tracking iframes) are filtered upstream by the `cand.visible`
|
||||
* gate in capture.ts — a blank frame only reaches a graft when it actually rendered at
|
||||
* ≥ MIN_FRAME_DIM on both axes, which a tracking pixel never does.
|
||||
*/
|
||||
export function planForFrameUrl(url: string): FramePlan {
|
||||
const u = (url || "").trim();
|
||||
if (!u || u === "about:blank" || u.startsWith("javascript:")) return "skip";
|
||||
if (u.startsWith("javascript:")) return "skip";
|
||||
if (!u || u === "about:blank") return "graft"; // JS-populated same-origin frame (visibility-gated)
|
||||
if (FRAME_SKIP_RE.test(u)) return "skip";
|
||||
if (FRAME_STILL_RE.test(u)) return "still";
|
||||
return "graft";
|
||||
|
||||
@@ -196,6 +196,21 @@ export function collectPage(opts?: { maxNodes?: number } | void): PageSnapshot {
|
||||
const scrollX = window.scrollX;
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
// Viewport + page extents used by isVisible's off-screen test. bbox coords are in
|
||||
// DOCUMENT space (r + scroll), so the visible window on each axis is
|
||||
// [scroll, scroll + inner]. A box whose border box lies wholly outside that window
|
||||
// on a NON-scrollable axis is unreachable and paints nothing to the user.
|
||||
const vpW = window.innerWidth;
|
||||
const vpH = window.innerHeight;
|
||||
const scrollEl = document.scrollingElement || document.documentElement;
|
||||
// Horizontal scrolling is legitimate when the page is wider than the viewport (RTL
|
||||
// carousels, horizontal galleries). In that case content parked to the RIGHT is
|
||||
// reachable by scrolling, so we only reject boxes fully off the LEFT edge (x <= 0
|
||||
// start-of-page, never reachable). Vertical always scrolls, so we never reject
|
||||
// in-flow content below the fold — only position:fixed boxes, which do NOT scroll
|
||||
// with the page and so are truly gone if parked above/below the viewport.
|
||||
const horizScrollable = round2(scrollEl.scrollWidth) > vpW + 1;
|
||||
|
||||
// Resolve `line-height: normal` to a concrete px value by probing the actual
|
||||
// line-box height for each (font-family, font-size, font-weight, font-style).
|
||||
// getComputedStyle reports the keyword "normal", which renders font-metric-
|
||||
@@ -234,6 +249,11 @@ export function collectPage(opts?: { maxNodes?: number } | void): PageSnapshot {
|
||||
|
||||
const isVisible = (el: Element, cs: CSSStyleDeclaration, bbox: RawBBox): boolean => {
|
||||
if (cs.display === "none") return false;
|
||||
// getComputedStyle already resolves `visibility` inheritance: a descendant that
|
||||
// sets visibility:visible inside a hidden ancestor reports "visible" here (and is
|
||||
// genuinely painted), so this test is exactly CSS computed semantics — no separate
|
||||
// ancestor walk is needed. The off-screen test below is what catches un-hidden
|
||||
// content parked outside the viewport (e.g. a slide-in drawer's inner nodes).
|
||||
if (cs.visibility === "hidden" || cs.visibility === "collapse") return false;
|
||||
if (parseFloat(cs.opacity || "1") === 0) return false;
|
||||
if (bbox.width === 0 && bbox.height === 0) {
|
||||
@@ -241,6 +261,28 @@ export function collectPage(opts?: { maxNodes?: number } | void): PageSnapshot {
|
||||
// as not visible for matching purposes.
|
||||
return false;
|
||||
}
|
||||
// Off-screen test. bbox is document-space (x/y already include scroll). The box is
|
||||
// invisible only when it lies WHOLLY outside a non-scrollable axis window — a box
|
||||
// that merely straddles an edge (negative-margin / overflow-hidden decoration
|
||||
// peeking in) still paints and stays visible.
|
||||
//
|
||||
// Horizontal: the page never scrolls left of origin, so anything whose right edge
|
||||
// is at/left of 0 is unreachable. When the page is NOT horizontally scrollable we
|
||||
// also reject boxes whose left edge is at/right of the viewport width; when it IS
|
||||
// scrollable (wide/RTL/carousel pages), right-parked content is reachable, so only
|
||||
// the fully-left case counts.
|
||||
const rightEdge = bbox.x + bbox.width;
|
||||
if (rightEdge <= 0) return false;
|
||||
if (!horizScrollable && bbox.x >= vpW) return false;
|
||||
// Vertical: the page scrolls, so below-/above-fold in-flow content is reachable and
|
||||
// must stay visible. Only position:fixed boxes are pinned to the viewport and do NOT
|
||||
// scroll into view — a fixed box parked entirely above or below the viewport is gone.
|
||||
if (cs.position === "fixed") {
|
||||
const top = bbox.y - scrollY;
|
||||
const bottom = top + bbox.height;
|
||||
if (bottom <= 0) return false;
|
||||
if (top >= vpH) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user