Author SHA1 Message Date
Kyeongmin bfe9225967 On main: B4 role reframe + F29 expansion experiments — held during Option E (phase_z2_pipeline/classifier/placement_planner/content_extractor) 2026-05-07 11:14:49 +09:00
Kyeongmin 5210aea00f index on main: 761a43d Add Phase Z B4 source-shape-aware placement 2026-05-07 11:14:49 +09:00
Kyeongmin 761a43da5e Add Phase Z B4 source-shape-aware placement
- enable B1/B2/B4 source-shape-aware F13 placement behind env flag
- align F13 placement_trace with mapper top_bullets cardinality
- preserve canonical render output when flag is off
2026-05-07 05:26:57 +09:00
Kyeongmin 8a201337f7 Add Phase Z B4 gatekeeper feature flag
- gate V4/B4 mismatch zones via PHASE_Z_B4_GATEKEEPER env (default OFF)
- record mismatch as adapter_needed_units with reason and mismatch_detail
- preserve render path byte-identical when flag unset
2026-05-06 17:01:47 +09:00
Kyeongmin aca6a992b9 Add Phase Z matching architecture anchor doc
- consolidate evidence-based principles for Step 5/6/7 and B-axis matching layer
- record CompositionUnit, candidate-based composition, and 4-tier terminology
- defer bridge architecture and slot_payload evolution as scope-locked open items
2026-05-06 15:46:52 +09:00
Kyeongmin 565e6b092e Add Phase Z classifier placement diagnostics
- consume debug_zones[i].placement_trace in classify_visual_runtime_check
- surface per-zone diagnostic in fit_classification.placement_diagnostics
- preserve canonical render SHA and existing classifier output schema
2026-05-04 17:40:21 +09:00
Kyeongmin b6b9173d2b Update CLAUDE anchor for Phase Z Layer A telemetry
- align Phase Z hierarchy terms with Layer A and Frame Slot planning
- reframe Type A/B/B'/B'' as legacy layout hints
- record trace-only render path activation as the current anchor model
2026-05-04 15:22:24 +09:00
Kyeongmin 4320f5e838 Update Phase Z status board for Layer A runtime trace
- mark content object, internal region, and placement steps as partial
- record B1-B5 and trace-only runtime activation status
- refresh current missing items and bottleneck summary
2026-05-04 10:52:06 +09:00
Kyeongmin 425a3054c1 Add Phase Z placement trace telemetry
- run B1/B2/B4 placement planning in trace-only mode
- record placement_trace per rendered zone in debug output
- preserve existing render output and visual routing behavior
2026-05-04 10:32:26 +09:00
Kyeongmin 02a6d44944 Add Phase Z frame slot telemetry markers
- add frame slot markers to F29 runtime partial
- collect per-cell frame slot metrics in visual runtime check
- preserve existing visual status and failure routing behavior
2026-05-04 10:05:03 +09:00
Kyeongmin 3f843d73f7 Add Phase Z Layer A placement planner
- add dormant placement planner integrating B1 / B2 / B3
- region 1:1 sub_zone mapping with narrowest-accepts-first heuristic
- frame selection by accepted_content_types coverage + declaration order
2026-05-04 09:34:49 +09:00
Kyeongmin 4144dc4c21 Add Phase Z runtime templates
- add slide base template for Phase Z rendering
- add family partials for F13, F29, and F16 frames
2026-05-04 09:34:27 +09:00
Kyeongmin 2ec8fc5a77 Add Phase Z Layer A planning scaffold
- add Internal Region model to Phase Z architecture docs and specs
- add frame contract content type and Frame Slot declarations
- add dormant content object extractor and internal region planner
2026-05-04 08:21:50 +09:00
Kyeongmin e7848b602d Add Phase Z runtime foundation
- add visual fit classifier, router, retry, and failure routing modules
- add composition planner and catalog-driven mapper
- add Phase Z pipeline orchestration and architecture docs
2026-05-04 08:21:28 +09:00
KyeongminandClaude Opus 4.6 79f0c55745 README: MDX 상태 수정 — 슬라이드 변환은 모두 완료, 파이프라인 자동화가 개선 대상
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:45:14 +09:00
KyeongminandClaude Opus 4.6 620da8de13 README 정리: 안 된 것 섹션 제거, MDX 01 추가, AS-IS/TO-BE 차이 명확화
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:14:56 +09:00
KyeongminandClaude Opus 4.6 b60a089e6c README를 프로젝트 설명 중심으로 재구성
- 무엇인가 → 구성/구현 → 문제점 → 개선 방향(AS-IS/TO-BE) → 현황 → 다음 단계
- 설계/계획 내용은 docs/architecture/ 문서로 분리 참조

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:08:02 +09:00
KyeongminandClaude Opus 4.6 a385ba08c0 README 업데이트: 토큰 체계, 폴더 구조, 설계 문서, 역할 분리 반영
- 토큰 기반 CSS 체계 섹션 추가 (2층 구조, 글자 위계, 블록 작성 규칙)
- 스타일 폴더 구조 명시 (tokens/themes/base/blocks)
- 실행 원칙 추가 (direct-fit, recipe-path, fit 루프)
- Source of Truth 명시
- 설계 문서 링크 (IMPROVEMENT-PLAN, TOKENS-v1, BLOCK-RULES)
- 역할 분리 (figma_to_html_agent / design_agent)
- 향후 개선 방향을 진행 중/다음 단계로 분리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:01:24 +09:00
KyeongminandClaude Opus 4.6 8e577b3163 토큰 기반 CSS 체계 구축 + slide-base 스타일 분리
- tokens: typography(35변수), spacing(28변수), colors(41변수) 정의
- slide-base.html: 인라인 style 제거, Jinja include로 토큰/CSS 조립
- slide-base.css: 모든 직접값을 토큰 변수 참조로 전환 (직접값 0)
- block_assembler.py: Template → Environment.from_string (include 지원)
- TOKENS-v1.md: 위계 기준표 초안 + component token 후보
- BLOCK-RULES.md: 블록 작성 규칙 (spacing 문구 실제 토큰과 일치)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:56:03 +09:00
KyeongminandClaude Opus 4.6 7a2615fb4b 개선 설계 문서 추가: 토큰 기반 통일 + 블록 구조 부품화 + 2경로 파이프라인
- 목표: MDX → BEPs 매칭 → 슬라이드 자동 생성
- 핵심 축: 표현 기준 통일, 블록 역할 재정의, direct-fit/recipe 2경로화
- 6단계 진행 계획: 토큰 → slide-base 분리 → 규칙 문서 → 폴더 정리 → 블록 전환 → 파이프라인 연결
- 역할 분리: figma_to_html_agent(원재료) / design_agent(조립 시스템)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 12:02:51 +09:00
KyeongminandClaude Opus 4.6 46e53beeaf README 재정리: AS-IS / TO-BE 프로세스 구조 명시
- 현재 프로세스(AS-IS) 7단계 정리
- 개선 프로세스(TO-BE) 5단계 정리
- 핵심 차이: 꼭지 추출 시점, redesign 반복 조정, 빈 공간 재분배, 시각 검증
- 프로젝트 구조/산출물/향후 방향 포함

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:53:51 +09:00
Kyeongmin 1ff6c6cbb2 Add remaining samples, tooling, and local project assets 2026-04-15 18:02:17 +09:00
Kyeongmin 05d43a7999 Refine README and detail preview layout 2026-04-15 17:59:46 +09:00
897 changed files with 29806 additions and 404 deletions
+70 -1
View File
@@ -56,7 +56,76 @@
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/01*.mdx)", "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/02*.mdx)",
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/03*.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(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": [ "additionalDirectories": [
"d:\\ad-hoc\\kei\\design_agent\\templates\\blocks\\new" "d:\\ad-hoc\\kei\\design_agent\\templates\\blocks\\new"
+111 -14
View File
@@ -12,10 +12,89 @@
--- ---
## 아키텍처 (Phase Q 파이프라인) ## 위계 + 용어 (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 P(다후보 렌더링 비교) 실행 결과 20/100점. 업계 조사(Beautiful.ai, Napkin.ai, VASCAR 등) 기반으로 Phase Q에서 재설계. > Phase P(다후보 렌더링 비교) 실행 결과 20/100점. 업계 조사(Beautiful.ai, Napkin.ai, VASCAR 등) 기반으로 Phase Q에서 재설계.
> 핵심 전환: "계산 먼저, AI 판단 나중에, 렌더링은 검증만" > 핵심 전환: "계산 먼저, AI 판단 나중에, 렌더링은 검증만"
>
> ⚠️ Phase Z (매칭 시스템 통합) 진행 후 이 섹션의 일부는 새 흐름으로 대체될 예정.
``` ```
[1단계] Kei 실장 (Opus) — AI 사고 [1단계] Kei 실장 (Opus) — AI 사고
@@ -65,30 +144,48 @@ reference 꼭지 있음 → sidebar-right
나머지 → single-column 나머지 → single-column
``` ```
### 역할 분리 (Phase R') ### 역할 분리 (Phase Z)
> ⚠️ Phase R' 의 역할 분리 (AI 가 HTML 구조 직접 생성) 는 **Phase Z 에서 폐기**. 아래는 Phase Z 기준.
| 역할 | 담당 | 방식 | 하는 일 | 하지 않는 일 | | 역할 | 담당 | 방식 | 하는 일 | 하지 않는 일 |
|------|------|------|---------|------------| |------|------|------|---------|------------|
| Kei 실장 | Opus (Kei API) | AI | 꼭지 추출, 비중 판단, relation_type + expression_hint 부여, 최종 검수 | HTML 생성, 레이아웃 계산 | | Kei 실장 | Opus (Kei API) | AI | MDX 본문 분석 보조, 최종 검수 | HTML 생성, 레이아웃 계산 |
| 컨테이너 계산 | 코드 | 결정론적 | Kei 비중 → 역할별 컨테이너 px 확정 | AI 판단 불필요 | | 레이아웃 결정 | 코드 | 룰 매칭 | MDX 콘텐츠 → 8-preset layout vocabulary 기반 slide/zone topology 선택 (legacy hint = Type A/B/B'/B'') | — |
| 프리셋 선택 | 코드 | 규칙 | 실장의 role에 따라 프리셋 자동 선택 | AI 판단 불필요 | | 컨테이너 계산 | 코드 | 결정론적 | zone 별 px 확정 (`space_allocator`) | — |
| **HTML 생성** | **AI (Kei API)** | **AI** | **콘텐츠 전달 의도에 맞는 HTML 구조를 직접 생성. 블록 CSS를 참고하되 구조는 AI가 결정.** | 블록 "선택" 안 함. 슬롯 "채우기" 안 함. | | 프레임 매칭 | 코드 (V1~V4; B4 frame_selection evidence integration pending) | 결정론적 | zone ↔ Figma 프레임 매칭 | — |
| 검증 | 코드 + AI | Selenium + 비전 모델 | overflow 측정, 시각 품질 평가 | | | **콘텐츠 매핑** | **코드 + AI** | **하이브리드** | **zone 안 콘텐츠 / 텍스트를 프레임 슬롯에 배치 / 다듬기 / 변형** | **HTML 구조 생성 X** |
| HTML 조립 | 코드 (Jinja2) | 결정론적 | `slide-base` + 프레임 + 슬롯 콘텐츠 합쳐서 final.html | AI 호출 X |
| 검증 | 코드 + AI | Selenium + Vision | overflow 측정, 시각 품질 평가 | — |
### HTML 생성 원칙 (Phase R') ### HTML 생성 원칙 (Phase Z)
``` ```
블록이 구조를 결정 (P=Q=R, 실패) → 콘텐츠가 구조를 결정 (R', 접근 C) 블록이 구조를 결정 (P=Q=R, 실패)
콘텐츠가 구조를 결정 (R', 폐기 — AI 가 HTML 직접 생성하면 회귀 가능성 큼)
→ slide-base + 프레임 DB 가 구조를 결정, AI 는 zone 안 콘텐츠만 (Phase Z)
``` ```
- AI가 콘텐츠 전달 의도(expression_hint)에 맞는 HTML 구조를 직접 생성 **Phase Z 의 핵심 원칙**:
- 기존 38개 블록의 CSS(색상, 폰트, 배경, radius)를 **스타일 참고**로 활용 - **HTML 구조** = `slide-base.html` + 코드 (Jinja2) 가 결정 (절대 AI 가 생성 X)
- 블록을 **"선택"하지 않음**. topic 합침/분리, 포함 관계, 핵심 메시지 분리 가능 - **AI 의 역할** = zone 안 콘텐츠 매핑 / 텍스트 다듬기 / 디자인 변형 (콘텐츠 단위)
- 디자인 토큰(CSS 변수)으로 품질 제약 + Selenium 측정 + 비전 모델 검증 - **프레임 DB 참고 필수** — 자유 디자인 금지
- **MDX 원문 무손실 보존** (본문 preview, 팝업에 원문)
⚠️ **Phase R' 회귀 방지**: AI 한테 "HTML 구조 만들어줘" 같은 호출 절대 X. AI 호출은 zone 안 콘텐츠 단위로만.
--- ---
## 핵심 프로세스 ## 핵심 프로세스 (구 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 붙여넣기 또는 파일 업로드) 사용자 콘텐츠 입력 (텍스트/MDX 붙여넣기 또는 파일 업로드)
+89 -213
View File
@@ -1,248 +1,124 @@
# Kei Design Agent # C.E.L. Slide Pipeline
MDX 콘텐츠를 분석해 1280x720 슬라이드 HTML로 변환하는 파이프라인입니다. ## 이 프로젝트는 무엇인가
이 문서는 "지금 실제 코드 기준으로 파이프라인이 어떻게 동작하는지"를 빠르게 파악하기 위한 개요 문서입니다. 과거 Phase 문서와 일부 legacy 경로는 남아 있지만, 아래 설명은 현재 메인 경로를 기준으로 정리했습니다. MDX 기반 콘텐츠를 입력하면, 1280×720 슬라이드 HTML로 자동 변환하는 파이프라인입니다.
## 한눈에 보기 텍스트 콘텐츠를 넣으면, 구조를 분석하고 BEPs(Figma) 디자인을 매칭하여 슬라이드를 만들어줍니다.
파이프라인의 큰 흐름은 아래와 같습니다. ---
1. MDX를 코드가 정규화한다. ## 어떻게 구성/구현되어 있는가
2. Kei가 문서의 의미와 topic을 읽는다.
3. 코드는 `normalized.sections`를 기준으로 실제 슬라이드 구조를 다시 만든다.
4. 코드는 schema, recipe, tag를 바탕으로 블록을 고른다.
5. Type B는 코드가 템플릿을 조립해 `final.html`을 만든다.
6. Type A는 아직 AI/renderer 비중이 더 크다.
7. Selenium과 vision gate로 측정/검증한 뒤 run 산출물을 저장한다.
핵심 원칙은 다음과 같습니다. ### 전체 흐름
- source of truth는 `normalized.sections` ```
- block 선택은 문서명 하드코딩이 아니라 shape, schema, tag 기반 MDX 입력 → 정규화 → 꼭지 추출(AI) → zone 구분 → BEPs 매칭 → 조립 → 검증 → 출력
- 흐름의 우선순위는 `구조 -> payload -> layout -> fit` ```
- popup/detail은 overflow를 덮는 임시 장치가 아니라 `메인 요약 + 상세 보기`의 2단 표현 계약
## 타입 구조 ### 구조
현재 메인 타입 선택은 사실상 `A`와 `B`입니다. - **slide-base:** 1280×720 슬라이드 프레임. 대목차 + 구분선 + 본문 영역 + 핵심 인사이트(footer)
- **zone:** 본문 영역 안에서 중목차(##) 기준으로 나뉘는 영역 (top/bottom 등)
- **블록:** zone 안에 들어가는 디자인 단위. Figma에서 추출한 BEPs 디자인을 HTML/CSS로 변환한 것
- **catalog:** 블록의 메타 정보 (구조, 슬롯, 매칭 조건)
### Type A ### 주요 파일
- 본문 외에 sidebar, reference, 부록성 영역이 함께 필요한 슬라이드 | 파일 | 역할 |
- 현재는 Type B보다 덜 닫혀 있고, AI 생성 + renderer 경로 비중이 큽니다 |------|------|
| `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` | 블록 메타 정보 |
### Type B ### 산출물
- top, bottom 같은 본문 zone 조합으로 해결되는 슬라이드
- 현재 가장 안정적인 메인 경로입니다
- 최근 구조화 작업은 대부분 이 Type B 경로를 중심으로 진행되었습니다
### Type B' / B''
- 역사적으로 실험/호환 경로에서 나온 변형입니다
- 문서와 일부 legacy 코드에 흔적이 남아 있습니다
- 현재 메인 개념의 1급 타입으로 보기보다, 과거 흐름과 호환 레이어로 이해하는 편이 맞습니다
## 단계별 파이프라인
아래는 현재 기준의 실질적인 단계입니다.
| 단계 | 담당 | 주요 파일 | 하는 일 | 주요 산출물 |
|---|---|---|---|---|
| Stage 0 | 코드 | `src/mdx_normalizer.py` | MDX를 정규화하고 sections, tables, images, popups로 분리 | `NormalizedContent` |
| Stage 1A | AI (Kei/Opus) | `src/kei_client.py` | title, core message, topic, 초기 layout 힌트 추출 | `Analysis`, `Topic[]` |
| Phase Y | 코드 | `src/pipeline.py`, `src/section_parser.py` | `normalized.sections` 기반으로 group schema, recipe, zone/page_structure 결정 | `PageStructure`, `mdx_sections` |
| Stage 1B | AI (Kei/Opus) | `src/kei_client.py` | topic별 relation, source_data, 표현 힌트 보강 | 보강된 `Topic[]` |
| Stage 1B-ST | AI (Kei/Opus) | `src/kei_client.py` | structured_text 생성 | `Topic.structured_text` |
| Stage 1.5a | 코드 | `src/space_allocator.py` | zone/container 크기, preset, font hierarchy 계산 | `containers`, `font_hierarchy` |
| Stage 1.7 | 코드 | `src/block_reference.py`, `templates/catalog.yaml` | tag_match, schema_match, fallback 기준으로 block 선택 | `references` |
| Stage 1.8 | 코드 + Selenium + 일부 AI | `src/pipeline.py`, `src/slide_measurer.py` | fit 측정, overflow 확인, 재배분, 보정 | `fit_result`, `measurement` |
| Stage 2 | 코드 중심 | `src/block_assembler.py` | Type B 기준 slide-base + block template + payload 조립 | `generated_html` |
| Stage 3 | 코드 | `src/renderer.py` | Type A 쪽 Jinja/renderer 조립, Type B는 대체로 생략 | `rendered_html` |
| Stage 4 | 코드 + Vision AI | `src/slide_measurer.py`, `src/kei_client.py` | Selenium overflow 측정, screenshot, vision quality 평가 | `measurement`, `quality_score` |
| Stage 5 | 코드 | `src/pipeline.py` | `final.html`, `final_context.json`, popup/detail html 저장 | run 산출물 |
## 현재 메인 실행 경로
### Type B 메인 경로
지금 실전에서 가장 중요한 경로는 아래입니다.
1. Stage 0에서 MDX를 정규화
2. Stage 1A/1B에서 Kei가 의미와 topic 추출
3. Phase Y에서 코드가 `normalized.sections`를 읽고 page_structure를 다시 생성
4. Stage 1.7에서 block 선택
5. Stage 1.8에서 fit/overflow 검증
6. Stage 2에서 `assemble_slide_html_final()`로 최종 HTML 조립
7. Stage 4/5에서 측정과 산출물 저장
Type B의 핵심 파일은 아래입니다.
- `src/pipeline.py`
- `src/section_parser.py`
- `src/block_reference.py`
- `src/block_assembler.py`
- `src/space_allocator.py`
- `templates/catalog.yaml`
### Type A 경로
Type A는 현재도 살아 있지만, Type B만큼 단단하게 닫힌 상태는 아닙니다.
- AI 생성 비중이 더 큼
- `src/renderer.py` 의존도가 더 큼
- sidebar/reference 구조를 포함하는 쪽에서 의미가 큼
## schema -> recipe -> block
최근 구조화에서 가장 중요한 변화 중 하나는 `schema -> recipe -> block` 레이어입니다.
### schema
콘텐츠의 의미 구조입니다.
예:
- `parallel_cluster`
- `parallel_cluster_plus_visual`
- `compare_asymmetric_paired`
- `sequence_plus_visual`
- `single_block`
### recipe
block 이름이 아니라 표현 규칙입니다.
예:
- `single_block`
- `two_col_text_visual`
- `stacked_summary_detail`
recipe는 이런 계약을 가질 수 있습니다.
- left/right kind
- top/bottom kind
- ratio
- vertical align
### block
실제 구현 템플릿 후보입니다.
예:
- `prerequisites-3col`
- `process-product-2col`
- `compare-detail-gradient`
- `card-icon-desc`
즉, "무슨 문서냐"가 아니라 "무슨 구조냐"를 먼저 읽고, 그 구조에 맞는 표현 규칙을 정한 뒤, 마지막에 구현 블록을 고르는 방향으로 가고 있습니다.
## popup / detail 계약
popup은 지금 다음 철학으로 정리되는 중입니다.
- 메인 슬라이드에는 존 크기에 맞는 요약만 남긴다
- 큰 표, 시각 컴포넌트, 과다한 bullet은 상세 popup으로 분리한다
- 메인에서는 `자세히보기` 링크를 제공한다
현재 popup 관련 핵심은 아래입니다.
- `PopupItem` 모델이 도입되어 popup 데이터를 명시적으로 다룸
- `popup_id`, `popup_file` 생애주기를 분리해 관리 중
- 최종 목표는 popup 판단을 휴리스틱이 아니라 명시적 contract로 만드는 것
다만 아직 일부 구간엔 추측 로직과 이중 관리가 남아 있어, 이 부분은 계속 정리 중입니다.
## run 산출물 구조
각 실행은 `data/runs/{run_id}/` 아래에 저장됩니다. 각 실행은 `data/runs/{run_id}/` 아래에 저장됩니다.
주요 파일은 다음과 같습니다. | 파일 | 내용 |
|------|------|
| `final.html` | 최종 슬라이드 |
| `final_context.json` | 파이프라인 결과 데이터 |
| `steps/*.html` | 단계별 디버그 보드 |
| `첨부*_상세*.html` | popup 상세 내용 |
- `final.html` ---
- `final_context.json`
- `steps/*.html`
- popup/detail html
### `final.html` ## 무슨 문제가 있는가
- 최종 렌더 결과 | 문제 | 설명 |
- 실제 눈으로 보는 산출물 |------|------|
| **블록마다 스타일이 제각각** | 각 블록 HTML 안에 font-size, color, padding이 직접 박혀있어서, 같은 슬라이드 안에서 블록이 섞이면 위계가 안 맞음 |
| **slide-base에 구조+스타일 혼재** | 프레임 HTML 안에 전체 CSS가 인라인으로 들어있어서 유지보수가 어려움 |
| **블록이 완성 HTML** | 블록이 구조+스타일+값을 모두 포함하고 있어서, 재사용/조합이 안 됨 |
| **매칭 안 되면 고정 렌더** | BEPs에 맞는 블록이 없을 때 코드가 1회 고정 렌더하고 끝. 반복 조정 없음 |
| **빈 공간/overflow 방치** | 렌더 후 빈 공간이 있어도 조정 안 하고, overflow만 감지 |
| **검증이 약함** | overflow 측정만 하고, 정렬/위계/가독성 같은 시각 품질은 미검증 |
### `final_context.json` ---
- 각 단계 결과를 최종 context 형태로 저장 ## 어떻게 개선하려 하는가
- block 선택, page_structure, measurement, quality_score 등을 확인할 때 가장 중요
### `steps/*.html` 핵심은 3가지입니다.
- 단계별 디버그/설명용 보드 1. **블록을 구조 부품화** — 완성 HTML이 아니라, 구조만 담고 스타일은 토큰으로 분리
- 현재는 검토용으로 유용하지만, 일부 인코딩/설명 품질은 더 다듬을 필요가 있습니다 2. **스타일을 토큰으로 통일** — 블록마다 제각각인 폰트/색/여백을 공통 기준으로
3. **2경로 파이프라인** — 매칭되면 바로 쓰고(direct-fit), 안 되면 재구성(recipe)
## 자주 봐야 하는 파일 ### AS-IS → TO-BE
### 파이프라인 핵심 ```
AS-IS:
AI가 먼저 꼭지를 추출하고
→ 매칭 블록이 있으면 삽입, 없으면 코드가 1회 고정 렌더
→ 빈 공간이 있어도 그냥 둠
→ 블록마다 font-size, color가 직접 박혀있어서 섞이면 위계 안 맞음
- [src/pipeline.py](src/pipeline.py) TO-BE:
- [src/pipeline_context.py](src/pipeline_context.py) 중목차 기준으로 zone을 먼저 나누고
- [src/section_parser.py](src/section_parser.py) → TF-IDF로 BEPs 매칭 시도
- [src/block_reference.py](src/block_reference.py) → 매칭되면 블록 삽입 + 크기 조절 (direct-fit)
- [src/block_assembler.py](src/block_assembler.py) → 안 되면 AI가 꼭지 정리 + 유사 디자인으로 redesign + 반복 조정 (recipe)
- [src/space_allocator.py](src/space_allocator.py) → 빈 공간/overflow를 자동 재분배
→ 모든 블록이 토큰 기반이라 스타일 통일
```
### 템플릿 / 카탈로그 ---
- [templates/catalog.yaml](templates/catalog.yaml) ## 현재 상태
- [templates/blocks/new/prerequisites-3col.html](templates/blocks/new/prerequisites-3col.html)
- [templates/blocks/redesign/process-product-2col.html](templates/blocks/redesign/process-product-2col.html)
- [templates/blocks/cards/compare-detail-gradient.html](templates/blocks/cards/compare-detail-gradient.html)
- [templates/blocks/slide-base.html](templates/blocks/slide-base.html)
주의: 현재 삭제/legacy 정리 여부를 별도로 확인해야 하는 경로입니다.
### 검증 / 측정 | 대상 | 슬라이드 변환 | 파이프라인 자동화 |
|------|-------------|----------------|
| MDX 03 (3개 목표 + 비교표) | ✅ 완료 | ✅ 파이프라인 연결 |
| MDX 02 (목표 + 프로세스 + 상세표) | ✅ 완료 | △ 파이프라인 연결, 시각 품질 개선 중 |
| MDX 01 (Type A, sidebar 구조) | ✅ 완료 (개별) | 미연결 |
| 토큰 기반 CSS 체계 | - | ✅ 정의 완료, slide-base 적용 |
| Figma 블록 추출 | - | 진행 중 (`figma_to_html_agent/blocks/`) |
- [src/slide_measurer.py](src/slide_measurer.py) ---
- [src/validators.py](src/validators.py)
### 역사 / 계획 문서 ## 다음 단계 방향
- [PIPELINE.md](PIPELINE.md) | 순서 | 단계 | 내용 |
- [docs/history/PHASE-Y-PLAN.md](docs/history/PHASE-Y-PLAN.md) |------|------|------|
- [ARCHITECTURE_OVERVIEW.md](ARCHITECTURE_OVERVIEW.md) | 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)
### 잘 닫혀가는 것 ---
- Type B 메인 경로 ## 참고 문서
- `normalized.sections` 기반 구조 해석
- schema / recipe 기반 block selection
- `prerequisites-3col`, `process-product-2col` 같은 redesign 블록 자산화
- popup/detail 2단 표현 계약의 초안 연결
### 아직 정리 중인 것
- Type A 전체 안정화
- popup을 완전한 source of truth로 정리
- `tag_match` 와 `schema_match`의 완전한 동등 점수 비교
- step 보드 인코딩/설명 품질
- legacy 경로와 문서의 정리
## 읽는 방법 추천
프로세스를 빠르게 파악하려면 아래 순서가 좋습니다.
1. 이 `README.md`
2. [src/pipeline.py](src/pipeline.py)
3. [src/section_parser.py](src/section_parser.py)
4. [src/block_assembler.py](src/block_assembler.py)
5. 최근 run의 `final_context.json`
히스토리까지 보려면 아래 문서를 이어서 보면 좋습니다.
- [PIPELINE.md](PIPELINE.md)
- [docs/history/PHASE-Y-PLAN.md](docs/history/PHASE-Y-PLAN.md)
| 문서 | 내용 |
|------|------|
| [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) | 블록 작성 규칙 (에이전트 간 계약서) |
Binary file not shown.
+162
View File
@@ -0,0 +1,162 @@
# 블록 작성 규칙
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
@@ -0,0 +1,183 @@
# 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
@@ -0,0 +1,157 @@
# 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
@@ -0,0 +1,285 @@
# 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) 의 자연 산출물
@@ -0,0 +1,849 @@
# 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 은 사용자가 잠근 후 진행.
@@ -0,0 +1,220 @@
# 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.
@@ -0,0 +1,229 @@
# 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#부록--제외--특수-항목) 참조.
@@ -0,0 +1,206 @@
# 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 구조.
@@ -0,0 +1,472 @@
# 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 자체를 다시 봄
@@ -0,0 +1,158 @@
# 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
@@ -0,0 +1,69 @@
# 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 정의 (공통 + 의미색)
+29
View File
@@ -0,0 +1,29 @@
# Legacy / 임시 파일
block_index.faiss
block_metadata.json
figma_beps_full.json
figma_center_elements.json
figma_center_full.json
figma_frames.json
figma-analysis/
figma-assets/
figma-screenshots/
figma_ref/
previews/
FIGMA-COMPONENT-EXTRACTION-PLAN.md
FIGMA-CONVERSION-REVIEW.md
FIGMA-DESIGN-LANGUAGE.md
FIGMA-EXTRACTION.md
INSIGHT-GRADIENT.md
PHASE-FIGMA-BLOCKS.md
PLAN.md
RESEARCH.md
# Selenium 렌더 결과 / 비교 스크린샷
block-tests/_renders/
block-tests/*.png
# Python
__pycache__/
*.pyc
scripts/__pycache__/
+23 -15
View File
@@ -19,7 +19,7 @@ Figma 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고,
6. **AI 역할 분담** — AI는 분류(고르기)만, 구성(만들기)은 코드. LLM은 px을 못 본다 6. **AI 역할 분담** — AI는 분류(고르기)만, 구성(만들기)은 코드. LLM은 px을 못 본다
7. **컨텍스트 관리는 compact로** — 한 세션에서 여러 프레임을 연속 작업할 수 있다. 컨텍스트가 무거워지면 `/compact` 로 핵심만 요약하고 계속 진행. 이유: 핵심 결정/구조/규칙은 모두 파일(CLAUDE.md, PROCESS.md, RULES.md, blocks_index.md, 산출물)에 박혀있어 compact 후에도 보존됨. 손실되는 건 시행착오/디버깅 과정 뿐이며, 이건 잃어도 OK. 매 프레임마다 새 세션을 강제하면 누적 학습이 silo되어 R13 같은 sub-pattern 발견의 즉시 적용이 불가능해짐. 7. **컨텍스트 관리는 compact로** — 한 세션에서 여러 프레임을 연속 작업할 수 있다. 컨텍스트가 무거워지면 `/compact` 로 핵심만 요약하고 계속 진행. 이유: 핵심 결정/구조/규칙은 모두 파일(CLAUDE.md, PROCESS.md, RULES.md, blocks_index.md, 산출물)에 박혀있어 compact 후에도 보존됨. 손실되는 건 시행착오/디버깅 과정 뿐이며, 이건 잃어도 OK. 매 프레임마다 새 세션을 강제하면 누적 학습이 silo되어 R13 같은 sub-pattern 발견의 즉시 적용이 불가능해짐.
8. **순수 CSS 우선, SVG는 곡선/필터에만** — 동적 재구성 위해 가능한 한 HTML div + linear-gradient 사용 8. **순수 CSS 우선, SVG는 곡선/필터에만** — 동적 재구성 위해 가능한 한 HTML div + linear-gradient 사용
9. **프로모션 게이트는 사용자 전용** — 에이전트는 절대 `design_agent/templates/` 에 직접 쓰지 않는다. 모든 작업은 `figma_to_html_agent/` 안에서 끝나며, 본체 라이브러리 이전은 사용자 수동 검수 후 사용자 본인이 수행한다. 9. **프로모션은 사용자 승인 후 진행** — 1:1 HTML 검증 완료 후, 사용자 승인을 받아 `templates/blocks/new/`에 블록을 생성하고 `templates/blocks/svg/`에 이미지를 정리한다. catalog.yaml 등록과 blocks_index.md 업데이트까지 에이전트가 수행한다. 단, 사용자 승인 없이 프로모션하지 않는다.
10. **시맨틱 우선, Figma 평면 레이어 그대로 옮기지 말 것** — Figma의 평면 레이어 구조는 디자인 도구의 한계일 뿐, 의미 구조가 아니다. 마커+텍스트는 list item, 카드 묶음은 column unit, 등 시맨틱하게 재그룹핑하여 작성한다. RULES.md R13 (Custom-marker bullet list) 참조. 새로 발견되는 sub-pattern은 [blocks_index.md](blocks_index.md) "디자인 인사이트" 섹션에 누적한다. 10. **시맨틱 우선, Figma 평면 레이어 그대로 옮기지 말 것** — Figma의 평면 레이어 구조는 디자인 도구의 한계일 뿐, 의미 구조가 아니다. 마커+텍스트는 list item, 카드 묶음은 column unit, 등 시맨틱하게 재그룹핑하여 작성한다. RULES.md R13 (Custom-marker bullet list) 참조. 새로 발견되는 sub-pattern은 [blocks_index.md](blocks_index.md) "디자인 인사이트" 섹션에 누적한다.
11. **모든 슬롯은 기본 optional** — 1:1 단계에서 모든 슬롯이 채워져 있다고 해서 "이 블록은 필수" 로 해석하지 않는다. 같은 블록이 사진 없는/짧은/긴 mdx에 모두 매칭되어야 한다는 가정으로 설계한다. 11. **모든 슬롯은 기본 optional** — 1:1 단계에서 모든 슬롯이 채워져 있다고 해서 "이 블록은 필수" 로 해석하지 않는다. 같은 블록이 사진 없는/짧은/긴 mdx에 모두 매칭되어야 한다는 가정으로 설계한다.
@@ -60,12 +60,18 @@ Figma 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고,
## 입출력 ## 입출력
**입력:** Figma 파일 + 노드 ID (또는 현재 선택 노드) **입력:** Figma 파일 + 노드 ID (또는 현재 선택 노드)
**출력:** **출력 (변환):**
- `block-tests/{slug}.html` — 변환 결과 - `block-tests/{slug}.html` — 1:1 변환 결과
- `block-tests/{slug}_flat.md` — 플래튼/이상 탐지/변형 축 메모 - `block-tests/{slug}_flat.md` — 플래튼/이상 탐지/변형 축 메모
- `assets/shared/...` — 공유 자산 캐시 - `assets/shared/...` — 공유 자산 캐시
- `blocks_index.md` 한 줄 추가 - `blocks_index.md` 한 줄 추가
**출력 (프로모션, 사용자 승인 후):**
- `templates/blocks/new/{pattern}.html` — AI가 재디자인 가능한 블록 (CSS + Jinja2)
- `templates/blocks/svg/{name}.png/svg` — 블록 공용 이미지
- `templates/catalog.yaml` — 블록 등록 (content_structure, when, not_for)
- `blocks_index.md` 상태 → promoted
## 폴더 구조 ## 폴더 구조
``` ```
@@ -73,9 +79,9 @@ figma_to_html_agent/ ← 에이전트 작업 영역 (staging)
├── CLAUDE.md ← 이 파일 (에이전트 명세) ├── CLAUDE.md ← 이 파일 (에이전트 명세)
├── PROCESS.md ← 10단계 운영 절차 (변환 핸드북) ├── PROCESS.md ← 10단계 운영 절차 (변환 핸드북)
├── MATH.md ← 수학 공식 레퍼런스 ├── MATH.md ← 수학 공식 레퍼런스
├── RULES.md ← CSS 보정 규칙 (R1~R12) ├── RULES.md ← CSS 보정 규칙 (R1~R16)
├── PROCESS-CONTROL.md ← "찍어맞추기 금지" 규칙 ├── PROCESS-CONTROL.md ← "찍어맞추기 금지" 규칙
├── PLAN.md ← 현재 진행 현황 ├── README.md ← 사용법 가이드
├── blocks_index.md ← 변환 완료 도서관 ├── blocks_index.md ← 변환 완료 도서관
│ │
├── scripts/ ├── scripts/
@@ -90,22 +96,22 @@ figma_to_html_agent/ ← 에이전트 작업 영역 (staging)
│ ├── shared/ ← 해시 기반 자산 캐시 (재사용) │ ├── shared/ ← 해시 기반 자산 캐시 (재사용)
│ └── frame_{id}/ ← 프레임 전용 자산 (legacy) │ └── frame_{id}/ ← 프레임 전용 자산 (legacy)
│ │
└── templates_staging/ ← Stage 2: Jinja2 추상화 └── templates_staging/ ← Stage 2: Jinja2 추상화 (legacy, 현재 미사용)
├── {pattern_id}.html.j2
└── {pattern_id}.meta.yaml ← when/slots/min_size_px 초안
──────────────────────────────────────────────────────── ────────────────────────────────────────────────────────
🚧 프로모션 게이트 (사용자 수동 작업) 🚧 프로모션 (사용자 승인 후 에이전트가 실행)
──────────────────────────────────────────────────────── ────────────────────────────────────────────────────────
design_agent/ ← 본체 라이브러리 (에이전트 접근 금지) design_agent/templates/
└── templates/ ├── blocks/
├── blocks/{category}/ │ ├── new/ ← Figma 추출 블록 (에이전트가 작성)
│ └── {pattern_id}.html.j2 ← 사용자가 staging에서 이전 │ ├── svg/ ← 블록 공용 이미지 (에이전트가 정리)
└── catalog.yaml ← 사용자가 when/slots 등록 │ ├── slide-base.html ← 고정 슬라이드 배경
│ └── cards/, emphasis/, ... ← 기존 블록
└── catalog.yaml ← 블록 등록 (에이전트가 업데이트)
``` ```
**중요:** 에이전트는 위 구분선 아래(`design_agent/templates/`)를 **절대 수정하지 않는다.** 그 영역은 사용자가 staging 결과물을 검수한 뒤 본인이 직접 프로모션한다. **중요:** 프로모션(블록 생성, 이미지 정리, catalog 등록)은 사용자가 1:1 HTML을 검증하고 승인한 후에만 진행한다. 사용자 승인 없이 templates/ 를 수정하지 않는다.
## 금지 사항 ## 금지 사항
@@ -116,6 +122,8 @@ design_agent/ ← 본체 라이브러리 (에이전트 접
- 블록 배경을 검정으로 표시 (미리보기는 항상 흰색 배경) - 블록 배경을 검정으로 표시 (미리보기는 항상 흰색 배경)
- **이미지 해석으로 gradient 방향 판단** (멀티모달 금지, 데이터로만 판단 — PROCESS-CONTROL.md 참조) - **이미지 해석으로 gradient 방향 판단** (멀티모달 금지, 데이터로만 판단 — PROCESS-CONTROL.md 참조)
- **한 번에 여러 값 동시 수정** (gradient 각도와 border-radius 동시 변경 금지) - **한 번에 여러 값 동시 수정** (gradient 각도와 border-radius 동시 변경 금지)
- **장식 요소를 이미지 슬롯(img src)으로 넣기** — gradient bar, ribbon, pill, badge, 오버레이 등은 CSS로 구현. AI가 색상/크기를 조정할 수 없는 이미지 의존 블록은 쓸 수 없다
- **사용자 승인 없이 templates/ 수정** — 프로모션은 사용자 승인 후에만
- **여러 프레임을 한 세션에 변환** (1세션 1프레임 원칙) - **여러 프레임을 한 세션에 변환** (1세션 1프레임 원칙)
- **plus-darker 블렌드 사용** (Safari 전용 → multiply로 교체, RULES.md R10) - **plus-darker 블렌드 사용** (Safari 전용 → multiply로 교체, RULES.md R10)
- **Figma 인벤토리/지문/군집 같은 사전 분류** (work-creating-work, 패턴은 bottom-up으로 발견) - **Figma 인벤토리/지문/군집 같은 사전 분류** (work-creating-work, 패턴은 bottom-up으로 발견)
+165
View File
@@ -0,0 +1,165 @@
# Figma → HTML Agent
Figma 디자인 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고, design_agent의 **블록 라이브러리**로 축적하는 독립 에이전트.
---
## 이 에이전트는 뭘 하는가?
1. Figma 파일에서 프레임을 선택하면
2. MCP(Figma Dev Mode)로 구조/스타일/스크린샷을 가져와서
3. 수학적 계산(scale, gradient 변환, shadow 등)으로 1:1 HTML/CSS를 만들고
4. AI가 재디자인할 수 있는 블록(순수 CSS + Jinja2 슬롯)으로 변환하여
5. design_agent의 블록 라이브러리(`templates/blocks/`)에 편입한다
**핵심:** AI redesigner가 색상/크기/구조를 조정할 수 있도록, 장식 요소는 **CSS로 구현**하고 이미지는 crop/곡선/일러스트/사진만 남긴다.
---
## 폴더 구조
```
figma_to_html_agent/
├── README.md ← 이 파일 (사용법)
├── CLAUDE.md ← 에이전트 명세 (11개 원칙)
├── PROCESS.md ← 10단계 변환 절차 핸드북
├── MATH.md ← 수학 공식 (§1~9)
├── RULES.md ← CSS 보정 규칙 (R1~R16)
├── PROCESS-CONTROL.md ← 작업 규율 (찍어맞추기 금지 등)
├── blocks_index.md ← 변환 완료 도서관 + 디자인 인사이트
│
├── scripts/
│ └── gradient_math.py ← SVG → CSS gradient 수학 변환 도구
│
├── block-tests/ ← 1:1 HTML 변환물 (원본 검증용)
│ ├── {slug}.html ← Figma 원본과 100% 동일한 정적 HTML
│ ├── {slug}_flat.md ← 플래튼/이상탐지/변형축 메모
│ └── assets/ ← 이미지 에셋
│ ├── shared/ ← 해시 기반 공유 캐시
│ └── frame_{id}/ ← 프레임 전용 (legacy)
│
└── templates_staging/ ← Jinja2 템플릿 초안 (legacy, 현재 미사용)
```
### 산출물 최종 위치 (design_agent 본체)
```
design_agent/
└── templates/
└── blocks/
├── new/ ← Figma 추출 블록 (8개)
├── svg/ ← 블록 공용 이미지 (33개)
├── slide-base.html ← 고정 슬라이드 배경 (16:9)
└── cards/, emphasis/, headers/, media/, visuals/, BEPs/, redesign/
```
---
## 사용법
### 사전 준비
1. **Figma Desktop** 앱에서 대상 파일을 열고 **Dev Mode** 활성화
2. `.mcp.json`에 Figma Desktop SSE 서버 등록 확인:
```json
{ "url": "http://127.0.0.1:3845/sse" }
```
3. Claude Code 세션 시작
### 변환 실행 (10단계)
```
STEP 0 에이전트가 blocks_index.md 읽기 → 기존 패턴 확인
STEP 1 get_metadata — 구조 + bbox (XML)
STEP 2 get_design_context — gradient/filter/font (React+Tailwind)
STEP 3 get_screenshot — Figma 원본 PNG (검증용)
STEP 4 자산 다운로드 — block-tests/assets/shared/ 캐시
STEP 5 flat.md 작성 — bottom-up 플래튼 + 이상탐지 + 변형축
STEP 6 gradient 수학 변환 — scripts/gradient_math.py
STEP 7 HTML 작성 — 순수 CSS 우선, transform: scale() 균일 축소
STEP 8 검증 — Figma 스크린샷과 비교
STEP 9 저장 — block-tests/{slug}.html + flat.md
STEP 10 인덱스 업데이트 — blocks_index.md 1줄 추가
```
**상세:** [PROCESS.md](PROCESS.md) 참조
### 블록 프로모션 (block-tests → templates/blocks/)
1:1 HTML이 검증되면:
1. **CSS 전환** — SVG/PNG 장식 요소를 CSS gradient/border-radius/shadow로 변환
- CSS 전환 대상: gradient bar, ribbon, pill, badge, 오버레이 등
- 이미지 유지 대상: crop/프레임 배치(R16), 곡선 아크, 아이콘, 일러스트, 실사 사진
2. **블록 작성** — `templates/blocks/new/{pattern}.html`
- flex layout, width: 100%, Jinja2 슬롯
- 수학적 계산 주석 (Figma px → scale → CSS 값)
- 이미지는 `svg/` 폴더에서 참조 (`{{ slot | default('svg/파일명') }}`)
3. **이미지 정리** — `templates/blocks/svg/`에 의미 있는 이름으로 저장
4. **catalog.yaml 등록** — content_structure, when, not_for 작성
5. **blocks_index.md 상태** — `staged` → `promoted` 업데이트
---
## 핵심 원칙 요약
| # | 원칙 | 설명 |
|---|------|------|
| 1 | 수학적 계산만 | Figma 좌표 → scale → CSS. 시행착오 px 조정 금지 |
| 2 | Bottom-up | leaf 노드부터 → 2개씩 묶기 → 계층 쌓기 |
| 3 | 순수 CSS 우선 | AI가 색상/크기 조정할 수 있게. SVG는 곡선/필터만 |
| 4 | 이미지 해석 금지 | gradient 방향은 데이터로. "보니까 ~인 것 같다" 금지 |
| 5 | 하드코딩 금지 | 결과물을 고치지 말고 프로세스를 고친다 |
| 6 | 전면 재작성 금지 | 80점에서 2개 고칠 때 구조를 갈아엎지 말 것 |
| 7 | 모든 슬롯 optional | 블록이 다양한 MDX에 매칭될 수 있게 |
**상세:** [CLAUDE.md](CLAUDE.md), [PROCESS-CONTROL.md](PROCESS-CONTROL.md), [RULES.md](RULES.md)
---
## 도구
| 도구 | 용도 |
|------|------|
| Figma MCP `get_metadata` | 프레임 구조 + 절대 좌표 (XML) |
| Figma MCP `get_design_context` | gradient/filter/font (React+Tailwind 코드) |
| Figma MCP `get_screenshot` | Figma 원본 PNG |
| `scripts/gradient_math.py` | SVG linearGradient → CSS linear-gradient 수학 변환 |
| Pillow | 이미지 픽셀 분석 (gradient 색상 추출, 이미지 해석 아님) |
---
## 수학 공식 요약
| 공식 | 용도 | 상세 |
|------|------|------|
| `S = 1280 / W_figma` | 균일 축소 scale | MATH.md §1 |
| `CSS_angle = 90 - Figma_angle` | gradient 각도 변환 | MATH.md §2 |
| SVG `<linearGradient>` → CSS | gradient stop % 변환 | gradient_math.py |
| `plus-darker → multiply` | blend mode 호환 | RULES.md R10 |
| bbox 비율 ≠ 텍스트 비율 → 회전 | rotation 감지 | RULES.md R11 |
---
## 현재 상태 (2026-04-13)
### 프로모션 완료 블록 (8개)
| 패턴 | 원본 프레임 | 블록 위치 |
|------|-----------|---------|
| statement-pill-highlight | 1171281207 | blocks/new/ |
| stacked-arrow-list | 1171281180 | blocks/new/ |
| split-panel-numbered | 1171281202 | blocks/new/ |
| issues-paired-rows | 1171281194 | blocks/new/ |
| compare-vs-rows | 1171281195 | blocks/new/ |
| quadrant-2x2-issues | 1171281193 | blocks/new/ |
| cards-3col-persona | 1171281191 | blocks/new/ |
| cycle-3way-intersect | 1171281211 | blocks/new/ |
### 공용 이미지 (templates/blocks/svg/, 33개)
화살표, 아크, 뱃지, 아이콘, 배경 텍스처, 사진 등. 블록에서 `svg/파일명`으로 참조.
### 고정 슬라이드 배경
`templates/blocks/slide-base.html` — 16:9 (1280×720), 상단 제목 + 구분선 + 본문 영역 + 하단 결론 pill.
+4 -5
View File
@@ -111,11 +111,10 @@ Sub-patterns: **bullet-list-with-marker (R13)** — 각 컬럼의 bullet_items
--- ---
## templates_staging 대기열 ## 프로모션 완료
> 2번째 등장한 패턴이 여기에 들어옴. 사용자 검수 후 design_agent 본체로 프로모션. > 8개 블록 모두 `templates/blocks/new/`로 프로모션 완료 (2026-04-13).
> 이미지 33개 `templates/blocks/svg/`로 통합. catalog.yaml 등록 완료.
(아직 없음)
--- ---
@@ -252,4 +251,4 @@ Sub-patterns: **bullet-list-with-marker (R13)** — 각 컬럼의 bullet_items
| slug | frame | pattern | 위반 사항 | 처리 | | slug | frame | pattern | 위반 사항 | 처리 |
|------|-------|---------|--------|------| |------|-------|---------|--------|------|
| [prerequisites-3col](block-tests/prerequisites-3col.html) | 45:15 / Frame 1171281190 | 3-column-comparison (legacy) | R8 위반 (수동 ×0.60290 곱셈, transform: scale 미사용), R9 미적용 (gradient text만, div 패턴 없음) | 재변환 권장. 동일 패턴 등장 시 신규 변환물 작성 후 이 행은 archive 처리. | | [prerequisites-3col](block-tests/prerequisites-3col.html) | 45:15 / Frame 1171281190 | prerequisites-3col | **promoted** → [block](../../templates/blocks/new/prerequisites-3col.html) + [catalog](../../templates/catalog.yaml) | 2026-04-13. legacy HTML 기반 블록화. 순수 CSS (이미지 없음). |
+274
View File
@@ -0,0 +1,274 @@
"""MDX 02 최종 4안 — 처음부터 독립, CSS font-size override 금지.
글씨 크기: 블록 CSS 원래 값 그대로. 절대 변경 금지.
zone 크기: 글씨 크기에 맞게 조정 (글씨가 기준, 레이아웃이 맞춤).
안 1: 내 판단 + new/ only
상단: cards-3col-persona 하단좌: stacked-arrow-list 하단우: cards-3col-persona(3주체)
안 2: 내 판단 + new/ + 기존
상단: card-compare-3col 하단좌: card-numbered 하단우: table-simple-striped
안 3: Kei 판단 + new/ only (Kei 응답: 상단1 하단좌4 하단우2 결론6)
상단: cards-3col-persona 하단좌: split-panel-numbered 하단우: compare-vs-rows
안 4: Kei 판단 + new/ + 기존 (Kei 응답: 상단A 하단좌7 하단우H 결론6)
상단: card-compare-3col 하단좌: issues-paired-rows 하단우: compare-3col-badge
"""
import base64, re
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
BLOCKS = Path("templates/blocks")
SVG = BLOCKS / "svg"
OUT = Path("data/runs/mdx02_4plans_final")
OUT.mkdir(parents=True, exist_ok=True)
env = Environment(loader=FileSystemLoader(str(BLOCKS)), autoescape=False)
def b64(f):
p = SVG / f
if not p.exists(): return ""
ext = "svg+xml" if f.endswith(".svg") else "png"
return f"data:image/{ext};base64," + base64.b64encode(p.read_bytes()).decode()
def clean(h): return re.sub(r'<!--.*?-->', '', h, flags=re.DOTALL).strip()
img_uri = open("data/runs/mdx02_v4_test/slide_image.txt").read().strip()
title = "DX의 시행 목표 및 기대효과"
ft = '고품질의 성과품, 비용 절감, 시간 단축, 의사소통에 도움이 <em>안 되면 DX가 아니다</em>'
# slide-body: 1200×590px. 블록 글씨 크기에 맞춰 zone 크기 계산.
# 컨테이너 계산 → 글자수 예산 → overflow 방지
IW = 500 # 이미지
GAP = 10
# zone 높이: 블록 필요 높이 기반 계산
# new/ 블록(15~22px): 상단 244px, 하단 336px
# 기존 블록(13~15px): 상단 204px, 하단 310px
TH_NEW = 244
BH_NEW = 336
TH_OLD = 204
BH_OLD = 310
# 원문 텍스트
TOP = [
("안전과 품질", [
"시설물의 요구 성능을 설계-시공-운영 전 과정에서 <strong>디지털로 검증</strong>하여 <strong>안전성 확보</strong>",
"Copy &amp; Paste로 하향 평준화된 성과물의 <strong>하자 최소화</strong>로 <strong>고품질 성과물 제공</strong>"]),
("생산성 향상", [
"Analogue 기반 업무를 Digital 기반 프로세스로 전환하여 <strong>업무 속도·정확성·일관성 향상</strong>",
"건설 비용 및 유지관리비 절감, 건설 기간 단축, 인력투입 최소화를 통해 <strong>부가가치 제고</strong>"]),
("소통과 신뢰", [
"성과품과 Solution을 통한 협업 강화로 <strong>의사소통 효율 및 운영·유지관리</strong>의 <strong>편리성 증진</strong>",
"3D 모델 및 데이터 기반 검증을 통한 <strong>오류 최소화 및 Claim 예방</strong>으로 <strong>신뢰성 확보</strong>"]),
]
BL = [
("생산 방식", "수작업 의존의 반복 업무에서 벗어나, <strong>SW를 활용한 체계화된 방식</strong>으로 전환"),
("인지·검토", "2D 도면 해석 중심에서 <strong>3D 모델 기반의 직관적 인지·검토 체계</strong>로 전환"),
("협업 구조", "개별 문서 중심 협업에서 <strong>데이터 통합 기반의 정보 공유·관리 협업 환경</strong>으로 전환"),
("검증·대응", "사후 대응 중심의 문제 처리에서 <strong>사전 검증 중심의 예방적 업무 방식</strong>으로 전환"),
]
BR = {
"headers": ["구분", "발주자", "시공자", "설계자"],
"rows": [
["필요 역량", "실행 의지와 합리적 판단", "기술 투자와 운영 역량", "SW개발 투자 역량"],
["SW 기반 체계화", "행정 자동화로 생산성 향상", "체계적 공정관리로 신뢰성 확보", "설계 프로세스 체계화"],
["3D 기반 전환", "직관적 시각화로 품질 향상", "안전성 제고 및 관리 편의", "3D 검증으로 설계 오류 방지"],
["데이터 통합 협업", "원활한 의사소통으로 오류 감소", "협업 효율 및 서류 감소", "설계 신뢰도로 상호신뢰 증진"],
["사전 검증 관리", "민원·소송 등 사전 예방", "설계·시공 오류 예방", "설계 책임 리스크 감소"],
],
}
def wrap_slide(body_html, fname):
sb = clean((BLOCKS / "slide-base.html").read_text(encoding="utf-8"))
r = sb.replace('{{ title|default("슬라이드") }}', title)
r = r.replace('{{ title|default("슬라이드 제목") }}', title)
r = r.replace('{% block body %}{% endblock %}', body_html)
pill = b64("pill_scroll.png")
r = r.replace('{% if footer_text %}', '').replace('{% if footer_pill_bg %}', '')
r = r.replace('{{ footer_pill_bg }}', pill).replace('{% else %}', '')
r = r.replace('<div class="slide-footer-bg slide-footer--css"></div>', '')
li = r.rfind('{% endif %}')
if li > 0: r = r[:li] + r[li + len('{% endif %}'):]
r = r.replace('{% endif %}', '').replace('{{ footer_text|safe }}', ft)
r = r.replace('src="svg/bg_slide_texture.png"', f'src="{b64("bg_slide_texture.png")}"')
r = r.replace('src="svg/line_divider.svg"', f'src="{b64("line_divider.svg")}"')
out = OUT / fname
out.write_text(r, encoding="utf-8")
return out
def make_body(top_html, bl_html, br_html, th, bh):
"""slide-body 안에 배치. zone 높이는 블록 글씨 크기 기반 계산값."""
return f"""
<div style="height:{th}px;margin-bottom:{GAP}px;overflow:hidden;">
<div style="font-weight:700;font-size:13px;color:#1a365d;margin-bottom:4px;">DX의 궁극적 목표</div>
<div style="display:flex;gap:8px;height:{th-20}px;">
<div style="flex:1;overflow:hidden;">{top_html}</div>
<div style="width:{IW}px;flex-shrink:0;display:flex;flex-direction:column;justify-content:center;">
<img src="{img_uri}" style="width:100%;border-radius:8px;object-fit:contain;">
<div style="font-size:10px;color:#94a3b8;text-align:center;margin-top:2px;">DX의 궁극적 목표</div>
</div>
</div>
</div>
<div style="height:{bh}px;overflow:hidden;">
<div style="font-weight:700;font-size:13px;color:#1a365d;padding-bottom:4px;border-bottom:1px solid #e2e8f0;margin-bottom:4px;">DX 기반 Process 혁신에 따른 주체별 기대효과</div>
<div style="display:flex;gap:12px;height:{bh-28}px;">
<div style="flex:1;overflow:hidden;">
<div style="font-weight:700;font-size:12px;color:#1a365d;margin-bottom:4px;">업무 수행 과정(Process)의 변화</div>
{bl_html}
</div>
<div style="width:1px;background:#cbd5e1;flex-shrink:0;"></div>
<div style="flex:1;overflow:hidden;">
<div style="font-weight:700;font-size:12px;color:#1a365d;margin-bottom:4px;">DX 시행 주체별 기대효과</div>
{br_html}
</div>
</div>
</div>"""
# Jinja2 .items() 충돌 방지용 클래스
class _Cat:
def __init__(self, name, color, items): self.name=name; self.color=color; self.items=items
# ══════════════════════════════════════════
# 안 1: 내 판단 + new/ only — 글씨 크기 그대로
# ══════════════════════════════════════════
print("=== 안 1 ===")
t1 = clean(env.get_template("new/cards-3col-persona.html").render(personas=[
{"overlay_color":c,"label_line1":t,"label_color":"#1a365d","bullets":[{"text":b} for b in bs]}
for (t,bs),c in zip(TOP,["#dce8d4","#d4dce8","#e8dcd4"])
]))
# 뱃지/사진만 숨김 — 글씨 크기는 원래 값(15px body, 20px label)
t1 += "<style>.c3p-badge{display:none}.c3p-photo{display:none}.c3p-badge-label{position:relative;z-index:2;margin-bottom:4px}</style>"
b1l = clean(env.get_template("new/stacked-arrow-list.html").render(items=[
{"text":f"<strong>{k}</strong>: {v}","border_color":c}
for (k,v),c in zip(BL,["#fb5915","#e79000","#919f00","#0d6361"])
]))
# 헤더/장식만 숨김 — 글씨 크기(22px)는 원래 값
b1l += "<style>.sal-header{display:none}.sal-deco{display:none}</style>"
b1r = clean(env.get_template("new/cards-3col-persona.html").render(personas=[
{"overlay_color":c,"label_line1":h,"label_color":"#1a365d",
"bullets":[{"text":BR["rows"][ri][ci+1]} for ri in range(5)]}
for ci,(h,c) in enumerate([("발주자","#c8d8e8"),("시공자","#d8e8c8"),("설계자","#e8d8c8")])
]))
b1r = b1r.replace('block-c3p','block-c3p2').replace('c3p-','c3p2-')
b1r += "<style>.c3p2-badge{display:none}.c3p2-photo{display:none}.c3p2-badge-label{position:relative;z-index:2;margin-bottom:4px}</style>"
p1 = wrap_slide(make_body(t1, b1l, b1r, TH_NEW, BH_NEW), "plan1_new_only.html")
print(f" {p1.stat().st_size:,} bytes")
# ══════════════════════════════════════════
# 안 2: 내 판단 + mixed — 글씨 크기 그대로
# ══════════════════════════════════════════
print("=== 안 2 ===")
t2 = clean(env.get_template("cards/card-compare-3col.html").render(cards=[
{"title":t,"color":c,"bullets":bs}
for (t,bs),c in zip(TOP,["#1a365d","#15803d","#b45309"])
]))
b2l = clean(env.get_template("cards/card-numbered.html").render(items=[
{"title":k,"description":v,"color":c}
for (k,v),c in zip(BL,["#2563eb","#16a34a","#d97706","#7c3aed"])
]))
b2r = clean(env.get_template("tables/table-simple-striped.html").render(**BR))
p2 = wrap_slide(make_body(t2, b2l, b2r, TH_OLD, BH_OLD), "plan2_mixed.html")
print(f" {p2.stat().st_size:,} bytes")
# ══════════════════════════════════════════
# 안 3: Kei 판단 + new/ only (1,4,2,6)
# ══════════════════════════════════════════
print("=== 안 3 (Kei: 1,4,2,6) ===")
# 상단: 1. cards-3col-persona (안 1과 동일 블록, 동일 데이터)
t3 = clean(env.get_template("new/cards-3col-persona.html").render(personas=[
{"overlay_color":c,"label_line1":t,"label_color":"#1a365d","bullets":[{"text":b} for b in bs]}
for (t,bs),c in zip(TOP,["#dce8d4","#d4dce8","#e8dcd4"])
]))
t3 += "<style>.c3p-badge{display:none}.c3p-photo{display:none}.c3p-badge-label{position:relative;z-index:2;margin-bottom:4px}</style>"
# 하단좌: 4. split-panel-numbered (Kei 선택)
b3l = clean(env.get_template("new/split-panel-numbered.html").render(
categories=[_Cat(k,c,[v]) for (k,v),c in zip(BL,["#417d38","#008e52","#008970","#2563eb"])],
right_items=[],
))
b3l += "<style>.spn-header{display:none}.spn-right{display:none}.spn-mid{display:none}.spn-left{flex:1}</style>"
# 하단우: 2. compare-vs-rows (Kei 선택) — 3주체를 좌/우로
b3r = clean(env.get_template("new/compare-vs-rows.html").render(
main_labels={"left":"발주자","center":"구분","right":"시공자·설계자"},
rows=[{"category":r[0],"left_text":r[1],"right_text":f"{r[2]} / {r[3]}"} for r in BR["rows"]],
))
b3r += "<style>.cvr-header{display:none}.cvr-conclusion{display:none}</style>"
p3 = wrap_slide(make_body(t3, b3l, b3r, TH_NEW, BH_NEW), "plan3_kei_new.html")
print(f" {p3.stat().st_size:,} bytes")
# ══════════════════════════════════════════
# 안 4: Kei 판단 + mixed (A,7,H,6)
# ══════════════════════════════════════════
print("=== 안 4 (Kei: A,7,H,6) ===")
# 상단: A. card-compare-3col (안 2와 동일 블록)
t4 = clean(env.get_template("cards/card-compare-3col.html").render(cards=[
{"title":t,"color":c,"bullets":bs}
for (t,bs),c in zip(TOP,["#1a365d","#15803d","#b45309"])
]))
# 하단좌: 7. issues-paired-rows (Kei 선택)
b4l = clean(env.get_template("new/issues-paired-rows.html").render(
pill_bg=b64("pill_scroll.png"),
rows=[
{"left":{"label":BL[0][0],"text":BL[0][1]},"right":{"label":BL[1][0],"text":BL[1][1]}},
{"left":{"label":BL[2][0],"text":BL[2][1]},"right":{"label":BL[3][0],"text":BL[3][1]},"pills_bottom":True},
],
))
b4l += "<style>.ipr-header{display:none}</style>"
# 하단우: H. compare-3col-badge (Kei 선택)
b4r = clean(env.get_template("tables/compare-3col-badge.html").render(**BR))
p4 = wrap_slide(make_body(t4, b4l, b4r, TH_OLD, BH_OLD), "plan4_kei_mixed.html")
print(f" {p4.stat().st_size:,} bytes")
# ══════════════════════════════════════════
# Selenium 검증
# ══════════════════════════════════════════
print("\n=== Selenium 검증 ===")
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
options = Options()
options.add_argument('--headless')
options.add_argument('--window-size=1400,900')
options.add_argument('--force-device-scale-factor=2')
driver = webdriver.Chrome(options=options)
for fname in ["plan1_new_only.html","plan2_mixed.html","plan3_kei_new.html","plan4_kei_mixed.html"]:
path = OUT / fname
driver.get(f"file:///{path.resolve()}")
time.sleep(2)
driver.save_screenshot(str(path).replace(".html",".png"))
html = path.read_text(encoding="utf-8")
c = re.sub(r'data:image/[^;]+;base64,[A-Za-z0-9+/=]+','I',html)
# font-size override 확인 — !important가 있으면 위반
overrides = re.findall(r'font-size.*?!important', c)
j = re.findall(r'\{[%{].*?[%}]\}',c)
ts = ['안전','품질','생산','향상','소통','신뢰','생산 방식','인지','협업','검증',
'발주자','시공자','설계자','안 되면 DX가 아니다','DX의 궁극적 목표','Process 혁신','업무 수행']
m = [t for t in ts if t not in c]
print(f" [{fname}]")
print(f" Jinja:{len(j)} 텍스트:{len(ts)-len(m)}/{len(ts)} font-size override:{len(overrides)}")
if m: print(f" MISSING: {m}")
if overrides: print(f" OVERRIDE 위반: {overrides[:3]}")
driver.quit()
print("\n완료!")
+306
View File
@@ -0,0 +1,306 @@
"""MDX 03: slide-base + prerequisites-3col(상단) + process-product-2col(하단) + pill(결론).
블록 CSS 글씨 크기 그대로. font-size override 금지.
텍스트 원문 그대로. 요약/추론 금지.
"""
import base64, re
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
BLOCKS = Path("templates/blocks")
SVG = BLOCKS / "svg"
OUT = Path("data/runs/mdx03_final")
OUT.mkdir(parents=True, exist_ok=True)
env = Environment(loader=FileSystemLoader(str(BLOCKS)), autoescape=False)
def b64(f):
p = SVG / f
if not p.exists(): return ""
ext = "svg+xml" if f.endswith(".svg") else "png"
return f"data:image/{ext};base64," + base64.b64encode(p.read_bytes()).decode()
def clean(h): return re.sub(r'<!--.*?-->', '', h, flags=re.DOTALL).strip()
# ══════════════════════════════════════════
# 상단: prerequisites-3col — MDX 원문 그대로
# ══════════════════════════════════════════
class _Item:
def __init__(self, heading, desc): self.heading = heading; self.desc = desc
class _Col:
def __init__(self, **kw):
for k, v in kw.items(): setattr(self, k, v)
# 블록 CSS만 가져오고, HTML은 콘텐츠 items 수에 맞게 동적 생성
# prerequisites-3col의 CSS를 추출
_p3c_raw = (BLOCKS / "new" / "prerequisites-3col.html").read_text(encoding="utf-8")
_p3c_css = re.search(r'<style>(.*?)</style>', _p3c_raw, re.DOTALL).group(0)
# MDX 원문 그대로 — 각 열의 항목 수가 다름
cols_data = [
{
"name": "기술", "sub": "디지털",
"bar": "linear-gradient(180deg, #0D78D0 0%, #023056 100%)",
"hgrad": "linear-gradient(180deg, #0D78D0 0%, #134D7F 100%)",
"items": [
("Digital 기술(S/W, H/W)과 업무 Process의 통합", [
"기존 업무 프로세스에 다양한 디지털 기술을 접목하여 업무 수행",
"프로젝트 전반에 걸친 업무 프로세스의 연결 및 조율",
]),
("분야별 전문 지식(설계, 시공, 유지관리 등) 보유", [
"건설 전 단계에 대한 근본적인 이해와 지식 및 경험",
"최신 토목 기술 트랜드 및 표준 기준 등에 대한 높은 지식",
]),
],
},
{
"name": "사람", "sub": "역량",
"bar": "linear-gradient(180deg, #FF9A23 0%, #CC5200 100%)",
"hgrad": "linear-gradient(180deg, #CC5200 0%, #883700 100%)",
"items": [
("혁신적 사고방식과 창의적 문제 해결 능력", [
"기존 수행 방식과 관습적 사고 등에 의한 접근 방식 탈피",
"디지털 기술을 활용한 창의적, 혁신적인 솔루션 제시",
]),
("사용자 중심 사고와 DX 수행 경험", [
"사용자의 요구와 기대를 충족시키는 설계 및 구현",
"시행착오를 포함한 수행 경험과 사용자 경험(UX)을 반영한 해결 방안 제시",
]),
],
},
{
"name": "자연", "sub": "여건",
"bar": "linear-gradient(180deg, #39BE49 0%, #23742C 100%)",
"hgrad": "linear-gradient(180deg, #39BE49 0%, #1E6328 100%)",
"items": [
("지속적인 투자 및 실행 의지", [
"기술 도입 초기 단계에 필요한 인력·기간·비용 등의 대규모 투자",
"기술 고도화를 위한 지속적인 개선 및 투자 체계 구축",
"변화와 혁신을 통해 부가가치를 창출하려는 실행 의지와 추진력",
]),
],
},
]
# 동적 HTML 생성 — 블록 CSS 클래스 사용, items 수 동적
cols_html = ""
for col in cols_data:
items_html = ""
n = len(col["items"])
for i, (heading, bullets) in enumerate(col["items"]):
pct_h = int(95 / n)
pct_top = int(3 + i * (95 / n))
bul = "".join(f'<div class="bul">• {b}</div>' for b in bullets)
items_html += f"""
<div class="p3c-section" style="position:absolute;left:60px;right:6px;top:{pct_top}%;height:{pct_h}%;">
<div class="p3c-heading" style="background-image:{col['hgrad']}">{heading}</div>
<div class="p3c-desc">{bul}</div>
</div>"""
if i < n - 1:
line_top = pct_top + pct_h
items_html += f'<div class="p3c-mid-line" style="position:absolute;left:56px;right:0;top:{line_top}%;border-top:1.2px dashed #000;"></div>'
cols_html += f"""
<div class="p3c-col" style="flex:1;position:relative;height:100%;border-top:1.2px solid #000;border-bottom:1.2px solid #000;">
<div class="p3c-bar" style="background:{col['bar']};position:absolute;left:0;top:0;width:56px;height:100%;"></div>
<div class="p3c-vlabel-area" style="position:absolute;left:0;top:0;width:56px;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;z-index:3;">
<div class="p3c-vlabel">{col['name']}</div>
<div class="p3c-vlabel-sub">{col['sub']}</div>
</div>
{items_html}
</div>"""
top = f'<div class="block-p3c" style="display:flex;gap:12px;width:100%;height:100%;">{cols_html}</div>\n{_p3c_css}'
# ══════════════════════════════════════════
# 하단: process-product-2col — MDX 원문 그대로
# ══════════════════════════════════════════
# process-product-2col: 블록 CSS 사용 + HTML은 좌우 소제목이 같은 행에 오도록 Grid 재구성
_pp2_raw = (BLOCKS / "BEPs" / "process-product-2col.html").read_text(encoding="utf-8")
_pp2_css = re.search(r'<style>(.*?)</style>', _pp2_raw, re.DOTALL).group(0)
arrow_uri = b64("arrow_asis_tobe.png")
# 좌우 소제목을 행 단위로 대응시킴
# 행1: Analogue 기반 업무의 Digital화 (As-is→To-be) | Copy & Paste로 인해... 품질 향상
# 행2: GIS + BIM의 연계 | Analogue 기반 도서 외 Digital 기반 정보물 추가
# 행3: 사용자 중심의 Solution 제공 | Solution을 활용한 업무 효율화
# 2열 grid + ::before로 열 배경 gradient → 행 높이 동기화 + gradient 유지
bottom = f"""
<div class="pp2-grid-wrap" style="position:relative;width:100%;height:100%;">
<!-- 좌측 배경 gradient (::before 대신 absolute div) -->
<div style="position:absolute;left:0;top:0;width:50%;height:100%;background:linear-gradient(180deg,#ffffff 46%,#39311e 100%);z-index:0;"></div>
<!-- 우측 배경 gradient -->
<div style="position:absolute;left:50%;top:0;width:50%;height:100%;background:linear-gradient(0deg,#296b55 0%,#ffffff 56%);z-index:0;"></div>
<!-- 2열 grid — 행 높이 동기화 -->
<div style="position:relative;z-index:1;display:grid;grid-template-columns:1fr 1fr;width:100%;height:100%;">
<!-- 헤더 행 -->
<div class="pp2-header-bar pp2-header-bar--left" style="background:linear-gradient(270deg,#a4a096 0%,#39311e 100%);border-radius:0 24px 24px 0;display:flex;align-items:center;justify-content:center;height:30px;margin-top:4px;">
<span class="pp2-header-text pp2-header-text--left" style="font-size:13px;font-weight:900;color:#3e3523;">과정(Process)의 혁신</span>
</div>
<div class="pp2-header-bar pp2-header-bar--right" style="background:linear-gradient(90deg,#296b55 0%,#022017 100%);border-radius:24px 0 0 24px;display:flex;align-items:center;padding-left:20px;height:30px;margin-top:4px;">
<span class="pp2-header-text pp2-header-text--right" style="font-size:13px;font-weight:900;color:#ffffff;">결과(Product)의 변화</span>
</div>
<!-- 행1: Analogue Digital화 | 품질 향상 -->
<div style="padding:3px 16px;">
<div class="pp2-mid-title pp2-mid-title--left">Analogue 기반 업무의 Digital화</div>
<div style="display:flex;align-items:center;gap:4px;">
<div style="flex:1;">
<div class="pp2-body-text">• <strong>개념·문서·행정 절차 중심</strong></div>
<div class="pp2-body-text">• <strong>2D 도면, 전문가, 규정</strong></div>
<div class="pp2-body-text">• <strong>업무 구분(단절), 책임</strong></div>
</div>
<div style="flex-shrink:0;width:30px;text-align:center;"><img src="{arrow_uri}" style="width:30px;height:16px;object-fit:contain;" alt="→"></div>
<div style="flex:1;">
<div class="pp2-body-text">• <strong>시각화된 목적물, 소통, 투명성 중심</strong></div>
<div class="pp2-body-text">• <strong>3D 모델, 참여자, 실체</strong></div>
<div class="pp2-body-text">• <strong>협업(융·복합), 창의성</strong></div>
</div>
</div>
</div>
<div style="padding:3px 16px;">
<div class="pp2-mid-title pp2-mid-title--right">Copy &amp; Paste로 인해 하향 평준화된 기존 성과물의 품질 향상</div>
<div class="pp2-body-text">• 과거 수작업으로 시행하면서 발생하던 오류 등의 최소화</div>
<div class="pp2-body-text">• 정확한 Data에 기반한 계획으로 고품질 성과물 도출</div>
</div>
<!-- 행2: GIS+BIM | Digital 정보물 -->
<div style="padding:2px 16px;">
<div class="pp2-mid-title pp2-mid-title--left">GIS + BIM의 연계</div>
<div class="pp2-body-text">• 지리·지형·지반 등 위치정보(GIS)와 3D모델(형상, 속성정보) 기반의 건설 정보를 포함하는 BIM의 연계를 통한 업무 프로세스의 혁신</div>
</div>
<div style="padding:2px 16px;">
<div class="pp2-mid-title pp2-mid-title--right">Analogue 기반 도서 외 Digital 기반 정보물 추가</div>
<div class="pp2-body-text">• 기존 성과물(도면, 수량, 계산서, 시방서 등)에 3D 모델, Simulation 등의 Digital 기반 정보물 추가</div>
</div>
<!-- 행3: Solution -->
<div style="padding:2px 16px;">
<div class="pp2-mid-title pp2-mid-title--left">사용자 중심의 Solution 제공</div>
<div class="pp2-body-text">• 서로 다른 S/W로 작성되어 분절화된 Analogue 방식의 성과물과 정보물을 연계할 수 있는 설계·시공 Solution 제공</div>
</div>
<div style="padding:2px 16px;">
<div class="pp2-mid-title pp2-mid-title--right">Solution을 활용한 업무 효율화</div>
<div class="pp2-body-text">• Engn. Solution을 통해 성과물에 관한 이슈를 함께 검토·논의하는 협업 환경 조성</div>
<div class="pp2-body-text">• 건설 단계별 정보를 디지털 데이터로 축적하여, 건설 전 과정을 통합관리</div>
</div>
</div>
</div>
{_pp2_css}
"""
# ══════════════════════════════════════════
# slide-base 조립
# ══════════════════════════════════════════
title = "DX 실행 체계 구축 방안"
ft = 'DX는 필요한 요건과 체계를 갖춘 후 시행해야만 그 효과를 <em>기대할 수 있다</em>'
# font_hierarchy: key_msg=14, core=12, bg=12, sidebar=11
# zone제목=13px, 블록 heading=12px, 블록 desc=11px
font_override = """<style>
/* prerequisites-3col: font_hierarchy 적용 + 높이 채움 */
.p3c-heading { font-size: 12px !important; line-height: 1.5 !important; }
.p3c-desc { font-size: 11px !important; line-height: 1.6 !important; }
.p3c-desc .bul { padding-left: 12px; text-indent: -12px; }
.p3c-vlabel { font-size: 14px !important; }
.p3c-vlabel-sub { font-size: 12px !important; }
.p3c-kanji { font-size: 16px !important; }
.p3c-col { min-height: 0 !important; height: 100% !important; }
.block-p3c { height: 100% !important; }
/* 한자 숨김 — MDX 원문에 없음 */
.p3c-kanji { display: none !important; }
/* 색상바 내 라벨 영역을 바 전체로 */
.p3c-vlabel-area { width: 56px !important; }
/* section을 바 바로 옆으로 — 들여쓰기 2단 효과 */
.p3c-section { left: 60px !important; right: 6px !important; }
.p3c-mid-line { left: 56px !important; }
/* 자연(3번째 열): heading 1개이므로 하단 숨김 */
.p3c-col:nth-child(3) .p3c-section--top { height: 90% !important; }
.p3c-col:nth-child(3) .p3c-section--bottom { display: none !important; }
.p3c-col:nth-child(3) .p3c-mid-line { display: none !important; }
/* process-product-2col: font_hierarchy 적용 + 높이 채움 */
.pp2-header-text { font-size: 13px !important; letter-spacing: 0.8px !important; }
.pp2-mid-title { font-size: 12px !important; line-height: 1.5 !important; margin-top: 6px !important; }
.pp2-mid-title:first-child { margin-top: 0 !important; }
.pp2-body-text { font-size: 11px !important; line-height: 1.6 !important; padding-left: 12px !important; text-indent: -12px !important; }
.pp2-col { min-height: 0 !important; }
.pp2-header-bar { height: 30px !important; margin-top: 4px !important; }
.pp2-body { padding: 6px 16px !important; }
.block-pp2 { height: 100% !important; }
/* 우측 헤더 텍스트: 배경 gradient와 겹쳐서 안 보임 → 흰색 */
.pp2-header-text--right { color: #ffffff !important; }
/* 좌우 본문 영역 높이 맞춤 */
.pp2-body { display: flex !important; flex-direction: column !important; }
</style>"""
body = f"""
{font_override}
<div style="height:38%;margin-bottom:1%;padding-top:8px;">
<div style="font-weight:700;font-size:13px;color:#1a365d;margin-bottom:8px;">DX 시행을 위한 필수 요건</div>
<div style="height:calc(100% - 28px);padding:0 12px 0 24px;">{top}</div>
</div>
<div style="height:60%;margin-top:12px;">
<div style="font-weight:700;font-size:13px;color:#1a365d;margin-bottom:8px;">Process의 혁신과 Product의 변화</div>
<div style="height:calc(100% - 28px);padding:0 12px 0 24px;">{bottom}</div>
</div>
"""
sb = clean((BLOCKS / "slide-base.html").read_text(encoding="utf-8"))
r = sb.replace('{{ title|default("슬라이드") }}', title)
r = r.replace('{{ title|default("슬라이드 제목") }}', title)
r = r.replace('{% block body %}{% endblock %}', body)
pill = b64("pill_scroll.png")
r = r.replace('{% if footer_text %}', '').replace('{% if footer_pill_bg %}', '')
r = r.replace('{{ footer_pill_bg }}', pill).replace('{% else %}', '')
r = r.replace('<div class="slide-footer-bg slide-footer--css"></div>', '')
li = r.rfind('{% endif %}')
if li > 0: r = r[:li] + r[li + len('{% endif %}'):]
r = r.replace('{% endif %}', '').replace('{{ footer_text|safe }}', ft)
r = r.replace('src="svg/bg_slide_texture.png"', f'src="{b64("bg_slide_texture.png")}"')
r = r.replace('src="svg/line_divider.svg"', f'src="{b64("line_divider.svg")}"')
out = OUT / "final.html"
out.write_text(r, encoding="utf-8")
print(f"저장: {out} ({out.stat().st_size:,} bytes)")
# ══════════════════════════════════════════
# Selenium 검증
# ══════════════════════════════════════════
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
options = Options()
options.add_argument('--headless')
options.add_argument('--window-size=1400,900')
options.add_argument('--force-device-scale-factor=2')
driver = webdriver.Chrome(options=options)
driver.get(f"file:///{out.resolve()}")
time.sleep(2)
driver.save_screenshot(str(out).replace(".html", ".png"))
driver.quit()
# 텍스트 검증
c = re.sub(r'data:image/[^;]+;base64,[A-Za-z0-9+/=]+', 'I', r)
overrides = re.findall(r'font-size.*?!important', c)
j = re.findall(r'\{[%{].*?[%}]\}', c)
ts = ['기술','사람','자연','디지털','역량','여건',
'Digital 기술','혁신적 사고방식','지속적인 투자',
'Process의 혁신','Product의 변화',
'Analogue','Digital','GIS','BIM','Solution',
'기대할 수 있다']
m = [t for t in ts if t not in c]
print(f"Jinja:{len(j)} font-override:{len(overrides)} 텍스트:{len(ts)-len(m)}/{len(ts)}")
if m: print(f" MISSING: {m}")
else: print(" ALL OK")
+48
View File
@@ -0,0 +1,48 @@
"""MDX 03을 기존 파이프라인으로 Stage 1.7까지 돌린 뒤, 산출물을 저장.
파이프라인 코드 그대로 사용:
- Stage 0: mdx_normalizer
- Stage 1A: kei_client.classify_content (Kei API)
- Stage 1B: kei_client.refine_concepts + generate_structured_text
- Stage 1.5a: space_allocator (컨테이너 계산 + font_hierarchy)
- Stage 1.7: block_reference (블록 선택)
Stage 2(조립)는 여기서 하지 않음 — 산출물만 저장.
"""
import asyncio
import json
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from src.pipeline import generate_slide
async def main():
mdx_path = Path("samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx")
content = mdx_path.read_text(encoding="utf-8")
print(f"MDX 03: {mdx_path.name}")
print(f"내용 길이: {len(content)}자")
print()
start = time.time()
async for event in generate_slide(content, base_path=str(mdx_path.parent)):
ev_type = event.get("event", "")
data = event.get("data", "")
if ev_type == "progress":
print(f" {data}")
elif ev_type == "result":
elapsed = time.time() - start
print(f"\n완료! ({elapsed:.1f}초)")
if isinstance(data, dict):
run_id = data.get("run_id", "")
print(f"run_id: {run_id}")
print(f"결과: data/runs/{run_id}/")
elif ev_type == "error":
print(f" 에러: {data}")
asyncio.run(main())
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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