Skip to content

chore(storybook): Ladle 카탈로그를 Storybook 10으로 교체 - #17

Merged
ohjihoon05 merged 35 commits into
mainfrom
feat/demo-fab-monitor-polish
May 20, 2026
Merged

ohjihoon05 merged 35 commits into
mainfrom
feat/demo-fab-monitor-polish

Conversation

@ohjihoon05

Copy link
Copy Markdown
Owner

요약

Ladle 컴포넌트 카탈로그를 Storybook 10으로 교체. apps/ladle/ → apps/storybook/.

  • CSF 포맷 스토리 25개를 그대로 재사용 (Ladle CSF가 Storybook과 호환 — 스토리 파일 수정 0건)
  • Ladle GlobalProvider(Theme/Toast 래핑)를 .storybook/preview.tsx 데코레이터로 이식, light/dark 토글은 globals.theme로 대응
  • @storybook/react-vite + @storybook/addon-a11y 구성, @ladle/react 의존성 제거
  • 워크스페이스 패키지명은 storybook npm 의존성과의 자기참조 충돌을 피해 storybook-app
  • 더블클릭 실행용 run-storybook.bat + 바탕화면 바로가기

참고

  • 커밋이 35개로 보이지만, PR feat(demo): WONIK Fab Monitor 데모 + 1차 polish 사이클 #16(이미 머지)이 squash merge라 그 이전 커밋들이 main에 개별로 존재하지 않아 함께 표시되는 것. 실제 diff는 38개 파일 = Storybook 교체뿐입니다.
  • origin/main 머지 커밋(51b5a17)으로 충돌 해소 완료 — 4개 충돌 모두 본 브랜치(최신) 채택, 순 파일 변경 없음.

Test plan

  • pnpm -F storybook-app dev → localhost:6006 카탈로그 정상
  • 상단 툴바 light/dark Theme 토글 동작
  • pnpm -F storybook-app build 통과 (스토리 25개 컴파일)

🤖 Generated with Claude Code

ohjihoon05 and others added 30 commits May 15, 2026 13:46
… dogfood로 ADR-018 #4 supersede (ADR-019)

- packages/web/package.json: dependencies."@wonik/tokens" 제거 → peerDependencies + devDependencies 조합으로 정상화 (토스/MUI/Radix 패턴)
- ppt 0.0.0 → 0.1.0 마이그레이션 trial 7에서 ERR_PNPM_FETCH_404 발견 후 정상화. host install 통과 + Vite HMR 통과 검증
- ADR-019 신설(packaging) + dogfood-trial-7.md 신설(사이클 기록) + .gitignore에 *.tgz 추가
- monorepo dev: web → tokens link는 devDependencies로 보존. sandbox 무영향.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- ADR.md: ADR-018 항목 #4(peer contract)에 ADR-019 supersede 인용박스 추가, "dependency도 유지" 부분 strikethrough
- INSTALL.md: 두 패키지 host 직접 install 필수 강조 박스 (peer-only contract — 토스 TDS / MUI / Radix 표준)
- QUICKSTART.md: 1번 점검에 vendor tarball / published / workspace 3가지 시나리오 분리 명시 + 옵시디언 노트 경로 갱신 (Frontend/ → Design-System/, 노트 3개 모두 명시)

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- 매 turn마다 '다음 뭐 할지' ad-hoc 결정하던 패턴 → ROADMAP을 reference하는 패턴으로 전환
- 5섹션 구조: 현재 위치 / 즉시 액션 / 사용자 결정 보류 / 큰 milestones / 의존성 매트릭스
- PRD.md '1단계 진행 현황' 섹션은 phase history만 남기고 단기 액션은 ROADMAP에 위임. stale "병행 가능" 줄 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- dogfood-trial-8.md: ppt Home.tsx GenerationForm의 4 element(Textarea 1 + Button 3, verbosity chip 5 포함) substitution 검증
- 발견 F5: 라인 절감(-18%)보다 결정 절감(색·border·hover·상태 ternary 사라짐)이 본질
- 발견 F6: chip selector는 Button variant 토글로 충분, ToggleGroup atom 보류 (evidence 2/3)
- 발견 F7: host 측 inline px(margin) 잔존 — 후속 cycle로 분리
- ROADMAP: Layer B(사내 registry/Changesets/publish/dual-direction)를 layer A 사이클 마무리 후 마지막 phase로 격하. 사용자 결정 보류 4개→3개, 의존성 매트릭스 9행→5행.
- 위치 표에 "다음 action" 컬럼 추가
- Layer A atom 14개를 분류 표로 시각화 (레이아웃/액션/입력/상태/로딩/표시)
- 결정 보류 표에 "추천 (왜)" 컬럼 추가
- M1 시각 통일을 step별 시간 견적 표로 (1+5+15+5+5 = 31분)
- M2 evidence 표에 출처 trial 명시
- M3 dogfood 후보에 추정 시간 추가
- cleanup 거리 4개 → 2개로 압축 (Dockerfile은 trial 8에 흡수, PORTFOLIO는 사용자 파일이라 제외)
- trial 1~8 + recapture 누적 history 표 신설
- reference에 핵심 ADR(010, 011, 019) 강조
- dogfood-trial-9.md 신설: phase A(1줄 minimal) → contrast 1.7:1 fail → phase B(theme 활성화 + 5 vars 정리)로 격상
- 발견 F9: light/dark dual에서 "변수 N개"가 단위가 아니라 "theme 활성화 + 책임 vars 일괄"이 단위
- 발견 F10: ppt hand-rolled dark vars 5/6 우리 dark tokens과 ~95% 동일 (disposable 손작업)
- 발견 F11: :root vs [data-theme="dark"] specificity 동일 — host overrides 제거 필수
- 발견 F12: --color-primary-hover를 var(--color-primary-selected) alias로 위임 — 마이그레이션 패턴
- 발견 F13: ADR-009 dual mode 약속이 host 활성화로 첫 실증
- ROADMAP: M1 ✅ 마감, 결정 보류 ① 종결, trial history 9건으로 갱신
…riven cycle 한 바퀴 closure

