18 KiB
HMDesign 잔여 이슈 최종 안정화 — 2026-10-10
RESULT: PARTIAL. 숨김 Select 비교 Story의 넘침을 수정했고 기존 Guard 40개와 추가 6개 테스트, 아래 빌드·브라우저·독립 설치 검증은 통과했다. 승인 후 최신 npm audit도 완료했으며 운영 취약점 0건·개발 High 7건·Critical 0건이다. 개발 도구 취약점, 기존 색상 대비 문제, 정적 Guard 한계, 실제 스크린리더·터치 미검증은 남긴다. 이전 보고서의 결과는 소급 변경하지 않는다.
TARGET: D:\디자인시스템\hmdesign / BOTH / React·Vite.
BASE_SHA: cb1d2050d552a5442357b9dc2d181c20e74e7010. 작업 전 로컬 main과 지정 GitHub main HEAD가 일치했고 Working Tree는 clean이었다. 최종 Git 상태는 로컬 실행 보고서와 최종 응답에 기록한다. 공식 Gitea: NOT ACCESSED / NOT MODIFIED. ZIP 생성·포터블 배포는 수행하지 않았다.
수정 파일과 영향 범위
| 파일 | 원인·변경·영향 |
|---|---|
src/components/select.stories.tsx |
숨김 ListSizeComparison만 수정. 기존 Grid의 최소 열 폭 280px과 실제 고정 폭 384px가 불일치했다. 최대 specimen 폭 384px를 기준으로 열 수를 결정하고, 좁으면 min(100%,384px) 및 specimen maxWidth:100%·min-w-0로 줄인다. 라벨에 최대 폭임을 표시한다. 공통 Select·대표 All·API·Variant·Theme는 변경하지 않는다. |
scripts/ai-generation-guard.test.mjs |
기존 40개 본문을 보존하고 6개 경계 테스트 추가. Guard 정책·분석기는 변경하지 않는다. |
docs/guidelines/ai-generation-guard.md |
추가 테스트의 범위와 미검증 판정 유지 이유를 설명한다. |
README.md 및 이 보고서 |
후속 검증 결과·보류 보안 항목·접근성 한계·수동 절차를 연결한다. 과거 기록은 보존한다. |
Select 넘침 분석과 전후 측정
실제 Chromium의 documentElement clientWidth/scrollWidth, 부모 Grid track과 specimen·Trigger·Popup rect를 측정했다. Popup 네 개를 모두 닫아도 수정 전 넘침 수치가 같았다. Trigger 텍스트와 아이콘은 기존 말줄임이 정상이며 공통 Select 자체의 문제를 확인하지 않았다. 비교 Grid가 자식의 고정 폭보다 작은 열을 만들면서 발생한 페이지 전체 넘침이며, 의도된 내부 스크롤이 아니다.
| Viewport | 수정 전 scrollWidth (열림/닫힘) | 수정 후 scrollWidth (열림/닫힘) | 수정 후 Grid track | specimen 폭 |
|---|---|---|---|---|
| 390 | 400 / 400 | 390 / 390 | 358px × 1 | 280, 280, 358, 358px |
| 768 | 784 / 784 | 768 / 768 | 384px × 1 | 280, 280, 384, 384px |
| 1280 | 1352 / 1352 | 1280 / 1280 | 384px × 3 | 280, 280, 384, 384px |
| 1440 | 1440 / 1440 | 1440 / 1440 | 384px × 3 | 280, 280, 384, 384px |
전역 overflow 숨김을 추가하지 않았다. 4개 인스턴스 × 4개 viewport에서 Enter로 열기, Home으로 첫 옵션 이동, Enter 선택, Escape 닫기와 Trigger focus 복귀를 확인했다. opening animation과 Radix의 비동기 option focus가 끝난 실제 상태로 판정했다. Popup은 Trigger 폭과 일치하고 viewport의 좌우 경계를 넘지 않았다. 대표 Select·Combobox·DatePicker의 네 viewport 렌더와 Combobox 검색·선택도 별도로 확인했다. 모든 props/모든 브라우저의 전수 검증을 의미하지 않는다.
개발 의존성 분석
로컬 npm ls와 lockfile의 설치 버전, 공개 npm 레지스트리의 최신 버전 및 GitHub 공식 보안 권고를 새로 확인했다. 권고의 영향 범위는 braces ≤3.0.3이며 패치 버전은 없다. npm 조회 결과 braces 3.0.3 / micromatch 4.0.8 / fast-glob 3.3.3 / shadcn 4.21.4다.
| 패키지 | 설치 버전 | 직접/간접·경로 |
|---|---|---|
| shadcn | 4.21.1 | UI workspace 직접 devDependency |
| @shadcn/registry | 0.1.0 | shadcn → registry |
| ts-morph | 26.0.0 | shadcn 또는 registry → ts-morph |
| @ts-morph/common | 0.27.0 | ts-morph → common |
| fast-glob | 3.3.3 | shadcn/registry 또는 common → fast-glob |
| micromatch | 4.0.8 | fast-glob → micromatch |
| braces | 3.0.3 | micromatch → braces |
기존 High 7건은 독립된 근본 취약점 7개가 아니라 같은 braces 문제의 상위 의존성 전파를 포함한다. 깊게 중첩된 brace pattern이 Node stack을 소진해 프로세스를 종료시키는 문제다.
영향 경로는 CLI뿐이 아니다. scripts/ai-generation-guard.mjs의 Storybook discovery는 fast-glob에 로컬 설정의 literal glob을 전달한다. 현재 관리하는 glob으로 검사했을 때 정상 종료했지만 비신뢰 설정·극단적으로 중첩된 pattern을 처리하면 위험이 남는다. Production profile은 이 discovery 경로를 실행하지 않는다. scripts/build-package.mjs는 shadcn CSS와 라이선스를 복사할 뿐 CLI/registry glob을 실행하지 않는다. npm scripts도 shadcn CLI를 호출하지 않는다. 공개 UI pack에는 CSS asset만 동봉하고 CLI의 개발 의존성 그래프를 소비 앱에 배포하지 않는다.
안전한 patch로 근본 문제가 해소되는 근거가 없어 shadcn 4.21.4 업데이트만으로 High가 사라진다고 판단하지 않았다. 이번 최신 audit도 shadcn 1.0.0을 isSemVerMajor:true로 제안했다. 4.x에서 major 역행이며 build의 shadcn/dist/tailwind.css 계약에도 영향을 줄 수 있어 채택하지 않았다. force fix/override/major 변경은 하지 않았다. package.json·lockfile은 byte 동일하며, Storybook 10.6.1 / Vite 8.3.0 / React 19.3.0을 보존하고 새 설치·타입 검사·빌드로 현재 조합을 확인했다.
완화 방법: 신뢰하는 로컬 glob만 사용하고 비신뢰 registry/pattern을 shadcn CLI·Guard에 전달하지 않는다. 개발 도구를 외부 공개하지 않고 패치가 나오면 별도 호환성 검증을 수행한다. CLI 삭제·다른 라이브러리로의 교체는 이번 최소 수정에 포함하지 않는다.
NPM_AUDIT: PARTIAL — 2026-10-10 14:38 KST에 이번 감사로 한정한 사용자의 전송 승인 후 전체·운영 의존성을 각각 실제 검사했다. npm audit --json 결과는 High 7 / Critical 0 / 다른 등급 0, npm audit --omit=dev --json 결과는 모든 등급 0이다. 전체 명령 exit 1은 확인된 High 7건 때문이며 검사 실패나 취약점 0으로 처리하지 않았다. 운영 감사 exit 0이다. 근본 권고는 GHSA-vfj7-8cjw-p6xm 하나이며 위 7개 개발 패키지로 전파된다. 이전 기록 7건과 이번 fresh 결과 7건이 같고, 의존성 변경은 없다.
최초 자동 승인 거부 기록과 승인 전 미검증 보고서는 로컬에 보존했다. 승인 후에는 registry를 https://registry.npmjs.org로 명시하고 빈 user/global npm 설정 및 npm 인증 환경변수 제외 상태로 audit 명령만 실행했다. 소스코드·환경변수 덤프·인증정보를 전송하는 작업은 수행하지 않았다. raw 결과는 로컬의 audit-total-current.json, audit-production-current.json으로 보관하고 Git에 추가하지 않는다. 새 Clone/consumer 설치는 기존처럼 --no-audit로 완료한 결과를 유지한다.
Guard 판정
| 실행 | 결과·실제 범위 |
|---|---|
| GUARD_TEST | PASS — 46/46, 기존 40개 본문 유지. |
| GUARD_FULL_SCREEN | NOT VERIFIED — 공개 HM 목록 예제/GNB_ONLY/React index.html, FAIL 0. ENTRY-003 1, RENDER-002 1, REUSE-003 3. |
| Production app FULL_SCREEN | NOT VERIFIED — GNB_ONLY/index.html, FAIL 0. RENDER-002 1, REUSE-003 13. |
| Dashboard/Recruit FULL_SCREEN | NOT VERIFIED — 각각 bootstrap/DASHBOARD_ONLY, FAIL 0. CSS intent 각각 10/13. compact 분기의 Sidebar는 실제 DOM에서도 0개 확인. |
| GUARD_PARTIAL | NOT VERIFIED — 변경 Select Story의 STORYBOOK profile, FAIL 0. native Story 예제 REUSE-004 2. |
| GUARD_STORYBOOK | NOT VERIFIED — 63 Story module, FAIL 0. 내부 Reference wrapper 1, CSS intent 19, native controls 20. mode 미지정 Reference 범위의 Navigation은 NOT APPLICABLE. |
추가 테스트 41~46은 preview CSS 단절, 명시 entry 범위, 상속 tsconfig, Reference/public slot 경계, literal Navigation과 CSS intent의 독립성, package 구현의 범위를 확인한다. Guard 정책을 참조한다. NOT VERIFIED 판정의 회귀 테스트 통과를 해당 미검증 사항의 PASS로 전환하지 않는다.
CSS intent·Reference 승인 의도, 상속 alias 전체 해석, opaque render, package 구현 자체는 정적 소비 Guard의 한계다. 실제 브라우저·SR·실기기 시험의 미실시는 실행 검증의 미검증이며 이와 구분한다. 공개 slot 위조·필수 CSS의 확정 누락·도달하는 금지 Navigation·Production의 Story 의존은 기존대로 FAIL이다. 허용 목록이나 무조건 PASS는 추가하지 않았다.
접근성
자동 axe-core의 WCAG 2 A/AA·2.1 A/AA와 Chromium 실제 키보드 조작을 각각 실행했다. 자동 검사는 대표 Story 9개 및 열린 Combobox/DatePicker popup을 대상으로 하며 모든 Story/state의 전수 검사는 아니다.
- Keyboard: PASS (확인 범위). Checkbox Tab/Shift+Tab/Space, Radio ArrowRight, Toggle Space, Select Enter/Home/Escape, Combobox 검색→ArrowDown→Enter→focus 복귀, DatePicker Enter/Escape→focus 복귀, Tabs 방향키·내부 overflow 이동 버튼, Dialog focus trap, Sheet Escape/focus 복귀, Drawer 취소를 확인했다.
- Focus Visible: PASS (확인 범위). Checkbox
:focus-visible, Theme Runtime의 실제 Tab/Shift+Tab/Enter를 확인했다. Runtime은 138 checks / 58 parts / KEYBOARD_FOCUS: PASS. 정적 CSS entry 판정으로 대신하지 않았다. - Label·ID: PASS (확인 범위). Label 클릭의 Input focus, 대표 9개 Story의 중복 ID 없음, 반복 popup·pagination label React 테스트, Combobox listbox·DatePicker popup 이름 연결을 확인했다.
- Disabled/readOnly: PASS (확인 범위). 입력값 보호·disabled 상태와 clear 미제공의 실제 React 테스트를 확인했다.
- AUTO_ACCESSIBILITY: PARTIAL. Checkbox/Radio/Toggle의 대상 scan은 위반이 없었다. 기존 Select/Combobox/DatePicker placeholder는
#999999/White 대비 2.84:1, Input 오류 문구는#d32f2f/#f5f0e5대비 4.38:1로 axe serious color-contrast를 보고했다. 확정 Token과 기존 Theme mapping이므로 임의 수정하지 않았다. 이 대비 항목은 실행해 확인한 FAIL이며 접근성 전체를 PASS로 보고하지 않는다. - DatePicker 일자 label-content-name-mismatch: NOT VERIFIED. 실제 DOM은 표시
1, 이름2026년 10월 1일…처럼 숫자를 포함한다. axe의 word token 연속 일치 판정은 한국어 일자 단위와의 결합을 불일치로 볼 가능성이 있다. 원본 위반 로그를 보존하고 WCAG Label in Name에 따른 수동 확인 대상으로 남긴다. SR·음성 조작을 실행하지 않고 자동 PASS로 바꾸지 않았다. - axe incomplete: NOT VERIFIED. Tabs gradient의 대비, 열린 popup의 ARIA 값 등 도구가 확정하지 못한 node는 수동 검사 대상으로 남겼다.
- Select 열린 popup 추가 scan에서 aria-hidden-focus가 보고됐다. 기존 Radix modal의 숨긴 배경 Trigger와 관련된 관찰로, 실제 focus trap·지원 보조기술을 추가 확인해야 한다. 원본 경고를 제거하거나 이름 없는 Select listbox를 접근성 인증 완료로 처리하지 않는다.
- SCREEN_READER / TOUCH_DEVICE / Drawer 실제 손가락 drag close: NOT VERIFIED.
기존 확정색 변경은 별도로 접근성 요구사항과 Token 영향 범위를 확인해 다룬다. Component/Theme source가 시작 시점과 같으므로 이번 변경이 대비 문제를 만들었다고 판단하지 않는다. 실제 SR 발화 인증이나 완전한 WCAG 적합성을 주장하지 않는다.
미실시 검증 절차
- Windows NVDA + Chrome/Edge로 이름·role·상태를 읽는다. Label이 있는 Input, Checkbox/Radio/Toggle을 Tab으로 이동하고 Space·방향키의 상태 변화와 disabled/readOnly 보호를 확인한다.
- Select/Combobox/DatePicker를 2개 이상 같은 화면에 둔다. 키보드로 popup을 열고 이름·expanded·선택 option/date·검색 결과를 읽는다. Escape 후 원래 Trigger로 복귀하며 다른 인스턴스의 ID·이름·값과 혼동되지 않는지 확인한다. Select listbox의 독립 이름도 확인한다.
- Calendar의 숫자와 년월일 이름을 NVDA·음성 입력으로 확인한다. axe의 한국어 token 판정만으로 합격·불합격을 결정하지 않는다.
- Dialog/Sheet/Drawer를 열어 Title/Description 발화·focus trap·Escape/취소 후 Trigger 복귀를 확인한다. 숨긴 배경으로 focus가 이동하는지도 검사한다.
- iOS Safari + VoiceOver, Android Chrome + TalkBack 실기기로 390px 상당 화면을 확인한다. 터치 선택·Tabs 스크롤·GNB/LNB 메뉴·Calendar 선택을 검사한다. Drawer를 실제 손가락으로 drag해서 닫고 스크롤 충돌·focus/발화 복귀를 확인한다. pointer 에뮬레이션을 실기기 PASS로 보지 않는다.
- 기존 대비 부족은 확정 Theme에서 실측하고 WCAG AA 적용 범위·승인한 수정 방침을 기록한다. Tabs gradient 등 axe incomplete도 실제 배경에서 수동 측정한다.
전체 회귀와 Git 공유
| 항목 | 결과·실행 내용 |
|---|---|
| TYPECHECK | PASS — root/UI/CENTER, 예제·임시 신규 화면 tsconfig. |
| UI_BUILD | PASS — UI/CENTER workspace, Theme defaults 71색/73항목 일치. |
| PRODUCTION_BUILD | PASS — root, 목록 예제, Dashboard/Recruit export. |
| STORYBOOK_BUILD | PASS — 최종 static build, 292 Story ID 유지·추가/삭제 0. |
| COMPONENT_TEST | PASS — 기존 8/8, 본문 byte 동일. |
| GUARD_TEST | PASS — 46/46. |
| DOC_LINKS | PASS — 최종 MD 91개·내부 링크 400개, 경로/heading 오류 0. |
| CSS_IMPORTS | PASS — 69 import 누락 0. |
| EXPORTS | PASS — 공개 import 736개 누락 0, export artifact 16개 존재. UI 255 / Theme 8 / CENTER 25 / CENTER Reference 10. |
| THEME_JSON_LOADER | PASS — 최종 static 네 viewport 각각 119 checks, 새 Clone dev에서도 119. |
| THEME_DESIGNER | PASS — 최종 static 네 viewport 각각 96 checks, 새 Clone dev에서도 96. 같은 preview 문서의 임시 색 격리·세션 복원·Reset 확인. |
| PACKAGE_CONSUMER | PASS — 최종 pack의 독립 설치, strict:true / skipLibCheck:false 타입 검사. |
| NPM_AUDIT | PARTIAL — 최신 전체 High 7 / Critical 0, 운영 0. 개발 취약점은 major 변경 보류와 영향 경로·완화 방법을 위에 기록. |
| RESPONSIVE | PASS (확인 범위) — 대표 Story 18개 × 4폭, 목록/root/Dashboard/Recruit/신규 예제, Select 비교 4폭, Sheet 4방향 × 4폭, Drawer 4폭. |
| RUNTIME | PASS (확인 범위) — 292 Story의 1280px 실제 React 렌더와 대상 interaction. 모든 props/state/mobile의 전수 시험은 아니다. |
Console의 broken-avatar-image·missing-verification-theme 404는 의도한 fallback/실패계 시험이며 원본 로그를 보존했다. 실행한 화면에서 예상하지 않은 pageerror는 관찰하지 않았다. 500kB chunk·Storybook bundler 성능 경고는 남겼으며 기능 실패나 braces 취약점 해결로 혼동하지 않는다.
GitHub baseline main을 별도 폴더로 Clone하고 후보 변경만 반영했다. node_modules/dist를 복사하지 않고 npm ci --no-audit로 526 packages를 새로 설치했다. UI/CENTER build·typecheck·Storybook build/dev를 실행했다. README의 npm scripts로 실행했으며 시험 포트는 충돌을 피한 6045다. 공개 HM Component/Pattern/Theme 렌더를 확인하고 새 목록 React 화면을 Clone의 _local/new-screen에만 만들어 typecheck/build/dev와 네 폭의 조회/Reset/모바일 메뉴를 확인했다. 공식 프로젝트에 새 화면이나 Story를 추가하지 않았다.
기존 구현 보존과 최종 판단
D-01/D-02, Primary Alpha 0.8/Secondary/Legacy, Component/Pattern/Theme/Token/공개 API, C2~R3, 추가 수정된 Button/Table/Progress/Accordion/Sheet/Drawer/Navigation, Theme Loader/Designer와 대표 Story는 변경하지 않았다. 이번 브라우저 검사에서 Tabs padding 24px 0·border 없음, Progress 우측 radius, Accordion hover 5%, Sheet 안쪽 radius, Drawer 반응형 footer, Dashboard 48px header·960px 내부 스크롤·Sidebar 없음도 확인했다. 모든 역사적 화면의 pixel diff나 전 상태 재시험을 수행했다고 주장하지 않는다.
시작 때 기록한 추적 파일 521개의 SHA-256을 현재와 비교하고 읽기 전용 요청 원본의 SHA도 비교한다. Guard 구현·Navigation 분석기·기존 컴포넌트 테스트·package/lockfile·Theme와 정상 영역을 보존한다. 추가 Guard 테스트는 기존 40개 본문의 원본 prefix 보존을 검사한다. 로컬 시험 코드·로그·캡처·Clone·신규 시험 화면은 _local/stabilization-20261010/에 보관하며 Git에 포함하지 않는다.
Select 비교 문제는 해결했고 Guard의 기존 판정을 유지하는 테스트를 추가했다. Git 테스트 공유의 주요 빌드·실행 검증과 최신 감사 실행은 완료했다. 운영 취약점·Critical은 0이며 새 Critical/High 기능 회귀는 확인되지 않았다. 기존 개발 High 7건은 안전한 patch가 없어 보류한 항목이며, 기존 대비 부족·정적 한계·실제 SR/touch도 남긴다. PARTIAL은 완전한 접근성 승인이나 Gitea 이전 승인을 의미하지 않는다. 검증된 보류 파일만 지정 GitHub main에 일반 Commit/Push하는 조건을 충족한다. Force Push·reset/clean·Gitea 접근·ZIP 작업은 수행하지 않았다.