@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.
- 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 +251 -205
- 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,281 +180,312 @@ Phase 1 완료 조건:
|
|
|
165
180
|
□ 빌드 성공
|
|
166
181
|
|
|
167
182
|
빈 화면 = Phase 1 미완성. Phase 2로 넘어가지 않는다.
|
|
168
|
-
스타일/이미지는 없어도 됨 — Phase
|
|
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:
|
|
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
|
-
|
|
196
|
+
### 2-1. 디자인 재료 추출
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
→ URL 입력 시: 2-3 반응형 추가 후 다시 질문
|
|
198
|
-
→ "없음" 응답 시: Phase 3으로
|
|
198
|
+
```
|
|
199
|
+
URL에서 fileKey, nodeId 추출
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
1단계 — 전체 스크린샷 (정답 사진):
|
|
202
|
+
node "[FIGMA_SCRIPT]" screenshot {fileKey} {nodeId} --out=/tmp/{feature}/full-screenshot.png
|
|
201
203
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
212
|
-
|
|
208
|
+
3단계 — 전체 이미지 다운로드 (시각 재료):
|
|
209
|
+
node "[FIGMA_SCRIPT]" images {fileKey} {nodeId} --out=/tmp/{feature}/images/ --depth=10
|
|
210
|
+
→ 모든 이미지 에셋 확보. 누락 0건, 0byte 0건.
|
|
213
211
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
229
|
-
|
|
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
|
-
|
|
244
|
+
### 스케일 팩터
|
|
232
245
|
|
|
233
246
|
```
|
|
234
|
-
|
|
235
|
-
|
|
247
|
+
스토리보드 CONFIG 또는 기본값에서:
|
|
248
|
+
모바일: scaleFactor = 480 / 720 = 0.667 (또는 targetMobile / designMobile)
|
|
249
|
+
PC: scaleFactor = 1920 / 2560 = 0.75 (또는 targetPc / designPc)
|
|
236
250
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
적용 대상: font-size, padding, margin, gap, border-radius, width, height
|
|
252
|
+
적용 안 함: color, opacity, font-weight, z-index, line-height(단위 없을 때)
|
|
253
|
+
```
|
|
240
254
|
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
|
|
259
|
+
**Phase 1에서 만든 컴포넌트에 Phase 2의 재료로 디자인을 입힌다.**
|
|
260
|
+
**스크린샷을 보면서 퍼즐을 맞추듯 조립한다.**
|
|
261
|
+
**첫 섹션(Hero) 단독 완료 후 나머지 섹션 병렬 진행.**
|
|
258
262
|
|
|
259
|
-
|
|
263
|
+
### 3-0. SCSS Setup + 등록 (첫 섹션 전)
|
|
260
264
|
|
|
261
265
|
```
|
|
262
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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
|
-
|
|
268
|
-
|
|
277
|
+
스타일 등록 (BLOCKING):
|
|
278
|
+
Grep "{feature}/index.scss" → 이미 등록되어 있으면 건너뜀.
|
|
279
|
+
없으면 프로젝트 방식에 맞게 등록.
|
|
269
280
|
```
|
|
270
281
|
|
|
271
|
-
|
|
282
|
+
### 3-1. 섹션 조립 프로세스
|
|
272
283
|
|
|
273
284
|
```
|
|
274
|
-
|
|
275
|
-
|
|
285
|
+
각 섹션에 대해:
|
|
286
|
+
|
|
287
|
+
1. 정답 확인 — 섹션 스크린샷을 Read로 본다
|
|
288
|
+
/tmp/{feature}/sections/{section}.png
|
|
289
|
+
→ "이 화면처럼 만들어야 한다"
|
|
276
290
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
291
|
+
2. 재료 확인 — 이 섹션에 필요한 재료 목록 확인
|
|
292
|
+
- 이미지: /tmp/{feature}/images/ 에서 해당 파일들
|
|
293
|
+
- CSS 수치: tree.json에서 해당 노드의 정확한 값
|
|
294
|
+
- 텍스트: TEXT 노드의 characters
|
|
280
295
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
311
|
+
4. 즉시 검증 — 스크린샷과 비교
|
|
312
|
+
- 이미지 빠진 거 없나?
|
|
313
|
+
- 텍스트 빠진 거 없나?
|
|
314
|
+
- 레이아웃 구조가 스크린샷과 맞나?
|
|
315
|
+
- Phase 1의 기능 요소가 보존되었나?
|
|
298
316
|
```
|
|
299
317
|
|
|
300
|
-
|
|
318
|
+
### 3-2. 코드 작성 규칙
|
|
301
319
|
|
|
302
320
|
```
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
$font-roboto-condensed: 'Roboto Condensed', sans-serif;
|
|
345
|
+
```
|
|
346
|
+
완료 후 질문: "다음 브레이크포인트 디자인 URL을 입력해주세요. (없으면 '없음')"
|
|
329
347
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
348
|
+
URL 있으면:
|
|
349
|
+
1. 새 URL에서 재료 확보 (Phase 1 반복)
|
|
350
|
+
2. 새 스크린샷과 기존 코드 비교
|
|
351
|
+
3. @media (min-width: $bp-desktop) 오버라이드만 추가
|
|
352
|
+
4. 기존 모바일 코드 삭제 금지
|
|
353
|
+
```
|
|
335
354
|
|
|
336
|
-
|
|
337
|
-
$font-weight-regular: 400;
|
|
338
|
-
$font-weight-bold: 700;
|
|
355
|
+
---
|
|
339
356
|
|
|
340
|
-
|
|
341
|
-
$space-xs: 5px;
|
|
342
|
-
$space-sm: 11px;
|
|
343
|
-
$space-md: 16px;
|
|
344
|
-
$space-lg: 21px;
|
|
357
|
+
## Phase 4: 검증 루프
|
|
345
358
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
$color-bg-login: rgba(14, 35, 62, 0.8);
|
|
363
|
+
```
|
|
364
|
+
자동 반복: P1=0 될 때까지. 최대 3라운드.
|
|
365
|
+
```
|
|
357
366
|
|
|
358
|
-
|
|
359
|
-
$color-border-primary: #203f6c;
|
|
367
|
+
### 4-0. 환경 준비
|
|
360
368
|
|
|
361
|
-
|
|
362
|
-
|
|
369
|
+
```
|
|
370
|
+
1. dev 서버 시작:
|
|
371
|
+
npm run dev (또는 프로젝트 dev 명령)
|
|
372
|
+
→ localhost:3000 (또는 해당 포트) 확인
|
|
363
373
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
384
|
-
FRAME → <div>, TEXT → <p>/<span>, VECTOR/RECTANGLE with imageRef → <img>
|
|
407
|
+
### 4-2. CSS 수치 정밀 비교
|
|
385
408
|
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
390
|
-
|
|
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
|
-
|
|
393
|
-
|
|
424
|
+
3. 차이 → 이슈 변환:
|
|
425
|
+
const issues = diffsToIssues(diffs)
|
|
426
|
+
→ delta > 4px: P1 (레이아웃 영향)
|
|
427
|
+
→ delta ≤ 4px: P2 (미세 차이)
|
|
428
|
+
```
|
|
394
429
|
|
|
395
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
483
|
+
### 4-6. 브라우저 정리
|
|
431
484
|
|
|
432
485
|
```
|
|
433
|
-
|
|
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
|
-
|
|
441
|
-
→ dev 서버/preview와 비교
|
|
442
|
-
P1 (필수): 이미지 누락, 레이아웃 구조 다름, 텍스트 스타일 미적용
|
|
443
|
-
P2 (권장): 미세 간격, 미세 색상 차이
|
|
444
|
-
→ P1 수정 → 재검증 (P1=0 될 때까지)
|
|
488
|
+
검증 완료 후:
|
|
489
|
+
await closeBrowser()
|
|
490
|
+
dev 서버 종료 (필요 시)
|
|
445
491
|
```
|