@su-record/vibe 2.8.34 → 2.8.36

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.
@@ -1,21 +1,31 @@
1
1
  ---
2
2
  name: vibe.figma
3
- description: Figma design to code — 스토리보드(기능) + 디자인(비주얼) 프로덕션 코드
3
+ description: Figma design to code — 시각 기반 퍼즐 조립 방식
4
4
  triggers: []
5
5
  tier: standard
6
6
  ---
7
7
 
8
- # vibe.figma — Figma Design to Code
8
+ # vibe.figma — Visual Puzzle Assembly
9
+
10
+ ## 핵심 원칙
11
+
12
+ ```
13
+ 스크린샷이 정답이다. Figma 데이터는 재료일 뿐이다.
14
+
15
+ ❌ Figma 트리 구조를 HTML로 변환하지 않는다 (이 방식은 실패한다)
16
+ ✅ 스크린샷을 보고 "무엇을 만들어야 하는지" 파악한다
17
+ ✅ Figma 데이터(이미지, 색상, 수치)를 정확한 재료로 사용한다
18
+ ✅ 사람 개발자처럼: 디자인 보고 → 에셋 받고 → 만들면서 비교
19
+ ```
9
20
 
10
21
  ## 금지 사항
11
22
 
12
23
  ```
13
- CSS로 이미지 재현 (삼각형/원/gradient로 나무/눈사람/배경 그리기)
24
+ Figma 레이어 트리를 그대로 div 구조로 변환
25
+ ❌ CSS로 이미지 재현 (gradient/shape으로 그림 그리기)
14
26
  ❌ 이미지 다운로드 없이 코드 생성 진행
15
- ❌ placeholder / 빈 template / 빈 src="" 남기기
16
- CSS 값을 추정 (참조 코드의 Tailwind 클래스에 정확한 값이 있음)
17
- ❌ 브라우저 기본 스타일(검은색 16px)로 보이는 텍스트
18
- ❌ 핵심 에셋만 다운로드 (const img... 전부 다운로드)
27
+ ❌ placeholder / 빈 src="" 남기기
28
+ 색상·크기를 추정 (재료함에 정확한 값이 있음)
19
29
  ❌ 컴포넌트 파일 안에 <style> 블록 / 인라인 style=""
20
30
  ✅ 외부 SCSS 파일에만 스타일 작성
21
31
  ```
@@ -25,9 +35,10 @@ tier: standard
25
35
  ```
26
36
  /vibe.figma
27
37
  → Phase 0: Setup (스택 감지, 디렉토리 생성)
28
- → Phase 1: Storyboard (선택 레이아웃+컴포넌트 구성 + 기능 주석)
29
- → Phase 2: Design (최소 뷰포트 퍼스트 브레이크포인트별 반복)
30
- → Phase 3: Verification (Grep 체크 + 스크린샷 비교)
38
+ → Phase 1: Storyboard (스토리보드 레이아웃 + 컴포넌트 + 기능 정의)
39
+ → Phase 2: 재료 확보 (디자인 URL스크린샷 + 이미지 + CSS + 텍스트)
40
+ → Phase 3: 퍼즐 조립 (스크린샷 보면서 Phase 1 컴포넌트에 디자인 입히기)
41
+ → Phase 4: 검증 루프 (빌드 → 스크린샷 → 비교 → 수정 → 반복)
31
42
  ```
32
43
 
33
44
  ---
@@ -52,6 +63,10 @@ tier: standard
52
63
  - components/{feature}/
53
64
  - public/images/{feature}/ (또는 static/images/{feature}/)
54
65
  - styles/{feature}/ (layout/, components/ 하위)
