Files
C.E.L_Slide_test2/figma_to_html_agent/PROCESS.md
kyeongmin 9fbe3ac90c add: figma_to_html_agent/blocks/ + 변환 도구 docs 갱신
전체 401 files (397 추가 + 4 수정), 14304 insertions.

추가:
- figma_to_html_agent/blocks/ — Figma 변환 결과 (32 frame, ~79MB).
  각 frame folder = {analysis.md, flat.md, texts.md, index.html, assets/,
  _renders/, _render.py, RELATIONSHIPS.md / STATUS.md / classification.md
  (일부 frame)}.
  Phase Z 의 *figma source layer* — runtime 에서 직접 사용 X, contract /
  partial / builder adapter (미래 axis A) 의 source.
- figma_to_html_agent/DISCUSSION-SUMMARY-20260411.md — 변환 설계 회의 기록.
- figma_to_html_agent/HARNESS.md — 변환 검증 harness.
- figma_to_html_agent/scripts/fetch_figma_screenshots.py — Figma 스크린샷 자동 수집.

수정:
- figma_to_html_agent/PROCESS-CONTROL.md / PROCESS.md / RULES.md —
  변환 프로세스 / 룰 갱신 (R8/R9 lock 강화 등).
- figma_to_html_agent/blocks_index.md — 32 frame 인덱스 갱신.

Phase Z 영향 0 (figma_to_html_agent/blocks/ 가 V4 catalog +
templates/phase_z2/families adapter 의 source — runtime 에서 직접 import X).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:41:05 +09:00

20 KiB
Raw Blame History

변환 절차 (10 STEP)

Figma 프레임 1개를 HTML+template으로 변환할 때 매번 동일하게 따르는 운영 핸드북.

원칙: 같은 세션에서 여러 프레임 연속 작업 OK. 컨텍스트가 무거워지면 /compact 로 정리하고 계속 진행. 핵심 결정/규칙/산출물은 모두 파일에 박혀있어 compact 후에도 보존됨. 이렇게 해야 누적 학습(R13 등 sub-pattern)이 즉시 적용됨. CLAUDE.md 원칙 7 참조.

원칙: 1 프레임 변환 = 1:1 reference + 템플릿 동시 작성. 1번째 등장이라도 templates_staging/{pattern}.html.j2 + meta.yaml + example.yaml 까지 작성한다. 정적 HTML만 두는 것은 work-creating-work. 사용자가 final 검수 후 design_agent/templates/ 로 직접 프로모션.


STEP 0 — 준비

0-A. 에이전트: blocks_index.md 한 번 읽기 (필수)

새 세션은 메모리가 없다. 패턴 발견 트리거(2번째 등장)가 작동하려면 세션 시작 직후 blocks_index.md를 한 번 통으로 읽어야 한다.

Read figma_to_html_agent/blocks_index.md

확인할 것:

  • "변환 완료 (현행 방법론)" 섹션의 패턴 목록
  • "패턴 카탈로그" 섹션의 등록 패턴 (등장 횟수)
  • "templates_staging 대기열" 의 진행 중 패턴

0-B. 사용자: 프레임 선택

  1. Figma desktop에서 변환할 프레임을 선택 (클릭) 한다
  2. 에이전트에게 "이 프레임 변환해줘"라고 알린다 (프레임 ID/이름 명시 권장)

0-C. 에이전트: 패턴 비교

STEP 1~3로 metadata + screenshot 받은 직후, 0-A에서 본 인덱스와 비교:

  • 비슷한 구조 발견 → "이거 X 패턴과 비슷합니다. 두 번째 등장이면 templates_staging/ 로 Jinja2 추출 진행할까요?" 사용자에게 확인
  • 비슷한 게 없음 → 일반 STEP 4 이하 진행