- dogfood-trial-10.md 신설: trial 4(branch-only, 미머지) 후속 + Outline에 새 atoms 첫 적용
- Outline.tsx 10 element + OutlineCard.tsx 6 element 교체
- Card 4회 적용 (loading/generating/generation/empty wrapper) — trial 4 H3 evidence "Card 4+ 반복" 정확 입증
- Alert 3 instance (Outline 2 + Home 1, 누적 -34줄) — 두 번째 host 페이지에서도 가치 동일
- Badge 첫 외부 적용 (layout indicator) — Phase 14 Badge atom 추가가 trial 10 적용으로 사이클 closure
- 발견 F14: branch dogfood는 main 자동 머지 안 됨 — 패턴 재정의
- 발견 F15-F17: trial 4 H3 예측이 trial 10 적용으로 정확히 매칭, evidence-driven 사이클 첫 입증
- 발견 F18: dashed border / hover-border-highlight polish 손실 — atom 후보 evidence 누적
- ROADMAP: trial 10 history 추가, evidence 표 갱신 (dashed/hover-Card 신규 등록 2/3, 1/3)
…i-pattern 제거 + Layer A 사이클 마감 직전

- dogfood-trial-11.md 신설: trial 6 미완 영역 12 element 일괄 청소
- 발견 F19: AI 수정 button의 hardcoded 보라 #8b5cf6은 host가 우리 status-processed-text 토큰을 모름에서 발생 — token vocabulary 공유 패턴
- 발견 F20: dashed Add Slide evidence 3/3 trigger 도달, 그러나 본질 needs 약함 — 격하 reaffirm
- 발견 F21: EditableField sub-component 1회 substitution × 4 instance — page-level dogfood보다 4배 coverage
- 발견 F22: Spinner child + Button parent 패턴이 raw inline svg 5줄 청소
- 발견 F23: host 측 inline px 누적 4회 — Stack gap 권장 가이드 후보
- ROADMAP: trial 11 history 추가, host page dogfood 컬럼 ✅ 마감, evidence 표 갱신, dashed 격하 reaffirm 표시
- ROADMAP.md:
  - 헤더: '🏁 Layer A 1차 사이클 마감 선언 — 11 trial 누적'
  - 현재 위치: Layer A 모든 영역 ✅, 다음 큰 단계 = Layer B(회사 PC + IT 협의 대기)
  - 새 섹션 '🏁 Layer A 마감 — 얻은 것': 정량(14 atoms × 3 host pages × 11 trials × anti-pattern 2종 제거) + 정성(evidence cycle closure / trigger ≠ atom 추가 / dual mode host 활성화 / token vocabulary 흐름) + 검증된 ADR 6종
  - 다음 호흡 simplified: cleanup 3종 + doc 갱신 2종 + (선택) sub-component dogfood
  - 새 섹션 '▷ Layer B 진입 준비': ADR-018 6단계 흐름, 진입 신호 대기 명시
  - milestone 섹션을 history reference로 격하
- PRD.md:
  - 진행 현황 헤더에 'Trial 7~11 closure — 2026-05-15' 추가
  - Phase 17 후속 dogfood 사이클 (Trial 7~11) 5줄 요약 추가
  - Layer A 1차 사이클 마감 + Layer B 진입 의존 명시
- ppt 측 변경 없음 (선언만)
… + Changesets)

사용자 통찰("집에 구성을 회사에서 할 수 있는 것처럼 할 수 있잖아? 나 컴퓨터 안 꺼")에서 출발. ADR-018 옵션 A(사내 npm registry) 흐름을 집에서 100% dry run.

8 step 한 바퀴:
1. Verdaccio @ 127.0.0.1:14873 (Windows excluded port range 4866-4965 회피)
2. Changesets 누적 8개 적용 → tokens 0.1.0→0.2.0, web 0.1.0→1.0.0
3. publishConfig.registry → 14873
4. 첫 publish 성공 (peer-only contract 자동 caret 갱신 ^0.2.0)
5. ppt vendor tarball → registry install (3.7초)
6. dual-direction patch: tokens 0.2.0→0.2.1 → ppt pnpm update (1.5초)
7. ADR-018 + ROADMAP + dogfood-trial-12.md
8. (현재) commit

핵심 발견:
- F25 changeset publish 자동 skip (변동 없는 web 1.0.0 자동 제외)
- F26 caret semver가 dual-direction 메커니즘 (1.5초 자동 흐름)
- F27 peer-only contract publish 시도 정합 (ADR-019 입증)
- F28 누적 5 minor → 0.1.0 → 1.0.0 (first stable cut, 받아들임)

결정:
- ADR-018 옵션 A 1순위 reaffirm — 사내 진입 시 URL 2 곳만 교체로 끝
- web 1.0.0 stable cut 받아들임 (14 atoms × 11 trials evidence 정당화)
- web-sandbox 0.0.2 자동 patch (changesets internal dep 추적)

