feat(app): mvp-visual-verify — OCR + palette + pixel-diff + expectation layer over audit:app#14437
Conversation
|
Verification update after
Local verification: Attempted real screenshot capture: Blocked before screenshots by existing environment/dependency issues:
No app screenshots were produced in this run because Playwright never reached the capture phase. |
c2939bc to
e376ccd
Compare
Proof — run on REAL captured screenshots + caught-regression demoAll five columns exercised against the real Real run: 195 states (desktop + mobile-portrait) in ~68sThese 3 are genuine regressions the harness caught, hand-verified:
OCR / palette / diff are real (hand-checked)
Caught-regression demo (real headless chromium)
The capture spec ( Unit tests
Notes
|
…on layer over audit:app Post-processes the screenshots audit:app already captures into a higher-fidelity visual-verification report. For each <viewport>/<slug>.png: - OCR text readout via the system tesseract binary (honest N/A when absent) - dominant-color palette (sharp raw RGBA quantized) with brand-bucket labels - pixel-diff vs a committed baseline (zero new deps: sharp raw diff, magenta PNG) - declarative expectation validation (OCR present/absent, brand-orange accent, no blue, no horizontal overflow) → pass/fail with reasons Also wires a document-level horizontal-overflow invariant (documentElement.scrollWidth − innerWidth) into the capture spec — recorded into report.json for every view, hard-gated under ELIZA_AUDIT_APP_STRICT. This is the WS5 transcript-overflow signature (overflow-y:auto without overflow-x:hidden). Pure functions (color-bucket parity-tested against the audit's own bucket(), quantizePalette, summarizeDiff/comparePixels, evaluateExpectations/resolveSpec) are unit-tested in test/audit/mvp-visual-verify.test.ts (20 tests). Scripts: mvp:visual-verify + audit:app:verify (audit:app && mvp:visual-verify). Evidence tooling only — no CI gate added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…verify - Add scripts/mvp-visual-verify/overflow-invariant-demo.mjs: a real headless- chromium demonstration that the horizontal-overflow invariant catches the WS5 transcript bug (un-contained wide token → 2886px document overflow → invariant FIRES) and clears the fix (overflow-x:hidden + wrap → 0px). Doubles as a self-test of the capture-side gate; exposed as mvp:visual-verify:overflow-demo. - Biome format pass on the mvp-visual-verify modules; drop an unused counter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
e376ccd to
5df9343
Compare
biome noNonNullAssertion warns on the highlight! accesses; assert not-null then read through a nullish-coalesced buffer instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lalalune
left a comment
There was a problem hiding this comment.
Reviewed together with the other visual-verification PRs landing this session (#14433 merged, #14437, #14451, #14452). No file collisions or forks: none of these edits or imports the merged packages/app/scripts/lib/visual-qa.mjs; the only shared file across the three is packages/app/package.json (each appends a DIFFERENT, non-overlapping script key — a trivial additive scripts hunk merge for whichever lands 2nd/3rd). All additive, disjoint output artifacts, low blast radius (test tooling, no product/runtime code). #14437 mvp-visual-verify operates on the whole aesthetic-audit-output/ dir and adds a genuinely new capability — the document-level horizontal-overflow invariant. It reimplements OCR (tesseract.js)/palette/pixel-diff/expectation-eval in its own mvp-visual-verify/*.mjs rather than importing #14433's lib. Safe to merge; will need a one-hunk package.json scripts rebase if it lands after #14451/#14452. Duplication of the OCR/palette/pixel primitives with #14433 (and #14451/#14452) is real tech-debt — filing a consolidation follow-up. LGTM (additive).
|
Follow-up opened: #14472. Reason: #14437 merged the strict visual verifier, but Focused evidence on #14472:
|
|
Update pushed in What changed:
Local verification:
Manual screenshot review performed for the states that originally failed expectations:
Separate blocker still visible from the full app audit, not introduced by this verifier commit:
Environment notes:
|
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
mvp-visual-verify — a higher-fidelity visual-verification layer over
audit:appEvidence tooling (not a new CI gate) that post-processes the screenshots
audit:appalready captures (packages/app/aesthetic-audit-output/<viewport>/<slug>.png) into a per-state readout a reviewer can trust without re-eyeballing 200 PNGs. For every captured state it produces OCR text, a dominant-color palette, a pixel-diff vs a committed baseline, and a declarative pass/fail over per-state expectations — then writesreport.json+ an annotatedcontact-sheet.html.What it adds
packages/app/scripts/mvp-visual-verify.mjs— the post-processor. Discovers viewport dirs under the audit output, runs OCR + palette + diff + expectations per<viewport>/<slug>.png, writes a separatemvp-verify/tree (report.json,contact-sheet.html,diffs/,baseline/) so it never collides with the audit's own report.scripts/mvp-visual-verify/ocr.mjs— on-screen text via the systemtesseractbinary, spawned throughchild_process. Honest{ available:false, reason }degrade when the binary is absent (the OCR column renders "N/A", never fabricated text).scripts/mvp-visual-verify/dominant-color.mjs+color-bucket.mjs— sharp decodes to raw RGBA;quantizePalette(pure) buckets to a coarse grid and returns the top-K swatches with brand-bucket labels. The classifier is a faithful ESM port of the audit's ownbucket()(test/ui-smoke/aesthetic-audit-rules.ts) and is parity-tested against it across an RGB-cube sweep so the no-blue rule cannot drift between capture and post-process.scripts/mvp-visual-verify/diff.mjs— pixel diff with zero new deps: sharp decodes both PNGs,comparePixels(pure) counts changed pixels + per-channel mean-abs delta and paints a magenta-on-dimmed-gray highlight buffer,summarizeDiff(pure) reduces to%. A missing baseline recordsstatus:"new"— never a fabricated 0%.scripts/mvp-visual-verify/expectation-eval.mjs+expectations.json— declarative per-slug specs (expected OCR substrings present/absent, brand-orange accent, no blue, no horizontal overflow) → pass/fail with reasons. A genuinely-unavailable input (OCR engine absent, report lacks the overflow field) resolves toskip, never a silent pass.scripts/mvp-visual-verify/overflow-invariant-demo.mjs— a real headless-chromium demonstration that the horizontal-overflow invariant catches the WS5 transcript bug.test/audit/mvp-visual-verify.test.ts— 20 unit tests over the pure functions (parity sweep, quantizer, diff summarizer/comparator, expectation evaluator).test/ui-smoke/all-views-aesthetic-audit.spec.ts(modified) — records a document-level horizontal-overflow invariant (documentElement.scrollWidth − innerWidth) intoreport.jsonfor every view; hard-gated only underELIZA_AUDIT_APP_STRICT. A measurement failure throws (no fabricated "0 = healthy").package.json—mvp:visual-verify,audit:app:verify(=audit:app && mvp:visual-verify),mvp:visual-verify:overflow-demo.Engines used
tesseractv5.5.2 (/opt/homebrew/bin/tesseract), spawnedtesseract <png> stdout -l eng.tesseract.jsis NOT used (not installed).{available:false}→ "N/A (tesseract not found)"sharpraw RGBA → hue-based bucketing (ported + parity-pinned to the audit'sbucket())sharpraw decode + hand-rolled compare (pixelmatch deliberately not installed)status:"new"page.evaluatedocument-level probeProof (real screenshots, opened by hand)
Ran against the checkout's real
audit:appoutput — 195 states in ~68s, 3 real findings caught:Adversarial self-review — each column verified against the actual pixels:
plugin-polymarket-gui.pngOCR read"Cannot read properties of undefined (reading 'ready')"; opening the PNG shows that exact crash string in red on screen. Theundefinedjunk-token expectation fired correctly (whole-word matched, soundefinedinfinanceswould not false-fire).plugin-clawville-tuipalette reportedblue 91.4%(dominant swatch#080818@ 85.3%); the PNG is a dark-navy TUI — a genuine no-blue violation, caught.builtin-charactervsbuiltin-settingsbaseline = 93.5% (meanDelta 44.7) with a magenta diff PNG; resized mobile-vs-desktop settings = 36.1% (resized:true). Diff PNG opened: magenta on changed pixels, dimmed-gray on the structurally-shared bands.skipwhen OCR is unavailable.newwithout a baseline; overflowskips when the report predates the field. Nothing is fabricated.Caught-regression demo (real chromium)
bun run --cwd packages/app mvp:visual-verify:overflow-demo:It renders the WS5 transcript bug (an un-contained wide token, no
overflow-xcontainment) in headless chromium →documentElement.scrollWidth − innerWidth = 2886px, invariant fires; the fix (overflow-x:hidden+overflow-wrap:anywhere) → 0px, passes. It uses the exact probe now wired into the capture spec, so the demo doubles as a self-test of the capture-side gate. (Note the CSS subtlety it is built around:overflow-y:autoalone auto-promotesoverflow-xtoauto, self-containing the token; the real user-visible leak is an un-contained wide descendant, which is what BUGGY reproduces.)How the visual MVP workstreams use it for evidence
audit:app:verifyafter any launcher-tile change gives per-viewport palette + OCR + diff-vs-baseline for the launcher states; the accent-orange / no-blue expectations enforce the brand rule automatically, and the committed baseline flags unintended pixel drift (e.g. the label-clip regressions) instead of relying on eyeballing.overflow-y:autowithoutoverflow-x:hidden). The capture spec recordshorizontalOverflowPxper view;mvp:visual-verifysurfaces it and the strict gate can hard-fail it — a long unbreakable token in a bubble can no longer ship a horizontally-scrolling chat. The overflow-demo is the reusable proof that the gate bites.Models/Voice,Personality/Knowledge,Automations/Workflows) and carries no render-crash junk (undefined,NaN,[object Object]) — the polymarket crash above is exactly the class of regression a views sweep must not reintroduce. The contact-sheet is the single artifact a reviewer scans to sign off all views at once.Verification
CI=true npx vitest run test/audit/mvp-visual-verify.test.ts→ 20 passed.mvp:visual-verify:overflow-demo→ exit 0 (2886px fires, 0px passes).mvp:visual-verifyon 195 real states → 3 real findings, report + contact-sheet written.origin/develop.Evidence tooling only — no required CI check added.
report.json/contact-sheet.html/ diff+baseline PNGs are generated into the gitignoredaesthetic-audit-output/mvp-verify/.🤖 Generated with Claude Code