66
+
67
+ 5. 기존 컴포넌트 스캔:
68
+ - Glob "components/**/*.vue" or "components/**/*.tsx"
69
+ - 재사용 가능한 컴포넌트 목록 수집 (GNB, Footer, Button 등)
55
70
  ```
56
71
 
57
72
  ---
@@ -86,7 +101,7 @@ URL에서 fileKey, nodeId 추출
86
101
  1순위: SPEC (기능 정의서) — 1개
87
102
  2순위: CONFIG (해상도) — 1개
88
103
  3순위: PAGE 중 메인 섹션만 (3.1, 3.2, 3.3, 3.4, 3.5, 3.6)
89
- 하위 케이스(3.1.1, 3.2.1 등)는 건너뜀 — Phase 2에서 필요 시 참조
104
+ 하위 케이스(3.1.1, 3.2.1 등)는 건너뜀
90
105
  4순위: SHARED (공통 요소, Popup) — 필요 시
91
106
 
92
107
  높이 1500px 이상 프레임:
@@ -139,7 +154,7 @@ URL에서 fileKey, nodeId 추출
139
154
  - 목 데이터 (빈 배열 금지, 3~7개 아이템)
140
155
  - 이벤트 핸들러 stub (body는 // TODO:)
141
156
 
142
- <style> 블록 없음 — 스타일은 Phase 2에서 외부 파일로.
157
+ <style> 블록 없음 — 스타일은 Phase 3에서 외부 파일로.
143
158
 
144
159
  3. 공통 컴포넌트 (SHARED에서 파악):
145
160
  → 프로젝트에 이미 있으면 import 재사용
@@ -165,281 +180,312 @@ Phase 1 완료 조건:
165
180
  □ 빌드 성공
166
181
 
167
182
  빈 화면 = Phase 1 미완성. Phase 2로 넘어가지 않는다.
168
- 스타일/이미지는 없어도 됨 — Phase 2에서 채움.
169
- ```
170
-
171
- ### 스케일 팩터
172
-
173
- ```
174
- 스토리보드 CONFIG 또는 기본값에서:
175
- 모바일: scaleFactor = 480 / 720 = 0.667 (또는 targetMobile / designMobile)
176
- PC: scaleFactor = 1920 / 2560 = 0.75 (또는 targetPc / designPc)
177
-
178
- 적용 대상: font-size, padding, margin, gap, border-radius, width, height
179
- 적용 안 함: color, opacity, font-weight, z-index, line-height(단위 없을 때)
183
+ 스타일/이미지는 없어도 됨 — Phase 3에서 채움.
180
184
  ```
181
185
 
182
186
  ---
183
187
 
184
- ## Phase 2: Design
188
+ ## Phase 2: 재료 확보
185
189
 
186
- Phase 1 컴포넌트에 **이미지 + 스타일**을 입힌다.
187
- 모바일 퍼스트. base = 최소 뷰포트, @media (min-width:)로 확장.
190
+ Phase 1 컴포넌트가 준비된 상태에서, 디자인 URL로 시각 재료를 수집한다.
188
191
 
189
192
  사용자에게 질문한다:
190
193
  - question: "베이스 디자인(모바일) Figma URL을 입력해주세요."
191
194
  - options 제공 금지 — 자유 텍스트 입력만 허용
192
195
 
193
- 2-1 2-2 섹션 루프 실행.
196
+ ### 2-1. 디자인 재료 추출
194
197
 
195
- 완료 후 다시 질문:
196
- - question: "다음 브레이크포인트 디자인 URL 입력해주세요. (없으면 '없음')"
197
- → URL 입력 시: 2-3 반응형 추가 후 다시 질문
198
- → "없음" 응답 시: Phase 3으로
198
+ ```
199
+ URL에서 fileKey, nodeId 추출
199
200
 
200
- ### 2-1. SCSS Setup + 등록 ( 섹션 전)
201
+ 1단계 전체 스크린샷 (정답 사진):
202
+ node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{feature}/full-screenshot.png
201
203
 
202
- ```
203
- SCSS 파일 기본 내용 Write:
204
- styles/{feature}/index.scss ← @import 진입점
205
- styles/{feature}/_tokens.scss ← 빈 파일 (섹션마다 채움)
206
- styles/{feature}/_mixins.scss ← breakpoint mixin
207
- styles/{feature}/_base.scss ← 루트 클래스 (.winterPcbang 등)
208
- styles/{feature}/layout/ ← 디렉토리
209
- styles/{feature}/components/ ← 디렉토리
204
+ 2단계 — 전체 트리 + CSS (수치 재료):
205
+ node "[FIGMA_SCRIPT]" tree {fileKey} {nodeId} --depth=10
206
+ /tmp/{feature}/tree.json 저장
210
207
 
