@makitt.io/mds-mcp-server 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +151 -34
  2. package/dist/catalog.d.ts +16 -0
  3. package/dist/catalog.d.ts.map +1 -0
  4. package/dist/catalog.js +383 -0
  5. package/dist/catalog.js.map +1 -0
  6. package/dist/data/catalog.json +41955 -4439
  7. package/dist/data/playbook/ai-fill.md +61 -48
  8. package/dist/data/playbook/anti-patterns.md +112 -110
  9. package/dist/data/playbook/array-input.md +94 -49
  10. package/dist/data/playbook/async-states.md +71 -61
  11. package/dist/data/playbook/data-grid.md +118 -101
  12. package/dist/data/playbook/feedback.md +103 -84
  13. package/dist/data/playbook/form.md +164 -134
  14. package/dist/data/playbook/overlay.md +97 -88
  15. package/dist/data/playbook/page-layout.md +95 -76
  16. package/dist/data/playbook/responsive-tokens.md +77 -58
  17. package/dist/data/recipes/admin-list-page.md +86 -0
  18. package/dist/data/recipes/async-state-section.md +60 -0
  19. package/dist/data/recipes/dashboard-overview.md +65 -0
  20. package/dist/data/recipes/detail-drawer.md +69 -0
  21. package/dist/data/recipes/modal-form.md +67 -0
  22. package/dist/data/recipes/settings-form-page.md +79 -0
  23. package/dist/index.d.ts +4 -23
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +31 -338
  26. package/dist/index.js.map +1 -1
  27. package/dist/loaders.d.ts +8 -0
  28. package/dist/loaders.d.ts.map +1 -0
  29. package/dist/loaders.js +120 -0
  30. package/dist/loaders.js.map +1 -0
  31. package/dist/recipes.d.ts +13 -0
  32. package/dist/recipes.d.ts.map +1 -0
  33. package/dist/recipes.js +82 -0
  34. package/dist/recipes.js.map +1 -0
  35. package/dist/responses.d.ts +8 -0
  36. package/dist/responses.d.ts.map +1 -0
  37. package/dist/responses.js +25 -0
  38. package/dist/responses.js.map +1 -0
  39. package/dist/text.d.ts +4 -0
  40. package/dist/text.d.ts.map +1 -0
  41. package/dist/text.js +20 -0
  42. package/dist/text.js.map +1 -0
  43. package/dist/tool-definitions.d.ts +3 -0
  44. package/dist/tool-definitions.d.ts.map +1 -0
  45. package/dist/tool-definitions.js +199 -0
  46. package/dist/tool-definitions.js.map +1 -0
  47. package/dist/tools.d.ts +4 -0
  48. package/dist/tools.d.ts.map +1 -0
  49. package/dist/tools.js +233 -0
  50. package/dist/tools.js.map +1 -0
  51. package/dist/types.d.ts +107 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +10 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +21 -16
@@ -5,17 +5,33 @@
5
5
  > 인용 컴포넌트는 모두 `packages/mds/src/` 에 실재 — fabrication 0. 임의 룰은
6
6
  > **TBD** 명시 — 사용자 합의 후 결정.
7
7
 
8
+ ## Scope
9
+
10
+ 이 문서는 **MDS component usage** 를 정한다.
11
+
12
+ - 컨테이너: page-form / drawer-form / modal-form
13
+ - 배치: vertical stack / 2-col pair / Section + AnchorNav
14
+ - 필드: `Field` compound + input component mapping
15
+ - 피드백: `Field.Error` / `Banner` / `toast` / `Notification`
16
+ - 기본 form library guidance: simple/medium form 은 RHF + Zod 기본/권장
17
+
18
+ 도메인 form architecture 는 각 app 의 playbook 이 정한다. MDS 는 UI/a11y/layout
19
+ contract 를 강제하고, state model 은 강제하지 않는다. 단, RHF + Zod 가
20
+ 기본값이다. 커스텀 store/reducer 는 RHF 로 표현하면 도메인 모델을 왜곡하거나
21
+ 기존 동작을 깨는 경우에만 허용하고, 그 사유와 adapter contract 를 app playbook
22
+ 에 명시해야 한다.
23
+
8
24
  ---
9
25
 
10
26
  ## 1. 컨테이너 결정
11
27
 
12
- | 상황 | 컨테이너 | 이유 | Mobile 변형 |
13
- |---|---|---|---|
14
- | 페이지 본문 메인 작업 (상품 생성/수정, 설정 페이지) | **page-form** (`Page` shell 안 직접 Form) | 본업 — 가장 큰 공간 | Sidebar → drawer, content full-width, sticky action bar bottom |
15
- | 목록 화면 옆에서 행 1개 부분 편집 (이름·상태 빠른 수정) | **drawer-form** (`Drawer` 안 Form) | 목록 컨텍스트 유지 + side-by-side | `Drawer side="bottom"` (Sheet style) |
16
- | 짧은 입력 (필드 ≤ 5 · 확인성 액션, 쿠폰 발급) | **modal-form** (`Modal` 안 Form) | 짧은 입력 + 강제 집중. 5 초과면 page 또는 drawer 로 | `Modal` → full-screen Sheet |
17
- | 메인 form 안 sub-entity 편집 (variant 추가) | **nested drawer-form** (메인 form 살아있는 채 Drawer 오픈) | nested modal 보다 mental stack 얕음 | 동일 — Drawer 가 적합 |
18
- | 단순 확인 ("정말 삭제?") | **`Dialog` (confirm)** — Form 아님 | input 없음, action 만 | `Modal.confirm` 그대로 |
28
+ | 상황 | 컨테이너 | 이유 | Mobile 변형 |
29
+ | ------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------- |
30
+ | 페이지 본문 메인 작업 (상품 생성/수정, 설정 페이지) | **page-form** (`Page` shell 안 직접 Form) | 본업 — 가장 큰 공간 | Sidebar → drawer, content full-width, sticky action bar bottom |
31
+ | 목록 화면 옆에서 행 1개 부분 편집 (이름·상태 빠른 수정) | **drawer-form** (`Drawer` 안 Form) | 목록 컨텍스트 유지 + side-by-side | `Drawer side="bottom"` (Sheet style) |
32
+ | 짧은 입력 (필드 ≤ 5 · 확인성 액션, 쿠폰 발급) | **modal-form** (`Modal` 안 Form) | 짧은 입력 + 강제 집중. 5 초과면 page 또는 drawer 로 | `Modal` → full-screen Sheet |
33
+ | 메인 form 안 sub-entity 편집 (variant 추가) | **nested drawer-form** (메인 form 살아있는 채 Drawer 오픈) | nested modal 보다 mental stack 얕음 | 동일 — Drawer 가 적합 |
34
+ | 단순 확인 ("정말 삭제?") | **`Dialog` (confirm)** — Form 아님 | input 없음, action 만 | `Modal.confirm` 그대로 |
19
35
 
20
36
  ### 절대 금지
21
37
 
@@ -26,47 +42,52 @@
26
42
 
27
43
  ## 2. Form 안 input 배치 — Section 개수 기준 + caller 재량
28
44
 
29
- | 패턴 | 사용 시점 | 예시 | Mobile 변형 |
30
- |---|---|---|---|
31
- | **vertical stack** (단일 column) | **1 section** (단일 의미 단위) | 회원가입, 단순 modal-form (쿠폰 발급), 단일 entity 의 simple form | 그대로 |
32
- | **2-col pair** (horizontal grid) | 의미상 쌍 (시작-종료, 가로-세로, 이름-이메일) — section 내부 또는 단독 | 주소 입력 (이름&이메일 가로 / 도시&우편 가로) | 1-col vertical |
33
- | **Section + AnchorNav** | **6+ section** (큰 form, 의미 단위 분명) | 상품 등록 (9 section), 큰 설정 페이지 | AnchorNav → drawer (좌측 sidebar 모드) |
34
- | **2-5 section** | 의미 단위 명확 시 vertical or 2-col pair — caller 재량 | 정책 편집 (3 section), 사용자 프로필 (4 section) | 그대로 또는 1-col |
45
+ | 패턴 | 사용 시점 | 예시 | Mobile 변형 |
46
+ | -------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------- |
47
+ | **vertical stack** (단일 column) | **1 section** (단일 의미 단위) | 회원가입, 단순 modal-form (쿠폰 발급), 단일 entity 의 simple form | 그대로 |
48
+ | **2-col pair** (horizontal grid) | 의미상 쌍 (시작-종료, 가로-세로, 이름-이메일) — section 내부 또는 단독 | 주소 입력 (이름&이메일 가로 / 도시&우편 가로) | 1-col vertical |
49
+ | **Section + AnchorNav** | **6+ section** (큰 form, 의미 단위 분명) | 상품 등록 (9 section), 큰 설정 페이지 | AnchorNav → drawer (좌측 sidebar 모드) |
50
+ | **2-5 section** | 의미 단위 명확 시 vertical or 2-col pair — caller 재량 | 정책 편집 (3 section), 사용자 프로필 (4 section) | 그대로 또는 1-col |
35
51
 
36
- > **결정 근거** — Section 개수가 form 의 정신적 무게의 진짜 기준. 필드 수보다 의미 단위 분명.
37
- > 동일 9 필드라도 1 section (단순 form) vs 6 section (복잡 form) 의 UX 가 다름.
52
+ > **결정 근거** — Section 개수가 form 의 정신적 무게의 진짜 기준. 필드 수보다
53
+ > 의미 단위 분명. 동일 9 필드라도 1 section (단순 form) vs 6 section (복잡 form)
54
+ > 의 UX 가 다름.
38
55
 
