designpaca/packages/skill/references/audit-gate.md
Yun Chan 46b47884c9 feat(skill): 왼쪽 인셋 리듬·픽셀 판정을 스킬에 내재화
이번 라운드(스크롤 잔류·빈약한 헤더·인셋 불일치 — 사용자 보고 3건)의 교훈을 스킬로 승격:
- design-gate leftInset 검사 내장(텍스트 노드 Range 기준 ≥12px, 모바일 390px 전 뷰) — 오버플로 검사는 오른쪽만 보는 계측 공백을 메움. 도그푸드: 양앱 통과(가온 16·두레 24 각자 일관 축)
- 브랜드 검사 오탐 수정(동일 셀렉터 다중 요소 시 보이는 것 기준 — 도그푸드가 잡음)
- preflight §0-E 왼쪽 인셋 리듬, 하니스 규칙 15~17(기준 갱신 의도 리뷰·스크린샷 픽셀 판정·RED 재현), 사고표 4행
- SKILL.md 규칙 6 보강: 계측의 공백 — 보고가 오면 픽셀 판정으로 수치화하고 게이트에 추가
2026-08-23 15:40:58 +09:00

12 KiB
Raw Blame History

audit-gate — 자율 검증 폐쇄 루프

5단계(감사)를 완료 선언의 게이트로 만드는 문서다. 핵심 원칙은 하나다:

완료의 정의는 게이트 통과다. 사용자를 QA 로 쓰지 않는다.

"고쳤다"는 말의 근거는 검증 리포트의 수치다. 게이트가 실패 항목을 내면 4단계로 돌아가 고치고 다시 전체를 돌린다 — 고친 것만 재검하지 않는다. 이 루프(구현 → 게이트 → 실패 → 수정 → 재게이트)가 designpaca의 기본 동작이며, 게이트 없이 6단계로 넘어가는 것은 경로 무관 금지다.

왜 필요한가 — 실제 사고 목록

이 게이트의 각 검사는 사용자 보고로야 발견됐던 결함에서 왔다. 어느 검사가 어느 사고를 잡는지가 설계 근거다.

사고 (실제) 검사
CSS 닫는 중괄호 누락 — 이후 규칙 절반이 죽은 채 렌더 stylelint(구문) + 중괄호 균형
브랜드가 모바일에서 1글자 폭으로 수축, 세로로 쌓임 수축 탐지(짧은 라벨 n줄 이상) + 브랜드 1줄
기기 글꼴 확대(안드로이드 큰 글꼴 1.3×)에서 제목 부서짐 스케일×폭 h1 행렬
본문 보조색이 배경 대비 4.4:1 (AA 미달) 전 요소 대비 스캔(블렌드 계산)
조사만 하고 안 고친 영역(관심 과목 등) 전 뷰 순회 — 검사는 '내가 고친 것'이 아니라 전체 표면
대시보드 26명 ↔ 명단 12명 숫자 모순 콘텐츠 정합(요약 = 재계산 비교)
aria-label 을 div/ul 에 오용(보조기술 무시) html-validate + axe
dl 안 <p>, role="grid" 자식 구문 위반 html-validate + axe
계산 로직 오류(행 배열을 값으로 셈 → 0/3) 단위 테스트(브라우저 밖 경계값)
같은 데이터 속성이 여러 뷰에 존재해 선택자 오작동 하니스 규칙: 선택자는 컨테이너 스코프
혼합 span 그리드의 자동 배치가 시간 라벨을 흩어놓음(같은 열 폭 40/78 혼재, Δ 불규칙) 리듬·트랙 불변식 — 같은 부모·같은 클래스 형제 중 (a) 숫자/시간 라벨(등폭 의도)과 (b) 빈 격자 셀(트랙 균일 의도)은 폭이 균일해야 하고, 한 축 정렬·크기 균일 형제는 등간격이어야 한다. 한국어 라벨은 글자수가 같아도 내용 폭차가 자연스러우므로 폭 검사 대상에서 뺀다(오탐 방지). 근본 수칙: 혼합 span 그리드는 자동 배치를 믿지 않고 좌표를 명시하라
프리셋 전환(v7)에서 브랜드 표면이 누락 — theme-color·파비콘이 종이 시대 값으로 11버전 생존 theme-color 정합(메타 값 ↔ 실제 배경색 비교) + 파비콘·og:image 존재. 토큰 마이그레이션은 브랜드 표면 3종을 갱신하기 전까지 끝나지 않는다
컴포넌트 제거 시 CSS/JS 잔존 — 미사용 선택자 12종·이중 계산 블록(첫 블록이 즉시 덮임) 죽은 선택자 검사(CSS 클래스 ↔ HTML·JS 텍스트 사용률 대조). 제거는 HTML·CSS·JS 삼위일체
크롬에 컨트롤 1개 추가 → 그 줄의 최소폭 합 초과(320px +18px) 오버플로 전 폭 검사(기존) + 규칙: 크롤에 컨트롤을 더하면 최소폭 합을 가장 좁은 폭에서 다시 잰다
같은 명시도의 숨김 규칙이 기본 규칙보다 앞에 있어 짐 — 모바일에서 숨기려던 kbd 안내가 렌더됨(6% 시각 diff) 시각 회귀 + 규칙: 숨김 오버라이드는 기본 규칙보다 파일 뒤쪽에 두거나 컴포넌트 규칙 근처의 미디어쿼리로
탐색 차터가 결과를 단언하지 않아 "9288점" 입력이 조용히 통과(값을 덧붙인 타이핑) 하니스 규칙 8(검사는 단언)·9(number 입력 조작)
모바일 라운드 — 중앙 모달이 썸존 밖, 백 키가 앱을 통째로 종료, safe area 무시, 입력 확대 L6 시나리오(백·시트·터치타깃 스윕) + references/mobile-app-ux.md 표준 — OS 관습은 조사 없이 바꾸지 않는다
탭 전환 스크롤 잔류 — 짧은 뷰에서 클램프되어 헤더 반쯤 잘림, 뷰마다 크롬 위치 제각각 뷰 전환 scrollTo(0,0) 단언(M5) + mobile-app-ux.md
크럼을 헤더로 내보내 빈약한 헤더 — 계측 전부 통과했지만 사용자에게 '깨져 보인다' 헤더 sticky·브랜드+액션 구조 단언 + 시각 무게 평가(눈) — mobile-app-ux.md
왼쪽 인셋 리듬 불일치(제목 16 vs 앱바·카드 24) — 오버플로 검사는 오른쪽만 보므로 통과 M6 왼쪽 인셋 스위트(텍스트 시작점 ≥12px·같은 축 ±10px) + preflight §0-E

