Capture & emission fidelity wave: dotLottie support, scroll-state isolation, authored-geometry trust, honest quality scoring

Capture:
- Preserve real asset extensions (.lottie no longer truncated); extract the
  default animation JSON from dotLottie ZIP containers at materialization
  (minimal built-in ZIP reader, no new deps)
- Reset scroll + settle before every viewport snapshot so scroll-linked
  styles can't bake into captured computed values
- Marquee detection now requires content overflow (>1.35x), sustained
  velocity across separated windows, scroll independence (jiggle test), and
  genuine content repetition — kills scroll-settle-lerp false positives
- Sizing probe trusts authored explicit heights (px/vh/calc) over circular
  parent/child reflow verdicts, fixing dropped 100vh heroes

Normalize/generate:
- Canonicalize identity transforms to "none" per viewport; emit explicit
  transform resets across bands when any band has a real transform
- DittoLottie keeps the captured placeholder until DOMLoaded; failed loads
  no longer blank the container
- mx-auto only for true auto-margin centering; literal per-band margins are
  preserved (fixes full-bleed regressions)
- Spacing-scale snap tightened to 0.25px (3.5px stays p-[3.5px]); emit
  whitespace-nowrap for wrap-vulnerable single-line text leaves
- Grid recipes no longer override captured track counts or column spans;
  responsive re-flow plans apply only when geometry agrees

Quality scoring:
- Rewritten 6-dimension rubric (payload, decomposition, duplication,
  semantics, hygiene, runtime) with catastrophe caps and per-dimension
  reporting; thresholds are documented calibration guides