확인사항:

  • Figma desktop 앱이 활성 탭에 올바른 파일이 떠 있는가
  • .mcp.json에 figma-desktop SSE 서버가 등록돼있는가 (http://127.0.0.1:3845/sse)

STEP 1~3 — 데이터 수집 (병렬)

세 도구를 단일 메시지에 multiple tool_use 블록으로 동시 호출한다 (도구 호출 단위 병렬). 순차 호출하면 같은 노드 ID를 두 번 추출하느라 토큰만 낭비됨.

[single message, multiple tool_use blocks]
1. mcp__figma-desktop__get_metadata        nodeId="" (현재 선택 노드)
2. mcp__figma-desktop__get_design_context  nodeId="" (현재 선택 노드)
3. mcp__figma-desktop__get_screenshot      nodeId="" (현재 선택 노드)

주의: nodeId를 비우면 현재 선택 노드를 사용하므로 metadata 응답을 기다릴 필요 없음. 셋 다 동시에 갈 수 있다.

도구 얻는 것 사용처
get_metadata 모든 leaf 노드의 id, type, name, x, y, width, height (XML) bottom-up 플래튼
get_design_context gradient/filter/font/color (React+Tailwind 코드) CSS 변환
get_screenshot Figma가 렌더한 PNG STEP 8 사람 눈 검증

주의:

  • get_metadata 응답이 100KB+ 면 frame이 너무 커서 자르지 않은 상태. 사용자에게 더 작은 단위 선택 요청
  • get_design_context는 응답이 매우 크므로 한 프레임당 1회만 호출

STEP 4 — 자산 정리 (block-tests/assets/shared/ 캐시)

design_context에서 localhost:3845/assets/{hash}.png|svg 패턴의 자산 URL 추출.

각 자산에 대해:

  1. URL 끝의 hash를 파일명으로 사용
  2. block-tests/assets/shared/{hash}.{ext}이미 있으면 다운로드 스킵
  3. 없으면 curl로 다운로드
cd block-tests/assets/shared
for url in $URLS; do
  hash=$(basename "$url")
  [ -f "$hash" ] || curl -sSo "$hash" "$url"
done

HTML에서 참조 시:

<img src="assets/shared/{hash}.png">

(block-tests/{slug}.html 기준으로 상대 경로 assets/shared/)

효과:

  • 동일 자산이 여러 프레임에서 등장해도 한 번만 다운로드 (해시 파일명이라 자동 dedup)
  • 후속 프레임 변환 시간 단축
  • 토큰 절약 (이미 있는지 확인만)

프레임 매핑 메모: block-tests/{slug}_assets.txt에 사용한 hash 목록 + 의미 라벨 기록 → 추후 재추출 시 빠른 매핑

# bim-goals-3circles_assets.txt
84965807....png   bg_texture
f05ebf15....png   arc_top
2f0f1750....png   arc_side

legacy: 이전에 다운로드한 자산이 block-tests/assets/frame_{id}/ 에 있다면 그대로 두되, 새 변환부터는 shared/ 만 사용한다.


STEP 5 — 산출물 3종 작성 (flat.md + texts.md + index.html)

산출물 폴더 구조 (프레임별)

blocks/{frame_id}/
├── index.html        ← ① HTML 블록 (CSS gradient, transform: scale)
├── flat.md           ← ② 실측 기록 (모든 노드 좌표/크기/속성)
└── texts.md          ← ③ TF-IDF용 텍스트 (모든 텍스트 빠짐없이)

texts.md 작성 규칙:

  • MCP get_design_context에서 모든 텍스트 노드의 텍스트를 추출
  • 노드 계층 설명이 아니라 텍스트만 깨끗하게 나열
  • 빠짐없이. "간략하게" 금지. flat.md에서 TEXT 타입 노드를 전수 추출
  • 섹션별로 그룹핑 (열1, 열2, 열3 등)
  • TF-IDF 인덱스 구축 시 이 파일을 직접 사용

flat.md 작성 규칙

blocks/{frame_id}/flat.md 파일 생성. 다음 섹션을 반드시 포함:

섹션 1. 메타

# Frame {ID} — {이름}

> 원본: {W} × {H} px (node {ID})
> Scale: × {S} → {1280} × {H×S} px
> 슬라이드 16:9 안 배치

섹션 2. 계층 경로 (bottom-up)

모든 leaf 노드를 들여쓰기 트리로 표현. 그룹별 누적 offset 표시.

Frame {root} ({W}×{H})
├─ Group "X" (offset → 누적)
│   ├─ TEXT "..." (abs_x, abs_y) {w}×{h}
│   └─ ...

섹션 3. 이상 탐지 결과

검사 결과
회전 단일문자 (bbox 가로 > 세로 × 1.5) 발견 노드 ID 또는 "없음"
좁은 박스 세로 텍스트 (width < fontSize × 0.8) ...
중복 노드 (동일 좌표 + 동일 내용) ...
Vector 좌표 metadata vs design_context 불일치 ... (있으면 어느 쪽 신뢰)

섹션 4. 변형 가능 축 메모 + 슬롯 옵션

이 블록을 템플릿화한다면 무엇이 파라미터가 될지 1~5줄로. 각 슬롯이 required인지 optional인지 표시:

## 변형 가능 축
- columns[N=2~4] (required)
  - badge (required)
  - bullet_items[1~12] (required)
  - bg_image (required)
  - bottom_photo (optional)  ← 사진 없는 mdx도 이 블록 매칭 가능
- color_palette[N] (required, N과 일치)

이 메모가 STEP 10의 blocks_index.md 요약 + 향후 templates_staging meta.yaml 의 초안.

섹션 5. Sub-pattern 식별 (재사용 가능한 atomic 단위)

이 블록 안에 다른 블록과 공유 가능한 sub-pattern이 있는가? RULES.md R13~ 참조.

## Sub-patterns
- `bullet-list-with-marker` (R13) — 각 텍스트 앞에 장식 마커
  - 위치: 각 컬럼 본문 영역
  - 마커: checkbox PNG
  - 적용 구조: .bullet-list / .bullet-row / .bullet-icon / .bullet-text

Sub-pattern을 즉시 RULES.md에 등록할 필요는 없다. 동일 sub-pattern이 2번째 등장하면 그때 R번호 부여해서 정식 등록.


STEP 6 — 그라데이션 수학 변환

각 SVG <linearGradient> 데이터를 scripts/gradient_math.py로 CSS로 변환.

python scripts/gradient_math.py \
  --w 350 --h 350 \
  --x1 110.833 --y1 18.2292 --x2 219.479 --y2 175 \
  --stops "0:#FDC69E,1:#E0782C"

출력:

linear-gradient(145.28deg, #FDC69E 16.04%, #E0782C 55.20%)

수학 원리는 MATH.md §2 참조.

여러 그라데이션을 한 번에 변환할 땐 Python 인라인 스크립트 사용:

import sys, os
# scripts/ 디렉토리를 sys.path에 명시 추가 (작업 디렉토리 무관)
sys.path.insert(0, os.path.join('figma_to_html_agent', 'scripts'))
from gradient_math import svg_to_css

svg_to_css(W=350, H=350, x1=110.833, y1=18.2292, x2=219.479, y2=175,
           stops=[(0, '#FDC69E'), (1, '#E0782C')])

작업 디렉토리가 figma_to_html_agent/ 인 경우:

import sys; sys.path.insert(0, 'scripts')
from gradient_math import svg_to_css

또는 정식 패키지로 사용 (scripts/__init__.py 가 있으므로):

# 작업 디렉토리가 figma_to_html_agent/ 일 때
from scripts.gradient_math import svg_to_css

⚠️ 금지: 함수 코드를 인라인 Python에 복사 붙여넣기. 한 번 만든 gradient_math.py를 항상 import해서 쓴다. 복사하면 버그 수정 시 여러 곳을 동시에 고쳐야 하고 수식이 미세하게 어긋날 위험.


STEP 7 — HTML 작성

7-A. 기본 구조

<div class="slide">             <!-- 1280×720 흰색 -->
  <div class="block">           <!-- 1280 × (H×S) -->
    <div class="inner">         <!-- 원본 W×H, transform: scale(S) -->
      ... 모든 요소 (Figma 원본 좌표 사용) ...
    </div>
  </div>
</div>
.inner {
  position: absolute;
  left: 0; top: 0;
  width: {W}px; height: {H}px;
  transform: scale({S});
  transform-origin: top left;
}

왜 transform: scale을 쓰는가: 모든 위치/크기/폰트/그림자/스트로크가 한 번의 transform으로 균일하게 축소됨. 매 값을 수동으로 ×S 곱하는 것보다 안전하고 검증 가능. (MATH.md §1)

7-A1. 콘텐츠 주도형 구조 (R17/R19, 권장)

텍스트 분량 변화에 유연한 블록을 만들 때는 아래 구조를 사용한다:

<div class="slide">
  <div class="block">
    <div class="inner">  <!-- zoom: S (transform:scale 대신, R19) -->
      <div class="title">...</div>
      <div class="rows">  <!-- flex column: 행 간 자연 flow -->
        <section class="row">  <!-- flex column: pill → body → pill -->
          <div class="pill-area">...</div>
          <div class="body-area">  <!-- flex row: left | divider | right -->
            <div class="body-left">텍스트 (자연 flow)</div>
            <div class="divider"></div>
            <div class="body-right">텍스트 (자연 flow)</div>
          </div>
        </section>
      </div>
    </div>
  </div>
</div>
.inner { width: {W}px; zoom: {S}; }
.rows { display: flex; flex-direction: column; }
.row { display: flex; flex-direction: column; }
.body-area { flex: 1; display: flex; }
.body-left, .body-right { flex: 1; }

핵심 원칙:

  • 본문 텍스트에 absolute 금지 → flex/flow로 자연 배치
  • 텍스트 늘면 body-area가 늘고, 아래 행이 밀림
  • overflow:hidden으로 텍스트 숨기기 절대 금지
  • pill 장식은 section 기준 상대 배치 (콘텐츠를 따름)
  • pill의 crop variant와 label position은 분리 (R18)

사용 시점: 텍스트 분량이 가변적인 블록 (비교표, 이슈 목록, 불릿 리스트 등)

7-A2. MCP 데이터 → HTML 1:1 변환 원칙 (PROCESS-CONTROL 규칙 8)

MCP get_design_context 응답의 React+Tailwind 코드를 HTML/CSS로 변환할 때:

  1. 모든 요소를 빠짐없이 반영 — "핵심만", "단순화" 금지
  2. 래퍼 구조 유지 — MCP에 flex container + rotate 래퍼가 있으면 HTML에도 동일 구조
  3. 시각 속성 절대 생략 금지:
    • mix-blend-mode: multiply → CSS에 반드시 포함
    • opacity-80, opacity-70 → CSS opacity 반드시 포함
    • transform: rotate(), scaleY() → 래퍼 div 구조로
    • border-radius, inset → Figma 값 그대로
    • background-image (gradient) → gradient_math.py 변환 또는 MCP 값 직접 사용
    • box-shadow, text-shadow, letter-spacing → 값 그대로
  4. 검증: 변환 후 MCP 응답의 CSS 속성 수 vs index.html CSS 속성 수 대조

변환 순서:

MCP className/style 읽기
  → 각 속성을 CSS로 매핑
  → position: absolute + MCP 좌표(left, top, width, height)
  → 래퍼가 있으면 래퍼 div 생성 + transform 적용
  → 시각 속성 (blend, opacity, radius, shadow) 전부 추가
  → 텍스트 내용 삽입
  → 완성 후 MCP 원본과 속성 수 대조

7-B. 요소 변환 우선순위

요소 종류 구현 방법 이유
원/사각형 + gradient + blend HTML div + border-radius + linear-gradient + mix-blend-mode: multiply 동적 재구성 위해
Stroke (경계선) border: Npx solid color + box-sizing: border-box gradient와 함께 사용 가능
Drop shadow blur box-shadow: 0 0 {2×stdDev}px {color} SVG feGaussianBlur 근사
곡선 (아크, 비원형) SVG <path> 또는 미리 export된 PNG CSS 불가능
텍스트 HTML <div> 절대 배치 선택 가능, 접근성
실사 이미지 <img> PNG 재현 불가
회전된 도형 래퍼 div + transform: rotate() (INSIGHT-GRADIENT.md) gradient 동시 회전

7-C. 보정 규칙

RULES.md R1~R16 모두 적용:

  • R1: descender padding-bottom
  • R2~R3: 회전/세로 텍스트
  • R4: 그라데이션 텍스트
  • R5: 다중 fills
  • R6: 중복 노드
  • R7: 흰 배경
  • R8: 스케일 팩터
  • R9: 순수 CSS 우선
  • R10: blend mode 호환
  • R11: stroke 정렬 (inside/outside)
  • R12: viewBox padding

STEP 8 — Selenium 렌더링 + 사람 눈 검증

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from PIL import Image
import os, time

# _renders/ 폴더 없으면 생성
os.makedirs('block-tests/_renders', exist_ok=True)

opts = Options()
opts.add_argument('--headless=new')
opts.add_argument('--hide-scrollbars')
opts.add_argument('--force-device-scale-factor=1')
opts.add_argument('--window-size=1600,900')
d = webdriver.Chrome(options=opts)

p = os.path.abspath('block-tests/{slug}.html').replace('\\','/')
d.get('file:///' + p)
time.sleep(1.5)
d.save_screenshot('block-tests/_renders/{slug}_full.png')

r = d.execute_script(
  'const r=document.querySelector(".slide").getBoundingClientRect();'
  'return [r.x,r.y,r.width,r.height];'
)
Image.open('block-tests/_renders/{slug}_full.png').crop(
  (int(r[0]), int(r[1]), int(r[0]+r[2]), int(r[1]+r[3]))
).save('block-tests/_renders/{slug}.png')
d.quit()

검증 방식:

  • 자동 픽셀 diff는 하지 않음 (font 렌더 차이로 노이즈만 많음)
  • Figma get_screenshot 응답과 Selenium 결과를 사람 눈으로 비교
  • 차이 발견 시 STEP 5~7로 돌아가서 원인 파악 (값 수정 금지)

STEP 8-B. 전수 대조 검증 (필수, 다음 프레임 진행 전 반드시 통과)

원칙: "간략하게", "핵심만", "중요한 것만" — 전부 금지. 전수 처리.

프레임 완료 전 아래 3가지 검증을 모두 통과해야 다음 프레임으로 넘어갈 수 있다:

① texts.md 전수 검증:

  • MCP get_design_context의 모든 텍스트 노드를 texts.md와 1:1 대조
  • texts.md에 없는 텍스트 노드가 하나라도 있으면 실패
  • 검증 방법: MCP 응답에서 텍스트 추출 → texts.md와 diff

② flat.md 전수 검증:

  • MCP get_metadata의 모든 노드가 flat.md에 기록되어 있는지 대조
  • 좌표, 크기, 타입, 이름이 빠짐없이 기록되어야 함
  • "간략하게만 적었습니다" → 절대 금지. 실측 기록부에 간략은 없다

③ index.html 전수 검증:

  • texts.md의 모든 텍스트가 index.html에 존재하는지 대조
  • flat.md의 모든 시각 요소(bar, line, icon 등)가 index.html에 반영되어 있는지 대조
  • 하나라도 누락되면 실패

검증 실행 방법:

# texts.md vs index.html 교차 대조
texts.md의 각 줄 텍스트 → index.html에서 grep → 없으면 FAIL

# MCP 텍스트 노드 수 vs texts.md 항목 수
MCP 텍스트 노드: N개
texts.md 항목: M개
N == M 이어야 PASS

검증 미통과 시: 다음 프레임 진행 금지. 누락 항목 수정 후 재검증.


STEP 9 — 결과물 저장

block-tests/
├── {slug}.html              ← 변환물
├── {slug}_flat.md           ← 플래튼/이상/변형 축 메모
└── _renders/
    └── {slug}.png           ← 검증 스크린샷

{slug} 명명 규칙: 의미 기반 kebab-case (예: bim-goals-3circles, cards-3col-icon). 프레임 ID는 metadata로 추적 가능하므로 파일명에 넣지 않음.


STEP 10 — blocks_index.md 1줄 업데이트

blocks_index.md 끝에 한 줄 추가:

| {slug} | {프레임 ID} | {1줄 변형 축 요약} | {날짜} |

이 인덱스가 패턴 발견의 단서가 된다. 다음 변환 시작 전에 이 인덱스를 한 번 훑어서 "이미 비슷한 거 했나?" 확인.


패턴 → 템플릿화 (1번째부터 즉시)

규칙: 1번째 등장부터 templates_staging 작성. 정적 HTML만 두는 것 금지.

등장 횟수 처리
1번째 block-tests/{slug}.html (1:1 reference) + templates_staging/{pattern_id}.html.j2 (Jinja2 + meta.yaml + example.yaml) 함께 작성
2번째 기존 staging 템플릿이 새 데이터로 잘 렌더되는지 확인. 안 되면 템플릿 수정. example 추가.
3번째 이후 동일

왜 1번째부터 템플릿화하나?

  • 변환의 목적은 블록 라이브러리 구축, 단순 HTML 복제가 아님
  • 1:1 단계에서 발견한 인사이트(R13 등)를 즉시 템플릿에 반영해야 잊지 않음
  • 사용자가 검수할 때 "이게 블록으로 어떻게 작동할지" 즉시 확인 가능
  • 2번째 등장을 기다리면 사용자 수동 복제 작업이 누적됨 (work-creating-work)

Stage 2 산출물:

templates_staging/
├── {pattern_id}.html.j2     ← Jinja2 템플릿 본체
└── {pattern_id}.meta.yaml   ← when / slots / min_size_px / 변형 축 초안

여기까지가 에이전트 책임의 끝.


🚧 프로모션 게이트 (사용자 전용)

이 게이트 이후 작업은 에이전트가 절대 수행하지 않는다. 모든 design_agent/templates/ 변경은 사용자 본인이 직접 한다.

사용자가 수행할 작업

  1. 검수: templates_staging/{pattern_id}.html.j2 를 다양한 파라미터로 렌더 테스트
  2. 품질 게이트 통과 확인:
    • 1:1 변환물과 시각적으로 동일한가
    • 슬롯 파라미터를 바꿔도 깨지지 않는가 (원 4개, 라벨 0개 등 극단 케이스)
    • meta.yaml의 when/slots가 design_agent의 다른 블록과 충돌 없는가
  3. 이동: templates_staging/{pattern_id}.html.j2design_agent/templates/blocks/{category}/
  4. 등록: design_agent/templates/catalog.yaml 에 when/slots/min_size_px 추가
  5. 상태 업데이트: blocks_index.md 의 해당 행 상태 → promoted

에이전트의 역할

  • staging 작성까지만
  • 사용자 요청 없이 design_agent/templates/ 를 절대 읽거나 쓰지 않음
  • "templates/ 에 옮겨드릴까요?" 같은 제안 금지 (월권)
  • 사용자가 명시적으로 "이 staging 결과 검토해줘"라고 요청하면 → staging 폴더 내에서만 검토

안티 패턴 (하지 말 것)

하지 말 것 이유
사전에 인벤토리/지문/군집 단계 work-creating-work, 패턴은 변환하면서 발견됨
1번째 등장은 정적 HTML로만 두기 (templates_staging 미작성) work-creating-work, 인사이트 잊혀짐. 1번째부터 템플릿 작성
컨텍스트 차면 강제 새 세션 compact 사용. 핵심 결정은 모두 파일에 박혀있어 손실 없음
Figma 데이터 안 보고 멀티모달 이미지로 추측 미묘한 alpha/blend에서 틀림
"여기 1px 어색하니 다른 곳도 같이 바꾸자" 사용자 피드백만 정확히 반영
같은 자산을 매번 새로 다운로드 block-tests/assets/shared/ 캐시 활용
그라데이션 각도/색을 눈대중으로 gradient_math.py로 수학 도출
gradient_math.py 함수 코드 인라인 복사 import만 한다. 복사하면 수식 어긋남
세션 시작에 blocks_index.md 안 읽음 패턴 발견 트리거 영영 작동 안 함
design_agent/templates/ 직접 수정 프로모션은 사용자 전용. 에이전트는 staging까지만
"templates/ 옮겨드릴까요?" 제안 월권. 사용자가 알아서 함
prerequisites-3col.html 을 신규 변환 레퍼런스로 사용 구 방법론 (R8/R9 미적용). legacy 표시됨