Clone-fidelity fix waves 1-3 (+ wave 4 in flight): capture settling, media, iframes

Fixes from the cropin.com / ooni.com audit reviews:
- walker: preserve whitespace-only text in inline elements ("ofthe" bug);
  capture ::placeholder styles for inputs
- css: emit visibility (hidden-at-rest wordmark artifact); exempt list
  markers from inherited elision (lost bullets); ::placeholder rules
- seo: sanitize og:type to Next's enum (render crash -> dev badge leak);
  generated next.config disables devIndicators
- capture/stabilize: promote lazy-loader data attrs before snapshots
  (collapsed sections, viewport-inconsistent captures); settle autoplay
  carousels to home slide before every snapshot; force-reveal hidden
  videos for poster shots + log failures
- generate: ship downloaded video files as local <video src>; keep
  picture>source through IR prune with srcset rewrite (mobile-crop-on-
  desktop heroes); pin Tailwind named screens to computeBands boundaries
- capture/graft: capture cross-origin iframe subtrees (Klaviyo signup
  forms) with element-screenshot fallback; asset download retry +
  visual-assets-missing reporting

Checkpoint commit: wave 4 (reveal settling, 206 range-response fix,
hidden-geometry banding, bare-zero utility gating) is mid-implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-03 17:45:42 -07:00
co-authored by Claude Fable 5
parent da537e68b8
commit 9aed2540aa
31 changed files with 2579 additions and 121 deletions
+66
View File
@@ -0,0 +1,66 @@
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import { chromium, type Browser, type Page } from "playwright";
import { collectPage, type RawNode, type RawChild } from "../src/capture/walker.js";
function isText(c: RawChild): c is { text: string } {
return (c as { text?: string }).text !== undefined;
}
function findByTag(root: RawNode, tag: string): RawNode | null {
if (root.tag === tag) return root;
for (const c of root.children) {
if (isText(c)) continue;
const hit = findByTag(c, tag);
if (hit) return hit;
}
return null;
}
function textRun(node: RawNode): string {
let out = "";
for (const c of node.children) out += isText(c) ? c.text : textRun(c);
return out;
}
describe("walker whitespace-only text nodes", () => {
let browser: Browser;
let page: Page;
before(async () => {
browser = await chromium.launch();
page = await browser.newPage();
});
after(async () => {
await browser.close();
});
const capture = async (html: string) => {
await page.setContent(html);
// tsx/esbuild wraps functions with a __name() helper for stack traces; the
// serialized collectPage carries those calls, so shim it (same as capture.ts).
await page.evaluate("globalThis.__name = globalThis.__name || ((fn) => fn);");
return page.evaluate(collectPage);
};
it("keeps the lone space that is the only child of an inline element", async () => {
// Real case (ooni.com): the space between "of" and "the" lives alone inside
// <strong>; dropping it fuses the adjacent text runs ("ofthe").
const snap = await capture("<p>Creator of<strong> </strong><em><strong>the world's</strong></em></p>");
const p = findByTag(snap.root, "p")!;
const strong = p.children.find((c) => !isText(c) && c.tag === "strong") as RawNode;
assert.deepEqual(strong.children, [{ text: " " }]);
assert.equal(textRun(p), "Creator of the world's");
});
it("still keeps the single space between inline elements", async () => {
const snap = await capture("<p><em>a</em> <em>b</em></p>");
const p = findByTag(snap.root, "p")!;
assert.equal(textRun(p), "a b");
});
it("does not emit a space inside an empty block container", async () => {
const snap = await capture("<main><section>\n \n</section></main>");
const section = findByTag(snap.root, "section")!;
assert.deepEqual(section.children, []);
});
});