218 tests pass (74 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-03 23:09:16 -07:00
co-authored by Claude Fable 5
parent 77be868ee3
commit d3ec154bae
19 changed files with 2345 additions and 386 deletions
+256
View File
@@ -0,0 +1,256 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
inlineBlobBytes,
filePayload,
whitespaceLiterals,
subpixelArbitraries,
customPropTokens,
probeArtifacts,
uncleanedListeners,
tagHistogram,
ariaHiddenFocusables,
duplicateHelpers,
svgPathDuplication,
nearDuplicateComponents,
componentNames,
toLetter,
scoreApp,
type SrcFile,
} from "../src/runner/qualityScore.js";
const srcFile = (rel: string, text: string): SrcFile => ({
path: rel, rel, ext: rel.slice(rel.lastIndexOf(".")), text, lines: text.split("\n").length,
});
// ---------------------------------------------------------------------------
// Metric extractors
// ---------------------------------------------------------------------------
describe("inlineBlobBytes", () => {
it("counts base64 data-URI payload bytes", () => {
const b64 = "A".repeat(500);
const text = `const img = "data:image/png;base64,${b64}";`;
assert.equal(inlineBlobBytes(text), 500);
});
it("counts a long markup string handed in as a prop", () => {
const html = "<div>" + "<span>x</span>".repeat(250) + "</div>";
const text = `<Frame html={${JSON.stringify(html)}} />`;
assert.ok(inlineBlobBytes(text) > 1000, "flags HTML-as-string blob");
});
it("ignores ordinary short strings", () => {
assert.equal(inlineBlobBytes(`const s = "hello world";`), 0);
});
});
describe("filePayload", () => {
it("reports a giant single line", () => {
const giant = "x".repeat(200_000);
const p = filePayload(srcFile("a.tsx", `const a = 1;\nconst b = "${giant}";\n`));
assert.ok(p.maxLine >= 200_000, "detects the giant line");
assert.ok(p.bytes >= 200_000, "counts the bytes");
});
it("a normal formatted file has a small max line", () => {
const p = filePayload(srcFile("a.tsx", "const a = 1;\nconst b = 2;\nexport default a;\n"));
assert.ok(p.maxLine < 40, "small max line");
});
});
describe("whitespaceLiterals", () => {
it("counts {\" \"} capture-whitespace literals", () => {
const text = `<p>Hi{" "}there{" "}world{' '}!</p>`;
assert.equal(whitespaceLiterals(text), 3);
});
it("does not flag meaningful expression literals", () => {
assert.equal(whitespaceLiterals(`<p>{name}{count}</p>`), 0);
});
});
describe("subpixelArbitraries", () => {
it("flags non-integer px/rem arbitraries but not whole ones", () => {
// 713.938px (frozen) + 12.5rem (=200px, whole) → only the first counts.
const text = `<div className="w-[713.938px] h-[12.5rem] p-[16px]" />`;
assert.equal(subpixelArbitraries(text), 1);
});
});
describe("customPropTokens", () => {
it("splits opaque --clr-N / hash tokens from named ones", () => {
const css = `:root{ --clr-7:#fff; --c12:#000; --a1b2c3:#111; --brand-primary:#f00; --space-4:1rem; }`;
const t = customPropTokens(css);
assert.equal(t.total, 5);
assert.equal(t.opaque, 3, "clr-7, c12, hash a1b2c3 are opaque; brand-primary/space-4 are named");
});
});
describe("probeArtifacts", () => {
it("flags off-screen capture-probe scaffolding", () => {
const text = `<span data-probe="1">m</span><i style={{clip: 'rect(0 0 0 0)'}} />`;
assert.ok(probeArtifacts(text) >= 2);
});
});
describe("uncleanedListeners", () => {
it("counts addEventListener with no cleanup", () => {
const text = `el.addEventListener("scroll", fn);\nwin.addEventListener("resize", fn);`;
assert.equal(uncleanedListeners(text), 2);
});
it("does not flag when a matching removeEventListener / teardown exists", () => {
const text = `useEffect(() => { el.addEventListener("scroll", fn); return () => el.removeEventListener("scroll", fn); });`;
assert.equal(uncleanedListeners(text), 0);
});
});
describe("tagHistogram", () => {
it("counts opening element tags by name", () => {
const h = tagHistogram(`<div><div/><span>x</span><button>b</button></div>`);
assert.equal(h["div"], 2);
assert.equal(h["span"], 1);
assert.equal(h["button"], 1);
});
});
describe("ariaHiddenFocusables", () => {
it("flags aria-hidden on a focusable element", () => {
const text = `<button aria-hidden="true">x</button><a aria-hidden={true} href="#">y</a>`;
assert.equal(ariaHiddenFocusables(text), 2);
});
it("ignores aria-hidden on a decorative div", () => {
assert.equal(ariaHiddenFocusables(`<div aria-hidden="true" />`), 0);
});
});
describe("duplicateHelpers", () => {
it("counts copy-paste helper bodies (e.g. a repeated cn())", () => {
const body = `{ return classes.filter(Boolean).join(" ").replace(/\\s+/g, " ").trim(); }`;
const text = `function cn(...classes) ${body}\nfunction cx(...classes) ${body}\nfunction merge(...classes) ${body}`;
const d = duplicateHelpers(text);
assert.equal(d.defs, 3);
assert.equal(d.dups, 2, "two of the three are identical duplicates");
});
it("does not flag distinct helper bodies", () => {
const text = `function a(x) { return x + 1111111111; }\nfunction b(x) { return x - 2222222222; }`;
assert.equal(duplicateHelpers(text).dups, 0);
});
});
describe("svgPathDuplication", () => {
it("counts repeated inline <path d=...> strings", () => {
const d = "M10 10 L20 20 L30 30 Z aaaaaaaaaaaaaaaa";
const text = `<path d="${d}"/><path d="${d}"/><path d="M1 1 L2 2 different pathhhhhhhh"/>`;
const r = svgPathDuplication(text);
assert.equal(r.total, 3);
assert.equal(r.repeats, 1);
});
});
describe("nearDuplicateComponents", () => {
it("counts pairs sharing a tag signature", () => {
assert.equal(nearDuplicateComponents(["div:2,span:1", "div:2,span:1", "nav:1"]), 1);
assert.equal(nearDuplicateComponents(["a", "a", "a"]), 2);
});
});
describe("componentNames", () => {
it("extracts exported + declared component symbols", () => {
const text = `export default function HeroSection(){}\nexport function Footer(){}\nconst Navbar = () => {};`;
const names = componentNames(text);
assert.ok(names.includes("HeroSection"));
assert.ok(names.includes("Footer"));
assert.ok(names.includes("Navbar"));
});
});
describe("toLetter", () => {
it("maps scores to the expected grade bands", () => {
assert.equal(toLetter(95), "A");
assert.equal(toLetter(82), "B-");
assert.equal(toLetter(75), "C");
assert.equal(toLetter(67), "D+");
assert.equal(toLetter(59), "F");
});
});
// ---------------------------------------------------------------------------
// scoreApp — end-to-end on synthetic app trees (generic fixture content)
// ---------------------------------------------------------------------------
function makeTree(files: Record<string, string>): string {
const root = mkdtempSync(join(tmpdir(), "qs-"));
for (const [rel, text] of Object.entries(files)) {
const p = join(root, rel);
mkdirSync(p.slice(0, p.lastIndexOf("/")), { recursive: true });
writeFileSync(p, text);
}
return root;
}
describe("scoreApp — hard cap on catastrophic payload", () => {
it("caps a tree containing a multi-megabyte source file into D-range", () => {
const giant = "x".repeat(1_200_000); // >1MB single file
const root = makeTree({
"src/app/page.tsx": `export default function Page(){ return <main><h1>Hi</h1><section><p>ok</p></section></main>; }`,
"src/app/svgs/blob.tsx": `export const blob = "${giant}";`,
});
try {
const rep = scoreApp(root);
assert.ok(rep.caps.length > 0, "a catastrophe cap is recorded");
assert.ok(rep.total <= 68, `grade is capped into D-range, got ${rep.total}`);
assert.ok(["D+", "D", "D-", "F"].includes(rep.grade), `grade ${rep.grade} is D-range or below`);
assert.ok(rep.categories.payload!.score < rep.categories.payload!.max * 0.5, "payload dimension collapses");
} finally { rmSync(root, { recursive: true, force: true }); }
});
});
describe("scoreApp — semantics", () => {
it("penalizes a page with no h1 vs one with a single h1", () => {
const withH1 = makeTree({
"src/app/page.tsx": `export default function Page(){ return <main><h1>Title</h1><section><p>a</p></section><nav><a href="#">x</a></nav></main>; }`,
});
const noH1 = makeTree({
"src/app/page.tsx": `export default function Page(){ return <div><div><div><div><span>a</span></div></div></div></div>; }`,
});
try {
const a = scoreApp(withH1);
const b = scoreApp(noH1);
assert.ok(a.categories.semantics!.score > b.categories.semantics!.score, "h1 + semantic tags score higher");
assert.equal(b.categories.semantics!.metrics.h1, 0);
} finally {
rmSync(withH1, { recursive: true, force: true });
rmSync(noH1, { recursive: true, force: true });
}
});
});
describe("scoreApp — decomposition", () => {
it("rates a decomposed tree above a single-file monolith of the same markup", () => {
const bodyTags = "<div><p>x</p><a href='#'>l</a></div>".repeat(40);
const monolith = makeTree({
"src/app/page.tsx": `export default function Page(){ return <main><h1>H</h1>${bodyTags}</main>; }`,
});
const decomposed = makeTree({
"src/app/page.tsx": `import Hero from "./sections/hero";\nimport Feature from "./sections/feature";\nexport default function Page(){ return <main><h1>H</h1><Hero/><Feature/></main>; }`,
"src/app/sections/hero.tsx": `export function Hero(){ return <section>${"<div><p>x</p></div>".repeat(20)}</section>; }`,
"src/app/sections/feature.tsx": `export function Feature(){ return <section>${"<div><a href='#'>l</a></div>".repeat(20)}</section>; }`,
});
try {
const m = scoreApp(monolith);
const d = scoreApp(decomposed);
assert.ok(d.categories.decomposition!.score > m.categories.decomposition!.score, "decomposed scores higher on decomposition");
assert.ok(d.total > m.total, "and grades higher overall");
} finally {
rmSync(monolith, { recursive: true, force: true });
rmSync(decomposed, { recursive: true, force: true });
}
});
});