39
56
  ---
40
57
 
41
58
  ## 3. Field 타입 매핑 (데이터 → MDS 컴포넌트)
42
59
 
43
- > 모든 컴포넌트 `packages/mds/src/primitives/inputs/` + `compounds/` 실재 검증됨.
44
-
45
- | 데이터 형태 | MDS 컴포넌트 | 비고 |
46
- |---|---|---|
47
- | 짧은 문자열 (이름, 슬러그) | `TextField` | |
48
- | 문자열 (설명, 메모) | `Textarea` | |
49
- | 리치 텍스트 (블로그 본문) | `RichText` (compounds) | 도메인 한정 |
50
- | 정수 / 통화 / 수량 | `NumberInput` | ArrowUp/Down + Shift x10 + Home/End 키보드 ✓ |
51
- | 단일 선택 (≤ 7) | `RadioGroup` | 시각적 비교 가능 |
52
- | 단일 선택 (8+) | `Select` | dropdown |
53
- | 다중 선택 (작은, ≤ 5) | `Checkbox` ×N | 시각적 비교 |
54
- | 다중 선택 (큰, 6+) | `<Select multiple>` | discriminated union — `multiple: true` value 가 array. token chip 표시 |
55
- | boolean 즉시 효과 toggle | `Switch` | TBD: 자동 저장 / 수동 저장 |
56
- | boolean 약관 동의 | `Checkbox` | submit 평가 |
57
- | 날짜 1개 | `DatePicker` | |
58
- | 시간 | `TimePicker` | |
59
- | 숫자 슬라이더 | `Slider` / `RangeSlider` | |
60
- | 색상 / 토큰 선택 | `Swatch` | |
61
- | chip 형태 다중 토글 | `ChipToggle` | |
62
- | 단계형 입력 (1→2→3) | `Stepper` (primitives/feedback) + 페이지 분할 | wizard |
60
+ > 모든 컴포넌트 `packages/mds/src/primitives/inputs/` + `compounds/` 실재
61
+ > 검증됨.
62
+
63
+ | 데이터 형태 | MDS 컴포넌트 | 비고 |
64
+ | -------------------------- | --------------------------------------------- | ------------------------------------------------------------------------- |
65
+ | 짧은 문자열 (이름, 슬러그) | `TextField` | |
66
+ | 문자열 (설명, 메모) | `Textarea` | |
67
+ | 리치 텍스트 (블로그 본문) | `RichText` (compounds) | 도메인 한정 |
68
+ | 정수 / 통화 / 수량 | `NumberInput` | ArrowUp/Down + Shift x10 + Home/End 키보드 ✓ |
69
+ | 단일 선택 (≤ 7) | `RadioGroup` | 시각적 비교 가능 |
70
+ | 단일 선택 (8+) | `Select` | dropdown |
71
+ | 다중 선택 (작은, ≤ 5) | `Checkbox` ×N | 시각적 비교 |
72
+ | 다중 선택 (큰, 6+) | `<Select multiple>` | discriminated union — `multiple: true` value array. token chip 표시 |
73
+ | boolean 즉시 효과 toggle | `Switch` | TBD: 자동 저장 / 수동 저장 |
74
+ | boolean 약관 동의 | `Checkbox` | submit 시 평가 |
75
+ | 날짜 1개 | `DatePicker` | |
76
+ | 시간 | `TimePicker` | |
77
+ | 숫자 슬라이더 | `Slider` / `RangeSlider` | |
78
+ | 색상 / 토큰 선택 | `Swatch` | |
79
+ | chip 형태 다중 토글 | `ChipToggle` | |
80
+ | 단계형 입력 (1→2→3) | `Stepper` (primitives/feedback) + 페이지 분할 | wizard |
63
81
 
64
82
  ### `Field` compound — label + control + error + hint
65
83
 
66
84
  `packages/mds/src/compounds/Field/` 가 input 을 감싸는 표준 compound:
85
+
67
86
  - `Field.Label`, control (TextField 등), `Field.Error`, `Field.Hint`
68
- - input 의 a11y attribute (htmlFor / aria-describedby / aria-errormessage / aria-invalid) 자동 wire
69
- - 원자 input 직접 사용 보다 `Field` compound 권장 (Storybook 의 stories 도 Field 사용 예시)
87
+ - input 의 a11y attribute (htmlFor / aria-describedby / aria-errormessage /
88
+ aria-invalid) 자동 wire
89
+ - 원자 input 직접 사용 보다 `Field` compound 권장 (Storybook 의 stories 도 Field
90
+ 사용 예시)
70
91
 
71
92
  ### 파일 / 이미지 — `FileUpload` (Step 13 완료)
72
93
 
@@ -94,83 +115,85 @@
94
115
  </Field.Root>