211
- 스타일 등록 (BLOCKING 미등록 섹션 루프 진행 금지):
212
- Grep "{feature}/index.scss" 이미 등록되어 있으면 건너뜀.
208
+ 3단계전체 이미지 다운로드 (시각 재료):
209
+ node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --out=/tmp/{feature}/images/ --depth=10
210
+ → 모든 이미지 에셋 확보. 누락 0건, 0byte 0건.
213
211
 
214
- 신규 프로젝트 (--new):
215
- 루트 페이지 파일에서 직접 로드:
216
- pages/{feature}.vue → <style lang="scss" src="~/assets/scss/{feature}/index.scss" />
217
- app/{feature}/page.tsx → import '~/styles/{feature}/index.scss'
212
+ 4단계 섹션별 스크린샷 (부분 정답):
213
+ 트리의 1depth 자식 프레임 각각:
214
+ node "[FIGMA_SCRIPT]" screenshot {fileKey} {child.nodeId} --out=/tmp/{feature}/sections/{child.name}.png
215
+ ```
218
216
 
219
- 기존 프로젝트 (업데이트):
220
- Grep "\.scss\|\.css" in nuxt.config.*/next.config.*/vite.config.*/main.ts
221
- → 기존 방식과 동일하게 등록
217
+ ### 2-2. 재료함 정리
222
218
 
223
- 검증: Grep "{feature}/index.scss" in 프로젝트 전체 → 0건이면 실패
224
219
  ```
220
+ Phase 2 완료 시 /tmp/{feature}/ 에 다음이 준비되어야 함:
225
221
 
226
- ### 2-2. 섹션 루프
222
+ /tmp/{feature}/
223
+ ├── full-screenshot.png ← 전체 정답 사진
224
+ ├── tree.json ← 노드 트리 + CSS 수치
225
+ ├── images/ ← 모든 이미지 에셋
226
+ │ ├── hero-bg.png
227
+ │ ├── hero-title.png
228
+ │ ├── card-item-1.png
229
+ │ └── ...
230
+ └── sections/ ← 섹션별 정답 사진
231
+ ├── hero.png
232
+ ├── daily-checkin.png
233
+ ├── playtime-mission.png
234
+ └── ...
227
235
 
228
- **섹션별로 a→f 순서는 지키되, 섹션 간 병렬 처리 허용.**
229
- **단, 번째 섹션(Hero)은 단독 완료 나머지를 병렬로.**
236
+ 재료 목록 (material-inventory):
237
+ - 이미지: 파일명 + 크기 + 용도 추정 (BG/icon/title/decoration)
238
+ - 색상: tree.json에서 추출한 모든 고유 색상값
239
+ - 폰트: 사용된 font-family, size, weight 목록
240
+ - 텍스트: 모든 TEXT 노드의 characters 값
241
+ - 간격: padding, gap, margin 사용 빈도 높은 값
242
+ ```
230
243
 
231
- #### a. 노드 트리 + CSS 추출
244
+ ### 스케일 팩터
232
245
 
233
246
  ```
234
- Figma REST API로 노드 트리와 CSS 속성을 직접 추출한다.
235
- MCP 플러그인(get_design_context/get_metadata)은 사용하지 않는다.
247
+ 스토리보드 CONFIG 또는 기본값에서:
248
+ 모바일: scaleFactor = 480 / 720 = 0.667 (또는 targetMobile / designMobile)
249
+ PC: scaleFactor = 1920 / 2560 = 0.75 (또는 targetPc / designPc)
236
250
 