ROADMAP "회사 PC + IT 협의 대기" → "URL만 교체 대기" 갱신.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
token이 commit history에 들어가지 않도록 untrack:
- .npmrc → .gitignore의 기존 패턴이 신규 파일만 ignore했으므로 git rm --cached로 명시 untrack
- .npmrc.example은 이미 존재 (Phase 17 산출물). 사내 진입 시 사용.
- ⚠️ 직전 commit (4ccf43e)에는 token이 한 번 들어간 채 남음. push 시점에 amend/rebase로 정리 결정.

F28 (web 0.1.0 → 1.0.0 점프) 조사 결과 추가:
- WebSearch: changesets/changesets#1011, #555, action#358 — 알려진 버그/edge case
- tokens는 같은 5 minor인데 0.2.0 정상 → 정책 아님 = 버그 의심
- 결론: 14 atoms × 11 trials evidence가 first stable cut 자격에 부합 → 받아들임
- 향후 patch publish에서 재현 시 upstream issue reproduction 후보

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
design system의 17개 컴포넌트를 활용한 엔터프라이즈 모니터링 데모
(apps/demo-fab-monitor)와, 데모 사용 중 발견된 잔여 이슈를 한 사이클에
정리한 polish 패치. 외부 UI 라이브러리 0개, design system + inline SVG로만 구성.

데모 (apps/demo-fab-monitor)
- 4 page: Dashboard / Equipment / Alarms / Settings + Sidebar nav + Topbar
- Mock 장비 10대(CVD/ETCH/PVD/ALD/CMP) + setInterval 시뮬레이션
- ThemeProvider 라이트/다크 토글 (localStorage persist)
- 14개 inline SVG 아이콘 (decorative emoji 금지 원칙)
- 데모 내부 primitive: FilterChip, IconButton, Avatar, NavButton, UtilizationChart,
  StatusBadge, ProgressBar — design system 본체 승급은 별 spec

Polish (specs/demo-fab-monitor-polish)
- spec.md: 데모 사용 중 발견된 부족점·문제점 인벤토리 (A1~A6, R1~R4, M1~M4, V1~V3)
- plan.md: 데모 내부 primitive 추출 + ensureCssInjected 패턴 통일 전략
- tasks.md: 18 task, 의존성 순서

주요 수정
- A1~A5: 9개 인터랙티브 요소에 :focus-visible outline 적용
  (CLAUDE.md CRITICAL, WCAG 2.4.7 AA, ADR-011)
- A6: Topbar LIVE dot box-shadow glow 제거 → outline ring 1px
  (UI_GUIDE anti-slop 회피)
- R1: 알람 cap-out 후 selectedAlarmId dangling 처리 — seenIds ref + isAlarmExpired API,
  AlarmsPage가 ExpiredPlaceholder graceful 표시
- R2: createAlarmsSeed(now) factory — module top-level Date.now() 제거
- M1: onMouseEnter/onMouseLeave 8개 → 0개, 모두 module-scope CSS inject 패턴으로 통일
- M3/M4: IconButton, Avatar 추출, 중복 사이트 일괄 교체
- V1: 24h utilization 시계열 line chart 추가 (inline SVG + grid + target + legend)
- V2: Equipment detail Open in console/History 버튼 placeholder 핸들러
- Major review fix: Topbar Bell 버튼 onClick → Alarms 페이지 이동

검증
- pnpm -F demo-fab-monitor build 통과 (TS strict, 52 modules, 257 kB JS)
- dev server http://localhost:5175 정상
- 외부 라이브러리 신규 도입 0개
- 보안 surface 0 (로컬 정적 데이터, 새 input/fetch/eval 없음)

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
시각 점검 사이클에서 발견한 layout 함정 두 family를 처방:

- over-grow (자식이 부모 밖으로 부풂): EquipmentRow의 `minWidth: 100` 함정 → `minWidth: 0` + StatusBadge wrapper에 `flexShrink: 0`. EquipmentPage의 grid `'1fr'`이 콘텐츠 min-content로 부풀어 Card가 viewport 넘침 → `'minmax(0, 1fr)'` + table cell `whiteSpace: nowrap` + `width: 100%` 제거(자연 폭 + wrapper overflowX:auto).

- over-shrink (자식이 한 글자씩 세로로 squeeze): AlarmsPage의 grid `'minmax(0, 1fr)'`이 부모 좁을 때 fr 트랙을 0까지 squeeze 허용 → detail panel 글자 세로 흐름. `'repeat(auto-fit, minmax(min(360px, 100%), 1fr))'`로 좁으면 1-column stack 강제.

UI_GUIDE.md `## Web 레이아웃` 아래 "Layout 함정 가드 (Flex / Grid)" subsection 추가 — 두 family + 사례 파일 경로.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
WONIK BLUE/GRAY가 packages/tokens/src/ canonical 정의 외 추가로 박혀 있으면 critical alert. 손-실험으로 30분 걸리던 cascade 끊김 찾기를 `pnpm tokens:duplicates` 1초로 압축.

- scripts/token-duplicates.mjs (~50줄 ESM Node script): canonical 리스트 + hex grep + 주석 line skip. critical 발견 시 exit 1.
- package.json: "tokens:duplicates" script entry.

현재 검출 결과 — 4곳 cascade 끊김 확인 (이전 손-실험 발견과 일치):
- statusTokens.run.dot/text (theme-light.ts:57)
- statusTokens.accessReady.text (:59)
- graphTokens.signalTuned.line (:77)
- graphTokens.signalReference.line (:78)

후속: web/sandbox 코드 hex literal까지 확장 검토 (CLAUDE.md CRITICAL 컴포넌트 hex 박힘 금지 detection).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
apps/web-sandbox/src/App.tsx:532 validation 에러 메시지의 인라인 `color: '#CC0000'` 를 `var(--color-status-alarm-text)` 로 교체. `color=\"brand\"` prop은 어차피 inline style로 덮였던 거라 같이 제거.

