Grid plan equality guard, named-token px validation, breakpoint-ordered source variants, hidden-text diagnostic

- A grid-cols-N recipe plan may only override the authored template when
  computed tracks are near-equal - asymmetric sidebar templates
  (260px 1fr) keep their authored geometry
- Source-intent named tokens (max-w-md etc.) re-emit only when their
  Tailwind-4 value matches the captured computed px; mismatches (older
  token scales) emit the captured value instead
- Per-axis source-variant selection keeps the highest-min-width active
  variant instead of last-in-class-order, matching real Tailwind cascade
  (lg:grid-cols-3 no longer loses to a later md:grid-cols-2 at desktop) -
  latent since the initial commit
- New non-blocking textHiddenInClone metric in the dom gate: counts
  capture-visible text that renders hidden in the clone, so silent
  disappearances surface in reports

435 tests pass (10 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-04 06:11:14 -07:00
co-authored by Claude Fable 5
parent f16c3aa4e8
commit 5b60ded62d
6 changed files with 384 additions and 7 deletions
+33
View File
@@ -86,6 +86,32 @@ function hasVisibleElementChild(node: IRNode, vp: number): boolean {
return false;
}
/** A source text node that the capture painted VISIBLE but the clone rendered HIDDEN at the same
* viewport is a high-signal regression: the words are in the markup yet fall in an invisible gen
* subtree (off-viewport-shifted, banded-hidden, or width-frozen off-screen). Cheap to detect —
* the gen node exists (matched by cid) but reports `visible === false`. Counts DISTINCT source
* cids over the run (a node hidden at several viewports counts once) so the number reads as
* "how many text nodes went dark", not "hidden-node×viewport". Non-blocking: reported as a metric
* only. Pure + input-driven → deterministic. */
export function countVisibleInCaptureHiddenInClone(
ir: IR,
genSnaps: Record<number, PageSnapshot>,
viewports: number[],
): number {
const hiddenCids = new Set<string>();
for (const vp of viewports) {
if (!genSnaps[vp]) continue;
const gen = indexByCid(genSnaps[vp]!);
for (const s of collectSrcNodes(ir, vp)) {
if (!s.visible) continue;
if (normText(s.directText).length === 0) continue;
const g = gen.get(s.node.id);
if (g && !g.visible) hiddenCids.add(s.node.id);
}
}
return hiddenCids.size;
}
// ---------- Pollution gate (stage 2): is the captured page degenerate? ----------
// A clone can pass every structural gate while faithfully reproducing the WRONG
// page: an egress/bot wall, a near-empty shell, or a cookie/consent modal that was
@@ -278,6 +304,12 @@ export function gate3Dom(ir: IR, genSnaps: Record<number, PageSnapshot>, viewpor
}
}
// Non-blocking, high-signal diagnostic: source text the capture painted VISIBLE that the clone
// renders HIDDEN at the same viewport (off-screen-shifted / banded / width-frozen subtree). Does
// NOT gate pass — text presence already covers markup fidelity — but surfaces the "went dark"
// count so a banner/feed row vanishing is legible in the report instead of hiding inside a 99.x.
const textHiddenInClone = countVisibleInCaptureHiddenInClone(ir, genSnaps, viewports);
const matchPct = totalVisible ? matched / totalVisible : 1;
const textPct = textTotal ? textPresent / textTotal : 1;
const linkPct = linksTotal ? linksOk / linksTotal : 1;
@@ -296,6 +328,7 @@ export function gate3Dom(ir: IR, genSnaps: Record<number, PageSnapshot>, viewpor
nodeMatchPct: round4(matchPct), textPresentPct: round4(textPct),
linkPct: round4(linkPct), mediaPct: round4(mediaPct),
totalVisible, matched, textTotal, textPresent, linksTotal, mediaTotal, inventedText,
textHiddenInClone,
},
issues,
};