95
116
  ```
96
117
 
97
- - mds 는 picker / drag-drop / preview / progress UI 만 책임. server upload (S3 / Cloudinary / 자체 API / multipart) 는 caller 결정.
98
- - `upload` 주면 deferred mode (File 객체 만 보관, form submit 시 caller 가 처리).
118
+ - mds 는 picker / drag-drop / preview / progress UI 만 책임. server upload (S3 /
119
+ Cloudinary / 자체 API / multipart) caller 결정.
120
+ - `upload` 안 주면 deferred mode (File 객체 만 보관, form submit 시 caller 가
121
+ 처리).
99
122
  - Controlled (`value` + `onChange`) + Uncontrolled (`defaultValue`) 둘 다 지원.
100
- - `FileItem`: `{ id, file?, url?, name, size, type, status: 'pending'|'uploading'|'success'|'error', progress?, error? }` — File 객체 + server URL 양쪽 표현 가능 (편집 시 기존 URL 도 자연 처리).
123
+ - `FileItem`:
124
+ `{ id, file?, url?, name, size, type, status: 'pending'|'uploading'|'success'|'error', progress?, error? }`
125
+ — File 객체 + server URL 양쪽 표현 가능 (편집 시 기존 URL 도 자연 처리).
101
126
  - `onReject` callback — `accept` / `maxSize` / `maxFiles` validation fail 시.
102
127
 
103
128
  ---
104
129
 
105
130
  ## 4. 검증 + 에러 표시
106
131
 
107
- | 에러 종류 | 표시 위치 | 컴포넌트 |
108
- |---|---|---|
109
- | 단일 필드 검증 (format / 필수 / length) | **inline 필드 아래** | `Field.Error` slot |
110
- | Cross-field (비밀번호 ≠ 확인) | inline 두 필드 + form summary | `Field.Error` + `Banner` 상단 |
111
- | Form 전체 검증 (서버 422 + 필드 에러) | 필드별 분배 + 상단 `Banner` 요약 | `Banner` + `Field.Error` |
112
- | 비검증성 서버 에러 (500, 네트워크) — **일시적** | **toast.error** | `toast.error(title, {description})` |
113
- | 단순 정보 (저장됐다) — **일시적** | **toast.success** | `toast.success(...)` |
114
- | **영속** 경고 (라이선스 만료, 결제 만료) | **Notification** (banner, 직접 닫기) | mds 의 영속 Notification — Step 14 별도 신규 작업. 임시 — `Banner` (variant=`warning` / `error`) 의 dismissible mode 활용 |
132
+ | 에러 종류 | 표시 위치 | 컴포넌트 |
133
+ | ----------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
134
+ | 단일 필드 검증 (format / 필수 / length) | **inline 필드 아래** | `Field.Error` slot |
135
+ | Cross-field (비밀번호 ≠ 확인) | inline 두 필드 + form summary | `Field.Error` + `Banner` 상단 |
136
+ | Form 전체 검증 (서버 422 + 필드 에러) | 필드별 분배 + 상단 `Banner` 요약 | `Banner` + `Field.Error` |
137
+ | 비검증성 서버 에러 (500, 네트워크) — **일시적** | **toast.error** | `toast.error(title, {description})` |
138
+ | 단순 정보 (저장됐다) — **일시적** | **toast.success** | `toast.success(...)` |
139
+ | **영속** 경고 (라이선스 만료, 결제 만료) | **Notification** (banner, 직접 닫기) | mds 의 영속 Notification — Step 14 별도 신규 작업. 임시 — `Banner` (variant=`warning` / `error`) 의 dismissible mode 활용 |
115
140
 
116
- > **핵심 구분**: **Toast = 일시적** (자동 사라짐), **Notification = 영속** (명시적 닫기 전 유지).
141
+ > **핵심 구분**: **Toast = 일시적** (자동 사라짐), **Notification = 영속**
142
+ > (명시적 닫기 전 유지).
117
143
 
118
144
  ### 검증 시점 (RHF mode)
119
145
 
120
- | Form 종류 | RHF mode | 동작 |
121
- |---|---|---|
122
- | **default** (modal-form / drawer-form / 작은 form) | `onSubmit` (RHF default) | submit 시 검증. 입력 중 방해 X |
123
- | **page-form** (큰 form / multi-section) | `onTouched` 권장 | blur 후 첫 검증 + 이후 change 시 즉시 재검증 (한 번 틀린 필드는 실시간 fix 도움) |
124
- | **특수 케이스** (비밀번호 강도, slug 중복) | `onChange` (caller 결정) | 실시간 도움 필수 |
146
+ | Form 종류 | RHF mode | 동작 |
147
+ | -------------------------------------------------- | ------------------------ | -------------------------------------------------------------------------------- |
148
+ | **default** (modal-form / drawer-form / 작은 form) | `onSubmit` (RHF default) | submit 시 검증. 입력 중 방해 X |
149
+ | **page-form** (큰 form / multi-section) | `onTouched` 권장 | blur 후 첫 검증 + 이후 change 시 즉시 재검증 (한 번 틀린 필드는 실시간 fix 도움) |
150
+ | **특수 케이스** (비밀번호 강도, slug 중복) | `onChange` (caller 결정) | 실시간 도움 필수 |
125
151
 
126
- 확정 룰:
127
- | 시점 | 동작 |
128
- |---|---|
129
- | submit | 전체 form 검증 + 첫 에러 필드로 scroll/focus |
130
- | 서버 응답 422 | 서버 에러를 RHF `setError` 로 필드 분배 |
152
+ 확정 룰: | 시점 | 동작 | |---|---| | submit | 전체 form 검증 + 첫 에러 필드로
153
+ scroll/focus | | 서버 응답 422 | 서버 에러를 RHF `setError` 로 필드 분배 |
131
154
 
132
155
  ---
133
156
 
134
157
  ## 5. 저장 액션
135
158
 
136
- | 액션 종류 | 버튼 위치 | 비활성 조건 |
137
- |---|---|---|
138
- | 페이지 form 저장 | 페이지 하단 sticky `Toolbar` 또는 `PageHeader` 우측 primary | `isDirty === false` 또는 `isSubmitting` |
139
- | Modal/Drawer form 저장 | Footer 우측 primary | 동일 |
140
- | 위험 액션 (삭제, 환불) | `Modal.confirm` + 빨간 primary | 항상 |
159
+ | 액션 종류 | 버튼 위치 | 비활성 조건 |
160
+ | ---------------------- | ----------------------------------------------------------- | --------------------------------------- |
161
+ | 페이지 form 저장 | 페이지 하단 sticky `Toolbar` 또는 `PageHeader` 우측 primary | `isDirty === false` 또는 `isSubmitting` |
162
+ | Modal/Drawer form 저장 | Footer 우측 primary | 동일 |
163
+ | 위험 액션 (삭제, 환불) | `Modal.confirm` + 빨간 primary | 항상 |
141
164
 
142
165
  ### Inline 편집 (DataGrid 의 1 필드 빠른 수정)
143
166
 
144
- | 동작 | 결정 |
145
- |---|---|
146
- | 활성화 | cell click → input mode (pencil icon optional) |
147
- | **저장** | **Enter 키 또는 체크 버튼 명시 click** (blur 자동 저장 ❌) |
148
- | 취소 | Esc 키 또는 X 버튼 |
149
- | 위험 / 큰 필드 변경 | Inline 안 함 — drawer / modal 사용 (caller 재량) |
167
+ | 동작 | 결정 |
168
+ | ------------------- | ---------------------------------------------------------- |
169
+ | 활성화 | cell click → input mode (pencil icon optional) |
170
+ | **저장** | **Enter 키 또는 체크 버튼 명시 click** (blur 자동 저장 ❌) |
171
+ | 취소 | Esc 키 또는 X 버튼 |
172
+ | 위험 / 큰 필드 변경 | Inline 안 함 — drawer / modal 사용 (caller 재량) |
150
173
 
151
- > **이유** — Notion/Airtable 같은 blur 자동 저장은 실수 위험 + 의도 불명확.
152
- > 명시 저장 = click 1 회 더지만 의도 확실.
174
+ > **이유** — Notion/Airtable 같은 blur 자동 저장은 실수 위험 + 의도 불명확. 명시
175
+ > 저장 = click 1 회 더지만 의도 확실.
153
176
 
154
177
  ### Submit 후 결과 처리
155
178
 
156
- | 결과 | 처리 |
157
- |---|---|
179
+ | 결과 | 처리 |
180
+ | --------------------- | ------------------------------------------------------- |
158
181
  | 200 OK (modal/drawer) | 컨테이너 닫기 + 부모 query invalidate + `toast.success` |
159
- | 200 OK (페이지) | 페이지 유지 + `isDirty=false` reset + `toast.success` |
160
- | 4xx 검증 | 컨테이너 유지 + 필드 에러 분배 + 첫 에러 scroll |
161
- | 5xx / 네트워크 | 컨테이너 유지 + `toast.error` |
182
+ | 200 OK (페이지) | 페이지 유지 + `isDirty=false` reset + `toast.success` |
183
+ | 4xx 검증 | 컨테이너 유지 + 필드 에러 분배 + 첫 에러 scroll |
184
+ | 5xx / 네트워크 | 컨테이너 유지 + `toast.error` |
162
185
 
163
186
  ### Optimistic update — 기본 적용
164
187
 
165
- | 결정 | 동작 |
166
- |---|---|
167
- | **기본** | **모든 mutation optimistic** — UI 즉시 갱신, 서버 응답 비동기 |
168
- | Rollback | 5xx / network error 시 자동 rollback + `toast.error("저장 실패")` |
169
- | 4xx 검증 에러 | rollback + 필드 에러 분배 + 첫 에러 scroll |
188
+ | 결정 | 동작 |
189
+ | ---------------------- | ------------------------------------------------------------------------ |
190
+ | **기본** | **모든 mutation optimistic** — UI 즉시 갱신, 서버 응답 비동기 |
191
+ | Rollback | 5xx / network error 시 자동 rollback + `toast.error("저장 실패")` |
192
+ | 4xx 검증 에러 | rollback + 필드 에러 분배 + 첫 에러 scroll |
170
193
  | **예외** (caller 결정) | 결제 / 환불 / 큰 금액 mutation — 명시 응답 대기 (`isSubmitting` loading) |
171
194
 
172
- > **이유** — UX 가장 빠름. React Query 의 `onMutate` + `setQueryData` + `onError rollback`
173
- > 패턴 표준화.
195
+ > **이유** — UX 가장 빠름. React Query 의 `onMutate` + `setQueryData` +
196
+ > `onError rollback` 패턴 표준화.
174
197
 
175
198
  ---
176
199
 
@@ -178,12 +201,12 @@
178
201
 
179
202
  상세 → [array-input Playbook](./array-input.md) (Step 4.6).
180
203
 
181
- | 패턴 | 컴포넌트 | 비고 |
182
- |---|---|---|
183
- | 짧은 동질 배열 (태그) | `ChipToggle` | item = 단순 string |
184
- | 가변 row (variant 가격) | RHF `useFieldArray` + `Stack` | 각 row = sub-form |
185
- | nested entity 다수 (정책, 결제 옵션) | `useFieldArray` + row 클릭 시 **drawer 편집** | row = 요약 |
186
- | reorder | `Tree` 또는 `useFieldArray` + dnd-kit (mds 자체 dnd 없음) | |
204
+ | 패턴 | 컴포넌트 | 비고 |
205
+ | ------------------------------------ | --------------------------------------------------------- | ------------------ |
206
+ | 짧은 동질 배열 (태그) | `ChipToggle` | item = 단순 string |
207
+ | 가변 row (variant 가격) | RHF `useFieldArray` + `Stack` | 각 row = sub-form |
208
+ | nested entity 다수 (정책, 결제 옵션) | `useFieldArray` + row 클릭 시 **drawer 편집** | row = 요약 |
209
+ | reorder | `Tree` 또는 `useFieldArray` + dnd-kit (mds 자체 dnd 없음) | |
187
210
 
188
211
  ---
189
212
 
@@ -194,29 +217,30 @@
194
217
  - `useSkillAutofill(agentType)` — generic hook
195
218
  - `FormFillApprovalBanner` — Apply / Reject UI
196
219
  - `skill-registry` — 등록 → AI 가 catalog query
197
- - 도메인별 fill hook / Banner / Prompt 작성 금지 (`useSkillAutofill` + shared 사용)
220
+ - 도메인별 fill hook / Banner / Prompt 작성 금지 (`useSkillAutofill` + shared
221
+ 사용)
198
222
 
199
223
  ---
200
224
 
201
225
  ## 8. 사용 시점 결정표 (lookup)
202
226
 
203
- | 케이스 | 답 |
204
- |---|---|
205
- | "필드 ≤ 5 짧은 입력" | modal-form |
206
- | "10+ 필드 + 설정 페이지" | page-form |
207
- | "리스트 행 빠른 편집" | drawer-form |
208
- | "정말 삭제?" | `Modal.confirm` |
209
- | "저장됐다 일시 알림" | `toast.success` |
210
- | "서버 500 일시 알림" | `toast.error` |
211
- | "라이선스 만료 영속 경고" | `Notification` (TBD) |
212
- | "input 옆 즉시 검증" | `Field.Error` inline |
213
- | "form 위 전체 요약 에러" | `Banner` 상단 |
214
- | "variant 줄 추가" | `useFieldArray` + 행 편집 = drawer |
215
- | "boolean 즉시 효과" | `Switch` (TBD 자동 저장 여부) |
216
- | "boolean submit 시 평가" | `Checkbox` |
217
- | "8+ 선택지" | `Select` |
218
- | "≤ 7 선택지" | `RadioGroup` |
219
- | "숫자 ↑↓ 키보드" | `NumberInput` (ArrowUp/Down + Shift x10) |
227
+ | 케이스 | 답 |
228
+ | ------------------------- | ---------------------------------------- |
229
+ | "필드 ≤ 5 짧은 입력" | modal-form |
230
+ | "10+ 필드 + 설정 페이지" | page-form |
231
+ | "리스트 행 빠른 편집" | drawer-form |
232
+ | "정말 삭제?" | `Modal.confirm` |
233
+ | "저장됐다 일시 알림" | `toast.success` |
234
+ | "서버 500 일시 알림" | `toast.error` |
235
+ | "라이선스 만료 영속 경고" | `Notification` (TBD) |
236
+ | "input 옆 즉시 검증" | `Field.Error` inline |
237
+ | "form 위 전체 요약 에러" | `Banner` 상단 |
238
+ | "variant 줄 추가" | `useFieldArray` + 행 편집 = drawer |
239
+ | "boolean 즉시 효과" | `Switch` (TBD 자동 저장 여부) |
240
+ | "boolean submit 시 평가" | `Checkbox` |
241
+ | "8+ 선택지" | `Select` |
242
+ | "≤ 7 선택지" | `RadioGroup` |
243
+ | "숫자 ↑↓ 키보드" | `NumberInput` (ArrowUp/Down + Shift x10) |
220
244
 
221
245
  ---
222
246
 
@@ -228,7 +252,8 @@
228
252
  - ❌ `Notification` 으로 "저장됨" — toast 가 맞음 (일시적)
229
253
  - ❌ Toast 로 "라이선스 만료" — Notification (영속)
230
254
  - ❌ raw `<input>` / `<textarea>` 사용 — mds `TextField` / `Textarea` 만
231
- - ❌ form value 를 `useState` 로 직접 관리 — RHF + Zod 만 (현재 ESLint 룰 추가 검토 중)
255
+ - ❌ simple/medium form value 를 ad-hoc `useState` 로 흩어 관리 — RHF + Zod 또는
256
+ app-level form adapter 사용
232
257
  - ❌ Field compound 외부에 input — Field 의 a11y wire 누락
233
258
  - ❌ 도메인별 fill hook 작성 — `useSkillAutofill` 사용
234
259
 
@@ -236,13 +261,13 @@
236
261
 
237
262
  ## 10. Cross-cutting
238
263
 
239
- | Axis | 적용 |
240
- |---|---|
241
- | **Responsive** | 모든 표의 "Mobile 변형" 컬럼 — 자동 적용 |
242
- | **A11y** | Field compound 의 aria-* 자동 wire / RHF 의 setFocus 첫 에러 |
243
- | **i18n** | label / error / placeholder 모두 `t()` 통과 — apps/web 의 ESLint rule 강제 (Step 7) |
244
- | **AI fill** | skill-registry 등록만 — AI 가 자동 query |
245
- | **Catalog** | 각 컴포넌트 (TextField / Select / Field 등) 의 catalog.json 자동 추출 (Step 5) |
264
+ | Axis | 적용 |
265
+ | -------------- | ----------------------------------------------------------------------------------- |
266
+ | **Responsive** | 모든 표의 "Mobile 변형" 컬럼 — 자동 적용 |
267
+ | **A11y** | Field compound 의 aria-\* 자동 wire / RHF 의 setFocus 첫 에러 |
268
+ | **i18n** | label / error / placeholder 모두 `t()` 통과 — apps/web 의 ESLint rule 강제 (Step 7) |
269
+ | **AI fill** | skill-registry 등록만 — AI 가 자동 query |
270
+ | **Catalog** | 각 컴포넌트 (TextField / Select / Field 등) 의 catalog.json 자동 추출 (Step 5) |
246
271
 
247
272
  ---
248
273
 
@@ -250,37 +275,42 @@
250
275
 
251
276
  ### 확정 (cycle 1)
252
277
 
253
- | § | 결정 |
254
- |---|---|
255
- | §2 Form 배치 | Section 개수 기준 (1 vertical / 2-5 caller 재량 / 6+ AnchorNav) |
256
- | §4 RHF mode | default `onSubmit`, page-form `onTouched`, 특수 `onChange` |
257
- | §5 Inline 편집 | 허용 + Enter/체크 명시 저장 (blur 자동 ❌) + Esc 취소 |
258
- | §5 Optimistic | 모든 mutation 기본 적용 + rollback + error toast. 결제/환불 예외 |
278
+ | § | 결정 |
279
+ | -------------- | ---------------------------------------------------------------- |
280
+ | §2 Form 배치 | Section 개수 기준 (1 vertical / 2-5 caller 재량 / 6+ AnchorNav) |
281
+ | §4 RHF mode | default `onSubmit`, page-form `onTouched`, 특수 `onChange` |
282
+ | §5 Inline 편집 | 허용 + Enter/체크 명시 저장 (blur 자동 ❌) + Esc 취소 |
283
+ | §5 Optimistic | 모든 mutation 기본 적용 + rollback + error toast. 결제/환불 예외 |
259
284
 
260
285
  ### 확정 (cycle 2 — Step 4.1)
261
286
 
262
- | § | 결정 | 근거 |
263
- |---|---|---|
264
- | §3 다중 선택 (큰 list) | `<Select multiple>` 사용 — discriminated union (SelectSingleProps vs SelectMultipleProps) | mds 의 Select 가 이미 multiple prop 지원 검증됨 (Select.types.ts §79 + tests §102-194 + stories §100-151) |
265
- | §5 저장 button i18n key | `t('common.save')` / `t('common.cancel')` / `t('common.delete')` / `t('common.discard')` — 공통 namespace | apps/web 의 i18n.md §3 — common namespace |
266
- | §5 위험 액션 typing rule | 일반 삭제 = `Modal.confirm` (단순). **영향 큰 액션** (계정 삭제 / 가입자 전체 삭제 / DB drop / 환불 전체) = typing confirm — Modal 안 input + 정확한 문구 입력 시에만 destructive button enabled | 실수 방지 — confirm click 한 번보다 typing 이 의도 명시가 강함. caller 가 판단 ("이게 영향 큰가") |
267
- | §5 위험 액션 typing 표준 문구 | i18n key `confirm.typeXToConfirm` + `{x}` 가 entity name (shop slug / customer email / etc.) — 영문 또는 일치 텍스트 | 일관 UX (caller 별 다른 typing rule 안 됨) |
287
+ | § | 결정 | 근거 |
288
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
289
+ | §3 다중 선택 (큰 list) | `<Select multiple>` 사용 — discriminated union (SelectSingleProps vs SelectMultipleProps) | mds 의 Select 가 이미 multiple prop 지원 검증됨 (Select.types.ts §79 + tests §102-194 + stories §100-151) |
290
+ | §5 저장 button i18n key | `t('common.save')` / `t('common.cancel')` / `t('common.delete')` / `t('common.discard')` — 공통 namespace | apps/web 의 i18n.md §3 — common namespace |
291
+ | §5 위험 액션 typing rule | 일반 삭제 = `Modal.confirm` (단순). **영향 큰 액션** (계정 삭제 / 가입자 전체 삭제 / DB drop / 환불 전체) = typing confirm — Modal 안 input + 정확한 문구 입력 시에만 destructive button enabled | 실수 방지 — confirm click 한 번보다 typing 이 의도 명시가 강함. caller 가 판단 ("이게 영향 큰가") |
292
+ | §5 위험 액션 typing 표준 문구 | i18n key `confirm.typeXToConfirm` + `{x}` 가 entity name (shop slug / customer email / etc.) — 영문 또는 일치 텍스트 | 일관 UX (caller 별 다른 typing rule 안 됨) |
268
293
 
269
294
  ### 별도 step (큰 작업 — Phase 2 계획)
270
295
 
271
- | 항목 | 별도 step |
272
- |---|---|
273
- | §3 FileUpload | **Step 13 — mds FileUpload 추가** (필요한 a11y / drag-drop / 진행률 / multi / preview / type validation 전체 컴포넌트 설계) |
296
+ | 항목 | 별도 step |
297
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
298
+ | §3 FileUpload | **Step 13 — mds FileUpload 추가** (필요한 a11y / drag-drop / 진행률 / multi / preview / type validation 전체 컴포넌트 설계) |
274
299
  | §4 Notification 영속 | **Step 14 — mds Notification 추가** (Toast 와 분리 — Toast 는 일시, Notification 은 영속. 직접 닫기 전 유지. badge 또는 inline) |
275
300
 
276
- > cycle 2 마무리 — form.md 의 모든 TBD 확정 또는 별도 step 으로 이관. mds Form 영역 = 완벽 마무리.
301
+ > cycle 2 마무리 — form.md 의 모든 TBD 확정 또는 별도 step 으로 이관. mds Form
302
+ > 영역 = 완벽 마무리.
277
303
 
278
304
  ---
279
305
 
280
306
  ## Related Playbooks
281
307
 
282
- - [feedback.md](./feedback.md) — Toast / Notification / Banner / Modal Dialog (Step 4.2)
308
+ - [feedback.md](./feedback.md) — Toast / Notification / Banner / Modal Dialog
309
+ (Step 4.2)
283
310
  - [overlay.md](./overlay.md) — Modal / Drawer / Sheet / Dialog (Step 4.4)
284
311
  - [array-input.md](./array-input.md) — useFieldArray + drawer 편집 (Step 4.6)
285
312
  - [async-states.md](./async-states.md) — loading/error/empty/skeleton (Step 4.7)
286
- - [ai-fill.md](./ai-fill.md) — skill-registry + FormFillApprovalBanner (Step 4.8)
313
+ - [ai-fill.md](./ai-fill.md) — skill-registry + FormFillApprovalBanner (Step
314
+ 4.8)
315
+ - [apps/web form-architecture.md](../../../../apps/web/docs/playbook/form-architecture.md)
316
+ — MAKITT web domain form adapter / shell rules