배경: token-duplicates 확장 검토 차 컴포넌트/sandbox 영역에 hex literal grep — packages/web/src·apps/demo-fab-monitor 0건 (CRITICAL 준수), apps/web-sandbox에서 7곳 발견. 그 중 line 955-986 로그인 카드 hex 6곳은 \"토큰 vs raw 비교\" 의도된 anti-pattern demo (line 986 ⚠ 주석)라 그대로 두고, line 532 한 곳만 실수로 박힌 거라 정리.

Script 확장(packages/web/src 자동 scanning)은 현재 위반 0건이라 ROI 낮음 — 도구 성숙도 더 올라간 뒤로 보류.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
demo-fab-monitor 한 바퀴 dogfood로 internal 4개 승격 후보가 가시화 + PRD §1.4.2 명시 6개 + ROADMAP M2 명시 Tooltip 1개 = 총 11개 부족 컴포넌트 식별. 다 채우면 14 + 11 = 25개 → Storybook(15+) + Chromatic(20+) 트리거 둘 다 충족.

- Layer A (PRD 핵심 6): Modal · Toast · Tabs · Select · SortableList · InlineEditableText
- Layer B (M2 명시 1): Tooltip
- Layer C (demo 승격 4): IconButton · FilterChip · Avatar · ProgressBar

총 추산 16-22h — 한 세션 불가. 5-phase sub-batch로 분배 (Phase 1 IconButton부터 Phase 5 ppt-derived까지). 각 phase 끝나는 시점에 atom 카운트로 도구 트리거 검토 가능.

demo 승격(Layer C)이 가장 안전한 첫 단계 — dogfood로 실제 사용 패턴 + 토큰 정합도 이미 검증.

최종 갱신 일자 2026-05-15 → 2026-05-18 (demo-fab-monitor 사이클 + token-duplicates + M2.b 정리).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…b Phase 1)

ROADMAP §M2.b sub-batch Phase 1. demo-fab-monitor 한 바퀴 dogfood로 internal IconButton의 토큰 정합·사용 패턴이 검증된 상태 (C tier — 가장 안전한 첫 단계).

API (CLARIFY 9 결정):
- variant: 'outline' (default, border 1px solid interfaceColors.border) / 'ghost' (border none)
- size: 'sm' (24×24) / 'md' (32×32, default) / 'lg' (40×40) — Button.tsx 패턴 답습으로 inline literal
- 'aria-label' 타입 레벨 필수 (WCAG 4.1.2)
- children: ReactNode (icon 1개, host가 size 명시)

시각 토큰:
- interfaceColors.text · interfaceColors.border · radius.sm
- :hover var(--color-surface-subtle, transparent), :focus-visible 2px solid var(--color-focus) (ADR-011)
- disabled opacity 0.5

거부된 대안: badge 내장 (Badge atom 조합), semantic 색 variant, icon size 자동, loading/tooltip, forwardRef.

테스트: 16 신규, 회귀 0. atom 14 → 15. Storybook(15+) trigger 통과.

phases/18-icon-button/spec.md + .changeset/18-icon-button.md (@wonik/web minor). demo dogfood (3 자리)는 후속 commit에 통합.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…2.b Phase 2)

ROADMAP §M2.b sub-batch Phase 2. demo-fab-monitor 한 바퀴 dogfood로 internal 3개의 토큰 정합·사용 패턴이 검증된 C tier 묶음 승격. atom 15 → 18 도달.

FilterChip (3 페이지 evidence — AlarmsPage 탭·EquipmentPage 다중필터·SettingsPage theme):
- active?: boolean — aria-pressed 자동
- 시각: inactive interfaceColors.surface/border/text, active primary.bg/selected + interfaceColors.primary border
- :hover surfaces.surfaceSubtle, active:hover primary.bg 유지
- single·multi-select 분기 X (host가 toggle 책임)

Avatar (1 사용처 — Sidebar 김기현):
- name: string (required) + size?: number (px, default 32) + color?: string (default interfaceColors.primary)
- Array.from(name.trim())[0] — surrogate-pair safe
- aria-hidden 기본 (인접 텍스트 노출 가정), aria-label 제공 시 자동 off
- size를 sm/md/lg tier로 박지 않음 — header/list/profile 자유 size 본성

ProgressBar (3 사용처 — EquipmentRow primary, EquipmentPage 작은/큰 ×2):
- value: number (0-100 자동 clamp) + height?: number (default 4) + tone?: 'primary'|'alarm'|'ready'
- role='progressbar' + aria-valuenow/min/max 자동
- 트랙 surfaces.surfaceSubtle, 필 interfaceColors.primary | statusTokens.alarm.text | statusTokens.ready.dot
- borderRadius: height (자연 pill), transition width 600ms

거부된 대안:
- FilterChip: variant filled/outlined, size tier, leading/trailing icon prop
- Avatar: src 이미지 fallback, shape variant, status indicator
- ProgressBar: tone 확장(warning/idle/init), label 내장, indeterminate, vertical

테스트: 37 신규 (FilterChip 10 + Avatar 13 + ProgressBar 14). 회귀 0. atom 15 → 18.

phases/19-c-tier-promotion/spec.md + .changeset/19-c-tier-promotion.md (@wonik/web minor). demo 7 사용처 dogfood + internal 3 파일 삭제는 후속 commit에 통합.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ix (M2.b Phase 4 부분 closure)

ROADMAP §M2.b sub-batch Phase 4 부분. demo-fab-monitor에 진짜 자연 자리 5개(Toast 4 + Modal 1) 발견 → dogfood-driven 동시 도입. 동시에 Phase 18+19 atom 6개의 demo dogfood + 시각 검토에서 발견한 layout 회귀 4종 fix 통합.

