@su-record/vibe 2.8.48 → 2.8.49
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/hooks/scripts/figma-extract.js +141 -16
- package/package.json +1 -1
- package/skills/vibe.figma/SKILL.md +106 -961
- package/skills/vibe.figma.convert/SKILL.md +91 -14
- package/skills/vibe.figma.extract/SKILL.md +44 -7
|
@@ -18,109 +18,43 @@ Figma 트리가 코드의 원천이다. 스크린샷은 검증용이다.
|
|
|
18
18
|
✅ 스크린샷은 생성이 아닌 검증에만 사용
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
## ⛔ 불변 규칙
|
|
21
|
+
## ⛔ 불변 규칙
|
|
22
22
|
|
|
23
23
|
```
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
1. screenshot으로 콘텐츠를 이미지화 금지
|
|
25
|
+
✅ BG 렌더링 (TEXT 자식 없는 배경), 벡터 글자 GROUP, 섹션 스크린샷(검증용)
|
|
26
|
+
❌ TEXT 자식 프레임, INSTANCE 반복, 버튼/가격, 섹션 통째 렌더링
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
screenshot 명령의 허용 범위:
|
|
29
|
-
✅ BG 프레임 렌더링 (TEXT 자식이 없는 순수 배경)
|
|
30
|
-
✅ 벡터 글자 GROUP 렌더링 (웹폰트 없는 장식 타이틀)
|
|
31
|
-
✅ 섹션별 스크린샷 → sections/ 폴더 (Phase 6 검증용)
|
|
32
|
-
screenshot 명령의 금지 범위:
|
|
33
|
-
❌ TEXT 자식이 있는 프레임 → HTML로 구현
|
|
34
|
-
❌ INSTANCE 반복 패턴이 있는 프레임 → HTML v-for로 구현
|
|
35
|
-
❌ 버튼/가격/수량이 있는 프레임 → HTML로 구현
|
|
36
|
-
❌ "섹션 콘텐츠"를 통째 렌더링 → HTML로 구현
|
|
28
|
+
2. BG는 CSS background-image만. <img> 태그 금지.
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
daily-step2-list.webp (보상 목록을 이미지로) → 수량 변경 불가, 접근성 불가
|
|
41
|
-
|
|
42
|
-
2. BG를 <img> 태그로 넣는 것은 금지한다.
|
|
43
|
-
❌ <img src="bg.webp"> 또는 <div class="bg"><img src="bg.webp"></div>
|
|
44
|
-
✅ .section { background-image: url('bg.webp'); background-size: cover; }
|
|
45
|
-
|
|
46
|
-
3. Phase 4 코드 생성 중 새로운 screenshot 호출은 금지한다.
|
|
47
|
-
Phase 2에서 확보한 재료만 사용한다.
|
|
48
|
-
"이 부분이 복잡하니 screenshot으로 이미지화하자"는 금지된 사고방식이다.
|
|
49
|
-
복잡한 UI도 tree.json의 구조를 따라 HTML+CSS로 구현한다.
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## 금지 사항
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
❌ 스크린샷을 보고 CSS 추정 (범용 LLM의 약점)
|
|
56
|
-
❌ Figma 레이어를 무분별하게 div soup로 변환
|
|
57
|
-
❌ CSS로 이미지 재현 (gradient/shape으로 그림 그리기)
|
|
58
|
-
❌ 이미지 다운로드 없이 코드 생성 진행
|
|
59
|
-
❌ placeholder / 빈 src="" 남기기
|
|
60
|
-
❌ tree.json에 없는 CSS 값을 추정하여 작성
|
|
61
|
-
❌ 컴포넌트 파일 안에 <style> 블록 / 인라인 style=""
|
|
62
|
-
✅ tree.json의 CSS 속성을 SCSS에 직접 매핑
|
|
63
|
-
✅ 외부 SCSS 파일에만 스타일 작성
|
|
64
|
-
|
|
65
|
-
━━━ 이미지 vs HTML 판별 (가장 흔한 실수) ━━━
|
|
66
|
-
|
|
67
|
-
❌ BG를 <img> 태그로 처리 → 반드시 CSS background-image
|
|
68
|
-
잘못된 예: <img src="hero-bg.webp" class="bg-img" />
|
|
69
|
-
올바른 예: .section { background-image: url('hero-bg.webp'); background-size: cover; }
|
|
70
|
-
|
|
71
|
-
❌ 카드/아이템 그리드를 통째 이미지 1장으로 렌더링
|
|
72
|
-
잘못된 예: <img src="exchange-section1.webp" /> (카드 4개가 한 이미지)
|
|
73
|
-
올바른 예: v-for로 개별 카드 HTML 생성 + 내부 아이콘만 <img>
|
|
74
|
-
|
|
75
|
-
❌ TEXT 자식이 있는 프레임을 이미지로 렌더링
|
|
76
|
-
잘못된 예: <img src="daily-step2-list.webp" /> (텍스트+카드 포함)
|
|
77
|
-
올바른 예: HTML로 텍스트/레이아웃 구현 + 순수 이미지 에셋만 <img>
|
|
78
|
-
|
|
79
|
-
✅ 이미지로 렌더링 가능한 것: 벡터 글자, 합성 BG(텍스트 미포함), 래스터 에셋, 복잡 벡터
|
|
30
|
+
3. Phase 4 코드 생성 중 새 screenshot 호출 금지.
|
|
31
|
+
Phase 2 재료만 사용. 복잡해도 HTML+CSS로 구현.
|
|
80
32
|
```
|
|
81
33
|
|
|
82
34
|
## 전체 플로우
|
|
83
35
|
|
|
84
36
|
```
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
PC Design: https://figma.com/...?node-id=yyy (있으면)
|
|
37
|
+
입력: 모든 URL을 한번에 받는다
|
|
38
|
+
Storyboard: figma.com/...?node-id=aaa (있으면)
|
|
39
|
+
MO Design: figma.com/...?node-id=xxx
|
|
40
|
+
PC Design: figma.com/...?node-id=yyy (있으면)
|
|
90
41
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
작업 디렉토리 구조:
|
|
100
|
-
각 Figma URL의 ROOT name에서 폴더명 추출 (kebab-case):
|
|
101
|
-
"MO_Main ..." → /tmp/{feature}/mo-main/
|
|
102
|
-
"PC_Main ..." → /tmp/{feature}/pc-main/
|
|
42
|
+
→ Phase 0: Setup
|
|
43
|
+
→ Phase 1: 스토리보드 분석 → 기능 스펙 문서
|
|
44
|
+
→ Phase 2: 재료 확보 (→ vibe.figma.extract)
|
|
45
|
+
→ Phase 3: 리매핑 (MO↔PC 매칭 → remapped.json)
|
|
46
|
+
→ Phase 4: 순차 코드 생성 (→ vibe.figma.convert)
|
|
47
|
+
→ Phase 5: 컴파일 게이트
|
|
48
|
+
→ Phase 6: 시각 검증 루프
|
|
103
49
|
|
|
50
|
+
작업 디렉토리:
|
|
104
51
|
/tmp/{feature}/
|
|
105
|
-
├── mo-main/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
│ ├── content/ ← 콘텐츠 노드 렌더링
|
|
109
|
-
│ └── sections/ ← Phase 4 검증용 스크린샷
|
|
110
|
-
├── pc-main/ ← 데스크탑 Phase 2 추출 결과
|
|
111
|
-
│ └── (동일 구조)
|
|
112
|
-
└── remapped.json ← Phase 3 리매핑 결과 (모든 BP 통합)
|
|
113
|
-
|
|
114
|
-
remapped.json이 Phase 4의 유일한 입력.
|
|
115
|
-
→ BP 간 노드 매칭 완료
|
|
116
|
-
→ CSS diff (mo/pc) 포함
|
|
117
|
-
→ 이미지 렌더링 파일 경로 포함
|
|
118
|
-
→ Phase 4에서 바로 코드 생성 가능
|
|
52
|
+
├── mo-main/tree.json, bg/, content/, sections/
|
|
53
|
+
├── pc-main/tree.json, bg/, content/, sections/
|
|
54
|
+
└── remapped.json ← Phase 4의 유일한 입력
|
|
119
55
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
styles/{feature}/layout/_hero.scss (모바일 기본 + @media PC)
|
|
123
|
-
styles/{feature}/components/_hero.scss
|
|
56
|
+
코드 출력: 프로젝트 디렉토리에 직접 배치
|
|
57
|
+
components/{feature}/, styles/{feature}/
|
|
124
58
|
```
|
|
125
59
|
|
|
126
60
|
---
|
|
@@ -128,937 +62,148 @@ Figma 트리가 코드의 원천이다. 스크린샷은 검증용이다.
|
|
|
128
62
|
## Phase 0: Setup
|
|
129
63
|
|
|
130
64
|
```
|
|
131
|
-
1.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
3. 피처명 결정:
|
|
141
|
-
- Figma 파일명에서 추출 → kebab-case
|
|
142
|
-
- 예: "PUBG 겨울 PC방 이벤트" → winter-pcbang
|
|
143
|
-
|
|
144
|
-
4. 디렉토리 생성:
|
|
145
|
-
- components/{feature}/
|
|
146
|
-
- public/images/{feature}/ (또는 static/images/{feature}/)
|
|
147
|
-
- styles/{feature}/ (layout/, components/ 하위)
|
|
148
|
-
|
|
149
|
-
5. 기존 컴포넌트 스캔 + 인덱싱:
|
|
150
|
-
- Glob "components/**/*.vue" or "components/**/*.tsx"
|
|
151
|
-
- 재사용 가능한 컴포넌트 목록 수집 (GNB, Footer, Button 등)
|
|
152
|
-
|
|
153
|
-
5-1. 컴포넌트 상세 인덱싱 (50개 이하 스캔):
|
|
154
|
-
대상 파일 수집 (우선순위 순서, 합산 50개 이내):
|
|
155
|
-
barrel file index.ts → components/ui/ → components/common/ → components/shared/ → 나머지
|
|
156
|
-
각 디렉토리 내에서는 1-depth 파일만 (하위 재귀 탐색 안 함)
|
|
157
|
-
|
|
158
|
-
각 파일에 대해 2단계 추출:
|
|
159
|
-
a. Grep: defineProps|interface Props|<slot|@description|class=|className=
|
|
160
|
-
→ 시작 줄 번호 탐색
|
|
161
|
-
b. Read: 시작줄 ~ +30줄 추출
|
|
162
|
-
Vue/Svelte template+script 분리 파일: 첫 300줄 전체 Read
|
|
163
|
-
barrel file (index.ts): 전체 Read
|
|
164
|
-
|
|
165
|
-
추출 항목:
|
|
166
|
-
- Props/Interface: defineProps<{...}> 또는 interface Props {...} 에서 prop 이름+타입
|
|
167
|
-
※ 외부 타입 참조 시 (defineProps<ButtonProps>()) → types 인덱스에서 교차 참조
|
|
168
|
-
- Slots: <slot> 또는 {children} 패턴
|
|
169
|
-
- 스타일 클래스: class="..." 또는 className={styles.xxx} 최상위 요소의 클래스명
|
|
170
|
-
- 용도 힌트: JSDoc @description 또는 파일 첫 번째 주석
|
|
171
|
-
|
|
172
|
-
5-2. 인덱스 결과 저장:
|
|
173
|
-
저장 경로: /tmp/{feature}/component-index.json
|
|
174
|
-
[
|
|
175
|
-
{ name: "BaseButton",
|
|
176
|
-
path: "components/common/BaseButton.vue",
|
|
177
|
-
props: [{ name: "label", type: "string" }, { name: "variant", type: "'primary'|'secondary'" }],
|
|
178
|
-
slots: ["default"],
|
|
179
|
-
classes: ["btn", "btn--primary"],
|
|
180
|
-
description: "공통 버튼 컴포넌트" },
|
|
181
|
-
...
|
|
182
|
-
]
|
|
183
|
-
|
|
184
|
-
컨텍스트 관리:
|
|
185
|
-
- 컴포넌트 20개 이하: 전체 인덱스를 프롬프트에 포함
|
|
186
|
-
- 컴포넌트 20개 초과: 이름+설명+축약 props(이름만, 타입 생략)만 포함
|
|
187
|
-
매칭 후보 발견 시 해당 파일 Read로 상세 확인
|
|
188
|
-
- classes 필드는 요약에서 제외 (매칭 시 파일 Read로 확인)
|
|
189
|
-
|
|
190
|
-
5-3. Hooks/Types/Constants 인덱싱:
|
|
191
|
-
추가 스캔 대상:
|
|
192
|
-
- Composables/Hooks: composables/**/*.ts, hooks/**/*.ts
|
|
193
|
-
→ export 함수명 + 파라미터 + 반환 타입
|
|
194
|
-
- 타입 정의: types/**/*.ts, types.ts
|
|
195
|
-
→ export interface/type 이름 + 최상위 필드
|
|
196
|
-
- 상수: constants/**/*.ts
|
|
197
|
-
→ export const 이름 + 값 (또는 타입)
|
|
198
|
-
|
|
199
|
-
저장: /tmp/{feature}/context-index.json (component-index와 별도)
|
|
200
|
-
컨텍스트 관리:
|
|
201
|
-
- 항목 30개 이하: 전체 인덱스를 프롬프트에 포함
|
|
202
|
-
- 항목 30개 초과: 이름+핵심 시그니처만 요약, 상세 필요 시 파일 Read로 지연 조회
|
|
203
|
-
|
|
204
|
-
타임아웃: 파일당 Read 최대 300줄, 전체 인덱싱 2분 이내 완료
|
|
205
|
-
|
|
206
|
-
6. 기존 디자인 토큰 스캔:
|
|
207
|
-
|
|
208
|
-
SCSS 토큰:
|
|
209
|
-
Glob: **/_variables.scss, **/_tokens.scss, **/_colors.scss, **/variables.scss
|
|
210
|
-
→ 패턴: $변수명: 값; 추출
|
|
211
|
-
→ 결과: { name: "$color-primary", value: "#3b82f6", source: "styles/_variables.scss" }
|
|
212
|
-
|
|
213
|
-
CSS Variables:
|
|
214
|
-
Glob: **/global.css, **/variables.css, **/root.css, **/app.css
|
|
215
|
-
Grep: "--[a-zA-Z].*:" 패턴
|
|
216
|
-
→ 결과: { name: "--color-primary", value: "#3b82f6", source: "styles/global.css" }
|
|
217
|
-
|
|
218
|
-
Tailwind:
|
|
219
|
-
tailwind.config.{js,ts,mjs} 존재 시 Read
|
|
220
|
-
→ theme.colors, theme.extend.colors 에서 커스텀 색상 추출
|
|
221
|
-
→ theme.spacing, theme.fontSize 에서 커스텀 값 추출
|
|
222
|
-
→ Tailwind v4: global CSS에서 @theme 디렉티브 내 CSS variable도 스캔
|
|
223
|
-
→ 결과: { name: "blue-500", value: "#3b82f6", type: "tailwind", category: "color" }
|
|
224
|
-
|
|
225
|
-
CSS-in-JS:
|
|
226
|
-
Glob: **/theme.{ts,js}, **/tokens.{ts,js}, **/design-tokens.{ts,js}
|
|
227
|
-
→ export 객체에서 color/spacing/typography 키 추출
|
|
228
|
-
|
|
229
|
-
통합 결과 → /tmp/{feature}/project-tokens.json:
|
|
230
|
-
{
|
|
231
|
-
colors: [{ name, value, source }],
|
|
232
|
-
spacing: [{ name, value, source }],
|
|
233
|
-
typography: [{ name, value, source }],
|
|
234
|
-
other: [{ name, value, source }]
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
토큰 소스 우선순위 (복수 시스템 공존 시):
|
|
238
|
-
Tailwind > CSS Variables > SCSS > CSS-in-JS
|
|
239
|
-
|
|
240
|
-
성능: 100개 파일 기준 5초 이내 완료
|
|
241
|
-
파싱 실패 시: 해당 파일 스킵 + 경고 로그 (전체 중단하지 않음)
|
|
65
|
+
1. 스택 감지: package.json → react/vue/svelte, next/nuxt, scss/tailwind
|
|
66
|
+
2. 피처명: Figma 파일명 → kebab-case
|
|
67
|
+
3. 디렉토리: components/{feature}/, public/images/{feature}/, styles/{feature}/
|
|
68
|
+
4. 컴포넌트 인덱싱 → /tmp/{feature}/component-index.json
|
|
69
|
+
(50개 이하 스캔, props/slots/classes 추출, 2분 이내)
|
|
70
|
+
5. Hooks/Types/Constants → /tmp/{feature}/context-index.json
|
|
71
|
+
6. 디자인 토큰 스캔 → /tmp/{feature}/project-tokens.json
|
|
72
|
+
(SCSS > CSS Variables > Tailwind > CSS-in-JS)
|
|
242
73
|
```
|
|
243
74
|
|
|
244
75
|
---
|
|
245
76
|
|
|
246
|
-
## Phase 1:
|
|
247
|
-
|
|
248
|
-
사용자에게 질문한다:
|
|
249
|
-
- question: "스토리보드, 디자인 리소스를 줄바꿈으로 입력해주세요. (순서 무관)"
|
|
250
|
-
- 예시:
|
|
251
|
-
https://figma.com/design/xxx/...?node-id=20-4964
|
|
252
|
-
https://figma.com/design/yyy/...?node-id=641-78147
|
|
253
|
-
https://figma.com/design/yyy/...?node-id=641-78200
|
|
254
|
-
또는:
|
|
255
|
-
docs/storyboard.pdf
|
|
256
|
-
https://figma.com/design/yyy/...?node-id=641-78147
|
|
257
|
-
- options 제공 금지 — 자유 텍스트 입력만 허용
|
|
258
|
-
- 디자인 URL 최소 1개 필수
|
|
259
|
-
|
|
260
|
-
### 입력 분류 (자동)
|
|
261
|
-
|
|
262
|
-
```
|
|
263
|
-
각 줄을 파싱하여 분류:
|
|
264
|
-
|
|
265
|
-
1. figma.com URL → Figma 리소스
|
|
266
|
-
→ ROOT name으로 스토리보드/MO/PC 판별
|
|
267
|
-
→ fileKey가 다르면 스토리보드 vs 디자인 구분
|
|
268
|
-
→ ROOT name에 "MO" → 모바일, "PC" → 데스크탑
|
|
269
|
-
|
|
270
|
-
2. .pdf 파일 경로 → PDF 스토리보드
|
|
271
|
-
→ Read 도구로 페이지별 분석
|
|
272
|
-
|
|
273
|
-
3. .png/.jpg/.webp 파일 경로 → 이미지 스토리보드
|
|
274
|
-
→ Read 도구로 시각 분석
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### 1-1. 스토리보드 분석
|
|
278
|
-
|
|
279
|
-
```
|
|
280
|
-
입력 타입에 따라 분기:
|
|
281
|
-
|
|
282
|
-
■ Figma URL인 경우:
|
|
283
|
-
URL에서 fileKey, nodeId 추출
|
|
284
|
-
|
|
285
|
-
1단계 (BLOCKING): 루트 depth=2로 전체 프레임 + nodeId 수집
|
|
286
|
-
# [FIGMA_SCRIPT] = ~/.vibe/hooks/scripts/figma-extract.js
|
|
287
|
-
node "[FIGMA_SCRIPT]" tree {fileKey} {nodeId} --depth=2
|
|
288
|
-
|
|
289
|
-
→ 모든 자식 프레임의 name + nodeId + size 를 테이블로 출력
|
|
290
|
-
→ nodeId가 빠진 프레임이 있으면 안 됨
|
|
291
|
-
|
|
292
|
-
2단계: name 패턴으로 프레임 분류
|
|
293
|
-
SPEC — "기능 정의서", "정책" → depth 높여서 텍스트 추출
|
|
294
|
-
CONFIG — "해상도", "브라우저" → designWidth, minWidth, breakpoint 확보
|
|
295
|
-
SHARED — "공통", "GNB", "Footer", "Popup" → 공통 컴포넌트 파악
|
|
296
|
-
PAGE — "화면설계", "메인 -" → 섹션 목록 + 인터랙션 스펙
|
|
297
|
-
|
|
298
|
-
핵심 프레임 선별 (전부 읽지 않음):
|
|
299
|
-
1순위: SPEC (기능 정의서) — 1개
|
|
300
|
-
2순위: CONFIG (해상도) — 1개
|
|
301
|
-
3순위: PAGE 중 메인 섹션만 (3.1, 3.2, 3.3, 3.4, 3.5, 3.6)
|
|
302
|
-
하위 케이스(3.1.1, 3.2.1 등)는 건너뜀
|
|
303
|
-
4순위: SHARED (공통 요소, Popup) — 필요 시
|
|
304
|
-
|
|
305
|
-
■ PDF 파일인 경우:
|
|
306
|
-
Read 도구로 페이지별 분석 (pages: "1-5" → "6-10" → ...)
|
|
307
|
-
각 페이지에서 추출:
|
|
308
|
-
- 섹션 목록 + 기능 정의
|
|
309
|
-
- 해상도/브레이크포인트 정보 (CONFIG)
|
|
310
|
-
- 인터랙션 흐름
|
|
311
|
-
- 상태 정의
|
|
312
|
-
→ Figma 스토리보드와 동일한 기능 스펙 문서 생성
|
|
313
|
-
|
|
314
|
-
■ 이미지 파일인 경우:
|
|
315
|
-
Read 도구로 시각 분석
|
|
316
|
-
→ 와이어프레임/목업에서 섹션 구조 + 기능 추출
|
|
317
|
-
|
|
318
|
-
■ 스토리보드 없음:
|
|
319
|
-
디자인 URL의 tree.json에서 섹션 목록만 추출
|
|
320
|
-
→ 기능 정의는 디자인 name/구조에서 추정 (정확도 낮음)
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### 1-2. 기능 스펙 문서 작성 (파일 생성 없음)
|
|
77
|
+
## Phase 1: 스토리보드 분석
|
|
324
78
|
|
|
325
79
|
```
|
|
326
|
-
|
|
327
|
-
→ Phase 1 HTML 구조와 Phase 4 트리 매핑이 충돌하면 이중 작업
|
|
328
|
-
→ Phase 4에서 remapped.json 기반으로 코드를 생성
|
|
80
|
+
사용자 입력: URL 또는 PDF/이미지를 줄바꿈으로 입력
|
|
329
81
|
|
|
330
|
-
|
|
82
|
+
URL 분류 (자동):
|
|
83
|
+
fileKey 다름 → 스토리보드 vs 디자인
|
|
84
|
+
ROOT name에 "MO" → 모바일, "PC" → 데스크탑
|
|
331
85
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
| 2 | DailyCheckIn | Daily | 3604px | 출석 미션 |
|
|
337
|
-
| 3 | PlayTime | Frame 633371 | 11363px | 플레이타임 미션 |
|
|
86
|
+
스토리보드 분석:
|
|
87
|
+
depth=2로 프레임 수집 → name 패턴으로 분류
|
|
88
|
+
SPEC(기능정의서) → CONFIG(해상도) → PAGE(메인 섹션) → SHARED(공통)
|
|
89
|
+
PDF/이미지도 동일 구조 추출
|
|
338
90
|
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* 일일 출석 미션 섹션
|
|
342
|
-
*
|
|
343
|
-
* [기능 정의]
|
|
344
|
-
* - 매일 출석 시 스노우 토큰 즉시 지급
|
|
345
|
-
* - 누적 3/5/7일 달성 시 추가 보상
|
|
346
|
-
*
|
|
347
|
-
* [인터랙션]
|
|
348
|
-
* ① 출석하기 클릭 → API호출 → 토큰지급 표시
|
|
349
|
-
* ② 누적 보상 클릭 → 보상 수령
|
|
350
|
-
*
|
|
351
|
-
* [상태] default, checked, reward-claimed
|
|
352
|
-
*/
|
|
353
|
-
|
|
354
|
-
3. 공통 컴포넌트 목록:
|
|
355
|
-
→ 프로젝트에 이미 있는 컴포넌트 (GNB, Footer 등)
|
|
356
|
-
→ 새로 만들 공통 컴포넌트
|
|
357
|
-
|
|
358
|
-
4. TypeScript 인터페이스 초안:
|
|
359
|
-
interface RewardItem { id: string; name: string; tokenAmount: number; status: 'locked'|'available'|'claimed' }
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### 1-3. 검증
|
|
91
|
+
❌ Phase 1에서 코드 파일 생성 금지
|
|
363
92
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
□ 공통 컴포넌트가 식별되어 있다
|
|
370
|
-
□ 파일을 하나도 생성하지 않았다
|
|
371
|
-
|
|
372
|
-
Phase 4에서 이 스펙 + remapped.json을 합쳐서 코드를 생성한다.
|
|
93
|
+
출력 (텍스트만):
|
|
94
|
+
1. 섹션 목록 테이블 (이름, Figma name, 높이, 설명)
|
|
95
|
+
2. 각 섹션 기능 정의 ([기능] + [인터랙션] + [상태])
|
|
96
|
+
3. 공통 컴포넌트 목록
|
|
97
|
+
4. TypeScript 인터페이스 초안
|
|
373
98
|
```
|
|
374
99
|
|
|
375
100
|
---
|
|
376
101
|
|
|
377
102
|
## Phase 2: 재료 확보
|
|
378
103
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
사용자에게 질문한다:
|
|
382
|
-
- question: "디자인 Figma URL을 입력해주세요. 여러 페이지는 줄바꿈으로 구분."
|
|
383
|
-
- options 제공 금지 — 자유 텍스트 입력만 허용
|
|
384
|
-
|
|
385
|
-
### 멀티 프레임 URL 검증
|
|
386
|
-
|
|
387
|
-
```
|
|
388
|
-
URL 유효성 검증:
|
|
389
|
-
- 모든 URL에서 fileKey 추출 → 동일한 fileKey인지 확인
|
|
390
|
-
- 서로 다른 fileKey 발견 시 에러: "멀티 프레임은 동일 Figma 파일 내 다른 프레임만 지원합니다"
|
|
391
|
-
- node-id 파라미터 누락 시 해당 URL 에러 보고
|
|
392
|
-
- 최대 5개 URL까지 지원
|
|
393
|
-
|
|
394
|
-
URL 개수에 따른 처리:
|
|
395
|
-
1개: 기존 방식 (변경 없음, 아래 2-1 그대로)
|
|
396
|
-
2개 이상: 멀티 프레임 모드 활성화 (2-1-multi 참조)
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### 2-1. 디자인 재료 추출 (단일 URL)
|
|
400
|
-
|
|
401
|
-
```
|
|
402
|
-
URL에서 fileKey, nodeId 추출
|
|
403
|
-
|
|
404
|
-
1단계 — 전체 스크린샷 (정답 사진):
|
|
405
|
-
node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{feature}/full-screenshot.webp
|
|
406
|
-
|
|
407
|
-
2단계 — 전체 트리 + CSS (수치 재료):
|
|
408
|
-
node "[FIGMA_SCRIPT]" tree {fileKey} {nodeId} --depth=10
|
|
409
|
-
→ /tmp/{feature}/tree.json 에 저장
|
|
410
|
-
|
|
411
|
-
3단계 — 전체 이미지 다운로드 (시각 재료):
|
|
412
|
-
node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --out=/tmp/{feature}/images/ --depth=10
|
|
413
|
-
→ 모든 이미지 에셋 확보. 누락 0건, 0byte 0건.
|
|
414
|
-
|
|
415
|
-
3.5단계 — 개별 에셋 노드 렌더링 (추가 시각 재료):
|
|
416
|
-
tree.json에서 이미지 에셋 후보를 식별:
|
|
417
|
-
- 아이콘: VECTOR/GROUP 크기 ≤ 64px
|
|
418
|
-
- 아이템 썸네일: name에 "item", "icon", "reward", "token", "coin", "badge"
|
|
419
|
-
- 크기 50~300px 범위의 독립 요소
|
|
420
|
-
- fill 이미지가 없지만 시각적으로 의미 있는 노드
|
|
421
|
-
|
|
422
|
-
⛔ 렌더링 전 TEXT 자식 검증 (BLOCKING):
|
|
423
|
-
후보 노드의 자식 트리에 TEXT 노드가 있으면 → 렌더링 대상에서 제외
|
|
424
|
-
후보 노드가 INSTANCE 반복 패턴의 부모이면 → 렌더링 대상에서 제외
|
|
425
|
-
"카드", "리스트", "그리드" 등 복합 UI를 통째 렌더링하지 않는다
|
|
426
|
-
|
|
427
|
-
해당 노드를 개별 렌더링:
|
|
428
|
-
node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --render --nodeIds={id1},{id2},... --out=/tmp/{feature}/images/
|
|
429
|
-
→ 이미지 fill이 아닌 벡터/인스턴스 에셋도 webp로 확보
|
|
430
|
-
→ Phase 4에서 카드 내부의 아이콘/썸네일로 사용
|
|
431
|
-
|
|
432
|
-
4단계 — 섹션별 스크린샷 (부분 정답):
|
|
433
|
-
트리의 1depth 자식 프레임 각각:
|
|
434
|
-
node "[FIGMA_SCRIPT]" screenshot {fileKey} {child.nodeId} --out=/tmp/{feature}/sections/{child.name}.webp
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### 2-1-multi. 멀티 프레임 재료 확보 (2개 이상 URL)
|
|
438
|
-
|
|
439
|
-
```
|
|
440
|
-
각 URL에 대해 순차 추출 (Figma API rate limit: 요청 간 500ms 간격):
|
|
441
|
-
URL 1 → /tmp/{feature}/frame-1/ (full-screenshot, tree, images, sections)
|
|
442
|
-
URL 2 → /tmp/{feature}/frame-2/ (URL 1 완료 후 500ms 대기)
|
|
443
|
-
URL 3 → /tmp/{feature}/frame-3/ (URL 2 완료 후 500ms 대기)
|
|
444
|
-
※ 추출 완료 후 후처리(섹션 분할 등)는 병렬 가능
|
|
445
|
-
|
|
446
|
-
부분 실패 처리:
|
|
447
|
-
- 개별 URL 추출 실패 시 해당 frame만 건너뛰고 나머지 진행
|
|
448
|
-
- 실패한 frame은 사용자에게 즉시 보고: "frame-{N} 추출 실패: {에러 사유}" (API 에러, 권한 부족, 잘못된 nodeId 등)
|
|
449
|
-
- 성공한 frame ≥ 2: Phase 3 계속 진행
|
|
450
|
-
- 정확히 1개만 성공: 단일 프레임 모드로 폴백 (Phase 3 스킵)
|
|
451
|
-
- 0개 성공: 전체 실패 보고
|
|
104
|
+
**→ vibe.figma.extract 스킬의 규칙을 따른다.**
|
|
452
105
|
|
|
453
|
-
결과:
|
|
454
|
-
/tmp/{feature}/
|
|
455
|
-
├── frame-1/ ← 메인 페이지
|
|
456
|
-
│ ├── full-screenshot.webp
|
|
457
|
-
│ ├── tree.json
|
|
458
|
-
│ ├── images/
|
|
459
|
-
│ └── sections/
|
|
460
|
-
├── frame-2/ ← 서브 페이지 1
|
|
461
|
-
│ └── ...
|
|
462
|
-
├── frame-3/ ← 서브 페이지 2
|
|
463
|
-
│ └── ...
|
|
464
|
-
└── sections/ ← Phase 6 검증용 스크린샷
|
|
465
106
|
```
|
|
107
|
+
# [FIGMA_SCRIPT] = ~/.vibe/hooks/scripts/figma-extract.js
|
|
466
108
|
|
|
467
|
-
|
|
109
|
+
각 BP(MO, PC)에 대해:
|
|
110
|
+
1. 전체 스크린샷: screenshot → full-screenshot.webp
|
|
111
|
+
2. 트리 + CSS: tree --depth=10 → tree.json
|
|
112
|
+
3. 이미지 다운로드: images → images/
|
|
113
|
+
4. 에셋 렌더링: 아이콘/썸네일 노드 개별 렌더링 (TEXT 자식 검증 BLOCKING)
|
|
114
|
+
5. 섹션 스크린샷: 1depth 자식 각각 → sections/
|
|
468
115
|
|
|
116
|
+
멀티 프레임 (같은 BP, 다른 페이지):
|
|
117
|
+
순차 추출 (500ms 간격), 부분 실패 허용
|
|
469
118
|
```
|
|
470
|
-
Phase 2 완료 시 /tmp/{feature}/ 에 다음이 준비되어야 함:
|
|
471
119
|
|
|
472
|
-
|
|
473
|
-
/tmp/{feature}/
|
|
474
|
-
├── full-screenshot.webp ← 전체 정답 사진
|
|
475
|
-
├── tree.json ← 노드 트리 + CSS 수치
|
|
476
|
-
├── images/ ← 모든 이미지 에셋
|
|
477
|
-
│ ├── hero-bg.webp
|
|
478
|
-
│ ├── hero-title.webp
|
|
479
|
-
│ ├── card-item-1.webp
|
|
480
|
-
│ └── ...
|
|
481
|
-
└── sections/ ← 섹션별 정답 사진
|
|
482
|
-
├── hero.webp
|
|
483
|
-
├── daily-checkin.webp
|
|
484
|
-
├── playtime-mission.webp
|
|
485
|
-
└── ...
|
|
120
|
+
---
|
|
486
121
|
|
|
487
|
-
|
|
122
|
+
## Phase 3: 리매핑
|
|
488
123
|
|
|
489
|
-
|
|
490
|
-
- 이미지: 파일명 + 크기 + 용도 추정 (BG/icon/title/decoration)
|
|
491
|
-
- 색상: tree.json에서 추출한 모든 고유 색상값
|
|
492
|
-
- 폰트: 사용된 font-family, size, weight 목록
|
|
493
|
-
- 텍스트: 모든 TEXT 노드의 characters 값
|
|
494
|
-
- 간격: padding, gap, margin 사용 빈도 높은 값
|
|
495
|
-
```
|
|
124
|
+
**MO + PC 있을 때만 실행. 단일 BP면 스킵.**
|
|
496
125
|
|
|
497
|
-
### 반응형
|
|
126
|
+
### MO↔PC 반응형 매칭
|
|
498
127
|
|
|
499
128
|
```
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
breakpoint: PC/모바일 분계 (예: 1025px)
|
|
504
|
-
|
|
505
|
-
UI 요소 → vw 비례:
|
|
506
|
-
vw값 = (Figma px / designWidth) × 100
|
|
507
|
-
예: gap: 24px → 24/720 × 100 = 3.33vw
|
|
508
|
-
예: width: 620px → 620/720 = 86.11% (부모 대비)
|
|
129
|
+
1. 섹션 매칭: 1depth name 기준 (완전일치 → prefix → 순서)
|
|
130
|
+
2. 노드 매칭: 재귀적 name 매칭 → CSS diff 추출
|
|
131
|
+
3. diff: 같은 값 유지, 다른 값만 @media 오버라이드
|
|
509
132
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
변환 안 함: color, opacity, font-weight, z-index, line-height(단위 없을 때)
|
|
133
|
+
출력 → /tmp/{feature}/remapped.json:
|
|
134
|
+
sections:
|
|
135
|
+
- name: Hero
|
|
136
|
+
mo: { nodeId, css, children }
|
|
137
|
+
pcDiff: { css: { 차이만 }, children }
|
|
138
|
+
images: { mo: 'bg/hero-bg.webp', pc: 'bg/hero-bg-pc.webp' }
|
|
517
139
|
```
|
|
518
140
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
## Phase 3: 리매핑 (tree.json → remapped.json)
|
|
522
|
-
|
|
523
|
-
**URL 2개 이상일 때만 실행. 단일 URL이면 Phase 3으로 건너뜀.**
|
|
524
|
-
**프레임 간 공통 요소를 식별하여 공유 컴포넌트로 추출한다.**
|
|
141
|
+
### 멀티 프레임 (같은 BP, 다른 페이지)
|
|
525
142
|
|
|
526
143
|
```
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
- 시각적으로 반복되는 요소 식별:
|
|
530
|
-
상단 영역 (GNB/Header), 하단 영역 (Footer),
|
|
531
|
-
카드 패턴, 버튼 스타일, 섹션 레이아웃
|
|
532
|
-
|
|
533
|
-
2. 구조 비교 — 각 tree.json의 1depth 자식 비교:
|
|
534
|
-
- 동일한 name 또는 prefix 일치 (예: "GNB", "Header", "Footer", "Nav")
|
|
535
|
-
- 동일한 size (width와 height 모두 ±10% 이내): 같은 컴포넌트 후보
|
|
536
|
-
- 동일한 CSS 패턴: 같은 스타일 사용 (색상, 폰트, 레이아웃)
|
|
537
|
-
|
|
538
|
-
3. 공통 컴포넌트 후보 목록:
|
|
539
|
-
shared-components:
|
|
540
|
-
- name: GNB
|
|
541
|
-
frames: [frame-1, frame-2, frame-3]
|
|
542
|
-
consistency: 100% (모든 프레임에서 동일)
|
|
543
|
-
action: 공유 컴포넌트로 1회만 생성
|
|
544
|
-
- name: Footer
|
|
545
|
-
frames: [frame-1, frame-3]
|
|
546
|
-
consistency: 67% (2/3 프레임)
|
|
547
|
-
action: 공유 컴포넌트 + frame-2는 다른 Footer
|
|
548
|
-
- name: CardItem
|
|
549
|
-
frames: [frame-1, frame-2]
|
|
550
|
-
size-match: 95%
|
|
551
|
-
action: 공유 컴포넌트 (props로 변형)
|
|
552
|
-
|
|
553
|
-
4. 공통 토큰 추출:
|
|
554
|
-
- 모든 프레임의 색상 팔레트 합집합 → 공유 _tokens.scss
|
|
555
|
-
- 모든 프레임의 폰트 목록 합집합
|
|
556
|
-
- 모든 프레임의 간격 패턴 합집합
|
|
557
|
-
→ 프레임별 고유 값만 프레임 로컬 토큰으로 분리
|
|
144
|
+
공통 요소 식별 → 공유 컴포넌트 추출
|
|
145
|
+
공통 토큰 합집합 → 공유 _tokens.scss
|
|
558
146
|
```
|
|
559
147
|
|
|
560
148
|
---
|
|
561
149
|
|
|
562
150
|
## Phase 4: 순차 코드 생성
|
|
563
151
|
|
|
564
|
-
|
|
565
|
-
**한 섹션 완료 → 브라우저 확인 → 다음 섹션. 병렬 금지.**
|
|
152
|
+
**→ vibe.figma.convert 스킬의 규칙을 따른다.**
|
|
566
153
|
|
|
567
|
-
**멀티 프레임 모드 시 조립 순서 변경:**
|
|
568
154
|
```
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
- 프레임별 변형은 props로 처리
|
|
575
|
-
|
|
576
|
-
2단계: 프레임별 고유 섹션 조립
|
|
577
|
-
- frame-1 고유 섹션: 공유 컴포넌트 import + 고유 섹션 신규 생성
|
|
578
|
-
- frame-2 고유 섹션: 동일
|
|
579
|
-
- 각 프레임의 페이지 파일에서 공유 + 고유 컴포넌트 배치
|
|
580
|
-
|
|
581
|
-
3단계: 스타일 구조
|
|
582
|
-
styles/{feature}/
|
|
583
|
-
├── _tokens.scss ← 공유 토큰 (합집합)
|
|
584
|
-
├── shared/ ← 공유 컴포넌트 스타일
|
|
585
|
-
│ ├── layout/_gnb.scss
|
|
586
|
-
│ └── components/_gnb.scss
|
|
587
|
-
├── frame-1/ ← 메인 고유 스타일
|
|
588
|
-
│ ├── layout/
|
|
589
|
-
│ └── components/
|
|
590
|
-
└── frame-2/
|
|
155
|
+
⛔ 병렬 금지. 한 섹션씩 순차:
|
|
156
|
+
1. tree.json에서 섹션 노드 Read
|
|
157
|
+
2. 이미지 vs HTML 판별 테이블 작성 (BLOCKING)
|
|
158
|
+
3. 기계적 매핑 + Claude 시맨틱 보강
|
|
159
|
+
4. 브라우저 확인 → OK → 다음 섹션
|
|
591
160
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
### 3-0. SCSS Setup + 등록 (첫 섹션 전)
|
|
596
|
-
|
|
597
|
-
```
|
|
598
|
-
Phase 1에서 생성한 빈 SCSS 파일에 기본 내용 Write:
|
|
599
|
-
styles/{feature}/index.scss ← @import 진입점
|
|
600
|
-
styles/{feature}/_tokens.scss ← tree.json에서 추출한 디자인 토큰
|
|
601
|
-
styles/{feature}/_mixins.scss ← breakpoint mixin
|
|
602
|
-
styles/{feature}/_base.scss ← 루트 클래스
|
|
603
|
-
|
|
604
|
-
토큰 매핑 (기존 토큰 우선 사용):
|
|
605
|
-
1. /tmp/{feature}/project-tokens.json 을 Read로 로드
|
|
606
|
-
2. Figma tree.json의 각 값에 대해 project-tokens에서 동일 값 검색:
|
|
607
|
-
- 색상: hex 정규화 후 완전 일치 (Figma RGBA 0-1 → hex, 3자리→6자리, 대소문자 무시)
|
|
608
|
-
※ alpha < 1: 8자리 hex (#RRGGBBAA) 또는 rgba() 함수로 변환
|
|
609
|
-
- 간격: px 값 완전 일치 (rem→px: 1rem=16px)
|
|
610
|
-
- 폰트: family 이름 포함 매칭
|
|
611
|
-
3. 매칭됨 → 기존 토큰 참조:
|
|
612
|
-
- SCSS: @use 'path' 로 기존 파일 import, $변수명 사용
|
|
613
|
-
- Tailwind: 해당 유틸리티 클래스 사용 (bg-blue-500)
|
|
614
|
-
- CSS Variables: var(--color-primary) 사용
|
|
615
|
-
4. 매칭 안 됨 → 새 토큰 생성 (기존 방식)
|
|
616
|
-
|
|
617
|
-
_tokens.scss 구조:
|
|
618
|
-
// ─── 기존 토큰 참조 ────────────────────
|
|
619
|
-
@use '../../styles/variables' as v;
|
|
620
|
-
|
|
621
|
-
// ─── 매핑 (기존 토큰 → 피처 시맨틱) ────
|
|
622
|
-
$color-bg-primary: v.$color-navy; // 기존 재사용
|
|
623
|
-
$color-text-primary: v.$color-white; // 기존 재사용
|
|
624
|
-
|
|
625
|
-
// ─── 새 토큰 (매칭 안 된 값만) ─────────
|
|
626
|
-
$color-accent-gold: #ffd700; // 새 값
|
|
627
|
-
$space-section-gap: 42px; // 새 값
|
|
628
|
-
|
|
629
|
-
매핑 결과 보고: "토큰 매핑: 12/18 매칭 (67%), 6개 새 토큰 생성"
|
|
630
|
-
|
|
631
|
-
기존 토큰 파일 수정 금지 — 참조만 함
|
|
632
|
-
같은 값의 토큰 중복 생성 금지
|
|
633
|
-
새 토큰은 피처 스코프 네이밍 ($feature-color-xxx)
|
|
634
|
-
|
|
635
|
-
project-tokens.json 없는 경우 (기존 토큰 없음):
|
|
636
|
-
기존 방식으로 전체 생성:
|
|
637
|
-
- 색상 → primitive ($color-navy: #0a1628) + semantic ($color-bg-primary)
|
|
638
|
-
- 폰트 → $font-pretendard, $font-size-md: 16px
|
|
639
|
-
- 간격 → $space-sm: 8px, $space-md: 16px
|
|
640
|
-
|
|
641
|
-
스타일 등록 (BLOCKING):
|
|
642
|
-
Grep "{feature}/index.scss" → 이미 등록되어 있으면 건너뜀.
|
|
643
|
-
없으면 프로젝트 방식에 맞게 등록.
|
|
644
|
-
```
|
|
161
|
+
SCSS Setup (첫 섹션 전):
|
|
162
|
+
index.scss, _tokens.scss, _mixins.scss, _base.scss
|
|
163
|
+
토큰 매핑: project-tokens.json에서 기존 토큰 참조 → 매칭 안 되면 새 생성
|
|
645
164
|
|
|
646
|
-
|
|
165
|
+
컴포넌트 매칭 (각 섹션 전):
|
|
166
|
+
component-index.json과 대조 → 매칭되면 import, 안 되면 새 생성
|
|
647
167
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
각 섹션 스크린샷 분석 후, 기존 컴포넌트 매칭:
|
|
652
|
-
|
|
653
|
-
1. 스크린샷에서 식별된 UI 요소를 component-index와 대조
|
|
654
|
-
2. 매칭 판정 기준:
|
|
655
|
-
- 필수: name/role 유사성 (시각적 역할 일치)
|
|
656
|
-
버튼 → BaseButton, 네비게이션 → GNB, 카드 → Card, 모달 → Modal
|
|
657
|
-
- 보조: props 호환성 (필요 props가 기존 컴포넌트에 존재)
|
|
658
|
-
- 보조: slots 호환성 (필요 slot이 존재)
|
|
659
|
-
- 불일치: props/slots 50% 미만 호환이면 매칭 거부 → 새로 생성
|
|
660
|
-
3. 매칭된 컴포넌트: import하여 props 전달 (내부 수정 금지)
|
|
661
|
-
4. 매칭 안 됨: 새로 생성 (강제 재사용 금지)
|
|
662
|
-
|
|
663
|
-
재사용 시 스타일 충돌 방지:
|
|
664
|
-
✅ 기존 컴포넌트 import하여 사용
|
|
665
|
-
✅ props로 variant/size 등 커스터마이즈
|
|
666
|
-
✅ 래퍼 클래스로 position/size만 오버라이드
|
|
667
|
-
❌ 기존 컴포넌트 내부 스타일 수정 금지
|
|
668
|
-
❌ 90% 유사한데 새로 만들기 금지
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
### 3-1. 순차 블록 빌딩 (병렬 금지)
|
|
672
|
-
|
|
673
|
-
```
|
|
674
|
-
❌ 여러 섹션을 동시에 생성하지 않는다
|
|
675
|
-
→ 병렬 생성 시 컨텍스트 분산 → 매핑 정확도 저하 → 품질 하락
|
|
676
|
-
→ 앞 섹션 결과를 확인하지 않으면 실수가 누적됨
|
|
677
|
-
|
|
678
|
-
✅ 한 섹션씩 순차적으로 블록을 쌓듯 만든다:
|
|
679
|
-
|
|
680
|
-
작업 폴더: ROOT name에서 추출 (Phase 2에서 결정)
|
|
681
|
-
"MO_Main ..." → /tmp/{feature}/mo-main/
|
|
682
|
-
코드 출력도 이 폴더 안에:
|
|
683
|
-
/tmp/{feature}/mo-main/components/{feature}/
|
|
684
|
-
/tmp/{feature}/mo-main/styles/{feature}/
|
|
685
|
-
Phase 5에서 최종 프로젝트 디렉토리에 배치
|
|
686
|
-
|
|
687
|
-
섹션 1 (Hero):
|
|
688
|
-
1. tree.json에서 Hero 노드 Read
|
|
689
|
-
2. 코드 생성 (컴포넌트 + SCSS)
|
|
690
|
-
3. 브라우저에서 확인 (dev 서버)
|
|
691
|
-
4. OK → 다음 섹션으로
|
|
692
|
-
NG → 수정 후 재확인
|
|
693
|
-
|
|
694
|
-
섹션 2 (KID):
|
|
695
|
-
1. tree.json에서 KID 노드 Read
|
|
696
|
-
2. 코드 생성
|
|
697
|
-
3. 브라우저에서 확인 (이전 섹션도 함께)
|
|
698
|
-
4. OK → 다음 섹션으로
|
|
699
|
-
|
|
700
|
-
섹션 3 (Daily) → ... 반복
|
|
701
|
-
|
|
702
|
-
⛔ Phase 4에서 screenshot/render 호출 금지:
|
|
703
|
-
Phase 2에서 확보한 이미지만 사용한다.
|
|
704
|
-
"이 섹션이 복잡하니 screenshot으로 이미지화하자" → 금지된 접근.
|
|
705
|
-
복잡한 섹션도 tree.json 구조를 따라 HTML+CSS로 구현한다.
|
|
706
|
-
섹션 내부에 카드 그리드/보상 목록이 있으면:
|
|
707
|
-
→ 카드 내부의 아이콘/썸네일(Phase 2에서 확보)만 <img>
|
|
708
|
-
→ 가격, 수량, 버튼 텍스트, 제목은 모두 HTML
|
|
709
|
-
|
|
710
|
-
각 섹션 완료 시:
|
|
711
|
-
- 해당 섹션의 컴포넌트 + SCSS가 동작하는 상태
|
|
712
|
-
- pages/{feature}.vue에 해당 섹션이 추가된 상태
|
|
713
|
-
- 브라우저에서 이전 섹션 + 현재 섹션이 모두 보이는 상태
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
### 3-2. 트리 기반 구조적 코드 생성
|
|
717
|
-
|
|
718
|
-
```
|
|
719
|
-
각 섹션에 대해 (vibe.figma.convert 참조):
|
|
720
|
-
|
|
721
|
-
1. 트리 구조 읽기 — tree.json에서 해당 섹션 노드를 Read
|
|
722
|
-
→ Auto Layout 속성(flex, gap, padding)이 코드의 기반
|
|
723
|
-
→ 스크린샷은 검증용으로만 참조
|
|
724
|
-
|
|
725
|
-
2. ⛔ 이미지 vs HTML 판별 (BLOCKING — 코드 작성 전 반드시 실행):
|
|
726
|
-
섹션의 모든 노드를 순회하여 각 노드가 이미지인지 HTML인지 판별한다.
|
|
727
|
-
판별하지 않고 코드를 작성하면 안 된다.
|
|
728
|
-
|
|
729
|
-
판별 결과 테이블을 먼저 작성한 후 코드 생성:
|
|
730
|
-
| 노드 name | 타입 | 판별 | 근거 |
|
|
731
|
-
|-----------|------|------|------|
|
|
732
|
-
| BG | FRAME | CSS background-image | BG 프레임 |
|
|
733
|
-
| Title | TEXT 포함 FRAME | HTML | TEXT 자식 보유 |
|
|
734
|
-
| CardGrid | INSTANCE ×4 | HTML v-for | 반복 패턴 |
|
|
735
|
-
| CoinIcon | RECTANGLE+imageRef | <img> | 순수 에셋 |
|
|
736
|
-
|
|
737
|
-
판별 규칙 (하나라도 YES → HTML):
|
|
738
|
-
Q1. TEXT 자식 있는가? → YES → HTML (텍스트를 이미지에 넣지 않는다)
|
|
739
|
-
Q2. INSTANCE 반복 패턴인가? → YES → HTML v-for (내부 이미지 에셋만 <img>)
|
|
740
|
-
Q3. 인터랙티브 요소인가? (btn, CTA) → YES → HTML <button>
|
|
741
|
-
Q4. 동적 데이터인가? (가격, 수량, 기간) → YES → HTML 텍스트
|
|
742
|
-
모두 NO → 이미지 렌더링 가능
|
|
743
|
-
|
|
744
|
-
⚠️ 특히 주의: 섹션을 통째로 이미지 렌더링하는 것은 절대 금지.
|
|
745
|
-
섹션 안의 개별 요소를 판별하여 이미지/HTML을 분리해야 한다.
|
|
746
|
-
|
|
747
|
-
3. 기계적 매핑 (추정 없음):
|
|
748
|
-
a. 이미지: 판별에서 "이미지"로 확정된 노드만 static/images/{feature}/에 배치
|
|
749
|
-
b. BG 프레임 처리:
|
|
750
|
-
❌ <img src="bg.webp"> 또는 <div><img src="bg.webp" class="bg"></div> 금지
|
|
751
|
-
✅ 부모에 CSS background-image만 사용:
|
|
752
|
-
.section { background-image: url('/images/{feature}/bg.webp'); background-size: cover; }
|
|
753
|
-
✅ BG 프레임은 HTML에 아무 요소도 렌더링하지 않음
|
|
754
|
-
c. 노드 → HTML 매핑:
|
|
755
|
-
- Auto Layout 있음 → <div> + flex (direction/gap/padding 직접)
|
|
756
|
-
- Auto Layout 없음 → <div> + position:relative (자식 absolute)
|
|
757
|
-
- TEXT 노드 → <span> (Claude가 h2/p/button으로 승격)
|
|
758
|
-
- 순수 이미지 에셋 → <img src="렌더링된 파일">
|
|
759
|
-
- 반복 패턴 (동일 구조 2+) → v-for (카드 내 이미지만 <img>, 나머지 HTML)
|
|
760
|
-
d. CSS 직접 매핑:
|
|
761
|
-
- node.css의 모든 속성을 SCSS에 1:1 매핑
|
|
762
|
-
- vw/clamp 반응형 단위 변환 (vibe.figma.convert 참조)
|
|
763
|
-
- tree.json에 없는 CSS 값은 작성하지 않음
|
|
764
|
-
d. Phase 1의 JSDoc, 인터페이스, 핸들러 삽입
|
|
765
|
-
|
|
766
|
-
3. Claude 시맨틱 보강:
|
|
767
|
-
- div → section/h2/p/button 태그 승격
|
|
768
|
-
- 컴포넌트 분리 + props 설계
|
|
769
|
-
- 접근성 (alt, aria)
|
|
770
|
-
- 인터랙션 (클릭, 상태)
|
|
771
|
-
|
|
772
|
-
4. 브라우저 확인 (섹션별 게이트):
|
|
773
|
-
- dev 서버에서 현재까지 만든 섹션이 모두 보이는지 확인
|
|
774
|
-
- 이미지 로드, 레이아웃, 텍스트 확인
|
|
775
|
-
- OK → 다음 섹션 진행
|
|
776
|
-
- NG → 수정 후 재확인 (다음 섹션으로 넘어가지 않음)
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
### 3-3. 코드 작성 규칙
|
|
780
|
-
|
|
781
|
-
```
|
|
782
|
-
컴포넌트 (Vue 예시):
|
|
783
|
-
<template>
|
|
784
|
-
tree.json의 Auto Layout 구조를 HTML flex 레이아웃으로 직접 매핑.
|
|
785
|
-
Claude가 시맨틱 태그로 승격 (div → section/h2/p/button).
|
|
786
|
-
Phase 1의 기능 요소(v-for, @click, v-if) 보존.
|
|
787
|
-
이미지 경로: /images/{feature}/파일명.webp (실제 파일 존재 확인)
|
|
788
|
-
텍스트: tree.json의 TEXT 노드 characters 값 그대로.
|
|
789
|
-
|
|
790
|
-
<script setup>
|
|
791
|
-
Phase 1의 JSDoc + 인터페이스 + 핸들러 보존.
|
|
792
|
-
새로운 데이터/상태 추가 시 기존과 병합.
|
|
793
|
-
|
|
794
|
-
<style> 블록 없음 — 외부 SCSS만.
|
|
795
|
-
|
|
796
|
-
SCSS (vibe.figma.convert 참조):
|
|
797
|
-
layout/ → position, display, flex, width, height, padding, gap
|
|
798
|
-
components/ → font, color, border, shadow, opacity
|
|
799
|
-
모든 수치는 tree.json의 정확한 값 → vw/clamp 변환 (vibe.figma.convert 참조).
|
|
800
|
-
추정 금지 — 값이 없으면 tree.json에서 다시 찾는다.
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
### 반응형 (추가 URL)
|
|
804
|
-
|
|
805
|
-
```
|
|
806
|
-
완료 후 질문: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
|
|
807
|
-
|
|
808
|
-
URL 있으면:
|
|
809
|
-
1. 새 URL에서 재료 확보 (Phase 1 반복)
|
|
810
|
-
2. 새 스크린샷과 기존 코드 비교
|
|
811
|
-
3. @media (min-width: $bp-desktop) 오버라이드만 추가
|
|
812
|
-
4. 기존 모바일 코드 삭제 금지
|
|
168
|
+
멀티 프레임 시:
|
|
169
|
+
1단계: 공유 컴포넌트 먼저 → components/shared/
|
|
170
|
+
2단계: 프레임별 고유 섹션
|
|
813
171
|
```
|
|
814
172
|
|
|
815
173
|
---
|
|
816
174
|
|
|
817
175
|
## Phase 5: 컴파일 게이트
|
|
818
176
|
|
|
819
|
-
**Phase 3 코드 생성 완료 후, 브라우저 검증 전에 컴파일 성공을 보장한다.**
|
|
820
|
-
**컴파일 에러는 스킵 불가 — 반드시 수정 또는 사용자 보고.**
|
|
821
|
-
**Phase 5 실패 시 Phase 4 진행 불가 (hard gate).**
|
|
822
|
-
|
|
823
|
-
```
|
|
824
|
-
자동 반복: 컴파일 성공까지. 최대 3라운드.
|
|
825
177
|
```
|
|
178
|
+
최대 3라운드 자동 반복.
|
|
826
179
|
|
|
827
|
-
|
|
180
|
+
0. 베이스라인 캡처 (Phase 4 전): tsc + build 기존 에러 기록
|
|
181
|
+
→ Phase 5에서는 새 에러만 수정 대상
|
|
828
182
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
2. 빌드 베이스라인: npm run build > /tmp/{feature}/baseline-build.txt 2>&1
|
|
833
|
-
|
|
834
|
-
Phase 5에서는 baseline에 없는 **새로 발생한 에러만** 수정 대상.
|
|
835
|
-
baseline에 존재하던 에러는 무시하고 별도 보고 ("기존 에러 {N}개 유지").
|
|
836
|
-
vibe.figma가 생성/수정한 파일 외의 에러는 자동 수정 금지.
|
|
837
|
-
```
|
|
838
|
-
|
|
839
|
-
### 3.5-1. TypeScript 컴파일 체크
|
|
840
|
-
|
|
841
|
-
```
|
|
842
|
-
1. 프로젝트 타입 체커 감지 → 실행:
|
|
843
|
-
- package.json scripts에 type-check 또는 typecheck 존재 → npm run type-check 사용
|
|
844
|
-
- vue-tsc 설치 확인 (Vue 프로젝트) → npx vue-tsc --noEmit 2>&1
|
|
845
|
-
- svelte-check 설치 확인 (Svelte 프로젝트) → npx svelte-check 2>&1
|
|
846
|
-
- 위 해당 없음 → fallback: npx tsc --noEmit 2>&1
|
|
847
|
-
→ 에러 0개: PASS → 다음 단계
|
|
848
|
-
→ 에러 있음: 에러 메시지 파싱 → 자동 수정
|
|
849
|
-
|
|
850
|
-
2. 에러 파싱:
|
|
851
|
-
각 에러에서 추출: 파일 경로, 줄 번호, 에러 코드, 메시지
|
|
852
|
-
예: "src/components/Hero.tsx(15,3): error TS2322: Type 'string' is not assignable to type 'number'"
|
|
183
|
+
1. TypeScript: vue-tsc/svelte-check/tsc --noEmit
|
|
184
|
+
2. Build: npm run build (120초 타임아웃)
|
|
185
|
+
3. Dev 서버: npm run dev → 포트 감지 → 폴링
|
|
853
186
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
- TS2339 (프로퍼티 없음): interface에 프로퍼티 추가
|
|
858
|
-
- TS7006 (암시적 any): 타입 어노테이션 추가
|
|
859
|
-
- 기타: Read로 해당 파일+줄 확인 → 컨텍스트 기반 수정
|
|
860
|
-
```
|
|
861
|
-
|
|
862
|
-
### 3.5-2. 빌드 체크
|
|
863
|
-
|
|
864
|
-
```
|
|
865
|
-
1. npm run build 실행:
|
|
866
|
-
Bash: npm run build 2>&1
|
|
867
|
-
→ 성공: PASS → 다음 단계
|
|
868
|
-
→ 실패: 에러 메시지 파싱 → 자동 수정
|
|
869
|
-
→ 타임아웃: 최대 120초 (초과 시 해당 라운드 실패 처리)
|
|
870
|
-
|
|
871
|
-
2. 일반적 빌드 에러 처리:
|
|
872
|
-
- SCSS 컴파일 에러: 변수명/import 오류 수정
|
|
873
|
-
- Module not found: import 경로 수정 (.js 확장자 등)
|
|
874
|
-
- ESLint 에러 (--max-warnings 초과): 자동 수정 가능한 것 처리
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
### 3.5-3. dev 서버 시작 확인
|
|
878
|
-
|
|
879
|
-
```
|
|
880
|
-
1. dev 서버 시작 + PID 캡처:
|
|
881
|
-
Bash: npm run dev & echo $! → DEV_PID 저장
|
|
882
|
-
→ localhost 포트 자동 감지: npm run dev stdout에서 localhost:\d+ 또는 port \d+ 파싱
|
|
883
|
-
감지 실패 시 기본값 3000, 5173, 4173 순서 시도
|
|
884
|
-
→ 포트 폴링 (3초 간격, 최대 30초 대기)
|
|
885
|
-
→ 성공: Phase 4 진행 (Phase 4 완료 후 정리)
|
|
886
|
-
→ 실패: kill $DEV_PID → 에러 로그 확인 → 수정 → 재시도
|
|
887
|
-
|
|
888
|
-
2. 프로세스 정리 규칙:
|
|
889
|
-
- Phase 4 완료 또는 3라운드 실패 시 반드시 정리
|
|
890
|
-
- 정리 순서: kill $DEV_PID → 3초 대기 → kill -9 $DEV_PID (응답 없으면)
|
|
891
|
-
- lsof -i :{port} -t 로 포트 점유 프로세스 확인 후 추가 정리
|
|
892
|
-
※ spawned child process만 대상 — 관련 없는 프로세스 kill 금지
|
|
893
|
-
- interrupt 시에도 cleanup 보장
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
### 3.5-4. 수정 루프
|
|
897
|
-
|
|
898
|
-
```
|
|
899
|
-
라운드 1~3:
|
|
900
|
-
1. tsc → build → dev 순서로 체크
|
|
901
|
-
2. 첫 번째 실패 단계의 에러 수정
|
|
902
|
-
3. 수정 후 해당 단계부터 재체크
|
|
903
|
-
4. 모든 단계 통과: Phase 4 진행
|
|
904
|
-
|
|
905
|
-
라운드 종료 조건:
|
|
906
|
-
- 3라운드 후 실패: 에러 목록 + 시도한 수정을 사용자에게 보고
|
|
907
|
-
- 같은 에러 반복: 해당 에러 스킵 불가 → 사용자 보고 (컴파일 에러는 스킵 불가)
|
|
908
|
-
|
|
909
|
-
컴파일 게이트 결과 보고:
|
|
910
|
-
|
|
911
|
-
✅ 통과:
|
|
912
|
-
"Phase 5: 컴파일 게이트 PASS (라운드 {N})"
|
|
913
|
-
- tsc: 0 errors
|
|
914
|
-
- build: success
|
|
915
|
-
- dev server: running on localhost:{port}
|
|
916
|
-
|
|
917
|
-
❌ 실패 (3라운드 후):
|
|
918
|
-
"Phase 5: 컴파일 게이트 FAIL"
|
|
919
|
-
- 남은 에러 목록 (파일, 줄, 메시지)
|
|
920
|
-
- 시도한 수정 내역
|
|
921
|
-
- 사용자 수동 수정 필요
|
|
922
|
-
→ Phase 4 진행하지 않음
|
|
187
|
+
에러 시: 파싱 → 자동 수정 → 재체크
|
|
188
|
+
3라운드 실패: 에러 목록을 사용자에게 보고 (Phase 6 진행 불가)
|
|
189
|
+
완료 시: dev 서버 PID 보존 → Phase 6에서 사용
|
|
923
190
|
```
|
|
924
191
|
|
|
925
192
|
---
|
|
926
193
|
|
|
927
194
|
## Phase 6: 시각 검증 루프
|
|
928
195
|
|
|
929
|
-
**Puppeteer + CDP로 실제 렌더링 결과를 확인하며 자동 수정한다.**
|
|
930
|
-
**사람이 브라우저 보면서 고치는 것과 동일한 루프.**
|
|
931
|
-
**인프라: `src/infra/lib/browser/` (범용 UI 검증 도구)**
|
|
932
|
-
|
|
933
|
-
```
|
|
934
|
-
자동 반복: P1=0 될 때까지. 최대 3라운드.
|
|
935
196
|
```
|
|
197
|
+
최대 3라운드. P1=0 될 때까지.
|
|
198
|
+
인프라: src/infra/lib/browser/ (Puppeteer + CDP)
|
|
936
199
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
2. Puppeteer 브라우저 시작:
|
|
945
|
-
import { launchBrowser, openPage } from 'src/infra/lib/browser'
|
|
946
|
-
const browser = await launchBrowser({ headless: true })
|
|
947
|
-
const page = await openPage(browser, 'http://localhost:3000/{feature}', {
|
|
948
|
-
width: 480, // 모바일 퍼스트 (또는 타겟 뷰포트)
|
|
949
|
-
height: 960,
|
|
950
|
-
})
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
### 4-1. 렌더링 스크린샷 vs Figma 스크린샷
|
|
954
|
-
|
|
955
|
-
```
|
|
956
|
-
import { captureScreenshot, compareScreenshots } from 'src/infra/lib/browser'
|
|
957
|
-
|
|
958
|
-
각 섹션에 대해:
|
|
959
|
-
1. 렌더링 결과 스크린샷 캡처:
|
|
960
|
-
await captureScreenshot(page, {
|
|
961
|
-
outPath: '/tmp/{feature}/rendered-{section}.webp',
|
|
962
|
-
selector: '.{section}Section', // Phase 1에서 만든 클래스
|
|
963
|
-
})
|
|
964
|
-
|
|
965
|
-
2. Figma 원본과 픽셀 비교:
|
|
966
|
-
const diff = await compareScreenshots(
|
|
967
|
-
'/tmp/{feature}/sections/{section}.webp', // Figma 원본
|
|
968
|
-
'/tmp/{feature}/rendered-{section}.webp', // 렌더링 결과
|
|
969
|
-
'/tmp/{feature}/diff-{section}.webp', // 차이 시각화
|
|
970
|
-
)
|
|
971
|
-
|
|
972
|
-
3. diff 이미지를 Read로 확인:
|
|
973
|
-
→ 빨간색 영역 = 차이 나는 부분
|
|
974
|
-
→ diffRatio > 0.1 이면 P1 이슈
|
|
975
|
-
```
|
|
976
|
-
|
|
977
|
-
### 4-2. CSS 수치 정밀 비교
|
|
978
|
-
|
|
979
|
-
```
|
|
980
|
-
import { getComputedStyles, compareStyles, diffsToIssues } from 'src/infra/lib/browser'
|
|
981
|
-
|
|
982
|
-
각 섹션의 주요 요소에 대해:
|
|
983
|
-
1. 렌더링된 computed CSS 추출:
|
|
984
|
-
const actual = await getComputedStyles(page, '.heroTitle', [
|
|
985
|
-
'font-size', 'color', 'width', 'height', 'padding', 'margin',
|
|
986
|
-
'background-color', 'border-radius', 'gap',
|
|
987
|
-
])
|
|
988
|
-
|
|
989
|
-
2. Figma tree.json의 기대값과 비교:
|
|
990
|
-
// tree.json에서 해당 노드의 CSS 수치 (vw/clamp 변환 후)
|
|
991
|
-
const expected = { 'font-size': '16px', 'color': '#ffffff', 'width': '465px' }
|
|
992
|
-
const diffs = compareStyles(expected, actual)
|
|
993
|
-
|
|
994
|
-
3. 차이 → 이슈 변환:
|
|
995
|
-
const issues = diffsToIssues(diffs)
|
|
996
|
-
→ delta > 4px: P1 (레이아웃 영향)
|
|
997
|
-
→ delta ≤ 4px: P2 (미세 차이)
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
### 4-3. 이미지·텍스트 누락 체크
|
|
1001
|
-
|
|
1002
|
-
```
|
|
1003
|
-
import { extractImages, extractTextContent } from 'src/infra/lib/browser'
|
|
1004
|
-
|
|
1005
|
-
1. 이미지 로드 상태 확인:
|
|
1006
|
-
const images = await extractImages(page)
|
|
1007
|
-
images.filter(img => !img.loaded)
|
|
1008
|
-
→ 로드 실패 이미지 = P1 (이미지 누락)
|
|
1009
|
-
→ src="" 이미지 = P1 (빈 경로)
|
|
1010
|
-
|
|
1011
|
-
2. 텍스트 콘텐츠 확인:
|
|
1012
|
-
const texts = await extractTextContent(page)
|
|
1013
|
-
→ tree.json의 TEXT 노드 characters와 대조
|
|
1014
|
-
→ 누락된 텍스트 = P1
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
### 4-4. 자동 수정 루프
|
|
1018
|
-
|
|
1019
|
-
```
|
|
1020
|
-
라운드 1~3:
|
|
1021
|
-
1. 4-1 ~ 4-3 실행 → 이슈 목록 수집
|
|
1022
|
-
2. P1 이슈 우선 수정:
|
|
1023
|
-
- 이미지 누락 → 이미지 경로 확인, static/ 에 파일 존재 확인
|
|
1024
|
-
- 레이아웃 다름 → 스크린샷 diff 이미지 + computed CSS로 원인 파악
|
|
1025
|
-
- 텍스트 누락 → tree.json의 정확한 텍스트 삽입
|
|
1026
|
-
- CSS 수치 틀림 → tree.json(tree.json)의 정확한 값으로 교체
|
|
1027
|
-
⚠️ 추정으로 수정하지 않는다. 반드시 tree.json 참조.
|
|
1028
|
-
3. 수정 후 컴파일 재검증:
|
|
1029
|
-
Bash: npx tsc --noEmit 2>&1 (또는 3.5-1에서 선택한 타입 체커)
|
|
1030
|
-
→ 시각 수정이 타입 에러를 유발하면 즉시 타입 에러 수정 후 진행
|
|
1031
|
-
4. 페이지 리로드 → 다시 캡처 → 비교
|
|
1032
|
-
5. P1=0 이면 종료
|
|
1033
|
-
|
|
1034
|
-
라운드 종료 조건:
|
|
1035
|
-
- P1=0: 성공 → 브라우저 종료, 결과 보고
|
|
1036
|
-
- 3라운드 후 P1 남음: TODO 목록으로 사용자에게 보고
|
|
1037
|
-
- 같은 이슈가 반복: 해당 이슈 스킵, 다음 이슈로
|
|
1038
|
-
|
|
1039
|
-
결과 보고:
|
|
1040
|
-
- 수정한 파일 목록
|
|
1041
|
-
- 남은 P2 이슈 목록 (선택적 수정)
|
|
1042
|
-
- 최종 diff 스크린샷 경로
|
|
1043
|
-
```
|
|
1044
|
-
|
|
1045
|
-
### 4-5. 반응형 검증 (추가 뷰포트)
|
|
1046
|
-
|
|
1047
|
-
```
|
|
1048
|
-
모바일 검증 완료 후, 추가 브레이크포인트가 있으면:
|
|
1049
|
-
|
|
1050
|
-
await page.setViewport({ width: 1920, height: 1080 })
|
|
1051
|
-
await page.reload({ waitUntil: 'networkidle0' })
|
|
1052
|
-
|
|
1053
|
-
→ 데스크탑 Figma 스크린샷과 동일한 4-1 ~ 4-4 루프 반복
|
|
1054
|
-
```
|
|
1055
|
-
|
|
1056
|
-
### 4-6. 브라우저 정리
|
|
1057
|
-
|
|
1058
|
-
```
|
|
1059
|
-
import { closeBrowser } from 'src/infra/lib/browser'
|
|
200
|
+
1. 렌더링 스크린샷 캡처 → Figma 스크린샷과 pixelmatch 비교
|
|
201
|
+
diffRatio > 0.1 → P1
|
|
202
|
+
2. CSS 수치 비교: computed CSS vs tree.json 기대값
|
|
203
|
+
delta > 4px → P1, ≤ 4px → P2
|
|
204
|
+
3. 이미지·텍스트 누락 체크
|
|
205
|
+
4. P1 우선 수정 (tree.json 참조, 추정 금지) → 컴파일 재검증 → 리로드
|
|
1060
206
|
|
|
1061
|
-
검증
|
|
1062
|
-
|
|
1063
|
-
dev 서버 종료 (필요 시)
|
|
207
|
+
반응형: MO 검증 후 viewport 변경 → PC 스크린샷과 동일 루프
|
|
208
|
+
종료: 브라우저 + dev 서버 정리
|
|
1064
209
|
```
|