agentsclimarketplace

Design qa

Skill chussum/cat-harness/skills/design-qa

디자인 QA에서 "Figma랑 다르다/간격 다르다/픽셀 안 맞는다" 지적을 받았을 때 사용. Figma 캡쳐와 구현 렌더를 동일 크기로 겹쳐(overlay) 육안 대조하고, 잉크(ink) 픽셀 측정으로 오프셋을 수치화해 일치할 때까지 수정 루프를 돈다. Figma 노드 좌표로 스펙을 실측하고, 브라우저 실측(getBoundingClientRect·computed style·CDP 특이도)으로 수정이 실제 반영됐는지 검증한다. 트리거: "디자인 QA", "피그마랑 달라", "피그마랑 비교", "피그마와 비교", "figma 비교", "Figma랑 비교해줘", "시안이랑 비교", "디자인이랑 맞는지 확인", "간격 이슈", "겹쳐서 비교", "픽셀 맞춰줘", "픽셀 비교", "16배 확대해서 비교", "디자인 정확도", "디자이너 코멘트 확인", "피그마 보고 맞춰줘", "피그마보고 수정", "figma 보고 체크", "피그마 참고해서 확인", "피그마대로", "시안대로 맞춰줘".From its SKILL.md

Install
npx -y skills add chussum/cat-harness --skill design-qa

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

31.4 KB, as published. Nobody here has run it

Design Pixel QA — Figma 겹침 대조 루프

Figma 디자인과 구현 화면을 픽셀 단위로 맞추는 검증·수정 루프. 실제 QA 세션에서 수십 번의 시행착오로 확립된 절차다. 아래 철칙을 어기면 같은 실수를 반복하게 된다.

철칙 (전부 실제 사고에서 나온 규칙)

  1. 반드시 겹쳐서 육안으로 판정한다. 수치 측정은 원인 추적용이고, 최종 판정은 항상 "두 이미지를 포갠 화면"을 눈으로 본다. 수치가 맞는데 눈에 다르면 측정이 틀린 것이다.
  2. 두 캡쳐는 반드시 같은 픽셀 크기여야 한다. Figma export가 375px면 구현 캡쳐도 375px. 뷰포트가 넓으면 width:100% 컴포넌트가 늘어나 캡쳐가 900px가 되고, 겹침이 전부 어긋나 보인다(실제 사고). 캡쳐 후 sips로 크기부터 대조하라.
  3. 목데이터(사진·상품명·썸네일)는 회색으로 마스킹하고 비교한다. 콘텐츠가 다르면 픽셀 diff가 전부 "다름"으로 나와 레이아웃 차이를 가린다. 겹침의 목적은 글자·버튼의 위치/크기/간격이다.
  4. 가로만 재지 말고 세로도 잰다. 세로 4px 오프셋을 가로 측정만 반복하며 못 찾는 사고가 있었다. 모든 요소를 가로 잉크 span + 세로 잉크 rows 둘 다 측정한다.
  5. 비교하는 두 요소는 같은 색 판정식(threshold)으로 잰다. 할인율(핑크/빨강)과 금액(검정)을 다른 판정식으로 재면 ±1px 가짜 오프셋이 나온다. 이걸 "보정"하면 멀쩡한 밑선을 망가뜨린다(실제 사고).
  6. 크롬은 글자를 정수 픽셀에 스냅한다(DPR 1). position/top/margin 0.5px 이동은 렌더에 반영되지 않는다(실측 확인). 위치 보정은 1px 단위, 폭 보정만 letter-spacing 소수점(0.1~0.15px)이 유효하다.
  7. 개별 텍스트에 세로 오프셋을 넣지 마라. 같은 행의 두 텍스트(예: 할인율+금액)는 브라우저가 밑선을 자동 정렬한다. 한쪽에만 top: 1px을 넣으면 구현 화면 자체가 어긋난다. 행 전체(부모 row)를 함께 이동시켜라.
  8. ±1 경계 안티앨리어싱 픽셀은 쫓지 마라. 같은 CSS에서도 측정마다 301↔302로 흔들리는 픽셀이 있다. 이걸 맞추려 값을 바꾸면 다른 곳이 깨진다. 흔들림 이력을 기록하고 육안 겹침으로 판정한다.
  9. 수정 1회 = 재캡쳐 + 재측정 + 겹침 1회. 수정 후 검증 없이 다음 수정으로 넘어가지 않는다. 일치할 때까지 루프.
  10. Figma 수치(metadata)보다 잉크 실측이 우선한다. Figma 노드 gap이 4px라 해도 실제 export 픽셀이 다르면 픽셀을 따른다. 단, 색상은 디자이너 확인 없이 바꾸지 마라(Figma가 옛 색일 수 있음 — 실제 사례: 할인율 핑크는 구버전, 현행 빨강 유지).
  11. 측정 범위를 미리 가르지 마라. "아이콘은 x≤256, 텍스트는 x≥256" 식으로 경계를 가정하고 재면 두 측정값이 경계에 인위적으로 딱 붙어 "일치"로 오판한다(실제 사고 — 아이콘·텍스트 간격 1px 차이를 이렇게 놓쳤다). 간격은 두 요소를 모두 포함하는 전체 영역에서 hruns(컬럼 런) 를 뽑아 런 사이 공백으로 계산한다.
  12. 명목 스펙값을 기계적으로 적용하지 마라 — DS 에셋의 잉크 폭부터 재라. Figma gap이 2px여도 DS 아이콘 잉크가 Figma 아이콘보다 1px 좁게 래스터되면 시각 간격은 1px 벌어진다(실제 사고: 벨 아이콘). 그리고 정렬 방향에 따라 무엇이 움직이는지 계산하라 — 우측 정렬 pill에서 gap 변경은 아이콘만 움직이고(텍스트는 우측에 고정), 우측 패딩 변경이 아이콘+텍스트 그룹 전체를 움직인다. "어느 잉크를 몇 px 옮길지" 계산한 뒤에 CSS를 바꾼다.
  13. 한 곳을 지적받아도 그 컴포넌트 전체를 전수 대조하라. "간격 다르다"를 고치면 "이번엔 세로 위치가…"가 이어진다(실제 흐름 — 같은 배너에서 버튼 간격→그룹 가로위치→썸네일 텍스트 세로가 연달아 나옴). 지적이 오면 가로·세로·간격·요소간 거리 전부를 한 번에 표로 대조하고 한꺼번에 고친다(아래 "전수 대조" 절).
  14. 글리프 전진폭(advance) 차이는 자간 스냅 실험으로 흡수한다. 브라우저와 Figma는 글자별 전진폭이 1px씩 달라 어떤 배치로도 모든 글자를 못 맞추는 지점이 생긴다. 소수점 자간은 누적 반올림 위치를 바꾸므로 0 / 0.15 / 0.2px 등 후보를 각각 실측해 일치 글자 수가 최대인 값을 고른다(실제: 0→3/5자, 0.15→3/5자, 0.2→4/5자 일치). 남는 1px은 렌더러 차이로 주석에 기록하고 종료한다.
  15. QA 첨부 이미지에서 어느 쪽이 시안인지 먼저 확정하라. "좌=구현 / 우=시안" 같은 팀 관례를 추정하면 정상 코드를 시안 반대로 바꾼다. 확정법: ① 같은 프로젝트에서 이미 완료된 티켓의 첨부 + 그 수정 커밋을 대조해 관례를 역산한다(그 커밋이 채택한 값이 어느 쪽 픽셀과 같은지 — 실제로 이걸로 확정했다), ② 구조적 단서(구현에만 있는 섹션 헤더·스크롤 상태, 시안에만 있는 목업 문구), ③ 그래도 애매하면 물어라.
  16. 디자이너가 붙인 합성 캡쳐는 패널마다 배율이 다르다. 두 스크린샷을 같은 시각 폭으로 맞춰 붙이기 때문이다(실제: 한 이미지 안에서 좌 1.75× / 우 1.61×). 알려진 CSS 치수(썸네일 72px 등)로 패널별 배율을 역산한 뒤 수치를 비교하라. 안 하면 "8px vs 16px"처럼 두 배 틀린 결론이 나온다.
  17. 디자이너 코멘트는 가설이다 — 박스 여백과 잉크 위치를 구분해 원인을 다시 특정하라. "상품명 상단 여백 2px" 지적의 실제 원인은 박스 여백 0 + line-height 과다(half-leading)였다. 박스는 getBoundingClientRect, 잉크는 픽셀 스캔으로 각각 잰다. 지적의 방향은 맞아도 원인·수치·수정 지점은 다를 수 있다.
  18. CSS 변수 override는 그 변수를 선언한 요소에 걸어라. 부모(래퍼)에 선언하면, 자식이 같은 변수를 선언하는 규칙(DS의 .small 같은 size variant)에 진다 — 상속값은 더 가까운 선언을 못 이긴다. 실제 사고: 히트영역 padding 변수를 래퍼에 줬다가 8px이 아니라 DS 기본 4px로 남았고, 그 4px 때문에 아이콘이 세로 중앙에서 4px 내려간 것까지 뒤늦게 발견했다.
  19. override가 안 먹으면 CDP CSS.getMatchedStylesForNode로 특이도를 확인하라. CSS 파일 grep은 중첩·조건부 셀렉터를 놓친다(실제: DS 규칙이 단일 클래스로 보였지만 실제로는 .a[data-x] .b.small .c = 0-5-0이라 0-3-1인 내 규칙이 졌다). 해시 클래스(.zds4_…)는 버전마다 바뀌니 셀렉터로 쓰지 말고, 레포에 선례가 있으면 !important로 덮고 이유를 주석에 남긴다.
  20. 브라우저 실측 없이 종료하지 마라. 단위 테스트나 CSS 파일 읽기로 대체하면 변수 상속·특이도·절대배치 앵커 문제를 못 잡는다(철칙 18·19가 정확히 그렇게 새어 나갔고, "반쪽만 반영된 수정"을 커밋·푸시까지 했다). 디버깅 세션이 죽으면 Playwright CLI로 폴백해서라도 getBoundingClientRect + getComputedStyle을 찍는다.
  21. 시안과 다르지만 유지하기로 한 항목은 코드 주석에 근거를 남긴다. 안 남기면 다음 QA·코드리뷰에서 같은 지적이 다시 올라오고 매번 재조사한다(실제: 할인율 색은 DS 기본 유지, 에셋 CDN CORS 제약은 프로덕션 무영향으로 클로즈 — 둘 다 리뷰에서 재지적됐다).
  22. 편차는 감수가 아니라 제거가 우선이다 — "시각적으로 구분 안 됨"으로 스스로 승인하지 마라. 1~2px 편차를 "라운드 근사라 감수", "안 보이는 수준"으로 트레이드오프 설명과 함께 종료했다가 "너무 후하게 판단한다"는 지적을 받았다(실제). 편차를 발견하면 ① 제거 방안부터 설계하고, ② 두 기준이 수학적으로 충돌할 때만 트레이드오프를 사용자에게 보고하고 선택을 받는다. 스스로 채택한 트레이드오프는 반드시 재지적으로 돌아온다.
  23. 판정 전에 통과 기준을 수치로 선언하고 시작하라. "일치해 보인다"가 아니라 "유의미 픽셀 diff(휘도≥8) 0% / 좌표 ±0.05px / 중심선 편차 0" 식으로 먼저 못 박고, 결과를 기준 대비로만 보고한다. 기준 없이 재면 판정이 관대해진다(철칙 22의 사고가 정확히 이렇게 발생).
  24. 시안이 커버하지 않는 폭에 시안 비율을 외삽하지 마라 — 그 폭의 실존 레퍼런스를 찾아라. 375 전용 스켈레톤 시안의 간격 비율을 320까지 축소 적용했다가, 320에서 실제 컴포넌트(탭)와 4.3px 어긋나 재지적받았다(실제). 시안이 없는 폭의 기준은 "그 폭에서 실존하는 가장 정확한 레퍼런스"(예: 함께 렌더되는 실제 컴포넌트의 정렬, 해상도 가이드의 인접 시안)다. 스켈레톤·로딩 상태는 실제 콘텐츠와의 정렬(전환 시 튐 여부) 까지가 QA 범위다.
  25. 두 레퍼런스가 충돌하면 하나로 타협하지 말고 구간을 나눠 각 구간에서 0을 만들어라. 시안 좌표와 실제 컴포넌트 정렬이 ≤1.9px 어긋나 동시 만족이 불가능했던 사례: 시안이 실존하는 정확히 그 폭(375)만 미디어쿼리로 시안 모드, 나머지 전 폭은 컴포넌트 정렬 — 모든 폭에서 편차 0이 됐다. 중간값 타협(양쪽 다 1px씩 어긋남)은 양쪽에서 재지적받는 최악수다.
  26. 해상도 가이드(여러 폭 시안)는 전 폭의 수치로 CSS 모델을 역산·검산한 뒤에 구현하라. "428 기준으로 안 커짐" 같은 주석 한 줄을 한 시안만 보고 해석하면 오해석한다(실제: 바 전체 max-width 428로 구현했다가 개별 pill 최대폭 54 의미로 정정). 4개 폭 시안의 요소 폭·간격을 전부 표로 뽑아 하나의 flex 규칙(grow/basis/cap/gap)이 모든 시안을 재현하는지 검산하고, 완성 후 각 폭에서 실측으로 재확인한다.