atom 18 → 20 도달. Storybook(15+) + Chromatic(20+) trigger 모두 통과. M2.b Phase 1+2+4(부분) 묶음 closure.

=== Toast (Provider + hook + view) ===
- ToastProvider — context provider. position 'top-right'|'bottom-right'|'top-center'|'bottom-center' (default bottom-right) + max=5 (oldest-drop)
- useToast(): { show, dismiss, dismissAll } — Provider 밖에서 throw
- tone: 'info'|'success'|'warning'|'error' → interfaceColors + statusTokens.ready/warning/alarm
- duration?: number | null (default 4000ms, 0/null = persistent)
- action?: { label, onClick } — Undo 패턴
- a11y: role='status' (info/success/warning) / role='alert' + aria-live='assertive' (error). 스택 region role='region' + aria-label='알림'
- 진입 애니메이션 0 (UI_GUIDE slop 회피)

=== Modal (controlled portal) ===
- isOpen + onClose controlled. title? 제공 시 header 자동 + aria-labelledby 연결
- size: 'sm'|'md'|'lg' (480/640/800 max-width). closeOnBackdrop + closeOnEsc (default true)
- compound: Modal.Body + Modal.Footer (padding/border 표준화)
- createPortal → document.body (SSR 가드)
- focus trap 자체 구현 — 첫 focusable 자동 focus + Tab/Shift-Tab 순환 + close 시 트리거 복귀
- body scroll lock (open 시 overflow:hidden)
- backdrop: rgba(0,0,0,0.6) + zIndex 9999 (top/left/right/bottom 명시) — sidebar/topbar 포함 viewport 전체 덮음 검증
- a11y: role='dialog' + aria-modal='true'

=== demo dogfood (총 15 자리) ===

Phase 18 IconButton (3 자리):
- Topbar 알림벨 + 테마토글 (outline default)
- EquipmentPage close (ghost)

Phase 19 (7 자리):
- FilterChip: AlarmsPage 5탭 + EquipmentPage 다중필터 + SettingsPage theme
- Avatar: Sidebar 김기현
- ProgressBar: EquipmentRow + EquipmentPage 작은/큰

Phase 20 (5 자리):
- main.tsx ToastProvider position='top-right' 감싸기
- useSimulation: 새 alarm 발생 시 severity→tone 매핑 toast.show
- AlarmsPage Acknowledge → success toast
- AlarmsPage Close alarm → Modal confirm(size='sm', danger Button) → 확인 시 closeAlarm + Undo action toast
- SettingsPage Save changes → success toast

internal 4 파일 삭제: components/internal/{IconButton,FilterChip,Avatar}.tsx + components/ProgressBar.tsx

=== layout fix 4종 (시각 검토 후속) ===
- EquipmentPage grid: 좁은 viewport에서 list↔detail squeeze → 'repeat(auto-fit, minmax(min(360px, 100%), 1fr))' (UI_GUIDE 표준 over-shrink 가드)
- Toast width: 'min(420px, calc(100vw - 32px))' — 좁은 viewport 우측 잘림 가드
- Topbar 우측 액션 Stack: flexShrink: 0 (UI_GUIDE over-shrink 가드)
- Modal backdrop: alpha 0.5→0.6 + zIndex 1000→9999 + inset 0 → top/left/right/bottom 명시 → sidebar/topbar까지 덮음 시각 검증 통과

=== 측정 ===
- 신규 test 32 (Toast 14 + Modal 18). 전체 283 passing, 회귀 0.
- demo build 통과 (modules 51 → 49, internal 삭제 반영).

phases/20-toast-modal/spec.md + .changeset/20-toast-modal.md (@wonik/web minor)
ROADMAP §M2.b: Phase 1+2 ✅, Phase 4 (Modal+Toast) ✅, Phase 4 잔여 Select는 demo 자리 부족(현 inline <select> 1개)으로 다음 dogfood로 발견 시 진입. Phase 3 (Tooltip+Tabs)는 dogfood 부재 — host evidence 권장.

거부된 대안: Toast 진입 애니메이션, Modal drawer variant, Modal 외부 focus-trap 라이브러리 의존, useToast 외 imperative 전역 호출.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…a11y (M2.b Phase 3 부분)

ROADMAP §M2.b sub-batch Phase 3 부분 closure. Tooltip만 추가 (Tabs는 dogfood 자리 미발견으로 별도 phase 보류). atom 20 → 21 도달.

API:
- children: ReactElement (trigger, cloneElement로 핸들러 + aria-describedby 자동 wire)
- content: ReactNode
- placement?: 'top'|'right'|'bottom'|'left' (default 'top')
- openDelay?: number (default 200ms, flicker 방지)
- closeDelay?: number (default 0ms)
- disabled?: boolean

동작 (WCAG 2.1 SC 1.4.13):
- 노출 트리거: mouseenter + focus (키보드 접근성 보장)
- 숨김 트리거: mouseleave + blur + Escape
- pending openTimer는 mouseLeave/blur 시 cancel (짧은 hover 깜빡임 방지)
- cleanup에서 모든 timer clear

시각 (hex/px 리터럴 0):
- bg: interfaceColors.text (host inverted), color: interfaceColors.surface
- padding 6px 10px, borderRadius radius.sm, fontSize fontSize.label
- maxWidth 280px, zIndex 1000, pointer-events none

A11y:
- role='tooltip' + auto-generated id (useId)
- aria-describedby chain (사용자 정의 값 보존)

