@su-record/vibe 2.8.35 → 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.
- package/dist/infra/lib/browser/capture.d.ts +26 -0
- package/dist/infra/lib/browser/capture.d.ts.map +1 -0
- package/dist/infra/lib/browser/capture.js +115 -0
- package/dist/infra/lib/browser/capture.js.map +1 -0
- package/dist/infra/lib/browser/compare.d.ts +19 -0
- package/dist/infra/lib/browser/compare.d.ts.map +1 -0
- package/dist/infra/lib/browser/compare.js +133 -0
- package/dist/infra/lib/browser/compare.js.map +1 -0
- package/dist/infra/lib/browser/index.d.ts +5 -0
- package/dist/infra/lib/browser/index.d.ts.map +1 -0
- package/dist/infra/lib/browser/index.js +4 -0
- package/dist/infra/lib/browser/index.js.map +1 -0
- package/dist/infra/lib/browser/launch.d.ts +19 -0
- package/dist/infra/lib/browser/launch.d.ts.map +1 -0
- package/dist/infra/lib/browser/launch.js +59 -0
- package/dist/infra/lib/browser/launch.js.map +1 -0
- package/dist/infra/lib/browser/types.d.ts +103 -0
- package/dist/infra/lib/browser/types.d.ts.map +1 -0
- package/dist/infra/lib/browser/types.js +8 -0
- package/dist/infra/lib/browser/types.js.map +1 -0
- package/package.json +1 -1
- package/skills/vibe.figma/SKILL.md +278 -112
- package/skills/vibe.figma.convert/SKILL.md +114 -163
- package/skills/vibe.figma.extract/SKILL.md +76 -37
|
@@ -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 —
|
|
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
|
-
❌
|
|
24
|
+
❌ Figma 레이어 트리를 그대로 div 구조로 변환
|
|
25
|
+
❌ CSS로 이미지 재현 (gradient/shape으로 그림 그리기)
|
|
14
26
|
❌ 이미지 다운로드 없이 코드 생성 진행
|
|
15
|
-
❌ placeholder / 빈
|
|
16
|
-
❌
|
|
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:
|
|
30
|
-
→ Phase 3:
|
|
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 등)는 건너뜀
|
|
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
|
|
157
|
+
<style> 블록 없음 — 스타일은 Phase 3에서 외부 파일로.
|
|
143
158
|
|
|
144
159
|
3. 공통 컴포넌트 (SHARED에서 파악):
|
|
145
160
|
→ 프로젝트에 이미 있으면 import 재사용
|
|
@@ -165,7 +180,65 @@ Phase 1 완료 조건:
|
|
|
165
180
|
□ 빌드 성공
|
|
166
181
|
|
|
167
182
|
빈 화면 = Phase 1 미완성. Phase 2로 넘어가지 않는다.
|
|
168
|
-
스타일/이미지는 없어도 됨 — Phase
|
|
183
|
+
스타일/이미지는 없어도 됨 — Phase 3에서 채움.
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Phase 2: 재료 확보
|
|
189
|
+
|
|
190
|
+
Phase 1 컴포넌트가 준비된 상태에서, 디자인 URL로 시각 재료를 수집한다.
|
|
191
|
+
|
|
192
|
+
사용자에게 질문한다:
|
|
193
|
+
- question: "베이스 디자인(모바일) Figma URL을 입력해주세요."
|
|
194
|
+
- options 제공 금지 — 자유 텍스트 입력만 허용
|
|
195
|
+
|
|
196
|
+
### 2-1. 디자인 재료 추출
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
URL에서 fileKey, nodeId 추출
|
|
200
|
+
|
|
201
|
+
1단계 — 전체 스크린샷 (정답 사진):
|
|
202
|
+
node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{feature}/full-screenshot.png
|
|
203
|
+
|
|
204
|
+
2단계 — 전체 트리 + CSS (수치 재료):
|
|
205
|
+
node "[FIGMA_SCRIPT]" tree {fileKey} {nodeId} --depth=10
|
|
206
|
+
→ /tmp/{feature}/tree.json 에 저장
|
|
207
|
+
|
|
208
|
+
3단계 — 전체 이미지 다운로드 (시각 재료):
|
|
209
|
+
node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --out=/tmp/{feature}/images/ --depth=10
|
|
210
|
+
→ 모든 이미지 에셋 확보. 누락 0건, 0byte 0건.
|
|
211
|
+
|
|
212
|
+
4단계 — 섹션별 스크린샷 (부분 정답):
|
|
213
|
+
트리의 1depth 자식 프레임 각각:
|
|
214
|
+
node "[FIGMA_SCRIPT]" screenshot {fileKey} {child.nodeId} --out=/tmp/{feature}/sections/{child.name}.png
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 2-2. 재료함 정리
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
Phase 2 완료 시 /tmp/{feature}/ 에 다음이 준비되어야 함:
|
|
221
|
+
|
|
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
|
+
└── ...
|
|
235
|
+
|
|
236
|
+
재료 목록 (material-inventory):
|
|
237
|
+
- 이미지: 파일명 + 크기 + 용도 추정 (BG/icon/title/decoration)
|
|
238
|
+
- 색상: tree.json에서 추출한 모든 고유 색상값
|
|
239
|
+
- 폰트: 사용된 font-family, size, weight 목록
|
|
240
|
+
- 텍스트: 모든 TEXT 노드의 characters 값
|
|
241
|
+
- 간격: padding, gap, margin 사용 빈도 높은 값
|
|
169
242
|
```
|
|
170
243
|
|
|
171
244
|
### 스케일 팩터
|
|
@@ -181,145 +254,238 @@ Phase 1 완료 조건:
|
|
|
181
254
|
|
|
182
255
|
---
|
|
183
256
|
|
|
184
|
-
## Phase
|
|
257
|
+
## Phase 3: 퍼즐 조립
|
|
185
258
|
|
|
186
|
-
Phase 1 컴포넌트에
|
|
187
|
-
|
|
259
|
+
**Phase 1에서 만든 컴포넌트에 Phase 2의 재료로 디자인을 입힌다.**
|
|
260
|
+
**스크린샷을 보면서 퍼즐을 맞추듯 조립한다.**
|
|
261
|
+
**첫 섹션(Hero) 단독 완료 후 나머지 섹션 병렬 진행.**
|
|
188
262
|
|
|
189
|
-
|
|
190
|
-
- question: "베이스 디자인(모바일) Figma URL을 입력해주세요."
|
|
191
|
-
- options 제공 금지 — 자유 텍스트 입력만 허용
|
|
192
|
-
|
|
193
|
-
→ 2-1 → 2-2 섹션 루프 실행.
|
|
194
|
-
|
|
195
|
-
완료 후 다시 질문:
|
|
196
|
-
- question: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
|
|
197
|
-
→ URL 입력 시: 2-3 반응형 추가 후 다시 질문
|
|
198
|
-
→ "없음" 응답 시: Phase 3으로
|
|
199
|
-
|
|
200
|
-
### 2-1. SCSS Setup + 등록 (첫 섹션 전)
|
|
263
|
+
### 3-0. SCSS Setup + 등록 (첫 섹션 전)
|
|
201
264
|
|
|
202
265
|
```
|
|
203
|
-
SCSS
|
|
266
|
+
Phase 1에서 생성한 빈 SCSS 파일에 기본 내용 Write:
|
|
204
267
|
styles/{feature}/index.scss ← @import 진입점
|
|
205
|
-
styles/{feature}/_tokens.scss ←
|
|
268
|
+
styles/{feature}/_tokens.scss ← 재료함에서 추출한 디자인 토큰
|
|
206
269
|
styles/{feature}/_mixins.scss ← breakpoint mixin
|
|
207
|
-
styles/{feature}/_base.scss ← 루트 클래스
|
|
208
|
-
styles/{feature}/layout/ ← 디렉토리
|
|
209
|
-
styles/{feature}/components/ ← 디렉토리
|
|
270
|
+
styles/{feature}/_base.scss ← 루트 클래스
|
|
210
271
|
|
|
211
|
-
|
|
212
|
-
|
|
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
|
|
213
276
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
277
|
+
스타일 등록 (BLOCKING):
|
|
278
|
+
Grep "{feature}/index.scss" → 이미 등록되어 있으면 건너뜀.
|
|
279
|
+
없으면 프로젝트 방식에 맞게 등록.
|
|
280
|
+
```
|
|
218
281
|
|
|
219
|
-
|
|
220
|
-
Grep "\.scss\|\.css" in nuxt.config.*/next.config.*/vite.config.*/main.ts
|
|
221
|
-
→ 기존 방식과 동일하게 등록
|
|
282
|
+
### 3-1. 섹션 조립 프로세스
|
|
222
283
|
|
|
223
|
-
|
|
284
|
+
```
|
|
285
|
+
각 섹션에 대해:
|
|
286
|
+
|
|
287
|
+
1. 정답 확인 — 섹션 스크린샷을 Read로 본다
|
|
288
|
+
/tmp/{feature}/sections/{section}.png
|
|
289
|
+
→ "이 화면처럼 만들어야 한다"
|
|
290
|
+
|
|
291
|
+
2. 재료 확인 — 이 섹션에 필요한 재료 목록 확인
|
|
292
|
+
- 이미지: /tmp/{feature}/images/ 에서 해당 파일들
|
|
293
|
+
- CSS 수치: tree.json에서 해당 노드의 정확한 값
|
|
294
|
+
- 텍스트: TEXT 노드의 characters
|
|
295
|
+
|
|
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에 새 토큰 추가
|
|
310
|
+
|
|
311
|
+
4. 즉시 검증 — 스크린샷과 비교
|
|
312
|
+
- 이미지 빠진 거 없나?
|
|
313
|
+
- 텍스트 빠진 거 없나?
|
|
314
|
+
- 레이아웃 구조가 스크린샷과 맞나?
|
|
315
|
+
- Phase 1의 기능 요소가 보존되었나?
|
|
224
316
|
```
|
|
225
317
|
|
|
226
|
-
###
|
|
318
|
+
### 3-2. 코드 작성 규칙
|
|
227
319
|
|
|
228
|
-
|
|
229
|
-
|
|
320
|
+
```
|
|
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 값 그대로.
|
|
329
|
+
|
|
330
|
+
<script setup>
|
|
331
|
+
Phase 1의 JSDoc + 인터페이스 + 핸들러 보존.
|
|
332
|
+
새로운 데이터/상태 추가 시 기존과 병합.
|
|
333
|
+
|
|
334
|
+
<style> 블록 없음 — 외부 SCSS만.
|
|
335
|
+
|
|
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
|
+
```
|
|
230
342
|
|
|
231
|
-
|
|
343
|
+
### 반응형 (추가 URL)
|
|
232
344
|
|
|
233
345
|
```
|
|
234
|
-
|
|
235
|
-
node "[FIGMA_SCRIPT]" render {fileKey} {섹션.nodeId} --out=/tmp/{feature}-{section}/ --depth=10
|
|
346
|
+
완료 후 질문: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
|
|
236
347
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
├── {section}-screenshot.png ← 섹션 스크린샷 (시각 기준점)
|
|
243
|
-
└── images/
|
|
244
|
-
├── {section}-bg-composite.png ← 복합 BG 합성 이미지
|
|
245
|
-
├── {section}-title.png ← 이름 있는 이미지
|
|
246
|
-
└── ...
|
|
247
|
-
|
|
248
|
-
이미지는 노드 name 기반 파일명 (해시 아님).
|
|
249
|
-
복합 BG(3개+ 하위 레이어)는 자동으로 합성 스크린샷 생성.
|
|
250
|
-
인스턴스 내부 자식도 depth로 전부 조회.
|
|
348
|
+
URL 있으면:
|
|
349
|
+
1. 새 URL에서 재료 확보 (Phase 1 반복)
|
|
350
|
+
2. 새 스크린샷과 기존 코드 비교
|
|
351
|
+
3. @media (min-width: $bp-desktop) 오버라이드만 추가
|
|
352
|
+
4. 기존 모바일 코드 삭제 금지
|
|
251
353
|
```
|
|
252
354
|
|
|
253
|
-
|
|
355
|
+
---
|
|
254
356
|
|
|
255
|
-
|
|
256
|
-
render 출력물을 프로젝트에 적용:
|
|
357
|
+
## Phase 4: 검증 루프
|
|
257
358
|
|
|
258
|
-
|
|
259
|
-
|
|
359
|
+
**Puppeteer + CDP로 실제 렌더링 결과를 확인하며 자동 수정한다.**
|
|
360
|
+
**사람이 브라우저 보면서 고치는 것과 동일한 루프.**
|
|
361
|
+
**인프라: `src/infra/lib/browser/` (범용 UI 검증 도구)**
|
|
260
362
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
styles/{feature}/components/_{section}.scss ← font, color, border, shadow
|
|
265
|
-
styles/{feature}/_tokens.scss ← 새 값 추가 (primitive/semantic, vibe.figma.convert 참조)
|
|
266
|
-
index.scss에 새 섹션 @import 추가.
|
|
363
|
+
```
|
|
364
|
+
자동 반복: P1=0 될 때까지. 최대 3라운드.
|
|
365
|
+
```
|
|
267
366
|
|
|
268
|
-
|
|
269
|
-
{section}.html을 읽고 Phase 1 컴포넌트에 반영:
|
|
270
|
-
- HTML 구조를 프로젝트 스택으로 변환 (class 유지)
|
|
271
|
-
- 이미지 경로를 static/images/{feature}/ 로 교체
|
|
272
|
-
- Phase 1 기능 요소(v-for, @click, v-if, $emit) 재배치
|
|
273
|
-
- script(JSDoc, 인터페이스, 핸들러) 보존
|
|
367
|
+
### 4-0. 환경 준비
|
|
274
368
|
|
|
275
|
-
|
|
276
|
-
|
|
369
|
+
```
|
|
370
|
+
1. dev 서버 시작:
|
|
371
|
+
npm run dev (또는 프로젝트 dev 명령)
|
|
372
|
+
→ localhost:3000 (또는 해당 포트) 확인
|
|
373
|
+
|
|
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
|
+
})
|
|
277
381
|
```
|
|
278
382
|
|
|
279
|
-
|
|
383
|
+
### 4-1. 렌더링 스크린샷 vs Figma 스크린샷
|
|
280
384
|
|
|
281
385
|
```
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
+
})
|
|
394
|
+
|
|
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
|
+
)
|
|
401
|
+
|
|
402
|
+
3. diff 이미지를 Read로 확인:
|
|
403
|
+
→ 빨간색 영역 = 차이 나는 부분
|
|
404
|
+
→ diffRatio > 0.1 이면 P1 이슈
|
|
405
|
+
```
|
|
285
406
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
407
|
+
### 4-2. CSS 수치 정밀 비교
|
|
408
|
+
|
|
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
|
+
])
|
|
418
|
+
|
|
419
|
+
2. Figma 재료함의 기대값과 비교:
|
|
420
|
+
// tree.json에서 해당 노드의 CSS 수치 (scaleFactor 적용 후)
|
|
421
|
+
const expected = { 'font-size': '16px', 'color': '#ffffff', 'width': '465px' }
|
|
422
|
+
const diffs = compareStyles(expected, actual)
|
|
423
|
+
|
|
424
|
+
3. 차이 → 이슈 변환:
|
|
425
|
+
const issues = diffsToIssues(diffs)
|
|
426
|
+
→ delta > 4px: P1 (레이아웃 영향)
|
|
427
|
+
→ delta ≤ 4px: P2 (미세 차이)
|
|
428
|
+
```
|
|
289
429
|
|
|
290
|
-
|
|
291
|
-
□ {section}-screenshot.png vs dev 서버 → 주요 차이 없음
|
|
430
|
+
### 4-3. 이미지·텍스트 누락 체크
|
|
292
431
|
|
|
293
|
-
|
|
432
|
+
```
|
|
433
|
+
import { extractImages, extractTextContent } from 'src/infra/lib/browser'
|
|
434
|
+
|
|
435
|
+
1. 이미지 로드 상태 확인:
|
|
436
|
+
const images = await extractImages(page)
|
|
437
|
+
images.filter(img => !img.loaded)
|
|
438
|
+
→ 로드 실패 이미지 = P1 (이미지 누락)
|
|
439
|
+
→ src="" 이미지 = P1 (빈 경로)
|
|
440
|
+
|
|
441
|
+
2. 텍스트 콘텐츠 확인:
|
|
442
|
+
const texts = await extractTextContent(page)
|
|
443
|
+
→ 재료함의 TEXT 노드 characters와 대조
|
|
444
|
+
→ 누락된 텍스트 = P1
|
|
294
445
|
```
|
|
295
446
|
|
|
296
|
-
###
|
|
447
|
+
### 4-4. 자동 수정 루프
|
|
297
448
|
|
|
298
449
|
```
|
|
299
|
-
|
|
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 이면 종료
|
|
460
|
+
|
|
461
|
+
라운드 종료 조건:
|
|
462
|
+
- P1=0: 성공 → 브라우저 종료, 결과 보고
|
|
463
|
+
- 3라운드 후 P1 남음: TODO 목록으로 사용자에게 보고
|
|
464
|
+
- 같은 이슈가 반복: 해당 이슈 스킵, 다음 이슈로
|
|
465
|
+
|
|
466
|
+
결과 보고:
|
|
467
|
+
- 수정한 파일 목록
|
|
468
|
+
- 남은 P2 이슈 목록 (선택적 수정)
|
|
469
|
+
- 최종 diff 스크린샷 경로
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### 4-5. 반응형 검증 (추가 뷰포트)
|
|
300
473
|
|
|
301
|
-
같은 값 → 유지
|
|
302
|
-
다른 px 값 → @media (min-width: $bp-desktop) 오버라이드
|
|
303
|
-
다른 레이아웃 → @media 블록 추가
|
|
304
|
-
다른 배경 이미지 → @media 이미지 분기
|
|
305
|
-
기존 코드/스타일 삭제 금지
|
|
306
474
|
```
|
|
475
|
+
모바일 검증 완료 후, 추가 브레이크포인트가 있으면:
|
|
307
476
|
|
|
308
|
-
|
|
477
|
+
await page.setViewport({ width: 1920, height: 1080 })
|
|
478
|
+
await page.reload({ waitUntil: 'networkidle0' })
|
|
479
|
+
|
|
480
|
+
→ 데스크탑 Figma 스크린샷과 동일한 4-1 ~ 4-4 루프 반복
|
|
481
|
+
```
|
|
309
482
|
|
|
310
|
-
|
|
483
|
+
### 4-6. 브라우저 정리
|
|
311
484
|
|
|
312
485
|
```
|
|
313
|
-
|
|
314
|
-
□ "<style" in components/{feature}/ → 0건
|
|
315
|
-
□ 'src=""' in components/{feature}/ → 0건
|
|
316
|
-
□ Glob: images/{feature}/ → 이미지 파일 존재
|
|
486
|
+
import { closeBrowser } from 'src/infra/lib/browser'
|
|
317
487
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
→ dev 서버/preview와 비교
|
|
322
|
-
P1 (필수): 이미지 누락, 레이아웃 구조 다름, 텍스트 스타일 미적용
|
|
323
|
-
P2 (권장): 미세 간격, 미세 색상 차이
|
|
324
|
-
→ P1 수정 → 재검증 (P1=0 될 때까지)
|
|
488
|
+
검증 완료 후:
|
|
489
|
+
await closeBrowser()
|
|
490
|
+
dev 서버 종료 (필요 시)
|
|
325
491
|
```
|