237
- Bash:
238
- # [FIGMA_SCRIPT] = ~/.vibe/hooks/scripts/figma-extract.js
239
- node "[FIGMA_SCRIPT]" tree {fileKey} {섹션.nodeId} --depth=10
251
+ 적용 대상: font-size, padding, margin, gap, border-radius, width, height
252
+ 적용 함: color, opacity, font-weight, z-index, line-height(단위 없을 때)
253
+ ```
240
254
 
241
- 반환 (JSON):
242
- {
243
- nodeId, name, type, size: {width, height},
244
- css: { display, flexDirection, gap, fontSize, color, ... },
245
- text: "텍스트 내용" (TEXT 노드),
246
- imageRef: "abc123" (이미지 fill),
247
- children: [...]
248
- }
255
+ ---
249
256
 
250
- CSS는 Figma 노드 속성에서 직접 추출 — Tailwind 역변환 불필요:
251
- fills → background-color effects → box-shadow, filter
252
- strokes → border style → font-family, font-size, color
253
- layoutMode → display:flex itemSpacing → gap
254
- padding* → padding cornerRadius → border-radius
257
+ ## Phase 3: 퍼즐 조립
255
258
 
256
- 인스턴스 내부 자식도 depth로 전부 조회 가능 (MCP 한계 해결).
257
- ```
259
+ **Phase 1에서 만든 컴포넌트에 Phase 2의 재료로 디자인을 입힌다.**
260
+ **스크린샷을 보면서 퍼즐을 맞추듯 조립한다.**
261
+ **첫 섹션(Hero) 단독 완료 후 나머지 섹션 병렬 진행.**
258
262
 
259
- #### b. 이미지 다운로드 (BLOCKING)
263
+ ### 3-0. SCSS Setup + 등록 (첫 섹션 전)
260
264
 
261
265
  ```
262
- 트리에서 imageRef가 있는 노드를 수집 Figma API로 다운로드.
266
+ Phase 1에서 생성한 SCSS 파일에 기본 내용 Write:
267
+ styles/{feature}/index.scss ← @import 진입점
268
+ styles/{feature}/_tokens.scss ← 재료함에서 추출한 디자인 토큰
269
+ styles/{feature}/_mixins.scss ← breakpoint mixin
270
+ styles/{feature}/_base.scss ← 루트 클래스
263
271
 
264
- Bash:
265
- node "[FIGMA_SCRIPT]" images {fileKey} {섹션.nodeId} --out=images/{feature}/ --depth=10
272
+ 토큰 추출 (tree.json의 CSS 수치에서):
273
+ - 색상 primitive ($color-navy: #0a1628) + semantic ($color-bg-primary)
274
+ - 폰트 → $font-pretendard, $font-size-md: 16px
275
+ - 간격 → $space-sm: 8px, $space-md: 16px
266
276
 
267
- 검증: result.total = refs.size (누락 0)
268
- 전부 완료해야 c 단계로 진행.
277
+ 스타일 등록 (BLOCKING):
278
+ Grep "{feature}/index.scss" 이미 등록되어 있으면 건너뜀.
279
+ 없으면 프로젝트 방식에 맞게 등록.
269
280
  ```
270
281
 
271
- #### c. 클래스 매핑 테이블 생성
282
+ ### 3-1. 섹션 조립 프로세스
272
283
 
273
284
  ```
274
- SCSS 작성 전에 반드시 매핑 테이블을 먼저 출력한다.
275
- 이 테이블 없이 SCSS를 작성하지 않는다.
285
+ 섹션에 대해:
286
+
287
+ 1. 정답 확인 — 섹션 스크린샷을 Read로 본다
288
+ /tmp/{feature}/sections/{section}.png
289
+ → "이 화면처럼 만들어야 한다"
276
290
 
277
- 1. Phase 1 컴포넌트의 클래스 목록을 Read로 수집
278
- 2. 트리의 name + css 속성을 분석
279
- 3. 매핑 테이블 출력:
291
+ 2. 재료 확인 섹션에 필요한 재료 목록 확인
292
+ - 이미지: /tmp/{feature}/images/ 에서 해당 파일들
293
+ - CSS 수치: tree.json에서 해당 노드의 정확한 값
294
+ - 텍스트: TEXT 노드의 characters
280
295
 
281
- ┌─────────────────────┬──────────────────┬────────────────────────────────────┐
282
- Phase 1 클래스 │ 트리 노드 name │ 추출된 CSS 값 │
283
- ├─────────────────────┼──────────────────┼────────────────────────────────────┤
284
- .kidSection │ KID (root) │ flex, column, gap:32px, pad:48px │
285
- .kidBg │ BG │ absolute, 720x800 │
286
- .kidLoginBtn │ Btn_Login │ flex, border, shadow, 640x148 │
287
- .kidLoginBtnText │ (TEXT 노드) │ fontSize:36px, color:#fff, w:700 │
288
- .kidDivider │ Divider │ 640x1 │
289
- .kidSteamLink │ steam_account │ fontSize:24px, w:600 │
290
- .kidSteamNote │ (하위 TEXT) │ fontSize:20px, color:#dadce3 │
291
- └─────────────────────┴──────────────────┴────────────────────────────────────┘
296
+ 3. Phase 1 컴포넌트에 디자인 입히기
297
+ a. 이미지 복사: images/ static/images/{feature}/
298
+ b. template 업데이트:
299
+ - Phase 1의 기능 요소(v-for, @click, v-if, $emit) 보존
300
+ - 스크린샷을 보고 시각 구조에 맞게 HTML 재구성
301
+ - Figma 레이어 구조를 HTML로 변환하지 않음 (시각 기반)
302
+ - 재료함의 정확한 이미지 경로 사용
303
+ - 재료함의 정확한 텍스트 사용
304
+ c. script 보존:
305
+ - Phase 1의 JSDoc, 인터페이스, 핸들러 유지
306
+ d. SCSS 작성: 재료함의 정확한 CSS 수치 사용 (scaleFactor 적용)
307
+ - layout/_{section}.scss ← 포지션, 사이즈, 플렉스
308
+ - components/_{section}.scss ← 폰트, 색상, 보더
309
+ - _tokens.scss에 새 토큰 추가
292
310
 
293
- 매핑 기준:
294
- name 일치 직접 매핑
295
- name 없음 트리 위치/text 내용으로 판단
296
- Phase 1에 없는 요소 → 클래스 신규 추가 (template에도 반영)
297
- 트리에 없는 클래스 스타일 없이 유지
311
+ 4. 즉시 검증 — 스크린샷과 비교
312
+ - 이미지 빠진 없나?
313
+ - 텍스트 빠진 없나?
314
+ - 레이아웃 구조가 스크린샷과 맞나?
315
+ - Phase 1의 기능 요소가 보존되었나?
298
316
  ```
299
317
 
300
- #### d. SCSS 작성
318
+ ### 3-2. 코드 작성 규칙
301
319
 
302
320
  ```
303
- 매핑 테이블의 각 행에 대해, 트리의 css 속성을 SCSS로 작성.
304
- CSS 값은 트리에서 이미 추출되어 있으므로 변환 불필요 — 그대로 사용.
321
+ 컴포넌트 (Vue 예시):
322
+ <template>
323
+ 스크린샷에서 보이는 시각 구조대로 작성.
324
+ Figma 레이어 구조 무시.
325
+ Phase 1의 기능 요소(v-for, @click, v-if) 보존.
326
+ 시맨틱 HTML 사용 (<section>, <h2>, <ul>, <button>).
327
+ 이미지 경로: /images/{feature}/파일명.png
328
+ 텍스트: tree.json의 TEXT 노드 characters 값 그대로.
305
329
 
306
- scaleFactor 적용:
307
- px × scaleFactor (font-size, padding, margin, gap, width, height, border-radius)
308
- 적용 color, opacity, font-weight, z-index, line-height(단위 없음), % 값
330
+ <script setup>
331
+ Phase 1의 JSDoc + 인터페이스 + 핸들러 보존.
332
+ 새로운 데이터/상태 추가 기존과 병합.
309
333
 
310
- 출력 파일:
311
- styles/{feature}/layout/_{section}.scss
312
- → position, display, flex, width, height, padding, overflow, z-index, background-image
313
- styles/{feature}/components/_{section}.scss
314
- → font-size, font-weight, color, line-height, letter-spacing, text-align,
315
- border, border-radius, box-shadow, opacity
316
- styles/{feature}/_tokens.scss
317
- → primitive/semantic 구조로 토큰 관리 (아래 참조)
334
+ <style> 블록 없음 — 외부 SCSS만.
318
335
 
319
- _tokens.scss 구조 (primitive/semantic 분리):
320
- // ─── Primitive (Figma 원시 값) ────────────────
321
- // Colors
322
- $color-white: #ffffff;
323
- $color-navy-dark: #0a1628;
324
- $color-purple-500: #604fed;
336
+ SCSS (vibe.figma.convert 참조):
337
+ layout/ position, display, flex, width, height, padding, gap
338
+ components/ → font, color, border, shadow, opacity
339
+ 모든 수치는 재료함의 정확한 값 × scaleFactor.
340
+ 추정 금지 — 값이 없으면 tree.json에서 다시 찾는다.
341
+ ```
342
+
343
+ ### 반응형 (추가 URL)
325
344
 