거부된 대안: portal(overflow:hidden 자르면 wrap 후속), 자동 flip(boundary detector 큰 단위), 진입 애니메이션(slop 회피), click trigger(별도 Popover atom 후보).

테스트: 19 신규. 회귀 0. atom 20 → 21.

QUICKSTART.md: host spacing 가이드 한 줄 추가 (Stack gap 권장, dogfood F7+F23 4회 evidence — host inline px 반복되면 wrap 후보).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
ROADMAP §M2.b sub-batch 마무리. atom 21개 모두 카탈로그 + 토큰 시각화 stories 작성. 디자이너·다른 개발자 onboarding surface + dogfood 외 시각 한곳 비교.

Ladle 채택 이유 (Storybook 대비):
- Vite 기반 → 우리 스택과 정합 ↑ (별도 build system 없음)
- 가벼움 (install 1분, deps 작음)
- CSF 표준 호환 → Vue 추가 시점에 Storybook으로 hours 단위 마이그레이션 가능
- Flutter는 어차피 Widgetbook 별개 — "한 도구 통합"은 불가능 (ADR-001 1단계 web만 유지)

apps/ladle/ 신규 워크스페이스:
- package.json (@ladle/react ^5.0.0 dev dep)
- tsconfig.json (base 상속)
- .ladle/config.mjs — appName, stories glob
- .ladle/components.tsx — GlobalProvider (ThemeProvider + ToastProvider + 24px padding wrap)
- .ladle/global.css — canvas bg, font, box-sizing reset
- src/stories/*.stories.tsx (23 file)

Stories 분류 (91 total):
- Intro (1): Welcome 화면
- Tokens (3+4): Color (Brand/Surface/Status/Text) · Spacing · Typography · Radius
- Layout (15): Box · Stack · Text 각 4-6 stories (variant/size 매트릭스)
- Action (15): Button · IconButton · FilterChip (Sizes/Variants/Matrix/Disabled/Active 등)
- Input (16): Input · Textarea · Checkbox · Radio · Switch (Sizes/States/Disabled)
- Feedback (9): Spinner · Skeleton · Alert (Sizes/InContext/CardPattern/Dismissable)
- Display (14): Card · Badge · ProgressBar · Avatar (Variants/Tones/Heights/Sizes/Names/Colors)
- Overlay (14): Modal · Toast · Tooltip (Basic/Sizes/Placements/Tones/WithAction)

Provider:
- ThemeProvider — globalState?.mode === 'dark' ? 'dark' : 'light' (optional 체이닝 첫 mount 가드)
- ToastProvider position="top-right"
- 24px padding wrap for story isolation

스크립트:
- pnpm -F ladle dev (localhost:61000)
- pnpm -F ladle build (정적 카탈로그 빌드)

ROADMAP 갱신: Ladle 도입 ✅ 2026-05-19, atom 21개 카탈로그 surface 확보.
Chromatic은 사내 CI(GitHub Actions 사내 권한) 필요로 별도. 자격(20+ atom · consumer 2+)은 통과.

거부된 대안:
- Storybook 지금 도입 — Ladle보다 무겁고 config 많음. MVP scope creep. Vue/Flutter는 ADR-001대로 다음 단계.
- Histoire — Vue 친화. React 우선이라 Ladle 적합도 ↑.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- radius.panelHeader 삭제 — 사용처 0개의 dead token. shorthand 값
  ('10px 10px 0 0')이라 DTCG 1:1 매핑 차단. build-css.ts special case도 제거.
- docs/TOKEN_DTCG_COMPAT.md 작성 — source-of-truth 위치, $type 매핑표,
  fontFamily.base 예외(comma-separated string) 1줄 special-case 안내.
- ADR-020 작성 — "DTCG JSON emit 미채택, 구조만 1:1 매핑 유지" 결정 기록.
  YAGNI: 변환기는 외부 trigger(Figma Variables / Style Dictionary / 사외
  디자이너 요청) 시 30분 내 작성 가능한 형태 보장.
- CLAUDE.md CRITICAL에 DTCG 룰 1줄 추가 — 새 토큰 leaf 1개 = \$value 1개,
  shorthand·template 합성 금지, group 안 동일 \$type.
- apps/ladle/build/ gitignore 처리 — 빌드 산출물 git index 제거.

검증: pnpm -F @wonik/tokens build OK (light/dark 각 75 vars),
pnpm -F @wonik/web test --run OK (302 tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…M2.b Phase 5a)

ppt OutlineCard.tsx 75-93줄의 inline 편집 boilerplate(useState×2 + useEffect
sync + Input+keydown+blur 토글 30줄)를 한 줄로 흡수.

핵심:
- Controlled API: value + onChange(next), Enter=save, Escape=cancel, onBlur=save
- trigger: 'single' (default) | 'double' — single-click 또는 double-click 진입
- saveOnBlur: true (default) | false — blur 시 save vs cancel
- selectOnFocus: true (default) — 편집 진입 시 select-all (rename UX 표준)
- as prop: 'span' (default) | 'p' | 'h1'-'h4' — semantic tag
- size pass-through: sm/md/lg → 내부 Input atom

a11y:
- 표시 모드 role="button" + aria-label (value fallback)
- Enter/Space로 키보드 진입 (WAI-ARIA button pattern)
- focus indicator outline 2px (WCAG 2.4.7 AA)
- disabled tabIndex=-1 + 진입 차단

edge cases:
- 편집 중 외부 value drift → draft 우선 (ignore)
- 비편집 시 외부 value drift → 즉시 sync
- Enter + onBlur 동시 trigger → committedRef guard로 중복 호출 0
- 빈 value → placeholder 색 자동 전환 (interfaceColors.textMuted)

토큰: 모두 interfaceColors/fontSize/fontFamily 경유 — hex/px 하드코딩 0.
CSS inject: ADR-011 module-scope 1회-inject (hover/focus pseudo).

테스트: 16 tests 모두 PASS (fireEvent 패턴, 프로젝트 표준 따라감).
전체 회귀: 302 → 318 (+16 새 atom tests).

dogfood 자리(별도 ppt PR 예정): OutlineCard 30줄 → 3줄 교체.

ROADMAP §M2.b Phase 5 → 5a (InlineEditableText, 본 PR ✅) / 5b (SortableList, 보류) 분할.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
… closure)

ROADMAP §M2.b 완료. ppt 두 자리(Presentation SortableThumbnail ~120줄 +
OutlineCard 화살표 button ~40줄)를 generic atom으로 흡수.

핵심:
- Generic API: items + getKey + renderItem(item, idx, args) + onReorder(from, to)
- renderItem args: dragHandleProps / isDragging / setNodeRef / itemStyle
- direction: vertical (default) | horizontal
- disabled: drag/keyboard 차단하되 renderItem은 그대로 호출 (consumer 대체 UI 가능)
- 핸들 위치는 consumer 결정 — 전체 element 또는 ⠿ 분리 모두 지원

a11y (WAI-ARIA drag pattern):
- 한국어 announcements 4개 default (drag start/over/end/cancel)
- role="button" + aria-roledescription="sortable" + tabIndex=0 자동 (dnd-kit)
- Space grab → 방향키 move → Space/Enter drop → Esc cancel
- disabled=true → aria-disabled

의존성 추가 (regular dep, peer 아님):
- @dnd-kit/core@^6.3.1
- @dnd-kit/sortable@^9.0.0
- @dnd-kit/utilities@^3.2.2
ppt와 동일 lock 버전 정렬. sideEffects:false + tree-shakable.

테스트: 12 tests 첫 시도 PASS (렌더링 3 / a11y 3 / reorder logic 2 / controlled 1 / disabled 2 / horizontal 1). jsdom 한계로 실제 drag simulation 대신 callback level + ARIA attribute 검증.

전체 회귀: 318 → 330 (+12). Build: ESM 59.81KB / CJS 63.31KB / DTS 22.56KB.

토큰 하드코딩: 0 (dnd-kit standard CSS는 opacity:0.4/zIndex:10 numeric literal만).

Ladle stories 5개: Basic / WithCustomHandle / Horizontal / Disabled / RealisticOutlineCard
(마지막은 Card + Badge + InlineEditableText[Phase 23]와 조합한 ppt OutlineCard 시뮬레이션).

dogfood 자리(별도 ppt PR 예정):
- Presentation.tsx SortableThumbnail 패턴 → SortableList 1 wrap (120→20줄)
- OutlineCard.tsx 화살표 button → drag handle 마이그레이션

ROADMAP §M2.b 표 Phase 5b ✅ 마킹 — 11개 부족 컴포넌트 모두 완료. atom 21→23.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…l 13

Layer A 1차 사이클 마감 commit 거리 도달 — atom 23 × ppt 4자리 적용
(Home/Outline/Presentation main + OutlineCard 인라인편집 + Presentation
SortableThumbnail).

- 누적 changeset 7개 publish (Verdaccio 14873 dry run, ADR-018)
- workspace consumers(demo/ladle/sandbox) caret cascade patch
- docs/dogfood-trial-13.md 신규 + ROADMAP trial 13 한 줄 추가
- F29 Verdaccio IPv4 bind + F30 changeset major 점프 (별도 거리, 본 trial doc 기록)

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
trial 13 사용자 시각 검증 통과:
- OutlineCard 인라인 편집 (Phase 23) — click/Enter/Esc/blur 4동작 정상
- Presentation 좌측 썸네일 drag (Phase 24) — drag 순서 변경 정상

영역 표에 "Layer A — 2차 사이클 마감 (atom 23 closure)" 줄 추가.
헤더 갱신일 + "trial 13으로 Layer A 2차 사이클 마감" 표현 반영.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…daccio IPv4)

Layer A 마지막 폴리시 패스 doc 갱신:

- ADR-007 보강: status.processed = #7C3AED 보라는 AI 처리 semantic 채택. brand
  color 아닌 status 카테고리라 ADR-007 변경 없음. trial 11에서 raw hex
  #8b5cf6 anti-pattern 제거하고 var(--color-status-processed-text) token으로
  흡수한 결정 정착. raw 보라 hex 금지 명시.

- ADR-018 보강: Verdaccio 단축 표기 --listen <port>는 IPv6 ::1만 bind →
  IPv4 127.0.0.1 publish 시 ECONNREFUSED. 해결: --listen 0.0.0.0:<port> 명시.
  사내 서버 셋업 시 IPv4 bind 보장 필요 (trial 13 재현 시 발견).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Layer A 마지막 폴리시 패스 마무리:

- atom 후보 4종 동결 — ToggleGroup 2/3 + FormField 1/3 + RadioGroup 1/3 +
  Card hover 1/3. ppt에 추가 evidence 누적 없음 (label/errorMessage/radio/
  cursor-pointer+Card 패턴 0건 grep 확인). 1단계 atom 23으로 충분. 새 host
  product 등장 시 evidence 재누적 가능.

- ADR-018 F30 publish prep 체크리스트 추가 — Layer B 진입 전 표준 절차:
  1) pnpm changeset status로 예상 bump 사전 확인 2) 의도 외 major 발견 시
  changeset frontmatter 정정 후 retry 3) snapshot dry-run 옵션. Semver
  연속성보다 신뢰 우선 (1.x → 2.x 건너뜀 비용 수용).

- ROADMAP 영역 표에 "Layer A — 마지막 폴리시 패스" 줄 추가.
  헤더 "Layer A 완벽 상태 도달" 표현 반영.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
ralph-loop 동작 시 생성되는 부산물(`.claude/ralph-loop.local.md`,
`.claude/scheduled_tasks.lock`)이 staging에 노출되지 않도록 ignore 추가.

- `.claude/commands/`, `.claude/settings.json`은 commit 대상이라 폴더 통째로
  ignore하지 않고 두 파일만 명시적으로 ignore.
- 기존 `.claude/settings.local.json` (사용자별 로컬 설정) ignore와 같은 layer.

Layer A 폴리시 패스 후속 hygiene.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…nge 정합

- §'다음 호흡' 표에 polish #1 추가 — SortableList의 한국어 a11y announcement이
  raw `active.id`(UUID 등)를 그대로 노출하는 한계. host가 의미있는 label 주입
  가능하게 `announceItem?: (id, items) => string` prop 추가 거리. minor
  changeset 분류. 우선도 낮음 (흐름 동작은 OK, 발음만 어색).
- §'reference' 의 dogfood-trial 범위를 11 → 13으로 정합 수정 (trial 12 Verdaccio
  + trial 13 Phase 23/24 흡수가 누락된 상태였음).

Layer A polish 패스의 hygiene 정합.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
daily-work-planner and others added 5 commits May 20, 2026 09:47
…endly Korean a11y label

ROADMAP §'다음 호흡' polish #1 closure.

### 문제

Phase 24의 SortableList는 dnd-kit announcement을 한국어로 1:1 mapping
(`${active.id} 항목을 잡았습니다...`)했으나, 실제 host(ppt Presentation
thumbnail 등)는 `slide.id`가 UUID-like string. 스크린리더가
"abc-123-def 항목을 잡았습니다"처럼 raw id를 그대로 발음 — 의미 전달 약함.

### 변경

`announceItem?: (id: string) => string` prop 추가. 기본은 id 그대로(`(id) => id`)
이므로 기존 사용처 영향 0. host는 의미있는 label 변환 함수를 주입할 수 있음:

```tsx
<SortableList<SlideSchema>
  items={slides}
  getKey={(s) => s.id}
  announceItem={(id) => `슬라이드 ${slides.findIndex(s => s.id === id) + 1}`}
  /* ... */
