LogoSkills

live-preview-match

Dual Webโ†”Flutter Web embed pixel matching for cocode-style live-preview pages. Detects when the Jaspr Web render and the Flutter Web render of the same CoUI component diverge. Works on production s...

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#

  1. Navigate to the page (Playwright).
  2. Find all variant cards: [data-co="preview-card"].
  3. 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-view data-flt-renderer ready).
    • Diff A.png vs B.png with anti-aliasing tolerance and font-rendering tolerance turned up.
  4. 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:

ClassTriggerAction
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 noiseBelow thresholds, no clusteringPass

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 regression instead.
  • Native Flutter on iOS/Android โ€” use cc-flutter-pixel-loop with 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).