7계층

tools/design-gate.mjs 가 한 파일로 돌리는 것과 프로젝트가 보강하는 것이 있다.

계층 도구 비고
L0 정적 stylelint + html-validate 문법·구문 위반을 브라우저 켜기 전에
L1 단위 순수 계산 로직 추출(calc.js) + 경계값 테스트 프로젝트별 작성 — 0명/만석/초과/빈배열. 입력 검증·신청 가능 같은 '판정'(가능/불가+사유)도 여기 둔다 — UI 는 판정을 문장으로 번역만
L2 불변식 design-gate.mjs — 오버플로·h1·수축·대비·SEO/meta·죽은 선택자·토큰 위생·theme-color 정합 전 뷰 × 전 폭
L3 시각 회귀 기준 화면 pixelmatch diff (≥0.1% 실패) 갱신은 검증된 배포 후 --update-baseline
L4 WebKit Playwright webkit 사파리 엔진 렌더 차이
L5 탐색 키보드 완전 통과 + 사용자 여정 차터 프로젝트별 작성 — 마우스 금지
L6 시나리오 E2E 실사용자 여정 재현 — 페르소나의 하루·장기 과업 포함 프로젝트별 작성. 기대값 단언 필수(하니스 규칙 8), 백 키·시트·양방향 흐름은 mobile-app-ux.md 패턴 기준. 실측: RED 1,742 단언 중 318 실패가 신규 기능에 정확히 집중 — 시나리오를 먼저 쓰면 미구현이 수치로 드러난다

설치 (프로젝트에 게이트 심기)

node <스킬>/tools/design-gate.mjs --init     # gate.config.json 초안
# pages[].views 를 실제 뷰 목록으로, viewAttribute 를 data-view 등으로
npm i -D puppeteer-core                       # CHROME_PATH 또는 자동 탐색
package.json: "verify": "node <스킬>/tools/design-gate.mjs"
배포는 항상: npm run verify && build && deploy   # verify 실패(exit 1)면 배포 불가

