feat(skill): absorb external design skills, restore interview gate, add review route

- Restore the step-0 interview as a mechanical gate the skill explicitly
  depends on; add harness.md (per-harness question tools, limits,
  fallbacks) and brief-interview.md (slots, question cards, rounds).
- Add 10 reference docs absorbed from external design skills
  (accessibility, interaction-feel, elevation, color, icons, product-copy,
  component-systems, critique, change-review, print-email) and extend
  existing references.
- Add a review-only route and two hard-gate clauses (truncated content
  reachability, three-flashes limit).
- design-gate: split tap targets into WCAG 2.5.8 and 44px contract layers,
  run axe-core when available, and fix false positives found on a real
  site (decorative alt="", stacked wordmark line count, url-only pages).
- lint-skill: fail if the interview gate section or its links disappear.
- Ship agents/openai.yaml and THIRD_PARTY_NOTICES.md.
This commit is contained in:
Yun Chan 2026-09-24 13:26:03 +09:00
parent 79e79c120b
commit 6805fb2be7
37 changed files with 5688 additions and 128 deletions

View file

@ -13,7 +13,7 @@
- **React·Vue·Astro 등 프레임워크 프로젝트 → 프로덕션 빌드 결과에 돌린다.** 소스만 보면 렌더된 DOM 이 없어 대비·가로 스크롤·LCP 를 잴 수 없다. `pnpm build && pnpm preview` 로 띄우고 그 URL 을 검사해라
- **소스만 보고 "측정 불가"로 남기지 마라.** 미측정은 통과가 아니다
grep 검사는 **주석과 문자열을 제외**해라. "100vh 금지"라고 쓴 주석이 게이트 #9 에 걸리는 오탐이 실제로 나왔다.
grep 검사는 **주석과 문자열을 제외**해라. "100vh 금지"라고 쓴 주석이 '지원 범위의 뷰포트에서 의도하지 않은 가로 스크롤·겹침·조작 불능이 없다' 하드 게이트 검사에 걸리는 오탐이 실제로 나왔다.
사람 눈보다 정확하고 빠르다. 셋 다 돌려라.
@ -65,6 +65,22 @@ canvas { display: none !important; }
| 모션 감소 | `prefers-reduced-motion` 켜고 확인 | **전부 끄는 게 정답이 아니다** — 위치 이동·시차·자동재생을 죽이고, 페이드·상태 변화는 남긴다 |
| 자동재생 | 확인 | 5초 이상 반복되는 애니메이션에는 정지 수단이 있다 |
| 캔버스/WebGL 실패 | JS 끄고 확인 | 콘텐츠가 여전히 보인다 |
| 네이티브 요소 | 소스 확인 | 이동은 `<a href>`, 액션은 `<button>`, 상태 없는 클릭 영역에 `<div onClick>`을 쓰지 않는다. 근거·예외는 [accessibility.md](accessibility.md) [SKILL-BETTER-A11Y] |
| 아이콘 버튼 이름 · Label in Name | 소스 확인 | 텍스트 라벨 없는 버튼·링크는 `aria-label` 또는 숨김 텍스트 필수, 장식 아이콘은 `aria-hidden="true"`. 보이는 라벨이 있으면 접근 가능한 이름이 그 텍스트를 포함한다 [WCAG-LABEL-NAME] [SKILL-WEB-A11Y] |
| 포커스 가림 | Tab 순회, 스티키 헤더·CTA 바 확인 | 포커스된 요소가 고정 바에 완전히 가려지지 않는다. `scroll-margin-top`을 헤더 높이 토큰에 연결 [WCAG-FOCUS-OBSCURED] |
| forced-colors | Windows 고대비 모드로 확인 | 강제 색상 모드에서도 컨트롤 경계·상태가 구분된다(Baseline: 2022-09~ 전 주요 브라우저 [WEB-BASELINE]). `forced-color-adjust: none`은 검증된 곳에서만 [SKILL-BETTER-A11Y] |
| 스킵 링크 · 랜드마크 | 첫 Tab, 소스 확인 | 반복 내비를 건너뛰는 링크가 첫 포커스 요소, 보이는 `<main>` 랜드마크 하나. 세부는 [accessibility.md](accessibility.md) [SKILL-WEB-A11Y] [SKILL-BETTER-A11Y] |
| 키보드 위젯 패턴 | 실제 키보드 조작 | 탭·메뉴버튼·콤보박스·리스트박스·다이얼로그는 WAI-ARIA APG 패턴을 따른다. 세부는 [accessibility.md](accessibility.md) [ARIA-APG] |
| 폼 — autocomplete·오류 | 소스·조작 확인 | autocomplete 값이 필드 의미와 맞고(무표시는 하드 게이트), 오류는 필드 옆에서 수정법을 말한다. 세부는 [accessibility.md](accessibility.md) [WCAG-INPUT-PURPOSE] [SKILL-BETTER-A11Y] |
| **[프로젝트 계약]** 폼 — 제출 버튼 | 소스·조작 확인 | 제출 버튼은 요청 시작 전까지 비활성화하지 않으며, `disabled`와 `aria-disabled`를 구분해 쓴다. 세부는 [accessibility.md](accessibility.md) [SKILL-BETTER-A11Y] |
| 상태 메시지 | 조작 후 확인 | 포커스 이동 없이 전달되는 상태 변화도 role·속성으로 보조기술에 전달된다. 라이브 리전 선택 기준은 [accessibility.md](accessibility.md) [WCAG-STATUS] [SKILL-BETTER-A11Y] |
| 깜빡임 | 초당 프레임 확인 | 1초에 3회를 넘게 번쩍이거나 일반·적색 섬광 한계를 넘는 콘텐츠가 없다 [WCAG-FLASH] |
| 호버 콘텐츠 | 마우스오버 확인 | 호버·포커스로 나타나는 콘텐츠는 해제 가능·호버 가능·계속 보임 세 조건을 만족한다 [WCAG-HOVER] |
| 드래그 대안 | 조작 확인 | 드래그로만 되는 기능은 단일 포인터 조작(탭 등)으로도 된다. 예외는 필수 드래그·UA 기본 스크롤 [WCAG-DRAG] |
| 잘린 텍스트 도달 수단 | 소스·렌더 확인 | `text-overflow: ellipsis` 등으로 시각적으로 잘린 텍스트·표 셀·태그에는 title·툴팁·상세보기 같은 전체 값 도달 수단이 있다 [SKILL-BETTER-INTERFACE] |
| **[프로젝트 계약]** 비활성 · placeholder 대비 | 렌더 확인 | 비활성 컨트롤은 WCAG 대비 예외 대상이지만, 활성처럼 보여 사용자를 속이지 않을 만큼은 구분한다. placeholder는 라벨이 아니며 지나치게 옅어 읽을 수 없게 두지 않는다 [SKILL-BETTER-COLORS] |
이 표에 새로 더한 행은 비활성/placeholder 대비(별도 표시한 프로젝트 계약)를 빼면 모두 하드 게이트다. forced-colors 도 하드 게이트다 — 판정 근거는 [accessibility.md](accessibility.md) §12. 대비가 실패로 나오면 수정 절차(색상환 고정·명도부터 조정)는 [color.md](color.md)를 따른다.
> **대비를 스크립트로 잴 때 — 반투명 배경을 불투명으로 계산하지 마라.**
> `getComputedStyle(el).backgroundColor` 로 조상을 거슬러 올라가며 "투명이 아닌 첫 값"을 배경으로 쓰면,
@ -140,7 +156,7 @@ canvas { display: none !important; }
- [ ] hero 검증만으로 text-media 분할 검증을 끝내지 않는다. 과정·추천·가맹처럼 DOM/여백 규칙이 다른 모든 분할 표면에서 rail·copy·media·제목의 실제 rect와 줄 수를 기록한다. wide override가 있다면 기존 `padding-left/right: calc(100vw …)`를 **양쪽 모두** 재설정했는지, 상위 `max-width`의 선택자 우선순위가 named stage를 다시 줄이지 않는지 확인한다
- [ ] 중간 뷰포트(768~1024px)에서 레이아웃이 깨지지 않음 — **가장 자주 빠뜨리는 구간**
- [ ] 터치 타깃: **버튼·아이콘·카드 등 독립 컨트롤은 44×44px 이상**(Apple/Google 권고)
- [ ] 터치 타깃: **본문 안 인라인 텍스트 링크는 24×24px 이상**(WCAG 2.5.8 AA). 여기에 44 를 요구하면 정상적인 내비 링크가 오탐된다
- [ ] 터치 타깃: **본문 안 인라인 텍스트 링크는 24×24px 이상** — 이 스킬의 프로젝트 계약(`inlineTargetMin` 24px)이다. WCAG 2.5.8은 문장 속 인라인 링크를 예외로 둔다. 여기에 44 를 요구하면 정상적인 내비 링크가 오탐된다
- [ ] **컨트롤의 내용 여백**: 버튼 라벨·입력값·select 표시·표 셀의 실제 렌더 글자가 테두리에 닿지 않는다. 이 저장소의 실렌더 기본값은 좌우 **8 CSS px** 이상이다. 이는 WCAG 적합성 수치가 아니라 검증 가능한 시각 품질 기본값이며, 아이콘 전용·체크박스·의도적인 데이터 정렬은 별도 근거를 적는다
- [ ] **폼 기하**: 연결된 label/name/autocomplete를 확인한 뒤 실제 렌더 rect로 입력끼리 겹치지 않는지 잰다. 좁은 폭에서는 필드를 세로로 쌓고, label·입력·오류 문장이 서로의 클릭/읽기 영역을 침범하지 않게 한다
- [ ] **표와 표처럼 보이는 flex/grid**: 데이터 관계에는 `th`/`td`와 `scope`를 쓰고, 모든 가시 셀에 좌우 읽기 여백을 둔다. 좁은 폭에서 스크롤·카드화로 형식이 바뀌어도 헤더-값 관계가 남아야 한다
@ -172,7 +188,12 @@ canvas { display: none !important; }
- [ ] **파괴적 행동에는 되돌림이 있다** — 삭제·취소는 실행취소 또는 확인. 되돌리기가 확인보다 낫다(흐름이 끊기지 않는다). 재신청·복원은 원래 판정 함수로 다시 심사한다 — 사이에 끼어든 다른 변화가 있으면 정당하게 막혀야 한다
- [ ] **열리는 것은 Esc 로 닫힌다** — 모달만이 아니다. 드롭다운·팝오버·알림 메뉴도. 닫힐 때 포커스는 연 요소로 돌아간다
- [ ] native `<dialog>` 또는 동등 모달은 전역 reset 뒤에도 `position: fixed; inset: 0; margin: auto`와 viewport 상한/내부 scroll owner를 가진다. 390·1440·2560에서 중심점, 화면 안 rect, 배경 scroll lock을 실제로 단언한다. W3C의 modal dialog 키보드·포커스·닫기 계약을 따른다: [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), [MDN dialog](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog)
- [ ] **[프로젝트 계약]** 모달 액션 행은 스크롤 영역과 분리된 스티키 푸터에 둔다 — 콘텐츠가 스크롤되어도 확인·취소 같은 핵심 액션은 항상 보인다. 리사이즈 가능한 패널 바닥이나 고정 높이 모달의 폴드 아래처럼 잘릴 수 있는 자리에 크리티컬 액션을 두지 않는다 [SKILL-BETTER-LAYOUT]
- [ ] **로딩 상태는 실제 비동기에만** — 동기 인라인 데이터에 스켈레톤을 붙이는 건 저장을 흉내내는 것이다. 없는 지연을 만들지 마라
- [ ] **[프로젝트 계약]** 변형 상태 매트릭스 — 새 컴포넌트·variant는 hover·focus·active·disabled·loading·selected 여섯 상태를 갖추었는지 표로 확인한다. 일부 상태만 구현하고 넘어가는 것이 가장 흔한 누락이다 [SKILL-INTERFACE-REVIEW]
- [ ] **[프로젝트 계약]** 오류·빈 상태 문구는 "존재"만으로 충분하지 않다 — 무엇이 잘못됐는지·어떻게 고치는지를 비난하지 않는 톤으로 말하는 내용 기준은 [product-copy.md](product-copy.md)를 따른다 [SKILL-BETTER-WRITING]
- [ ] **[하드 게이트]** 낙관적 업데이트는 실패하면 되돌린다 — 즉시 반응이 필요한 저위험 토글(좋아요·북마크 등)에 낙관적 업데이트를 쓸 때는 실패 시 같은 판정 함수로 원상 롤백하고, 실패를 토스트·인라인 오류로 알린다. "판정은 순수 함수다"(위) 원칙과 연결된다 [SKILL-INTERACTION-DESIGN](근거: SKILL.md 하드 게이트 5 — 실패를 성공으로 남기지 않는다)
- [ ] **[프로젝트 계약]** 제스처·모션은 실제로 조작해 QA한다 — 커스텀 컨트롤의 제스처 QA는 [interaction-feel.md](interaction-feel.md) §11, 모션 QA(배속 재생·프레임 단위 점검)는 [motion.md](motion.md) §5를 따른다 [SKILL-EMIL-DESIGN-ENG] [SKILL-IMPECCABLE]
---
@ -216,6 +237,9 @@ canvas { display: none !important; }
4. **사용한 레퍼런스의 원칙이 브리프와 렌더 결과에 맞게 번역됐는가?** → 슬롯 수나 업종 차이 자체로 판단하지 않는다
5. **강조색·radius·정렬·반복이 프로젝트 토큰과 과업에 맞는가?**
6. **여백이 의도적인가, 남은 것인가?**
7. **감수하기로 한 리스크는 몇 개이고, 하나로 압축할 수 있는가?** → 여러 개를 동시에 감수했다면 서로 상쇄해 사실상 아무 것도 감수하지 않은 것과 같다 [SKILL-FRONTEND-DESIGN]
반사실 제네릭 점검("이 선택이 다른 업종의 브리프에도 그대로 나왔을 것인가")은 2단계(방향 결정)에서 먼저 하고 design.md에 기록한다. 여기서는 그 결정이 구현 중 안전한 쪽으로 후퇴하지 않았는지만 다시 본다.
---