326
- // Font families
327
- $font-pretendard: 'Pretendard', sans-serif;
328
- $font-roboto-condensed: 'Roboto Condensed', sans-serif;
345
+ ```
346
+ 완료 후 질문: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
329
347
 
330
- // Font sizes (scaled)
331
- $font-size-xs: 11px; // 16 × 0.667
332
- $font-size-sm: 13px; // 20 × 0.667
333
- $font-size-md: 16px; // 24 × 0.667
334
- $font-size-lg: 19px; // 28 × 0.667
348
+ URL 있으면:
349
+ 1. URL에서 재료 확보 (Phase 1 반복)
350
+ 2. 스크린샷과 기존 코드 비교
351
+ 3. @media (min-width: $bp-desktop) 오버라이드만 추가
352
+ 4. 기존 모바일 코드 삭제 금지
353
+ ```
335
354
 
336
- // Font weights
337
- $font-weight-regular: 400;
338
- $font-weight-bold: 700;
355
+ ---
339
356
 
340
- // Spacing (scaled)
341
- $space-xs: 5px;
342
- $space-sm: 11px;
343
- $space-md: 16px;
344
- $space-lg: 21px;
357
+ ## Phase 4: 검증 루프
345
358
 
346
- // ─── Semantic (용도별) ────────────────────────
347
- // Text
348
- $color-text-primary: $color-white;
349
- $color-text-secondary: #dadce3;
350
- $color-text-label: #003879;
351
- $color-text-link: #419bd3;
359
+ **Puppeteer + CDP로 실제 렌더링 결과를 확인하며 자동 수정한다.**
360
+ **사람이 브라우저 보면서 고치는 것과 동일한 루프.**
361
+ **인프라: `src/infra/lib/browser/` (범용 UI 검증 도구)**
352
362
 
353
- // Background
354
- $color-bg-primary: $color-navy-dark;
355
- $color-bg-section: #00264a;
356
- $color-bg-login: rgba(14, 35, 62, 0.8);
363
+ ```
364
+ 자동 반복: P1=0 될 때까지. 최대 3라운드.
365
+ ```
357
366
 
358
- // Border
359
- $color-border-primary: #203f6c;
367
+ ### 4-0. 환경 준비
360
368
 
361
- // Breakpoint
362
- $bp-desktop: 1024px;
369
+ ```
370
+ 1. dev 서버 시작:
371
+ npm run dev (또는 프로젝트 dev 명령)
372
+ → localhost:3000 (또는 해당 포트) 확인
363
373
 
364
- 규칙:
365
- - primitive: Figma에서 추출한 고유 (색상 hex, 폰트, 크기)
366
- - semantic: primitive를 참조하여 용도별 이름 부여 ($color-text-primary: $color-white)
367
- - 섹션 처리할 때마다 발견 시 적절한 카테고리에 추가
368
- - 같은 값이 이미 있으면 기존 토큰 재사용 (중복 금지)
374
+ 2. Puppeteer 브라우저 시작:
375
+ import { launchBrowser, openPage } from 'src/infra/lib/browser'
376
+ const browser = await launchBrowser({ headless: true })
377
+ const page = await openPage(browser, 'http://localhost:3000/{feature}', {
378
+ width: 480, // 모바일 퍼스트 (또는 타겟 뷰포트)
379
+ height: 960,
380
+ })
381
+ ```
369
382
 
370
- BG 레이어 패턴 (트리에서 position:absolute + 이미지 fill):
371
- .{section}Bg → position: absolute; inset: 0; z-index: 0;
372
- .{section}Content → position: relative; z-index: 1;
383
+ ### 4-1. 렌더링 스크린샷 vs Figma 스크린샷
373
384
 
374
- index.scss에 새 섹션 @import 추가.
375
385
  ```
386
+ import { captureScreenshot, compareScreenshots } from 'src/infra/lib/browser'
387
+
388
+ 각 섹션에 대해:
389
+ 1. 렌더링 결과 스크린샷 캡처:
390
+ await captureScreenshot(page, {
391
+ outPath: '/tmp/{feature}/rendered-{section}.png',
392
+ selector: '.{section}Section', // Phase 1에서 만든 클래스
393
+ })
376
394
 
377
- #### e. template 업데이트
395
+ 2. Figma 원본과 픽셀 비교:
396
+ const diff = await compareScreenshots(
397
+ '/tmp/{feature}/sections/{section}.png', // Figma 원본
398
+ '/tmp/{feature}/rendered-{section}.png', // 렌더링 결과
399
+ '/tmp/{feature}/diff-{section}.png', // 차이 시각화
400
+ )
378
401
 
402
+ 3. diff 이미지를 Read로 확인:
403
+ → 빨간색 영역 = 차이 나는 부분
404
+ → diffRatio > 0.1 이면 P1 이슈
379
405
  ```
380
- Phase 1 컴포넌트의 template을 트리 구조 기반으로 리팩토링.
381
- script(JSDoc, 인터페이스, 목 데이터, 핸들러)는 보존.
382
406
 
383
- 1. 트리의 HTML 구조를 프로젝트 스택으로 변환:
384
- FRAME → <div>, TEXT → <p>/<span>, VECTOR/RECTANGLE with imageRef → <img>
407
+ ### 4-2. CSS 수치 정밀 비교
385
408
 
386
- 2. 이미지 경로를 imageMap으로 교체:
387
- imageRef "abc123" src="/images/{feature}/abc123.png"
409
+ ```
410
+ import { getComputedStyles, compareStyles, diffsToIssues } from 'src/infra/lib/browser'
411
+
412
+ 각 섹션의 주요 요소에 대해:
413
+ 1. 렌더링된 computed CSS 추출:
414
+ const actual = await getComputedStyles(page, '.heroTitle', [
415
+ 'font-size', 'color', 'width', 'height', 'padding', 'margin',
416
+ 'background-color', 'border-radius', 'gap',
417
+ ])
388
418
 
389
- 3. BG 레이어 구조 적용:
390
- .{section}Bg div (배경) + .{section}Content div (콘텐츠)
419
+ 2. Figma 재료함의 기대값과 비교:
420
+ // tree.json에서 해당 노드의 CSS 수치 (scaleFactor 적용 후)
421
+ const expected = { 'font-size': '16px', 'color': '#ffffff', 'width': '465px' }
422
+ const diffs = compareStyles(expected, actual)
391
423
 
392
- 4. Phase 1 기능 요소 재배치:
393
- v-for, @click, v-if, $emit 등을 새 구조의 적절한 위치에 배치
424
+ 3. 차이 이슈 변환:
425
+ const issues = diffsToIssues(diffs)
426
+ → delta > 4px: P1 (레이아웃 영향)
427
+ → delta ≤ 4px: P2 (미세 차이)
428
+ ```
394
429
 
395
- 5. 접근성:
396
- 장식 이미지 (BG 내) → alt="" aria-hidden="true"
397
- 콘텐츠 이미지 → alt="설명적 텍스트"
430
+ ### 4-3. 이미지·텍스트 누락 체크
398
431
 
399
- 컴포넌트에 <style> 블록 없음. 스타일은 전부 외부 SCSS.
400
432
  ```
433
+ import { extractImages, extractTextContent } from 'src/infra/lib/browser'
401
434
 
402
- #### f. 섹션 검증
435
+ 1. 이미지 로드 상태 확인:
436
+ const images = await extractImages(page)
437
+ images.filter(img => !img.loaded)
438
+ → 로드 실패 이미지 = P1 (이미지 누락)
439
+ → src="" 이미지 = P1 (빈 경로)
403
440
 
441
+ 2. 텍스트 콘텐츠 확인:
442
+ const texts = await extractTextContent(page)
443
+ → 재료함의 TEXT 노드 characters와 대조
444
+ → 누락된 텍스트 = P1
404
445
  ```