L1·L5 는 프로젝트 안에 tools/unit/*.test.mjs, tools/exploratory.mjs 로 보강하고 verify 체인에 묶는다. 근거와 완성 예는 designpaca 저장소 apps/site/tools/ 가 정본 샘플이다.

SEO / meta 감사 (design-gate 내장)

조사 기준(2026): title 1065자·유일, description 60165자, viewport, html lang, charset, canonical(프로덕션), og:title/description/image/url, twitter:card, favicon, h1 유일, img alt 전부, JSON-LD 파싱. 데모·로컬 페이지는 canonical/robots 없음을 허용한다 — 검사는 맥락을 존중하되 나열 근거는 리포트에 남긴다.

OS · 브라우저 특성 — 검사에 반영할 것

  • Windows: 스크롤바가 오버레이가 아니다 — overflow-x:auto 짝에는 overflow-y:hidden + scrollbar-width 검토. 스크롤바 자체가 레이아웃을 민다.
  • iOS/Safari(WebKit): 100vh 주소창 문제(100dvh), 텍스트 인플레이션(-webkit-text-size-adjust:100%), WebKit 만의 flex/행높이 차이 → L4 로 재검.
  • 안드로이드: 시스템 글꼴 확대(1.15~1.3× 흔함) — rem 전면 확대로 제목 칸 수축 → 스케일×폭 행렬으로 재현·검증.
  • 폰트 스왑: font-display: swap 의 CLS — 폴백 메트릭 보정(typography.md §4) 없이 검사 통과를 선언하지 않는다.

하니스 규칙 (테스트를 만드는 테스트)

  1. 선택자는 스코프필수 — 같은 data-* 가 다른 뷰에 있으면 숨은 요소를 잡는다(#as-table [data-assign] 처럼).
  2. page.evaluate 클로저 금지 — Node 스코프 함수를 페이지 안에서 부르지 않는다. 측정 코드는 문자열로 주입.
  3. 줄 수 측정의 오탐 두 종류 — 폰트 메트릭 top 차이(±5px 허용오차), 인라인 아이콘과 텍스트의 top 차이(텍스트 노드만 분리 측정).
  4. CSS 수정 직후 검증은 캐시 차단(setCacheEnabled(false)) + 실제 로드된 ?v= 확인.
  5. 비전(모델) 검수는 '여부'가 아니라 '지점 평가'에만 — 여부는 계측으로.
  6. 모달 닫힘 직후 포커스 강탈 — 뷰 전환+타깃 포커스는 한 evaluate 로, settle 후 유실 재포커스.
  7. 게이트를 통과해도 리포트에 수치를 남긴다 — "ALL PASS"가 아니라 "무엇을 몇으로 확인했나".
  8. 검사는 단언한다 — pass("차터", st) 처럼 상태를 상세로만 찍는 건 검사가 아니다. 실측 사고: 점수 입력에 "88"을 type() 하면 기존 값에 덧붙어 "9288"이 되었고, 차터는 상태 텍스트만 인쇄해 통과했다. 기대값과 비교해 pass/fail 을 내라.
  9. number 입력은 선택-덮어쓰기가 안 된다 — 트리플 클릭 선택이 무시된다. 값을 비우고 타이핑하거나 el.value = x; dispatchEvent(new Event("input")) 로 설정한다.
  10. 등장 모션 지속시간 ≤ 감사 도구 대기시간 — 시각 회귀가 뷰 전환 100ms 뒤에 스크린샷을 찍는다면 등장 애니메이션은 그보다 짧게(≤70ms). 아니면 매번 다른 프레임이 찍혀 회귀가 흔들린다.
  11. 같은 diff 수치가 반복되면 패치가 적용되지 않은 것 — 수정하고도 시각 diff %가 소수점까지 동일하면 수정이 렌더에 닿지 않았다(캐시·캐스케이드 순서·잘못된 파일). 고치기를 반복하지 말고 적용 자체를 의심해라.
  12. 페이지→노드 직렬화는 NaN 방어 — getComputedStyle(el).fontSize 등을 문자열 보간하면 "16px"이 NaN이 되고, 하니스는 NaN을 null로 받아 "////px" 같은 고장난 상세를 리포트한다. 수치는 항상 parseFloat(x) || 0 로 감싼다.
  13. position: fixed 요소의 보임 판정은 offsetParent 로 하지 마라 — fixed 는 offsetParent 가 null이다. display 계산값·높이로 판정한다.
  14. 실기기(에뮬레이터) 검증은 텍스트 근거로 — uiautomator dump 의 text="…"에서 화면 제목·활성 탭을 읽어 단언한다. 스크린샷+비전은 "백 직후 전환 중 프레임"을 잡는 타이밍 오탐이 있다(실측 사고). 물리 폰이 붙어 있으면 adb -s <emulator> 로 대상을 한정한다.
  15. 시각 기준 갱신은 의도 변경 리뷰를 동반한다 — "달라졌으니 갱신"으로 결함이 기준에 박힌다(실측: 왼쪽 인셋 결함 화면이 기준 갱신으로 '정상'이 되어 재발을 숨겼다). 갱신 전에 무엇이 의도적으로 바뀌었는지 한 줄씩 나열하고, 사용자 보고가 있던 화면은 수리 후 재촬영한다.
  16. 사용자 보고 스크린샷은 비전 요약이 아니라 픽셀 판정으로 해부한다 — 비전 모델의 픽셀 추정("~0px", "~16px")은 부정확하다. pngjs 등으로 화면을 가로 밴드로 나눠 각 밴드의 텍스트 시작 x를 직접 재면(보임 행만, 왼쪽 테두리 아티팩트 제외) "무엇이 어디서 어긋났는지"가 수치로 나온다. 실측: '제목이 가장자리에 붙는다'는 보고가 앱바 24px·제목 16px의 리듬 불일치로 확정됐다.
  17. 새 검사는 수정 전 코드로 RED 재현 후 GREEN — 검사 자체의 측정 하자(오탐)도 이렇게 잡는다. 실측: 왼쪽 인셋 검사 초안이 ①display:none 요소의 rect=0, ②전폭 컨테이너의 보더박스(텍스트가 아님)로 38개 오탐 — RED 단계에서 발견해 교정했다.

리포트 양식

게이트: 전 계층 통과 — 배포 가능 · 총 Ns
| 계층 | 결과 | 항목 | 소요 |

실패 상세는 원문 테일 포함. 리포트 파일(gate-report.md / verify-report.md)은 커밋 대상이다.