1515 changed files with 4645 additions and 58250 deletions
-134
View File
@@ -1,134 +0,0 @@
{
"permissions": {
"allow": [
"Bash(cmd /c \"npx -y figma-developer-mcp --help\")",
"Bash(npx -y figma-developer-mcp --version)",
"mcp__Framelink_Figma_MCP__get_figma_data",
"mcp__Framelink_Figma_MCP__download_figma_images",
"Bash(start \"\" \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/prerequisites-3col.html\")",
"Bash(python -c \"from selenium import webdriver; print\\('selenium OK'\\)\")",
"Bash(python -c ':*)",
"Bash(python)",
"Bash(start \"\" \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/bim-goal-circles.html\")",
"Bash(start \"\" \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/bg-shapes-only.html\")",
"Bash(start \"\" \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/bim-figma-devmode.html\")",
"Bash(claude mcp:*)",
"Bash(curl -sS -o /dev/null -w \"mcp endpoint: HTTP %{http_code}\\\\n\" http://127.0.0.1:3845/mcp)",
"Bash(curl -sS -o /dev/null -w \"sse endpoint: HTTP %{http_code}\\\\n\" http://127.0.0.1:3845/sse)",
"Bash(curl -sS -o /dev/null -w \"root: HTTP %{http_code}\\\\n\" http://127.0.0.1:3845/)",
"Bash(curl -s -o NUL -w \"%{http_code}\" http://127.0.0.1:3845/mcp)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:3845/sse --max-time 3)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:3845/mcp --max-time 3)",
"Bash(curl -s -X POST http://127.0.0.1:3845/mcp -H \"Content-Type: application/json\" -H \"Accept: application/json, text/event-stream\" -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0\"}}}' --max-time 5)",
"Bash(curl -v http://127.0.0.1:3845/mcp)",
"Bash(curl -s -m 3 http://127.0.0.1:3845/sse)",
"Bash(curl -s -m 3 -o /dev/null -w \"%{http_code}\\\\n\" http://127.0.0.1:3845/sse)",
"mcp__figma-desktop__get_metadata",
"mcp__figma-desktop__get_screenshot",
"mcp__figma-desktop__get_design_context",
"Bash(curl -sSo bg_texture.png \"http://localhost:3845/assets/849658071be46a26936e8666e3722b2dd548aee7.png\")",
"Bash(curl -sSo arc_top.png \"http://localhost:3845/assets/f05ebf15a1125b6c5809f9ffa35b4e4e750687d3.png\")",
"Bash(curl -sSo arc_side.png \"http://localhost:3845/assets/2f0f17507c681b7bc2fb109f3d4fafb9ff2f7ce0.png\")",
"Bash(curl -sSo big_fill_productivity.svg \"http://localhost:3845/assets/21a89b8138bd47debcc6f12bb140ee63bbd9fdf2.svg\")",
"Bash(curl -sSo big_ring_productivity.svg \"http://localhost:3845/assets/fbe84134d2e14bbf84b2c42516e9b85ffe6f7c1e.svg\")",
"Bash(curl -sSo big_fill_safety.svg \"http://localhost:3845/assets/1f24875931dc3c36e2c841eaf5b94466fa035a48.svg\")",
"Bash(curl -sSo big_ring_safety.svg \"http://localhost:3845/assets/c5aeccdfc884051848fc60f04abf2a9d367dd731.svg\")",
"Bash(curl -sSo big_fill_trust.svg \"http://localhost:3845/assets/67ef527c29921d401d31032c02d6b3a0ae1d3050.svg\")",
"Bash(curl -sSo acc_outer_speed.svg \"http://localhost:3845/assets/1391787caa4cb8241a1adadbb2c70aed3625e1b8.svg\")",
"Bash(curl -sSo acc_inner_speed.svg \"http://localhost:3845/assets/eeb8e9bf8b1841215ae0253017512a7e4a6d5a95.svg\")",
"Bash(curl -sSo acc_inner_profit.svg \"http://localhost:3845/assets/4885055cba20f72e83401be371fe74b9b43ec869.svg\")",
"Bash(curl -sSo acc_outer_safety.svg \"http://localhost:3845/assets/688b5af1d813b16cd6410453e3d4d1f79c084222.svg\")",
"Bash(curl -sSo acc_inner_safety.svg \"http://localhost:3845/assets/2fab268821fc763dbdff12e1dd65820dfa9b628e.svg\")",
"Bash(ls block-tests/*.html block-tests/*.md)",
"Bash(python scripts/gradient_math.py --test)",
"Bash(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\")",
"Bash(python -c \"from scripts.gradient_math import svg_to_css; print\\(svg_to_css\\(W=350,H=350,x1=110.833,y1=18.2292,x2=219.479,y2=175,stops=[\\(0,'#FDC69E'\\),\\(1,'#E0782C'\\)]\\)\\)\")",
"Bash(python render.py cards-3col-persona example)",
"Bash(python render.py cards-3col-persona example-no-photos)",
"Bash(python render.py cycle-3way-intersect example)",
"WebFetch(domain:claude.com)",
"WebFetch(domain:help.figma.com)",
"Bash(curl -sSo \"527bd7809f4b2e5f3cd42f2e713ccbfb37537d82.png\" \"http://localhost:3845/assets/527bd7809f4b2e5f3cd42f2e713ccbfb37537d82.png\")",
"Bash(ls \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/_renders/pill_flex_\"*)",
"Bash(awk '/visual_diff:/{found=1} found && /^- id:/{print NR\": \"$0; found=0}' \"d:/ad-hoc/kei/design_agent/templates/catalog.yaml\")",
"Bash(curl -s -o \"bg_slide_texture.png\" \"http://localhost:3845/assets/16a1b2ea5b64663a3ee44bfad24671a612952c29.png\")",
"Bash(curl -s -o \"line_divider.svg\" \"http://localhost:3845/assets/01731a60f7d9d35816932c019149e301a3aae1a7.svg\")",
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/01*.mdx)",
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/02*.mdx)",
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/03*.mdx)",
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260413_*/)",
"Bash(python -c \"from src.config import settings; print\\(f'API configured: {bool\\(settings.anthropic_api_key\\)}'\\)\")",
"Bash(python run_test.py)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/health)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/)",
"Read(//d/ad-hoc/kei/**)",
"Bash(curl -s http://localhost:8000/docs)",
"Bash(taskkill //F //IM python.exe)",
"Bash(python assemble_mdx02_test.py)",
"Bash(wc -c data/runs/20260407_*/final.html)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/docs)",
"Bash(curl -s http://localhost:8080/docs -o /dev/null -w \"%{http_code}\")",
"Bash(curl -s http://localhost:8001/docs -o /dev/null -w \"%{http_code}\")",
"Bash(curl -s http://localhost:3000/ -o /dev/null -w \"%{http_code}\")",
"Bash(python assemble_mdx02_v3.py)",
"Bash(python assemble_mdx02_v4.py)",
"Bash(python assemble_mdx02_v5.py)",
"Bash(python assemble_mdx02_v6.py)",
"Bash(python assemble_mdx02_v7.py)",
"Bash(python assemble_mdx02_v8_3plans.py)",
"Bash(python assemble_mdx02_v9.py)",
"Bash(python build_plan1.py)",
"Bash(python build_plan2.py)",
"Bash(python build_plan3.py)",
"Bash(python build_plan1_v2.py)",
"Bash(python build_4plans_final.py)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:8000/docs)",
"Bash(uvicorn backend.main:app --port 8000)",
"Bash(python final_plan1.py)",
"Bash(python final_plan2.py)",
"Bash(python build_all_4plans.py)",
"Bash(python build_plan3_kei.py)",
"Bash(python build_plan4_kei.py)",
"Bash(python make_4plans.py)",
"Bash(ls -la \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/html_render_final\"*)",
"Bash(taskkill //PID 48540 //F)",
"Bash(python make_mdx03.py)",
"Bash(python run_mdx03_pipeline.py)",
"Bash(powershell -Command \"Get-Process python -ErrorAction SilentlyContinue | Select-Object Id,StartTime\")",
"Bash(grep \"class FontHierarchy\" src/*.py)",
"Bash(ls -ltr /d/ad-hoc/kei/design_agent/data/runs/*/step_*_context.json)",
"Bash(awk '{print $2}')",
"Bash(stat /d/ad-hoc/kei/design_agent/data/runs/20260414_120225/stage_*_context.json)",
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260414_120225/stage_*_context.json)",
"Bash(awk '{print $6, $7, $8, $9}')",
"Bash(ls -lt data/runs/20260414_120225/*_context.json)",
"Bash(awk '{print $6,$7,$8,$9}')",
"Bash(python -c \" import yaml with open\\('catalog.yaml'\\) as f: data = yaml.safe_load\\(f\\) blocks = data.get\\('blocks', []\\) for b in blocks: print\\(f\\\\\"{b['id']} | {b.get\\('category',''\\)} | items:{b.get\\('min_items','?'\\)}-{b.get\\('max_items','?'\\)}\\\\\"\\) \")",
"Bash(python add_tags.py)",
"Bash(python -c \"import src.block_reference; print\\('OK'\\)\")",
"Bash(python -c \"import src.block_assembler; print\\('OK'\\)\")",
"Bash(ls -t d:/ad-hoc/kei/design_agent/docs/history/PHASE-*.md d:/ad-hoc/kei/design_agent/docs/history/IMPROVEMENT-PHASE-*.md)",
"Bash(python -c \"import src.pipeline_context; import src.kei_client; import src.pipeline; print\\('모든 import OK'\\)\")",
"Bash(python -c \"import src.step_visualizer; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.block_assembler; print\\('OK'\\)\")",
"Bash(python -c \"import src.block_assembler; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.kei_client; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.pipeline_context; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.validators; print\\('OK'\\)\")",
"Bash(python -c \"import src.validators; print\\('OK'\\)\")",
"Bash(python -c \"import src.validators; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.section_parser; import src.block_assembler; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.section_parser; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.space_allocator; import src.pipeline_context; print\\('OK'\\)\")",
"Bash(grep -l \"pp2-grid-wrap\\\\|pp2\" templates/blocks/**/*.html)",
"Bash(echo file:///D:/ad-hoc/kei/design_agent/data/runs/20260415_110323/final.html)",
"Bash(python -c \"import src.block_reference; import src.section_parser; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.block_assembler; import src.section_parser; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.block_reference; import src.pipeline; import src.block_assembler; print\\('OK'\\)\")"
],
"additionalDirectories": [
"d:\\ad-hoc\\kei\\design_agent\\templates\\blocks\\new"
]
}
}
+10 -3
View File
@@ -1,8 +1,15 @@
{
"mcpServers": {
"figma-desktop": {
"type": "sse",
"url": "http://127.0.0.1:3845/sse"
"Framelink Figma MCP": {
"command": "cmd",
"args": [
"/c",
"npx",
"-y",
"figma-developer-mcp",
"--figma-api-key=figd_s23TfSDL0hS97DIialy0R2P6QsoZQHfuGx1l_t-k",
"--stdio"
]
}
}
}
+1 -9
View File
@@ -1,12 +1,4 @@
# ⚠ DEPRECATED — 이 문서는 2026-03-27 기준 스냅샷입니다
> **최신 파이프라인 문서는 [`PIPELINE.md`](PIPELINE.md)를 참조하세요.**
> 이 문서는 Type A/B 분기, B'/B'' 변형, Stage 1.7/1.8 등 현재 구조를 반영하지 않습니다.
> 히스토리 참고용으로만 유지합니다.
---
# Design Agent 전체 구조 파악 리포트 (archived)
# Design Agent 전체 구조 파악 리포트
**작성일:** 2026-03-27
**목표:** design_agent의 아키텍처, 파이프라인, 코드 구조를 체계적으로 이해
+14 -111
View File
@@ -12,89 +12,10 @@
---
## 위계 + 용어 (Phase Z 정리, 2026-04-28)
> 매칭 시스템 (V1~V4) 통합 설계 시 정리. 상세는 [`IMPROVEMENT-REDESIGN.md`](IMPROVEMENT-REDESIGN.md) 4 장 참조.
>
> Phase Z 사전 작업 — Frame Zone 적용 분류 [`docs/architecture/FRAME-INTEGRATION-MAP.md`](docs/architecture/FRAME-INTEGRATION-MAP.md), Frame / Style / Token 인벤토리 [`docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md`](docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md).
### 슬라이드 위계
```
[ slide ] 1280×720 (전체 슬라이드)
├─ slide-title ← MDX 대목차 제목 (자동 매핑)
├─ slide-divider (고정)
├─ slide-body ≈ 1200×590 (콘텐츠 영역 — `templates/blocks/slide-base.html`)
│ │
│ └─ 레이아웃 (8-preset layout vocabulary; legacy hint = Type A/B/B'/B'')
│ │
│ └─ Zone (top / bottom_l / bottom_r 등)
│ │
│ └─ 프레임 (Figma 디자인 단위)
└─ slide-footer ← MDX 대목차 결론 (자동 매핑)
```
### 용어
| 용어 | 의미 |
|---|---|
| **슬라이드** | 1280×720 한 장 |
| **slide-base** | 모든 슬라이드 공통 그릇 (배경 + 제목 + 구분선 + 결론 pill) — `templates/blocks/slide-base.html` |
| **slide-body** | 본문 가용 영역 (≈ 1200×590) |
| **레이아웃 (Layout)** | 8-preset layout vocabulary — slide-body 안 zone 분배 형태 (legacy hint = Type A/B/B'/B'') |
| **Zone (영역)** | 레이아웃이 결정한 콘텐츠 구역 |
| **Internal Region** | Zone 안의 placement / planning unit (Layer A) — content_object 를 배치하는 내부 영역 (SPEC v1 §2) |
| **컨테이너 (Container)** | zone 의 px 명세 (코드 레벨) |
| **프레임 (Frame)** | Figma 디자인 단위 (= 기존 "블록") — zone 안 |
| **Frame Slot** | Frame 안의 declared placement slot (Layer B) — frame contract 가 선언하는 콘텐츠 placement target (SPEC v1 §3) |
| **Layer A** | composition planning 의 zone-내 layer — content_object 를 Internal Region 으로 배치하는 단위 (SPEC v1 §1~§2) |
| **Layer B** | frame contract 기반 frame-내 layer — 콘텐츠 매핑 대상인 Frame Slot 구조를 선언하는 단위 (SPEC v1 §3) |
### MDX → 슬라이드 매핑
| MDX 위치 | 슬라이드 위치 |
|---|---|
| `# 대목차 제목` | `slide-title` |
| 본문 (`##` / `###`) | `slide-body` 안 (레이아웃 + zone + 프레임) |
| `# 대목차 결론` | `slide-footer` |
| `<details>` 팝업 | 슬라이드 위 별도 레이어 |
Layer A planning telemetry is active in trace-only mode; render path activation remains the next axis.
---
## 아키텍처 — Phase Z 흐름 (5 단계)
> 상세는 [`IMPROVEMENT-REDESIGN.md`](IMPROVEMENT-REDESIGN.md) 5 장 참조.
```
STAGE 1) MDX 분석 + 레이아웃 매칭 (8-preset layout vocabulary; legacy = Type A/B/B'/B'')
STAGE 2) Zone 별 텍스트 1차 배치
STAGE 3) Zone 별 프레임 매칭 (V1~V4; B4 frame_selection evidence integration pending)
├ 매칭 완벽 → 텍스트 업데이트
├ 매칭 어정쩡 → 디자인 참고 재구성
└ 매칭 안 됨 → 디자인 컨셉 바탕 재구성
STAGE 4) 프레임 검토 + 컨테이너 조정 (5 차 Fallback)
STAGE 5) HTML 조립 + 검증 + 출력
```
핵심 원칙 :
- **MDX 1 파일 = 슬라이드 1 장** (절대 분할 X)
- **텍스트 원문 무손실 보존** (본문 미리보기 + 팝업 원문)
- **자유 디자인 금지** (항상 Figma 프레임 DB 참고)
- **불일치 시 레이아웃 회귀** (콘텐츠 줄이지 않고 그릇 변경)
---
## 아키텍처 (Phase Q 파이프라인 — 기존)
## 아키텍처 (Phase Q 파이프라인)
> Phase P(다후보 렌더링 비교) 실행 결과 20/100점. 업계 조사(Beautiful.ai, Napkin.ai, VASCAR 등) 기반으로 Phase Q에서 재설계.
> 핵심 전환: "계산 먼저, AI 판단 나중에, 렌더링은 검증만"
>
> ⚠️ Phase Z (매칭 시스템 통합) 진행 후 이 섹션의 일부는 새 흐름으로 대체될 예정.
```
[1단계] Kei 실장 (Opus) — AI 사고
@@ -144,48 +65,30 @@ reference 꼭지 있음 → sidebar-right
나머지 → single-column
```
### 역할 분리 (Phase Z)
> ⚠️ Phase R' 의 역할 분리 (AI 가 HTML 구조 직접 생성) 는 **Phase Z 에서 폐기**. 아래는 Phase Z 기준.
### 역할 분리 (Phase R')
| 역할 | 담당 | 방식 | 하는 일 | 하지 않는 일 |
|------|------|------|---------|------------|
| Kei 실장 | Opus (Kei API) | AI | MDX 본문 분석 보조, 최종 검수 | HTML 생성, 레이아웃 계산 |
| 레이아웃 결정 | 코드 | 룰 매칭 | MDX 콘텐츠 → 8-preset layout vocabulary 기반 slide/zone topology 선택 (legacy hint = Type A/B/B'/B'') | — |
| 컨테이너 계산 | 코드 | 결정론적 | zone 별 px 확정 (`space_allocator`) | — |
| 프레임 매칭 | 코드 (V1~V4; B4 frame_selection evidence integration pending) | 결정론적 | zone ↔ Figma 프레임 매칭 | — |
| **콘텐츠 매핑** | **코드 + AI** | **하이브리드** | **zone 안 콘텐츠 / 텍스트를 프레임 슬롯에 배치 / 다듬기 / 변형** | **HTML 구조 생성 X** |
| HTML 조립 | 코드 (Jinja2) | 결정론적 | `slide-base` + 프레임 + 슬롯 콘텐츠 합쳐서 final.html | AI 호출 X |
| 검증 | 코드 + AI | Selenium + Vision | overflow 측정, 시각 품질 평가 | — |
| Kei 실장 | Opus (Kei API) | AI | 꼭지 추출, 비중 판단, relation_type + expression_hint 부여, 최종 검수 | HTML 생성, 레이아웃 계산 |
| 컨테이너 계산 | 코드 | 결정론적 | Kei 비중 → 역할별 컨테이너 px 확정 | AI 판단 불필요 |
| 프리셋 선택 | 코드 | 규칙 | 실장의 role에 따라 프리셋 자동 선택 | AI 판단 불필요 |
| **HTML 생성** | **AI (Kei API)** | **AI** | **콘텐츠 전달 의도에 맞는 HTML 구조를 직접 생성. 블록 CSS를 참고하되 구조는 AI가 결정.** | 블록 "선택" 안 함. 슬롯 "채우기" 안 함. |
| 검증 | 코드 + AI | Selenium + 비전 모델 | overflow 측정, 시각 품질 평가 | |
### HTML 생성 원칙 (Phase Z)
### HTML 생성 원칙 (Phase R')
```
블록이 구조를 결정 (P=Q=R, 실패)
콘텐츠가 구조를 결정 (R', 폐기 — AI 가 HTML 직접 생성하면 회귀 가능성 큼)
→ slide-base + 프레임 DB 가 구조를 결정, AI 는 zone 안 콘텐츠만 (Phase Z)
블록이 구조를 결정 (P=Q=R, 실패) → 콘텐츠가 구조를 결정 (R', 접근 C)
```
**Phase Z 의 핵심 원칙**:
- **HTML 구조** = `slide-base.html` + 코드 (Jinja2) 가 결정 (절대 AI 가 생성 X)
- **AI 의 역할** = zone 안 콘텐츠 매핑 / 텍스트 다듬기 / 디자인 변형 (콘텐츠 단위)
- **프레임 DB 참고 필수** — 자유 디자인 금지
- **MDX 원문 무손실 보존** (본문 preview, 팝업에 원문)
⚠️ **Phase R' 회귀 방지**: AI 한테 "HTML 구조 만들어줘" 같은 호출 절대 X. AI 호출은 zone 안 콘텐츠 단위로만.
- AI가 콘텐츠 전달 의도(expression_hint)에 맞는 HTML 구조를 직접 생성
- 기존 38개 블록의 CSS(색상, 폰트, 배경, radius)를 **스타일 참고**로 활용
- 블록을 **"선택"하지 않음**. topic 합침/분리, 포함 관계, 핵심 메시지 분리 가능
- 디자인 토큰(CSS 변수)으로 품질 제약 + Selenium 측정 + 비전 모델 검증
---
## 핵심 프로세스 (구 Phase Q 기준 — Phase Z 에서 대체됨)
> ⚠️ **아래 흐름은 구 Phase Q 기준의 5 단계 (Kei 실장 → 디자인 팀장 → 텍스트 편집자 → 디자인 실무자 → 재검토). Phase Z 에서 대체됨.**
>
> **신규 흐름은 위 "아키텍처 — Phase Z 흐름 (5 단계)" 섹션 또는 [IMPROVEMENT-REDESIGN.md](IMPROVEMENT-REDESIGN.md) 5 장 따름.**
>
> ⚠️ **특히 Phase Z 와 충돌하므로 무시할 것**:
> - "페이지 분리" → Phase Z: MDX 1 파일 = 슬라이드 1 장 (절대 분할 X)
> - "텍스트 압축 / 요약" → Phase Z: MDX 원문 무손실 보존 (본문 preview, 팝업 원문)
> - "AI 5 단계" → Phase Z: AI 호출은 zone 안 콘텐츠 단위로만 (HTML 구조 / 레이아웃 / 프리셋 결정 X)
## 핵심 프로세스
```
사용자 콘텐츠 입력 (텍스트/MDX 붙여넣기 또는 파일 업로드)
+189
View File
@@ -0,0 +1,189 @@
# Phase X: 템플릿 기반 동적 레이아웃
> 작성일: 2026-04-06
> 상태: 계획 수립
---
## 배경
현재 파이프라인은 모든 MDX를 "배경/본심/첨부/결론" 4칸에 억지로 끼워넣는다.
배경이 없는 콘텐츠도 배경을 만들어내고, 3분할이 적절한 콘텐츠도 2분할로 넣는다.
Kei가 내용을 잘못 파악한 게 아니라, **"4칸을 채워라"는 지시가 잘못**된 것이다.
## 핵심 아이디어
**미리 정의된 레이아웃 템플릿 중 Kei가 콘텐츠에 맞는 것을 선택한다.**
- Kei가 자유롭게 구조를 만드는 것이 아님 (불안정)
- 옵션을 주고 고르게 함 (안정적)
- 하드코딩 아님 — 어떤 MDX가 와도 적절한 템플릿이 선택됨
## 고정 영역
모든 템플릿 공통:
- **상단**: 슬라이드 제목 헤더 (항상 존재)
- **하단**: 결론 footer (항상 존재)
- **중간**: 템플릿에 따라 달라지는 영역
## 중간 영역 템플릿 옵션
### A. body + sidebar
```
┌──────────┬─────┐
│ 본심1 │참조 │
│ 본심2 │ │
└──────────┴─────┘
```
적합: 참조자료(용어 정의 등)가 별도로 있는 콘텐츠
예시: 01번 MDX (DX/BIM 용어 정립)
### B. 상단 wide + 하단 2분할
```
┌─────────────────┐
│ 강조/핵심 │
├────────┬────────┤
│ 항목1 │ 항목2 │
└────────┴────────┘
```
적합: 핵심 1개 + 두 가지 측면 비교/설명
예시: 03번 MDX (필수요건 + 과정혁신/결과변화)
### C. 상단 wide + 하단 3분할
```
┌─────────────────┐
│ 강조/핵심 │
├─────┬─────┬─────┤
│항목1│항목2│항목3 │
└─────┴─────┴─────┘
```
적합: 핵심 1개 + 세 가지 항목 병렬
예시: 02번 MDX (궁극적 목표 + 발주처/설계사/시공사)
### D. 2분할 (좌우 대등)
```
┌────────┬────────┐
│ 항목1 │ 항목2 │
│ │ │
└────────┴────────┘
```
적합: 두 가지 비교/대비
### E. 단일 전체
```
┌─────────────────┐
│ │
│ 본심 (전체) │
│ │
└─────────────────┘
```
적합: 하나의 흐름, 분할 불필요
### F. 상단 wide + 하단 2분할 + 보조 sidebar
```
┌─────────────┬───┐
│ 강조/핵심 │참조│
├──────┬──────┤ │
│항목1 │항목2 │ │
└──────┴──────┴───┘
```
적합: B + sidebar 조합
---
## 프로세스
```
1. Kei가 MDX 원본을 읽고 내용 분석
→ 핵심 메시지, 콘텐츠 구조 파악
2. Kei가 꼭지를 나눔 (개수와 역할명 자유)
→ "핵심목표 1개, 주체별 기대효과 3개, 결론 1개"
→ 역할명: 고정 4칸 아님. 콘텐츠에 맞는 이름 사용
3. Kei가 템플릿 선택
→ "꼭지 구조를 보니 C템플릿이 맞다"
→ 옵션 A~F 중 하나
4. Kei가 각 영역에 꼭지 배정 + weight
→ 상단: 핵심목표(0.3)
→ 하단좌: 발주처(0.2), 하단중: 설계사(0.2), 하단우: 시공사(0.2)
→ footer: 결론(0.1)
5. 파이프라인이 템플릿대로 컨테이너 생성 → BEFORE
6. 콘텐츠 채움 → 측정 → 재배분 → FILLED → AFTER
7. 조립 → code_assembled / final
```
---
## 작업 리스트
### X-1: 템플릿 정의
- 옵션 A~F의 구체적 컨테이너 구조 정의
- 각 옵션의 zone 이름, 비율 계산 공식, 좌표 계산 로직
- `src/design_director.py``LAYOUT_TEMPLATES` 정의
- 하드코딩 아님: 템플릿은 구조만 정의, 크기는 weight와 슬라이드 크기에서 동적 계산
### X-2: Kei 프롬프트 수정
- `KEI_PROMPT`에 템플릿 A~F 옵션 제시
- Kei가 콘텐츠를 보고 `layout: "C"` 선택
- page_structure의 역할명이 자유 (배경/본심 고정 아님)
- 각 역할에 zone 배정 (상단/하단좌/하단우 등)
- 하드코딩 아님: Kei가 콘텐츠마다 다른 선택을 함
### X-3: space_allocator 템플릿 기반 컨테이너 생성
- 선택된 템플릿에 따라 컨테이너 좌표/크기 생성
- weight 비율로 각 영역 크기 결정
- `select_preset()``build_layout_from_template()`
- 하드코딩 아님: 템플릿 구조 + weight + 슬라이드 크기로 동적 계산
### X-4: block_assembler / assemble_stage2 동적 역할
- `["배경", "본심", "첨부", "결론"]` 고정 루프 → `page_structure.keys()` 동적 루프
- 좌표 계산은 X-3에서 생성한 컨테이너 정보 사용
- 색상/폰트: 역할 수에 맞게 동적 배분
- 하드코딩 아님: 역할 수가 3개든 5개든 동작
### X-5: 나머지 파일 동적화
- step_visualizer: before/after 시각화에서 동적 역할 루프
- fit_verifier: 4역할 고정 → 동적 역할
- html_generator: Sonnet에게 동적 영역 수만큼 생성 요청
- renderer: 동적 grid-template 생성
- 하드코딩 아님: 모두 ctx.containers.keys() 기반
### X-6: 검증
- 01번 MDX → A템플릿 → 기존과 동일하거나 더 나은 결과
- 02번 MDX → C템플릿 → 상단 강조 + 하단 3분할
- 03번 MDX → B템플릿 → 상단 요건 + 하단 2분할
- 텍스트가 컨테이너 안에 있음
- 공란 최소
- 01번이 깨지면 롤백
---
## 주의사항
- 하드코딩 절대 금지: 특정 MDX에만 동작하는 코드 없음
- 01번 보호: Phase X 전에 git commit 완료 (1f7579c). 깨지면 롤백
- 점진적 진행: X-1 → X-2 후 Kei 응답 확인 → X-3~X-5 순차 진행
- 각 단계마다 검증
---
## 관련 코드 (고정 역할 참조 현황)
| 파일 | 참조 수 | 수정 범위 |
|------|---------|----------|
| src/html_generator.py | 54건 | X-5 |
| src/step_visualizer.py | 32건 | X-5 |
| src/space_allocator.py | 26건 | X-3 |
| scripts/assemble_stage2.py | 26건 | X-4 |
| src/kei_client.py | 18건 | X-2 |
| src/block_assembler.py | 17건 | X-4 |
| src/fit_verifier.py | 16건 | X-5 |
| src/pipeline.py | 15건 | X-3~X-5 |
| src/renderer.py | 7건 | X-5 |
| src/pipeline_context.py | 4건 | 필요 시 |
| **합계** | **215건** | |
-304
View File
@@ -1,304 +0,0 @@
# Design Agent 파이프라인 현황
> **최종 갱신:** 2026-04-13
> **목적:** 새 세션의 AI가 이 문서만 읽으면 파이프라인 전체를 이해하고 작업할 수 있도록 한다.
---
## 1. 전체 흐름 요약
```
MDX 입력
[Stage 0] MDX 정규화 (코드)
[Stage 1A] Kei 실장 — 꼭지 추출 (AI: Opus)
→ layout_template: A 또는 B 선택
[Stage 1B] 컨셉 구체화 (AI: Opus)
→ relation_type, expression_hint, source_data
[Stage 1B-ST] 구조화 텍스트 생성 (AI: Opus)
→ structured_text per topic
[Stage 1.5a] 컨테이너 계산 (코드: 결정론적)
→ FontHierarchy, ContainerSpec, Preset
[Stage 1.7] 블록 레퍼런스 선택 (코드 + AI 1회)
→ relation_type → 카테고리 → 필터 → 블록 결정
[Stage 1.8] 적합성 검증 + 보강 (코드 + Selenium + AI)
→ overflow 감지 → Kei 에스컬레이션 → 재배분
[Stage 1.5b] 디자인 예산 계산 (코드)
[Stage 2] HTML 생성 (Type에 따라 다름)
→ Type B/B'/B'': block_assembler (코드)
→ Type A: Sonnet 재구성 (AI, 미완성)
[Stage 3] 렌더링 조립 (Type A만, Jinja2)
[Stage 4] 검증 (Selenium + Opus Vision)
→ overflow 측정 + 스크린샷 품질 평가
최종 HTML 출력 (data/runs/{id}/final.html)
```
---
## 2. 레이아웃 유형 (Type A / B / B' / B'')
### 2.1 Kei가 선택하는 유형: A와 B
Kei 프롬프트(`src/kei_client.py:34-46`)에서 A 또는 B를 선택한다.
| 유형 | 조건 | Zone 구조 |
|------|------|-----------|
| **Type A** | 참조자료(용어 정의, 부록 등)가 별도로 존재 | body(배경+본심) + sidebar(첨부) + footer(결론) |
| **Type B** | 본문 흐름만. 배경/첨부가 없거나 억지로 만들어야 하면 | top + bottom_left + bottom_right + footer |
### 2.2 Type B 변형: B'과 B''
B'과 B''은 **Kei가 선택하지 않는다.** 특정 MDX 테스트 과정에서 하드코딩한 변형이다.
| 변형 | 생성 경위 | 차이점 | 코드 위치 |
|------|----------|--------|----------|
| **B** | 범용 | 상단(전체폭 텍스트+이미지) + 하단 2분할 + 결론 | `block_assembler.py:461` `_assemble_slide_html_type_b()` |
| **B'** | 03번 MDX 테스트 중 생성 | 상단이 세로 카드 형태 + 하단에 표 렌더링 + 불릿 전용 | `block_assembler.py:885` `_assemble_slide_html_type_b_prime()` |
| **B''** | B'에서 스타일 변형 | border/gradient 없음. 색상바+여백으로 구분 | `block_assembler_b2.py:9` `_assemble_slide_html_type_b_double_prime()` |
**분기 코드** (`block_assembler.py:370-378`):
```python
if ctx.analysis.layout_template == "B":
return _assemble_slide_html_type_b(ctx, title_text)
if ctx.analysis.layout_template == "B'":
return _assemble_slide_html_type_b_prime(ctx, title_text)
if ctx.analysis.layout_template == "B''":
return _assemble_slide_html_type_b_double_prime(ctx, title_text)
```
### 2.3 향후 방향
B'/B''은 **범용화가 필요하다.** 현재는 03번 콘텐츠 구조(카드형+표)를 B 조립 함수가 커버하지 못해서 만든 땜질이다. 궁극적으로는 B 하나로 다양한 콘텐츠 구조를 커버하거나, AI가 서브타입을 판단하게 해야 한다.
---
## 3. MDX 샘플 ↔ 유형 매핑
| MDX | 파일 | 콘텐츠 성격 | 선택 유형 | 상태 |
|-----|------|-----------|----------|------|
| **01번** | `samples/mdx/01. 건설산업 DX의 올바른 이해(0127).mdx` | 용어 혼용 문제 + 용어 정의(참조) | **Type A** | ⚠ Stage 2 미완성 (Sonnet 의존) |
| **02번** | `samples/mdx/02. DX의 시행 목표 및 기대효과.mdx` | 본문 흐름 (3대 목표) | **Type B** | ✅ 동작 |
| **03번** | `samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx` | 카드형 구조 + 표 + 불릿 | **Type B'** | ✅ 동작 (하드코딩) |
---
## 4. 단계별 상세
### Stage 0: MDX 정규화
- **파일:** `src/mdx_normalizer.py``normalize_mdx_content()`
- **입력:** raw MDX 텍스트
- **출력:** `NormalizedContent` (sections, images, tables 분리)
### Stage 1A: Kei 실장 — 꼭지 추출
- **파일:** `src/kei_client.py``classify_content()`
- **AI:** Opus (Kei API)
- **입력:** 정규화된 텍스트
- **출력:** `Analysis` (title, core_message, layout_template, total_pages, page_structure, topics)
- **핵심 판단:**
- 꼭지 5개 이내 추출
- 각 꼭지에 purpose, layer, role, emphasis, direction 부여
- layout_template = "A" 또는 "B" 선택
- page_structure에 역할별 weight(비중) 배정
### Stage 1B: 컨셉 구체화
- **파일:** `src/kei_client.py``refine_concepts()`
- **AI:** Opus
- **출력:** topics에 relation_type, expression_hint, source_data 추가
### Stage 1B-ST: 구조화 텍스트 생성
- **파일:** `src/kei_client.py``generate_structured_text()`
- **AI:** Opus
- **출력:** topic별 structured_text (마크다운 형태)
### Stage 1.5a: 컨테이너 계산 (결정론적)
- **파일:** `src/space_allocator.py`
- **함수:**
- Type A → `calculate_container_specs()`
- Type B/B'/B'' → `build_containers_type_b()`
- 공통 → `calculate_font_hierarchy()`, `select_preset()`
- **입력:** page_structure의 weight, slide 크기(1280×720)
- **출력:** 역할별 `ContainerSpec` (width_px, height_px, zone)
- **로직:** weight × available_px = 각 zone px 확정
### Stage 1.7: 블록 레퍼런스 선택
- **파일:** `src/block_reference.py``select_and_generate_references()`
- **로직 (코드 결정론적 + AI 1회):**
1. relation_type → 블록 카테고리 매핑
2. expression_hint 키워드 매칭
3. 컨테이너 크기 적합성 필터
4. role/zone 제약 (sidebar → visuals/media 제외)
5. catalog.yaml 존재 검증 (유령 블록 차단)
6. 후보 2-3개 → Kei 1회 호출로 최종 선택
- **출력:** 역할별 `BlockReference` (block_id, design_reference_html)
### Stage 1.8: 적합성 검증 + 보강
- **파일:** `src/fit_verifier.py``calculate_fit()`
- **로직:**
1. 텍스트 분량 vs 할당 공간 계산
2. Selenium으로 실제 높이 측정 (3회 루프)
3. overflow 시 → Kei 에스컬레이션 (`call_kei_fit_escalation()`)
- 팝업 분리 판단, zone 간 재배분
4. 보강 제안: bold 키워드, 팝업 요약 등
### Stage 2: HTML 생성
**Type B/B'/B'' (코드 조립):**
- **파일:** `src/block_assembler.py``assemble_slide_html()`
- 역할별 `assemble_role_html()` 호출 → 블록 HTML 조립
- structured_text + design_reference_html 결합
- 이미지/팝업 embed
- **즉시 완성 HTML 반환**
**Type A (AI 재구성, 미완성):**
- **파일:** `src/content_verifier.py``generate_with_retry()`
- Sonnet에 phase_t_context 전달 → CSS + 레이아웃 생성
- **현재 검증 불완전**
### Stage 3: 렌더링 조립 (Type A만)
- **파일:** `src/renderer.py``render_slide_from_html()`
- Type B는 Stage 2에서 완전한 HTML이므로 스킵
### Stage 4: 검증
- **파일:** `src/slide_measurer.py`
- `measure_rendered_heights()` — Selenium 실측
- `capture_slide_screenshot()` — 스크린샷 캡처
- **파일:** `src/kei_client.py``vision_quality_gate()`
- Opus 멀티모달: 스크린샷 보고 시각 품질 평가
---
## 5. 핵심 파일 맵
```
src/
├── main.py ← FastAPI 서버, POST /api/generate
├── pipeline.py ← 파이프라인 오케스트레이터 (generate_slide)
├── pipeline_context.py ← PipelineContext 데이터 클래스
├── config.py ← 설정 (API key, 슬라이드 크기)
├── mdx_normalizer.py ← Stage 0: MDX → NormalizedContent
├── kei_client.py ← Stage 1A/1B/1B-ST: Kei API 호출 + 프롬프트
├── space_allocator.py ← Stage 1.5a: 컨테이너 px 계산
├── block_reference.py ← Stage 1.7: 블록 선택 (relation_type 기반)
├── fit_verifier.py ← Stage 1.8: 적합성 검증 + Selenium 루프
├── block_assembler.py ← Stage 2: Type B/B' HTML 조립
├── block_assembler_b2.py ← Stage 2: Type B'' HTML 조립
├── content_verifier.py ← Stage 2: Type A HTML (Sonnet, 미완성)
├── renderer.py ← Stage 3: Jinja2 렌더링 (Type A만)
├── slide_measurer.py ← Stage 4: Selenium 측정 + 스크린샷
├── validators.py ← Kei 응답 검증 (A/B별 구조 확인)
├── image_utils.py ← 이미지 크기 측정 + data URI 변환
├── svg_calculator.py ← SVG 다이어그램 좌표 계산
└── sse_utils.py ← SSE 스트리밍 유틸
templates/
├── slide-base.html ← 슬라이드 기본 구조 (Jinja2, CSS Grid)
├── catalog.yaml ← 블록 라이브러리 정의 (50+개)
└── blocks/ ← 블록 HTML 템플릿
├── headers/ (8개)
├── cards/ (17개)
├── emphasis/ (12개)
├── tables/ (8개)
├── visuals/ (6개)
├── media/ (4개)
└── BEPs/ (6개)
```
---
## 6. 데이터 흐름 (PipelineContext)
```
PipelineContext:
raw_content ← 원본 MDX
normalized ← NormalizedContent (sections, images, tables)
analysis ← Analysis (title, core_message, layout_template, page_structure, topics)
topics ← list[Topic] (relation_type, expression_hint, structured_text 포함)
page_structure ← PageStructure (roles → {topic_ids, weight, zone})
containers ← dict[role → ContainerSpec(width_px, height_px)]
font_hierarchy ← FontHierarchy (key_msg, core, bg, sidebar 폰트 크기)
references ← dict[role → list[BlockReference]]
sub_layouts ← dict[role → SubLayout]
fit_result ← 역할별 fit_status, 재배분값
enhancement_result ← bold_keywords, popup_summaries 등
generated_html ← Stage 2 출력
rendered_html ← Stage 3 출력 (완전 HTML)
measurement ← Selenium 측정값
quality_score ← 0-100
```
---
## 7. 현재 구현 상태 (Phase Y-11~13, 2026-04-15)
> Phase Y: slide-base 기반 파이프라인 재설계. 상세: `docs/history/PHASE-Y-PLAN.md`
### 파이프라인 흐름 (현재)
```
[Stage 0] MDX → normalized.sections (source of truth)
[Stage 1A] Kei 꼭지 추출 (영역/zone 판단 안 함)
[Phase Y] 코드: normalized → 대목차 추출 → group schema 분류 → 블록 매칭 → 영역 확정
[Stage 1.5a] space_allocator: weight → zone px (% 기반)
[Stage 1.7] block_reference: tag_match → schema_match → fallback 순서
[Stage 1.8] assembler(measure_mode) → Selenium 측정 → fit 루프
[Stage 2] assembler(slide-base + 블록) → final HTML
[Stage 4] Selenium overflow + 비전 (-1 미평가)
```
### MDX별 상태
| MDX | 상태 | 비고 |
|-----|------|------|
| **03** | ✅ 동작 | prerequisites-3col + pp2. 텍스트 누락 없음. 회귀 기준. |
| **02** | ⚠ schema 1차 | top: parallel_3_with_image. bottom: 분류 정교화 필요. |
| **01** | ⬜ 미착수 | Type A. 별도 작업. |
### 핵심 원칙 (확립됨)
- source of truth = normalized.sections (Stage 0)
- 영역 = 코드가 결정 (Kei 아님). sub_titles 기반 + group schema.
- 블록 CSS에 최종 고정값. slide_font_css는 공통 레이아웃 계약만.
- zone = % 기반, block = height:100%.
- 글씨 크기 고정. fit은 padding → 내용량 → font 1단계(responsive tier).
- 기존 경로 삭제 금지. 새 schema 점진적 추가. MDX 03 회귀 기준.
- 하드코딩 금지. 프로세스가 결과를 만드는 구조.
3. **블록 글씨 크기 하드코딩 (px 고정)**
- 블록 CSS에 font-size가 Figma 원본 px로 고정
- 컨테이너 크기에 따라 조정 불가 → overflow 원인
- CSS 변수(`var(--block-font-heading)`)로 전환 → assembler가 zone 크기에 따라 계산
### 미해결 프로세스
1. **overflow 시 font 조정 루프** — 재배분만으로 부족할 때 font/padding 줄이기 (Y-5)
2. **Sonnet redesign 경로** — tag 매칭 실패 시 블록 단위 redesign → 저장 (Y-6)
---
## 8. 검증 계획
업데이트된 템플릿이 파이프라인에서 제대로 동작하는지 확인한다.
| MDX | 유형 | 검증 포인트 |
|-----|------|-----------|
| **01번** | Type A | 업데이트된 블록 + slide-base.html로 조립 정상 동작 |
| **02번** | Type B | 업데이트된 블록 선택 + 조립 + overflow 없음 |
| **03번** | Type B' | 카드/표 구조가 업데이트된 템플릿으로 정상 렌더링 |
### 검증 방법
1. 각 MDX를 파이프라인에 투입
2. 중간 산출물(step1_analysis.json 등) 확인 — 블록 선택이 의도대로인지
3. 최종 HTML(final.html) 렌더링 — overflow, 시각 품질 확인
4. 업데이트 전/후 비교
-30
View File
@@ -393,36 +393,6 @@ P2-E (누락기능) ── 병렬 │
---
## Phase Y: MDX 외부 컴포넌트 인라인 삽입
> 근거: MDX에서 `import ... from '*.astro'`로 불러오는 외부 컴포넌트(표, 다이어그램 등)가 파이프라인에서 누락됨. import문은 제거되고 `<DxEffect />` 같은 태그는 사라져서 콘텐츠 손실 발생.
### Y-1: import문 파싱 — 컴포넌트명:파일경로 매핑
- **파일:** `src/mdx_normalizer.py`
- **내용:** `import Foo from '../../components/foo.astro'``{"Foo": 절대경로}` 매핑 추출
- **의존성:** base_path (MDX 원본 파일 위치, pipeline.py에서 전달)
- **완료 기준:** import문에서 컴포넌트명→절대경로 dict 반환
### Y-2: .astro 파일 파싱 — HTML + CSS 추출
- **파일:** `src/mdx_normalizer.py`
- **내용:** .astro 파일에서 `---` frontmatter 제거, HTML 본문 + `<style>` 블록 추출
- **의존성:** Y-1
- **완료 기준:** dx.astro → `<div class="table-wrapper">...</div>` + `<style>...</style>` 반환
### Y-3: 셀프클로징 태그 교체 — 인라인 삽입
- **파일:** `src/mdx_normalizer.py`
- **내용:** `<DxEffect />` 태그를 Y-2에서 추출한 HTML+CSS로 교체
- **의존성:** Y-1, Y-2
- **완료 기준:** MDX 정규화 결과에 외부 컴포넌트 HTML이 인라인으로 포함
### Y-4: Astro 특수 문법 정리
- **파일:** `src/mdx_normalizer.py`
- **내용:** Astro의 멀티라인 태그(`<td class="category-cell">텍스트</td>` 줄바꿈 패턴), `style="letter-spacing: -0.9px"` 등 인라인 스타일 정리
- **의존성:** Y-2
- **완료 기준:** 추출된 HTML이 브라우저에서 정상 렌더링
---
## 의존 관계
```
+268 -89
View File
@@ -1,124 +1,303 @@
# C.E.L. Slide Pipeline
# Kei Design Agent
## 이 프로젝트는 무엇인가
콘텐츠를 시각적으로 구조화된 슬라이드 HTML(1280×720px, 16:9)로 변환하는 AI 파이프라인.
MDX 기반 콘텐츠를 입력하면, 1280×720 슬라이드 HTML로 자동 변환하는 파이프라인입니다.
## 개요
텍스트 콘텐츠를 넣으면, 구조를 분석하고 BEPs(Figma) 디자인을 매칭하여 슬라이드를 만들어줍니다.
텍스트/MDX 콘텐츠를 입력하면:
1. Kei 실장(Opus)이 정보 구조와 비중을 판단하고
2. 코드가 컨테이너 크기를 계산하고
3. 블록을 선택하고
4. 콘텐츠-컨테이너 적합성을 검증하고
5. AI(Sonnet)가 블록 디자인을 참고하여 HTML을 생성하고
6. 코드가 슬라이드 프레임에 조립하고
7. 측정+비전 모델로 검증합니다
---
## 어떻게 구성/구현되어 있는가
### 전체 흐름
## 파이프라인 (10단계)
```
MDX 입력 → 정규화 → 꼭지 추출(AI) → zone 구분 → BEPs 매칭 → 조립 → 검증 → 출력
MDX 원본
[Stage 0] MDX 정규화 (코드)
[Stage 1A] 꼭지 추출 + 영역 배정 (Kei API / Opus)
[Stage 1B] 컨셉 구체화 (Kei API / Opus)
[Stage 1.5a] 컨테이너 초기 계산 (코드)
[Stage 1.7] 블록 선택 (코드)
[Stage 1.8] 적합성 검증 + 재배분 + 보강 (코드 + Kei 에스컬레이션)
[Stage 1.5b] 디자인 예산 재계산 (코드)
[Stage 2] HTML 생성 (영역별 개별 호출) (Claude Sonnet)
[Stage 3] 렌더링 조립 + 후처리 (코드)
[Stage 4] 측정 + 품질 검증 (Selenium + Opus Vision)
검증 통과 시 → final.html 저장 + 팝업 분리 (파일 출력)
```
### 구조
- **slide-base:** 1280×720 슬라이드 프레임. 대목차 + 구분선 + 본문 영역 + 핵심 인사이트(footer)
- **zone:** 본문 영역 안에서 중목차(##) 기준으로 나뉘는 영역 (top/bottom 등)
- **블록:** zone 안에 들어가는 디자인 단위. Figma에서 추출한 BEPs 디자인을 HTML/CSS로 변환한 것
- **catalog:** 블록의 메타 정보 (구조, 슬롯, 매칭 조건)
### 주요 파일
| 파일 | 역할 |
|------|------|
| `src/pipeline.py` | 파이프라인 오케스트레이션 |
| `src/section_parser.py` | 중목차 추출, 구조 분류 |
| `src/block_reference.py` | BEPs 디자인 매칭 |
| `src/block_assembler.py` | 슬라이드 HTML 조립 |
| `templates/blocks/slide-base.html` | 슬라이드 프레임 |
| `templates/catalog.yaml` | 블록 메타 정보 |
### 산출물
각 실행은 `data/runs/{run_id}/` 아래에 저장됩니다.
| 파일 | 내용 |
|------|------|
| `final.html` | 최종 슬라이드 |
| `final_context.json` | 파이프라인 결과 데이터 |
| `steps/*.html` | 단계별 디버그 보드 |
| `첨부*_상세*.html` | popup 상세 내용 |
※ Stage 4 이후의 파일 저장은 별도 Stage가 아닌 후처리입니다.
---
## 무슨 문제가 있는가
## 단계별 상세
| 문제 | 설명 |
### Stage 0: MDX 정규화
| 항목 | 내용 |
|------|------|
| **블록마다 스타일이 제각각** | 각 블록 HTML 안에 font-size, color, padding이 직접 박혀있어서, 같은 슬라이드 안에서 블록이 섞이면 위계가 안 맞음 |
| **slide-base에 구조+스타일 혼재** | 프레임 HTML 안에 전체 CSS가 인라인으로 들어있어서 유지보수가 어려움 |
| **블록이 완성 HTML** | 블록이 구조+스타일+값을 모두 포함하고 있어서, 재사용/조합이 안 됨 |
| **매칭 안 되면 고정 렌더** | BEPs에 맞는 블록이 없을 때 코드가 1회 고정 렌더하고 끝. 반복 조정 없음 |
| **빈 공간/overflow 방치** | 렌더 후 빈 공간이 있어도 조정 안 하고, overflow만 감지 |
| **검증이 약함** | overflow 측정만 하고, 정렬/위계/가독성 같은 시각 품질은 미검증 |
| **목적** | 원본 MDX에서 JSX/frontmatter를 제거하고, 섹션/팝업/이미지/테이블로 분리 |
| **적용기술** | 코드 (`normalize_mdx_content()`) |
| **인풋** | 원본 MDX 문자열 |
| **아웃풋** | `normalized` — clean_text, title, sections[], popups[], images[], tables[] |
| **연계** | → Stage 1A가 clean_text를 Kei에게 전달 |
### Stage 1A: 꼭지 추출 + 영역 배정
| 항목 | 내용 |
|------|------|
| **목적** | 콘텐츠에서 핵심 파트(꼭지)를 식별하고, 슬라이드의 어떤 영역(배경/본심/첨부/결론)에 배치할지 결정 |
| **적용기술** | Kei API (`classify_content()`) |
| **인풋** | normalized.clean_text |
| **아웃풋** | `topics[]` (id, title, purpose, layer, relation_type, expression_hint), `page_structure` (role별 topic_ids, weight) |
| **연계** | → Stage 1B가 각 꼭지를 구체화 |
### Stage 1B: 컨셉 구체화
| 항목 | 내용 |
|------|------|
| **목적** | 각 꼭지에 실제 원본 텍스트(source_data)와 요약(summary)을 매핑 |
| **적용기술** | Kei API (`refine_concepts()`) |
| **인풋** | topics + clean_text |
| **아웃풋** | `topics` 업데이트 — source_data, summary 추가 |
| **연계** | → Stage 1.5a가 텍스트 양을 기반으로 컨테이너 비율 계산 |
### Stage 1.5a: 컨테이너 초기 계산
| 항목 | 내용 |
|------|------|
| **목적** | 폰트 위계 확정 + 슬라이드 내 영역별 컨테이너 크기(px) 계산 + 프리셋 선택 |
| **적용기술** | 코드 (`calculate_font_hierarchy()`, `calculate_dynamic_ratio()`, `calculate_container_specs()`) |
| **인풋** | topics, page_structure (weight), preset |
| **아웃풋** | `font_hierarchy` (key_msg/core/bg/sidebar px), `container_ratio` (71:29 등), `containers` (role별 width_px, height_px), `preset` |
| **연계** | → Stage 1.7이 컨테이너 크기를 보고 블록 선택 |
### Stage 1.7: 블록 선택
| 항목 | 내용 |
|------|------|
| **목적** | 각 꼭지의 relation_type + expression_hint + 컨테이너 크기로 적합한 블록 결정. 같은 영역 꼭지들의 layer가 다르면 주종관계 판단 (블록 1개로 합침) |
| **적용기술** | 코드 (`select_and_generate_references()`) — catalog.yaml 기반 결정론적 매칭 |
| **인풋** | topics, containers, page_structure |
| **아웃풋** | `references` — role별 block_id, variant, design_reference_html, topic_id, is_hierarchical, supporting_topic_ids |
| **연계** | → Stage 1.8이 선택된 블록+콘텐츠가 컨테이너에 맞는지 검증 |
### Stage 1.8: 적합성 검증 + 재배분 + 보강 + 서브 컨테이너
| 항목 | 내용 |
|------|------|
| **목적** | 콘텐츠가 컨테이너에 들어가는지 검증 → 안 맞으면 재배분 → 여전히 안 되면 Kei 에스컬레이션 → 여유 공간에 보충 콘텐츠 → 서브 컨테이너 배치 계산 |
| **적용기술** | 코드 (`calculate_fit()`, `redistribute()`, `analyze_enhancements()`, `apply_enhancements()`, `calculate_sub_layout()`) + Kei API (에스컬레이션 시 `call_kei_fit_escalation()`) |
| **인풋** | topics, containers, references, font_hierarchy, normalized, core_message |
| **아웃풋** | `containers` (재배분된 height_px), `fit_result` (role별 fit_status, redistribution), `enhancement_result` (V-7 subordinate_treatments, V-8 supplement_blocks, V-9 emphasis_blocks, V-10 bold_keywords, V-4 kei_decisions), `sub_layouts` (role별 서브 컨테이너 name/width/height, table_rows) |
| **내부 흐름** | Step 1: 필요 높이 계산 → Step 2: 재배분 → Step 3: Kei 에스컬레이션 → Step 4-5: 보강 분석+적용 → Step 6: fit 재검증 → Step 7: 서브 컨테이너 배치 → Step 8: 확정 |
| **연계** | → Stage 1.5b가 재배분된 크기로 디자인 예산 재계산, → Stage 2가 sub_layouts + enhancements를 프롬프트에 반영 |
### Stage 1.5b: 디자인 예산 재계산
| 항목 | 내용 |
|------|------|
| **목적** | 재배분된 컨테이너 크기 + 선택된 블록 schema 기준으로 영역별 가용 공간 계산 |
| **적용기술** | 코드 (`calculate_design_budget()`) |
| **인풋** | containers (재배분 후), references (블록 schema) |
| **아웃풋** | `containers` 업데이트 — design_budget (available_height_px, available_width_px, fits) |
| **연계** | → Stage 2가 design_budgets를 프롬프트에 포함 |
### Stage 2: HTML 생성 (영역별 개별 호출)
| 항목 | 내용 |
|------|------|
| **목적** | page_structure에 존재하는 각 역할(배경/본심/첨부/결론)의 HTML을 **영역별 개별 Sonnet 호출**로 생성. 블록 디자인을 참고하되 콘텐츠가 구조를 결정 (Phase R' 방식) |
| **적용기술** | Claude Sonnet API — 영역당 1회 호출 (`build_area_prompt()``_call_claude()`) |
| **인풋** | raw_content, topics, containers, font_hierarchy, references (design_reference_html), sub_layouts (서브 컨테이너 치수), enhancements (V-4~V-10 지시), design_budgets |
| **호출 흐름** | Sonnet(배경) → bg_html, Sonnet(본심) → core_html, Sonnet(첨부) → sidebar_html, Sonnet(결론) → footer_html. 해당 역할에 꼭지가 없으면 스킵. body_html = bg_html + spacer + core_html |
| **아웃풋** | `generated_html` — body_html, sidebar_html, footer_html |
| **프롬프트에 포함되는 것** | 서브 컨테이너 레이아웃 제약, 디자인 레퍼런스 HTML (블록 CSS 참고), Kei 에스컬레이션 결정, 종속 꼭지 처리 지시, 보충 블록 지시, 강조 문장, bold 키워드, 폰트/컨테이너 크기 제약 |
| **연계** | → Stage 3이 영역별 HTML을 슬라이드 프레임에 배치 |
### Stage 3: 렌더링 조립 + 후처리
| 항목 | 내용 |
|------|------|
| **목적** | 생성된 HTML 조각을 CSS Grid 슬라이드 프레임에 삽입 + 후처리 (폰트 캡핑, overflow 제거, sidebar width 조정, bold 변환) |
| **적용기술** | 코드 (`render_slide_from_html()`) |
| **인풋** | generated_html, preset (grid_areas, grid_columns), font_hierarchy, container_ratio |
| **아웃풋** | `rendered_html``final.html` 파일 저장 |
| **연계** | → Stage 4가 렌더링 결과를 측정+검증 |
### Stage 4: 품질 검증
| 항목 | 내용 |
|------|------|
| **목적** | Selenium으로 실제 브라우저 렌더링 후 overflow 측정 + Opus Vision으로 시각적 품질 평가 |
| **적용기술** | Selenium (`measure_rendered_heights()`) + Claude Opus Vision (`vision_quality_gate()`) |
| **인풋** | rendered_html |
| **아웃풋** | `measurement` (zone별 clientHeight, scrollHeight, overflow, excess_px), `quality_score` |
| **연계** | 파이프라인 완료. overflow 시 경고 포함하여 진행 |
---
## 어떻게 개선하려 하는가
## 중간 산출물
핵심은 3가지입니다.
파이프라인 실행마다 `data/runs/{timestamp}/`에 단계별 결과가 저장된다.
1. **블록을 구조 부품화** — 완성 HTML이 아니라, 구조만 담고 스타일은 토큰으로 분리
2. **스타일을 토큰으로 통일** — 블록마다 제각각인 폰트/색/여백을 공통 기준으로
3. **2경로 파이프라인** — 매칭되면 바로 쓰고(direct-fit), 안 되면 재구성(recipe)
### JSON Context (Stage별 누적 상태)
| 파일 | Stage | 내용 |
|------|-------|------|
| `stage_0_context.json` | 0 | normalized (섹션, 팝업, 이미지) |
| `stage_1a_context.json` | 1A | topics, page_structure |
| `stage_1b_context.json` | 1B | topics (source_data 추가) |
| `stage_1_5a_context.json` | 1.5a | font_hierarchy, containers, ratio |
| `stage_1_7_context.json` | 1.7 | references (블록 선택 결과) |
| `stage_1_8_context.json` | 1.8 | fit_result, enhancements, sub_layouts |
| `stage_1_5b_context.json` | 1.5b | containers (design_budget 추가) |
| `stage_2_context.json` | 2 | generated_html |
| `stage_3_context.json` | 3 | (rendered_html은 final.html로 별도 저장) |
| `stage_4_context.json` | 4 | measurement, quality_score |
| `final_context.json` | 최종 | 전체 context |
### AS-IS → TO-BE
### HTML 시각화 (`steps/` 폴더)
| 파일 | Stage | 내용 |
|------|-------|------|
| `stage_0.html` | 0 | 섹션/팝업/이미지 목록 |
| `stage_1a.html` | 1A | 꼭지 테이블 (purpose, layer, 영역) |
| `stage_1b.html` | 1B | 꼭지 + source_data + summary |
| `stage_1_5a.html` | 1.5a | 빈 컨테이너 (1280×720) |
| `stage_1_5a_content.html` | 1.5a | 컨테이너에 콘텐츠 배치 |
| `stage_1_5b.html` | 1.5b | 디자인 예산 (available height/width) |
| `stage_1_7.html` | 1.7 | 블록 선택 표시 |
| `stage_1_8_fit_before.html` | 1.8 | 적합성 (재배분 전) |
| `stage_1_8_fit_after.html` | 1.8 | 재배분 후 + 보강 |
| `stage_1_8_blocks.html` | 1.8 | SLOT 구조 + 블록 디자인 + 주종관계 (1280×720) |
| `stage_2.html` | 2 | 영역별 Sonnet 출력을 실제 렌더링 (역할별 개별 확인) |
| `stage_3.html` | 3 | 영역을 합쳐 슬라이드 프레임에 배치한 결과 (1280×720 실제 렌더링) |
| `stage_4.html` | 4 | 측정 결과 + 품질 점수 |
```
AS-IS:
AI가 먼저 꼭지를 추출하고
→ 매칭 블록이 있으면 삽입, 없으면 코드가 1회 고정 렌더
→ 빈 공간이 있어도 그냥 둠
→ 블록마다 font-size, color가 직접 박혀있어서 섞이면 위계 안 맞음
---
TO-BE:
중목차 기준으로 zone을 먼저 나누고
→ TF-IDF로 BEPs 매칭 시도
→ 매칭되면 블록 삽입 + 크기 조절 (direct-fit)
→ 안 되면 AI가 꼭지 정리 + 유사 디자인으로 redesign + 반복 조정 (recipe)
→ 빈 공간/overflow를 자동 재분배
→ 모든 블록이 토큰 기반이라 스타일 통일
## 핵심 원칙
1. **콘텐츠가 구조를 결정** — 블록 CSS는 참고만. AI가 콘텐츠 전달 의도를 보고 HTML 구조 결정 (Phase R')
2. **하드코딩 금지** — font-size 외 모든 수치는 동적 계산. 어떤 MDX가 들어와도 동일하게 동작
3. **스크롤 절대 금지** — overflow:auto/scroll 어떤 영역에서도 불허
4. **Kei API 필수** — fallback 없음. 성공할 때까지 무한 재시도
5. **AI가 옵션 생성, Kei가 결정** — 공간 부족 시 하드코딩 대응이 아니라 Kei 판단 요청
6. **계산 먼저, AI 판단 나중에, 렌더링은 검증만**
7. **overflow 상태에서 출력 금지** — Vision 모델 품질 게이트 통과 필수
---
## 블록 라이브러리 (38개)
6개 카테고리, 38개 블록. 각 블록은 `catalog.yaml`에 용도(when), 금지(not_for), purpose_fit, schema(슬롯 정의)가 있음.
| 카테고리 | 개수 | 용도 |
|---------|------|------|
| **headers** | 5 | 타이틀, 꼭지 헤더 |
| **cards** | 9 | 항목 나열, 카드 그리드 |
| **tables** | 3 | 비교표, 데이터 테이블 |
| **visuals** | 6 | SVG 다이어그램, 관계도 |
| **emphasis** | 10 | 강조, 인용, 결론, 불릿 |
| **media** | 5 | 이미지/사진 |
---
## 기술 스택
| 역할 | 도구 |
|------|------|
| 서버 | FastAPI + uvicorn (포트 8001) |
| AI (Kei 실장/편집자) | Kei API → Opus (localhost:8000) |
| AI (HTML 생성) | Anthropic API → Claude Sonnet |
| AI (품질 검증) | Anthropic API → Claude Opus Vision |
| 블록 검색 | FAISS + bge-m3 |
| 템플릿 | Jinja2 (블록 디자인 레퍼런스용) |
| 렌더링 | CSS Grid + 디자인 토큰 (1280×720) |
| 렌더링 측정 | Selenium headless Chrome |
| SVG 시각화 | svg_calculator.py (N개 동적 배치) |
| 이미지 | Pillow (크기 측정) + base64 인라인 |
| 폰트 | Pretendard Variable |
| 공간 계산 | space_allocator.py + fit_verifier.py (결정론적) |
---
## 설치 및 실행
```bash
# 설치
cd design_agent
pip install -e .
# FAISS 인덱스 빌드 (블록 추가/수정 시)
python scripts/build_block_index.py
# .env 설정
ANTHROPIC_API_KEY=sk-ant-...
KEI_API_URL=http://localhost:8000
LOG_LEVEL=DEBUG
```
---
```bash
# 터미널 1: Kei API (필수)
cd D:\ad-hoc\kei\persona_agent
python -m uvicorn backend.main:app --host 127.0.0.1 --port 8000
## 현재 상태
# 터미널 2: Design Agent
cd D:\ad-hoc\kei\design_agent
python -m uvicorn src.main:app --host 127.0.0.1 --port 8001 --reload
```
| 대상 | 슬라이드 변환 | 파이프라인 자동화 |
|------|-------------|----------------|
| MDX 03 (3개 목표 + 비교표) | ✅ 완료 | ✅ 파이프라인 연결 |
| MDX 02 (목표 + 프로세스 + 상세표) | ✅ 완료 | △ 파이프라인 연결, 시각 품질 개선 중 |
| MDX 01 (Type A, sidebar 구조) | ✅ 완료 (개별) | 미연결 |
| 토큰 기반 CSS 체계 | - | ✅ 정의 완료, slide-base 적용 |
| Figma 블록 추출 | - | 진행 중 (`figma_to_html_agent/blocks/`) |
접속: http://localhost:8001
---
## 다음 단계 방향
## 개선 이력
| 순서 | 단계 | 내용 |
|------|------|------|
| 1 | 폴더 구조 정리 | structures/recipes/legacy 분리 |
| 2 | 기존 블록 점진 전환 | 분류(direct-fit/recipe/rewrite) → 토큰 기반 전환 |
| 3 | catalog 고도화 | 파일명 중심 → 속성 테이 기반 매칭 |
| 4 | 파이프라인 연결 | TF-IDF 매칭 + recipe/composition 경로 |
| 5 | fit 루프 확장 | 빈 공간 재분배, preview 축약, 자동 조정 반복 |
| 6 | 시각 품질 검증 | 정렬, 위계, 가독성 검증 강화 |
상세: [IMPROVEMENT-PLAN.md](docs/architecture/IMPROVEMENT-PLAN.md)
| Phase | 내용 | 상태 |
|-------|------|------|
| A~D | 슬라이드 품질 핵심 | 완료 |
| G~N | Kei API, 스토리라인, 정합성, 블록 선택, 비중, 측정 | 완료 |
| O | 테이 기반 레이아웃 | 완료 |
| P | 다후보 렌더링 비교 | 완료 (20/100점 → 방향 전환) |
| Q | 제약 기반 블록 선택 | 완료 |
| R | 하이브리드 블록 (실패 — P=Q=R 동일 구조) | 실패 |
| R' | 블록 CSS 참고 + AI 구조 결정 | 설계 확정 |
| S | 검증 합격 프롬프트 + Claude HTML 생성 | 설계 확정 |
| T | 11-Stage 파이프라인 + 디자인 레퍼런스 | 완료 (31/31 통과) |
| V | 적합성 검증 + Kei 에스컬레이션 + 서브 컨테이너 | 완료 |
| W | Stage 2 출력 품질 수정 (6건) | 진행 중 |
---
## 참고 문서
## Kei Persona와의 관계
| 문서 | 내용 |
|------|------|
| [IMPROVEMENT-PLAN.md](docs/architecture/IMPROVEMENT-PLAN.md) | 개선 설계 (목표/방향/6단계 계획) |
| [TOKENS-v1.md](docs/architecture/TOKENS-v1.md) | 토큰 위계 기준표 초안 |
| [BLOCK-RULES.md](docs/architecture/BLOCK-RULES.md) | 블록 작성 규칙 (에이전트 간 계약서) |
```
Kei Persona Agent (localhost:8000)
├── Opus + RAG + 세션 컨텍스트
├── 도메인 지식 (건설/DX/BIM)
└── 대화/생성/피드백/실행 모드
Design Agent (localhost:8001, 이 프로젝트)
├── 슬라이드 생성 전용
├── Kei API로 꼭지 추출(1A) + 컨셉 구체화(1B) + 에스컬레이션(1.8) 호출
├── Sonnet으로 HTML 생성(Stage 2)
├── Opus Vision으로 품질 검증(Stage 4)
└── 두 프로젝트는 독립. 코드 공유 없음. API 연동만.
```
Binary file not shown.
+105
View File
@@ -0,0 +1,105 @@
# 45개 블록 BLOCK_SLOTS — design_director.py에 반영 필요
# 다른 쪽 작업 완료 후 교체
BLOCK_SLOTS = {
# headers/
"section-title-with-bg": {"required": ["title_ko"], "optional": ["title_en", "breadcrumb", "bg_image"]},
"section-header-bar": {"required": ["title"], "optional": ["subtitle"]},
"topic-left-right": {"required": ["title", "description"], "optional": []},
"topic-center": {"required": ["title"], "optional": ["subtitle", "description"]},
"topic-numbered": {"required": ["number", "title"], "optional": ["description", "color"]},
# cards/
"card-image-3col": {"required": ["cards"], "optional": []},
"card-text-grid": {"required": ["cards"], "optional": []},
"card-dark-overlay": {"required": ["cards"], "optional": []},
"card-tag-image": {"required": ["cards"], "optional": []},
"card-icon-desc": {"required": ["cards"], "optional": []},
"card-compare-3col": {"required": ["cards"], "optional": []},
"card-step-vertical": {"required": ["steps"], "optional": []},
"card-image-round": {"required": ["cards"], "optional": []},
"card-stat-number": {"required": ["stats"], "optional": []},
"card-numbered": {"required": ["items"], "optional": []},
# tables/
"compare-3col-badge": {"required": ["headers", "rows"], "optional": []},
"compare-2col-split": {"required": ["left_title", "right_title", "rows"], "optional": []},
"table-simple-striped": {"required": ["headers", "rows"], "optional": []},
# visuals/
"venn-diagram": {"required": ["center_label", "items"], "optional": ["center_sub", "description"]},
"circle-gradient": {"required": ["label"], "optional": ["sub_label"]},
"compare-pill-pair": {"required": ["left_label", "right_label"], "optional": ["left_sub", "right_sub"]},
"process-horizontal": {"required": ["steps"], "optional": []},
"flow-arrow-horizontal": {"required": ["steps"], "optional": []},
"keyword-circle-row": {"required": ["keywords"], "optional": []},
"layer-diagram": {"required": ["layers"], "optional": ["title"]},
"timeline-vertical": {"required": ["events"], "optional": []},
"timeline-horizontal": {"required": ["events"], "optional": []},
"pyramid-hierarchy": {"required": ["levels"], "optional": []},
# emphasis/
"quote-left-border": {"required": ["quote_text"], "optional": ["source"]},
"quote-big-mark": {"required": ["quote_text"], "optional": ["source"]},
"quote-question": {"required": ["question"], "optional": ["description"]},
"conclusion-accent-bar": {"required": ["conclusion_text"], "optional": ["label"]},
"comparison-2col": {"required": ["left_title", "left_content", "right_title", "right_content"], "optional": ["left_subtitle", "right_subtitle"]},
"banner-gradient": {"required": ["text"], "optional": ["sub_text"]},
"dark-bullet-list": {"required": ["bullets"], "optional": ["title"]},
"highlight-strip": {"required": ["segments"], "optional": []},
"callout-solution": {"required": ["title", "description"], "optional": ["icon", "source"]},
"callout-warning": {"required": ["title", "description"], "optional": ["icon"]},
"tab-label-row": {"required": ["tabs"], "optional": []},
"divider-text": {"required": ["text"], "optional": []},
# media/
"image-row-2col": {"required": ["images"], "optional": []},
"image-grid-2x2": {"required": ["images"], "optional": []},
"image-side-text": {"required": ["image_src"], "optional": ["image_alt", "title", "description", "bullets"]},
"image-full-caption": {"required": ["src"], "optional": ["alt", "caption"]},
"image-before-after": {"required": ["before_src", "after_src"], "optional": ["before_label", "after_label", "caption"]},
}
# _apply_defaults 용
BLOCK_DEFAULTS = {
"section-title-with-bg": {"title_ko": "(제목)"},
"section-header-bar": {"title": "(섹션)"},
"topic-left-right": {"title": "(소제목)", "description": ""},
"topic-center": {"title": "(제목)"},
"topic-numbered": {"number": "1", "title": "(단계)"},
"card-image-3col": {"cards": []},
"card-text-grid": {"cards": []},
"card-dark-overlay": {"cards": []},
"card-tag-image": {"cards": []},
"card-icon-desc": {"cards": []},
"card-compare-3col": {"cards": []},
"card-step-vertical": {"steps": []},
"card-image-round": {"cards": []},
"card-stat-number": {"stats": []},
"card-numbered": {"items": []},
"compare-3col-badge": {"headers": [], "rows": []},
"compare-2col-split": {"left_title": "A", "right_title": "B", "rows": []},
"table-simple-striped": {"headers": [], "rows": []},
"venn-diagram": {"center_label": "관계도", "items": [], "center_sub": "", "description": ""},
"circle-gradient": {"label": "(라벨)"},
"compare-pill-pair": {"left_label": "A", "right_label": "B"},
"process-horizontal": {"steps": []},
"flow-arrow-horizontal": {"steps": []},
"keyword-circle-row": {"keywords": []},
"layer-diagram": {"layers": []},
"timeline-vertical": {"events": []},
"timeline-horizontal": {"events": []},
"pyramid-hierarchy": {"levels": []},
"quote-left-border": {"quote_text": "(인용)"},
"quote-big-mark": {"quote_text": "(인용)"},
"quote-question": {"question": "(질문)"},
"conclusion-accent-bar": {"conclusion_text": "(결론)"},
"comparison-2col": {"left_title": "A", "left_content": "-", "right_title": "B", "right_content": "-"},
"banner-gradient": {"text": "(배너)"},
"dark-bullet-list": {"bullets": []},
"highlight-strip": {"segments": []},
"callout-solution": {"title": "(솔루션)", "description": ""},
"callout-warning": {"title": "(경고)", "description": ""},
"tab-label-row": {"tabs": []},
"divider-text": {"text": "구분"},
"image-row-2col": {"images": []},
"image-grid-2x2": {"images": []},
"image-side-text": {"image_src": ""},
"image-full-caption": {"src": ""},
"image-before-after": {"before_src": "", "after_src": ""},
}
-162
View File
@@ -1,162 +0,0 @@
# 블록 작성 규칙
Figma 1:1 HTML을 재사용 가능한 블록으로 전환할 때, 이 규칙을 따른다.
figma_to_html_agent와 design_agent 간의 **계약서**.
---
## 1. HTML 규칙
### 구조 스타일은 OK
블록의 배치/정렬을 위한 CSS는 블록 안에 있어야 한다.
```css
/* OK */
display: flex;
grid-template-columns: repeat(3, 1fr);
align-items: center;
overflow: hidden;
position: relative;
```
### 직접값은 금지
하드코딩된 폰트/색/여백은 블록 안에 넣지 않는다.
```css
/* NG */
font-size: 11px;
color: #475569;
padding: 16px;
/* OK — 토큰 참조 */
font-size: var(--font-body);
color: var(--color-body);
padding: var(--card-padding);
```
---
## 2. 클래스명 규칙
### 공통 클래스 (모든 블록에서 사용)
| 클래스 | 용도 | 토큰 참조 |
|--------|------|-----------|
| `.zone-title` | 중목차 제목 | `--font-zone-title`, `--color-zone-title` |
| `.sub-title` | 소목차 제목 | `--font-sub-title` |
| `.bul` | 본문 블릿 (hanging indent) | `--font-body`, `--bullet-indent` |
| `.body-text` | 본문 텍스트 | `--font-body`, `--color-body` |
| `.caption` | 캡션/보조 | `--font-caption`, `--color-caption` |
### 블록 고유 클래스
블록별 고유 요소는 블록 prefix를 붙인다.
```
.p3c-bar (prerequisites-3col의 gradient 바)
.pp2-col (process-product-2col의 컬럼)
.cid-card (card-icon-desc의 카드)
```
---
## 3. 슬롯 규칙
블록이 받아들이는 콘텐츠 슬롯. catalog에 명시.
| 슬롯 | 용도 | 예시 |
|------|------|------|
| `zone_title` | 중목차 제목 | "DX의 궁극적 목표" |
| `sub_title` | 소목차 제목 | "안전과 품질" |
| `body` | 본문 텍스트 | 설명 문장 |
| `bullets` | 블릿 리스트 | D2: 항목들 |
| `image` | 이미지 | 시각 앵커 |
| `preview` | 상세 preview | 표 헤더+행 |
| `detail_link` | 자세히보기 링크 | 첨부 파일 연결 |
---
## 4. 토큰 참조 방법
### 토큰 파일 위치
```
templates/styles/tokens/
├── typography.css ← 글자 위계
├── spacing.css ← 여백/간격
└── colors.css ← 색상
```
### 사용법
```css
/* 블록 CSS에서 */
.my-block-title {
font-size: var(--font-sub-title);
font-weight: var(--weight-sub-title);
line-height: var(--lh-sub-title);
color: var(--color-zone-title);
margin-bottom: var(--heading-gap);
}
.my-block-body {
font-size: var(--font-body);
color: var(--color-body);
padding-left: var(--bullet-indent);
text-indent: calc(var(--bullet-indent) * -1);
}
```
### 블록 의미색이 필요할 때
공통 테마색으로 안 되는 역할색은 colors.css의 2층 의미색을 참조.
```css
/* 3열 비교의 열별 색상 */
.col-1 .bar { background: linear-gradient(180deg, var(--color-col-1-from), var(--color-col-1-to)); }
.col-2 .bar { background: linear-gradient(180deg, var(--color-col-2-from), var(--color-col-2-to)); }
```
---
## 5. 블록 HTML 예시
### 좋은 예
```html
<section class="block block-3col">
<div class="col">
<div class="sub-title">{{ col.title }}</div>
<div class="bul">• {{ col.desc }}</div>
</div>
</section>
<style>
.block-3col {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--card-gap);
}
.block-3col .col {
padding: var(--card-padding);
background: var(--color-bg-subtle);
border: 1px solid var(--color-border);
border-radius: var(--card-radius);
}
</style>
```
### 나쁜 예
```html
<!-- NG: 직접값 하드코딩 -->
<div style="font-size:12px; color:#1e293b; padding:16px; background:#f8fafc; border-radius:6px;">
```
---
## 6. 전환 체크리스트
Figma 1:1 HTML → 재사용 블록으로 전환할 때:
- [ ] font-size 직접값 → `var(--font-*)` 교체
- [ ] color 직접값 → `var(--color-*)` 교체
- [ ] padding/margin 직접값 → semantic spacing 또는 `var(--space-*)` 교체
- 카드 내부: `var(--card-padding)`
- 블릿 들여쓰기: `var(--bullet-indent)`
- 소제목 아래: `var(--heading-gap)`
- 일반 여백: `var(--space-sm)`, `var(--space-md)`
- [ ] gap → `var(--card-gap)`, `var(--flex-gap)`
- [ ] border-radius → `var(--card-radius)` 교체
- [ ] 공통 클래스 사용 (`.bul`, `.sub-title` 등)
- [ ] 블록 고유 요소에 prefix 클래스
- [ ] catalog에 슬롯/구조/호환 정보 등록
-183
View File
@@ -1,183 +0,0 @@
# Frame Integration Map — 32 Figma Frame ↔ Phase Z Zone 통합 매핑
> **핵심 방향 (2026-04-28 정정)**
>
> Figma frame 은 슬라이드에 **원본 그대로 꽂는 게 아니라**, **디자인 레퍼런스 / 구조 패턴 / 슬롯 힌트**로 본다.
> **최종 결과는 항상 현재 slide-body 의 Zone 안에 맞게 재구성한다.**
>
> - 원본이 full-slide 디자인이어도 → Zone 안 맞게 축약 / 재배치 / 슬롯화해서 사용
> - 복합 슬라이드여도 → Zone 안에서는 일부 패턴만 참고해 재구성
>
> → 분류는 **"원본 크기"** 가 아니라 **"Zone 에 어떻게 적용할지"** 기준으로 한다.
---
## 라벨 정의
### Zone 적용 방식 (zone_application)
| 값 | 의미 |
|---|---|
| `zone_direct` | Zone 안에 거의 그대로 적용 가능 (구조 / 사이즈 변환 최소) |
| `zone_adapt` | 구조는 맞지만 Zone 크기에 맞게 재구성 필요 |
| `zone_extract` | 전체 frame 중 일부 패턴만 추출해서 Zone 에 사용 |
| `reference_only` | 직접 구조로 쓰기보다 디자인 톤 / 아이디어만 참고 |
| `reject` | Phase Z 에서 사용하지 않음 |
### Zone 적합성 (zone_fit)
| 값 | 의미 |
|---|---|
| `high` | Zone 에 바로 맞추기 쉬움 (단순 리스트 / 표 등) |
| `medium` | 조정하면 가능 (3 단 카드 / 다이어그램 등) |
| `low` | 많이 재구성해야 함 (복합 구조 / 분할 패널) |
| `reference` | 참고용 |
### 검토 상태 (review_status)
| 값 | 의미 |
|---|---|
| `auto_estimated` | 코드가 layout 패턴으로 추정. 사용자 검토 필요 |
| `user_confirmed` | 사용자 검토 후 확정 |
| `needs_review` | 자동 추정에 의문 — 사람이 Figma 캔버스 직접 확인 필요 |
---
## 1차 — 32 Frame Zone 적용 분류
> ⚠️ **`legacy 스타일 출처(참고)` 컬럼 의미**
>
> 기존 `templates/blocks/` 는 **Phase Z 의 실제 조립 재료가 아님** (삭제 / 폐기 방향).
> 이 컬럼은 **frame ↔ 블록 매핑이 아니라**, frame 의 디자인 / 시각 언어를 만들 때 참고할 수 있는 **스타일 출처** (색감, 여백, 폰트 위계, 표 스타일, 카드 스타일, pill / badge, SVG / CSS 구현 힌트) 만 가리킨다.
>
> Phase Z 의 실제 실행 기준은 **새 frame / zone catalog**.
> 정밀화는 [`PHASE-Z-STYLE-SOURCES.md`](PHASE-Z-STYLE-SOURCES.md) (예정) 에서 별도 진행.
| Frame | Figma ID | 패턴 | Zone 적합성 | Zone 적용 방식 | 검토 상태 | legacy 스타일 출처(참고) | 비고 |
|---|---|---|---|---|---|---|---|
| **01** | `1171281172` | `circular-nodes-6` | `medium` | `zone_adapt` | `auto_estimated` | `templates/blocks/visuals/` (다이어그램) | S/W 개발 방향 순환도. 6 노드 — Zone 사이즈에 맞춰 시각 재구성 |
| **02** | `1171281173` | `bullet-cards-4-plus-center` | `low` | `zone_extract` | `auto_estimated` | (복합 — 일부 패턴 추출) | 4 카드 + 중앙 강조. Zone 에서는 4 카드 부분만 추출하거나 중앙만 강조로 사용 |
| **03** | `1171281174` | `list-numbered-4` | `high` | `zone_direct` | `auto_estimated` | `templates/blocks/cards/` 또는 list 류 | 4 항목 번호 리스트 — 단순 구조, Zone 직접 적용 |
| **04** | `1171281175` | `quadrilateral-relations` | `medium` | `zone_extract` | `user_confirmed` | (관계도 — 기존 venn / cycle 로 부족 가능) | 4 actors + 관계도 + hierarchy 단계 결합된 복합형. 큰 Zone 에서는 zone_adapt 가능, 기본은 actor / hierarchy 패턴 추출 |
| **05** | `1171281176` | `side-card-with-list` | `medium` | `zone_extract` | `auto_estimated` | (좌우 분할 — 일부 추출) | 좌측 카드 + 우측 리스트. Zone 안에서 좌·우 패턴 추출 |
| **06** | `1171281177` | `full-page-map-banner` | `reference` | `reference_only` | `user_confirmed` | (콘텐츠 슬롯형 X — 참고용) | 지도 / 마커 / 현황 시각자료 중심. 일반 텍스트 Zone 구조로 직접 사용 X. 지도형 콘텐츠가 있을 때 디자인 참고 |
| **07** | `1171281178` | `2col-paired-list` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` 또는 페어드 | 2 컬럼 페어드 리스트 — Zone 사이즈 맞춰 표 재구성 |
| **08** | `1171281179` | `3-section-framework` | `medium` | `zone_extract` | `auto_estimated` | (3 섹션 — 일부 추출) | 효율적 정보 관리 (What/How/When). 3 섹션 패턴 추출 |
| **09** | `1171281180` | `list-stacked-vertical` | `high` | `zone_direct` | `auto_estimated` | `templates/blocks/cards/` 또는 list 류 | 5 항목 세로 리스트 — Zone 직접 적용 |
| **10** | `1171281181` | `radial-diagram-5` | `medium` | `zone_adapt` | `auto_estimated` | `templates/blocks/visuals/` | 5 way 방사형 다이어그램 — Zone 시각 재구성 |
| **11** | `1171281182` | `cards-3-category` | `medium` | `zone_extract` | `auto_estimated` | (3 카드 — 패턴 추출) | 시공단계 BIM 활용 — 3 카드 패턴 추출 |
| **12** | `1171281189` | `cycle-3way-intersection` | `medium` | `zone_adapt` | `auto_estimated` | `templates/blocks/visuals/venn-diagram.html` | 3 way 벤다이어그램. Zone 사이즈 맞춰 SVG 재구성 |
| **13** | `1171281190` | `3-column` | `medium` | `zone_extract` | `auto_estimated` | (3 단 — 패턴 추출) | 필수조건 (pillar 3). Zone 안에 3 단 패턴 추출. 02-2.2 매칭 실패 정답 frame (3차 대상) |
| **14** | `1171281191` | `persona-3col` | `medium` | `zone_extract` | `auto_estimated` | `templates/blocks/cards/card-text-grid.html` (참고) | 주체별 기대효과. Zone 안에서 3 카드 패턴 추출 |
| **15** | `1171281192` | `policy-4card-plus-list` | `low` | `zone_extract` | `auto_estimated` | (복합 — 일부 추출) | 4 카드 + 부속 리스트. Zone 안에서 4 카드만 추출하거나 리스트만 사용 |
| **16** | `1171281193` | `quadrant-4` | `medium` | `zone_extract` | `auto_estimated` | (4 사분면 — 패턴 추출) | BIM 수행 이슈 4사분면. 4 사분면 패턴 추출 |
| **17** | `1171281194` | `paired-rows-2x2` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | 2x2 페어드 행 — Zone 표 재구성 |
| **18** | `1171281195` | `compare-rows` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/compare-3col-badge.html` (참고) | BIM·DX 비교. 매칭 시스템 검증된 대표 frame. Zone 안 표 재구성 |
| **19** | `1171281197` | `cards-3-compare` | `medium` | `zone_extract` | `auto_estimated` | (3 단 비교 — 패턴 추출) | 설계방식 왜곡 — 3 비교 패턴 추출 |
| **20** | `1171281198` | `cards-3-header` | `medium` | `zone_extract` | `auto_estimated` | (3 헤더형 — 패턴 추출) | DX 는 S/W 가 필수 — 3 헤더 패턴 추출 |
| **21** | `1171281201` | `split-panel-diagram` | `low` | `zone_extract` | `auto_estimated` | (분할 패널 — 패턴 추출) | Solution Engn. S/W. 분할 패널 패턴 일부 추출 |
| **22** | `1171281202` | `split-panel-numbered` | `low` | `zone_extract` | `auto_estimated` | (분할 패널 + 번호 — 패턴 추출) | Model 특화 Engn. S/W. 분할 + 번호 패턴 추출 |
| **23** | `1171281203` | `table-2col` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | Application S/W 의 구분. **table family 중복 — variant 통합 검토** |
| **24** | `1171281204` | `table-3col` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | Engn. S/W 구성과 특징. **table family 중복 — variant 통합 검토** |
| **25** | `1171281205` | `left-categories-right-logos` | `medium` | `zone_extract` | `auto_estimated` | (좌우 분할 — 패턴 추출) | 상용 Engn. S/W. 좌우 분할 패턴 추출 |
| **26** | `1171281206` | `cards-4-grid` | `medium` | `zone_extract` | `auto_estimated` | (4 카드 — 패턴 추출) | 상용 S/W 의존 4 대 문제. 4 카드 그리드 추출 |
| **27** | `1171281208` | `central-split-synthesis` | `low` | `zone_extract` | `auto_estimated` | (중앙 합성 — 일부 추출) | 건설산업 고부가가치화. 중앙 + 양쪽 패턴 일부 추출 |
| **28** | `1171281209` | `title-plus-3-emphasis` | `medium` | `zone_extract` | `auto_estimated` | (제목 + 3 강조 — 패턴 추출) | 현존 상용 S/W 의 현실. 제목 + 3 카드 패턴 추출 |
| **29** | `1171281210` | `banner-top-2col-bottom` | `low` | `zone_extract` | `auto_estimated` | (banner + 2col 복합 — 패턴 추출) | Process/Product 혁신 (AS-IS/TO-BE). Zone 안에서 banner + 2col 패턴만 추출 |
| **30** | `1171281211` | `table-3col` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | 산업별 현황. **table family 중복 — variant 통합 검토** |
| **31** | `1171281212` | `table-3col` | `high` | `zone_adapt` | `auto_estimated` | `templates/blocks/tables/` | 산업별 특성과 발전방향. **table family 중복 — variant 통합 검토** |
| **32** | `1171281213` | `central-5-goals` | `low` | `zone_extract` | `auto_estimated` | (5 목표 복합 — 일부 추출) | 정책 달성. 중앙 5 목표 패턴 일부 추출 |
---
## 자동 추정 룰
### layout 패턴 → Zone 적용 방식
| 패턴 / family | Zone 적용 방식 | Zone 적합성 | 근거 |
|---|---|---|---|
| `list-*` (단순 리스트) | `zone_direct` | `high` | 구조 단순 — Zone 안 그대로 |
| `compare-rows` / `table-*col` / `paired-rows-*` (표 / 페어드) | `zone_adapt` | `high` | 표 구조 — Zone 사이즈 맞춰 재구성 |
| `cycle-*` / `circular-*` / `radial-*` / `quadrilateral-*` (시각 다이어그램) | `zone_adapt` | `medium` | 시각 디자인 — Zone 사이즈 맞춰 SVG 재구성 |
| `cards-3-*` / `persona-3col` / `3-column` / `cards-4-grid` / `quadrant-4` / `3-section-*` (3·4 단 카드 / 칼럼) | `zone_extract` | `medium` | 패턴 일부 추출 (카드 / 칼럼) |
| `side-*` / `left-*-right-*` / `title-plus-*` (좌우 / 제목+카드) | `zone_extract` | `medium` | 부분 패턴 추출 |
| `banner-top-*-bottom` / `bullet-cards-*-plus-center` / `policy-*-plus-list` (복합) | `zone_extract` | `low` | 복합 구조 — Zone 에서는 일부만 추출 |
| `split-panel-*` / `central-*` (분할 / 중앙 합성) | `zone_extract` | `low` | 새 시각 패턴 — 일부 추출 |
| `full-page-map-*` (지도 / 배너) | `reference_only` | `reference` | 콘텐츠 슬롯형 X — 디자인 참고만 |
---
## 분포 요약
### Zone 적용 방식
| 적용 방식 | 개수 | Frame |
|---|---|---|
| `zone_direct` | **2** | 03, 09 |
| `zone_adapt` | **11** | 01, 04, 07, 10, 12, 17, 18, 23, 24, 30, 31 |
| `zone_extract` | **18** | 02, 05, 08, 11, 13, 14, 15, 16, 19, 20, 21, 22, 25, 26, 27, 28, 29, 32 |
| `reference_only` | **1** | 06 |
| `reject` | **0** | — |
**합계 검증** : 2 + 11 + 18 + 1 + 0 = **32**
### Zone 적합성
| 적합성 | 개수 | Frame |
|---|---|---|
| `high` | **9** | 03, 07, 09, 17, 18, 23, 24, 30, 31 |
| `medium` | **15** | 01, 04, 05, 08, 10, 11, 12, 13, 14, 16, 19, 20, 25, 26, 28 |
| `low` | **7** | 02, 15, 21, 22, 27, 29, 32 |
| `reference` | **1** | 06 |
**합계 검증** : 9 + 15 + 7 + 1 = **32**
### 검토 상태
| review_status | 개수 | Frame |
|---|---|---|
| `auto_estimated` | **30** | (04, 06 제외 모두) |
| `needs_review` | **0** | (04, 06 사용자 확정 완료) |
| `user_confirmed` | **2** | **04** (zone_extract 확정), **06** (reference_only 확정) |
---
## 핵심 메시지
> **모든 frame 은 최종적으로 Zone 에 맞게 들어간다.**
> **원본이 full-slide 인지 아닌지는 참고 정보일 뿐, 최종 사용 단위는 Zone 이다.**
>
> - 사용자가 의미한 "needs_split" = 실제 의미는 `zone_extract` 또는 `zone_adapt`
> - frame 은 디자인 레퍼런스 / 구조 패턴 / 슬롯 힌트
> - Phase Z output 은 항상 Zone 에 맞는 재구성 결과
---
## 다음 단계
### ✅ 완료 (1 차)
- 32 frame 자동 추정 표 작성
- Frame 04, 06 사용자 확정 (`user_confirmed`)
- 컬럼명 정정 : `legacy 대응 블록(참고, 미검증)``legacy 스타일 출처(참고)`
### ✅ 완료 (2 차, 2026-04-28) — Phase Z Frame Style Inventory 작성
산출물 : [`PHASE-Z-FRAME-STYLE-INVENTORY.md`](PHASE-Z-FRAME-STYLE-INVENTORY.md)
- Source Policy : 메인 (`figma_to_html_agent/blocks/` 32 frame) / 토큰 (`templates/styles/tokens/`) / legacy (`templates/blocks/structures/`)
- Frame Inventory 32 행 (변환 14 + 미변환 18 보일러플레이트)
- Token Inventory 18 행 (`covered` 7 / `gap_candidate` 5 / `hierarchy_mapping_only` 3 / `hold_recheck_after_conversion` 3)
- Legacy Reference 6 행 (모두 `delete_after_extract` 후보)
> ⚠️ inventory = **추출 / 검증 단계**. 실제 token 파일 생성 / 변경 / catalog 설계 / templates/blocks 삭제 등 **실행은 별도 승인 단계**.
### 후속 작업
- **3차** — Frame 14 anchor_sets 재라벨링 (02-2.2 매칭 실패 교정 — 매칭 성능 교정 작업, 별도 진행)
- **추가** — table family (23/24/30/31) variant 통합 설계 (Phase Z catalog 작성 시)
- **확정 처리** — 2 차 조사 결과 후 high confidence frame 부터 `user_confirmed` 일괄 처리
---
## 부록 — 제외 / 특수 항목
`1171281171``texts.md` 만 존재하고 `index.html` / `analysis.md` 가 없어 Phase Z frame / style inventory 메인 대상에서 제외한다. 정체는 미확인.
-157
View File
@@ -1,157 +0,0 @@
# C.E.L. Slide Pipeline — 개선 설계
## 목표
**MDX 콘텐츠를 넣으면, 중목차/소목차 기준으로 BEPs 디자인과 매칭하여 슬라이드를 자동 생성한다.**
현재는 블록마다 스타일이 제각각이고, 매칭/조립/크기 조정이 불완전하여 수작업 보정이 필요한 상태. 이를 토큰 기반 통일 + 블록 구조 부품화 + 파이프라인 2경로화로 개선한다.
---
## 개선 방향
| 현재 문제 | 개선 방향 |
|-----------|-----------|
| 블록마다 폰트/색/여백이 직접 박혀있어 통일 안 됨 | 토큰으로 공통 기준 확정, 블록은 변수 참조 |
| slide-base에 구조+스타일이 섞여있음 | 프레임(HTML)과 스타일(CSS) 분리 |
| 블록이 완성 HTML이라 재사용/조합이 어려움 | 블록 = 구조 부품, 스타일은 테마로 분리 |
| 블록 매칭이 파일명/tag 중심 | 속성 테이블(catalog) 기반 매칭 |
| 크기 조정이 1회 렌더로 끝남 | 입력→조정 반복 (빈 공간/overflow 자동 재분배) |
| 검증이 overflow만 | 시각 품질까지 검증 (정렬, 위계, 가독성) |
---
## 핵심 축 3개
### 1. 표현 기준 통일
typography, spacing, colors, layout을 토큰으로 확정하여 모든 블록이 이 공통 기준을 따르게 한다.
### 2. 블록의 역할 재정의
블록 = 완성 HTML이 아니라 **구조 부품**. 구조 스타일(flex, grid)은 블록에, 직접값(font-size, color)은 토큰으로 분리한다.
### 3. 파이프라인 2경로화
- **direct-fit:** BEPs 매칭 → 블록 삽입 + 크기 조절
- **recipe/composition:** 미매칭 → 꼭지 정리 → redesign + 반복 조정
---
## 실행 원칙
**direct-fit 기준:**
direct-fit은 구조, 슬롯, 시각 위계가 기존 블록과 거의 1:1로 대응될 때만 허용한다.
**recipe-path 목적:**
recipe/composition 경로는 미매칭 콘텐츠를 새로운 구조로 재정리하되, 기존 visual language를 최대한 상속한다.
**fit 루프 우선순위:**
fit 조정은 줄바꿈 → 간격 조정 → preview 축약 → 폰트 1단계 축소 순으로 수행한다.
---
## 진행 계획
### 1단계: 토큰 기준 확정
**목표:** 모든 블록이 따라야 할 공통 기준표 확정
| 토큰 | 내용 |
|------|------|
| typography | 대목차/중목차/소목차/본문/캡션/footer 크기·굵기·줄간격 |
| spacing | padding/gap/indent/margin |
| colors 1층 | 공통 테마색 (title, body, border, card-bg, accent) |
| colors 2층 | 블록 의미색 (compare-left/right, role-a/b, pill-left/right) |
| layout | slide-base 위치값 (body-top, body-height, footer-bottom) |
### 2단계: slide-base CSS 분리
**목표:** slide-base를 프레임 전용으로 얇게
- HTML에서 `<style>` 추출 → 외부 CSS
- 직접값 → 토큰 변수로 교체
- slide-base = 대목차 + divider + body container + footer만 책임
### 3단계: 블록 작성 규칙 문서
**목표:** 블록화 시 참조할 기준 제공 (두 에이전트 간 계약서)
- 구조 스타일 OK (display:flex, grid, align-items, overflow)
- 직접값 금지 (hardcoded font-size/color/padding)
- 값은 토큰 참조 (`var(--font-body)`)
- 클래스명 규칙 (`.zone-title`, `.bul` 등)
- 슬롯 규칙 (zone_title, sub_title, body, bullets, image 등)
### 4단계: 폴더 구조 정리
**목표:** 프레임/구조/스타일/메타/자산 분리
```
templates/
├── base/ ← 프레임 (slide-base)
├── blocks/
│ ├── structures/ ← 구조 부품 (토큰 기반)
│ ├── recipes/ ← 조합형 구조
│ └── legacy/ ← 기존 블록 (점진 전환)
├── styles/
│ ├── tokens/ ← 공통 기준값
│ ├── themes/ ← 톤 변형 (light/dark/formal)
│ ├── base/ ← slide-base CSS
│ └── blocks/ ← 블록별 CSS
├── assets/ ← 이미지/SVG/텍스처
└── catalog/ ← 속성 테이블 (blocks/recipes/rules)
```
### 5단계: 기존 블록 점진 전환
**목표:** Figma 1:1 HTML → 토큰 기반 재사용 블록으로 전환
**5-1. 블록 분류 먼저**
| 분류 | 의미 | 전환 순서 |
|------|------|-----------|
| direct-fit candidate | 바로 사용 가능 | 먼저 (토큰 교체만) |
| recipe component candidate | 조합용 부품 | 다음 |
| rewrite required | 구조부터 다시 | 마지막 |
**5-2. 분류 결과에 따라 전환**
- `figma_to_html_agent/blocks/` 완료물을
- 토큰 기준에 맞춰 `templates/blocks/structures/`
- 한 번에 다 뜯지 않고 점진적으로
### 6단계: 파이프라인 연결
**목표:** TO-BE 프로세스 완성
- TF-IDF 기반 **direct-fit block 매칭**
- 미매칭 시 **recipe/composition 경로 전환**
- 토큰 기반 resize
- 입력→조정 반복 (fit 루프 확장)
- 시각 품질 검증 강화
**Source of Truth:**
| 기준 | 위치 |
|------|------|
| 구조 판정 | `normalized.sections` |
| 블록 매칭 | `catalog/blocks.yaml` |
| 스타일 기준 | `styles/tokens/` + `styles/themes/` |
---
## 역할 분리 (동시 진행)
```
figma_to_html_agent (원재료) design_agent (조립 시스템)
───────────────────── ──────────────────────
"이 블록은 무엇인가" "이 블록을 언제 쓸까"
구조 유형 발견 토큰 기준 확정
슬롯 정의 catalog 필드 설계
자산 메타 direct-fit vs recipe 규칙
seam/crop/anchor theme/token 적용
fit/validation
↓ 접점: 블록 작성 규칙 문서 (3단계) ↓
블록화 시 이 규칙에 맞춰 전환
```
File diff suppressed because it is too large Load Diff
@@ -1,285 +0,0 @@
# Phase Z — composition planning schema plan (Layer A + Layer B)
**Status** : v1 plan (2026-04-30 refactor — SPEC v1 의 Layer A + Layer B 구조 반영). schema 작업 *범위 / 순서 / 완료 기준* 정의. 구현 X.
**Anchor** : [`PHASE-Z-PIPELINE-STATUS-BOARD.md`](PHASE-Z-PIPELINE-STATUS-BOARD.md) 의 핵심 missing — Step 3 / 4 / 11 + Step 10 (부분).
> **v0 → v1 변경 요약**
> - SPEC v1 의 renumbered section 으로 cross-reference 일관 갱신 (§1 / §2 신규 / §3 / §4 / §5 …)
> - Step 4 의 schema 가 SPEC v1 §2 (Internal Region — Layer A) 로 *이미 정의됨* → 본 plan 에서는 *신규 작성* 이 아니라 *완성도 점검* 으로 전환
> - Step 11 의 schema 가 SPEC v1 §4 (2-stage placement: Stage A → Stage B) 로 재정의됨에 따라 validation 기준 보강
> - mechanical rename : `sub_zone` → `Frame Slot` (PLAN 본문). YAML 필드명 `sub_zones` 는 코드 reality 로 유지
> - 4 step 범위 (Step 3 / 4 / 10-partial / 11) 그대로. Step 10 의 density envelope 제외 그대로
> - implementation / MDX 실행 검증 / AI 호출 / 파일명 변경 / code marker 이름 결정 — *모두 제외 그대로*
---
## 0. 문서 역할 분리
| 문서 | 역할 |
|---|---|
| [`PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md`](PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md) (v1) | schema / contract *명세 자체* (authoritative) |
| **본 문서 (`PHASE-Z-CONTENT-OBJECT-SUBZONE-PLAN.md`)** (v1) | 그 schema 작업을 *어떤 범위 / 순서 / 산출물 / validation 기준* 으로 진행할지 *계획* |
본 plan 은 SPEC 을 *대체하지 않음*. SPEC 의 어느 section 이 어느 step 의 schema 인지 *cross-reference + 완성도 점검* 까지가 본 plan 의 영역.
---
## 1. 목적
일반 MDX 1 파일이 *render 전 단계* 에서 typed content_object → Internal Region → Frame Slot 으로 결정 가능한 상태에 도달하도록, 다음 4 step 의 schema 정의를 *완결* 시킨다.
```
Step 3. Content Object 추출
Step 4. Section Internal Composition Planning (Layer A)
Step 10. Frame Contract 의 accepted_content_types + Frame Slot 선언 (부분, Layer B)
Step 11. Content Unit / Child Group → Internal Region → Frame Slot Mapping (2-stage)
```
---
## 2. 범위
### 2.1 포함
| Step | schema 항목 | SPEC v1 의 위치 |
|---|---|---|
| Step 3 | content_object base schema + 6 type 별 schema (text_block / table / transform_table / image / diagram / details) | SPEC v1 §1.1, §1.2 |
| Step 3 | role 의미 (summary / detail / decorative / reference) | SPEC v1 §1.3 |
| Step 4 | Internal Region entity schema (region_id / role / content_type / ratio_estimate / content_unit_ids / frame_match_strategy) | SPEC v1 §2.1 |
| Step 4 | Universal Region Model (every zone = 1+ regions) | SPEC v1 §2.2 |
| Step 4 | 3-way decision tree (whole / group / split) | SPEC v1 §2.3 |
| Step 4 | region 비율 산정 (content type 별 size proxy) | SPEC v1 §2.4 |
| Step 4 | region → frame / display 매칭 interface (frame_match / display_only) | SPEC v1 §2.6 |
| Step 10 | frame contract 의 `accepted_content_types` 필드 schema | SPEC v1 §3.1 |
| Step 10 | Frame Slot 선언 schema (YAML 필드명 `sub_zones` — 의미 = Frame Slot) | SPEC v1 §3.1 |
| Step 10 | F13 / F29 / F16 의 Frame Slots 선언 예시 | SPEC v1 §3.2 |
| Step 11 | placement algorithm 2-stage I/O schema | SPEC v1 §4.1 |
| Step 11 | Stage A — content → Internal Region (Layer A 매핑) | SPEC v1 §4.2 |
| Step 11 | Stage B — Internal Region content → Frame Slot (Layer B 매핑) | SPEC v1 §4.3 |
| Step 11 | tie-break (Frame Slot 단위) | SPEC v1 §4.4 |
| Step 11 | display_only region 처리 (frame 우회) | SPEC v1 §4.5 |
| Step 11 | display_strategy schema (inline_full / inline_preview_with_details / details_only / dropped) | SPEC v1 §5.1, §5.5 |
| Step 11 | display_strategy 의 region-level + slot-level 양쪽 적용 | SPEC v1 §5.3 |
### 2.2 Step 10 의 *부분 범위* 명시 (불변)
> 본 plan 에서 Step 10 은 **두 항목** 까지만 다룸 :
> - `accepted_content_types`
> - Frame Slot 선언 (YAML 필드명 `sub_zones`)
>
> 본 plan 에서 *다루지 않는* Step 10 항목 :
> - `density envelope`
>
> `density envelope` 은 `frame_internal_fit_candidate` (Step 17 / Step 19 Gap) 와 연결된 *frame internal fit policy* 영역. 본 plan 에 넣으면 *공통 padding 축소 antipattern* (`feedback_phase_z_spacing_direction`) 과 섞일 risk 있어 *별 plan* 으로 분리.
>
> 따라서 본 plan 이 완료되어도 Step 10 전체가 ✅ 완료되는 것은 아니고 — `accepted_content_types` + Frame Slot 부분만 ✅ / density envelope 부분은 ❌ 잔존.
### 2.3 명시적 제외
- 구현 (extractor / planner / parser 코드 작성) — 본 plan 은 schema 까지
- frame partial template 변경 (Frame Slot / region container marker 추가)
- mapper / classifier 의 region / Frame Slot-aware 진화
- details / popup runtime
- backward flow 자동화 (telemetry → composition 재호출)
- MDX 01 / 02 / 03 / 04 적용 검증
- AI 호출 (plan 단계 자체 + plan 산출 schema 자체 모두)
- code / module / HTML marker / attribute *이름 결정* (SPEC v1 와 동일 — implementation step 에서 결정)
- 파일명 변경 (PLAN / SPEC 둘 다 — 향후 별 결정)
- 다음 단계 우선순위 / A/B/C 선택지
---
## 3. 산출물
### 3.1 schema-side 산출물
본 plan 의 schema 작업이 완료되면, SPEC v1 가 다음 상태에 도달 :
| schema | 현재 SPEC v1 상태 | plan 종료 후 목표 상태 |
|---|---|---|
| content_object base + 6 type 별 schema | draft (SPEC v1 §1.1, §1.2) | completeness reviewed + gaps listed |
| role 의미 4 종 | draft (SPEC v1 §1.3) | completeness reviewed + gaps listed |
| Internal Region entity + Universal Region Model | draft (SPEC v1 §2.1, §2.2) | completeness reviewed + gaps listed |
| 3-way decision tree | draft (SPEC v1 §2.3) | completeness reviewed + gaps listed |
| region 비율 산정 | draft (SPEC v1 §2.4) | completeness reviewed + gaps listed |
| region → frame / display 매칭 interface | draft (SPEC v1 §2.6) | completeness reviewed + gaps listed |
| accepted_content_types + Frame Slot 선언 | draft (SPEC v1 §3.1, 3 frame 예시 §3.2) | completeness reviewed + gaps listed |
| 2-stage placement (Stage A + Stage B I/O) | draft (SPEC v1 §4.1, §4.2, §4.3) | completeness reviewed + gaps listed |
| tie-break + display_only region 처리 | draft (SPEC v1 §4.4, §4.5) | completeness reviewed + gaps listed |
| display_strategy 4 종 + region/slot 양쪽 적용 | draft (SPEC v1 §5.1, §5.3, §5.5) | completeness reviewed + gaps listed |
### 3.2 plan-side 산출물
- 본 PLAN 문서 (v1) — schema 작업 범위 정의
- 작업 결과로 SPEC v1 의 *추가 / 수정 부분* (edge case 보강 등 — substantive 신규 schema 가 아닌 점검 결과)
- pipeline data flow 위치 표 (§5 참조)
- schema validation 기준 표 (§6 참조)
---
## 4. SPEC ↔ PLAN cross-reference
본 plan 의 4 step 각각이 SPEC v1 의 어느 section 으로 매핑되는지 :
| Step | SPEC v1 section | plan 에서의 작업 |
|---|---|---|
| Step 3 | §1 (content_object schema) | *완성도 점검*. nested_list / sub-decomposition edge case (SPEC v1 §9.2 미해결 부분 — text_block 의 nested 구조를 sub_text_block 으로 sub-decompose vs Frame Slot cardinality aggregate 해석) |
| Step 4 | §2 (Internal Region schema, Layer A) | *완성도 점검*. 3-way decision 의 boundary 케이스 / region 비율의 fallback / frame_match_strategy 가 unknown / ambiguous 일 때 거동 |
| Step 10 (partial) | §3.1 (schema) + §3.2 (3 frame 예시) | *완성도 점검*. `density envelope` 미포함 명시 |
| Step 11 | §4 (placement algorithm 2-stage) + §5 (display strategy) | *완성도 점검*. Stage A → Stage B *interface* 정합 (Stage A 의 frame_match_strategy 가 Stage B 의 입력으로 자연 호환) / backward flow 자동화 X 등 v1 한계 재확인 |
---
## 5. Pipeline data flow 위치
본 4 step 의 schema 가 *어디서 생성 / 누가 소비* 하는지.
```
Step 1 MDX 업로드
Step 2 MDX 정규화 (section / heading / raw_content 분리)
├─→ Step 3. Content Object 추출
│ INPUT : section.raw_content (markdown 문자열)
│ OUTPUT : section.content_objects = [ContentObject ...]
│ SCHEMA : SPEC v1 §1
│ 소비자 : Step 4, Step 11
└─→ Step 4. Section Internal Composition Planning (Layer A)
INPUT : section.content_objects (from Step 3) + section metadata + V4 evidence
OUTPUT : zone.internal_regions = [
{region_id, role, content_type, ratio_estimate,
content_unit_ids, frame_match_strategy}, ...
]
SCHEMA : SPEC v1 §2 (entity / Universal Region Model / 3-way decision / 비율 / interface)
소비자 : Step 6 (composition planning) / Step 8 (region ratio) /
Step 9 (region-level frame match) / Step 11 (Stage A 결과)
Step 5 Matching Evidence 생성 (V4 top-k)
Step 6 Composition Planning (Step 4 region 분할 결과 입력)
Step 7 Slide-Level Layout Planning
Step 8 Zone + Internal Region Ratio Planning (region 비율 = Step 4 산출)
Step 9 Region-Level Frame / Display Selection (region 별 frame_match_strategy = Step 4 산출)
└─→ Step 10 (partial). Frame Contract 확인
INPUT : selected frame_id (region 별)
OUTPUT : frame_contract.accepted_content_types
+ frame_contract.sub_zones (= Frame Slots, Layer B)
SCHEMA : SPEC v1 §3
소비자 : Step 11 (Stage B)
└─→ Step 11. Content Unit / Child Group → Internal Region → Frame Slot Mapping (2-stage)
INPUT : section.content_objects (from Step 3)
+ zone.internal_regions (from Step 4)
+ frame_contract.sub_zones (= Frame Slots) (from Step 10, frame_match region 만)
Stage A : content → Internal Region (Step 4 의 region 분할 결과 소비)
Stage B : Internal Region content → Frame Slot (frame_match region 만 진입)
display_only region → display strategy 처리 (Stage B 우회)
OUTPUT : placement = {
internal_regions: [
{..., slot_assignments[], overflow_buffer[], rejection[]}, ...
]
}
SCHEMA : SPEC v1 §4 + §5
소비자 : Step 12 (slot payload 생성, region + Frame Slot 단위 grouping)
Step 12 Slot Payload 생성
Step 13 Render
...
```
---
## 6. schema validation 기준
> validation = *schema 자체의 구조 검증* 까지만. *실제 MDX 적용 검증 X* (sample budget rule).
### 6.1 Step 3 — content_object schema
- [ ] base schema 의 모든 필수 필드 (id, type, role, size_estimate, raw_payload, type_specific) 정의됨
- [ ] 6 type 모두 type_specific schema 있음 (text_block / table / transform_table / image / diagram / details)
- [ ] role 4 종 (summary / detail / decorative / reference) 정의됨
- [ ] role 별 fallback 거동 (decorative drop / detail → details escalate / summary·reference rejection) 명시됨
- [ ] *원문 raw_payload 보존* 룰이 schema 에 포함됨 (자름 / 변형 X)
### 6.2 Step 4 — Internal Region schema (Layer A)
- [ ] Internal Region entity 의 모든 필수 필드 (region_id, role, content_type, ratio_estimate, content_unit_ids, frame_match_strategy) 정의됨
- [ ] Universal Region Model 명시 (every zone has 1+ regions / single-region for text-only / multi-region for mixed-content)
- [ ] 3-way decision tree 의 3 분기 (whole-section frame match / child-section grouping / content-type split) 가 *결정론적 함수* 로 표현됨 (AI 판단 X)
- [ ] 각 분기의 판단 기준 (cardinality / accepted_content_types / heading depth + content 구조) 명시됨
- [ ] region 비율 산정의 size proxy (text_block: line_count / table: rows × line_height / transform_table: pair_count × pair_height / image: aspect_ratio / details: summary line_count) 정의됨
- [ ] zone 내 region ratio 합 = 1.0 normalize 룰 명시됨
- [ ] frame_match_strategy 의 두 kind (frame_match / display_only) 정의됨
- [ ] display_only path 가 frame contract 없이 동작 가능함이 명시됨
- [ ] decision 이 unknown / ambiguous 일 때의 fallback 명시됨
### 6.3 Step 10 (partial) — frame contract schema (Layer B 선언)
- [ ] `accepted_content_types` 필드 schema 정의됨 (list of type 이름)
- [ ] `not_accepted` 필드 schema 정의됨 (디버그용)
- [ ] Frame Slot 선언 schema 정의됨 (id / role / accepts / cardinality / partial_target_path) — YAML 필드명 `sub_zones`, 의미 = Frame Slot
- [ ] cardinality 표현 방식 (`strict` 또는 `min`/`max`) 정의됨
- [ ] F13 / F29 / F16 3 frame 의 Frame Slot declaration 예시 *완비*
- [ ] *density envelope 미포함* 이 schema 위에 명시 주석으로 박혀 있음
### 6.4 Step 11 — placement (2-stage) + display_strategy schema
- [ ] placement algorithm 2-stage I/O schema 정의됨 (input + Stage A 출력 + Stage B 출력)
- [ ] Stage A schema 정의됨 (content_object → Internal Region 매핑 결과 + frame_match_strategy 결정)
- [ ] Stage B schema 정의됨 (Internal Region content → Frame Slot 매핑 결과 — frame_match region 만)
- [ ] sorting / type 매칭 / cardinality 적용 / role 우선순위 / tie-break 의 결정론적 룰 (Stage B) 명시됨
- [ ] display_only region path 의 display_strategy 매핑 정의됨 (image area / table preview / details button / diagram inline)
- [ ] 6 type × 3 escalation (inline / preview+details / popup-only) 매트릭스 정의됨
- [ ] display_strategy 4 종 (inline_full / inline_preview_with_details / details_only / dropped) 정의됨
- [ ] display_strategy 가 *region-level + slot-level 둘 다* 적용됨이 명시됨
- [ ] *AI 호출 X* + *원문 손실 금지* 룰이 schema 위에 명시됨
- [ ] backward flow 자동화 X (v1 한계) 가 schema 위에 명시됨
### 6.5 통합 validation
- [ ] Step 3 → Step 4 의 type 호환 — content_object.type 이 Internal Region.content_type 으로 *결정론적* 매핑
- [ ] Step 3 → Step 11 (Stage B) 의 type 호환 — Step 3 의 모든 type 이 어딘가 frame.accepted_content_types 에 등장 가능 (또는 명시적 reject) 또는 display_only path 로 처리 가능
- [ ] Step 4 → Step 8 의 ratio 호환 — Internal Region.ratio_estimate 가 Step 8 의 region-level ratio 입력으로 호환
- [ ] Step 4 → Step 9 의 frame_match_strategy 호환 — Step 9 의 region-level frame 매칭 입력으로 호환
- [ ] Step 4 → Step 11 (Stage A) 의 region 분할 호환 — Stage A 가 Step 4 의 internal_regions 를 *재계산하지 않고 그대로 소비*
- [ ] Step 10 의 cardinality 가 Step 11 Stage B 의 placement algorithm 이 소비 가능한 형태
---
## 7. AI 원칙
- 본 plan 작성 / schema 정의 단계 — **AI 호출 없음**
- runtime AI = Step 12 의 *light_edit / restructure 의 content_object → Internal Region / Frame Slot proposal* 1 곳만
- Step 0 (Figma → HTML 변환 등 사전 준비) 의 AI 사용은 *precondition phase* 로, runtime AI 가 아님
---
## 8. 금지
- 구현 금지 (extractor / planner / parser 코드 작성)
- render 변경 금지
- frame partial 변경 금지 (Frame Slot / region container marker 추가 미포함)
- mapper / classifier 의 region / Frame Slot-aware 진화 금지
- details / popup runtime 작성 금지
- MDX 01 / 02 / 03 / 04 실행 금지
- AI 호출 금지
- code / module / HTML marker / attribute *이름 결정* 금지 (SPEC v1 와 동일 — implementation step 에서 결정)
- 파일명 변경 금지
- next step 추천 금지
- 우선순위 결정 금지
- A / B / C 선택지 제시 금지
---
## 9. 본 plan 의 보존 / 변경 정책
- 본 plan 은 *schema 작업 범위 + 완료 기준* 의 기준점. schema 작업 진행 중 새로운 edge case 발견 시 본 plan 의 6 절 (validation 기준) 에 *항목 추가* 형태로 갱신
- *범위 확장* (예: Step 7 추가, density envelope 포함, code marker 이름 결정 포함) 은 사용자 명시 잠금 후에만
- 본 plan 이 완료되면 STATUS-BOARD 의 :
- Step 3 → ⚠ partial (schema 정의 완료, 구현 미완)
- Step 4 → ⚠ partial (Layer A schema 정의 완료, 구현 미완)
- Step 11 → ⚠ partial (2-stage schema 정의 완료, 구현 미완)
- Step 10 → ⚠ partial (accepted_content_types + Frame Slot 부분 완료, density envelope 미포함 잔존)
- SPEC 의 *추가 갱신* (edge case 보강 등) 은 본 plan 의 §3.1 목표 상태 (completeness reviewed + gaps listed) 의 자연 산출물
@@ -1,849 +0,0 @@
# Phase Z-2 — content composition planning spec (Layer A: Internal Region + Layer B: Frame Slot)
**Status** : v1 spec (2026-04-30 refactor — Layer A / Internal Region 추가 + Layer B / Frame Slot 명확화). 정의만. 구현은 별도 step (사용자 승인 후).
> **v0 → v1 변경 요약**
> - Zone Internal Region (Layer A) 를 first-class entity 로 추가 (§2 신규)
> - 기존 §2 ~ §8 → §3 ~ §9 로 renumber (v0 의 ## 9 다음 step → v1 의 ## 10)
> - 기존 `sub_zone` 단어 = *Frame Slot (Layer B)* 의미로 일관 정리. *YAML 필드명 `sub_zones` 는 코드 reality 로 유지 — 의미만 명시*
> - placement algorithm (§4) 을 *2-stage* (Stage A: content → Internal Region / Stage B: Internal Region content → Frame Slot) 로 재작성
> - content_object schema (§1) / display strategy 어휘 (§5) / telemetry 구조 (§6) 는 *layer-agnostic 공유 개념* 으로 보호 — substantive 미변경
> - code / module / HTML marker 이름은 *implementation step* 으로 defer
---
## §0. 목적 / 위치
본 spec 은 **render *전* composition planning layer** 의 정의. fit_classifier / overflow_router / zone_ratio_retry 같은 *post-render telemetry* 가 아니라, *애초에 content 를 어디에 어떻게 배치할지* 결정하는 *진짜 fit policy 의 중심*.
```
1. PLANNING (composition) ← 본 spec 의 영역
- section raw_content → content_object 정규화
- Zone Internal Region (Layer A) 분할 — text/table/image/details 에 따라
- frame contract → accepted_content_types + Frame Slot (Layer B) 선언
- content_object → Internal Region → Frame Slot 배치 (compatibility 기반)
- inline preview vs details/popup 표시 전략
2. RENDER (Jinja2 + frame partial — Frame Slot aware)
3. POST-RENDER TELEMETRY (A1~A4) ← 별 spec, 이미 구축됨
- Selenium → fit_classifier → router → retry → failure_classifier → next_action
- 1 단계 (본 spec 영역) 가 정밀하면 거의 trigger 안 됨
- exception 케이스의 *진단 + 다음 capability 안내*
```
### Layer 구분
```
Slide → Zone → Internal Region → Frame → Frame Slot → Content
──────────── ──────────
Layer A Layer B
```
- **Layer A — Zone Internal Region** : Zone *내부* 영역, frame **. content type 기반 분할 (text region / table region / image region / details region). region 별 frame 또는 display strategy 선택.
- **Layer B — Frame Slot** : frame *내부* 자리 (= F13 의 pillar_1, F29 의 process_column 등). frame 안에서 content unit 이 들어갈 곳.
Layer A 와 Layer B 는 *별개 entity* 가 아니라 *한 composition pipeline 의 두 sub-phase*. content_object schema / display strategy 어휘 / telemetry interface 는 *공유*.
### 본 spec 의 핵심 원칙
- *render 전* 결정. *render 후 retry* 가 아님
- content_object 의 *type* 이 핵심 — text / table / image / diagram / details
- Layer A 가 *region 분할* 결정 / Layer B 가 *region 안 Frame Slot 매핑* 결정
- 매칭 안 되는 content 는 *details/popup 으로 escalate*. *원문 삭제 / 압축 X*
- frame 의 *Frame Slot* 이 어떤 type 을 받을 수 있는지 *명시적으로 선언*
본 spec 은 *정의만*. 구현 우선순위는 별도 step (사용자 승인 후).
---
## §1. content_object 정규화 schema
> **layer-agnostic** — Layer A / Layer B 둘 다 사용. v0 → v1 refactor 시 *substantive 미변경*.
MDX section 의 `raw_content` (markdown 문자열) 를 *typed content_object list* 로 정규화.
### 1.1 base schema
```yaml
section:
section_id: str
title: str
content_objects:
- id: str # section 내 unique
type: str # text_block / table / image / diagram / details / transform_table
role: str # summary / detail / decorative / reference
size_estimate:
line_count: int # text/details 의 경우
rows: int # table 의 경우
aspect_ratio: float # image/diagram 의 경우
bytes: int # raw payload 크기 (heuristic용)
raw_payload: str # 원본 (자름 / 변형 X)
type_specific: {...} # 아래 type 별 schema
```
### 1.2 type 별 schema
#### `text_block` — 자유 텍스트 / 불릿
```yaml
type: text_block
type_specific:
format: paragraph | bullet_list | nested_list
bullet_count: int # 불릿이면
max_indent_level: int
has_emphasis: bool # **bold** 등 inline emphasis
```
#### `table` — markdown 표
```yaml
type: table
type_specific:
rows: int # header 제외 데이터 row
cols: int
header_present: bool
is_transform: bool # AS-IS / arrow / TO-BE 구조면 true → transform_table 으로 분류
raw_md: str # 원본 markdown
```
#### `transform_table` — AS-IS / TO-BE pair (`table` 의 specialization)
```yaml
type: transform_table
type_specific:
pair_count: int # 행 수 (각 행 = 1 transform pair)
arrow_glyph: str # ➠ 등
rows: [{from: str, arrow: str, to: str}]
```
#### `image` — markdown / HTML 이미지
```yaml
type: image
type_specific:
src: str
alt: str
aspect_ratio: float | null # 알면 (asset metadata 에서)
intrinsic_width_px: int | null
intrinsic_height_px: int | null
```
#### `diagram` — SVG / 도식
```yaml
type: diagram
type_specific:
source_type: svg_inline | svg_file | mermaid | other
src: str | null
```
#### `details` — `<details>/<summary>` 또는 ":::note[...]" 같은 명시 marker
```yaml
type: details
type_specific:
summary: str # 펼치기 전 보일 헤더
body_raw: str # 펼친 후 content (자름 X)
display_hint: button | inline_collapse | popup # MDX 가 hint 줄 수 있음
```
### 1.3 role 의미
- `summary` — section 의 *핵심 메시지*. inline 으로 반드시 표시
- `detail` — 보조 / 부연. 공간 부족 시 details 로 escalate 가능
- `decorative` — 시각 보조 (배경 이미지 등). 공간 부족 시 *생략 가능*
- `reference` — 출처 / footnote / 보충 자료
### 1.4 정규화 parser 위치
신규 module (이름 *implementation step 에서 결정* — defer) :
- `extract_content_objects(section: MdxSection) -> list[ContentObject]`
- markdown AST parser (예: mistune) 활용 또는 regex 기반 v0
- 현재 `align_sections_to_v4_granularity` 다음 단계에 삽입
---
## §2. Zone Internal Region schema (Layer A — 신규)
본 section 은 **v1 신규**. Zone *내부* 영역 (frame **) 의 entity 정의 + 3-way decision tree + region 비율 + region → frame/display interface.
### 2.1 Internal Region entity schema
```yaml
zone:
zone_id: str
layout_position: str # top / bottom / left / right / ...
internal_regions:
- region_id: str # zone 내 unique
role: str # primary / secondary / supporting / reference
content_type: str # text / table / image / diagram / details / mixed
ratio_estimate: float # 0.0 ~ 1.0 (zone 내 비율, 합 = 1.0)
content_unit_ids: [str] # 이 region 에 배치된 content_object id 들 (Layer A → B 의 입력)
frame_match_strategy: # region → frame/display 매칭 결과
kind: str # frame_match | display_only
frame_id: str | null # frame_match 이면 실제 frame
display_strategy: str # inline_full | inline_preview_with_details | details_only | dropped
```
### 2.2 Universal Region Model
```
모든 Zone 은 1 개 이상의 Internal Region 을 가짐.
text-only zone = single-region zone (현 거동의 자연 표현)
mixed-content zone = multi-region zone
각 Internal Region 은 *자기만의* frame match + display strategy 를 가질 수 있음.
```
text-only section 도 *single-region zone* 으로 표현 (= 현 거동 보존). mixed-content (text + table / text + image / 등) 은 *multi-region zone* 으로 확장. region 이 *first-class entity* — special case 가 아님.
### 2.3 3-way decision tree
각 section 에 대해 *Internal Region 분할* 여부 결정 :
```
section 전체 → 1 frame 매칭 가능?
├ YES → whole-section frame match
│ → single-region zone (region 1개, content_type=primary)
└ NO → child-section grouping 가능?
├ YES → group merge → 1 frame 매칭
│ → single-region zone (region 1개, content_type=primary)
└ NO → content-type split
→ text region / table region / image region / details region
→ region 비율 산정 (예: text 80% / table 20%)
→ multi-region zone (region 2~N개)
```
**판단 기준** :
- *whole-section frame match* — section 전체와 frame contract 의 accepted_content_types 가 호환 + cardinality 가 맞는 경우
- *child-section grouping* — sibling section 들이 같은 frame contract (예: F16 의 4-quadrant) 와 묶이는 경우. heading depth + content 구조 + frame cardinality 로 판단
- *content-type split* — section 안에 *호환 안 되는 content type 조합* 이 있을 때 (text + table 처럼)
### 2.4 region 비율 산정
content type 별 *expected size* 기반 :
| content type | size proxy |
|---|---|
| `text_block` | line_count (text_block.size_estimate.line_count) |
| `table` | rows × line_height_factor (rows × 1.2 ~ 1.5) |
| `transform_table` | pair_count × pair_height |
| `image` | aspect_ratio 기반 height (width 고정 시) |
| `diagram` | aspect_ratio 기반 height |
| `details` | summary line_count (펼치기 전) |
*zone 내 합 = 1.0* 으로 normalize. role 가중치 (primary > supporting) 는 v1 에서 균등 — *별 step refinement*.
### 2.5 Internal Region Layout / Topology Vocabulary
region 들의 *공간 배치 패턴* 어휘. multi-region zone 의 *방향 / 배치* 결정. ratio 와 content_type 만으로는 *어떻게 배치되는지* 가 결정 안 되므로 *vocabulary 단계* 가 명시적으로 필요.
#### 명명 style — slide-level vs region-level 의 *의도된 비대칭*
- **Slide-level 8 vocabulary** (Step 7) = *count-based* 명명 (`horizontal-2`, `top-1-bottom-2`). zone 의 *layout-driven* 성격 반영
- **Region-level vocabulary** (본 §) = *descriptor-based* 명명 (`vertical-stack`, `main-support`). region 의 *content-type / role-driven* 성격 반영
- 이 비대칭은 *의도된 것*. region count 가 작고 (1~4) content type / role 이 핵심 결정 기준이라 descriptor 가 더 의미 전달
#### v1 vocabulary (6 entry)
| region_layout_type | 의미 | 사용 조건 |
|---|---|---|
| `region-single` | 1 region 만 (zone 전체 = region 1개) | region count = 1 (single-region zone) |
| `region-vertical-stack` | 위·아래 수직 stack | region count ≥ 2, content type 이 *순차적 흐름* (예: 본문 + supporting). default fallback |
| `region-horizontal-split` | 좌·우 수평 분할 | region count = 2, content type 이 *대등 비교* 또는 *side-by-side 시각* (text + image, text + diagram 등) |
| `region-main-support` | main region + supporting region (asymmetric ratio) | region count = 2, role = [primary, supporting], ratio asymmetric (예: 0.7 / 0.3) |
| `region-preview-details` | inline preview region + details/popup region | details_presence = true, 또는 큰 content (table N ≥ 5, long text 등) |
| `region-grid-2x2` | 2×2 grid (4 region) | region count = 4, content type 이 *대등 4 항목* |
#### deterministic decision rule
`region_layout_type` 은 AI 호출 X. 다음 *결정론적 함수* 로 도출 :
```
입력 :
- region_count : int
- content_type_mix : list[str]
- ratio_estimate : list[float]
- role 분포 : list[str] (primary / supporting / ...)
- details_presence : bool
결정 분기 (순차 적용, 첫 매칭 채택) :
1. region_count == 1
→ region-single
2. details_presence == true 또는 큰 content (table N ≥ 5 / long text 등)
→ region-preview-details
3. region_count == 4 AND content_type_mix 가 *4 종 대등*
→ region-grid-2x2
4. region_count == 2 AND role == [primary, supporting] AND ratio asymmetric (max / min ≥ 2)
→ region-main-support
5. region_count == 2 AND content_type_mix 내 visual element (image / diagram) 포함
→ region-horizontal-split
6. fallback (위 모든 분기 미매칭)
→ region-vertical-stack
```
#### 출력 schema
각 zone 의 `internal_regions` 컨테이너에 region_layout 필드 추가 :
```yaml
zone:
zone_id: str
internal_regions: [...]
region_layout:
region_layout_type: str # 위 6 entry 중 하나
region_order: [str] # region_id 의 배치 순서 (위→아래 / 좌→우 등)
region_placement: str # vertical | horizontal | grid | main-side | stack
```
#### 구현 위치 (예정)
신규 module (이름 *implementation step 에서 결정* — defer) :
- input : `zone.internal_regions` (from §2.1, ratio + content_type 산정 후)
- output : `zone.region_layout` (region_layout_type + order + placement)
- 위치 : §2.4 region 비율 산정 *직후*, §2.6 region → frame / display interface *직전*
- 결정 함수 deterministic — AI 호출 X
#### v1 vocabulary 의 한계 / 향후 확장 (참고)
- 현재 6 entry = v1 starting set. 추후 sample / frame DB 확장 시 vocabulary 추가 가능 (예: `region-vertical-3` / `region-horizontal-3` / `region-main-side-bottom` 등)
- v1 fallback (`region-vertical-stack`) 이 매칭 안 되는 패턴 발견 시 별 step 으로 entry 추가
### 2.6 region → frame / display 매칭 interface
각 region 은 다음 중 하나 :
- **frame_match** — region 의 content_type 에 호환되는 frame 선택. 매칭된 frame 의 contract 가 §3 의 입력. Stage B 진입
- **display_only** — frame 없이 display strategy 로 처리 (image area 직접 / table preview / details button). frame contract 미사용. Stage B 우회
현재 *runtime contract-registered / verified* frame set (F13 / F29 / F16) 은 모두 text region 만 수용. image / table / details region 은 현재 *display_only* path.
### 2.7 구현 위치 (예정)
신규 module (이름 *implementation step 에서 결정* — defer) :
- input : `section.content_objects` (from §1)
- output : `zone.internal_regions` (with ratio + frame_match_strategy) + `zone.region_layout` (from §2.5)
- 현재 composition planner 의 frame 매칭 *직전* 에 삽입
- region 분할 / 비율 산정 / topology vocabulary 선택 / 매칭 기준 — 모두 deterministic rule 기반 (AI 호출 X)
---
## §3. frame contract 확장 — accepted_content_types + Frame Slot (Layer B)
> **v0 의 §2 → v1 의 §3 (renumber)**. *Layer B / Frame Slot* spec.
`templates/phase_z2/catalog/frame_contracts.yaml`*2 개 신규 필드* 추가.
### 3.1 schema
```yaml
<template_id>:
... (기존 필드 그대로 — source_shape / cardinality / payload / visual_hints / ...)
# NEW : 이 frame 이 받을 수 있는 content type 들
accepted_content_types:
- text_block
- transform_table
- ...
not_accepted: # 명시적 비호환 (디버그용)
- image
- diagram
# NEW : frame 내부 Frame Slot 선언
# YAML field name = 'sub_zones' — 코드 / catalog reality 로 유지. 의미 = Frame Slot (Layer B).
sub_zones:
- id: str # Frame Slot 식별자
role: main_text | supporting_visual | label | details_button | ...
accepts: # 이 Frame Slot 이 받는 content_object type
- text_block
- transform_table
cardinality: # Frame Slot 내 capacity
strict: int # 정확히 N개
# or
min: int
max: int
partial_target_path: # frame partial template 에서 이 Frame Slot 의 위치
# 예 : "f29b__cell--left.row-1" — partial 안 marker (attribute name *implementation step 에서 결정*)
```
> **YAML field 이름** : 코드 / catalog reality 로 `sub_zones` 유지. 의미는 *Frame Slot* (= Layer B).
### 3.2 구체 예시 (현재 3 frame)
#### F13 — three_parallel_requirements
```yaml
three_parallel_requirements:
...
accepted_content_types: [text_block]
sub_zones: # = Frame Slots (Layer B)
- id: pillar_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
- id: pillar_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
- id: pillar_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
```
#### F29 — process_product_two_way
```yaml
process_product_two_way:
...
accepted_content_types: [text_block, transform_table]
sub_zones: # = Frame Slots (Layer B)
- id: process_column
role: main_text
accepts: [text_block, transform_table]
cardinality: { strict: 3 } # 3 sections per column
- id: product_column
role: main_text
accepts: [text_block] # product 쪽은 transform 안 받음 (현재 frame 의 시각적 구분)
cardinality: { strict: 3 }
```
#### F16 — bim_issues_quadrant_four
```yaml
bim_issues_quadrant_four:
...
accepted_content_types: [text_block]
sub_zones: # = Frame Slots (Layer B)
- id: quadrant_1
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
- id: quadrant_2
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
- id: quadrant_3
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
- id: quadrant_4
role: main_text
accepts: [text_block]
cardinality: { strict: 1 }
```
### 3.3 partial template 의 Frame Slot 마커
frame partial 의 HTML 에 Frame Slot 식별 marker 추가 — render 후 Selenium 이 Frame Slot 단위 측정 가능, A1~A4 의 정밀도 향상. *marker attribute name (예: `data-subzone` / `data-frame-slot` / 기타) 은 implementation step 에서 결정 — defer*.
---
## §4. placement algorithm — 2-stage (Layer A → Layer B)
> **v0 의 §3 → v1 의 §4 (renumber + 2-stage 재작성)**. *layer 순차 dependency*.
### 4.1 input / output
```
input :
section: { section_id, content_objects: [...] }
zone: { zone_id, layout_position }
available_frames: [...] # V4 top-k from Step 5
output :
internal_regions: [
{
region_id, role, content_type, ratio_estimate,
content_unit_ids: [...],
frame_match_strategy: { kind, frame_id, display_strategy },
# Stage B 결과 (frame_match region 만)
slot_assignments: [
{ content_object_id, frame_slot_id, display_strategy }
],
overflow_buffer: [...],
rejection: [...]
},
...
]
```
### 4.2 Stage A — content → Internal Region (Layer A)
> region 분할 결정 + content_object → region 배치.
```
1. 3-way decision (§2.3) 적용
- whole-section frame fit 가능 → single-region (Stage B 로 1 region)
- child-grouping 가능 → group merge → single-region
- content-type split 필요 → multi-region
2. multi-region 인 경우 :
- content_object.type 별로 region 분류 (text region / table region / image region / ...)
- 같은 region 안의 content_object 끼리 묶음
- region 별 ratio 산정 (§2.4)
3. region 별 frame_match_strategy 결정 :
- region.content_type 이 frame.accepted_content_types 에 매칭 가능 → frame_match
- 매칭 frame 없음 → display_only (image / table / details path)
4. 결과 : zone.internal_regions = [region_1, region_2, ...]
각 region 은 content_unit_ids + frame_match_strategy 를 가짐
```
### 4.3 Stage B — Internal Region content → Frame Slot (Layer B)
> region 의 content 를 frame 의 Frame Slot 에 배치. *frame_match_strategy.kind == "frame_match"* 인 region 에만 적용.
각 frame_match region 에 대해 :
```
1. content_object 정렬
- role 기준 우선순위 : summary > reference > detail > decorative
- 같은 role 내 raw_payload 등장 순서
2. content_object.type 이 frame.accepted_content_types 에 없는 것
→ rejection 으로 분리. 본 frame 부적합 신호 (Stage A 의 frame_match_strategy 재검토 신호)
3. 남은 content_object 를 Frame Slot 들에 배치
- 각 Frame Slot 을 순회 (frame contract 의 declaration 순서)
- Frame Slot.accepts 에 매칭되는 content_object 들에서
cardinality.strict 또는 max 수만큼 할당
- role 우선순위 높은 것부터
4. 배치 안 된 content_object
- role = decorative → 무조건 drop (생략)
- role = detail → overflow_buffer 로 (details/popup 후보)
- role = summary / reference → rejection (frame 부적합)
5. 결과 :
- slot_assignments : 정확히 무엇이 어디로
- overflow_buffer : details/popup 으로 escalate 할 candidate
- rejection : 본 frame 으로는 표현 불가 — frame_reselect 신호
```
### 4.4 매칭 충돌 / tie-break
Frame Slot 단위로 :
- 동일 Frame Slot 에 다수 content_object 후보 시 :
- role 우선순위 (summary > reference > detail)
- 동률 시 size_estimate 작은 것 우선 (fit 가능성 ↑)
- 동일 content_object 가 여러 Frame Slot 에 매칭 가능 시 :
- role 매칭 우선 (Frame Slot.role == content_object.role)
- 그래도 동률이면 contract declaration 순서 (앞쪽 Frame Slot 우선)
### 4.5 display_only region 의 처리
`frame_match_strategy.kind == "display_only"` 인 region 은 Stage B 우회. 대신 :
- image region → image area 직접 배치 (frame 없이, region 안에 직접 inline)
- table region → table preview (rows ≤ N inline) + 자세히보기 (rows > N popup)
- details region → details button + popup
- diagram region → diagram inline
display strategy 어휘는 §5 와 동일 — region-level 적용.
### 4.6 구현 위치 (예정)
신규 module (이름 *implementation step 에서 결정* — defer) :
- `plan_placement(section, zone, available_frames) -> Placement`
- composition planner 의 frame 매칭 *직후*, slot_payload 생성 *직전*
- Stage A → Stage B 순차 실행
- 결과를 slot_payload 생성 단계에 전달
---
## §5. 표시 전략 — inline preview vs details / popup escalation
> **v0 의 §4 → v1 의 §5 (renumber)**. **layer-agnostic** — region-level (Stage A) + slot-level (Stage B) 둘 다 적용. *어휘 미변경*.
### 5.1 결정 기준 (per content_object type)
| type | inline 가능 조건 | preview + details 전환 | popup-only 전환 |
|---|---|---|---|
| `text_block` | line_count ≤ Frame Slot capacity | line_count > capacity AND role=detail | role=detail AND line_count >> capacity (예: 20+) |
| `table` (rows N) | N ≤ 4 | 5 ≤ N ≤ 7 (preview 첫 N rows + details) | N ≥ 8 (popup-only) |
| `transform_table` | rows ≤ frame 의 transform Frame Slot capacity (보통 3) | rows > capacity, 일부 inline | rows >> capacity |
| `image` | aspect_ratio fit 가능 | 일부 frame 에서 inline + details 의 thumbnail | 거의 없음 (image 는 보통 inline 또는 drop) |
| `diagram` | Frame Slot 호환 | preview thumbnail + popup full | popup-only |
| `details` (already-marked) | inline 만 안 함 (정의상) | summary inline + body popup | summary + body popup |
### 5.2 *원문 손실 금지* 룰
- 표시 전략 결정은 *어디 보여줄지*. *content 자르지 / 압축 / 요약 X*
- inline preview 도 *raw_payload 의 일부* 만 빌려옴. 나머지는 details 로
- AI 호출 X — 모든 결정은 deterministic rule 기반
### 5.3 적용 layer
display strategy 어휘 (`inline_full` / `inline_preview_with_details` / `details_only` / `dropped`) 는 *동일* :
- **region-level** (Stage A 의 display_only region) — image area / table preview / details button 등
- **slot-level** (Stage B 의 frame_match region 안 Frame Slot 별 content) — Frame Slot 안 content 가 fit 안 되면 escalate
### 5.4 details / popup runtime
- frame partial 또는 region container 에 `<details>/<summary>` 또는 별 button + popup layer
- 단순 v0 : `<details>` 내장 — 클릭으로 펼침
- 향후 v1 : 별도 popup overlay (CLAUDE.md 의 자세히보기 원칙)
### 5.5 구현 위치 (예정)
placement planner (§4.6) 의 후속 단계 — 각 assignment / region 에 `display_strategy` 부착 :
- `inline_full` — content 전체 inline
- `inline_preview_with_details` — 일부 inline, 나머지 details
- `details_only` — summary 만 inline, content 는 popup
- `dropped` — decorative 가 공간 부족으로 생략
---
## §6. A1~A4 telemetry 와의 interface
> **v0 의 §5 → v1 의 §6 (renumber)**. *layer-agnostic*. *구조 미변경* — `sub_zone` 단어 mechanical rename + region-level metadata 추가.
본 composition layer 와 기존 telemetry layer (A1~A4) 가 *어떻게 흐르는지*.
### 6.1 forward flow (composition → render → telemetry)
```
section
↓ extract_content_objects
content_objects
↓ placement_planner (Stage A → Stage B)
placement {
internal_regions: [
{
region_id, content_type, ratio_estimate,
slot_assignments: [{content_object_id, frame_slot_id, display_strategy}],
overflow_buffer: [...],
rejection: [...],
}
]
}
↓ slot_payload 생성 (region + Frame Slot 단위로 grouping)
slot_payload (with region + Frame Slot metadata)
↓ render (frame partial — Frame Slot aware + region container aware)
HTML
↓ Selenium check
overflow signals
↓ A1 fit_classifier
categories
↓ A2 router
proposed_actions
↓ A3 retry / A4 failure_classifier / next_action
final_status
```
### 6.2 telemetry 에 전달되는 새 metadata
각 zone 의 debug entry 에 추가 :
```yaml
zone:
... (기존)
internal_regions: # Layer A
- region_id
content_type
ratio_estimate
frame_match_strategy
placement:
slot_assignments: [...] # 이 zone 에 어떤 content_object 가 어디 Frame Slot 으로
overflow_buffer: [...] # details 로 간 것
rejection: [...] # frame 부적합 후보
region_metrics: # Selenium 이 region 별로 측정 (Layer A)
- region_id
ch / sh / excess_y # region 단위 overflow
frame_slot_metrics: # Selenium 이 Frame Slot 별로 측정 (Layer B, frame_match region 만)
- frame_slot_id
content_object_id
ch / sh / excess_y # Frame Slot 단위 overflow
```
### 6.3 backward flow (telemetry → composition)
A4 의 `next_proposed_action``frame_internal_fit_candidate` 또는 `frame_reselect` 일 때 :
- composition layer 가 *재호출* 됨 (단, retry budget 별도)
- 다른 frame 또는 다른 placement 시도
본 v1 에서는 *backward flow 자동화 X* (구현 단계). placement 가 정확히 되어 있으면 telemetry 거의 trigger X.
### 6.4 fit_classifier 의 *content_type aware* 진화
현재 fit_classifier 는 *className → semantic_content_type* 매핑. 본 spec 적용 후 :
- Selenium 이 region marker / Frame Slot marker / content_object_id marker 를 읽음 (marker attribute name *implementation step 에서 결정* — defer)
- classifier 는 *content_object 의 type* 을 직접 알 수 있음
- 분류 정밀도 향상 (예: F29 의 frame_match region 안 Frame Slot 의 transform-block 이 transform_table content_object 임을 *직접* 알 수 있음 — 현재는 inner_content_signals 로 추론)
---
## §7. current code gap — 재사용 / 신규 분리
> **v0 의 §6 → v1 의 §7 (renumber)**. 신규 module 이름 *defer*.
### 7.1 이미 있는 것 (재사용)
- MDX parser : section 단위 (## / ### drilling)
- align_sections_to_v4_granularity
- composition planner (parent_merged_inferred 포함)
- frame_contracts.yaml + builder/parser registry
- mapper (catalog-driven slot_payload 생성)
- Jinja2 render
- 8-preset layout vocabulary
- A1~A4 telemetry chain
### 7.2 신규 필요
| 항목 | 위치 | 비고 |
|---|---|---|
| **content_object 정규화** | 신규 module (이름 *defer*) | markdown AST 또는 regex 기반 v0 |
| **Internal Region planner (Layer A)** | 신규 module (이름 *defer*) | 3-way decision + region 비율 + frame_match_strategy 결정 |
| **frame_contracts.yaml**`accepted_content_types` + `sub_zones` 필드 (= Frame Slot 선언) | catalog (기존 yaml 확장) | 3 frame (F13/F29/F16) 우선 |
| **placement_planner (Layer A → Layer B)** | 신규 module (이름 *defer*) | Stage A: content → Internal Region / Stage B: region content → Frame Slot |
| **display_strategy** 결정기 | placement_planner 내부 | inline_full / inline_preview_with_details / details_only / dropped |
| **frame partial 에 Frame Slot 마커** | `templates/phase_z2/families/*.html` | marker attribute name *defer* |
| **region container 마커** | `templates/phase_z2/slide_base.html` 또는 partial | region 단위 측정 marker, name *defer* |
| **details/popup runtime** | partial template 또는 base slide | `<details>` 우선, 추후 popup overlay |
| **fit_classifier 의 region / Frame Slot 인식** | `src/phase_z2_classifier.py` 확장 | inner_content_signals → region / Frame Slot 직접 read |
| **mapper 의 region / Frame Slot-aware slot_payload** | `src/phase_z2_mapper.py` 확장 | builder 들이 region + Frame Slot 그룹핑 인식 |
### 7.3 정의 vs 구현 분리
본 spec 은 *정의만*. 구현 axis 는 별도 step :
- B1. content_extractor (MDX → content_object 정규화)
- B2. internal_region_planner (Layer A — 3-way decision + 비율 + frame_match_strategy)
- B3. frame_contracts 의 accepted_content_types + sub_zones (= Frame Slot) 선언 (3 frame)
- B4. placement_planner (Layer A → Layer B 통합)
- B5. partial / region container marker 추가 + telemetry 연동 (이름 결정 포함)
- B6. details/popup runtime
각 axis 는 *별도 step*. 한 axis 씩 사용자 승인 후 진행.
> **module / marker / attribute 이름** : 본 spec 에서 *defer*. implementation step 에서 결정.
---
## §8. 본 spec 의 활용
> **v0 의 §7 → v1 의 §8 (renumber)**.
### 8.1 composition layer 의 룰북
향후 frame 추가 / content_object 변경 / Layer A 재분할 / Frame Slot 매핑 변경 시 본 spec 의 schema 를 따름. *임의 매핑 / hack 차단*.
### 8.2 telemetry 와의 cross-check
A1~A4 의 분류 결과 (`structural_minor_overflow` 등) 가 본 spec 의 placement 결과와 *일치하는가* 확인 가능. 불일치 = composition planning 의 *예상치 못한 케이스* — 진단 자료.
### 8.3 미사용 sample (MDX 01 / 02) 진단
본 spec 적용 후 MDX 01/02 를 돌리면 :
- 각 section 의 content_object 정규화 결과 visible
- 각 zone 의 Internal Region 분할 결과 visible (single vs multi)
- 어떤 content type 이 frame contract 에 없는지 (frame 추가 필요 신호)
- placement 의 rejection 비율 (frame coverage gap)
- overflow_buffer 의 details 후보 (popup runtime 필요 신호)
- display_only region 비율 (현재 frame DB 의 Layer A 미커버 영역)
이 정보가 *generalization validation* 의 진짜 신호.
---
## §9. MDX 03 의 case 를 본 spec 으로 검증 (illustrative)
> **v0 의 §8 → v1 의 §9 (renumber)**. mechanical rename + 2-stage 표현.
> MDX 03 = sample. *fix 대상 X*. 본 spec 룰의 *예시 적용*.
### 9.1 03-1 의 content_object 정규화 (예상)
```yaml
section_id: "03-1"
title: "1. DX 시행을 위한 필수 요건"
content_objects:
- id: "03-1.text-1"
type: text_block
role: summary
type_specific: { format: nested_list, bullet_count: 3 (top), nested_count: 7 }
size_estimate: { line_count: ~12 }
```
→ 1 content_object (text_block, role=summary).
### 9.2 03-1 의 Stage A → Stage B (F13 contract 적용)
**Stage A** :
- 3-way decision : section 전체가 F13 (3 pillars) 의 child grouping 으로 매칭 → *whole-section frame match*
- single-region zone, content_type=text, ratio=1.0
- frame_match_strategy = { kind: "frame_match", frame_id: "F13" }
**Stage B** :
- F13 sub_zones (= Frame Slots) : [pillar_1, pillar_2, pillar_3] (각 cardinality strict 1, accepts text_block)
- text_block 1 개 → 3 Frame Slot 에 어떻게 배치?
- 현재 mapper (`pillar_item` parser) 가 *implicit* 으로 top_bullet 3 개를 3 pillar 에 분배
- 본 spec 적용 시 : text_block 의 nested 구조를 *3 sub_text_block* 으로 sub-decompose 하거나, Frame Slot cardinality 를 *aggregate (3)* 으로 해석할지 결정 필요
- v1 단순화 : text_block 의 top-bullet 단위가 *implicit 한 sub-content_object* — 향후 explicit 화
### 9.3 03-2 의 case (transform_table 포함)
```yaml
section_id: "03-2"
content_objects:
- id: "03-2.transform-1"
type: transform_table
role: summary
type_specific: { pair_count: 3 }
- id: "03-2.text-1"
type: text_block
role: detail
type_specific: { bullet_count: 1 }
- id: "03-2.text-2"
type: text_block
role: detail
type_specific: { bullet_count: 1 }
- id: "03-2.text-3"
type: text_block
role: detail
type_specific: { bullet_count: 3 (large) }
- ... (product 쪽도 4 개)
```
**Stage A** :
- 3-way decision : section 전체가 F29 (process/product 2-column structure) 와 매칭 → *whole-section frame match*
- single-region zone, content_type=text+transform_table, ratio=1.0
- frame_match_strategy = { kind: "frame_match", frame_id: "F29" }
**Stage B** :
- F29 sub_zones (= Frame Slots) : [process_column (accepts: text_block + transform_table, cardinality 3), product_column (accepts: text_block, cardinality 3)]
- process_column → transform_table + 2 text_block (3 개)
- product_column → 3 text_block
- 모두 inline_full 로 표시
이건 *현재 mapper (column_with_transform / column_plain) 가 implicit 으로 하는 것* — 본 spec 이 *explicit 하게 표현*.
### 9.4 03-2 의 cell row 1 (transform_table) 의 10 px overflow 재해석
placement 가 explicit 하게 되어도 transform_table 이 row 1 cell 에 *콘텐츠 height 131 vs 가용 121* 인 건 변하지 않음.
**그러나** :
- placement 가 *transform_table 의 size_estimate* 를 미리 알면
- frame contract 의 Frame Slot 이 *expected_height* 를 declare 하면
- planning 단계에서 *"transform_table 이 row 1 Frame Slot 의 expected_height 초과한다"* 를 사전 감지 가능
- 그 시점에서 display_strategy = `inline_preview_with_details` 로 자동 전환 (3 transforms 중 2 inline + "1 더 보기")
- 또는 placement 가 *frame 부적합* 으로 판정 → frame_reselect 신호
*본 spec 의 §4 placement algorithm 에 size_estimate 기반 fit pre-check* 가 들어가면 — A1~A4 telemetry 가 *trigger 안 되는 정상 path* 가 됨.
이게 본 spec 이 가리키는 *진짜 fit policy 의 자리*.
---
## 10. 다음 step (사용자 결정)
본 spec v1 정의 후 구현 axis 후보 :
- B1. content_extractor (MDX → content_object 정규화)
- B2. internal_region_planner (Layer A — 3-way decision + 비율 + frame_match_strategy)
- B3. frame_contracts 에 accepted_content_types + sub_zones (= Frame Slot) 선언 (3 frame)
- B4. placement_planner (Layer A → Layer B 통합)
- B5. partial / region container marker 추가 + telemetry 연동 (이름 결정 포함)
- B6. details/popup runtime
각 axis 는 *별도 step*. 사용자가 우선순위 결정.
본 spec 자체는 *implementation 0 단계의 정의*. 다음 step 은 사용자가 잠근 후 진행.
@@ -1,220 +0,0 @@
# Phase Z-2 — fit_classifier / overflow_router spec
**Status** : v0 spec (2026-04-29). 정의만. 구현은 별도 step (사용자 승인 후).
---
## 0. 목적 / 위치
자동 파이프라인이 Selenium 으로 *detect 한* overflow / clipping 을 *어떤 pipeline action 으로 routing 할지* 결정하는 layer 의 spec.
현재 파이프라인은 detection 까지 정상 작동 (Selenium + debug.json 으로 신호 캡처). 그러나 detection 결과를 받은 직후 `sys.exit(1)` 으로 abort — **detection 과 action 사이의 decision layer 가 비어 있음**.
```
parse_mdx → align → composition (v0.2: capacity_fit) → render (Jinja2)
Selenium visual_runtime_check ← 기존 (detection)
🆕 fit_classifier ← 신규 (사실 분류)
🆕 overflow_router ← 신규 (정책 결정)
action :
- zone_ratio_retry ← 신규 미구현
- layout_adjust ← 신규 미구현
- details_popup_escalation ← 신규 미구현 (CLAUDE.md 의 <details> 원칙 활성)
- frame_reselect ← 신규 미구현 (V4 top-k 활용)
- adapter_needed ← composition v0.1.1 partial
- abort ← 기존 (현재 default)
```
**핵심 원칙** : classifier = *사실 분류* (이 overflow 가 어떤 종류인가), router = *정책 결정* (그 종류면 무엇을 할 것인가). 두 layer 분리 — 같은 분류가 context (retry 횟수 등) 에 따라 다른 action 을 요구할 수 있음.
---
## 1. fit_classifier 입력 schema
### 1.1 detection-side (기존 — 이미 캡처됨)
| 입력 | 출처 | 비고 |
|---|---|---|
| `clipped_inner: [{class_name, excess_x, excess_y, scrollWidth/Height, clientWidth/Height}]` | `run_overflow_check` Selenium JS | ✅ |
| zone 별 `overflowed`, `excess_y/x` | 같음 | ✅ |
| slide / slide_body level overflow | 같음 | ✅ |
### 1.2 composition-side (기존 — composition v0.2)
| 입력 | 출처 | 비고 |
|---|---|---|
| `unit.frame_template_id` / `contract_id` | composition + pipeline | ✅ |
| `capacity_fit` (item count, fit_status) | `mapper.compute_capacity_fit` | ✅ (v0.2) |
| `content_truncated_count` (zone 별) | pipeline 의 mapper 호출 후 | ✅ (v0.1.1) |
| zone size (`height_px`, `min_height_px`, `content_weight`) | `compute_zone_layout` | ✅ |
### 1.3 신규 입력 (이번 spec 에서 정의)
| 입력 | 비고 |
|---|---|
| `semantic_content_type` | className → 의미적 분류. §2 registry 참조 |
| `line_equivalent` | excess_y / 해당 element 의 line-height (1 줄 단위로 환산) |
| `structural_unit_drop_count` | structural_unit 중 *완전히 또는 부분적으로 잘린* 개수 |
| `retry_budget_used` (router 의 상태) | 같은 slide 에 대해 router 가 이미 시도한 retry 횟수 |
---
## 2. className → semantic content_type registry
| className 패턴 | semantic type | 설명 |
|---|---|---|
| `transform-block`, `transform-block__*`, `transform-row*` | `structural_unit` | paired comparison (AS-IS/TO-BE 한 쌍이 의미 단위). 행 단위 자르면 의미 깨짐 |
| `text-line`, `text-line--bullet`, `text-line--indent-*` | `text_flow` | 자유 wrap, 줄 단위 자르기 가능 |
| `*table*`, native `<table>` | `tabular` | 행/열 단위 의미 — 행 잘리면 의미 손실 |
| `f29b`, `f13b`, `f16b` (frame-family root) | `frame_internal` | frame 자체가 zone 안에 못 들어감 (zone level 문제) |
| `*__cell`, `*__pillar`, `*__quadrant` 등 frame 내부 cell | `frame_internal_cell` | frame 내부 cell 단위 (cell 내부 content 가 cell 경계 초과) |
| `*__title`, `*__section-title`, `*__banner`, `*__label` 등 | `frame_label` | 제목/라벨 단위 (text 와 비슷하지만 wrap 제약 있음) |
| `<img>`, `<svg>`, `*-bg` 등 | `visual_asset` | 시각 자산 (cropping 가능) |
| 매칭 안 됨 | `unknown` | classifier 가 보수적으로 처리 (가장 안전한 action 선택) |
본 registry 는 신규 module (예: `src/phase_z2_classifier.py`) 의 상수 또는 catalog yaml entry 로 구현.
---
## 3. fit_classifier 출력 taxonomy
### 3.1 카테고리 정의 (계산 가능한 룰)
| 카테고리 | 판정 룰 |
|---|---|
| `frame_capacity_mismatch` | composition 단계의 `capacity_fit.fit_status` ∈ {`strict_mismatch`, `exceeds_max`, `below_min`, `exceeds_truncate`}. → 이미 v0.2 가 잡고 있는 영역. 본 카테고리는 *post-render 검증 / 누락된 케이스 캐치* 용 |
| `structural_major_overflow` | content_type = `structural_unit` 또는 `tabular` AND `structural_unit_drop_count` ≥ 1 (1 개 이상 *완전 단위* 잘림) |
| `structural_minor_overflow` | content_type = `structural_unit` 또는 `tabular` AND `structural_unit_drop_count` < 1 (마지막 1 단위가 *부분만* 잘림, 즉 boundary spill) |
| `tabular_overflow` | content_type = `tabular` (위와 별도 — 표는 행 1개라도 잘리면 popup) |
| `layout_zone_mismatch` | content_type = `frame_internal` (frame root 자체 overflow) — zone 이 frame 을 못 담음 |
| `moderate_overflow` | content_type ∈ {`text_flow`, `frame_label`} AND `line_equivalent` ∈ (1.5, 4] |
| `minor_overflow` | content_type ∈ {`text_flow`, `frame_label`} AND `line_equivalent` ≤ 1.5 |
| `hard_visual_fail` | 위 어디에도 매핑 안 됨 OR retry budget 소진 |
### 3.2 분류 우선순위 (위에서 아래로)
1. `frame_capacity_mismatch` (composition 결과 우선)
2. `tabular_overflow` (표는 즉시 popup 영역)
3. `structural_major_overflow` (1+ 완전 단위 잘림)
4. `layout_zone_mismatch` (frame root level)
5. `structural_minor_overflow` (boundary spill — 양 작음)
6. `moderate_overflow`
7. `minor_overflow`
8. `hard_visual_fail` (fallback)
### 3.3 핵심 구분
- **structural_minor vs structural_major** : 부분만 잘렸나 (`< 1` unit) vs 완전 단위가 잘렸나 (`≥ 1` unit). 부분 잘림은 zone 을 조금 더 주면 fit 가능. 완전 단위 잘림은 의미 손실 — popup escalation.
- **structural vs moderate vs minor** : content type 이 *구조적 의미 단위* 인지 여부. 같은 px 양이라도 text_flow 는 minor, structural_unit 은 structural_minor 이상.
---
## 4. overflow_router action mapping
| 카테고리 | action | retry budget | fallback (안 풀리면) |
|---|---|---|---|
| `minor_overflow` | `zone_ratio_retry` (양보 가능 zone 식별 → compute_zone_layout 재실행) | 1 | escalate → `moderate_overflow` 처리 |
| `moderate_overflow` | `layout_adjust` (8-preset 중 다른 preset 검토 + zone ratio 재분배) | 1 | escalate → `structural_major_overflow` 처리 |
| `structural_minor_overflow` | `zone_ratio_retry` (구조 자르지 않도록 zone 키움) | 1 | escalate → `structural_major_overflow` 처리 |
| `structural_major_overflow` | `details_popup_escalation` (`<details>/<summary>` path) | N/A | popup 미구현 시 → `frame_reselect``adapter_needed` |
| `tabular_overflow` | `details_popup_escalation` 또는 `frame_reselect` (table-friendly frame 후보) | N/A | 없으면 `adapter_needed` |
| `frame_capacity_mismatch` | `frame_reselect` (V4 top-k rank 2+ 평가) | 1 | 없으면 `adapter_needed` |
| `layout_zone_mismatch` | `layout_adjust` 또는 `zone_ratio_retry` | 1 | escalate → `frame_reselect` |
| `hard_visual_fail` | `abort` (현재 sys.exit(1) 그대로) | — | — |
---
## 5. current code gap
### 5.1 이미 있어서 *재사용* 할 것
- detection (Selenium `run_overflow_check`) — clipped_inner / excess_y / className 모두 캡처
- composition v0.2 의 `compute_capacity_fit` (item count level)
- composition v0.1.1 의 `adapter_needed` catch (mapper FitError)
- debug.json 의 `slide_status` / `zones` / `candidates_summary` (입력 자료원)
- 8-preset layout vocabulary + `compute_zone_layout`
### 5.2 새로 만들어야 할 것
| 신규 항목 | 비고 |
|---|---|
| **content_type registry** (§2) | className → semantic type. classifier 의 핵심 입력 |
| **`fit_classifier` 모듈** | §1 입력 → §3 카테고리 |
| **`overflow_router` 모듈** | §4 카테고리 → action |
| `zone_ratio_retry` action 구현 | compute_zone_layout 의 retry path |
| `layout_adjust` action 구현 | preset 동적 변경 |
| `details_popup_escalation` 구현 | `<details>/<summary>` runtime + slot_payload 분리 룰 (큰 작업) |
| `frame_reselect` 구현 | V4 top-k 사용 (rank-2+ 평가) |
### 5.3 정의 vs 구현 분리
본 spec 은 **정의만**. 위 신규 항목 중 어느 axis 부터 구현할지는 *별도 step* 에서 사용자 승인 후. 한꺼번에 다 만들지 X.
---
## 6. 본 spec 의 활용 — *visual fix 결정의 검증 자료*
본 spec 은 *룰북*. 향후 overflow 발생 시 :
1. classifier 가 *어떤 카테고리* 인지 결정
2. router 가 *어떤 action* 인지 결정
3. action 이 *현재 구현되어 있나* 확인
4. 미구현이면 → "본 spec 의 이 path 미구현이라 처리 불가" 로 명확히 보고
**중요 활용** : 누군가 (Claude 든 사람이든) "padding 줄여서 끼우자" 같은 fix axis 를 제시하면 — 이 fix 가 본 spec 의 어느 action 에도 매핑되지 않음 → **자동 반려**. 본 spec 을 검증 자료로 가지면 *symptom-silencing fix* 가 들어올 자리가 없어짐.
---
## 7. MDX 03 의 10px clipping 을 본 spec 으로 분류 (검증용 sample, fix 대상 X)
> MDX 03 = sample instance. spec 룰의 *작동 검증* 용 — fix 대상 아님.
### 측정값
- excess_y = 10px (~0.6 줄, transform-row line-height 15.95px 기준)
- clipped element className = `transform-block` 의 마지막 row (cell 내부)
- semantic content_type = `structural_unit` (transform-row pair)
- structural_unit_drop_count = 0.6 (1 개의 마지막 row 가 부분만 잘림 — 완전 단위 1 개가 아님)
- composition `capacity_fit.fit_status` = `ok`
### 분류 적용 (§3.2 우선순위)
1. `frame_capacity_mismatch`? — capacity_fit ok → ✗
2. `tabular_overflow`? — content_type 이 tabular 아님 → ✗
3. `structural_major_overflow`? — drop_count `< 1` → ✗
4. `layout_zone_mismatch`? — frame_internal 아님 → ✗
5. `structural_minor_overflow`? — content_type = structural_unit AND drop_count `< 1`**✓**
**카테고리 = `structural_minor_overflow`**
### Action mapping 적용 (§4)
`zone_ratio_retry` (구조 자르지 않도록 F29 zone 을 더 키움)
### 현재 구현 상태
`zone_ratio_retry`**MISSING**. 따라서 본 spec 기준으로 MDX 03 은 *classifier 가 정상 작동하면 structural_minor_overflow 로 분류되고 zone_ratio_retry 로 routing 되어야 하는 case* 인데, *그 path 가 현재 미구현*. 따라서 정직한 상태는 `RENDERED_WITH_VISUAL_REGRESSION` 그대로 유지.
### 반례 검증 (이전 잘못된 fix)
이전에 시도했던 "transform-block padding 6→4 + transform-row padding 3→2" :
- 본 spec 의 어떤 action 에도 매핑되지 X (`density_reduce` 같은 action 자체가 없음)
- 따라서 본 spec 기준으로 **자동 반려되는 fix axis** — 들어올 자리 없음
이게 본 spec 이 *visual fix 결정의 검증 자료* 로 작동한다는 증거.
---
## 8. 다음 step (구현 우선순위 — 사용자 결정 영역)
본 spec 정의 후 구현 axis 후보 (사용자가 우선순위 결정):
- A. content_type registry + fit_classifier (분류 layer)
- B. overflow_router (정책 layer)
- C. `zone_ratio_retry` action (가장 자주 트리거될 action)
- D. `details_popup_escalation` (큰 작업 — 새 path)
- E. `frame_reselect` (V4 top-k 사용 layer)
각 axis 는 *별도 step* 으로 한 단위씩. 한꺼번에 묶지 X.
@@ -1,229 +0,0 @@
# Phase Z Frame Style Inventory
> Phase Z 가 계승할 **색감, 여백, 폰트 위계, 표 / 카드 / 다이어그램 스타일, pill / badge, SVG / CSS 구현 힌트** 를 추출하는 인벤토리.
>
> ⚠️ 이 문서는 **블록 매핑이 아니다.** Figma frame 은 디자인 레퍼런스 / 구조 패턴 / 슬롯 힌트로 본다 ([`FRAME-INTEGRATION-MAP.md`](FRAME-INTEGRATION-MAP.md) 참조).
>
> ⚠️ `Phase Z Target` 컬럼은 **결정이 아니라 후보**. 사용자 승인 후 프로모션 게이트에서 확정.
---
## 1. Source Policy
| 구분 | 위치 | 역할 |
|---|---|---|
| **메인 소스** | `figma_to_html_agent/blocks/{figma_id}/` | 32 frame (1171281171 제외) — Phase Z 스타일 추출의 1 차 출처 |
| **토큰 소스** | `templates/styles/tokens/` | `colors.css` / `spacing.css` / `typography.css` — Phase Z 에서 계승 / 조정 |
| **legacy 참고** | `templates/blocks/structures/` 등 | Phase Z 의 실제 조립 재료 X. 폐기 / 아카이브 방향. 스타일 / 시각 언어 참고만 |
추출 우선 순위 :
1. **변환 완료 frame** (`index.html` + `flat.md` 보유) — 실제 CSS 관찰 기반 스타일 추출
2. **미변환 frame**`Style Elements` / `Extracted Style Hints` / `Phase Z Target` 보류, 변환 완료 후 갱신
3. **MCP / Figma 직접 조회 사용 X**`figma_to_html_agent` 의 본업이므로 inventory 작성 단계에서 침범하지 않음
---
## 2. Frame Inventory — 컬럼 정의
| 컬럼 | 의미 |
|---|---|
| **Frame** | `FRAME-INTEGRATION-MAP.md` 의 row 번호 (01~32) |
| **Figma ID** | `figma_to_html_agent/blocks/` 디렉토리 ID |
| **Layout** | `layouts.yaml` controlled vocabulary |
| **Style Elements** | frame 안에서 *관찰되는* 스타일 요소 (gradient bar, pill, table header, radial node, soft shadow 등) |
| **Extracted Style Hints** | Phase Z 에 *계승할* 구체 힌트 (예 : "table header dark fill + white text", "card gap 12~16px") |
| **Phase Z Target** | **후보** 위치 (예 : `tokens/colors.css`, `styles/frame-patterns/table.css`, `svg-helpers/`). 결정 X |
| **Asset Notes** | 자산 의존도 / Phase Z 재사용 가능성 (예 : "타이틀 아이콘 PNG 1 개, conclusion box 는 CSS 변환 가능") |
| **Notes** | cardinality / optional slot / 변형 축 / 기타 |
### 작성 룰
1. **관찰 가능한 값만 작성** — 변환 frame 의 셀은 `flat.md` + `index.html`*실제 있는* 관찰값만 채운다. flat.md 깊이는 frame 마다 다를 수 있고, 빠진 항목 (예 : 변형 축 명시 없음) 은 채우지 않고 비운다. **다른 frame 깊이에 맞추기 위한 추론 / 추측 채움은 하지 않는다.**
2. **Phase Z Target 후보는 가능한 한 family 단위로 수렴** — 같은 layout family (표 / 카드 / 다이어그램 / 리스트 / banner) 의 frame 들은 동일한 target 파일 후보를 가리키게 작성. variant 차이는 별도 target 으로 쪼개기보다 `Notes` 에 메타로 남긴다. 표기는 항상 `(후보)` 접미사 — "만들 파일" 이 아니라 "수렴 위치 후보" 로 읽히게.
3. **scale / zoom 은 Notes 에 메타** — Figma 원본 폭이 1280 이 아닐 경우 `flat.md` 의 scale / zoom 값을 그대로 두 (raw px 는 원본 기준). Phase Z slide-body (≈1200×590) 에 적용 시 재계산이 필요하다는 사실을 `Notes` 에 한 줄 기록.
4. **redescription 금지**`analysis.md` 의 cardinality / slot / anchor / layout 설명, `FRAME-INTEGRATION-MAP.md` 의 비고 / 검토 상태를 Inventory 에 다시 베끼지 않는다. 같은 정보가 두 문서에 들어가면 drift 위험.
### 미변환 frame 의 셀 표기
- `Style Elements` : *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.*
- `Extracted Style Hints` / `Asset Notes` / `Notes` : `—`
- `Phase Z Target` : `TBD after conversion` (단, `reference_only` frame 은 `N/A — reference_only`)
---
## 3. Frame Inventory (32 행)
> 14 변환 완료 frame 은 `flat.md` + `index.html` 관찰 기반 스타일 추출.
> 18 미변환 frame 은 보일러플레이트 일괄.
> 작성 룰 #1~4 (위 섹션 2) 준수.
| Frame | Figma ID | Layout | Style Elements | Extracted Style Hints | Phase Z Target | Asset Notes | Notes |
|---|---|---|---|---|---|---|---|
| **01** | `1171281172` | `circular-nodes-6` | • 6 원형 노드 absolute 배치 (각 노드 = 배경 원 + 내부 아이콘 + 라벨)<br>• 모든 노드 / 연결선 / 중앙 / 배경 = 이미지 자산 (9 개) | • **2D 다이어그램 패턴** — 노드 좌표 절대 배치<br>• 자산 의존도 매우 큼 — 본 frame 의 시각 구성은 거의 이미지 | svg helpers (helper area 후보, 자산 의존 제한적) | 9 자산 (배경, 중앙, 노드 ×6, 연결선) — 모두 이미지 유지. Phase Z 재현 시 자산 풀 / placeholder 필요 | 원본 1579×981, scale 0.81064. 변형 축 명시 없음 (flat.md sparse) |
| **02** | `1171281173` | `bullet-cards-4-plus-center` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **03** | `1171281174` | `list-numbered-4` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **04** | `1171281175` | `quadrilateral-relations` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **05** | `1171281176` | `side-card-with-list` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **06** | `1171281177` | `full-page-map-banner` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `N/A — reference_only` | — | — |
| **07** | `1171281178` | `2col-paired-list` | • 좌 H/W 7 항목 + 우 S/W 6 항목 + 중앙 시스템 원 + 하단 ground 이미지<br>• 16 자산 (배경 / 패널 / 중앙 원 / 장식 아이콘 / 헤더 바 SVG) | • **2D 복합 시스템 구성도 패턴**<br>• 자산 의존도 매우 큼 — Phase Z 재구성 곤란 | `styles/frame-patterns/system-diagram.css` (후보, 자산 의존 제한적) | 16 자산 모두 이미지 유지. Phase Z 재현 시 자산 풀 필수 | 원본 2446×1943, scale 0.52331. 변형 축 명시 없음 (flat.md sparse) |
| **08** | `1171281179` | `3-section-framework` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **09** | `1171281180` | `list-stacked-vertical` | • 5 pill 행 : `bg: rgba(255,255,255,0.5)`, `border-bottom: 3px solid {color}`, `radius: 30px`, `box-shadow: 2px 4px 5px rgba(0,0,0,0.5)`, `padding: 10px 20px`<br>• pill 색상 5 개 : `#fb5915` / `#e79000` / `#e9a804` / `#919f00` / `#0d6361`<br>• 다이아몬드 stacking : 넓→좁→좁→넓→넓 (좌측 indent 변화)<br>• 타이틀 바 : `#fbd5b9`, `radius: 5px`, shadow<br>• 좌측 아크 장식 SVG (이미지) + 화살표 SVG (`rotate(-90deg)`) | • **pill row + colored bottom border** : 핵심 패턴, 색만 갈아끼우면 N=3~7 동작<br>• **다이아몬드 stacking 패턴** : indent 차이로 시각 리듬<br>• translucent bg + colored border = 부드러운 카테고리 분리 | `styles/frame-patterns/pill-list.css` (후보) + `tokens/colors.css` 5 pill color palette (후보) | 좌측 아크 SVG / 화살표 SVG 2 개 — 이미지 유지. pill 본체는 CSS 변환 완료 | 변형 축 : `items[N=3~7]`, `stacking_pattern` (required), `arc_decoration` / `vertical_label` (optional). 원본 1153×592 (scale 1.11015 — 원본이 1280 보다 작음, zoom up 처리) |
| **10** | `1171281181` | `radial-diagram-5` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **11** | `1171281182` | `cards-3-category` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **12** | `1171281189` | `cycle-3way-intersection` | • 메인 3 원 (350×350) : outer + inner SVG (Ellipse585~592) + 중앙 라벨 (50px Bold white, `text-shadow: #cc5200`)<br>• 액센트 6 원 (130.9 px) : 한자 라벨 (45px Bold white, 같은 text-shadow)<br>• 사이드 라벨 6 그룹 : 40px Bold + 30px Medium desc<br>• 영역별 heading color : 상단 `#cc5200` / 좌측 `#604f32` / 우측 `#124133`, desc 공통 `#525151`<br>• 장식 RECT : gradient 회전 + `mix-blend-mode: multiply`<br>• 타이틀 : 70px Bold gradient `#000→#883700` | • **3 원 교차 다이어그램** : main 3 + accent 6 으로 영역 표현<br>• **white text + colored text-shadow** : 깊이 부여 효과<br>• **영역별 hue 분리** (`#cc5200` / `#604f32` / `#124133`) — 시각 zone 구분<br>• bg_texture multiply blending — 부드러운 배경 강조<br>• 사이드 라벨 위계 : Bold heading + Medium desc | svg helpers (helper area 후보, 원 / 교차 영역) + `styles/frame-patterns/cycle.css` (후보) + `tokens/colors.css` 영역별 hue palette (후보) | 19 SVG (Ellipse585~603 outer/inner pairs) — 좌표 기반 SVG 재구성 가능. bg_texture PNG 1 개는 이미지 유지 | 변형 축 명시 없음 (메인 3 원 고정 가능성). 원본 2195×1195, scale 0.58312. 수학 : main 350px = 15.94% width / accent 130.9px = 5.96% width |
| **13** | `1171281190` | `3-column` | • 3 컬럼 (각 690 원본) : 좌 BAR (152.5 px) gradient + 우 본문<br>• BAR gradient 3 가지 : 기술 `#0D78D0→#023056` / 사람 `#FF9A23→#CC5200` / 자연 `#39BE49→#23742C`<br>• 한자 (技術 / 人材 / 天地) : 50px Bold white on bar<br>• 헤딩 : 45px Bold gradient (top / bottom 별도 gradient 2 종)<br>• 본문 : 35px Medium `#3E3523`<br>• 세로 라벨 (rotate 90°) — 옵셔널 메타<br>• 테두리 : 실선 + 점선 SVG | • **3-pillar 카드 패턴** : 동등 카테고리 3 개 (예 : 기술/사람/자연)<br>• **gradient bar + 한자 + heading + body** 조합<br>• **컬럼별 hue rotation** (blue / orange / green) — 의미 차별화<br>• heading 도 gradient (단색 X) — 일관된 시각 언어 | `styles/frame-patterns/three-pillar.css` (후보) + `tokens/colors.css` 3 column gradient palette (후보) | 아이콘 PNG 1 개 + 테두리 SVG 4 개 (CSS border 변환 가능). 자산 의존 적음 | 변형 축 명시 없음 (3 pillar 고정 가능성 큼). 원본 2123×724, scale 0.60290. 수학 : 열 너비 416px / 바 92px after scale |
| **14** | `1171281191` | `persona-3col` | • 3 컬럼 동일 사이즈 (833×1845 원본) + 각 컬럼 텍스처 BG 이미지<br>• 컬러 오버레이 (opacity 0.80) — 컬럼별 다른 색감 hue<br>• 하단 사진 3 개 : `border-radius: 49~50px`, opacity 0.70<br>• 상단 원형 뱃지 (3 개) : outer + inner 이미지 + 한글 라벨<br>• 라벨 색 hue rotation : 발주자 `#285B4A` / 시공자 `#445A2F` / 설계자 `#743002`<br>• 체크박스 불릿 아이콘 (이미지, 32×32)<br>• 불릿 텍스트 : 40px Medium `#000` | • **3 컬럼 persona / actor 카드 패턴** : 역할별 한 컬럼<br>• **컬럼별 hue rotation** : 같은 톤 안에서 색상만 다르게 (역할 구분)<br>• **타이틀을 원형 뱃지로 표현** — 컬럼 상단 절반 걸침 (overhang)<br>• **사진을 borderless 가 아니라 둥근 corner + opacity 처리** (텍스트 가독성 확보)<br>• **체크박스 불릿** — 토큰화 가능 (Phase Z list-marker 후보) | `styles/frame-patterns/persona-cards.css` (후보) + `tokens/colors.css` 컬럼별 actor hue palette (후보) + svg helpers 원형 뱃지 (helper area 후보) | 다수 이미지 자산 : BG texture (×3), overlay (×3), photo (×3), badge outer/inner (×6), 체크박스 (×20 동일). 사진은 컨텍스트 의존 → Phase Z 에서 placeholder / 사용자 제공 자산 필요. 체크박스는 SVG 단순 대체 가능 | 원본 2601×1927 (대형 frame), scale 0.49213 |
| **15** | `1171281192` | `policy-4card-plus-list` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **16** | `1171281193` | `quadrant-4` | • 2×2 사분면 (각 1080×270 헤더/푸터바 + 본문)<br>• 헤더/푸터 bar (4 개) : brown gradient (좌측, `270deg, rgba(165,161,150,0.5)→#39321E`) / green gradient (우측, `270deg, rgba(41,107,85,0.5)→#032118`)<br>• Bar 라벨 : 60px Black white + `text-shadow: 0 0 4px #322c1e`<br>• 사분면 헤드라인 : red `#ff0000` 55px Bold (강조)<br>• 본문 : black 42px Regular, bullet list (`<ul><li>`, 텍스트 마커)<br>• 중앙 원 + 영문 인용 (white 55px Bold)<br>• 배경 텍스처 PNG (`border-radius: 50px`, ×4 동일) | • **2×2 quadrant 패턴** : 4 사분면에 헤드라인 + body 쌍 + 헤더/푸터 bar<br>• **bar 색 양극** (brown / green) — 좌·우 의미 구분<br>• **bar text 강한 시각 강조** : 60px Black + text-shadow<br>• **사분면 헤드라인 red** — 문제 / 이슈 강조 패턴<br>• 중앙 원 + 인용 — 결론 표현 (옵셔널)<br>• bullet — `<ul><li>` 텍스트 마커 (이미지 마커 X) | `styles/frame-patterns/quadrant.css` (후보) + `tokens/colors.css` bar gradient + 강조 red (후보) | 배경 텍스처 PNG (×4 동일) + 중앙 원 PNG + bar SVG (×4, CSS gradient 변환 가능). 배경 / 원만 이미지 유지 | 변형 축 : `quadrants[4]` (required, 고정), `bar_labels[4]` (required), `center_quote` / `center_image` / `bg_texture` (optional). 원본 2226×1766, scale 0.57503 |
| **17** | `1171281194` | `paired-rows-2x2` | • 4 행 (각 좌 pill + 본문 + 분할선 + 우 pill + 본문)<br>• 행 배경 : `border: 3px #60A451`, `radius: 30px`, `bg: rgba(250,237,203,0.15)`<br>• 분할선 : `dashed 2px #60A451` (CSS)<br>• pill 이미지 (R16: 두루마리 곡선) — 좌측 `left:-45.3% width:145.3%` / 우측 `left:0 width:151.25%`<br>• pill 라벨 : 40px Bold white<br>• 본문 : 36px Medium `#0c271e`<br>• 행 교대 pill 회전 : 상행 정상 / 하행 `rotate(180deg)`<br>• 타이틀 : 70px Bold gradient `#CC5200→#883700` | • **paired-rows 패턴** : 좌 / 우 라벨 + body 페어, 분할선 중앙<br>• **두루마리 pill (R16)** : 이미지 기반 곡선 형상 — CSS 재구성 곤란<br>• **상/하 pill 회전 교대** = 시각 리듬<br>• translucent bg + colored border = visual containment<br>• dashed 분할선 = soft separation | `styles/frame-patterns/compare-paired.css` (후보, Frame 18 과 같은 family) + `tokens/colors.css` border / bg color (후보) | 타이틀 아이콘 PNG + 두루마리 pill PNG (R16 frame 배치, CSS 재구성 곤란) + 분할선 SVG (CSS 변환 가능) | 변형 축 : `rows[N=3~6]`, `pill_alternation` 상/하 교대 (required), `pill_image` (required), `bg_border_color: #60A451` (required). 원본 1857×1326, scale 0.68927. **compare-paired family — variant : `paired-rows` (pill alternation)** |
| **18** | `1171281195` | `compare-rows` | • 타이틀 70px Bold gradient text (`#CC5200 → #883700`) + 아이콘<br>• 서브헤더 pill bar : `linear-gradient(270deg, #285B4A → #4A4026)`, `border-radius: 50px`<br>• 중앙 카테고리 뱃지 12 개 : 같은 gradient (alpha 0.64~0.8), `border-radius: 10px`<br>• 좌·우 텍스트 색 양극 : `#5C3714` (BIM 측, 갈색계) ↔ `#285B4A` (DX 측, 청록계), 40px Bold<br>• 결론 박스 : `#FAEDCB` + `mix-blend-mode: multiply`<br>• 결론 강조 텍스트 : `#AE3607` 55px Bold | • **다행 비교표 패턴** : 좌 (BIM/AS-IS) ↔ 중앙 카테고리 라벨 ↔ 우 (DX/TO-BE) 의 3 컬럼 페어드<br>• **양극 색 표현** : 좌·우를 명도·색상이 다른 두 hue 로 분리 (대비 의도)<br>• **gradient 재사용** : title gradient + bar/badge gradient 가 동일 팔레트 (저채도 그린 + 다크 brown) 변주<br>• **결론 처리** : multiply blending + accent color 로 강조 | `styles/frame-patterns/compare-paired.css` (후보, Frame 17 과 같은 family) + `tokens/colors.css``--g-title` / `--c-as-is` / `--c-to-be` (후보) + `styles/blocks/conclusion.css` multiply blend (후보) | 타이틀 아이콘 PNG 1 개 (이미지 유지) + 화살표 SVG 1 개 (`rotate(180deg)`, 이미지 유지) + 결론 박스 SVG → CSS 변환 완료 (자산 불필요) | 변형 축 : `rows[N=8~15]` (required), `title` (required), `conclusion` / `arrow_decoration` (optional). 원본 1868×1908, scale 0.68524. **compare-paired family — variant : `vs-center-badge` (좌·우 텍스트 + 중앙 카테고리 라벨 컬럼)** |
| **19** | `1171281197` | `cards-3-compare` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **20** | `1171281198` | `cards-3-header` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **21** | `1171281201` | `split-panel-diagram` | • 좌 다이어그램 이미지 + 우 4 번호 항목 + 하단 결론 바<br>• 14 자산 (다이어그램 요소 / 번호 뱃지 / 행 바 / 화살표 / 결론 바) — 모두 이미지 유지 | • **split-panel 패턴** (이미지-기반 다이어그램 + 번호 리스트)<br>• 자산 의존 큼 — Phase Z 재구성 시 다이어그램 이미지 제공 필요 | `styles/frame-patterns/split-panel.css` (후보, Frame 22 와 같은 family) | 14 자산 모두 이미지 유지. 다이어그램 이미지 = 핵심 자산 | 변형 축 : `right_items[N=3~6]`, `conclusion_text` (optional). 원본 1889×824, scale 0.67761. flat.md sparse |
| **22** | `1171281202` | `split-panel-numbered` | • 좌 패널 : 배경 IMG + 카테고리 텍스트 (40px Bold white, text-shadow) + detail (35px Medium black)<br>• 우 패널 : 5 행 (번호 뱃지 IMG + 행 바 IMG + 텍스트 45px Medium `#11231d` + 화살표 IMG `rotate(180deg)`)<br>• 중앙 연결 : 세로 괄호 IMG + 커넥터 IMG<br>• 행 바 (×5 동일 이미지)<br>• 번호 뱃지 (5 개 별개 이미지)<br>• 타이틀 : 70px/50px gradient | • **split-panel + numbered list 패턴** : 좌 카테고리 패널 + 우 번호 항목 페어<br>• 카테고리 텍스트 = text-shadow + white (배경 위 가독성)<br>• 번호 뱃지 + 행 바 + 화살표 = 단위 리스트 행 컴포넌트<br>• 중앙 괄호 / 커넥터 = 좌 ↔ 우 연결 시각화 | `styles/frame-patterns/split-panel.css` (후보, Frame 21 과 같은 family) | 15 자산 (좌측 BG, 타이틀 장식, 구분선 ×3, 세로 괄호, 커넥터, 행 바 ×5 동일, 뱃지 ×5 별개, 화살표 ×5 동일, 타이틀 아이콘) — 모두 이미지 유지 | 변형 축 : `left_categories[N=2~5]`, `right_items[N=3~8]` (required), `bg_image` (required), `bracket_image` (optional). 원본 1863×834, scale 0.68707 |
| **23** | `1171281203` | `table-2col` | • 열 헤더 bar (3 개) : `#589e8d` (구분/좌) / `#ef7a26` (우), 40px Bold white<br>• 행 배경 교대 : white / `rgba(253,198,158,0.16)`<br>• 강조 키워드 : `#a14101` Bold inline<br>• 본문 : black 40px Medium<br>• 그리드 라인 : 모두 CSS border<br>• 배경 텍스처 PNG (상단 / 하단 분할) | • **compare-table 패턴** (구분 컬럼 + N 열 비교)<br>• **헤더 bar 색상 양극** (`#589e8d` 청록 / `#ef7a26` 오렌지) — 의미 구분<br>• **행 alternating bg** = readability<br>• **강조 키워드 inline color** (`#a14101`) — 표 셀 안 강조 | `styles/frame-patterns/compare-table.css` (후보, Frame 24 와 같은 family) + `tokens/colors.css` 헤더 bar palette (후보) | 배경 PNG 2 개 + 아이콘 PNG + line SVG ×5 (모두 CSS border 변환). 배경 PNG 만 이미지 유지 | 변형 축 : `columns[2]`, `rows[N=3~7]` (required), `header_colors[2]` (required), `bg_images[2]` (optional). 원본 1924×2014, scale 0.66527. **table family — Frame 24 / 30 / 31 과 column 수 / 행 수만 다름, variant 통합 후보** |
| **24** | `1171281204` | `table-3col` | • 열 헤더 bar (4 개) : `#589e8d` (구분/상용) / `rgba(62,53,35,0.9)` (3rd) / `#ef7a26` (전용), 40px Bold white<br>• 행 배경 교대 : white / `rgba(253,198,158,0.16)`<br>• 강조 키워드 : `#a14101` Bold inline<br>• 본문 : black 35px Medium<br>• 그리드 라인 : 모두 CSS border<br>• 행 라벨 (좌측 열) : 35px Bold | • **compare-table 패턴** (Frame 23 의 3-column variant — 같은 family)<br>• **헤더 색상 3-way** (`#589e8d` / 다크 brown / `#ef7a26`) — Frame 23 의 2-way 확장<br>• **행 라벨 좌측 열** = 행 그룹 식별자 (예 : 개념 / 개발주체 / 성과품 / 사용) | `styles/frame-patterns/compare-table.css` (후보, Frame 23 과 같은 family — variant: `columns[N=2~4]`) + `tokens/colors.css` 헤더 bar palette 확장 (후보) | 아이콘 PNG + line SVG ×8 (전부 CSS border 변환). 자산 의존 거의 없음 | 변형 축 : `columns[N=2~4]`, `rows[N=3~6]` (required), `header_colors[N]` (required), `highlight_color` (optional, default `#a14101`). 원본 1869×1926, scale 0.68511. **table family — Frame 23 / 30 / 31 과 variant 통합 후보** |
| **25** | `1171281205` | `left-categories-right-logos` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **26** | `1171281206` | `cards-4-grid` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **27** | `1171281208` | `central-split-synthesis` | • 좌 (생산성 향상) + 우 (디지털 전환) + 중앙 원 (건설산업의 고부가가치화)<br>• 상단 헤더 bar / 하단 결론 bar (SVG `rotate(180deg)`)<br>• 2D 배치 (중앙 원 좌 / 우 영역 겹침) → absolute + zoom | • **split-center 패턴** : 좌 / 우 / 중앙 3-area 합성<br>• 중앙 원 = 좌·우 영역에 걸침 (overhang) — 결론 표현 방식 | `styles/frame-patterns/split-center.css` (후보) | 변환 완료 (preview.png 존재). 자산 상세 미기록 (flat.md sparse) | 원본 1697×914, scale 0.75427. flat.md sparse — 추가 관찰 필요 |
| **28** | `1171281209` | `title-plus-3-emphasis` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **29** | `1171281210` | `banner-top-2col-bottom` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **30** | `1171281211` | `table-3col` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **31** | `1171281212` | `table-3col` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
| **32** | `1171281213` | `central-5-goals` | *미변환 — 스타일 추출 보류. analysis.md 기준 layout / slot 존재만 확인.* | — | `TBD after conversion` | — | — |
---
## 4. Token Inventory
> 14 변환 frame 에서 관찰된 값과 기존 `templates/styles/tokens/` 의 매칭 검증.
>
> ⚠️ **본 inventory 는 신규 발굴표가 아니라 *기존 token 검증 + gap 발견 + 보류* 정리표**.
> ⚠️ 추출 / 검증만. 실제 token 파일 생성 / 변경 / 폐기는 별도 승인 단계.
### 작성 룰 (최종 6 개)
1. **2+ frame 에서 반복 관찰된 값만 행 승격**.
2. **1 frame 에서만 관찰된 값은 Token Inventory 에 올리지 않음** — 해당 Frame Inventory 의 `Notes` 에 "관찰 보류 (single-frame)" 로만 남김.
3. **패턴 / 기법은 token 이 아니라 family CSS / variant 영역**`mix-blend-mode`, R16 pill 곡선, hue rotation, badge overhang, pill alternation 등.
4. **타이포 / 스페이싱은 값 직접 매칭이 아니라 위계 매핑** — figma raw px (frame 1280 폭 기준) ↔ slide-body token (slide-body 스케일) 은 스케일이 다르므로 위계만.
5. **gradient 는 from / to pair 단위 한 행**.
6. **`covered` 는 hex 정확 일치일 때만** — 부분 일치 / 변형은 `gap_candidate` 또는 Notes 에 별도 표기.
### 명명 컨벤션 (가벼운 가이드)
신규 token 후보에만 적용. 기존 token (`--color-*`, `--font-*`, `--space-*`, `--card-*`) 은 그대로 사용.
- `--c-*` color
- `--g-*` gradient
- `--fs-*` font-size
- `--sp-*` spacing
- `--r-*` radius
- `--sh-*` shadow
### 컬럼 정의
| 컬럼 | 의미 |
|---|---|
| **Token Category** | color / gradient / typography / spacing / radius / shadow |
| **Existing Token** | `templates/styles/tokens/{file}` 의 token 명. 없으면 `—` |
| **Covered Frame Observations** | frame 번호 + 짧은 컨텍스트 라벨만 (값 X). status 에 따라 의미 다름 (아래 참조) |
| **Gap / Candidate** | 신규 token 후보 (이름 + 값 + `(후보)`). 기존 token 검증인 행은 `—` |
| **Status** | `covered` / `gap_candidate` / `hierarchy_mapping_only` / `hold_recheck_after_conversion` |
| **Notes** | family CSS cross-reference + 변형 메타 + scale 경계 등 |
### `Covered Frame Observations` 셀의 Status 별 의미
| Status | `Covered Frame Observations` 의 의미 |
|---|---|
| `covered` | 이 token 이 *cover 한* frame 들 (hex 정확 일치) |
| `gap_candidate` | 이 후보가 *target 으로 하는* frame 들 (2+ frame 에서 동일 값 관찰) |
| `hierarchy_mapping_only` | 이 위계 매핑이 *적용 가능한* frame 들 (값 직접 매칭 X) |
| `hold_recheck_after_conversion` | 현재 cover 한 frame 없음 (`—`). 14 converted 기준 미관찰 — 전체 변환 후 재검증 |
### Token Inventory — 18 행
| Token Category | Existing Token | Covered Frame Observations | Gap / Candidate | Status | Notes |
|---|---|---|---|---|---|
| gradient | `--color-block-title-from` / `--color-block-title-to` (`#CC5200` / `#883700`) | F17, F18 (frame inner title) | — | `covered` | compare-paired family 의 title gradient slot. F12, F13 의 title 은 `from``#000` 으로 변형 — 별도 행 (`gap_candidate`) 처리 |
| gradient | `--color-col-1-from` / `--color-col-1-to` (`#0D78D0` / `#023056`) | F13 (기술 bar — blue tone) | — | `covered` | three-pillar.css 의 column 1. 1 frame 매칭이지만 token 자체가 frame 값을 정확 흡수 |
| gradient | `--color-col-2-from` / `--color-col-2-to` (`#FF9A23` / `#CC5200`) | F13 (사람 bar — orange tone) | — | `covered` | three-pillar.css 의 column 2. `#CC5200` 는 title gradient `from` 과 같은 hex 이지만 의미 다름 (column-2 끝값) |
| gradient | `--color-col-3-from` / `--color-col-3-to` (`#39BE49` / `#23742C`) | F13 (자연 bar — green tone) | — | `covered` | three-pillar.css 의 column 3 |
| color | `--color-compare-left` (`#5c3714`) | F18 (BIM / AS-IS 측 텍스트) | — | `covered` | compare-paired.css 의 좌측 / AS-IS 색 |
| color | `--color-compare-right` (`#285b4a`) | F14 (발주자 라벨), F18 (DX / TO-BE 측 텍스트) | — | `covered` | compare-paired.css 의 우측 / TO-BE + persona-cards.css 의 actor 색. F14, F18 모두 hex 정확 일치 (대소문자 제외) — 의미 다르지만 token 재사용 가능 |
| color | `--color-compare-badge` (`#ae3607`) | F18 (결론 강조 / VS 뱃지) | — | `covered` | compare-paired.css 의 결론 / VS 뱃지 강조색 |
| gradient | — | F12, F13 (title — `#000``#883700`) | `--g-title-dark: linear-gradient(#000, #883700)` (후보) | `gap_candidate` | title gradient 변종 (`from``#000`). `--color-block-title-to` 와 끝값 공유 — 신규 token 으로 묶을지 / variant 처리할지 검토. 영향 family : 미정 (cycle.css / three-pillar.css) |
| color | — | F23, F24 (table 헤더 좌 / 구분 — 청록 톤) | `--c-table-header-cyan: #589e8d` (후보) | `gap_candidate` | compare-table.css 의 좌측 / 구분 헤더. 기존 `--color-table-header-bg: #64748b` (회색) 과 다른 톤 — 회색 헤더 token 은 `hold` 행 참조 |
| color | — | F23, F24 (table 헤더 우 / 전용 — 오렌지 톤) | `--c-table-header-orange: #ef7a26` (후보) | `gap_candidate` | compare-table.css 의 우측 헤더 |
| color | — | F23, F24 (table 행 강조 키워드) | `--c-table-highlight: #a14101` (후보) | `gap_candidate` | compare-table.css 의 inline 강조색 |
| color | — | F23, F24 (table 행 교대 배경) | `--c-table-row-alt: rgba(253,198,158,0.16)` (후보) | `gap_candidate` | compare-table.css 의 alternating row bg. white / `--c-table-row-alt` 교대 |
| typography | `--font-slide-title` (22px) / `--font-zone-title` (13px) / `--font-sub-title` (12px) | F12, F13, F17, F18 (frame inner title — raw 70px Bold) | — | `hierarchy_mapping_only` | frame raw 70px 는 figma 1280 폭 기준 — slide-body 스케일과 다름. **위계 매핑만**. frame inner title 이 slide-body 안에서 어느 위계 (`--font-zone-title` / `--font-sub-title`) 로 매핑될지는 catalog 설계 단계 결정 |
| typography | `--font-body` (11px) | F12, F13, F16, F17, F18, F22, F23, F24 (본문 — raw 35~42px Medium) | — | `hierarchy_mapping_only` | frame raw 35~42px 는 figma 1280 폭 기준. slide-body 안에서는 `--font-body` 위계 적용 |
| spacing / radius | `--space-md` / `--space-lg` / `--card-radius` 등 | F09 (pill `radius: 30px`), F16 (배경 `radius: 50px`), F17 (행 `radius: 30px`), F18 (badge `radius: 10px`, pill `radius: 50px`), F23/F24 (셀 padding) | — | `hierarchy_mapping_only` | frame raw radius / gap / padding 은 figma 폭 기준. slide-body 안에서는 위계 매핑 + 재산정 필요 |
| color | `--color-dark-card-1` (`#1a365d`) / `-2` (`#1e3a2f`) / `-3` (`#3b1f2b`) / `-title` (`#fbbf24`) / `-body` (`#e2e8f0`) | — | — | `hold_recheck_after_conversion` | 다크 카드 시각 시스템 5 token. 14 converted 기준 미관찰. 전체 32 frame 변환 후 재검증 (특히 미변환 zone_extract 18 개) |
| color | `--color-pill-bg` (`#1e293b`) / `--color-pill-text` (`#ffffff`) | — | — | `hold_recheck_after_conversion` | 다크 pill 스타일. F09 pill (translucent + colored border) / F18 badge (gradient) 와 다른 톤. 14 converted 기준 미관찰 — 전체 변환 후 재검증 |
| color | `--color-table-header-bg` (`#64748b`) / `--color-table-header-text` (`#ffffff`) | — | — | `hold_recheck_after_conversion` | 회색계 표 헤더. F23 / F24 의 colored 헤더 (`#589e8d` / `#ef7a26`) 와 다른 톤. 14 converted 기준 미관찰. 향후 회색 헤더 frame 등장 시 재검증 |
---
## 5. Legacy Reference
- legacy structures 6 개는 runtime 재사용 후보 X
- frame 변환본 (`figma_to_html_agent/blocks/`) 이 우선 source
- disposition 분류 목적은 archive / delete 판단
- Phase Z catalog / runtime 설계 근거로 **직접 사용 X**
### 발견 — 6 파일 모두 frame 변환본의 *slide-body 스케일 재구현*
각 legacy file 의 헤더 주석에 `Source: figma_to_html_agent/blocks/{figma_id}` 명시 (확인). figma 변환 (raw 1280 폭, 40~70px 폰트) → slide-body 스케일 (`var(--font-sub-title)` 12px, `var(--space-sm)` 8px 등 token 적용) 재구현 시도. 즉 *figma 변환과 별개의 legacy* 가 아니라, *figma 변환에서 파생된 slide-body 스케일 시도*.
→ Phase Z runtime 은 frame catalog + family CSS 로 rebuild 예정. legacy structures 는 *변환 검증 증거 / 토큰 매핑 참고* 외 직접 사용 X.
### `Phase Z Disposition` 값
- `archive` — 보존 (스타일 / 토큰 매핑 증거 가치)
- `delete_after_extract` — Style Note 추출 후 삭제
- `hold_until_catalog_ready` — Phase Z catalog 안정화 전까지 유지
### Legacy Reference — 6 행
| Legacy File | Current Role | Phase Z Disposition | Style Note | Notes |
|---|---|---|---|---|
| `compare-table-2col.html` | F23 (`1171281203`) 의 slide-body 스케일 재구현. 표 헤더 colored / 행 교대 bg / 강조 inline color | `delete_after_extract` (후보) | inline hex (`#589e8d` / `#ef7a26` / `#a14101` / `rgba(253,198,158,0.16)`) 가 Token Inventory 의 `gap_candidate` 4 행과 정확 일치 | `compare-table.css` family 의 first reference 가치 |
| `compare-table-3col.html` | F24 (`1171281204`) 의 3-column variant slide-body 재구현 | `delete_after_extract` (후보) | F23 과 같은 hex + 추가 `rgba(62,53,35,0.9)` (column 2 어두운 brown 헤더). 단일 frame 관찰값 — Token Inventory 비승격 (룰 #2) | `compare-table.css` family variant — F23 과 통합 후보 |
| `compare-vs-rows.html` | F18 (`1171281195`) 의 slide-body 재구현 | `delete_after_extract` (후보) | `var(--color-compare-left)` / `var(--color-compare-right)` 기존 token 활용 — covered token 검증 증거 | `compare-paired.css` family |
| `issues-paired-rows.html` | F17 (`1171281194`) 의 slide-body 재구현 | `delete_after_extract` (후보) | `--color-row-border: #60A451` inline 정의 — F17 단일 frame 관찰값, Token Inventory 비승격 (룰 #2). family CSS variant 처리 검토 | `compare-paired.css` family |
| `prerequisites-3col.html` | F13 (`1171281190`) 의 slide-body 재구현 | `delete_after_extract` (후보) | `--color-col-N-{from,to}` 기존 token 활용 가능 — covered token 검증 증거 | `three-pillar.css` family |
| `stacked-arrow-list.html` | F09 (`1171281180`) 의 slide-body 재구현 | `delete_after_extract` (후보) | 타이틀 바 `#fbd5b9` / 텍스트 `#144838` inline — F09 단일 frame 관찰값, Token Inventory 비승격 (룰 #2) | `pill-list.css` family |
---
## 6. 진행 단계
| 단계 | 상태 |
|---|---|
| 33 frame ↔ Integration Map 32 행 대조 | ✅ 완료 (`FRAME-INTEGRATION-MAP.md` row 21~28 ID 정정 반영) |
| Inventory 골격 + 샘플 5 행 (이 문서) | ✅ 본 단계 |
| 사용자 검토 | ⬜ |
| Frame Inventory 27 행 일괄 확장 (32 frame 완성) | ⬜ |
| Token Inventory 본격 작성 | ⬜ |
| Legacy Reference 본격 작성 | ⬜ |
| Phase Z catalog / runtime template 설계 | ⬜ (별도 단계) |
| 사용자 승인 → `templates/blocks/` 신규 구조 교체 (프로모션 게이트) | ⬜ (별도 단계) |
### 샘플 5 검증 포인트
| 검증 항목 | 결과 |
|---|---|
| 변환 / 미변환 frame 이 같은 양식에 들어가는가 | ✅ — 미변환은 `Style Elements` 셀 1 개에 통일 보일러플레이트, 나머지 4 셀은 `—` (`Phase Z Target``TBD after conversion` 또는 `N/A — reference_only`) |
| `Style Elements``Extracted Style Hints` 의 구분이 명확한가 | ✅ (Frame 18, 14) — 관찰 사실 (CSS 값 / 픽셀) vs 계승 의도 (패턴 / 의미) |
| `Phase Z Target` 후보 표현이 통일되는가 | ✅ — 변환 frame 은 "(후보)" 명시, 미변환은 `TBD after conversion`, reference_only 는 `N/A — reference_only` |
| 미변환 frame 이 `analysis.md` 내용을 redescribe 하지 않는가 | ✅ — 보일러플레이트 1 행 외 어떤 redescription 도 없음 |
| 변환 frame 의 `Notes` 가 스타일 계승에 의미 있는 메타만 담는가 | ⚠️ 샘플은 적정. 32 행 확장 시 단순 출처 (Scale 값, "대표 frame" 등) 는 추가로 정리 필요 |
---
## 7. 부록 — 제외 / 특수 항목
`figma_to_html_agent/blocks/` 의 33 개 디렉토리 중 `1171281171` 은 본 인벤토리 메인 32 frame 에서 제외한다 (`texts.md` 만 존재, `index.html` / `analysis.md` 없음, 정체 미확인). 상세는 [`FRAME-INTEGRATION-MAP.md` 부록](FRAME-INTEGRATION-MAP.md#부록--제외--특수-항목) 참조.
@@ -1,206 +0,0 @@
# Phase Z 매칭 아키텍처 — 원칙 anchor
> 22-step pipeline 의 *매칭 layer* (Step 5/6/7 + B-axis Step 9/10/11) 를 다루는 *원칙 anchor* doc.
>
> 본 문서는 *새 디자인 생성* 이 아니라 *기존 코드 / 과거 docs / 관찰 사례 / session 인사이트* 를 근거로 한 *forward improvement consolidation*.
>
> 관련 anchors :
> - `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md` (D1, 22-step pipeline)
> - `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` (D2, Layer A/B SPEC)
> - `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-PLAN.md` (D3, schema completion plan)
> - `docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md` (D4, 현재 status)
## §0. 본 문서의 작성 방법론 (P0)
**evidence-based forward improvement** — 매번 *4 source* 를 거쳐 도출 :
1. 과거 docs (D1 / D2 / D3 / D4) — *원래 의도*
2. 현재 코드 (`src/phase_z2_*.py` / `templates/phase_z2/catalog/frame_contracts.yaml`) — *실제 동작*
3. 관찰된 혼선 / 실패 사례 — *경험적 evidence*
4. session 인사이트 — *최근 정리된 mental model*
이걸 *관찰 → 혼선 → 도출 원칙 → 개선 방향* form 으로 표면화. *과거 회귀* 가 아니라 *과거를 근거로 한 개선*. *감으로 결정* / *가장 쉬운 선택* 금지.
## §1. 현재 코드 상태 (관찰)
### 1.1 매칭 단위 — `CompositionUnit`
매칭의 *atomic unit* 은 section 이 아니라 `CompositionUnit`. `src/phase_z2_composition.py``collect_candidates`*3 종 candidate type* 을 동시 생성 :
- `single``src/phase_z2_composition.py:230-243` (1 section 단독)
- `parent_merged``src/phase_z2_composition.py:260-273` (parent 자체가 V4 매칭, children 묶음)
- `parent_merged_inferred``src/phase_z2_composition.py:352-368` (children rep_match 기반 inferred merge)
각 candidate 는 `source_section_ids[]` + `merge_type` (line 232 / 262 / 354) 로 *어떤 section 들을 어떻게 묶었는지* 표현.
### 1.2 frame 선택 — render path authority = V4 rank-1
현재 render path 의 `frame_template_id` 는 composition planner 의 *V4 rank-1* 기반 :
- `lookup_v4_match()` in `src/phase_z2_pipeline.py` (D1:212 명시) — rank-1 만 반환
- composition planner 가 V4 결과로 candidate 생성
B4 (`src/phase_z2_placement_planner.py:90-107``_select_frame`) 도 frame selection 을 *수행* 하지만 *trace-only* — render path authority 는 V4 rank-1.
V4 = `top-k` 선언만 됐고 *rank-1 사용* 만 활성. D4 의 ⚠ partial.
### 1.3 binding — `phase_z2_mapper.map_with_contract` (Layer 1)
`src/phase_z2_mapper.py:584-609``map_with_contract(section, contract)` 는 contract 에서 *오직* 두 field 만 read :
- `contract["source_shape"]` (split 규칙)
- `contract["payload"]["builder"]` (named PAYLOAD_BUILDERS dispatch)
`accepted_content_types` / `sub_zones` 등 Layer 2 field 는 *접근 X*. 즉 mapper 는 *Layer 1 binder* 로 격리.
### 1.4 placement — `phase_z2_placement_planner` (Layer 2 reader, trace-only)
`src/phase_z2_placement_planner.py` :
- `_select_frame` (90-107) — `accepted_content_types ⊇ content_type_set` cover + declaration order first
- `_assign_region_to_sub_zone` (113-151) — `sub_zones` 의 narrowest-accepts first
- `plan_placement` (157+) — Stage A (B2 internal regions) + Stage B (region ↔ sub_zone) 통합
B4 는 *Layer 2 reader* 이지만 `src/phase_z2_pipeline.py:1060-1145` 에서 *trace-only* 로만 호출. render path (slot_payload 생성) 미연결. D4:78-83 의 *render path placement_trace 미사용* 과 일치.
### 1.5 capacity / fit — classifier (post-render telemetry)
`src/phase_z2_classifier.py``classify_visual_runtime_check`*render 후* visual measurement 기반 fit 판정. *Architectural reframe* (D2:16-33 의 PLANNING / RENDER / POST-RENDER 3-layer) : A1~A4 = *post-render telemetry layer*. *덜 중요* 가 아니라 *위치가 다름*. 매칭 결정 자체에는 직접 입력 X (현재).
## §2. 확인된 혼선 (resolution)
### 2.1 Sub-zone 용어 4-방향 충돌 → 4-tier 분리 (P5'')
session 안 누적 혼선 :
- D1 의 "Zone (top / bottom_l / bottom_r 등)"
- composition.py 의 child unit 분리 (single / parent_merged 등)
- yaml 의 `sub_zones` field
- 사용자 mental model 의 "### split"
*4 tier 별 명칭 lock* :
| Tier | 명칭 | 위치 | 정체 |
|---|---|---|---|
| 1 | **Layout** | slide-level | slide-body 안 zone topology (8-preset vocabulary) |
| 2 | **Zone** | layout 안 | 콘텐츠 구역 (top / bottom_l / bottom_r 등). 1 frame 매칭 단위 |
| 3 | **Split Zone (Child Unit)** | zone 안 | composition planner 가 만든 child unit (parent_merged 의 분리 단위) |
| 4 | **Frame Slot** | frame 안 | yaml 의 `sub_zones` = Layer B placement target. region 매칭 대상 |
이후 본 문서 / 후속 doc 은 *위 명칭 어휘* 만 사용. yaml field `sub_zones`*기존 이름 유지하되 의미 = Frame Slot* 으로 lock (mechanical rename 은 D3 plan).
### 2.2 composition tie-break ≠ Frame Slot tie-break
scope-lock 단계에서 *D2:524-533 의 tie-break**composition tie-break evidence* 로 잘못 분류. 실제로는 :
- D2:524-533 = *Frame Slot 단위* tie-break (Stage B 안, frame 이미 선택된 후)
- composition tie-break = `composition.py:433-441` (sort + greedy + coverage)
두 layer 가 다름. P7 evidence 는 *composition layer* 만, *Frame Slot layer* 는 별개 location.
### 2.3 "Layer 2 미사용" framing 오류
scope-lock 초안의 *"mapper 가 Layer 2 를 consume 안 함"* framing 은 암묵적으로 *mapper 가 Layer 2 reader 가 돼야 한다* 의 fix direction 함의. 실제 architectural intent :
- mapper = Layer 1 binder (의도된 격리)
- B4 = Layer 2 reader (의도된 분리)
*문제**Layer 2 unconsumed* 가 아니라 *B4 의 Layer 2 read 가 trace-only 라 render path 까지 안 닿음*. *bridge* 가 답이지 *mapper 확장* 이 아님.
### 2.4 Step 4/5/9 분리 미완
현재 구현은 D1 이 의도한 *Step 4 → Step 5 → Step 9* 분리가 완성되지 않음 :
- Step 4 (composition planning input — section_layout_signature / content_object 구조) = *partial/dormant* (D4:73 의 ⚠ partial transition)
- Step 5 evidence 와 Step 9 final selection = *conflate* (D1:255)
D1:185 가 명시적으로 *Step 4 가 frame matching 보다 먼저 와야 함* 을 지적.
## §3. 도출된 원칙
### P1 : 매칭 단위 = `CompositionUnit` (section 아님)
- *근거* : `composition.py:230-243` (single) / `:260-273` (parent_merged) / `:352-368` (parent_merged_inferred). 각 line `:232` / `:262` / `:354``merge_type` signal
- *함의* : *MDX section* 을 1:1 zone 매칭 단위로 가정하는 코드 / doc 는 모두 *추상화 누락*. *CompositionUnit* 으로 통일.
### P3 : frame 선택 / content binding *layer 분리*
- *근거* : 현재 코드의 *de facto* 분리 (mapper §1.3 + placement_planner §1.4)
- *원칙* : frame *선택* (어떤 frame 을 쓸지) 과 content *binding* (선택된 frame 에 콘텐츠를 어떻게 채울지) 는 *별개 axis*. 함께 결정하면 search space 가 곱셈으로 폭발 + 실패 격리 불가.
- *함의* : 어떤 axis 가 frame 을 *결정* 하는지 + 어떤 axis 가 *binding* 하는지 *명시적 분리* — 한 module 이 둘 다 하면 회귀 위험.
### P5'' : 4-tier terminology lock
§2.1 표 그대로. *후속 doc / commit message / 코드 docstring* 모두 4-tier 어휘만 사용.
### P7 : candidate-based composition (sequential A/B 아님)
- *근거* :
- 원리 : `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:216-218` ("child 따로 / sibling 묶기 / parent 단위" + scoring inputs)
- 구현 : `composition.py:230-243` / `:260-273` / `:352-368` (3 candidate type 동시 생성)
- 선택 : `composition.py:433-441` (`(score desc, source_section_ids count desc)` + greedy covered skip)
- *원칙* : composition 은 "## → fail → ### split → re-merge" sequential 이 아니라 *3 candidate type 을 동시 생성 → score + coverage tie-break**parallel evaluation*.
- D2:524-533 은 *Frame Slot tie-break (Stage B)* 로 본 P7 evidence 와 *별도 layer*.
### P8 : staged migration — frame 선택 authority + binding 진화 분리
#### P8-a (near-term)
- *목표* : frame selection authority 정리 (V4 rank-1 단독 → B4 informant 또는 B4-mediated 의 *어떤 형태*)
- *유지* : mapper binding channel (PAYLOAD_BUILDERS) — 현재 working channel 안전
- *open* : B4 결과를 render path frame selection 에 *어떻게 반영* 할지의 bridge 형태 → §5 의 Q-V4B4 / Q-LB 에서 별도 scope-lock
#### P8-b (longer-term, *지금 결정 X*)
- region / Frame Slot-aware slot_payload 진화
- PLACEMENT_PAYLOAD_BUILDERS 등 별도 namespace 가능성
- *DC2 Open question (Q-LB) 해결 후* 결정. 본 문서 시점에서는 *deferred*.
## §4. 코드 변경 결정 지점
### DC1 : Step 4/5/9 분리 미완
- *현 상태* : D1 의도한 Step 4 → Step 5 → Step 9 분리가 완성되지 않음. Step 4 (composition planning input — section_layout_signature / content_object 구조) 는 *partial/dormant* (D4:73 의 ⚠ partial), Step 5 evidence 와 Step 9 final selection 은 *conflate* (D1:255).
- *결정 지점* :
- Step 4 가 frame matching ** 에 composition candidate 평가 input 으로 흘러야 (D1:185 명시적 지적)
- Step 5 evidence layer 와 Step 9 final selection layer 의 *명시적 분리* 필요
- *영향 module* : `src/phase_z2_pipeline.py` orchestrator + `src/phase_z2_composition.py` (input 확장)
### DC2-a : `phase_z2_mapper` = Layer 1 working channel
- *현 상태* : `src/phase_z2_mapper.py:584-609``source_shape` + `payload.builder` 만 read. *현재 render path 의 유일 working channel*.
- *결정 지점* : *Layer 1 격리 유지*. mapper 에 Layer 2 read 를 추가하는 방향은 *반대* — 실제 의도는 별 axis (DC2-b + Open question Q-LB).
### DC2-b : `phase_z2_placement_planner` = Layer 2 reader, render path 미연결
- *현 상태* : `src/phase_z2_placement_planner.py:90-107` (accepted_content_types) + `:113-151` (sub_zones) — Layer 2 read 활성. 그러나 `src/phase_z2_pipeline.py:1060-1145` 에서 *trace-only*. final render 미연결.
- *결정 지점* : Layer 2 read 결과를 render path 에 *어떻게 합류시킬지* 의 bridge 결정 — §5 의 Q-LB 에서 별도 scope-lock.
### DC3 : Internal Region runtime — trace-only partial
- *현 상태* : SPEC v1 (D2) 가 Internal Region (Layer A) / Frame Slot (Layer B) 의 layered placement 정의. Layer A runtime 은 *trace-only partial*`src/phase_z2_placement_planner.py:157+``plan_placement` 가 Stage A 호출 (`:192-197``plan_internal_regions` call) 수행하지만, 결과가 render path 미합류. *부재* 가 아니라 *render path authority 미연결*. D4:73 의 ⚠ partial 분류와 일치.
- *결정 지점* : Layer A planning telemetry 활성 단계 → render path 합류 단계의 *separate axis*. P8-b 와 연관 (slot_payload 진화 이전 단계).
## §5. open scope-lock questions
본 axis 범위 ** 의 deferred 결정. 후속 axis 에서 각각 별도로 lock.
### Q-FW : 4 filter score weight / threshold
- F1 frame internal fit / F2 child frame divergence / F3 content type-structure match / F4 content loss risk 의 *score weight**auto vs review threshold* 결정 필요.
### Q-CO : candidate priority tie-break
- composition.py:433 의 `(score, source_section_ids count)` 외에 *추가 axis* (cardinality_fit / hierarchy_coherence / density) 도입 시점.
### Q-RT : review / adapter_needed threshold
- W1/W2/W3 신호 + auto_selectable=False candidate 의 review surface 정책.
### Q-V4B4 : V4 → B4 frame selection authority transition timing
- P8-a 의 *언제* — V4 단독 / B4 informant / B4-mediated 의 단계.
### Q-LB : Layer 1 ↔ Layer 2 bridge architecture
- DC2-b 의 *어떻게* — mapper direct consume / B4-mediated integration / 별도 translator. Q-V4B4 와 연관되나 *별개 layer* (frame 선택 timing vs Layer 2 → render path bridge 구조).
### Q-CE : catalog extension surface
- frame_contracts.yaml 에 신규 frame 추가 시 *어떤 layer 부터 채워야 self-consistent* 인지의 declarative form.
### Q-DT : decision trace 표준 form
- composition / placement / fit_classification 의 trace 통합 schema. 현재 각각 다른 dict 구조.
@@ -1,472 +0,0 @@
# Phase Z — master pipeline overview
**Status** : 마스터 reference (2026-04-30 잠금). 본 문서 = *워크플로우 전체 도면*. 향후 모든 작업은 *이 22-step 도면의 어느 위치에 속하는지* 먼저 self-locate 해야 함.
**용도** :
- 새 작업 시작 시 — "지금 하는 게 22-step 중 어느 step 인가" 식별
- 새 spec / memory rule 추가 시 — "어느 step 의 어느 의사결정에 적용되는가" 매핑
- 새 sample / 새 frame 추가 시 — "어디서 막힐 가능성이 높은가" 사전 예측
**본 문서가 *하지 않는* 것** :
- 새 구현 제안 X
- next step 추천 X
- 우선순위 결정 X
- A/B/C 선택지 X
- specific MDX sample 분석 X
본 문서는 *기준점*. 의사결정은 별도 step 에서.
---
## 3-block 구조
전체 22 step 은 다음 3 block 으로 grouping :
| Block | Step 범위 | 역할 |
|---|---|---|
| **A. PRE-RENDER PLANNING** | 0 — 12 | render ** 모든 결정 — *slide-level zone 분배* + *zone-internal region 분배* + frame / slot 매핑. *진짜 fit policy 의 중심* |
| **B. RENDER** | 13 | Jinja2 + frame partial → final.html |
| **C. POST-RENDER TELEMETRY / EXCEPTION HANDLING** | 14 — 22 | render 결과 검증 + 분류 + routing + status. *exception 처리 layer* |
**중요** : 진짜 fit policy 의 자리는 A block (composition planning). C block (telemetry) 은 *exception 처리 + 진단 안내* layer. 둘 다 필요하지만 *위치가 다름*.
---
## 위계 + 용어 (entity hierarchy)
본 파이프라인은 다음 entity 위계 위에서 동작 :
> **Lock phrase (canonical)** : `Slide → Zone → Internal Region → Frame → Frame Slot → Content`
```
Slide
└─ Zone (slide-level layout 이 만든 큰 영역)
└─ Internal Region (zone *내부* 영역, frame *밖*)
└─ Frame (Figma design 단위)
└─ Frame Slot (frame *내부* 자리)
└─ Content unit (text / table / image / details / ...)
```
### Universal Region Model
> **Lock phrase (canonical)** :
> `Every Zone has 1+ Internal Regions.`
> `text-only zone = single-region.`
> `mixed-content zone = multi-region.`
```
모든 Zone 은 1 개 이상의 Internal Region 을 가짐.
text-only zone = single-region zone (현 거동의 자연 표현)
mixed-content zone = multi-region zone
각 Internal Region 은 *자기만의* :
- frame match
- display strategy (inline / preview+details / popup-only / dropped)
을 가질 수 있음.
```
text-only section 도 *single-region zone* 으로 표현 (= 현 거동 보존). mixed-content (text + table / text + image / 등) 은 *multi-region zone* 으로 확장. region 이 *first-class entity* — special case 가 아님.
### 용어 표
| 용어 | 의미 | 위치 |
|---|---|---|
| **Slide** | 1280×720 한 장 | 최상위 |
| **Zone** | slide-level layout 이 만든 큰 영역 (top / bottom / left / right 등) | Slide 안 |
| **Internal Region** | Zone *내부* 영역, frame **. content type 기반 분할 (text region / table region / image region / details region) | Zone 안 |
| **Frame** | Figma design 단위 (= F13 / F29 / F16 등) | Internal Region 안 |
| **Frame Slot** | frame *내부* 자리 (= pillar_1 / quadrant_1 / process_column 등) | Frame 안 |
| **Content unit** | MDX section 안의 typed 콘텐츠 조각 (text_block / table / image / details / ...) | Frame Slot 에 배치 |
> **주의** : `PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` 의 `sub_zones` (YAML 필드명) 은 본 표의 *Frame Slot (Layer B)* 의미로 정의되어 있음. 본 표의 *Internal Region (Layer A)* 는 SPEC v1 §2 에 정의됨.
---
## Operating Principles / Hard Locks
본 섹션 = *anchor / index*. 각 원칙의 상세 정의 / 적용 룰 / 예외 처리는 *referenced source* 에 있음. drift 방지를 위해 OVERVIEW 는 *짧은 anchor* 로만 둠.
### 1. MDX mapping convention
| MDX | 슬라이드 |
|---|---|
| `# 대목차 제목` | `slide-title` |
| `# 대목차 결론` / note | `slide-footer` |
| `##` / `###` 본문 | `slide-body` 안 (layout + zone + region + frame + slot) |
| `<details>` | 별도 details layer |
> 참조 : `CLAUDE.md` 의 *MDX → 슬라이드 매핑* 표
### 2. 자유 디자인 금지
Figma frame DB / catalog / frame contract 기반으로만 디자인 결정. *임의 HTML / CSS 디자인 생성 X*. AI 가 frame 자체 / layout 자체 / 새 디자인 패턴을 *생성하지 않음*.
> 참조 : `CLAUDE.md` 디자인 원칙 + `feedback_no_hardcoding` + `feedback_blocks_must_be_css`
### 3. 원문 무손실
MDX 원문 *삭제 / 요약 / 압축 금지*. AI 호출이 normal path 에서 콘텐츠를 *재작성하지 않음*. 원문은 본문 preview 또는 details/popup 어딘가에 *반드시* 보존.
> 참조 : `feedback_ai_isolation_contract` + `PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §5.2
### 4. 그릇 변경 원칙 (positive form)
콘텐츠가 안 맞을 때 *콘텐츠를 줄이지 않음*. 대신 *그릇* (layout / zone / internal region / frame / display strategy) 을 변경하여 수용. 공통 CSS / padding / tolerance 임의 축소는 *그릇 변경* 이 아님 → 금지.
> 참조 : `feedback_phase_z_spacing_direction`
### 5. preview / details 원칙
inline preview = 원문의 *일부* 만 빌려 보여주는 것. 원문은 details / popup 에 *반드시* 보존. preview 자체가 원문을 대체하지 않음.
> 참조 : `PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §5.1 / §5.5
---
## Heritage / Current State (참고)
본 섹션 = 시간 따라 변할 수 있는 *history / state* 기록. *원칙* 아님. frame DB 확장 / vocabulary 진화 시 mechanical 갱신.
### 1. Type A / B / B' / B'' → 8-layout vocabulary 진화
기존 *Type A / B / B' / B''* 의 4 preset 은 사라진 게 아니라 **8-layout vocabulary** (single / horizontal-2 / vertical-2 / top-1-bottom-2 / top-2-bottom-1 / left-1-right-2 / left-2-right-1 / grid-2x2) 로 *일반화**전신*. Step 7 의 8 vocabulary 는 이 진화의 결과.
### 2. 현재 runtime-verified frame set 은 text-frame 중심
현재 *runtime contract-registered / verified* frame set = `F13` (three_parallel_requirements) / `F29` (process_product_two_way) / `F16` (bim_issues_quadrant_four) — *모두 text 전용 성격*. `figma_to_html_agent/blocks` 의 전체 frame inventory 가 image / table / mixed frame 을 얼마나 포함하는지는 *전수 audit 전까지 미확정*. 따라서 현재 runtime 기준 :
- *text region* → frame 매칭 (현 거동)
- *image region* / *table region* / *details region* → 현재 contract-registered frame set 안에서는 frame 매칭 근거가 부족하므로 *display strategy* 로 처리 (image area 직접 배치 / table preview / details button 등)
**Step 9 의 region 단위 매칭은 현재 *runtime-verified frame set 기준으로 text region 만 frame 매칭 가능*** 함을 인지. 전체 frame inventory audit 또는 contract 등록 상태가 바뀌면 본 항목은 갱신 대상.
---
## 22-step 상세
각 step 의 형식 :
> **Step N. 이름** — purpose (1-2 줄)
> **Status** : ✅ implemented / ⚠ partial / ❌ missing
> **Code 위치** : (해당 시)
> **Gap** : (해당 시)
### Block A — PRE-RENDER PLANNING
#### Step 0. 사전 준비
파이프라인 가동 전 준비되어 있어야 하는 정적 자료들. catalog / contract / matching data / template / asset.
- **포함** : Figma/BEP frame → HTML 변환물 / frame catalog / frame contract / V4 matching data + ontology / slide-base template / render assets
- **frame contract 필수 필드** : frame_id, template_id, accepted_content_types, slots, sub_zones, capacity, visual_hints, asset paths
- **Status** : ⚠ partial
- **Code 위치** : `templates/phase_z2/catalog/frame_contracts.yaml` / `tests/matching/v4_full32_result.yaml` / `templates/phase_z2/slide_base.html` / `templates/phase_z2/families/*.html` / `figma_to_html_agent/blocks/`
- **Gap** : `accepted_content_types``sub_zones` 필드 contract 에 미선언. *visual_hints* 는 일부만 (min_height_px). *density envelope* 미선언.
#### Step 1. MDX 업로드
사용자 MDX 파일 입력. 목표 : *MDX 1 → 자동 슬라이드 1 장*.
- **Status** : ✅ implemented
- **Code 위치** : `src/phase_z2_pipeline.py` 의 CLI entry (`run_phase_z2_mvp1(mdx_path, run_id)`)
#### Step 2. MDX 정규화
업로드된 MDX 를 파이프라인 표준 구조로 변환. frontmatter 분리 / slide title / heading tree / section id / 대중소 목차 관계 / note·footer·details 분리 / **raw content 보존**.
- **Status** : ⚠ partial
- **Code 위치** : `parse_mdx()` (frontmatter, ## sections, footer 추출) + `align_sections_to_v4_granularity()` (### drilling)
- **Gap** : heading tree 자체는 미생성 (현재 flat list). note / details 분리 미완. 대중소 목차 관계도 implicit. 정규화 결과가 *단순 문자열 + section_id* 수준 — heading tree 가 있는 *정규화 MDX 모델* 이 아직 아님.
#### Step 3. Content Object 추출
각 section 의 raw content 를 type 별 객체로 분해. text_block / bullet_list / numbered_list / table / image / diagram / jsx_block / note / details / long_original. *MDX 원문 보존, AI 요약 X*.
- **Status** : ❌ missing
- **Cross-reference** : `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §1 (content_object 정규화 schema)
#### Step 4. Section Internal Composition Planning
각 section 을 어떻게 다룰지 결정 — *whole-section 단일 frame 매칭* / *child-section grouping* / *content-type split* 의 3-way decision. split 인 경우 *Internal Region* 들로 분해 + region 비율 산정. **이 단계가 frame matching 보다 *먼저* 와야 함**.
- **3-way decision** :
```
section 전체 → 1 frame 매칭 가능?
├ YES → whole-section frame match (single-region zone)
└ NO → child-section grouping 가능?
├ YES → group merge → 1 frame 매칭 (single-region zone)
└ NO → content-type split
→ text region / table region / image region / details region
→ region 비율 산정 (예: text 80% / table 20%)
→ multi-region zone
```
- **출력** :
- `section_layout_signature` = text_only / text_plus_table / text_plus_image / table_heavy / image_with_caption / mixed_visual_text / details_heavy
- `composition_decision` = whole / group / split
- `internal_regions` (split 인 경우) = [{region_id, role, content_type, ratio_estimate}, ...]
- **Status** : ❌ missing
- **Cross-reference** : `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §2 (Internal Region schema, Layer A — entity / Universal Region Model / 3-way decision tree / 비율 산정 / topology vocabulary / region → frame·display interface). §1 의 content_object size_estimate / role 도 입력 자료.
#### Step 5. Matching Evidence 생성
정규화된 section + layout need 기반으로 V4 매칭 evidence 수집. *최종 선택이 아니라 후보 evidence*.
- **대상** : 소목차 section / 중목차 parent / 필요 시 sibling group 후보
- **V4 출력** : top-k frame candidates (frame_id, template_id, confidence, label, axes score)
- **Label** : use_as_is / light_edit / restructure / reject
- **Status** : ⚠ partial
- **Code 위치** : `lookup_v4_match()` in `phase_z2_pipeline.py`
- **Gap** : 현재 *rank-1 만* 반환. top-k 사용 안 됨. sibling group 후보도 없음.
#### Step 6. Composition Planning
어떤 MDX 덩어리를 하나의 *slide-level zone unit* 으로 볼지 결정. child 따로 / sibling 묶기 / parent 단위.
- **판단 기준** : heading 관계 / content_object 구조 / section_layout_signature / V4 top-k evidence / frame compatibility / capacity fit / content density
- **Status** : ⚠ partial
- **Code 위치** : `src/phase_z2_composition.py` (`plan_composition`, `parent_merged_inferred`, `capacity_fit` integration)
- **Gap** : section_layout_signature / content_object 구조 input 부재 (step 3, 4 가 없어서). frame compatibility 도 rank-1 매칭만 활용.
#### Step 7. Slide-Level Layout Planning
composition unit 개수와 성격을 보고 slide 전체 layout 선택. *기존 Type A/B/B'/B'' 의 후속 — 8-vocabulary 로 명시화*.
- **8 layout vocabulary** : single, horizontal-2, vertical-2, top-1-bottom-2, top-2-bottom-1, left-1-right-2, left-2-right-1, grid-2x2
- **Status** : ⚠ partial
- **Code 위치** : `src/phase_z2_composition.py` 의 `select_layout_preset()` + `LAYOUT_PRESETS`
- **Gap** : 현재 *count-based 만* (1→single, 2→horizontal-2, 3→top-1-bottom-2, 4→grid-2x2). "성격" (content_object 분포 / section_layout_signature) 미반영. 8 preset 중 horizontal-2 + single 만 실제 검증됨.
#### Step 8. Zone + Internal Region Ratio Planning
선택된 layout 안에서 각 zone 의 크기 / 비율 결정 + 각 zone *내부의* Internal Region 비율 결정. *두 단계 ratio* 산정 (zone-level + region-level). *50/50 고정 X*. *slide-base / title / divider / footer / gap 임의 축소 금지*.
- **두 단계 ratio** :
- zone-level : layout 의 각 zone 크기 (slide-body 안 분배)
- region-level : 각 zone 안 Internal Region 비율 (single-region 이면 100%, multi-region 이면 Step 4 의 ratio_estimate)
- **기준** : composition unit 중요도 / content_object 분량 / text·table·image 비중 / frame aspect / capacity / min·max zone / region 별 content type
- **Status** : ⚠ partial
- **Code 위치** : `compute_zone_layout()` (min_height + content_weight 분배) + `build_layout_css()` in `phase_z2_pipeline.py`
- **Gap** : horizontal-2 만 zone-level dynamic. 나머지 7 preset 은 fr-default. **region-level ratio 미구현** (Internal Region 자체가 Step 4 부재로 입력 X). content_object 분량 기반 정밀화 미반영.
#### Step 9. Region-Level Frame / Display Selection
각 *Internal Region* 에 들어갈 frame 또는 display strategy 확정. step 5 evidence 위에 composition / layout / region 제약 반영해 *최종* 선택. *unit of analysis = region*. single-region zone 은 자연스럽게 zone 1:1 frame 선택과 같음.
- **region 별 처리** :
- text region → text frame 매칭 (현 runtime-verified contract set 기준 F13 / F29 / F16 등)
- table region → table preview / details / table frame
- image region → image area / image frame
- details region → details / popup 전용 region
- **Label 처리** (region 단위) :
- use_as_is → deterministic slot mapping
- light_edit → 같은 frame contract 유지, minor adaptation 가능
- restructure → frame 후보 유지하되 content-to-slot 재배치 proposal 필요
- reject → 자동 적용 X
- **Status** : ⚠ partial — *step 5 와 분리되지 않음 + region-level 미구현 (zone 단위 만)*
- **Code 위치** : `plan_composition()` 이 V4 rank-1 즉시 선택 (step 5 와 conflate, zone 단위)
- **Gap** : top-k 활용 / composition 제약 반영한 final 단계가 없음. *region-level 매칭 부재* (현재 zone 단위만). restructure label 은 현재 *filter* (선택 X). MVP1_ALLOWED_STATUSES = {matched_zone, adapt_matched_zone} 만 통과.
#### Step 10. Frame Contract 확인
선택된 frame 의 contract 읽어서 accepted_content_types / slots / sub_zones / cardinality / capacity / visual_hints / density envelope / asset 확인.
- **Status** : ⚠ partial
- **Code 위치** : `get_contract()` + `frame_contracts.yaml` (F13/F29/F16)
- **Gap** : `accepted_content_types` 미선언. `sub_zones` 미선언. `density envelope` 미선언.
- **Cross-reference** : `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §3 (frame contract + Frame Slot, Layer B)
#### Step 11. Content Unit / Child Group → Internal Region → Frame Slot Mapping
각 zone 안에서 *Internal Region 별로* content unit 또는 child group 을 배치 → 그 region 의 frame 내부 *Frame Slot* 에 매핑. 표 작으면 inline / 크면 preview + 자세히보기 / image aspect 유지 / 긴 원문 details / text capacity 내.
- **2 단계 매핑** :
- Layer A : content unit / child group → Internal Region (Step 4 의 region 분할 결과 소비)
- Layer B : Internal Region 안 → Frame Slot (frame contract 의 sub_zone 선언 소비)
- **Status** : ❌ missing
- **Cross-reference** : `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` §4 (placement algorithm 2-stage: Stage A → Stage B) + §5 (display strategy). *해당 SPEC 의 `sub_zones` (YAML 필드명) = Frame Slot (Layer B). Internal Region (Layer A) 는 §2 에 정의됨.*
#### Step 12. Slot Payload 생성
frame partial 에 주입할 데이터 생성. *deterministic mapper* 가 기본. *AI 는 normal path 에 없음*.
- **AI 가능 위치 (제한적)** : light_edit / restructure 에서 content_object → slot 배치 proposal 필요 시
- **AI 금지** : MDX 원문 요약·삭제 / HTML·CSS 직접 생성 / 새 디자인 임의 / layout·frame 임의 선택
- **Status** : ✅ implemented (deterministic 부분)
- **Code 위치** : `src/phase_z2_mapper.py` (`map_with_contract`, PAYLOAD_BUILDERS, ITEM_PARSERS)
- **Gap** : restructure label 의 AI proposal path 미구현 (현재 restructure 는 filter). content_object → sub_zone 매핑이 step 11 부재로 *implicit*.
### Block B — RENDER
#### Step 13. Render
Jinja2 로 HTML 생성. **고정** : slide-base / slide size / title / divider / footer / slide-body. **가변** : layout / zone ratio / frame partial / slot payload / assets.
- **산출** : final.html / assets/ / debug.json / preview.png
- **Status** : ✅ implemented
- **Code 위치** : `render_slide()` in `phase_z2_pipeline.py` + `templates/phase_z2/slide_base.html` + `templates/phase_z2/families/*.html`
### Block C — POST-RENDER TELEMETRY / EXCEPTION HANDLING
> 본 block 의 핵심 — *A block (planning) 이 정밀하면 거의 trigger 안 일어남*. 이상적으로 대기 상태. exception 케이스의 *진단 + 다음 capability 안내*.
#### Step 14. Selenium Visual Runtime Check
브라우저 렌더링 기준 실제 결과 검사. slide size / zone overflow / frame internal clipping / text·table·image clipping / content truncation.
- **Status** : ⚠ partial
- **Code 위치** : `run_overflow_check()` in `phase_z2_pipeline.py`
- **Gap** : 현재 *text / structural element overflow* 만 검사. image aspect mismatch / table clipping / under-fill 검사 미구현. clipped_inner 의 inner_content_signals 는 추가됨 (A1 step).
#### Step 15. Fit Classification
visual fail 발생 시 원인 분류.
- **카테고리** : minor_overflow / structural_minor_overflow / structural_major_overflow / tabular_overflow / image_aspect_mismatch / frame_capacity_mismatch / layout_zone_mismatch / hard_visual_fail
- **Status** : ✅ implemented (text / structural 도메인)
- **Code 위치** : `src/phase_z2_classifier.py` (`classify_visual_runtime_check`, `CONTENT_TYPE_PATTERNS`)
- **Cross-reference** : `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` §1 / §2 / §3
- **Gap** : image_aspect_mismatch / tabular_overflow 분류는 정의됐지만 *실제 trigger 가 step 14 의 검사 부재로 일어나지 않음*.
#### Step 16. Overflow Router
fit classification 결과를 action 후보로 매핑.
- **매핑 예** : structural_minor_overflow → zone_ratio_retry / tabular_overflow → details_popup_candidate / image_aspect_mismatch → image_fit_candidate / frame_capacity_mismatch → frame_internal_fit_candidate
- **Status** : ✅ implemented
- **Code 위치** : `src/phase_z2_router.py` (`route_fit_classification`, `ACTION_BY_CATEGORY`)
- **Cross-reference** : `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` §4
#### Step 17. Implemented Action 실행
구현된 action 만 실행. retry budget 제한 / 성공시만 final.html promote / 실패 candidate 는 final.html 아님 / 공통 CSS·padding·tolerance 변경 X / MDX 내용 삭제·요약 X.
- **Status** : ⚠ partial
- **Implemented** : `zone_ratio_retry` (A3)
- **Code 위치** : `src/phase_z2_retry.py` (`plan_zone_ratio_retry`, `apply_retry_to_layout_css`) + `_attempt_zone_ratio_retry` orchestrator in `phase_z2_pipeline.py`
- **Missing actions** : `layout_adjust` / `frame_reselect` / `details_popup_escalation` / `image_fit_candidate` / `frame_internal_fit_candidate`
- **Note (사용자 잠금)** : `frame_internal_fit_candidate` 가 *허용할 수 있는 내부 sub-mechanism* (density envelope / line rhythm / internal grid row / text block allocation 등) 은 *frame contract 가 declare 한 envelope 안* 에서만 동작하는 *내부 영역*. **별도 action label 로 등재하지 않음** — `density_adjust_candidate` 같은 이름은 *공통 CSS/padding 축소 antipattern* 을 초대할 위험이 있어 *unified label `frame_internal_fit_candidate` 하나* 로 묶음.
#### Step 18. Failure Classification
action 실패 시 원인 분류.
- **Failure types** : donor_slack_insufficient / no_donor_candidates / rerender_still_fails / not_attempted
- **Status** : ✅ implemented
- **Code 위치** : `src/phase_z2_failure_router.py` (`classify_retry_failure`, `FAILURE_TYPE_DESCRIPTIONS`)
#### Step 19. Next Action Proposal
실패 원인 + 원래 overflow severity *함께* 보고 다음 후보 기록. failure_type 단독 X. **overflow_category + line_equivalent + failure_type 의 결합**으로 결정.
- **예시 (severity-aware)** :
- structural_minor_overflow + donor_slack_insufficient → frame_internal_fit_candidate
- structural_major_overflow + * → details_popup_candidate
- tabular_overflow + * → table_preview_or_details_candidate
- frame mismatch → frame_reselect_candidate
- **Status** : ⚠ partial
- **Code 위치** : `src/phase_z2_failure_router.py` (`route_retry_failure`, `NEXT_ACTION_BY_FAILURE`)
- **Gap** : 현재 *failure_type 단독* mapping (1-차원). severity (overflow_category × line_equivalent) 와의 *2-차원* mapping 미구현. `frame_internal_fit_candidate` 의 *execution contract / internal envelope* 미정의 (label 자체는 router/failure routing 에 등장하지만 *실제로 어떻게 동작하는지 + frame contract 가 declare 할 envelope 의 형식* 은 미정).
#### Step 20. Slide Status 결정
final.html 존재 ≠ PASS. 정확한 상태 분류.
- **Status enum** : PASS / RENDERED_WITH_VISUAL_REGRESSION / PARTIAL_COVERAGE / ABORTED
- **판단** : 모든 section coverage + visual ok → PASS / visual fail 있음 → RENDERED_WITH_VISUAL_REGRESSION / 일부 section 만 렌더 → PARTIAL_COVERAGE / 필수 단계 실패 → ABORTED
- **Status** : ✅ implemented
- **Code 위치** : `compute_slide_status()` in `phase_z2_pipeline.py`
#### Step 21. Debug / Trace 기록
전체 의사결정을 debug.json 에 기록. 정규화 MDX / content_objects / section_layout_signature / V4 evidence / composition_units / layout / zone sizes / frames / contracts / sub_zone mapping / slot_payload / render result / visual check / fit classification / router decision / action trace / failure classification / next action proposal / slide_status.
- **Status** : ⚠ partial
- **Code 위치** : `write_debug_json()` in `phase_z2_pipeline.py`
- **Gap** : content_objects / section_layout_signature / sub_zone mapping 항목은 step 3, 4, 11 부재로 미기록. *region-level telemetry* (region count / region ratios / region-level frame matching / region-level display strategy) 도 Internal Region (Layer A) 부재로 미기록. 그 외 항목은 모두 기록됨.
#### Step 22. 사용자 확인 / Export
사용자가 결과 확인. 현재 목표 = MDX → 자동 슬라이드 1 장 → status / debug. 향후 = layout 재선택 UI / top3 frame 선택 UI / zone 이동 / HTML 다운 / Gitea push.
- **Status** : ❌ missing (UI 영역 — 현재 범위 외)
- **Code 위치** : 없음 (CLI 만)
---
## Status matrix 요약
| Block | Step | Status |
|---|---|---|
| A | 0. 사전 준비 | ⚠ partial |
| A | 1. MDX 업로드 | ✅ |
| A | 2. MDX 정규화 | ⚠ partial |
| A | 3. Content Object 추출 | ❌ |
| A | 4. Section Internal Composition Planning | ❌ |
| A | 5. Matching Evidence | ⚠ partial (rank-1 only) |
| A | 6. Composition Planning | ⚠ partial |
| A | 7. Slide-Level Layout Planning | ⚠ partial (count-based) |
| A | 8. Zone + Internal Region Ratio Planning | ⚠ partial (zone-level horizontal-2 만 dynamic, region-level 미구현) |
| A | 9. Region-Level Frame / Display Selection | ⚠ merged with step 5 + region-level 미구현 |
| A | 10. Frame Contract 확인 | ⚠ partial (no sub_zones) |
| A | 11. Content Unit / Child Group → Internal Region → Frame Slot Mapping | ❌ |
| A | 12. Slot Payload 생성 | ✅ (deterministic) |
| B | 13. Render | ✅ |
| C | 14. Selenium Visual Runtime Check | ⚠ partial (text/structural only) |
| C | 15. Fit Classification | ✅ |
| C | 16. Overflow Router | ✅ |
| C | 17. Implemented Action 실행 | ⚠ partial (zone_ratio_retry only) |
| C | 18. Failure Classification | ✅ |
| C | 19. Next Action Proposal | ⚠ partial (1-D mapping) |
| C | 20. Slide Status 결정 | ✅ |
| C | 21. Debug / Trace 기록 | ⚠ partial (planning trace 누락) |
| C | 22. 사용자 확인 / Export | ❌ (UI 미구현) |
**핵심 gap 위치 (❌ 표시)** :
- Step 3 — Content Object 추출
- Step 4 — Section Internal Composition Planning (3-way decision + Internal Region 분할)
- Step 11 — Content Unit / Child Group → Internal Region → Frame Slot Mapping
- Step 22 — 사용자 UI
**부분 구현 위치 (⚠) 의 주요 결손** :
- Step 5 — top-k 미사용
- Step 8 — region-level ratio 미구현 (zone-level horizontal-2 만 dynamic)
- Step 9 — Step 5 와 conflate + region-level 매칭 부재
- Step 10 — sub_zones 미선언 (frame contract / Layer B)
- Step 14 — image / table 검사 부재
- Step 17 — `zone_ratio_retry` 외 action 모두 미구현
- Step 19 — severity-aware 2-차원 매핑 미구현
- Step 21 — planning trace 누락 (step 3, 4, 11 부재 종속) + region-level telemetry 미기록 (Layer A 부재 종속)
---
## 기존 spec 문서 cross-reference
| Spec 문서 | 다루는 step |
|---|---|
| `docs/architecture/PHASE-Z-CATALOG-RUNTIME-DESIGN.md` | Step 0 (catalog 룰), Step 10 (frame contract), Step 12 (mapper) |
| `docs/architecture/PHASE-Z-FRAME-STYLE-INVENTORY.md` | Step 0 (frame inventory) |
| `docs/architecture/FRAME-INTEGRATION-MAP.md` | Step 0 (frame inventory) |
| `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` | Step 14, 15, 16, 17, 18, 19 |
| `docs/architecture/PHASE-Z-CONTENT-OBJECT-SUBZONE-SPEC.md` | Step 3 (§1), Step 4 (§2 Internal Region / Layer A), Step 10 (§3 frame contract + Frame Slot / Layer B), Step 11 (§4 placement 2-stage + §5 display strategy). *해당 SPEC 의 `sub_zones` (YAML 필드명) = Frame Slot (Layer B). Internal Region (Layer A) 는 §2 에 정의됨.* |
## Memory feedback rules cross-reference
| Memory rule | 적용 step / 의사결정 |
|---|---|
| `feedback_one_step_per_turn` | 모든 step (작업 분할 discipline) |
| `feedback_no_hardcoding` | 모든 step (특히 9, 11, 12, 17) |
| `feedback_ai_role_separation` | Step 12 (AI 위치 제한) |
| `feedback_ai_isolation_contract` | Step 12 (normal path AI 금지) |
| `feedback_phase_z_spacing_direction` | Step 17 (CSS 공통 spacing 변경 금지) |
| `feedback_artifact_status_naming` | Step 20 (slide_status enum) |
| `feedback_auto_pipeline_first` | Block C 전체 (review/UI 개념 끼우지 말 것) |
| `feedback_sample_budget` | Step 1 (미사용 sample 분리 보존) |
| `feedback_detail_quality` | 모든 step (self-check) |
| `feedback_blocks_must_be_css` | Step 13 (frame partial CSS 원칙) |
| `feedback_recipe_variety` | Step 7, 9 (vocabulary 표현 범주) |
| `feedback_absolute_paths` | 보고 / 문서 작성 시 |
| `feedback_html_preview_whitebg` | Step 13 (slide-base 배경) |
| `feedback_figma_*` | Step 0 (figma frame 변환 / asset 작업) |
---
## How to use this document
새 작업 시작 시 :
1. *어느 step* 의 작업인지 식별
2. 그 step 의 *Status* 확인 (✅ / ⚠ / ❌)
3. 해당 step 의 cross-reference 된 spec 문서 / memory rule 확인
4. 작업 결과가 *다른 step 에 영향* 주는지 확인 (block A 변경 → block C 의 trace 자동 변동)
새 spec 문서 추가 시 :
- 본 문서의 *cross-reference 표* 에 등록 (어느 step 영역인지)
새 memory rule 추가 시 :
- 본 문서의 *Memory feedback rules cross-reference* 표에 등록 (어느 step 의 의사결정인지)
작업 도중 — *어느 step 에 속하는지 모르는 작업이 들어오면* — 본 22-step 도면에 매핑이 안 된다는 것 자체가 *작업이 over-scoped 되었거나 새 step 정의가 필요* 하다는 신호.
---
## 본 문서의 보존 / 변경 정책
- 본 문서는 *기준점*. 가벼운 정정 / status 갱신은 진행 가능 (예: ❌ → ⚠ → ✅ 변동)
- *22 step 의 추가 / 제거 / 순서 변경* 은 사용자 명시 잠금 후에만
- 본 문서의 *3-block 구조* 는 architectural reframe lock 의 직접 반영. 변경 시 reframe 자체를 다시 봄
@@ -1,158 +0,0 @@
# Phase Z — pipeline status board
**Snapshot date** : 2026-05-04 (B1~B5 + trace-only runtime 연결 closure 반영 — Layer A telemetry first activation)
**역할** : 현재 위치표 / grading snapshot. *지도 본문* 은 [`PHASE-Z-PIPELINE-OVERVIEW.md`](PHASE-Z-PIPELINE-OVERVIEW.md).
| 문서 | 역할 | 변동 |
|---|---|---|
| `PHASE-Z-PIPELINE-OVERVIEW.md` | 고정 지도 (22-step 도면) | 거의 안 바뀜 |
| `PHASE-Z-PIPELINE-STATUS-BOARD.md` | 현재 진행 snapshot | 자주 갱신 |
본 문서가 *하지 않는* 것 :
- 새 구현 제안 X
- next step 추천 X
- 우선순위 결정 X
- A/B/C 선택지 X
- MDX03 / MDX04 추가 분석 X
- 코드 변경 X
- OVERVIEW 구조 수정 X
---
## 1. Counting rule
```
Step 0 = precondition (파이프라인 가동 전 사전 준비)
Step 1~22 = runtime pipeline ("22-step pipeline" = 이 범위)
총 항목 수 = 23 (Step 0 + Step 1~22)
명명 = "22-step" (runtime 기준)
```
Step 0 은 본체가 아닌 *준비 조건*. Step 1 (MDX 업로드) 부터가 runtime entry.
---
## 2. 22-step status board
| Block | Step | 이름 | Status |
|---|---|---|---|
| — | 0 | 사전 준비 (catalog / contract / V4 / template / asset) | ⚠ partial |
| A | 1 | MDX 업로드 | ✅ |
| A | 2 | MDX 정규화 | ⚠ partial |
| A | 3 | Content Object 추출 | ⚠ partial (B1 v0 dormant module + trace-only runtime 호출, render path 미연결) |
| A | 4 | Section Internal Composition Planning | ⚠ partial (B2 v0 dormant module + trace-only runtime 호출, render path 미연결) |
| A | 5 | Matching Evidence 생성 | ⚠ partial (rank-1 only) |
| A | 6 | Composition Planning | ⚠ partial |
| A | 7 | Slide-Level Layout Planning | ⚠ partial (count-based) |
| A | 8 | Zone + Internal Region Ratio Planning | ⚠ partial (zone-level horizontal-2 만 dynamic, region-level 은 B2 안 partial) |
| A | 9 | Region-Level Frame / Display Selection | ⚠ partial (B4 가 catalog cover + declaration order 로 frame 선택 분담 / V4 evidence 미통합 / Step 5 와 conflate 잔존) |
| A | 10 | Frame Contract 확인 | ⚠ partial (B3 의 accepted_content_types + sub_zones 선언 추가 — B4 만 읽음, mapper 미읽음 / density envelope 별 axis) |
| A | 11 | Content Unit / Child Group → Internal Region → Frame Slot Mapping | ⚠ partial (B4 v0 dormant 2-stage + region 1:1 sub_zone + narrowest first + trace-only runtime 호출, render path 미연결) |
| A | 12 | Slot Payload 생성 | ✅ (deterministic) |
| B | 13 | Render | ✅ |
| C | 14 | Selenium Visual Runtime Check | ⚠ partial (text/structural overflow + B5 frame_slot_metrics F29 만 / image / table 검사 부재) |
| C | 15 | Fit Classification (A1) | ✅ |
| C | 16 | Overflow Router (A2) | ✅ |
| C | 17 | Implemented Action 실행 (A3) | ⚠ partial (zone_ratio_retry only) |
| C | 18 | Failure Classification (A4-1) | ✅ |
| C | 19 | Next Action Proposal (A4-2) | ⚠ partial (1-D mapping) |
| C | 20 | Slide Status 결정 | ✅ |
| C | 21 | Debug / Trace 기록 | ⚠ partial (placement_trace per-zone 기록 + frame_slot_metrics F29 기록 — render path 활성화 X / region marker partial 미주입) |
| C | 22 | 사용자 확인 / Export | ⚠ future (UI 영역 — 현재 범위 외) |
범례 :
- ✅ implemented
- ⚠ partial
- ❌ missing
- ⚠ future (현 범위 외 — 후속)
---
## 3. 핵심 missing (전이 후)
이전 Step 3 / 4 / 11 = ❌ missing → **본 session 작업으로 ⚠ partial 로 전이**.
**현재 *남은* gap** :
```
1. render path 의 placement_trace 활용 X
- B4 PlacementPlan 이 trace-only — render_slide() / mapper 가 미사용
- region-id / content_unit-id marker 가 partial template 에 미주입 (B5 후속 axis)
2. B4 frame_selection 의 V4 evidence 미통합
- B4 v0 = catalog declaration order 만 (cover + first-match)
- composition_planner 의 V4 rank-1 와 *cross-axis 비교 자료* 만 — 통합 미완
3. region-level / Frame Slot-level partial 측정
- B5 v0 frame_slot_metrics = F29 1 partial 만
- F13 / F16 marker 미적용
4. 그 외 잔존
- rules 2~5 (region-preview-details / region-grid-2x2 / region-main-support /
region-horizontal-split) 의 algorithm 미구현 (SPEC v1 §2.5 deferred)
- frame contract 의 density envelope 미선언
- tabular_overflow / image_aspect_mismatch 검사 부재 (Step 14)
- layout_adjust / frame_reselect / details_popup_escalation / image_fit /
frame_internal_fit_candidate (Step 17 missing actions) 미구현
```
**Cross-cutting Layer A — 진전 단계 정리** :
| 단계 | 상태 |
|---|---|
| (a) OVERVIEW reframe (Layer A first-class lock + Universal Region Model) | ✓ |
| (b) SPEC v1 schema (Internal Region §2 + topology vocabulary §2.5 + 2-stage placement §4) | ✓ |
| (c) PLAN v1 (cross-ref sync) | ✓ |
| (d) B1 v0 ContentObject extractor (dormant) | ✓ |
| (e) B2 v0 InternalRegion planner (dormant) | ✓ |
| (f) B3 frame_contracts.yaml extension (dormant catalog 면) | ✓ |
| (g) B4 v0 placement planner (dormant) | ✓ |
| (h) B5 v0 Frame Slot telemetry markers (F29 만) | ✓ |
| (i) trace-only runtime 연결 (B1→B2→B4 real data 첫 호출 / debug.json placement_trace) | ✓ |
| (j) **render path 활성화 (region marker partial 주입 / B4 → mapper 통합 / V4 evidence 통합)** | **❌ pending** |
= (a)~(i) 완료 + (j) 가 *남은 핵심 axis* (B5 후속 / runtime 통합).
**Step 22** 는 별도 범주 (UI 영역 — 현재 자동 파이프라인 범위 외).
---
## 4. 구조 적절성 검토 (brief)
> snapshot — 22-step *재구성 / 합치기 / 쪼개기 제안 X*. OVERVIEW 영역.
- **3-block 구조 (A 계획 / B 렌더 / C 사후 telemetry) 적절**. 위계 추가 (Zone Internal Region) 후에도 block 경계는 변동 없음
- **Step 3~4 가 Step 5 보다 앞** 인 순서 적절. content_object 와 internal composition decision (3-way) 이 frame matching 의 입력이어야 함
- **Step 5 (evidence 생성) 와 Step 9 (final frame / display 선택) 가 분리** 된 구조 적절. 현재 conflate 된 건 구현 결손이지 도면 결손 아님. Step 9 의 unit of analysis = *region* 으로 reframe (OVERVIEW)
- **A1~A4 는 post-render telemetry layer**. 진짜 fit policy 의 자리는 Block A (composition planning, region 분할 포함). C block 은 *exception 처리 + 진단 안내*
- **Universal Region Model 적용 후에도 step numbering 보존** : Layer A 도입은 step 추가가 아니라 Step 4 / 8 / 9 / 11 의 *granularity unit shift* 로 흡수됨. step 0 ~ 22 그대로
- **Layer A trace-only runtime 활성화 = boolean 차원 X** : B1~B4 가 *real MDX runtime 위에서 호출* 되나 *render path 미대체*. debug.json 의 placement_trace = *진단 telemetry only* — final.html / canonical SHA 미영향. *render 활성화* 는 별 axis (B5 후속)
---
## 5. AI 사용 위치 (runtime 기준)
```
runtime AI = Step 12 의 light_edit / restructure 1 곳만
├ 입력 : content_object + frame contract + Internal Region 배치 + Frame Slot 명세
├ 출력 : content → Internal Region / Frame Slot proposal
└ 금지 : MDX 원문 요약·삭제 / HTML·CSS 직접 생성 / layout·zone·region·frame 임의 선택
```
Step 0 (사전 준비) 의 Figma → HTML 변환은 *precondition phase 의 작업* — runtime AI 아님.
다른 step 에서의 AI 호출은 본 도면 안에 *없음*.
---
## 6. 현재 병목 (한 줄)
> 현재 Phase Z 의 *Layer A pre-render planning* (Step 3 / 4 / 11) 은 본 session 작업으로 ❌ → ⚠ partial 전이 (B1/B2/B4 dormant module + trace-only runtime 호출). *Layer A telemetry 의 first activation* — debug.json 의 placement_trace per-zone + frame_slot_metrics F29 partial 기록. 단 **render path 활성화는 미완** : B4 PlacementPlan 이 mapper output 을 *대체하지 않고* trace-only / region-id / content_unit_id marker 가 partial template 에 *미주입* / B4 frame_selection 이 V4 evidence *미통합*. 핵심 다음 axis = **(B5 후속) render path 의 placement_trace 활용 + region marker runtime activation + V4 통합**. *runtime contract-registered / verified frame set 이 text-frame 중심* 한계는 잔존 (frame inventory audit / refinement 별 axis).
---
## 사용 방법
- 새 작업 들어오면 → 본 board 의 *어느 step* 의 status 를 바꾸는 작업인지 식별
- 작업이 *Step 매핑이 안 되면* → over-scoped 또는 새 step 정의 필요 (OVERVIEW 영역)
- ✅ → ⚠ → ❌ status 전이 / 갱신 시 → 본 board 만 수정. OVERVIEW 는 step 추가/제거/순서 변경 시에만
-69
View File
@@ -1,69 +0,0 @@
# Typography Tokens — 초안 v1
## Global Hierarchy Tokens
슬라이드 전체의 공통 글자 위계. 모든 블록이 이 기준을 따른다.
| 위계 | 토큰명 | font-size | font-weight | line-height | 용도 |
|------|--------|-----------|-------------|-------------|------|
| 대목차 | `--font-slide-title` | 22px | 700 | 1.4 | 슬라이드 상단 제목 |
| 중목차 | `--font-zone-title` | 13px | 700 | 1.4 | zone 제목 (## 대목차 하위) |
| 소목차 | `--font-sub-title` | 12px | 700 | 1.45 | 블록 내 소제목, 카드 제목 |
| 본문 | `--font-body` | 11px | 400~500 | 1.55 | 블릿, 설명 텍스트 |
| 캡션 | `--font-caption` | 10px | 400 | 1.4 | 각주, 출처, 보조 텍스트 |
| footer | `--font-footer` | 20px | 700 | 1.2 | 핵심 인사이트 pill |
---
## Component Semantic Token 후보
위계 6종으로 안 덮이는 역할. 가까운 위계에서 기본값을 가져오되, 필요시 override.
| 역할 | 기반 위계 | 예상 override | 비고 |
|------|-----------|---------------|------|
| body-strong (본문 강조) | 본문 (11px) | weight: 600~700 | 본문 heading, 인라인 강조 |
| detail-link (자세히보기) | 캡션 (10px) | weight: 500, color: muted | 링크 텍스트 |
| pill-label | 소목차 (12px) | weight: 700, color: white | pill/badge 안 라벨 |
| table-header | 소목차 (12px) | weight: 700, color: white, bg: dark | 표 헤더 셀 |
| table-cell | 본문 (11px) | - | 표 데이터 셀 |
| compare-badge | 소목차 (12px) | weight: 700 | 비교 블록 VS 뱃지 |
| callout | 소목차 (12px) | weight: 700, color: accent | 강조 인용 |
| overline | 캡션 (10px) | weight: 600, letter-spacing | 상단 라벨 |
---
## Source
현재 코드에서 추출한 실제 사용값 기준.
| 출처 | 대목차 | 중목차 | 소목차 | 본문 | 캡션 | footer |
|------|--------|--------|--------|------|------|--------|
| slide-base.html | 22px | - | - | - | - | 20px |
| block_assembler.py zone title | - | 13px | - | - | - | - |
| block_assembler.py direct render | - | - | 12px | 11px | - | - |
| block_assembler.py .bul | - | - | - | 11px | - | - |
| FontHierarchy (legacy) | - | - | key_msg 14 | core 12 | sidebar 10 | - |
---
## FontHierarchy 매핑 (보조 체계)
기존 FontHierarchy는 역할 기반이므로, 위계 기반으로 매핑하여 보조로 유지.
| FontHierarchy | 위계 매핑 | 비고 |
|---------------|-----------|------|
| key_msg (14px) | 소목차~중목차 사이 | 강조 메시지용, component token 후보 |
| core (12px) | 소목차 | 기본 블록 제목급 |
| bg (11px) | 본문 | 배경/보조 텍스트 |
| sidebar (10px) | 캡션 | 사이드바/첨부 |
---
## 상태
- [x] inventory 수집 완료
- [x] global hierarchy v1 초안
- [x] component token 후보 분리
- [ ] typography.css 파일 작성
- [ ] spacing tokens 정의
- [ ] color tokens 정의 (공통 + 의미색)
Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

Some files were not shown because too many files have changed in this diff Show More