/>
```

스크린리더가 "슬라이드 3 항목을 잡았습니다. 방향키로 이동..." 식으로 의미 전달.

### 내부

- `KOREAN_ANNOUNCEMENTS` const → `makeKoreanAnnouncements(labelOf)` factory.
- SortableList 내부에서 `useMemo`로 announceItem 변동 시에만 announcements 재생성.

### 검증

- packages/web: **331 tests passing** (Phase 24의 330 + announceItem smoke 1).
- backward-compatible — 기존 9개 사용처(ppt + ladle story + sandbox) 영향 0.
- changeset minor (`.changeset/sortable-list-announce-item.md`). 사내 진입 시
  web 2.0.0 → 2.1.0 publish 거리.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Phase 25 announceItem prop의 dogfood-friendly 예시 카탈로그화.

- id가 'b3f9-a01' 같은 UUID-like 일 때 announceItem 미사용 시 스크린리더가
  "b3f9-a01 항목을 잡았습니다..." 식으로 raw id를 발음하는 한계 시연.
- announceItem={(id) => `슬라이드 ${idx + 1}`} 적용 후 "슬라이드 1 항목을
  잡았습니다..." 로 의미 전달 향상.
- caption으로 before/after 발음을 명시 — host 개발자가 prop 효과를 즉시 인지.

Ladle build: SortableList story 4.68KB → 5.84KB (+1.16KB) — 카탈로그 hygiene OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
직전 'Layer A 마지막 폴리시 패스 완료' 선언(2026-05-19) 이후
- announceItem prop 추가 (Phase 25 / web 2.1.0 changeset)
- ROADMAP §'다음 호흡' polish #1 closure
- ladle WithAnnounceItem story 카탈로그화

까지 추가됐으므로 갱신 라인을 hygiene 측면에서 한 줄 갱신.
Layer A는 여전히 '완벽 상태' — polish는 evidence-driven cycle 외 hygiene 후속.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
apps/ladle/ → apps/storybook/ 전환. CSF 포맷 스토리 25개를 그대로
재사용하고 Ladle GlobalProvider를 .storybook/preview.tsx 데코레이터로
이식 (light/dark 토글은 globals.theme 로 대응). @storybook/react-vite
+ addon-a11y 구성, @ladle/react 의존성 제거. 워크스페이스 패키지명은
storybook npm 의존성과의 자기참조 충돌을 피해 storybook-app 으로 지정.
더블클릭 실행용 run-storybook.bat 추가.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
PR #16(d29d10f)이 Fab Monitor 데모·Ladle 도입 등 본 브랜치의 이전
슬라이스를 main에 먼저 반영하며 생긴 충돌 해결. 본 브랜치가 전부 최신
버전이라 4개 파일 모두 본 브랜치를 채택 — 순 파일 변경 없음:
- .gitignore / docs/ADR.md / docs/ROADMAP.md: Ladle→Storybook 한 줄
- pnpm-lock.yaml: pnpm install 로 재정합
main이 되살린 apps/ladle/ 제거 (apps/storybook/ 으로 교체 완료 상태).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@ohjihoon05
ohjihoon05 merged commit 9b9ce37 into main May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant