전 화면 Figma 일치 작업
여러 화면을 한꺼번에 맞추는 작업에서는 화면 일부의 개선이나 캡처 성공을 전체 완료로 보고하지 않는다. 아래 절차는 비교 수단을 바꾸는 규칙이 아니라, 구현·데이터·실행 검증의 누락을 막는 작업 관리 규칙이다. 비교 판정은 `visual-verify`의 기존 게이트가 소유한다.
여러 화면을 한꺼번에 맞추는 작업에서는 화면 일부의 개선이나 캡처 성공을 전체 완료로 보고하지 않는다. 아래 절차는 비교 수단을 바꾸는 규칙이 아니라, 구현·데이터·실행 검증의 누락을 막는 작업 관리 규칙이다. 비교 판정은
visual-verify의 기존 게이트가 소유한다.
상태 목록부터 고정한다#
- 원본의 모든 화면·상태를 읽어 원본 파일/노드, 시나리오, 장치 크기, 테마, 로케일, 글자 배율을 기록한다. 정상 화면뿐 아니라 로딩·빈 화면·오류·선택·완료·시트·다이얼로그를 포함한다.
-
미구현,미비교,차이 있음,일치,디자인 보류,실행 미검증을 구분한다. 목록에 없는 상태를 조용히 빼거나, 보류를 완료에 합산하지 않는다. - 화면 단위가 아닌 상태 단위로 남은 작업을 추적한다. 새로운 요구는 기존 목록에 추가하며, 다음 메시지가 왔다고 앞선 범위를 버리지 않는다.
샘플 데이터와 원본을 분리한다#
- 디자이너 원본을 보존하라는 요청이 있으면, 이미 선택된 조직/공간에 별도 편집 가능한 검증 파일을 만든다. 원본 파일에 임시 복제본을 만드는 절차보다 사용자 지정 보존 범위가 우선한다.
- 앱 mock, Widgetbook, 골든, 검증용 Figma는 하나의 co_faker fixture를 사용한다. seed·고정 시각·로케일·schemaVersion과 필드별 파생 키를 기록한다. 이름만 맞추지 말고 날짜·통화 소수 단위·금액·건수·선택 상태·문장도 맞춘다.
- Figma 텍스트 갱신은 fixture에서 생성한 노드 ID→값 매핑으로 반복 가능하게 만든다. 디자이너가 입력한 샘플을 앱에 별도로 하드코딩하지 않는다.
- 원본 노드→검증 사본 노드 대응과 각각의 PNG 출처를 보존한다. 원본/사본 export를 렌더 이미지로 덮어쓰지 않는다.
- 검증 사본은 샘플 데이터 외의 시각 속성도 원본과 같은지 확인한다. 복사 과정에서 paint opacity가 1로 변하면 반투명 Dim이 검게 덮이거나 투명 Hotspot이 흰 사각형으로 나타날 수 있다. 노드 전체 opacity와 각 fill의 opacity를 따로 읽고, 원본 화면→사본 화면 및 자식 경로·이름·타입이 일치하는 노드만 복구한다. 구조가 다르면 추측하지 않고 다시 조사한다.
- 사본 복구는 원본을 읽기 전용으로 유지한 채 수행한다. 검사한 노드 수, 변경 ID, 원본 값, 재검사 불일치 수를 기록한다. 영향을 받은 Figma PNG를 다시 내보내고 해당 골든을 재비교한다. 검은 배경이나 흰 영역을 앱 색상 수정으로 따라 만들지 않는다.
- mock 데이터를 운영 경로에 주입하지 않는다. 실제 서버 검증 계정의 값이 골든과 다르면 서버 값과 fixture 값을 구별하여 기록한다.
- 실제 정책과 시안 문구가 다르면 승인된 검증 사본의 범위 안에서만 정합화한다. 변경 노드의 전후 문구와 출처를 기록하고 스타일·레이아웃은 보존한다. 샘플/정책 정합화 직후의 차이율을 먼저 저장한 뒤 UI를 수정하여, 참조 변경으로 줄어든 차이와 구현 수정으로 줄어든 차이를 구분한다.
- 지도 샘플은 배경 타일과 UI를 분리한다. 동일 fixture의 좌표·카메라를 앱 mock과 검증 사본에 사용하고, 공유 배경에는 버튼·핀·문구·앱 스크린샷을 넣지 않는다. 실제 지도 제공자와 저작권 표시를 테스트용 샘플로 운영 경로에서 대체하지 않는다.
화면을 컴포넌트와 동작으로 분해한다#
픽셀 비교 전에 Figma 원본의 인스턴스·계층·슬롯을 실제 위젯과 대응시킨다. 표시 이름만 신뢰하지 말고 인스턴스의 main component·component set·variant 속성까지 읽는다. 이름이 Banner여도 원본 컴포넌트는 Alert일 수 있다. 각 행에
Figma 노드 → CoUI 컴포넌트 → 앱 조합/스타일 슬롯 → 상태와 동작 → 검증 증거를 적는다. 캐러셀을 큰 카드 한 장과 숫자 버튼으로 대체하는 식의 구조 누락은 색상·패딩 조절로 해결하지 않는다.
- 캐러셀은 카드 폭·간격·다음 카드 노출·미디어/제목/본문 슬롯·클리핑·활성 표시를 함께 읽는다. 드래그 후 정지한 카드와 활성 표시·콜백 값이 같은 인덱스인지 검사한다. 움직이는 도중의 캡처만으로 동작을 승인하지 않는다.
- 제목·설명·주의 문구를 역할에 맞는 컴포넌트와 슬롯에 넣는다. 시안이 경고 Banner라면 일반 텍스트 단락으로 대체하지 말고 배경·전경·행간·패딩과 함께 조합한다. 큰 글자와 다른 언어에서도 내용을 잘라서 수치를 맞추지 않는다.
- 컴포넌트가 존재한다는 사실과 요구 동작을 지원한다는 사실은 다르다. 예를 들어 활성 점의 너비가 다른 인디케이터를 기본 원형 점으로 구현하지 않는다. 현재 고정 커밋의 스타일 슬롯을 확인하고, 지원하지 않으면 제품 조합 또는 별도 CoUI 확장으로 책임을 명확히 한다.
- 앱에서 정상 API를 사용해도 카드·선택 표시가 엇갈리면 라이브러리 상태 계산을 확인한다. 최소 재현과 회귀 테스트를 CoUI 저장소에 남기고 양 플랫폼의 계약을 검증한다. 앱의 임의 인덱스 보정으로 결함을 가리지 않는다. 수정한 CoUI를 머지·동기화한 뒤 소비 앱 테스트와 실제 실행을 다시 검증한다.
세부 요소를 측정하고 고칠 위치를 찾는다#
배치가 비슷해 보여도 완료가 아니다. 꼬리·모서리·선택 표시·아이콘·아바타·글자 행간·내부 패딩·시트의 배경까지 해당 노드의 수치와 경로로 확인한다. 사양을 붙인 co_golden 캡처면 차이 항목이 그 수치(반경·패딩·행간·간격·굵기)와 노드 경로, 고칠 소스 줄을 준다 — 픽셀 영역을 추측하기 전에 먼저 읽는다.
채팅 말풍선이라면 다음을 함께 읽는다:
- 수신/발신 각각의 꼬리 존재 여부, 어느 모서리에 연결되는지, 꼬리 프레임 크기와 벡터 경로
- 본체와 꼬리가 레이아웃에서 차지하는 폭, 음수 오프셋으로 그려지는 부분, 부모의 clip 설정
- 네 모서리 반경, 패딩, 본문 역할·행간, 아바타 크기/정렬, 시각의 위치와 표시 조건
픽셀 차이 영역을 스타일 한두 값으로 추측해 덮지 않는다. Figma는 대화방 위 시트인데 앱은 전체 페이지라면, 배경색 조정보다 라우트·오버레이 구조를 먼저 바로잡는다. 그 구조 변경은 뒤로 가기·키보드·작은 화면·중복 요청 동작도 검증한다.
CoUI와 앱을 함께 개선한다#
- 저장소의 고정 SDK와 동기화 명령을 따른다. 최신 CoUI가 요구되면 원격 main을 한 번 해석하여 core/flutter/web와 독립 pub 루트를 같은 불변 커밋으로 고정한다. lockfile 변경을 검토한다.
- 실제로 해석된 커밋의 Contract→Style→Theme→Resolver→Widget을 확인한다. 스킬 문서의 옛 API를 현재 API라고 가정하지 않는다.
-
먼저 semantic prop과 스타일 슬롯, 기존 컴포넌트 조합으로 해결한다.
tail: false처럼 앱에서 기능을 끈 경우 라이브러리에 새 구현을 추가하지 않는다. 꼬리처럼 본체 밖에 그리는 요소는 호출자의 여백도 확인한다. - 실제 기능 부족이면 CoUI 저장소에 최소 재현과 회귀 테스트를 만들고, core 계약·Flutter·Web·문서/Widgetbook을 함께 고친다. 선택적 스타일 확장은 기존 기본값을 보존한다. 사용자가 최신 기본값 변경을 승인한 범위는 따르되 다른 소비자의 회귀도 검사한다.
- pub-cache 수정, 앱에 CoUI 구현 복사, 배포 manifest의 커밋 위조로 해결하지 않는다. 별도 CoUI 변경을 검증·리뷰하고, 소비 앱에는 사용할 불변 커밋을 반영해 다시 캡처한다. 배포에 main 일치 게이트가 있으면 머지 후 동기화·빌드·검증한다.
co_golden의 component 차이 항목 중 catalog(시안의 컴포넌트가 앱이 쓰는 CoUI 릴리스의 카탈로그에 없다)는 CoUI 확장 후보다. 기존 컴포넌트로 표현되면(이름·축 철자만 다르다) 캡처 테스트의
GoldenDesignSystem(components: [GoldenComponentMapping(…)])으로 짝을 적고, 아니면 위 4번 절차로 CoUI에 더한다. 시안은 CoUI 컴포넌트인데 앱이 손으로 그렸다는
component 항목은 3번(기존 컴포넌트 조합)으로 바꾼다.
CoUI Drawer의 딤은 barrierColor 알파와 barrierOpacity를 함께 해석한다. Figma가 50% 검정이면 고정 커밋의 resolver를 확인하고
barrierOpacity: 0.5처럼 의미에 맞는 슬롯을 사용한다. 반투명 색만 넣으면 기본 opacity가 추가로 곱해질 수 있다. RGBA 완전 일치 차이가 넓게 나올 때는 한 채널 1단계 반올림 차이와 실제 배치/내용 차이를 나눠 기록하되, 임계값 변경이나 원본 덮어쓰기로 통과시키지 않는다.
렌더와 실제 실행은 각각 증거를 남긴다#
-
Figma PNG와
design-spec.figma.js사양을 정확히 일치하는 장치/테마/로케일/글자 배율 시나리오의designReference(imagePath·specPath)로 연결한다. 사양이 있어야 서체·컴포넌트·위치의 차이 항목이 소스 줄과 함께 나온다. 연결할 수 없는 변형을 일치로 표시하지 않는다. - 모노레포의 기능 골든은 해당 pubspec/asset 번들을 소유한 패키지 디렉터리에서 실행한다. 루트에서 하위 테스트 경로만 지정하면 성공 로그가 나와도 SVG 로고·내비게이션 아이콘이 누락될 수 있다. 공통 출력은 절대 경로로 지정하고, 새 PNG에서 번들 에셋이 실제로 그려졌는지 확인한다. 기존 전체 갤러리를 지우는 캡처 래퍼를 부분 검증에 사용하지 않는다.
- 참조 화면과 실제 위젯의 상태가 같은지 먼저 확인한다. 예를 들어 미설정 상태의 fallback을 저장 완료 시안과 비교하지 않는다. 운영 경로가 사용하는 컴포넌트를 동일 상태로 연결하고 fallback은 별도 시나리오로 보존한다. 이 교정은 UI 개선으로 집계하지 않는다.
-
타일·이미지 디코딩은 위젯 테스트의 fake async 밖에서 완료시킨 뒤 같은 ImageProvider 캐시 키로 캡처한다.
pumpAndSettle이나 성공 로그만으로 이미지 로딩을 단정하지 말고, 새 PNG에서 회색 placeholder 대신 실제 fixture가 보이는지 검사한다. 데이터 로딩과 이미지 디코딩은 각각 확인한다. - 사용자 목표가 원시 RGBA 차이율이면 co_golden의 허용 오차 분석 결과와 별도로 그 지표를 유지한다. 두 판정의 이름·수치·임계값을 함께 기록하며 더 낮은 수치로 바꿔 완료를 선언하지 않는다. 색상 한 채널 차이와 텍스트 래스터 차이도 남은 차이에서 조용히 제외하지 않는다.
-
캡처 → 갤러리(
--design-report) → 차이 항목 읽기 → 수정 → 재캡처를 반복한다.font·frame항목을 먼저, 그다음error를 보고된 소스 줄에서 CoUI 부품·토큰·텍스트 역할로 고친다. 차이 항목이 설명하지 못하는 곳만 overlay/split/허용 오차 지도로 본다. 알려진 차이는GoldenKnownDifference(issue: …)로 적고 앱에서 고치지 않는다. 캡처 수나All tests passed는 디자인 비교 통과도 골든 기준선 승인도 아니다. -
인터랙션 후 선택 색이 변하지 않으면
TickerMode/애니메이션 정지를 확인한다. 멈춘 ticker에서pumpAndSettle만 반복해도 전환은 완료되지 않는다. 상태를 안정적으로 초기화하거나 필요한 전환만 완료한 뒤 결정적인 프레임을 캡처한다. -
팝업은 위젯 존재 검사와 캡처 가시성 검사를 구분한다.
Dialog가 트리에 있어도 캡처용 RepaintBoundary 바깥 Navigator에 열리거나 전환 opacity가 0에 멈추면 PNG에는 배경만 나온다. 실제 앱의 라우트 소유권을 유지하면서 캡처 경계 안에 해당 Navigator·Overlay를 포함하고, 필요한 전환을 완료한 후 PNG에 패널·딤·버튼이 실제로 보이는지 확인한다. 테스트를 맞추려고 운영 팝업을 가짜 Stack으로 대체하지 않는다. -
사용자가 실제 앱 실행을 요청했다면 렌더 테스트가 그 요청을 대체하지 않는다. 지정한 브라우저/기기로 최신 코드의 로그인·이동·입력·저장·재진입을 확인한다. 컴파일 성공,
No client connected, 라우트의 DOM 목록은 화면 검수 증거가 아니다. - 가로 카드 넘김은 실제 터치로 끝까지 확인한다. 웹의 마우스 드래그와 모바일 터치는 스크롤 정책이 다를 수 있다. 제스처 전달 직후의 중간 프레임을 완료로 보지 말고, 손을 뗀 뒤 카드 정렬·페이지 표시·선택에 따른 목록 필터를 확인한다. 브라우저 재시작/핫 재시작 뒤에는 실제 페이지가 최신 코드를 로드했는지도 확인한다.
- 오프라인 화면은 성공 응답을 받은 뒤 연결을 끊어 재조회하고, 읽기 전용 상태·실제 수신 시각·재연결 복구를 검증한다. 캐시는 사용자와 권한 범위에 묶고 로그아웃·계정 변경 시 비운다. 갱신되는 토큰 객체의 참조 동일성을 사용자 동일성으로 쓰지 않는다. 복구 버튼은 네트워크로 받는 아이콘이 없어도 식별할 수 있어야 한다.
- 서버 테스트 계정은 허용된 테스트 환경과 정상 인증 경로로 준비한다. 비밀번호·토큰은 Git 밖에 둔다. 서버가 차단한 capability를 UI에서 우회하거나 테스트 저장소의 성공을 서버 성공으로 보고하지 않는다.
- 브라우저 제어가 사용자에게 넘어가면 해당 브라우저 규칙을 따른다. 동시에 진행할 수 있는 코드·Figma·골든 작업은 계속한다. 이미 부여된 권한을 다시 묻지 말고, 새 확인이 꼭 필요한 경우 출처와 범위를 짧게 설명한다.
측정 규율#
수백 건의 일치 작업에서 같은 화면의 수치가 코드와 상관없이 움직인 이유는 거의 비교 조건이었다. 수치를 쓰기 전에 조건을 고정한다. 무엇부터 구현할지의 순서(구조 → 넓은 면의 색 → 쌓이는 오프셋 → 서체 미세 조정)는 첫 구현 점검표의 0절이 소유한다.
- 주 지표는 조건을 고정한 엄격 RGBA 불일치율 하나로 둔다. 장치 크기 · 배율 · 테마 · 로케일 · 글자 배율 · 시간대와 CoUI 커밋 · Flutter 버전을 적고, 마스크 · 리사이즈 · 허용 오차 없이 센다. co_golden 허용 오차 판정과 저장된 기준선 비교는 서로 다른 검증이다 — 이름 · 수치 · 임계를 따로 적고 합치지 않는다. 갤러리의 「디자인 차이만」 필터는 허용 오차 판정에 걸린 변형만 보여 주므로 엄격 실패가 필터 밖에 숨을 수 있다(엄격 2.9% 인데 허용 오차 오류는 0 이고 경고만 있는 화면). 엄격 수치는 요약 JSON 에서 따로 센다.
- 바닥값을 알고 읽는다. OS 상태바 띠(402×874 에서 약 800px, 0.23%p — Flutter 위젯 캡처에는 상태 줄이 없다), 글자 가장자리 안티앨리어싱, HUG 의 소수 폭, 반투명 합성의 1-LSB 차이. 채널 하나가 3쯤 다른 색도 면이 넓으면 5% 를 넘긴다. 임계에 닿은 것은 요소가 적은 상태 화면뿐이었고, 본문 위주 화면은 1.9 ~ 2.1% 에 모였다. 잔차는 영역(Y 구간)별로 쪼개 귀속한 뒤 PR 에 쓰고, 격리하지 못한 원인(글자 래스터화)은 추정이라고 적는다. 상태바를 마스크로 빼거나 가짜 상태바를 그려 수치를 만들지 않는다.
- 전후 비교는 같은 핀에서만 한다. CoUI 커밋 · Flutter 버전 · 시간대 · 참조 PNG 가 하나라도 다르면 점수가 움직인다 — 같은 렌더가 핀만 바뀌어 4.29% → 4.46%, 오래된 브랜치의 측정이 최신 main 과 1.3 ~ 3.2%p 어긋난 일이 있었다. 고치기 전의 같은 HEAD 에서 전 수치를 먼저 재고, 핀이 바뀐 효과는 이전 핀으로 되돌린 통제 캡처로 따로 적는다. CoUI 후보 PR 은 머지 전에 앱에 얹어 잰다.
- 참조 PNG 는 노드 ID · SHA-256 · 내보낸 시점으로 고정한다. 재내보내기가 PNG 를 조용히 바꾼다(같은 코드가 8.2% → 2.5%, 내비게이션이 16px 이동). 신 · 구 원본을 해시와 함께 두고 참조를 바꾼 효과와 코드를 바꾼 효과를 나눠 적는다. 참조 해제 · 상태 정합 · 새 비교의 추가는 개선으로 세지 않는다.
- 진행은 같은 키의 전후 델타로 센다. 비교가 175 → 188개로 늘어도 임계 미만은 25개 그대로였다. 새로 연결한 비교 수 · 통과 수 · 개선/동일/악화 수를 따로 적는다.
- 올바른 수정이 수치를 올릴 수 있다. 시안의 상태에 맞춰 fixture 를 고치거나, 숨은 요소를 복원하거나, 정책에 맞춰 문구를 바꾸면 수치가 오른다. 정확성은 해석된 스타일 · 치수 단언(수정 전 실패 → 후 통과)과 영역 지표로 보이고, 명세에 맞는 값을 점수 때문에 되돌리지 않는다. 기능 정합(서버가 준 값 복원, 접근성)은 엄격 지표와 따로 집계한다.
- 기각한 후보와 수치를 남긴다. 메타데이터에 맞춘 간격 · 줄 높이 후보의 상당수가 전체를 악화시켜 되돌려졌다. 후보 · 전후 수치 · 되돌린 이유를 적어 같은 실험을 되풀이하지 않는다.
- 주장의 등급을 구분해 적는다. 측정(같은 핀의 전후 수치) · 캡처(렌더만) · 기능 테스트 · 실앱 실행 · 미검증. 막힌 검증은 막혔다고 적는다. 위치 권한 대화상자처럼 사람의 상호작용을 기다리는 검증은 권한 상태를 fixture 로 주입해 무인 경로를 만든다.
- 연결 범위를 감사한다. 디자인 참조가 연결된 변형이 전체 변형의 얼마인지 센다(4천여 변형 중 참조는 한 장치 · 라이트 · ko · 1배 갈래의 200여 장뿐이었다). 참조가 가리키는 장치 크기가 매트릭스에 없으면 그 변형은 조용히 비교되지 않는다 — 장치를 더하자 가려져 있던 12 ~ 15% 차이가 드러났다. 시안 노드 매니페스트로 분모를 먼저 정한다(한 파일에 이름이 같은 프레임 집합이 둘이었다). 연결하지 못한 변형은 「대조하지 않음」이고 통과가 아니다.
기준선 갱신#
- 갱신하기 전에 고치기 전 코드의 렌더가 커밋된 기준선과 같은지 본다. 이미 다르면(구조 변경 뒤 낡은 기준선, 환경 차이) 그 차이를 의도한 변경과 섞지 않는다. 기준선이 기본 브랜치에서 이미 적색이면 갱신을 의도한 시각 변경과 같은 흐름에서 끝낸다 — 낡은 기준선은 이후 모든 PR 을 적색으로 만든다.
- 같은 렌더를 공유하는 자매 시나리오(원본 연결 · 확대 글자 · 키보드 · 다크)를 한 번에 갱신하고 마지막에 전수 비교를 한 번 돌린다. 이름 목록으로 고른 부분 갱신은 자매를 놓친다 — 한 갱신이 키보드 · 확대 변형 6 ~ 8장을 빠뜨려 다음 전수 비교에서 실패한 일이 세 번 있었다.
- 로컬에서 만든 이미지를 CI 기준선으로 쓰지 않는다. 같은 OS · 아키텍처여도 기준은 프로젝트가 정한 CI 러너가 만든 것만이다. 정확한 시나리오만 갱신하고 기존 추적 PNG 만 바꾸는 부분 갱신은 부분 검증이다 — 그 커밋의 전체 비교 통과가 따로 필요하다.
- 시안에는 가까워졌는데 기준선과의 거리는 커질 수 있다(기준선은 옛 구현의 사진이다). 기준선 비교 통과와 Figma 일치는 다른 말이다.
- 진단 · 실험 캡처는 제품 골든 디렉터리 밖에 두고 명시 등록한 것만 돌린다. 기준 이미지가 없는 시나리오가 기본 비교를 실패시킨다. 인프라가 실패한 실행에서 기준선을 다시 만들지 않는다.
- 골든 이미지가 쌓이는 비용은 매트릭스를 늘리기 전에 정한다. 변형 수는 로케일 × 기기 × 테마 × 배율이다. 추적 이미지가 수천 장이면 체크아웃이 CI 의 고정비를 지배한다(sparse 체크아웃 · 저장 방식을 먼저 정한다).
중간 커밋과 완료 보고#
- 검증된 변경을 기능 단위로 자주 커밋한다. 다른 작업자의 변경과 비밀 파일, 임시 브라우저 출력은 포함하지 않는다. 큰 작업 트리에서는 명시적 경로로 stage하고 staged diff를 검토한다.
- 앱·CoUI·skills 변경은 저장소별 커밋/PR로 연결하고, 검사한 커밋과 실제 소비 커밋을 기록한다. 새 코드를 고친 뒤 이전 실행 캡처를 최신 증거로 재사용하지 않는다.
- 공통 테마 변경으로 기준선 차이가 많으면 변형별 목록과 원인을 남기고 테마·장치·글자 확대를 포함한 대표 이미지를 검토한다. 잘린 버튼 문구나 가려진 지도 출처처럼 새로 발견한 회귀를 먼저 고친다. 공식 기준선 갱신 뒤에는 봇 커밋이 승인된 이미지 변경만 포함하는지 확인하고, 그 커밋의 compare 통과를 기다린다. 기준선 갱신 성공을 Figma 일치로 보고하지 않는다.
-
완료 보고에는 전체 상태 수, 일치/차이/미비교/보류 수, 최신 실제 실행 범위, 남은 의존성을 적는다. 차이가 있는 상태는 co_golden 분석 판정과 남은
error차이 항목 수를 함께 적는다. 남은 차이를 기록한 것만으로 승인된 예외가 되지 않는다. - 불가능한 외부 조건과 미구현 작업을 구분한다. 브라우저 문제 하나를 이유로 독립적으로 수행할 수 있는 구현까지 종료하지 않는다. 실제로 일치하지 않은 상태를 완료라고 말하거나, 턴 종료 후 작업이 계속되는 것처럼 보고하지 않는다.