Live Preview Match#
ํ๋ง๋๋ก (๋น๊ฐ๋ฐ์์ฉ)#
๊ฐ์ ๋์์ธ์ ๋ ๊ฐ์ง ๊ธฐ์ (Jaspr Web๊ณผ Flutter Web)๋ก ๊ฐ๊ฐ ๊ทธ๋ ค ๋๊ณ , ๋ ๊ทธ๋ฆผ์ด ์ ๋ง ๋๊ฐ์ด ๋ณด์ด๋์ง ์๋์ผ๋ก ๋น๊ตํด ์ฃผ๋ ๋๊ตฌ์ ๋๋ค. ๋ง์น "ํ๋ฆฐ ๊ทธ๋ฆผ ์ฐพ๊ธฐ" ๊ฒ์์ ์ปดํจํฐ๊ฐ ๋์ ํด ์ฃผ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ๊ฐ์ ๋ฒํผ์ธ๋ฐ ํ์ชฝ์ ์์ด ๋ค๋ฅด๊ฑฐ๋ ๊ธ์๊ฐ ์ด์ง ์ด๊ธ๋ ์์ผ๋ฉด ์๋์ผ๋ก ์ง์ด ์ค๋๋ค. ์ฌ๋ ๋์ผ๋ก ์ผ์ผ์ด ๋์กฐํ์ง ์์๋ ๋๋ ์๊ฐ์ ํฌ๊ฒ ์๋ ์ ์์ต๋๋ค.
๋ฌด์์ยท์ธ์ (๋น๊ฐ๋ฐ์์ฉ)#
- ๋ฌด์์ ํด ์ฃผ๋์: ํ ํ์ด์ง์ ๋๋ํ ๋์ธ ๋ ๊ฐ์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ๋ฉด(Web ์ชฝ๊ณผ Flutter ์ชฝ)์ ํ๋ฉด ์บก์ฒํด์, ์ยท๊ธ์ยท๊ฐ๊ฒฉ ๊ฐ์ ์ฐจ์ด๋ฅผ ์ฐพ์๋ด๊ณ ์นด๋๋ณ๋ก ํฉ๊ฒฉ/๋ฌธ์ ํ์ ์ ๋ด๋ ค ์ค๋๋ค.
- ์ธ์ ์๋ํ๋์:
coui.cocode.im/live-preview-poc๊ฐ์, ๊ฐ์ ๋์์ธ์ ๋ ๋ฐฉ์์ผ๋ก ๋์์ ๋ณด์ฌ ์ฃผ๋ ํ์ด์ง๋ฅผ ์ ๊ฒํ ๋ ์ผ์ง๋๋ค. - ์ด๋์ ๋๋์: ๊ฐ๋ฐ์ฉ ํน์ ๋๊ตฌ ์์ด๋ ์ค์ ๋ก ๋ฐฐํฌ๋(์ด์ ์ค์ธ) ์ฌ์ดํธ์์ ๊ทธ๋๋ก ๋์ํฉ๋๋ค.
- ์ด๋ค ์ ์ด ์ข๋์: ์ฐจ์ด๊ฐ ๋ฐ๊ฒฌ๋๋ฉด ๊ทธ ์ข ๋ฅ(๊ธ์์ฒด ๋ฌธ์ ยท์ ๋ฌธ์ ยท๋ฐฐ์น ๋ฌธ์ )๋ฅผ ๊ตฌ๋ถํด ์ฃผ๊ณ , ์๋ณธยท๋น๊ต๋ณธยท์ฐจ์ด ํ์๋ฅผ ํ๋์ ๋ณด์ฌ ์ฃผ๋ ์ด๋ฏธ์ง๋ฅผ ๋ง๋ค์ด ์ค๋๋ค.
ํต์ฌ ์ฉ์ด (๋น๊ฐ๋ฐ์์ฉ)#
| ์ฉ์ด | ์ฌ์ด ์ค๋ช |
|---|---|
| Jaspr Web / Flutter Web | ๊ฐ์ ํ๋ฉด์ ๊ทธ๋ ค ๋ด๋ ์๋ก ๋ค๋ฅธ ๋ ๊ฐ์ง ๊ธฐ์ . ์ด ๋์ด ๋๊ฐ์ด ๋ณด์ด๋์ง๊ฐ ๋น๊ต ๋์์ ๋๋ค. |
| CoUI | ๋ ๊ธฐ์ ์ด ๊ณตํต์ผ๋ก ์ฐ๋ ๋์์ธ ๋ฌถ์(๋ฒํผยท์นด๋ ๊ฐ์ ํ๋ฉด ๋ถํ ๋ชจ์). |
| pixel diff (ํฝ์ ๋น๊ต) | ๋ ๊ทธ๋ฆผ์ ์ ๋จ์๋ก ๋ง๋์ด ๋ณด๋ฉฐ ๋ค๋ฅธ ๊ณณ์ ์ฐพ์๋ด๋ ์์ . |
| tolerance (ํ์ฉ ์ค์ฐจ) | "์ด ์ ๋ ๋ฏธ์ธํ ์ฐจ์ด๋ ๊ฐ์ ๊ฒ์ผ๋ก ๋ณธ๋ค"๊ณ ์ ํด ๋๋ ๊ธฐ์ค. ์๊ฒฉํ๊ฒ(strict)ยท๋ณดํต(default)ยท๋์จํ๊ฒ(loose) ์กฐ์ ํ ์ ์์ต๋๋ค. |
| screenshot (์คํฌ๋ฆฐ์ท) | ํ๋ฉด์ ๊ทธ๋๋ก ์ฌ์ง์ฒ๋ผ ์ฐ์ด ๋ ์ด๋ฏธ์ง. ๋น๊ต์ ์ฌ๋ฃ๊ฐ ๋ฉ๋๋ค. |
| verdict (ํ์ ) | ์ฐจ์ด๋ฅผ ๋ถ์ํ ๋ค ๋ด๋ฆฌ๋ ๊ฒฐ๋ก (ํฉ๊ฒฉ์ธ์ง, ๋ฌธ์ ๊ฐ ์๋์ง). |
| regression (ํ๊ท ์ ๊ฒ) | ์์ ์ด ๋ค๋ฅธ ๋ ์บก์ฒ๋ฅผ ๋น๊ตํด "์์ ๊ณผ ๋ฌ๋ผ์ก๋์ง" ํ์ธํ๋ ๊ฒ. ์ด ๋๊ตฌ์ ๋น๊ต์๋ ๋ค๋ฅธ ์์ ์ ๋๋ค. |
| selector (์ ํ์) | ํ์ด์ง์์ ๋น๊ตํ ๋ ํจ๋์ด ์ด๋์ธ์ง ์ฝ ์ง์ด ์ฃผ๋ ํ์. |
CoUI's promise is "Flutter and Jaspr Web render the same design". The live-preview-poc page already shows them side-by-side; this skill turns that page into an automated proof.
Why this needs a dedicated skill#
The native pixel-loop (Maestro + Figma) doesn't help because both sides run in a browser. The Jaspr audit (/jaspr-web audit) doesn't help because it counts elements but doesn't compare two renders to each other. This skill is the missing piece:
same-page, two-panel pixel diff with awareness of Flutter Web's canvas quirks.
Page Topology Assumption#
โโ Variant Card โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โโโโโ Web panel โโโโโ โโโโโ Flutter panel โโโโโ โ
โ โ Jaspr-rendered โ โ Flutter Web canvas โ โ
โ โ < button class= โ โ < flutter-view > โ โ
โ โ " co-button โ โ ... โ โ
โ โ co-primary " > โ โ < /flutter-view > โ โ
โ โ Primary โ โ โ โ
โ โ < /button > โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
clip rect derived from clip rect derived from
[data-co= " preview-web " ] [data-co= " preview-flutter " ]
The skill assumes each variant card has two clearly marked panels reachable via two stable container selectors. cocode's live-preview emits exactly this structure based on the page's HTML โ see
commands/jaspr-web/match.md for the assumed catalogue.
How matching works#
- Navigate to the page (Playwright).
- Find all variant cards:
[data-co="preview-card"]. -
For each card:
getBoundingClientRect()for the Web panel container โ clip screenshot โA.png.getBoundingClientRect()for the Flutter panel container โ clip screenshot โB.png.- Wait for the Flutter panel to finish loading (no "Loading Flutter..." text, canvas mounted, first frame painted via
flutter-viewdata-flt-rendererready). - Diff
A.pngvsB.pngwith anti-aliasing tolerance and font-rendering tolerance turned up.
- Emit per-variant verdict + 3-up image (
A | B | diff).
Tolerance presets#
Flutter Web font-rendering and Jaspr DOM font-rendering will never be byte-identical. Defaults are tuned for "same design language" rather than "same bytes":
default:
pixel: 2
color_delta_e: 3.0 # CIEDE2000
ignore_aa: true # ignore anti-alias edge differences
mask_outside_intersection: true # only compare the overlapping rect
strict:
pixel: 1
color_delta_e: 2.0
ignore_aa: false
loose:
pixel: 4
color_delta_e: 5.0
ignore_aa: true
Pass with --tolerance-preset=strict (default default).
What counts as a real divergence#
The skill classifies diffs into:
| Class | Trigger | Action |
|---|---|---|
| typography | Glyph centroids shift > 1px or font weight differs | Open issue; this is usually a CSS variable miss on one side |
| color | color_delta_e > threshold over > 5% of the panel |
Open issue; usually a theme token diff |
| layout | Element bounding-box diff > pixel threshold | Open issue; usually a spacing scale diff |
| rendering noise | Below thresholds, no clustering | Pass |
The classifier writes a short LLM-style verdict per card to verdict.md.
Production readiness#
This skill works on the deployed production site. It does not require:
- VM Service.
- Source maps.
- Flutter Semantics being enabled.
It only requires that the Jaspr page marks the two panels with stable selectors. The default selectors are listed in
commands/jaspr-web/match.md and can be overridden via --selectors=<yaml>.
When not to use#
- A single render (Web-only or Flutter-only) โ use
/jaspr-web regressioninstead. - Native Flutter on iOS/Android โ use
cc-flutter-pixel-loopwith Maestro. - Cross-screenshot comparison across two different runs/timestamps โ that's regression, not match.
Cross-references#
- Sibling skill:
cc-jaspr-web:jaspr-web - Native counterpart:
cc-flutter-pixel-loop:pixel-loop - Article reference: Pixel-Perfect Flutter UIs with Figma, Maestro, and Dart MCPs โ Very Good Ventures, May 2026 (informs tolerance defaults).