Figma 대조 — 기기 · 원본 연결 · 엄격 차이 보고

목표는 화면마다 엄격 픽셀 차이 3% 미만이다(Figma 「Cocode Scaffold App v1.0」 페이지 49:4). 이 문서는 그 차이를 어떤 기기로 찍고, 무엇을 가리고, 어떻게 세고, 어디서 판정하는지를 정한다. 판정 규칙의 정본은 figma-fidelity.json 하나다 — 기준 · 세는 법을 바꾸는 것은 그 파일을 바꾸는 PR 이다.

구성 자리 하는 일
화면 대장 docs/product/figma-screens.json Figma 프레임 79개 — 노드 · 크기 · 기기 · 원본 PNG · insets(띠)
원본 PNG docs/design/figma/reference/<키>.png Figma 에서 1배율로 내보낸 정본. 구현 캡처로 덮어쓰지 않는다
Figma 기기 FigmaDevices (package:test_driver/golden.dart) 폰 · 태블릿 · 데스크톱 — DPR 1
대장 → 골든 FigmaLedger · FigmaScreen 키 하나 → 기기 · co_golden 원본(GoldenDesignReference)
화면 등록 FigmaGoldenScreens capture 전용 시나리오 + 대장 연결 기록
수락 보고 tool/design/check_visual_acceptance.dart 화면마다 엄격 차이를 다시 세어 판정 → build/visual-acceptance.json
판정 정책 docs/product/figma-fidelity.json 기준 3%(미만) · 세는 법 · Δ 의 N · 글자 밖 판정(선택 textMask)
CI .github/workflows/golden.yml 의 Figma 대조 (수락 보고) 요약 · 아티팩트 — 지금은 보고만

기기

Figma 프레임 그대로 찍는다. 원본 PNG 가 1배율이므로 기기도 DPR 1 이다 — 캡처와 원본이 같은 픽셀 격자에 놓여야 co_golden 이 견준다(크기가 다르면 sizeMismatch 로 견주지 않는다).

기기 크기 플랫폼 기본 안전 영역
FigmaDevices.phone (figma-phone) 390×844 iOS 위 44 · 아래 34
FigmaDevices.tablet (figma-tablet) 1024×768 iOS(가로) 위 44 · 아래 34
FigmaDevices.desktop (figma-desktop) 1440×900 macOS(웹 화면) 없음

가림

가리는 것은 띠 둘뿐이다 — 위 상태 바(insets.top)와 아래 홈 인디케이터(insets.bottom). 둘 다 기기(OS)가 그리고 앱은 그리지 않는다. 띠마다 사유가 기록된다(상태 바 띠(위 44px) — 기기(OS)가 그리고 앱은 그리지 않는다).

가림 예외

화면 영역(논리 px) 사유 이슈
(없음)

세는 법

분모는 띠 밖의 픽셀(comparable = 전체 − 띠)이다. 두 이미지를 8비트 RGBA 로 풀어 픽셀마다 견준다.

이름 「다르다」로 세는 픽셀 보고 키
exact RGBA 네 채널 중 하나라도 다르다 — 엄격(정확) 차이 exact
Δ≤N 무시 네 채널 차이의 최댓값이 N 을 넘는다(N 이하는 세지 않는다) — 기본 N = 16 delta

글자 밖 — 글자 잉크 상자를 빼고 세기

같은 글자를 같은 자리에 그려도 두 렌더러가 가장자리를 다르게 칠해, 글자 상자(+2 px) 픽셀의 26–32% 가 Δ>8 로 남는다 (아래 「글자 바닥값」). 그래서 글자는 구조로 엄격하게, 픽셀은 글자 밖에서 센다. 오너가 2026-11-02 에 판정에 쓰기로 했다 — figma-fidelity.json 의 "textMask": {"padding": 2}.

판정

상태 뜻
accepted ✅ 정책의 세는 법으로 기준 미만
above-threshold ⛔ 기준 이상
unverified ⚠️ 판정 못 함 — 캡처 실패 · 넘침 · 크기 불일치 · 원본 · 대장 없음 · 대장 연결 기록 없는 디자인 대조 · 검산 실패 · 글자 밖으로 판정하는데 사양을 읽지 못함. 통과가 아니다
stale 🕰 낡음 — 캡처 뒤 원본 PNG 나 대장 항목(노드 · 크기 · 띠 …)이 바뀌었거나 --since 보다 이른 캡처. 다시 찍는다

