@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.
- package/README.md +151 -34
- package/dist/catalog.d.ts +16 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +383 -0
- package/dist/catalog.js.map +1 -0
- package/dist/data/catalog.json +41955 -4439
- package/dist/data/playbook/ai-fill.md +61 -48
- package/dist/data/playbook/anti-patterns.md +112 -110
- package/dist/data/playbook/array-input.md +94 -49
- package/dist/data/playbook/async-states.md +71 -61
- package/dist/data/playbook/data-grid.md +118 -101
- package/dist/data/playbook/feedback.md +103 -84
- package/dist/data/playbook/form.md +164 -134
- package/dist/data/playbook/overlay.md +97 -88
- package/dist/data/playbook/page-layout.md +95 -76
- package/dist/data/playbook/responsive-tokens.md +77 -58
- package/dist/data/recipes/admin-list-page.md +86 -0
- package/dist/data/recipes/async-state-section.md +60 -0
- package/dist/data/recipes/dashboard-overview.md +65 -0
- package/dist/data/recipes/detail-drawer.md +69 -0
- package/dist/data/recipes/modal-form.md +67 -0
- package/dist/data/recipes/settings-form-page.md +79 -0
- package/dist/index.d.ts +4 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -338
- package/dist/index.js.map +1 -1
- package/dist/loaders.d.ts +8 -0
- package/dist/loaders.d.ts.map +1 -0
- package/dist/loaders.js +120 -0
- package/dist/loaders.js.map +1 -0
- package/dist/recipes.d.ts +13 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/recipes.js +82 -0
- package/dist/recipes.js.map +1 -0
- package/dist/responses.d.ts +8 -0
- package/dist/responses.d.ts.map +1 -0
- package/dist/responses.js +25 -0
- package/dist/responses.js.map +1 -0
- package/dist/text.d.ts +4 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +20 -0
- package/dist/text.js.map +1 -0
- package/dist/tool-definitions.d.ts +3 -0
- package/dist/tool-definitions.d.ts.map +1 -0
- package/dist/tool-definitions.js +199 -0
- package/dist/tool-definitions.js.map +1 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +233 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +21 -16
|
@@ -2,29 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
AI 가 form 자동 채우기 (autofill) 를 적용하는 약속.
|
|
4
4
|
|
|
5
|
-
> **단일 통로 강제**: `useSkillAutofill` + `FormFillApprovalBanner` +
|
|
6
|
-
> 도메인별 hook / Banner / Prompt 작성 **금지**.
|
|
5
|
+
> **단일 통로 강제**: `useSkillAutofill` + `FormFillApprovalBanner` +
|
|
6
|
+
> `skill-registry`. 도메인별 hook / Banner / Prompt 작성 **금지**.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## 1. AI Fill 의 의도
|
|
11
11
|
|
|
12
|
-
| 시나리오
|
|
13
|
-
|
|
14
|
-
| 사용자가 ai-drawer 에 자연어 입력 ("새 상품 만들어줘, 캐시미어 니트") | AI 가 schema 따라 form value 생성 → eventBus 발행
|
|
15
|
-
| 페이지에 이미 form 열림 (예: `/products/new`)
|
|
16
|
-
| 페이지 외부 (다른 page)
|
|
12
|
+
| 시나리오 | AI 의 역할 |
|
|
13
|
+
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
14
|
+
| 사용자가 ai-drawer 에 자연어 입력 ("새 상품 만들어줘, 캐시미어 니트") | AI 가 schema 따라 form value 생성 → eventBus 발행 |
|
|
15
|
+
| 페이지에 이미 form 열림 (예: `/products/new`) | `useSkillAutofill(agentType)` 수신 → banner 표시 → 사용자 approve/reject |
|
|
16
|
+
| 페이지 외부 (다른 page) | `useSkillAutofillNavigation` 가 prompt → 사용자 navigation → sessionStorage 통해 데이터 전달 |
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
## 2. 단일 통로 — 4 layer 강제
|
|
21
21
|
|
|
22
|
-
| Layer
|
|
23
|
-
|
|
24
|
-
| **1. ai-drawer 패키지**
|
|
25
|
-
| **2. skill-registry**
|
|
26
|
-
| **3. useSkillAutofill**
|
|
27
|
-
| **4. FormFillApprovalBanner** | `apps/web/src/shared/ui/FormFillApprovalBanner/`
|
|
22
|
+
| Layer | 컴포넌트 / 위치 | 책임 |
|
|
23
|
+
| ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------- |
|
|
24
|
+
| **1. ai-drawer 패키지** | `packages/ai-drawer/` | AI 결과를 eventBus.emit 으로 발행 (도메인 무관) |
|
|
25
|
+
| **2. skill-registry** | `apps/web/src/shared/lib/skill-registry/` | agentType → { eventName, storageKey, schema, mutator } single source 등록 |
|
|
26
|
+
| **3. useSkillAutofill** | `apps/web/src/shared/lib/skill-registry/useSkillAutofill.ts` | generic hook — 모든 form page 가 1 줄 호출 |
|
|
27
|
+
| **4. FormFillApprovalBanner** | `apps/web/src/shared/ui/FormFillApprovalBanner/` | 사용자에게 approve/reject UI |
|
|
28
28
|
|
|
29
29
|
→ **도메인별 hook / Banner / Prompt 작성 금지**. ESLint rule 자동 강제 (Step 1).
|
|
30
30
|
|
|
@@ -59,7 +59,8 @@ import { useSkillAutofill } from '@/shared/lib/skill-registry/useSkillAutofill';
|
|
|
59
59
|
import { FormFillApprovalBanner } from '@/shared/ui/FormFillApprovalBanner';
|
|
60
60
|
|
|
61
61
|
function ProductFormPage() {
|
|
62
|
-
const { hasPendingFill, pendingData, applyFill, rejectFill } =
|
|
62
|
+
const { hasPendingFill, pendingData, applyFill, rejectFill } =
|
|
63
|
+
useSkillAutofill<ProductAutofillData>('product');
|
|
63
64
|
|
|
64
65
|
return (
|
|
65
66
|
<>
|
|
@@ -80,42 +81,48 @@ function ProductFormPage() {
|
|
|
80
81
|
|
|
81
82
|
## 4. 결정 표 (lookup)
|
|
82
83
|
|
|
83
|
-
| 케이스
|
|
84
|
-
|
|
85
|
-
| "새 도메인 AI fill 통합"
|
|
86
|
-
| "form 안에서 AI 결과 수신"
|
|
87
|
-
| "AI 가 채운 후 사용자 확인"
|
|
84
|
+
| 케이스 | 답 |
|
|
85
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| "새 도메인 AI fill 통합" | `registerSkill({ agentType, eventName, storageKey, schema, mutator })` 30줄 |
|
|
87
|
+
| "form 안에서 AI 결과 수신" | `useSkillAutofill(agentType)` |
|
|
88
|
+
| "AI 가 채운 후 사용자 확인" | `<FormFillApprovalBanner data={pendingData} onApply onReject>` |
|
|
88
89
|
| "다른 page 에서 AI 가 채움 → form page 이동 prompt" | `useSkillAutofillNavigation` (TBD generic 화 — 현재 도메인별 useProductAutofillNavigation 6 hook) |
|
|
89
|
-
| "사용자가 reject 후 데이터 보존"
|
|
90
|
-
| "AI 가 schema 안 맞는 데이터 발행"
|
|
90
|
+
| "사용자가 reject 후 데이터 보존" | sessionStorage 그대로, 사용자 다른 page 가서 다시 trigger |
|
|
91
|
+
| "AI 가 schema 안 맞는 데이터 발행" | `useSkillAutofill` 의 schema validation (Zod) 자동 — invalid 시 toast.error |
|
|
91
92
|
|
|
92
93
|
---
|
|
93
94
|
|
|
94
95
|
## 5. 안티 패턴
|
|
95
96
|
|
|
96
|
-
- ❌ **도메인별 fill hook 작성** (`useProductFormFill` 등) — `useSkillAutofill`
|
|
97
|
-
|
|
97
|
+
- ❌ **도메인별 fill hook 작성** (`useProductFormFill` 등) — `useSkillAutofill`
|
|
98
|
+
만 사용. ESLint rule 자동 강제
|
|
99
|
+
- ❌ **도메인별 FormFillBanner** (`ProductFormFillBanner` 등) — shared
|
|
100
|
+
`FormFillApprovalBanner` 만
|
|
98
101
|
- ❌ **eventBus name hardcode** — skill registry 가 자동 발행
|
|
99
|
-
- ❌ **schema 검증 없는 mutator** — skill 등록 시 schema 필수 + Zod 자동
|
|
100
|
-
|
|
102
|
+
- ❌ **schema 검증 없는 mutator** — skill 등록 시 schema 필수 + Zod 자동
|
|
103
|
+
validation
|
|
104
|
+
- ❌ **AI 결과 직접 store 변경** — banner 의 approve 거치는 게 표준 (사용자 의식
|
|
105
|
+
보장)
|
|
101
106
|
- ❌ **다른 도메인 의 skill 직접 trigger** — agentType 으로 격리
|
|
102
|
-
- ❌ **mutator 안 navigate** — mutator = 순수 함수 (data → store mutation).
|
|
107
|
+
- ❌ **mutator 안 navigate** — mutator = 순수 함수 (data → store mutation).
|
|
108
|
+
navigation 은 별도
|
|
103
109
|
|
|
104
110
|
---
|
|
105
111
|
|
|
106
112
|
## 6. AI 친화 — Catalog + MCP
|
|
107
113
|
|
|
108
|
-
| Layer
|
|
109
|
-
|
|
110
|
-
| **skill-registry 자체가 catalog** | AI 가 어떤 form 에 어떤 schema fill 가능 자동 lookup
|
|
111
|
-
| **MCP server (Step 6 예정)**
|
|
112
|
-
| **schema export**
|
|
114
|
+
| Layer | 효과 |
|
|
115
|
+
| --------------------------------- | -------------------------------------------------------------------------- |
|
|
116
|
+
| **skill-registry 자체가 catalog** | AI 가 어떤 form 에 어떤 schema fill 가능 자동 lookup |
|
|
117
|
+
| **MCP server (Step 6 예정)** | AI 가 `mds.skills.list()` 또는 `mds.skills.fillable(routePath)` query 자동 |
|
|
118
|
+
| **schema export** | Zod schema → JSON Schema 변환 → AI 가 정확한 데이터 생성 |
|
|
113
119
|
|
|
114
120
|
---
|
|
115
121
|
|
|
116
122
|
## 7. 마이그레이션 — 6 도메인 의 hand-rolled → generic
|
|
117
123
|
|
|
118
124
|
현재 apps/web 에 6 도메인 hand-rolled (Step 8 마이그레이션 대상):
|
|
125
|
+
|
|
119
126
|
- `useProductFormFill` (199줄)
|
|
120
127
|
- `useSkuFormFill` (182줄)
|
|
121
128
|
- `useSellableUnitFormFill` (162줄)
|
|
@@ -123,7 +130,8 @@ function ProductFormPage() {
|
|
|
123
130
|
- `useFilterFormFill`
|
|
124
131
|
- `useBrandProfileFormFill`
|
|
125
132
|
|
|
126
|
-
각 ~30줄 의 `registerSkill` 호출로 마이그레이션. + 도메인별 Banner / Prompt →
|
|
133
|
+
각 ~30줄 의 `registerSkill` 호출로 마이그레이션. + 도메인별 Banner / Prompt →
|
|
134
|
+
shared.
|
|
127
135
|
|
|
128
136
|
총 마이그레이션 ~1.5h Claude time (Step 8 web 마이그레이션 의 일부).
|
|
129
137
|
|
|
@@ -131,21 +139,23 @@ function ProductFormPage() {
|
|
|
131
139
|
|
|
132
140
|
## 8. Cross-cutting
|
|
133
141
|
|
|
134
|
-
| Axis
|
|
135
|
-
|
|
136
|
-
| **Responsive**
|
|
137
|
-
| **A11y**
|
|
138
|
-
| **i18n**
|
|
139
|
-
| **AI Integration** | 본 layer 자체. 모든 form 의 AI fill 통일
|
|
140
|
-
| **Telemetry**
|
|
141
|
-
| **Portability**
|
|
142
|
+
| Axis | 적용 |
|
|
143
|
+
| ------------------ | ------------------------------------------------------------------------ |
|
|
144
|
+
| **Responsive** | FormFillApprovalBanner 의 mobile = vertical layout (action 버튼 stacked) |
|
|
145
|
+
| **A11y** | banner 의 `role="status"` + 사용자 approve/reject button focusable |
|
|
146
|
+
| **i18n** | "AI 가 form 을 채웠어요" + approve/reject 라벨 모두 `t()` |
|
|
147
|
+
| **AI Integration** | 본 layer 자체. 모든 form 의 AI fill 통일 |
|
|
148
|
+
| **Telemetry** | approve/reject ratio 추적 (AI 정확도 측정) |
|
|
149
|
+
| **Portability** | 외부 admin 프로젝트도 skill-registry import 만으로 동일 (Step 10) |
|
|
142
150
|
|
|
143
151
|
---
|
|
144
152
|
|
|
145
153
|
## 9. TBD
|
|
146
154
|
|
|
147
|
-
1. **AutofillNavigationPrompt 의 generic 화** — 현재 도메인별 prompt 5
|
|
148
|
-
|
|
155
|
+
1. **AutofillNavigationPrompt 의 generic 화** — 현재 도메인별 prompt 5
|
|
156
|
+
component. generic + agentType prop 로 통합
|
|
157
|
+
2. **Approve/Reject 후 navigation** — apply 후 자동 form submit vs 사용자
|
|
158
|
+
confirm
|
|
149
159
|
3. **Schema → JSON Schema** — AI 가 catalog query 시 사용. converter 추가
|
|
150
160
|
4. **Multi-step skill** — 단순 form fill 외 multi-page wizard (TBD)
|
|
151
161
|
5. **AI 가 자체 검증** — Zod validation 외 의미 검증 (예: SKU 가 unique 한지)
|
|
@@ -156,20 +166,23 @@ function ProductFormPage() {
|
|
|
156
166
|
## Related Playbooks
|
|
157
167
|
|
|
158
168
|
- [form.md](./form.md) — Form 의 AI fill 사용 시점 (Step 4.1)
|
|
159
|
-
- [feedback.md](./feedback.md) — FormFillApprovalBanner 는 banner-style (Step
|
|
169
|
+
- [feedback.md](./feedback.md) — FormFillApprovalBanner 는 banner-style (Step
|
|
170
|
+
4.2)
|
|
160
171
|
|
|
161
172
|
---
|
|
162
173
|
|
|
163
174
|
## Future — Step 6 MCP server
|
|
164
175
|
|
|
165
|
-
AI 가 mds 의 사용 약속 + props spec + 안티패턴 자동 query 가능 —
|
|
176
|
+
AI 가 mds 의 사용 약속 + props spec + 안티패턴 자동 query 가능 —
|
|
177
|
+
`mds-mcp-server` (Step 6).
|
|
166
178
|
|
|
167
179
|
```ts
|
|
168
180
|
// AI 가 query 가능
|
|
169
|
-
const fillable = await mds.skills.list();
|
|
170
|
-
const schema = await mds.skills.get('product').schema;
|
|
181
|
+
const fillable = await mds.skills.list(); // ['product', 'sku', 'sellable-unit', ...]
|
|
182
|
+
const schema = await mds.skills.get('product').schema; // Zod schema → JSON Schema
|
|
171
183
|
const pattern = await mds.playbook.search('form anti-patterns');
|
|
172
|
-
const violation = await mds.lint.check(code);
|
|
184
|
+
const violation = await mds.lint.check(code); // ESLint + audit 자동
|
|
173
185
|
```
|
|
174
186
|
|
|
175
|
-
→ AI fabrication 0 의 최종 layer. skill-registry + Playbook + Catalog 자동
|
|
187
|
+
→ AI fabrication 0 의 최종 layer. skill-registry + Playbook + Catalog 자동
|
|
188
|
+
lookup.
|
|
@@ -2,24 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
9 영역의 모든 안티 패턴 모음 — **이거 보고 commit 막힘 / 코드리뷰 reject** 기준.
|
|
4
4
|
|
|
5
|
-
> 각 항목 = `금지 패턴` + `이유` + `대안`. ESLint rule 으로 자동 강제하는 항목
|
|
5
|
+
> 각 항목 = `금지 패턴` + `이유` + `대안`. ESLint rule 으로 자동 강제하는 항목
|
|
6
|
+
> 표시.
|
|
6
7
|
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
## 1. Component 작성 (Step 1 ESLint 자동 강제 ✓)
|
|
10
11
|
|
|
11
|
-
| 안티 패턴
|
|
12
|
-
|
|
13
|
-
| `export const X = (props) => ...` (forwardRef 없음)
|
|
14
|
-
| Props interface 에 `BaseComponentProps` 미extend
|
|
15
|
-
| Root element 에 `data-mds-component` 미명시
|
|
16
|
-
| `cn(className, styles.x)` 순서 (className 앞)
|
|
17
|
-
| Root element 에 `{...rest}` 누락
|
|
18
|
-
| 컴포넌트 default export
|
|
19
|
-
| `import styled from 'styled-components'`
|
|
20
|
-
| 같은 layer cross-import (primitives/A → primitives/B)
|
|
21
|
-
| file/folder 명명 (PascalCase / useCamelCase /
|
|
22
|
-
| inline hex / px / rem / em literal
|
|
12
|
+
| 안티 패턴 | 이유 | 대안 | ESLint |
|
|
13
|
+
| --------------------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------------- | ------------------------------------ |
|
|
14
|
+
| `export const X = (props) => ...` (forwardRef 없음) | ref 전달 불가 — 일관성 깨짐 | `forwardRef<...>(function X(...))` | `mds/forward-ref-required` ✓ |
|
|
15
|
+
| Props interface 에 `BaseComponentProps` 미extend | className/style/data-testid 누락 | `extends BaseComponentProps` | `mds/base-component-props-extend` ✓ |
|
|
16
|
+
| Root element 에 `data-mds-component` 미명시 | Catalog lookup + Playwright + debug 불가 | `<div data-mds-component="X">` | `mds/data-mds-component-required` ✓ |
|
|
17
|
+
| `cn(className, styles.x)` 순서 (className 앞) | consumer override 불가 | `cn(styles.x, className)` (마지막) | `mds/classname-merge-last` ✓ |
|
|
18
|
+
| Root element 에 `{...rest}` 누락 | HTML attribute passthrough X (aria/data/event) | `<div {...rest}>` | `mds/spread-rest-props` ✓ |
|
|
19
|
+
| 컴포넌트 default export | named import 일관성 깨짐 | `export const X` | `mds/no-default-export-components` ✓ |
|
|
20
|
+
| `import styled from 'styled-components'` | runtime CSS-in-JS — bundle 부담 + token 시스템 우회 | SCSS module 만 | `mds/no-runtime-css-in-js` ✓ |
|
|
21
|
+
| 같은 layer cross-import (primitives/A → primitives/B) | layer 격리 깨짐 | 공통 코드는 foundations/hooks/utils | `mds/no-cross-layer-import` ✓ |
|
|
22
|
+
| file/folder 명명 (PascalCase / useCamelCase / _.types / _.store / \*.module.scss) | 명명 표준 깨짐 | CLAUDE.md #6 | `mds/file-naming-conventions` ✓ |
|
|
23
|
+
| inline hex / px / rem / em literal | token 시스템 우회 | `var(--token)` | `mds/no-hardcoded-visual-values` ✓ |
|
|
23
24
|
|
|
24
25
|
→ **10 rule 자동 강제**. commit 시 차단.
|
|
25
26
|
|
|
@@ -27,149 +28,149 @@
|
|
|
27
28
|
|
|
28
29
|
## 2. Form (form.md)
|
|
29
30
|
|
|
30
|
-
| 안티 패턴
|
|
31
|
-
|
|
32
|
-
| `useState<string>('')` 으로 form value 관리 | 검증 / 의도 / RHF integration 없음
|
|
33
|
-
| raw `<input>` / `<textarea>` 직접
|
|
34
|
-
| Field compound 외부 input
|
|
35
|
-
| Modal 안 Modal 안 Form
|
|
36
|
-
| inline error + toast.error 동시
|
|
37
|
-
| 가로 정렬 input 두 개가 의미 무관
|
|
38
|
-
| Notification 으로 "저장됨"
|
|
39
|
-
| Toast 로 "라이선스 만료"
|
|
40
|
-
| 도메인별 fill hook 작성 (useXxxFormFill)
|
|
41
|
-
| Inline 편집 의 blur 자동 저장
|
|
31
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
32
|
+
| ------------------------------------------- | ------------------------------------ | ---------------------------------- |
|
|
33
|
+
| `useState<string>('')` 으로 form value 관리 | 검증 / 의도 / RHF integration 없음 | RHF `useForm()` + Zod schema |
|
|
34
|
+
| raw `<input>` / `<textarea>` 직접 | Field compound 의 a11y wire 누락 | `<Field>` + `<TextField>` |
|
|
35
|
+
| Field compound 외부 input | label / error / hint 의 aria 안 wire | `<Field.Root>` 안 input |
|
|
36
|
+
| Modal 안 Modal 안 Form | 3단 mental stack | drawer 분리 또는 페이지 이동 |
|
|
37
|
+
| inline error + toast.error 동시 | 중복 — 사용자 혼란 | 한 곳만 |
|
|
38
|
+
| 가로 정렬 input 두 개가 의미 무관 | mental scan 깨짐 | vertical |
|
|
39
|
+
| Notification 으로 "저장됨" | 영속 의도 아님 | Toast |
|
|
40
|
+
| Toast 로 "라이선스 만료" | 사용자 놓침 | Notification (영속) |
|
|
41
|
+
| 도메인별 fill hook 작성 (useXxxFormFill) | 6 도메인 ~900줄 hand-rolled | `useSkillAutofill(agentType)` 30줄 |
|
|
42
|
+
| Inline 편집 의 blur 자동 저장 | 실수 위험 | Enter / 체크 명시 저장 |
|
|
42
43
|
|
|
43
44
|
---
|
|
44
45
|
|
|
45
46
|
## 3. Feedback (feedback.md)
|
|
46
47
|
|
|
47
|
-
| 안티 패턴
|
|
48
|
-
|
|
49
|
-
| Toast 로 "정말 삭제?"
|
|
50
|
-
| Notification 으로 "저장됨"
|
|
51
|
-
| Banner + Toast 동일 메시지 동시
|
|
52
|
-
| Toast 5+ 동시 stacking
|
|
53
|
-
| Dialog 안 multiple input
|
|
54
|
-
| Banner 2줄+ 긴 텍스트
|
|
55
|
-
| 영속 Notification 의 dismiss 없음 | a11y 위반
|
|
48
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
49
|
+
| --------------------------------- | -------------------------- | ------------------------------ |
|
|
50
|
+
| Toast 로 "정말 삭제?" | dismissible — 응답 못 받음 | `Modal.confirm` (Dialog) |
|
|
51
|
+
| Notification 으로 "저장됨" | 영속 의도 아님 | Toast |
|
|
52
|
+
| Banner + Toast 동일 메시지 동시 | 중복 | 한 곳 |
|
|
53
|
+
| Toast 5+ 동시 stacking | 사용자 의식 한계 | stacking 제한 |
|
|
54
|
+
| Dialog 안 multiple input | Dialog 의 의도 깨짐 | `Modal.prompt` 또는 form Modal |
|
|
55
|
+
| Banner 2줄+ 긴 텍스트 | 시각 부담 | collapse 또는 Modal |
|
|
56
|
+
| 영속 Notification 의 dismiss 없음 | a11y 위반 | 명시 닫기 |
|
|
56
57
|
|
|
57
58
|
---
|
|
58
59
|
|
|
59
60
|
## 4. Overlay (overlay.md)
|
|
60
61
|
|
|
61
|
-
| 안티 패턴
|
|
62
|
-
|
|
63
|
-
| Modal 안 Modal
|
|
64
|
-
| Drawer 안 Drawer
|
|
65
|
-
| Modal ↔ Drawer 혼합 (Modal 안 Drawer 등) | focus / layer 충돌
|
|
66
|
-
| Popover 안 Popover
|
|
67
|
-
| Dialog 안 form (input multiple)
|
|
68
|
-
| Popover 안 큰 form
|
|
69
|
-
| isDirty close 시 force confirm 없음
|
|
70
|
-
| Drawer 의 width 변경 (사용자 resize)
|
|
71
|
-
| Sheet 의 height 가 viewport 90%+
|
|
62
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
63
|
+
| ----------------------------------------- | -------------------------------- | --------------------------- |
|
|
64
|
+
| Modal 안 Modal | 3단 stack | drawer 분리 |
|
|
65
|
+
| Drawer 안 Drawer | 동일 | nested drawer 외 금지 |
|
|
66
|
+
| Modal ↔ Drawer 혼합 (Modal 안 Drawer 등) | focus / layer 충돌 | 한 종류 만 |
|
|
67
|
+
| Popover 안 Popover | auto-position 충돌 | nested popover 금지 |
|
|
68
|
+
| Dialog 안 form (input multiple) | Dialog 의도 깨짐 | `Modal.prompt` |
|
|
69
|
+
| Popover 안 큰 form | Popover 의도 (작은 inline) 아님 | Modal |
|
|
70
|
+
| isDirty close 시 force confirm 없음 | 사용자 변경 사항 손실 | `<Modal isDirty={isDirty}>` |
|
|
71
|
+
| Drawer 의 width 변경 (사용자 resize) | layout 깨짐 | caller fix |
|
|
72
|
+
| Sheet 의 height 가 viewport 90%+ | full-screen modal 이 차라리 명확 | Sheet 의 의도 모호 |
|
|
72
73
|
|
|
73
74
|
---
|
|
74
75
|
|
|
75
76
|
## 5. DataGrid + Table (data-grid.md)
|
|
76
77
|
|
|
77
|
-
| 안티 패턴
|
|
78
|
-
|
|
79
|
-
| DataGrid 안 form (모든 row 의 input)
|
|
80
|
-
| Table 으로 DataGrid 흉내 (filter/pagination caller 구현) | 중복 + 일관성 X
|
|
81
|
-
| column 50+
|
|
82
|
-
| loading 시 ErrorState / EmptyState 표시
|
|
83
|
-
| row 클릭 + checkbox 동시 작동
|
|
84
|
-
| pagination size 50+
|
|
85
|
-
| selection 이 page 이동 후 사라짐
|
|
86
|
-
| column header 가 너무 길거나 wrap
|
|
78
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
79
|
+
| -------------------------------------------------------- | ----------------------- | -------------------------------- |
|
|
80
|
+
| DataGrid 안 form (모든 row 의 input) | 큰 form 은 별도 페이지 | page-form 분리 |
|
|
81
|
+
| Table 으로 DataGrid 흉내 (filter/pagination caller 구현) | 중복 + 일관성 X | DataGrid |
|
|
82
|
+
| column 50+ | 사용자 scan 불가 | hidden + caller 선택 |
|
|
83
|
+
| loading 시 ErrorState / EmptyState 표시 | async-states.md §2 위반 | loading 만 |
|
|
84
|
+
| row 클릭 + checkbox 동시 작동 | 의도 충돌 | selection 모드 시 row click 막힘 |
|
|
85
|
+
| pagination size 50+ | render 부담 + a11y | 최대 20-30 |
|
|
86
|
+
| selection 이 page 이동 후 사라짐 | cross-page 의도 깨짐 | DataGrid 의 selectedIds Set 유지 |
|
|
87
|
+
| column header 가 너무 길거나 wrap | fixed height 깨짐 | ellipsis |
|
|
87
88
|
|
|
88
89
|
---
|
|
89
90
|
|
|
90
91
|
## 6. Page Layout (page-layout.md)
|
|
91
92
|
|
|
92
|
-
| 안티 패턴
|
|
93
|
-
|
|
94
|
-
| 페이지마다 다른 layout
|
|
95
|
-
| DataGrid 페이지 에 max-width
|
|
96
|
-
| form 페이지에 max-width 없음
|
|
97
|
-
| PageHeader 의 actions 가 row-level
|
|
98
|
-
| Sidebar 안 sub-Item 3 단계+
|
|
99
|
-
| section gap / padding hardcoded
|
|
100
|
-
| mobile 에서 Sidebar 그대로 left sticky | UX 깨짐
|
|
93
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
94
|
+
| -------------------------------------- | ------------------------------ | --------------------------------------- |
|
|
95
|
+
| 페이지마다 다른 layout | 사용자 mental model 깨짐 | AppShell + PageHeader 표준 |
|
|
96
|
+
| DataGrid 페이지 에 max-width | content 답답 (1920px 모니터) | full-bleed |
|
|
97
|
+
| form 페이지에 max-width 없음 | 가로 input 한 줄 어색 | 640~800px |
|
|
98
|
+
| PageHeader 의 actions 가 row-level | DataGrid column action 의 의도 | PageHeader 는 page-level |
|
|
99
|
+
| Sidebar 안 sub-Item 3 단계+ | mental load | 2 단계 max |
|
|
100
|
+
| section gap / padding hardcoded | token 시스템 우회 | `var(--section-gap)` / `var(--space-*)` |
|
|
101
|
+
| mobile 에서 Sidebar 그대로 left sticky | UX 깨짐 | drawer 변환 |
|
|
101
102
|
|
|
102
103
|
---
|
|
103
104
|
|
|
104
105
|
## 7. Array Input (array-input.md)
|
|
105
106
|
|
|
106
|
-
| 안티 패턴
|
|
107
|
-
|
|
108
|
-
| 무한 깊은 nested (drawer 안 drawer 안 drawer) | mental stack 한계
|
|
109
|
-
| inline edit + drawer 편집 동시
|
|
110
|
-
| add 버튼이 row 사이
|
|
111
|
-
| remove 의 confirm 없음 (이미 저장 entity)
|
|
112
|
-
| 0 row 의 empty state 없음
|
|
113
|
-
| dnd 만 (keyboard reorder 없음)
|
|
114
|
-
| submit 시 빈 row valid
|
|
107
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
108
|
+
| --------------------------------------------- | ------------------- | ------------------------ |
|
|
109
|
+
| 무한 깊은 nested (drawer 안 drawer 안 drawer) | mental stack 한계 | 2 단 max |
|
|
110
|
+
| inline edit + drawer 편집 동시 | 의도 충돌 | 한쪽만 |
|
|
111
|
+
| add 버튼이 row 사이 | mental scan 깨짐 | row 끝 |
|
|
112
|
+
| remove 의 confirm 없음 (이미 저장 entity) | 실수 위험 | `Modal.confirm` |
|
|
113
|
+
| 0 row 의 empty state 없음 | placeholder 부족 | `<EmptyState size="sm">` |
|
|
114
|
+
| dnd 만 (keyboard reorder 없음) | a11y 위반 | dnd-kit keyboard sensor |
|
|
115
|
+
| submit 시 빈 row valid | 데이터 quality 깨짐 | Zod `.min(1)` |
|
|
115
116
|
|
|
116
117
|
---
|
|
117
118
|
|
|
118
119
|
## 8. Async States (async-states.md)
|
|
119
120
|
|
|
120
|
-
| 안티 패턴
|
|
121
|
-
|
|
122
|
-
| Spinner 우선 사용
|
|
123
|
-
| Loading 의 깜빡임 (< 200ms)
|
|
124
|
-
| Action button loading + 별도 progress bar | 중복
|
|
125
|
-
| Background 의 modal loading
|
|
126
|
-
| Empty 의 "다시 시도" 만
|
|
127
|
-
| Error 시 toast 만 (page-level)
|
|
128
|
-
| Skeleton 의 layout mismatch
|
|
121
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
122
|
+
| ----------------------------------------- | ------------------------- | ------------------------ |
|
|
123
|
+
| Spinner 우선 사용 | 의도 모호 / placeholder X | Skeleton |
|
|
124
|
+
| Loading 의 깜빡임 (< 200ms) | UX 불안정 | min display time 200ms |
|
|
125
|
+
| Action button loading + 별도 progress bar | 중복 | button loading 만 |
|
|
126
|
+
| Background 의 modal loading | 사용자 인터럽트 | silent 또는 corner toast |
|
|
127
|
+
| Empty 의 "다시 시도" 만 | empty != error | EmptyState 의 의도 따라 |
|
|
128
|
+
| Error 시 toast 만 (page-level) | content 자리 missing | ErrorState content area |
|
|
129
|
+
| Skeleton 의 layout mismatch | 깜빡임 효과 | 실 content shape |
|
|
129
130
|
|
|
130
131
|
---
|
|
131
132
|
|
|
132
133
|
## 9. AI Fill (ai-fill.md)
|
|
133
134
|
|
|
134
|
-
| 안티 패턴
|
|
135
|
-
|
|
136
|
-
| 도메인별 fill hook (`useXxxFormFill`) | 6 도메인 ~900줄 hand-rolled | `useSkillAutofill(agentType)`
|
|
137
|
-
| 도메인별 FormFillBanner
|
|
138
|
-
| eventBus name hardcode
|
|
139
|
-
| schema 검증 없는 mutator
|
|
140
|
-
| AI 결과 직접 store 변경 (banner skip) | 사용자 의식 없음
|
|
141
|
-
| 다른 도메인 skill trigger
|
|
142
|
-
| mutator 안 navigate
|
|
135
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
136
|
+
| ------------------------------------- | --------------------------- | ------------------------------- |
|
|
137
|
+
| 도메인별 fill hook (`useXxxFormFill`) | 6 도메인 ~900줄 hand-rolled | `useSkillAutofill(agentType)` |
|
|
138
|
+
| 도메인별 FormFillBanner | 6 component 중복 | shared `FormFillApprovalBanner` |
|
|
139
|
+
| eventBus name hardcode | 도메인별 name typo 위험 | skill-registry 자동 발행 |
|
|
140
|
+
| schema 검증 없는 mutator | invalid 데이터 store 진입 | Zod schema 필수 |
|
|
141
|
+
| AI 결과 직접 store 변경 (banner skip) | 사용자 의식 없음 | banner approve 거치기 |
|
|
142
|
+
| 다른 도메인 skill trigger | agent 격리 깨짐 | agentType 격리 |
|
|
143
|
+
| mutator 안 navigate | 순수성 깨짐 | navigation 별도 |
|
|
143
144
|
|
|
144
145
|
---
|
|
145
146
|
|
|
146
147
|
## 10. Cross-cutting 안티 패턴
|
|
147
148
|
|
|
148
|
-
| 안티 패턴
|
|
149
|
-
|
|
150
|
-
| **i18n raw string** ("저장" 직접)
|
|
151
|
-
| **a11y aria-label / role 없음**
|
|
152
|
-
| **token 외 hardcoded color / spacing**
|
|
149
|
+
| 안티 패턴 | 이유 | 대안 |
|
|
150
|
+
| ------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------- |
|
|
151
|
+
| **i18n raw string** ("저장" 직접) | locale 변경 시 깨짐 | `t('common.save')` |
|
|
152
|
+
| **a11y aria-label / role 없음** | screen reader 누락 | Radix 기반 + 명시 aria |
|
|
153
|
+
| **token 외 hardcoded color / spacing** | theme 안 자동 변환 | `var(--token)` 만 |
|
|
153
154
|
| **mobile breakpoint hardcoded** (`@media (max-width: 768px)`) | breakpoint 변경 시 mismatch | `var(--breakpoint-md)` (CSS @media 안 직접 사용 못 함 — SCSS @media + Tailwind) |
|
|
154
|
-
| **새 컴포넌트 추가 시 stories / test 누락**
|
|
155
|
-
| **CI 통과 안 한 채 commit**
|
|
155
|
+
| **새 컴포넌트 추가 시 stories / test 누락** | audit 차단 + 사용 예 없음 | `pnpm mds:new` (Step 9 generator) |
|
|
156
|
+
| **CI 통과 안 한 채 commit** | drift 가능 | pre-commit + CI 강제 (작동 중) |
|
|
156
157
|
|
|
157
158
|
---
|
|
158
159
|
|
|
159
160
|
## 11. 강제 layer 종합
|
|
160
161
|
|
|
161
|
-
| Layer
|
|
162
|
-
|
|
163
|
-
| **ESLint 10 rule (Step 1)**
|
|
164
|
-
| **audit-components (Step 2)**
|
|
165
|
-
| **baseline lock (max-warnings 551)** | 새 ESLint warning 1개도 차단
|
|
166
|
-
| **verify:tokens**
|
|
167
|
-
| **verify:stories**
|
|
168
|
-
| **a11y axe-playwright (Step 2.C)**
|
|
169
|
-
| **visual regression**
|
|
170
|
-
| **prefers-reduced-motion**
|
|
171
|
-
| **style-dictionary**
|
|
172
|
-
| **pre-commit + CI**
|
|
162
|
+
| Layer | 자동 강제 |
|
|
163
|
+
| ------------------------------------ | ----------------------------------------------------------------------------- |
|
|
164
|
+
| **ESLint 10 rule (Step 1)** | §1 의 10 항목 + cross-layer + hardcoded |
|
|
165
|
+
| **audit-components (Step 2)** | file-structure / forwardRef / data-mds / use-client / BaseComponentProps / cn |
|
|
166
|
+
| **baseline lock (max-warnings 551)** | 새 ESLint warning 1개도 차단 |
|
|
167
|
+
| **verify:tokens** | hardcoded color / spacing |
|
|
168
|
+
| **verify:stories** | 정의 안 된 CSS var() |
|
|
169
|
+
| **a11y axe-playwright (Step 2.C)** | WCAG 2.1 AA 위반 |
|
|
170
|
+
| **visual regression** | snapshot diff 강제 confirm |
|
|
171
|
+
| **prefers-reduced-motion** | 운동 민감증 자동 |
|
|
172
|
+
| **style-dictionary** | theme 별 자동 token 적용 |
|
|
173
|
+
| **pre-commit + CI** | drift 차단 |
|
|
173
174
|
|
|
174
175
|
→ **새 컴포넌트 / 변경 시 위 10 layer 모두 자동 통과 필수**. 우회 시도해도 차단.
|
|
175
176
|
|
|
@@ -178,6 +179,7 @@
|
|
|
178
179
|
## Related Playbooks
|
|
179
180
|
|
|
180
181
|
각 영역의 detail:
|
|
182
|
+
|
|
181
183
|
- [form.md](./form.md) (Step 4.1)
|
|
182
184
|
- [feedback.md](./feedback.md) (Step 4.2)
|
|
183
185
|
- [data-grid.md](./data-grid.md) (Step 4.3)
|