405
- Grep 체크:
406
- □ 'src=""' in 컴포넌트 파일 → 0건
407
- □ "<style" in 컴포넌트 파일 → 0건
408
446
 
409
- Read 체크:
410
- □ 외부 SCSS 파일에 font-size, color 존재 (브라우저 기본 스타일 방지)
411
- □ 이미지 파일 수 = imageRef 수 (누락 0)
447
+ ### 4-4. 자동 수정 루프
412
448
 
413
- 실패 → 수정 → 재검증
414
449
  ```
450
+ 라운드 1~3:
451
+ 1. 4-1 ~ 4-3 실행 → 이슈 목록 수집
452
+ 2. P1 이슈 우선 수정:
453
+ - 이미지 누락 → 이미지 경로 확인, static/ 에 파일 존재 확인
454
+ - 레이아웃 다름 → 스크린샷 diff 이미지 + computed CSS로 원인 파악
455
+ - 텍스트 누락 → 재료함의 정확한 텍스트 삽입
456
+ - CSS 수치 틀림 → 재료함(tree.json)의 정확한 값으로 교체
457
+ ⚠️ 추정으로 수정하지 않는다. 반드시 재료함 참조.
458
+ 3. 수정 후 페이지 리로드 → 다시 캡처 → 비교
459
+ 4. P1=0 이면 종료
415
460
 
416
- ### 2-3. 반응형 (두 번째 URL부터)
461
+ 라운드 종료 조건:
462
+ - P1=0: 성공 → 브라우저 종료, 결과 보고
463
+ - 3라운드 후 P1 남음: TODO 목록으로 사용자에게 보고
464
+ - 같은 이슈가 반복: 해당 이슈 스킵, 다음 이슈로
417
465
 
466
+ 결과 보고:
467
+ - 수정한 파일 목록
468
+ - 남은 P2 이슈 목록 (선택적 수정)
469
+ - 최종 diff 스크린샷 경로
418
470
  ```
419
- 두 번째 이후 URL: 기존 스타일 유지 + @media 추가만.
420
471
 
421
- 같은 유지
422
- 다른 px 값 → @media (min-width: $bp-desktop) 오버라이드
423
- 다른 레이아웃 → @media 블록 추가
424
- 다른 배경 이미지 → @media 이미지 분기
425
- 기존 코드/스타일 삭제 금지
472
+ ### 4-5. 반응형 검증 (추가 뷰포트)
473
+
426
474
  ```
475
+ 모바일 검증 완료 후, 추가 브레이크포인트가 있으면:
427
476
 
428
- ---
477
+ await page.setViewport({ width: 1920, height: 1080 })
478
+ await page.reload({ waitUntil: 'networkidle0' })
479
+
480
+ → 데스크탑 Figma 스크린샷과 동일한 4-1 ~ 4-4 루프 반복
481
+ ```
429
482
 
430
- ## Phase 3: Verification
483
+ ### 4-6. 브라우저 정리
431
484
 
432
485
  ```
433
- Grep 체크:
434
- □ "<style" in components/{feature}/ → 0건
435
- □ 'src=""' in components/{feature}/ → 0건
436
- □ Glob: images/{feature}/ → 이미지 파일 존재
486
+ import { closeBrowser } from 'src/infra/lib/browser'
437
487
 
438
- 시각 검증:
439
- 섹션 스크린샷:
440
- node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{section}.png
441
- → dev 서버/preview와 비교
442
- P1 (필수): 이미지 누락, 레이아웃 구조 다름, 텍스트 스타일 미적용
443
- P2 (권장): 미세 간격, 미세 색상 차이
444
- → P1 수정 → 재검증 (P1=0 될 때까지)
488
+ 검증 완료 후:
489
+ await closeBrowser()
490
+ dev 서버 종료 (필요 시)
445
491
  ```