# 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 안 `

`, role="grid" 자식 구문 위반 | html-validate + axe | | 계산 로직 오류(행 배열을 값으로 셈 → 0/3) | 단위 테스트(브라우저 밖 경계값) | | 같은 데이터 속성이 여러 뷰에 존재해 선택자 오작동 | 하니스 규칙: 선택자는 컨테이너 스코프 | | 혼합 span 그리드의 자동 배치가 시간 라벨을 흩어놓음(같은 열 폭 40/78 혼재, Δ 불규칙) | **리듬·트랙 불변식** — 같은 부모·같은 클래스 형제 중 (a) 숫자/시간 라벨(등폭 의도)과 (b) 빈 격자 셀(트랙 균일 의도)은 폭이 균일해야 하고, 한 축 정렬·크기 균일 형제는 등간격이어야 한다. 한국어 라벨은 글자수가 같아도 내용 폭차가 자연스러우므로 폭 검사 대상에서 뺀다(오탐 방지). 근본 수칙: 혼합 span 그리드는 자동 배치를 믿지 않고 좌표를 명시하라 | ## 6계층 `tools/design-gate.mjs` 가 한 파일로 돌리는 것과 프로젝트가 보강하는 것이 있다. | 계층 | 도구 | 비고 | |---|---|---| | L0 정적 | stylelint + html-validate | 문법·구문 위반을 브라우저 켜기 전에 | | L1 단위 | 순수 계산 로직 추출(calc.js) + 경계값 테스트 | 프로젝트별 작성 — 0명/만석/초과/빈배열 | | L2 불변식 | `design-gate.mjs` — 오버플로·h1·수축·대비·SEO/meta | 전 뷰 × 전 폭 | | L3 시각 회귀 | 기준 화면 pixelmatch diff (≥0.1% 실패) | 갱신은 검증된 배포 후 `--update-baseline` | | L4 WebKit | Playwright webkit | 사파리 엔진 렌더 차이 | | L5 탐색 | 키보드 완전 통과 + 사용자 여정 차터 | 프로젝트별 작성 — 마우스 금지 | ## 설치 (프로젝트에 게이트 심기) ``` 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 10~65자·유일, description 60~165자, 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"가 아니라 "무엇을 몇으로 확인했나". ## 리포트 양식 ``` 게이트: 전 계층 통과 — 배포 가능 · 총 Ns | 계층 | 결과 | 항목 | 소요 | ``` 실패 상세는 원문 테일 포함. 리포트 파일(gate-report.md / verify-report.md)은 커밋 대상이다.