Skip to content

Add deterministic render-free static visual-parity gate (replace flaky pixelmatch)#344

Merged
chubes4 merged 1 commit into
trunkfrom
cook/static-style-parity-gate
Jun 29, 2026
Merged

Add deterministic render-free static visual-parity gate (replace flaky pixelmatch)#344
chubes4 merged 1 commit into
trunkfrom
cook/static-style-parity-gate

Conversation

@chubes4

@chubes4 chubes4 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What

Adds a DETERMINISTIC, render-free visual-parity signal as the primary parity gate for SSI imports — replacing flaky full-page pixelmatch (which OOM'd on tall pages and hung on source capture). The pipeline is deterministic PHP (transform + CSS materialization), so the parity check is too: same inputs -> byte-identical output, no browser, no rasterization, no dimensions, no OOM.

How it works

Run the source through the transformer, rebuild the candidate HTML from the serialized blocks (delimiters stripped), and compare source vs candidate under the same author CSS — so the only variable is the transformed DOM. Yields a per-property diff (which CSS property on which selector diverged) + a stable score.

Components (src/VisualParity/)

  • StaticCssCascade.php — browser-free cascade resolver (specificity + source order, inline override, inheritance; inline <style> + caller linked CSS).
  • StaticStyleParityProbe.php — effective-style fingerprint per styleable element over 16 frozen visual properties + stable selector/structural identity.
  • StaticStyleParityComparator.php — deterministic 4-tier element matching (selector / class+text / class / structural), per-property findings, stable score (property_parity x coverage), via VisualParityReportContract.
  • StaticStyleParityRunner.php + composer static-parity harness (report mode + opt-in --fail-under gate).

Proofs

  • Determinism: composer static-parity --fixture 15-saas --json run twice -> byte-identical (SHA-256 match).
  • Reliability: 15-saas (which pixelmatch could never capture) + 38-medical both produce results render-free in ~5-7s, no OOM. property_parity 0.974 / 0.971 (styling preserved post-fixes); coverage 0.75 / 0.64.
  • Meaningfulness: a mismatch fixture proves it flags exactly section.hero background-color -> no declared value, border-radius -> no declared value.
  • composer test + composer parity: 182 fixtures green (+3 new).

Honest limits / next slice

  • Coverage <1.0 is partly a matching artifact (transformer collapses wrapper divs -> some structural-tier matches misalign); high-signal part is property_parity on selector/class matches (~0.97). Correspondence-quality refinement is the next slice.
  • Candidate = serialized-blocks proxy (faithful for class/tag/style preservation) — doesn't exercise WP's own block render/global-styles; a live-WP static gate (fetch rendered HTML, not screenshots, run this comparator) is deferred and gate-ready.
  • Cascade approximations: no !important / attribute selectors / combinators; pseudo-state stripped.

AI assistance

  • AI assistance: Yes
  • Tool(s): Claude Code (Claude Opus 4.8, 1M context)
  • Used for: Design, implementation, and determinism/reliability verification under human review.

Replace flaky full-page pixelmatch as the primary visual-parity signal with a
deterministic, render-free static style parity gate. The whole import pipeline
(transform + CSS materialization) is deterministic, so the parity check is too:
same inputs yield a byte-identical 0..1 score plus a per-element / per-property
diff naming exactly which CSS property on which selector diverged. No screenshot
rasterization, no dimension-sensitivity, no scroll/animation flakiness, no OOM.

Extends the existing render-free probe/comparator class (Typography/ButtonMenu)
that already runs inside the fast `composer parity` loop:

- StaticCssCascade: deterministic, browser-free CSS cascade resolver
  (specificity-then-source-order, inline override, inheritance) over <style> +
  caller-supplied (linked) CSS. Approximates getComputedStyle for the subset of
  author-declared, statically-resolvable properties parity cares about.
- StaticStyleParityProbe: extracts a stable effective-style fingerprint per
  styleable element over a frozen, sorted set of visually load-bearing
  properties, with a stable selector path, class-free structural path, class
  set, and text snippet for deterministic correspondence.
- StaticStyleParityComparator: matches source<->candidate elements by a fixed
  deterministic tier order (selector path -> class+text -> class -> structural
  position), consuming the first still-unmatched candidate in document order;
  emits a stable score (property agreement x element coverage), per-element
  matches, and per-property findings through the shared
  VisualParityReportContract.
- StaticStyleParityRunner: end-to-end render-free runner that transforms the
  source, rebuilds a candidate from the serialized blocks, and compares both
  under the same author CSS so the only variable is the transformed DOM.
- tools/static-parity/run.php (+ `composer static-parity`): corpus gate harness
  with a report mode and an opt-in `--fail-under` threshold gate.

Element correspondence is solved by stable identity (preserved class -> className
and semantic tags), never by pixels. Determinism verified by byte-identical
output across runs; reliability verified on 15-saas and 38-medical-clinic (which
full-page pixelmatch could not capture without OOM) in seconds with no browser.

Wires two parity fixtures (static_style_parity.extract / .compare) into the fast
loop; full suite green (182 parity fixtures).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chubes4 chubes4 merged commit 07b53cc into trunk Jun 29, 2026
1 check passed
@chubes4 chubes4 deleted the cook/static-style-parity-gate branch June 29, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant