designmatch
Skill5 starsChanged 38 days agoMITView on GitHub
Use when matching a React Native screen to a Claude Design HTML canonical at ≥95% visual parity on real Android/iOS hardware — closes the loop with a VQA stub mode, Playwright canonical renderer at 1080×2444, and a dual-metric pixelmatch + SSIM diff harness producing composite triptychs.
In this repository7 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- heimdallSkill
- mac-deep-cleanSkill
- self-improveSkill
- stacksSkill
- system-healthSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 3 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 23
Do NOT use for: unit logic, redux state shape, navigation graph correctness — those are not visual.
- Very specific: line 68
All px literals → normalize(n)
- Distinctive: line 71
Keep variable names + structure identical to canonical.
- Very specific: line 76
If the canonical is registered as a URL (not yet downloaded), designmatch port and designmatch port-all auto-run designmatch fetch first — …
- Very specific: line 135
default → poll window.__APP_READY__ truthy
- Distinctive: line 191
Default Typography variant resolves to body family → headings MUST specify display variant.
- Very specific: line 194
Custom
tabBarStyle: when expand-to-label animation is needed. - Very specific: line 203
Avoid worktree isolation when agents need full node_modules + live device.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
designmatch
Overview
Match RN screen → HTML canonical at ≥95% visual parity. Closed loop: VQA stub seeds redux → Playwright renders canonical PNG → adb/xcrun captures native PNG → pixelmatch + ssim.js scores diff → composite triptych for eyeball review → iterate.
Origin: a production React Native app. A single screen's SSIM climbed 35% → 55% over ~30 commits of eyeballed pixel-tweaking before this loop replaced it.
Pass gate: SSIM ≥ 0.95 OR pixelDiffPct ≤ 5%.
When to Use
- RN screen drift from Claude Design HTML canonical
- Visual parity audit before ship
- Per-screen iteration loop on real device
- Closing the design ↔ code feedback gap
Do NOT use for: unit logic, redux state shape, navigation graph correctness — those are not visual.
Quick Start — URL in, ready-to-translate out
From the RN project root:
designmatch init "<claude-design-url>" --app-dir . --port-allThis single command does the full bootstrap:
- App side: copies
assets/visual-qa.tsinto the app (src/lib/visual-qa.tsby default; auto-detectssrc/utils/,src/,app/, or root), writes default.designmatch/state.vqa.json, updates.gitignore. - Fetch: Playwright headless downloads the entire canonical bundle (HTML + JSX + assets) to
.designmatch/canonical/by intercepting every network response, then flipsconfig.jsonkind fromurl→local-dir(original URL preserved for re-fetch). - Port-all: discovers every
screen-*.jsx|tsx/*Screen.jsx|tsxin the bundle and writes each tosrc/screens/<Name>.tsxpreceded by the TRANSLATION GUIDE (web → RN idiom map). - Wire: prints the
App.tsxsnippet (primeVisualQaFlag / applyVisualQaState / overrideFeatureFlags / VqaBadge / long-press handler).
Slash command equivalent (inside a Heimdall session): /hmd:designmatch <url-or-path>.
Auth: if the canonical URL is behind login, add --headed so Chromium launches visibly for interactive auth — the fetch picks up after sign-in.
Granular subcommands (when you want pieces, not the one-shot):
designmatch wire --app-dir . # app side only (no canonical)
designmatch fetch --app-dir . [--headed] # download URL bundle
designmatch port <ScreenName> --out src/screens/<ScreenName>.tsx
designmatch port-all --out-dir src/screens # port every screen found
designmatch action-types # print ACTION_TYPES starter
designmatch iterate Home --platform android --device emulator-5554Peer deps the app must have: @react-native-async-storage/async-storage, react-native-restart. Dev deps for the harness: playwright pixelmatch pngjs ssim.js sharp.
Build Path (port-first, mandatory)
Methodology: port the canonical source, do NOT eyeball pixels. The HTML/JSX in the canonical bundle is the spec; the PNG is the verification gate. Eyeballing pixels re-derives layout / spacing / colors that already exist in the source — drift, token bloat, the multi-commit grind documented in the anti-patterns reference.
Per-screen flow:
- Port —
designmatch port <ScreenName> --out src/screens/<ScreenName>.tsxEmits the canonical JSX preceded by a TRANSLATION GUIDE (web → RN idiom map). Optional--guide-onlyprints just the guide;--no-guideskips it. - Translate — apply the guide top-down:
<div>→<View>;<span>/<p>/<h*>→<Text>;<img>→<Image>;<button>→<Pressable>className/ Tailwind →StyleSheet.create()- All px literals →
normalize(n) fontWeighton bold-family text →Platform.OSgate (anti-pattern #2)<svg>→react-native-svgprimitives- Keep variable names + structure identical to canonical.
- Verify —
designmatch iterate <ScreenName> --platform android --device <id>Renders canonical, captures device, diffs, opens composite. Pass when SSIM ≥ 0.95 OR pixel-diff ≤ 5%. - Refine — only adjust translation deltas the diff surfaces. Do NOT freelance pixel adjustments.
If the canonical is registered as a URL (not yet downloaded), designmatch port and designmatch port-all auto-run designmatch fetch first — transparent. Add --headed if the URL is behind login. To skip auto-fetch on init, pass --no-fetch.
Why mandatory: rebuilding by eyeballing PNGs is anti-pattern #9. PNGs are the gate, not the build input.
Architecture
canonical HTML ──Playwright──► canonical.png ─┐
├──diff──► metrics.json + composite.png ──► iterate
device (adb/xcrun) ──capture──► native.png ───┘Canonical viewport locked to 1080×2444. Orientation locked. State seeded via window.__VQA_STATE__ before bundle eval.
Layout
skills/designmatch/
├── SKILL.md
├── scripts/
│ ├── render-canonical.js # Playwright renderer
│ ├── visual-diff.js # pixelmatch + ssim.js + composite
│ └── iterate-screen.sh # per-screen loop
├── assets/
│ └── visual-qa.ts # RN VQA stub helper (drop-in)
└── references/
├── anti-patterns.md # 9-item checklist (incl. port-first rule)
└── canonical-values.md # typography + spacing cheat-sheetVQA Stub Mode (assets/visual-qa.ts)
Trigger: 5× long-press AppLogo within 4s → flip AsyncStorage dm_visual_qa → RNRestart.restart().
Boot path: primeVisualQaFlag() reads AsyncStorage → applyVisualQaState(dispatch) seeds:
- user:
{ onboarded: true, verified: true, locale: 'en-US', name: 'Visual QA' } - items: 1 saved
{ id: 'vqa-1', title: 'Test Item', category: 'sample', detail: 'vqa-detail' }+setSelectedItem('vqa-1') - wallet:
{ balance: 1000, ledger: [], applyCapPct: 50 } - session:
{ region: 'primary', channel: 'default' }
overrideFeatureFlags(isFeatureEnabled) → force-enables flag-gated UI when VQA on.
Visible indicator: red "VQA" pill badge top-right (safe-area inset).
Peer deps (consumer): react, react-native, @react-native-async-storage/async-storage.
Optional injected dep: react-native-restart (passed to toggleVqaAndRestart).
Renderer (scripts/render-canonical.js)
Node + Playwright (chromium). Viewport { width: 1080, height: 2444, deviceScaleFactor: 1 }.
Inject window.__VQA_STATE__ via page.addInitScript() BEFORE bundle eval → redux seeds from it. Optional window.__VQA_SCREEN__ for routing.
Serve bundle dir via local HTTP (pure node http + fs) → no extra deps.
Wait strategy:
--wait <ms>→ timeout--wait <selector>→waitForSelector- default → poll
window.__APP_READY__truthy
Full-page screenshot 1080×2444 → --out canonical.png.
node render-canonical.js --html <App.html> --state <state.json> --out <canonical.png> [--screen <Name>] [--wait <ms|selector>]Exit 0 + {"ok":true,...} stdout on success. Nonzero + error JSON on failure.
Diff Harness (scripts/visual-diff.js)
Dual metric:
- pixelmatch → perceptual pixel diff (catches color/position drift)
- ssim.js → structural similarity (catches structural matches under accumulated minor pixel diffs)
Resize-to-match via sharp if PNG sizes differ (document in top-of-file comment).
Outputs:
diff.png— pixelmatch overlaycomposite.png— 3-up horizontal: canonical | native | diff, 2px black separatorsmetrics.json—{ ssim, pixelDiffCount, totalPixels, pixelDiffPct, width, height, canonical, native, timestamp }
Stdout (terse): SSIM 0.823 | diff 4.2% | composite: <path>.
Pass: SSIM ≥ 0.95 OR pixelDiffPct ≤ 5 → exit 0. Else exit 1.
node visual-diff.js --canonical <c.png> --native <n.png> --out-dir <dir> [--threshold 0.1]Iteration Loop (scripts/iterate-screen.sh)
iterate-screen.sh <ScreenName> [--platform android|ios] [--device <id>] [--bundle <App.html>] [--state <state.json>] [--out <dir>]Defaults: OUT_DIR=./.designmatch/<ScreenName>. BUNDLE_HTML / VQA_STATE from env.
Steps:
mkdir -p $OUT_DIR- render canonical →
$OUT_DIR/canonical.png - capture native:
- android →
adb -s <id> exec-out screencap -p > $OUT_DIR/native.png(exec-outavoids CRLF mangling) - ios sim →
xcrun simctl io <id> screenshot $OUT_DIR/native.png - ios real → fall back to
idevicescreenshot $OUT_DIR/native.pngif on PATH
- android →
- diff via
visual-diff.js→ capture exit code as PASS/FAIL open(mac) /xdg-open(linux) the composite- print metrics + pass/fail + next-step hint
set -euo pipefail. Validate node, adb/xcrun per platform. Clear error messages.
Patterns to Enforce
- Typography Platform gate:
Platform.OS === 'android' ? {} : { fontWeight: 'N' }— keeps Android on family-name bold (e.g.Bricolage-Bold) instead of synthesized weight that drifts from canonical. normalize()always-on wrapper for px values (width-relative RN scaler, base 414).- Default Typography variant resolves to body family → headings MUST specify display variant.
- Single safe-area: Screen wrapper OR inner container, never both.
- Header choice: native stack header OR in-screen PushHeader, never both.
- Custom
tabBarStylewhen expand-to-label animation is needed.
See references/anti-patterns.md for the 8-item ❌/✅ checklist.
See references/canonical-values.md for fonts + spacing cheat-sheet.
Agent Dispatch Guidance
- One agent per file or per logical concern → avoid file-overlap conflicts.
- Each agent commits independently (
--no-verifyif hooks broken in worktree). - Avoid worktree isolation when agents need full
node_modules+ live device. - Explicitly send each agent the
Platform.OSpattern — don't assume inference from canonical jsx. - Explicitly tell each agent to use
normalize()wrapping — they otherwise inline literal px and bloat the diff.
Scoring & Success
- Report SSIM (0–1) + pixel-diff % per screen.
- Target: SSIM ≥ 0.95 OR pixel-diff ≤ 5%.
- Baseline (a real screen, pre-loop): 35% → 55% over ~30 eyeballed commits.
Quick Reference
| Concern | Answer |
| Canonical viewport | 1080×2444, deviceScaleFactor 1 |
| Pass gate | SSIM ≥ 0.95 OR pixelDiff ≤ 5% |
| State injection | window.__VQA_STATE__ via addInitScript |
| Android capture | adb -s <id> exec-out screencap -p |
| iOS sim capture | xcrun simctl io <id> screenshot |
| iOS real capture | idevicescreenshot |
| VQA toggle | 5× long-press AppLogo in 4s |
| Storage key | dm_visual_qa |
| Composite layout | canonical | native | diff (2px black sep) |
Common Mistakes
- Skipping
Platform.OSgate on fontWeight → Android synthesizes bold → diff bloats. - Inlining literal px instead of
normalize()→ fails on non-base-414 devices. - Double safe-area (Screen + inner) → top padding doubles → header position drifts.
- Native stack header + in-screen PushHeader → two headers stack.
- Forgetting
exec-outon adb → CRLF mangles PNG → unreadable native.png. - Injecting
__VQA_STATE__after bundle eval → redux already booted → seed ignored.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