비교는 대장 연결 기록(build/co_golden/figma/**)마다 한 줄이다. 기록은 그리기 전에 남으므로 그리다 실패한 화면도 보고에서 사라지지 않는다. coverage 는 대장 화면 중 몇 개를 견줬는지와 빠진 키를 보인다(대장이 없으면 세지 않는다). 수락은 비교마다의 지표다 — 커버리지 · 실제 앱 동작 · 디자인 승인은 따로 본다.

화면 등록

화면 패키지의 test/golden/ 에서 대장 키로 등록한다. 원본과 같은 상태(같은 데이터 · 선택 · 스크롤)를 만들어 찍는다.

import 'package:test_driver/golden.dart';

final FigmaGoldenScreens figma = FigmaGoldenScreens(suite: 'booking');

void main() {
  figma.screen('SH-01@390', build: (_) => const HomePage());
  figma.screen('SH-03+SH-04@1440', build: (_) => const ServiceDetailPage(), interact: …);
}

로컬 실행

# 1) 캡처 — 모든 골든 패키지(Figma 대조 포함) → build/co_golden
tool/golden/capture.sh
#    Figma 대조만, 한 패키지에서(출력을 저장소 build/co_golden 으로):
(cd <패키지> && TZ=Asia/Seoul CO_GOLDEN_MODE=capture \
  CO_GOLDEN_OUTPUT="$(git rev-parse --show-toplevel)/build/co_golden" \
  flutter test --no-pub --tags figma test/golden)

# 2) 수락 보고 — 보고만(종료 코드 0) · Markdown 요약까지
tool/design/check_visual_acceptance.sh --report-only --summary build/visual-acceptance.md
#    판정으로(수락이 아닌 비교가 있으면 1): --report-only 를 뺀다
#    보정 탐색 — 정책은 그대로 두고 보고에 덮어쓰기로 남긴다
tool/design/check_visual_acceptance.sh --report-only --count delta --delta 8
#    글자 밖으로 판정(정책에 textMask 가 있으면 이미 켜져 있다 — 「세는 법」의 「글자 밖」)
tool/design/check_visual_acceptance.sh --report-only --count delta --delta 8 --text-mask

# 3) 갤러리
tool/golden/gallery.sh   # → build/golden-gallery/index.html

tool/design 은 별도 pub 루트다(PNG 를 푸는 image 의존 — 루트 워크스페이스 의존을 늘리지 않는다). 래퍼 check_visual_acceptance.sh 가 lock 그대로 받고 저장소 루트에서 돌린다. 도구 시험: (cd tool/design && dart test).

출력

build/co_golden/                        # capture.sh (co_golden)
├── images/<suite>/<scenario>/<변형>.png        캡처
├── references/<suite>/<scenario>/<변형>.png    원본 사본(바이트 그대로)
├── differences/<suite>/<scenario>/<변형>.png   co_golden 정확 차이(띠 안 포함, 자홍)
├── analysis/<suite>/<scenario>/<변형>.{json,png} co_golden 허용 오차 분석 · 지도
├── figma/<suite>/<scenario>/<변형>.json        대장 연결 기록(키 · 대장 · 띠) — FigmaGoldenScreens
└── runs/<suite>/<scenario>.json               실행 기록
build/visual-acceptance.json            # 수락 보고(정책 · 수 · 커버리지 · 비교마다 exact · delta · 글자 밖 · 분석)
build/visual-acceptance.md              # --summary
build/visual-acceptance/<suite>/<scenario>/<변형>.png  # 보정 지도
build/golden-gallery/index.html         # gallery.sh

보정 지도는 수락 보고가 쓴다 — 회색 같음 · 파랑 가린 띠 · 노랑 exact 로만 세는 픽셀(Δ≤N) · 자홍 두 규칙 모두 세는 픽셀(Δ>N) · 초록 글자 가림(사양이 있는 화면 — 진한 초록은 그 안의 Δ>N). 노랑이 글자 · 모서리 가장자리뿐이면 Δ 규칙이 래스터 차이만 빼는 것이다.

갤러리 읽는 법

gallery.sh 의 갤러리에서 Figma 대조가 있는 변형은 원본과 캡처를 함께 보인다.

CI

golden.yml 의 골든 (compare) 가 PR(준비 완료) · dispatch 에서 확인용 capture 를 돌린 뒤 출력을 아티팩트 co-golden-capture 로 올리고, Figma 대조 (수락 보고) 잡이 그것을 받아 도구 시험 → 수락 보고를 돌려 잡 요약과 아티팩트 visual-acceptance(JSON · Markdown · 보정 지도)로 남긴다.

보정 — SH-01 첫 측정

엄격 3% 를 무엇으로 셀지는 SH-01(홈)을 구현해 처음 잰 뒤 정했다(figma-fidelity.json 의 calibration). 세는 법은 오너가 확정했다(2026-11-02) — 「글자 밖 Δ≤8 3% 미만」(count: delta · maxChannelDelta: 8 · textMask.padding: 2, co_golden 구조 오류 0건 지향). exact · Δ≤8 전체는 참고로 계속 보고한다.

  1. SH-01@390 을 구현하고 FigmaGoldenScreens 로 등록한다. 원본과 같은 데이터 · 상태로 찍는다(브랜드 테마 #2 반영 뒤).
  2. 수락 보고에서 exact · Δ≤16 · co_golden 허용 오차(안티에일리어싱 픽셀 수)를 함께 읽는다.
  3. --count delta --delta N 으로 N = 0 · 4 · 8 · 16 · 32 의 비율을 표로 만든다. 보정 지도에서 노랑(Δ≤N 이라 빠지는 픽셀)이 글자 · 모서리 가장자리뿐인지 본다 — 레이아웃 · 색 · 크기 차이가 노랑에 숨으면 N 이 너무 크다.
  4. 기준선(Figma 끼리 exact 14.1% · Δ≤16 4.7%)과 견준다 — 구현이 아무리 맞아도 exact 로는 3% 아래로 내려가지 않을 수 있다는 근거다.
  5. 오너가 count · maxChannelDelta 를 정한다 → figma-fidelity.json 을 바꾸는 PR(calibration.status: decided, 측정 표 링크). 그다음 FIGMA_ACCEPTANCE_BLOCKING 을 "true" 로 바꿀지 정한다(아직 아니다 — Figma NavigationBar 수정 뒤).

첫 측정 결과 — SH-01 (2026-11-01, 로컬 Darwin/arm64 capture)

SH-01 세 크기를 feature/booking/app 의 test/golden/booking_home_figma_golden_test.dart 로 찍어 tool/design/check_visual_acceptance.sh --report-only --count delta --delta N 으로 잰 값이다(정책 파일은 바꾸지 않았다 — 판정은 아직 exact). 띠 밖 픽셀(분모)은 390 298,740 · 1024 706,560 · 1440 1,296,000.

크기 exact Δ≤4 Δ≤8 Δ≤16 Δ≤32 co_golden 허용 오차
SH-01@390 25.624% 23.540% 4.143% 3.079% 2.196% fail(11.5%)
SH-01@1024 31.249% 30.155% 4.642% 2.272% 1.648% fail(17.8%)
SH-01@1440 32.417% 31.420% 1.941% 1.330% 0.937% fail(19.3%)

원인별 분해(화면 비율 %, 다른 픽셀을 한 원인에만 넣는다 — 차례: 하단 탭 띠 → 사진 → 글자 → 그 밖의 CoUI 부품 → 나머지). 사진은 Figma 렌더와 캡처 사이의 채널별 선형 색 맞춤(이득 · 치우침)으로 사라지는 몫을 「필터」, 맞춘 뒤에도 남는 몫을 「나머지(리샘플링 · 반올림)」로 나눴다. 글자는 아래 「글자 바닥값」 실험의 같은 픽셀 차이를 「바닥값」, 그 밖을 「배치」로 나눴다.

원인 390 exact / Δ>8 / Δ>16 1024 exact / Δ>8 / Δ>16 1440 exact / Δ>8 / Δ>16
사진 — Figma 이미지 보정(필터) 5.17 / 0.33 / 0.02 12.24 / 0.36 / 0.02 15.08 / 0.33 / 0.02
사진 — 나머지(리샘플링 · ±1 반올림) 15.95 / 0.16 / 0.03 14.47 / 0.20 / 0.08 15.55 / 0.12 / 0.02
글자 — 바닥값(렌더러) 2.23 / 1.87 / 1.51 1.74 / 1.56 / 1.36 1.51 / 1.32 / 1.13
글자 — 배치(엔진 글자 폭 · 기준선) 0.02 / 0.04 / 0.06 0.07 / 0.10 / 0.11 0.07 / 0.08 / 0.10
CoUI NavigationBar 높이(아래 표) 1.64 / 1.61 / 1.40 2.24 / 2.23 / 0.63 —
그 밖의 CoUI 부품(아이콘 선 · 칩 폭 · 모서리) 0.31 / 0.12 / 0.06 0.44 / 0.18 / 0.07 0.20 / 0.08 / 0.05
나머지 0.30 / 0.01 / 0.00 0.04 / 0.02 / 0.01 0.02 / 0.01 / 0.01

글자 바닥값

(cd feature/booking/app && TZ=Asia/Seoul CO_GOLDEN_MODE=capture BOOKING_CALIBRATION=1 flutter test --no-pub test/calibration) 이 화면마다 Figma 원본 PNG 를 1:1 로 깔고, 사양의 글자 노드마다 글자 상자(+2px)를 그 자리 바탕색으로 지운 뒤 같은 서체 · 굵기 · 크기 · 자간 · 행간 · 색의 Flutter 글자를 Figma 상자 자리에 다시 그린다(build/calibration/<키>/floor_<dy>.json). 글자 밖 픽셀은 원본과 같다(1024 · 1440 은 0px, 390 은 Figma 가 하단 탭 아래로 자른 값 글자의 199px). 그래서 남는 차이는 배치 · 색 · 글꼴이 같은 상태에서 두 렌더러가 글자 가장자리를 다르게 칠해서 생긴다 — 글리프 모양 · 위치는 같고 Flutter 쪽이 부분 덮임 픽셀을 더 넓게 칠한다(1440 큰 제목: 바탕과 다른 픽셀 9,112 → 10,038).

크기 글자 상자 Δ>0 Δ>4 Δ>8 Δ>16 Δ>32 Δ>64
390 6.87% 2.68% (38.9%) 2.43% 2.23% (32.4%) 1.78% (25.9%) 1.21% 0.57%
1024 5.58% 1.84% (33.0%) 1.72% 1.63% (29.3%) 1.42% (25.4%) 1.04% 0.66%
1440 5.03% 1.51% (30.0%) 1.40% 1.32% (26.2%) 1.13% (22.4%) 0.78% 0.41%

화면 비율(괄호는 글자 상자 안 비율). 글자를 세로로 ±0.5 · ±1px 옮겨 노드마다 가장 맞는 자리를 골라도 Δ>16 이 1.03–1.46% 로 남는다 — 기준선 반올림이 아니라 래스터화 차이다.

그 밖에 잰 것

권고(결정은 오너)

파일럿

package/test_driver/test/golden/figma_pilot_golden_test.dart 는 SH-S1 스플래시의 구현이 아니다. 파이프라인 (캡처 → 원본 → 차이 → 분석 → 수락 보고)이 끝까지 도는지 보이는 자리표시이고, 이미지 안에도 「파일럿 자리표시」 라고 적혀 있다. 대장은 파일럿 픽스처(package/test_driver/test/fixtures/figma/figma-screens.pilot.json, 원본 PNG 는 2026-11-01 Figma 에서 받음)다. 처음 잰 값(2026-11-01, 로컬 Darwin/arm64): 띠 밖 312,000px 중 exact 97.703% · Δ≤16 무시 97.689% · co_golden 허용 오차 fail 95.668%(안티에일리어싱 4,359px) — 자리표시라 크게 다른 것이 정상이다. 정본 대장(#1)이 main 에 오고 스플래시를 구현하면 그 화면 패키지에서 SH-S1@390 을 등록하고 파일럿과 픽스처를 지운다.