준비물

  • 구현 렌더: 검증 대상 컴포넌트를 격리해 띄운 화면 — 컴포넌트 뷰어, 전용 라우트, 또는 최소 재현 페이지 등 프로젝트에 맞는 무엇이든. 전체 앱 화면보다 컴포넌트 격리 렌더가 픽셀 대조에 정확하다.
  • Figma: Figma MCP (get_screenshot으로 TO-BE export, get_metadata로 노드 구조/치수, get_design_context로 컴포넌트 매핑 — Code Connect로 디자인 시스템(DS, 공용 컴포넌트 모음) 컴포넌트가 지정된 경우 그 스펙이 정답).
    • 두 도구를 합쳐 스펙 표를 만든다: get_design_context는 Tailwind 클래스로 gap/padding/text-[]를, get_metadata는 하위 노드의 절대 좌표·박스 크기를 준다. 후자가 line-height·인셋을 정확히 뜬다 — 실제로 [ Metadata ] 하위가 상품명 y0 h14 / 가격 y18 h18 / 그룹 h36으로 나와 "line-height 14, 이름↔가격 gap 4"가 확정됐고, 찜 44×44 + icon x8 y8로 히트영역 스펙이 확정됐다.
    • get_design_context 응답이 토큰 한도를 넘으면 파일로 떨어진다 — 통째로 읽지 말고 data-name="…" 앵커로 필요한 구간만 파싱한다.
  • 캡쳐/측정: Playwright MCP + 스크래치패드에 python3 -m http.server 7020 (측정용 canvas HTML은 file:// 차단이라 HTTP로 서빙).
  • 폴백 (브라우저 디버깅 세션이 죽었을 때): npx playwright screenshot --channel=chrome --viewport-size=375,700 --wait-for-timeout=5000 <url> out.png — 번들 브라우저를 안 받고 시스템 Chrome을 쓴다(Executable doesn't exist 오류 회피). 측정·특이도 조회는 chromium.launch({channel:'chrome'}) + page.context().newCDPSession(page).
  • 픽셀 대조: references/pixel-compare.py — 표준 라이브러리만으로 PNG를 직접 읽어 잉크 밴드 / 16배 3단 차이 이미지 / 크롭을 낸다. http.server·canvas 셋업이 필요 없어 1차 스캔이 가장 빠르다.

워크플로우

0. 방향 확정 (첨부 이미지 판독)

수정에 들어가기 전에 어느 쪽이 시안이고 어느 쪽이 구현인지, 그리고 패널별 배율을 확정한다(철칙 15·16). 배율 역산은 구현 쪽에서 아는 CSS 치수를 쓴다:

패널 배율 = (그 패널에서 잰 요소 px) / (그 요소의 CSS px)
예: 썸네일 잉크 126px / CSS 72px = 1.75×  →  이 패널의 모든 측정값을 1.75로 나눠 design px로 환산

확정 결과(어느 쪽/배율/환산값)를 보고에 한 줄로 남긴다 — 이후 모든 수치의 기준이 된다.

1. 캡쳐 (둘 다, 같은 크기)

# Figma TO-BE: get_screenshot(fileKey, nodeId) → curl로 저장
# 구현 AS-IS: 뷰포트를 Figma 프레임 폭(보통 375)으로 resize 후 element 캡쳐
browser_resize(375, 700)
browser_navigate("<컴포넌트를 격리 렌더한 URL>")   # 컴포넌트 뷰어 iframe / 전용 라우트 / 최소 재현 페이지
browser_evaluate: 캡쳐 대상 밖의 여백·배경 제거 CSS 주입 (컨테이너 margin/padding 0, 배경 흰색)
  'html,body{margin:0!important;padding:0!important;background:#fff!important;}
   <대상 컨테이너 셀렉터>{padding:0!important;margin:0!important;}'
browser_take_screenshot(target="<대상 컴포넌트 루트 셀렉터>", scale="device")
# 저장 후 반드시: sips로 두 PNG 크기 동일 확인

주의: Playwright 스크린샷은 repo 루트에 떨어진다 → 스크래치패드로 옮기고 .playwright-mcp/ 삭제(깃 오염 방지).

diff 전에 애니메이션을 정지시켜라. 스켈레톤 셔머·트랜지션이 살아 있으면 diff에 애니메이션 프레임이 섞여 가짜 차이가 나온다(실제: 셔머 정지 전 8.9% → 정지 후 유의미 diff 0.000%). 캡쳐 직전에 주입:

const s = document.createElement('style');
s.textContent = '*, *::before, *::after { animation: none !important; transition: none !important; }';
document.head.appendChild(s);

dev server(Storybook 등) 재시작 시 포트 드리프트 주의. 이전 프로세스가 좀비로 남으면 새 서버가 다음 포트(7011→7012→7013)로 밀리는데, 옛 포트는 stale 번들을 정상 응답하므로 수정 전 코드를 측정하게 된다(실제 사고 — 두 번). 서버 로그에서 실제 포트를 읽어 접속하고, 측정값이 "수정 반영 안 됨"으로 나오면 포트/번들 신선도부터 의심하라.

Figma 캡쳐 함정 (실제 발생):

  • get_screenshot이 1×1 png를 반환하면 export가 깨진 것(컴포넌트 마스터 등) — 같은 디자인의 다른 인스턴스 노드로 다시 찍는다.
  • 반환 크기가 노드 폭보다 작으면(예: 323 노드가 73px) 부모 프레임에 클립된 인스턴스다 — 부모 안에 완전히 들어있는 인스턴스를 찾아 찍는다.
  • 겹침을 1:1로 하려면 구현 렌더 폭이 Figma 노드 폭과 같아지는 뷰포트를 고른다(반응형 카드면 노드 폭이 나오는 기기 폭으로 resize — 예: Figma 343 노드 ↔ 375 뷰포트의 343 카드, Figma 323 노드 ↔ 428 뷰포트의 323 카드).

2. 측정 — 0차: 잉크 밴드 1차 스캔 (references/pixel-compare.py)

요소별 표를 만들기 전에 텍스트 컬럼 전체의 행 프로파일을 뽑아 세로 오프셋부터 드러낸다. 요소를 특정하지 않으므로 빠르고, 무엇을 자세히 잴지 정해준다.

python3 references/pixel-compare.py bands FIGMA.png IMPL.png 100 270 360 470
# 시안: [(377,389), (397,410), (422,429), (465,469)]
# 구현: [(378,390), (399,412), (—),       (466,469)]   ← 상품명 1px·가격 2px 아래, 뱃지 누락

밴드가 한 줄씩 어긋나면 세로 리듬(line-height/gap) 문제, 전부 같은 값으로 밀리면 컨테이너 위치 문제다. 밴드가 일치하면 그 요소는 종료하고 다음으로 넘어간다.

2.1 측정 (잉크 스캔 — references/measure-template.html)

canvas로 두 PNG를 읽어 잉크 픽셀(배경이 아닌 픽셀)의 가로 span/세로 rows를 요소별로 스캔한다. 색 판정식:

const dark = (R,G,B)=>R<110&&G<110&&B<110;          // 검정 텍스트
const warm = (R,G,B)=>R>180&&G<130;                  // 빨강/핑크 (B 조건 넣으면 핑크 누락 — 실제 사고)
const blue = (R,G,B)=>B>150&&R<150&&B-R>40;          // 파랑 아이콘/텍스트
const white= (R,G,B)=>R>235&&G>235&&B>235;           // 흰 pill 배경

요소마다 {ours: [L,R]/[top,bottom], figma: [...]}" 표를 만들어 오프셋을 확정한다.

잉크가 못 재는 곳은 DOM + Figma 좌표로 잰다. 배경이 사진(노이즈)이라 판정식이 안 서는 텍스트(썸네일 위 오버레이 등)는:

  1. get_metadata로 Figma 노드 트리의 절대 좌표·박스 높이를 표로 만든다.
  2. 구현에서 getBoundingClientRect()(컴포넌트 루트 기준 상대좌표) + getComputedStyle().lineHeight를 전 요소 일괄 수집한다.
  3. 두 표를 붙여 x/y/h를 1:1 대조한다 — 이 방법이 잉크보다 빠르고, 문구가 목데이터라 달라도 흔들리지 않는다. 최종 확인만 겹침 육안으로.

2.4 목데이터가 비어 대조가 안 되는 요소 (서버 이미지 뱃지·엠블럼)

뱃지·엠블럼이 서버에서 이미지 URL로 오는 구조면 목데이터가 비어 화면에 안 나온다. 이건 구현 결함이 아니라 목데이터 한계이므로 "차이"로 보고하지 말고, 대조가 필요하면 시안 캡쳐에서 그 픽셀을 잘라 data URI로 목데이터에 주입한다:

python3 references/pixel-compare.py crop FIGMA.png badge.png 100 418 47 16   # 실측 크기 그대로
python3 references/pixel-compare.py b64 badge.png                            # → data:image/png;base64,…

주입 후엔 위치·크기가 픽셀 일치해야 정상이다(같은 에셋이므로 차이 이미지에서 그 영역이 완전히 사라진다 — 이게 차이 이미지의 기준선이 된다). 목데이터 파일에는 출처(Figma 노드·실측 크기)를 주석으로 남긴다.

2.5 전수 대조 (부분 지적 → 전체 검증)

디자이너가 한 군데를 지적하면 그 컴포넌트의 모든 요소를 위 DOM+Figma 표로 한 번에 대조한다(가로 x·세로 y·박스 h·요소간 거리). 실제 사례: "버튼 간격" 지적 → 전수 대조에서 썸네일 텍스트 세로 4px/2px 처짐이 추가로 발견됨. 표에서 어긋난 것만 모아 일괄 수정 → 일괄 재검증이 지적 1건당 왕복 1회보다 훨씬 빠르고 신뢰를 준다.

3. 원인 매핑 (자주 나오는 실제 원인)

증상실제 원인수정
아래 요소들이 전부 N px 위로 밀림DS Text 컴포넌트 기본 line-height < Figma 텍스트박스 높이해당 텍스트에 line-height: <Figma값>px
행 안의 그룹이 1~2px 밀림flex align-items:center의 높이 재분배flex-start + 명시적 gap으로 전환
텍스트 폭이 1px 좁음/넓음브라우저 vs Figma 글리프 advance 차이letter-spacing: ±0.1~0.15px
버튼이 큼/작음커스텀 pill이 DS 표준 버튼 크기와 다름box-sizing:border-box+height 고정+패딩 계산
버튼 내용 쏠림Figma는 좌측 편향인데 구현은 가운데(또는 반대)비대칭 패딩으로 잉크 x좌표 맞춤
이미지가 텍스트 위/아래 반대DS BottomSheet(바텀시트) 컴포넌트의 imagePosition 류 속성'top'↔'bottom'
하단 고정 블록의 텍스트가 아래로 처짐absolute bottom + padding-bottom 배치에서 DS 기본 line-height < Figma 박스 → 짧아진 만큼 아래로 (실제: 스토어명 4px·날짜 2px)각 텍스트 line-height: <Figma박스>px
하단 고정 스택에서 위 요소일수록 더 밀림텍스트 박스 부족분이 위로 누적 (실제: 타이틀 3px+날짜 3px → 배지 6px)스택 내 모든 텍스트 line-height 고정
아이콘-텍스트 간격만 1px 넓음DS 아이콘 잉크가 Figma 아이콘보다 좁게 래스터gap을 잉크 기준으로 -1 (철칙 12 — 정렬 방향 계산 필수)
아이콘+텍스트 그룹이 통째로 밀림우측 정렬 컨테이너에서 우측 패딩 차이우측 패딩으로 그룹 이동 (gap은 그룹을 못 움직임)
문자열 중간 글자부터 1px 어긋남글리프 전진폭 렌더 차이 (균일 시프트 아님 = 위치 문제 아님)자간 0.15/0.2px 후보 실측 비교 (철칙 14)
특정 텍스트만 가로 1~2px 어긋남Figma에서 그 텍스트만 옵티컬 들여쓰기(x+1~2)metadata에서 텍스트 노드 x 확인 후 padding-left
좁은 기기에서만 줄바꿈텍스트가 컨테이너 패딩에 갇힘 (Figma는 최소 마진까지 확장 허용)width: calc(100% + Npx) 또는 min(고정폭, calc(100% + Npx)); 그래도 1~2px 모자라면 해당 폭 이하 미디어쿼리로 자간 -0.1px
박스 여백은 0인데 잉크만 1~2px 아래DS line-height > Figma 텍스트박스 → half-leading (LH − font)/2가 잉크를 내림그 텍스트 line-height: <Figma 박스 h>px (박스가 줄면 아래 요소 간격도 같이 맞는지 재확인)
아이콘이 히트영역 안에서 한쪽으로 붙음히트영역만 키우고 내부 padding 변수가 DS 기본으로 남음 + 버튼이 absolute bottom/right:0 앵커변수를 버튼 요소 자체에 선언(철칙 18) — padding이 곧 인셋이 되고 박스가 히트영역을 채워 중앙 정렬된다
override가 계산값에 안 나타남DS의 중첩 셀렉터 특이도가 더 높음(0-5-0 등)CDP matched styles로 확인 후 !important(선례 있으면) 또는 안정적 훅으로 특이도 상향(철칙 19)
가격에 통화 단위가 붙어 우측 요소와 겹침서버 display_currency를 접미사로 전달시안에 단위 표기가 없으면 전달하지 않는다(글로벌 통화 계획은 별도 확인)

4. 수정 → 재검증 루프

수정 → 재캡쳐 → 재측정 → 겹침 육안 → 다르면 반복. 겹침 확인은 두 가지 뷰:

  • 색 유지 겹침(Figma 50% 투명도 위에): 최종 판정용. 8배 확대로 본다.
  • 색 제거 위치 겹침(파랑=우리/빨강=Figma/보라=일치): 색 차이·렌더링 차이에 속지 않고 순수 위치만 볼 때.

4.2 override가 계산값에 안 나타날 때 (특이도 진단)

수정을 넣었는데 getComputedStyle이 그대로면 셀렉터 싸움에서 진 것이다. 추측하지 말고 matched rules를 직접 본다:

const cdp = await page.context().newCDPSession(page);
await cdp.send('DOM.enable'); await cdp.send('CSS.enable');
const { root } = await cdp.send('DOM.getDocument', { depth: -1 });
const { nodeId } = await cdp.send('DOM.querySelector', { nodeId: root.nodeId, selector: '<대상>' });
const m = await cdp.send('CSS.getMatchedStylesForNode', { nodeId });
// 각 rule의 selectorList.text + selectors[].specificity(a,b,c) + 해당 속성값을 나열

내 규칙과 DS 규칙의 (a,b,c)를 나란히 놓으면 무엇을 얼마나 올려야 하는지가 바로 나온다. CSS 변수 값이 예상과 다르면 철칙 18(선언 위치)부터 의심한다 — 특이도가 아니라 상속 거리 문제인 경우가 많다.

4.5 확대 배율과 문구 다른 캡쳐

  • 겹침 판독은 16배(픽셀 단위 판독), 넓은 영역 훑기는 8배. 확대 크롭이 화면에서 축소돼 보이면 배율을 의심하라 — "너무 작게 확대했나"는 실제 지적이다. 크롭 폭 × 배율이 뷰어 폭을 넘으면 영역을 쪼개서 본다.
  • 16배 3단 이미지(시안 / 구현 / 차이) 를 한 장으로 만들면 판독이 가장 빠르다: pixel-compare.py compare FIGMA.png IMPL.png x0 x1 y0 y1 16. 차이 패널은 임계 8 이하를 무시한다 — Figma 래스터와 브라우저 렌더는 글립 AA가 달라 위치가 완전히 같아도 diff가 10~20% 나온다. 따라서 차이 패널은 "형태·있음/없음·색"만 보고, 위치 판정은 밴드 수치로 한다. 같은 에셋을 주입한 영역(2.4)이 diff 0으로 나오는지로 판독 기준선을 잡는다.
  • 문구가 다른 두 캡쳐(목데이터 vs Figma 예시 문구)는 겹침 육안이 오판하기 쉽다 — 문구가 동일한 앵커(숫자 "01."/날짜, 아이콘, 디바이더, pill 경계)만 육안 신뢰하고, 나머지는 잉크 런/DOM 수치로 판정한다. 리포트에도 "문구는 목데이터 — 위치·간격만 볼 것"을 명시한다.

5. 보고

리포트(HTML artifact): 영역별로 [① 우리 화면 | ② Figma | 겹침(투명도 슬라이더)] 3열, 사진 영역 회색 마스킹, 라벨은 색 태그로 명확히("① 우리 화면(지금 코드)" / "② 피그마 원본"). 티켓별 수정 내역 표 + 테스트 결과.

오버레이 리포트를 쓸 만하게 만드는 조건 (전부 "보기 어렵다"는 지적에서 나왔다):

  • 구현 캡쳐를 내장하라. 열자마자 겹쳐진 상태여야 한다 — 매번 파일을 올리게 만들면 안 쓴다. 프리셋 버튼은 시안·구현을 쌍으로 교체한다.
  • 구현 캡쳐가 없는 초기 상태에서 시안에 불투명도를 걸지 마라. 회색 바닥 위 반투명 시안이 흐려 보여 "안 보인다"가 된다 — 겹치기 모드일 때만 불투명도를 적용한다.
  • 겹치기보다 좌우 와이프·차이 모드가 판독이 쉽다. 와이프는 경계에서 어긋남이 즉시 보이고, 차이는 1~2px을 잡는다. 모드는 4개(겹치기·와이프·차이·나란히)면 충분하다.
  • 기본 확대는 100%, 슬라이더로 최대 400%. 처음부터 200%면 프레임이 화면을 넘어간다.
  • 모든 수치를 시안 프레임 기준 design px로 표기한다(오프셋·스케일 readout 포함) — 티켓에 그대로 붙일 수 있어야 한다.
  • 비교 바닥은 라이트/다크 모두 중립 회색 고정. 배경색이 바뀌면 색 판정이 흔들려 QA 툴로 못 쓴다.
  • 섹션을 추가하면 하단 요약(현황) 리스트에도 같은 항목을 추가한다 — 사용자는 요약만 보고 "빠졌다"고 인지한다(실제 발생).
  • 슬라이더 동작 검증은 두 이미지의 픽셀이 실제로 다른 좌표에서 알파 0/100 픽셀 변화를 확인한다(동일 픽셀 지점에서 재면 false negative).
  • 같은 파일 경로로 재발행하면 artifact URL이 유지된다 — 캡쳐를 갱신했으면 반드시 재빌드+재발행.

안티패턴 (이 세션에서 실제로 저지른 실수들)

  • ❌ 수치만 보고 "일치"라고 보고 (→ 육안 겹침이 판정 기준)
  • ❌ 리포트 뷰포트(1180px)에서 캡쳐해 900px 이미지로 겹침 (→ 크기 불일치)
  • ❌ 가로만 측정하고 세로 오프셋을 놓침
  • ❌ 색 판정식이 다른 두 요소를 비교해 가짜 1px을 "보정" → 멀쩡한 밑선 파괴
  • ❌ 눈대중으로 gap을 늘렸다 줄였다 (→ 반드시 잉크 실측 후 계산된 값만 적용)
  • ❌ 0.5px position 이동 시도 (→ 크롬 스냅으로 무효, 실측으로 확인됨)
  • ❌ 경계 AA 픽셀(±1 flutter)을 맞추려 반복 수정
  • ❌ Figma 구버전 색을 "정답"으로 적용 (→ 색은 사용자/디자이너 확인 필수)
  • ❌ 아이콘/텍스트 측정 x범위를 미리 갈라서 잼 → 간격 1px 차이를 "일치"로 오판 (→ 철칙 11: 무가정 전체 런)
  • ❌ Figma 명목 gap을 그대로 적용하고 종료 (→ DS 아이콘 잉크 폭 차이로 시각 간격 어긋남 — 철칙 12)
  • ❌ 간격을 맞추며 어긋남을 다른 곳(아이콘 좌측 여백)으로 밀어놓고 보고 (→ 사용자가 "그룹이 밀렸다"로 재지적 — 트레이드오프는 계산해서 가장 안 보이는 곳이 아니라 잉크 대조가 최대로 일치하는 배치를 고르고, 남는 px은 보고에 명시)
  • ❌ 지적받은 요소만 고치고 종료 → 같은 컴포넌트에서 다음 지적 반복 (→ 철칙 13: 전수 대조)
  • ❌ 요약(현황) 리스트 갱신 누락 → 사용자가 "리포트에 빠졌다"고 인지
  • ❌ 첨부 이미지의 좌/우를 관례로 추정 → 시안 반대로 수정할 뻔함 (→ 철칙 15: 완료 티켓 커밋으로 역산)
  • ❌ 합성 캡쳐를 한 배율로 가정하고 수치 비교 → 8px을 16px로 오판 (→ 철칙 16: 패널별 배율 역산)
  • ❌ 디자이너가 말한 수치("여백 2px")를 원인으로 단정 → 실제는 line-height half-leading (→ 철칙 17)
  • ❌ CSS 변수를 래퍼에 선언하고 반영됐다고 판단 → DS variant 클래스가 이김, 게다가 그 값 때문에 세로 정렬까지 어긋남 (→ 철칙 18)
  • ❌ CSS 파일 grep으로 특이도를 판단 → 중첩 셀렉터를 놓쳐 override 실패 (→ 철칙 19: CDP matched styles)
  • ❌ 브라우저 실측을 건너뛰고 jsdom 테스트로 "검증 완료" 보고 → 반쪽 수정을 커밋·푸시 (→ 철칙 20)
  • ❌ 목데이터가 비어 안 보이는 서버 이미지(뱃지·엠블럼)를 "구현 누락"으로 보고 (→ 2.4: 목데이터 한계 명시 또는 시안 픽셀 주입)
  • ❌ 차이 이미지의 텍스트 윤곽 diff를 위치 문제로 판독 → 렌더러 AA 차이라 항상 나온다 (→ 4.5: 위치는 밴드로 판정)
  • ❌ 시안과 다르게 두기로 한 결정을 기록하지 않음 → 다음 QA·리뷰에서 같은 지적 재발 (→ 철칙 21)
  • ❌ 1~2px 편차를 "라운드 근사라 감수"로 스스로 승인하고 종료 → "너무 후하게 판단한다" 재지적 (→ 철칙 22: 제거 우선, 트레이드오프는 사용자 선택)
  • ❌ 375 전용 시안의 간격 비율을 320까지 축소 적용 → 실제 컴포넌트와 4.3px 어긋남 (→ 철칙 24: 시안 밖 폭은 실존 레퍼런스)
  • ❌ 충돌하는 두 기준 중 하나를 전 폭에 일괄 적용 → 반대쪽 기준으로 재지적 (→ 철칙 25: 구간 분리로 양쪽 다 0)
  • ❌ 주석 한 줄("428 기준")을 한 시안만 보고 해석해 구현 (→ 철칙 26: 전 폭 시안 수치로 모델 검산)
  • ❌ 셔머 애니메이션이 살아 있는 캡쳐로 diff → 가짜 8.9% 차이 (→ 캡쳐 절: animation none 주입 후 0.000%)
  • ❌ 좀비 dev server의 stale 번들을 측정해 "수정 반영 안 됨"으로 오판 — 두 번 반복 (→ 캡쳐 절: 로그에서 실제 포트 확인)

Keep looking

Skills are one crate of 628,971. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.