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
897 changed files with 29787 additions and 462 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/02*.mdx)",
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/03*.mdx)",
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260413_*/)"
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260413_*/)",
"Bash(python -c \"from src.config import settings; print\\(f'API configured: {bool\\(settings.anthropic_api_key\\)}'\\)\")",
"Bash(python run_test.py)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/health)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/)",
"Read(//d/ad-hoc/kei/**)",
"Bash(curl -s http://localhost:8000/docs)",
"Bash(taskkill //F //IM python.exe)",
"Bash(python assemble_mdx02_test.py)",
"Bash(wc -c data/runs/20260407_*/final.html)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/docs)",
"Bash(curl -s http://localhost:8080/docs -o /dev/null -w \"%{http_code}\")",
"Bash(curl -s http://localhost:8001/docs -o /dev/null -w \"%{http_code}\")",
"Bash(curl -s http://localhost:3000/ -o /dev/null -w \"%{http_code}\")",
"Bash(python assemble_mdx02_v3.py)",
"Bash(python assemble_mdx02_v4.py)",
"Bash(python assemble_mdx02_v5.py)",
"Bash(python assemble_mdx02_v6.py)",
"Bash(python assemble_mdx02_v7.py)",
"Bash(python assemble_mdx02_v8_3plans.py)",
"Bash(python assemble_mdx02_v9.py)",
"Bash(python build_plan1.py)",
"Bash(python build_plan2.py)",
"Bash(python build_plan3.py)",
"Bash(python build_plan1_v2.py)",
"Bash(python build_4plans_final.py)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:8000/docs)",
"Bash(uvicorn backend.main:app --port 8000)",
"Bash(python final_plan1.py)",
"Bash(python final_plan2.py)",
"Bash(python build_all_4plans.py)",
"Bash(python build_plan3_kei.py)",
"Bash(python build_plan4_kei.py)",
"Bash(python make_4plans.py)",
"Bash(ls -la \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/html_render_final\"*)",
"Bash(taskkill //PID 48540 //F)",
"Bash(python make_mdx03.py)",
"Bash(python run_mdx03_pipeline.py)",
"Bash(powershell -Command \"Get-Process python -ErrorAction SilentlyContinue | Select-Object Id,StartTime\")",
"Bash(grep \"class FontHierarchy\" src/*.py)",
"Bash(ls -ltr /d/ad-hoc/kei/design_agent/data/runs/*/step_*_context.json)",
"Bash(awk '{print $2}')",
"Bash(stat /d/ad-hoc/kei/design_agent/data/runs/20260414_120225/stage_*_context.json)",
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260414_120225/stage_*_context.json)",
"Bash(awk '{print $6, $7, $8, $9}')",
"Bash(ls -lt data/runs/20260414_120225/*_context.json)",
"Bash(awk '{print $6,$7,$8,$9}')",
"Bash(python -c \" import yaml with open\\('catalog.yaml'\\) as f: data = yaml.safe_load\\(f\\) blocks = data.get\\('blocks', []\\) for b in blocks: print\\(f\\\\\"{b['id']} | {b.get\\('category',''\\)} | items:{b.get\\('min_items','?'\\)}-{b.get\\('max_items','?'\\)}\\\\\"\\) \")",
"Bash(python add_tags.py)",
"Bash(python -c \"import src.block_reference; print\\('OK'\\)\")",
"Bash(python -c \"import src.block_assembler; print\\('OK'\\)\")",
"Bash(ls -t d:/ad-hoc/kei/design_agent/docs/history/PHASE-*.md d:/ad-hoc/kei/design_agent/docs/history/IMPROVEMENT-PHASE-*.md)",
"Bash(python -c \"import src.pipeline_context; import src.kei_client; import src.pipeline; print\\('모든 import OK'\\)\")",
"Bash(python -c \"import src.step_visualizer; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.block_assembler; print\\('OK'\\)\")",
"Bash(python -c \"import src.block_assembler; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.kei_client; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.pipeline_context; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.validators; print\\('OK'\\)\")",
"Bash(python -c \"import src.validators; print\\('OK'\\)\")",
"Bash(python -c \"import src.validators; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.section_parser; import src.block_assembler; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.section_parser; print\\('OK'\\)\")",
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.space_allocator; import src.pipeline_context; print\\('OK'\\)\")",
"Bash(grep -l \"pp2-grid-wrap\\\\|pp2\" templates/blocks/**/*.html)",
"Bash(echo file:///D:/ad-hoc/kei/design_agent/data/runs/20260415_110323/final.html)",
"Bash(python -c \"import src.block_reference; import src.section_parser; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.block_assembler; import src.section_parser; import src.pipeline; print\\('OK'\\)\")",
"Bash(python -c \"import src.block_reference; import src.pipeline; import src.block_assembler; print\\('OK'\\)\")"
],
"additionalDirectories": [
"d:\\ad-hoc\\kei\\design_agent\\templates\\blocks\\new"
+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에서 재설계.
> 핵심 전환: "계산 먼저, AI 판단 나중에, 렌더링은 검증만"
>
> ⚠️ Phase Z (매칭 시스템 통합) 진행 후 이 섹션의 일부는 새 흐름으로 대체될 예정.
```
[1단계] Kei 실장 (Opus) — AI 사고
@@ -65,30 +144,48 @@ reference 꼭지 있음 → sidebar-right
나머지 → 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 비중 → 역할별 컨테이너 px 확정 | AI 판단 불필요 |
| 프리셋 선택 | 코드 | 규칙 | 실장의 role에 따라 프리셋 자동 선택 | AI 판단 불필요 |
| **HTML 생성** | **AI (Kei API)** | **AI** | **콘텐츠 전달 의도에 맞는 HTML 구조를 직접 생성. 블록 CSS를 참고하되 구조는 AI가 결정.** | 블록 "선택" 안 함. 슬롯 "채우기" 안 함. |
| 검증 | 코드 + AI | Selenium + 비전 모델 | overflow 측정, 시각 품질 평가 | |
| Kei 실장 | Opus (Kei API) | AI | MDX 본문 분석 보조, 최종 검수 | HTML 생성, 레이아웃 계산 |
| 레이아웃 결정 | 코드 | 룰 매칭 | MDX 콘텐츠 → 8-preset layout vocabulary 기반 slide/zone topology 선택 (legacy hint = Type A/B/B'/B'') | — |
| 컨테이너 계산 | 코드 | 결정론적 | zone 별 px 확정 (`space_allocator`) | — |
| 프레임 매칭 | 코드 (V1~V4; B4 frame_selection evidence integration pending) | 결정론적 | zone ↔ Figma 프레임 매칭 | — |
| **콘텐츠 매핑** | **코드 + AI** | **하이브리드** | **zone 안 콘텐츠 / 텍스트를 프레임 슬롯에 배치 / 다듬기 / 변형** | **HTML 구조 생성 X** |
| HTML 조립 | 코드 (Jinja2) | 결정론적 | `slide-base` + 프레임 + 슬롯 콘텐츠 합쳐서 final.html | AI 호출 X |
| 검증 | 코드 + AI | Selenium + Vision | overflow 측정, 시각 품질 평가 | — |
### 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 구조를 직접 생성
- 기존 38개 블록의 CSS(색상, 폰트, 배경, radius)를 **스타일 참고**로 활용
- 블록을 **"선택"하지 않음**. topic 합침/분리, 포함 관계, 핵심 메시지 분리 가능
- 디자인 토큰(CSS 변수)으로 품질 제약 + Selenium 측정 + 비전 모델 검증
**Phase Z 의 핵심 원칙**:
- **HTML 구조** = `slide-base.html` + 코드 (Jinja2) 가 결정 (절대 AI 가 생성 X)
- **AI 의 역할** = zone 안 콘텐츠 매핑 / 텍스트 다듬기 / 디자인 변형 (콘텐츠 단위)
- **프레임 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 붙여넣기 또는 파일 업로드)
+86 -284
View File
@@ -1,322 +1,124 @@
# C.E.L. Slide Pipeline
MDX 기반 콘텐츠를 분석해 1280x720 슬라이드 HTML로 변환하는 파이프라인입니다.
## 이 프로젝트는 무엇인가
이 문서는 현재 코드 기준으로 이 프로젝트가 무엇을 하는지, 어떤 프로세스로 동작하는지, 현재 어디까지 와 있는지, 앞으로 무엇을 개선하려는지를 빠르게 파악하기 위한 개요 문서입니다.
MDX 기반 콘텐츠를 입력하면, 1280×720 슬라이드 HTML로 자동 변환하는 파이프라인입니다.
## 무엇을 하는가
텍스트 콘텐츠를 넣으면, 구조를 분석하고 BEPs(Figma) 디자인을 매칭하여 슬라이드를 만들어줍니다.
이 프로젝트는 MDX 문서를 입력으로 받아 다음 과정을 거쳐 슬라이드 결과물을 생성합니다.
---
- 콘텐츠 정규화
- 문서 의미 분석
- 슬라이드 구조 해석
- schema / recipe / block 선택
- HTML 조립
- fit / overflow 검증
- 최종 산출물 저장
## 어떻게 구성/구현되어 있는가
최종 산출물은 보통 다음 형태로 저장됩니다.
### 전체 흐름
- `final.html`
- `final_context.json`
- `steps/*.html`
- popup / detail HTML
## 한눈에 보는 프로세스
```text
MDX Input
↓
Stage 0 Normalize
↓
Stage 1A/1B AI Meaning Analysis
↓
Phase Y Group / Schema / Recipe Parsing
↓
Stage 1.5a Space Allocation
↓
Stage 1.7 Block Selection
↓
Stage 1.8 Fit / Measure / Adjustment
↓
Stage 2 Assemble final slide HTML
↓
Stage 4 Validate with Selenium / Vision
↓
Stage 5 Save final artifacts
```
MDX 입력 → 정규화 → 꼭지 추출(AI) → zone 구분 → BEPs 매칭 → 조립 → 검증 → 출력
```
핵심 원칙은 다음과 같습니다.
### 구조
- source of truth는 `normalized.sections`
- 문서명 하드코딩보다 shape, schema, recipe, tag를 우선한다
- 흐름의 우선순위는 `구조 -> payload -> layout -> fit`
- popup / detail은 overflow를 덮는 임시 장치가 아니라 `메인 요약 + 상세 보기`의 표현 계약이다
- **slide-base:** 1280×720 슬라이드 프레임. 대목차 + 구분선 + 본문 영역 + 핵심 인사이트(footer)
- **zone:** 본문 영역 안에서 중목차(##) 기준으로 나뉘는 영역 (top/bottom 등)
- **블록:** zone 안에 들어가는 디자인 단위. Figma에서 추출한 BEPs 디자인을 HTML/CSS로 변환한 것
- **catalog:** 블록의 메타 정보 (구조, 슬롯, 매칭 조건)
## 타입 구조
### 주요 파일
현재 메인 타입 선택은 사실상 `Type A`와 `Type B`입니다.
| 파일 | 역할 |
|------|------|
| `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 A
- 본문 외에 sidebar, reference, 부록성 영역이 함께 필요한 슬라이드
- 현재는 Type B보다 덜 닫혀 있고, AI 생성 + renderer 경로 비중이 더 큼
### 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에서 AI가 문서 의미와 topic 추출
3. Phase Y에서 코드가 `normalized.sections`를 읽고 page_structure를 다시 생성
4. Stage 1.7에서 block / recipe 후보 선택
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`
- `two_col_text_detail`
- `stacked_summary_detail`
recipe는 보통 이런 계약을 가질 수 있습니다.
- left / right kind
- top / bottom kind
- ratio
- vertical align
- direct render 우선 여부
### block
실제 구현 템플릿 후보입니다.
예:
- `prerequisites-3col`
- `process-product-2col`
- `compare-detail-gradient`
- `card-icon-desc`
즉, “무슨 문서냐”보다 “무슨 구조냐”를 먼저 읽고, 그 구조에 맞는 표현 규칙을 정한 뒤, 마지막에 구현 block을 고르는 방향으로 가고 있습니다.
## popup / detail 계약
popup은 현재 다음 철학으로 정리되는 중입니다.
- 메인 슬라이드에는 zone 크기에 맞는 요약만 남긴다
- 큰 표, 시각 컴포넌트, 과다 bullet은 상세 popup으로 분리한다
- 메인에서는 `자세히보기` 링크를 제공한다
현재 popup 관련 핵심은 아래입니다.
- `PopupItem` 모델로 popup 데이터를 명시적으로 다루기 시작함
- `popup_id`와 `popup_file` 생애주기를 분리해 관리 중
- 최종 목표는 popup 판단을 휴리스틱이 아니라 명시적 contract로 만드는 것
다만 아직 일부 구간에는 추측 로직과 이중 관리가 남아 있어, 이 부분은 계속 정리 중입니다.
## run 산출물 구조
### 산출물
각 실행은 `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)
- [src/pipeline_context.py](src/pipeline_context.py)
- [src/section_parser.py](src/section_parser.py)
- [src/block_reference.py](src/block_reference.py)
- [src/block_assembler.py](src/block_assembler.py)
- [src/space_allocator.py](src/space_allocator.py)
TO-BE:
중목차 기준으로 zone을 먼저 나누고
→ TF-IDF로 BEPs 매칭 시도
→ 매칭되면 블록 삽입 + 크기 조절 (direct-fit)
→ 안 되면 AI가 꼭지 정리 + 유사 디자인으로 redesign + 반복 조정 (recipe)
→ 빈 공간/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`
### 검증 / 측정
- [src/slide_measurer.py](src/slide_measurer.py)
- [src/validators.py](src/validators.py)
- [src/step_visualizer.py](src/step_visualizer.py)
### 계획 / 히스토리
- [PIPELINE.md](PIPELINE.md)
- [docs/history/PHASE-Y-PLAN.md](docs/history/PHASE-Y-PLAN.md)
---
## 현재 상태
### 비교적 잘 닫혀가는 것
| 대상 | 슬라이드 변환 | 파이프라인 자동화 |
|------|-------------|----------------|
| MDX 03 (3개 목표 + 비교표) | ✅ 완료 | ✅ 파이프라인 연결 |
| MDX 02 (목표 + 프로세스 + 상세표) | ✅ 완료 | △ 파이프라인 연결, 시각 품질 개선 중 |
| MDX 01 (Type A, sidebar 구조) | ✅ 완료 (개별) | 미연결 |
| 토큰 기반 CSS 체계 | - | ✅ 정의 완료, slide-base 적용 |
| Figma 블록 추출 | - | 진행 중 (`figma_to_html_agent/blocks/`) |
- Type B 메인 경로
- `normalized.sections` 기반 구조 해석
- schema / recipe 기반 block selection의 골격
- redesign block 자산화
- popup / detail 2단 표현 계약의 초안 연결
---
### 아직 정리 중인 것
## 다음 단계 방향
- Type A 전체 안정화
- popup을 완전한 source of truth로 정리
- tag_match 와 schema_match의 완전한 동등 점수 비교
- step 보드 인코딩 / 설명 품질
- fit loop의 공간 재분배 고도화
- legacy 경로와 문서 정리
| 순서 | 단계 | 내용 |
|------|------|------|
| 1 | 폴더 구조 정리 | structures/recipes/legacy 분리 |
| 2 | 기존 블록 점진 전환 | 분류(direct-fit/recipe/rewrite) → 토큰 기반 전환 |
| 3 | catalog 고도화 | 파일명 중심 → 속성 테이블 기반 매칭 |
| 4 | 파이프라인 연결 | TF-IDF 매칭 + recipe/composition 경로 |
| 5 | fit 루프 확장 | 빈 공간 재분배, preview 축약, 자동 조정 반복 |
| 6 | 시각 품질 검증 | 정렬, 위계, 가독성 검증 강화 |
### 최근 Type B에서 특히 중요해진 방향
상세: [IMPROVEMENT-PLAN.md](docs/architecture/IMPROVEMENT-PLAN.md)
- recipe direct render가 block 선택에 끌려가지 않도록 구조 계약을 더 강하게 만든다
- Type B direct render가 Type B'의 검증된 표시 계약을 최대한 재사용하도록 정리한다
- sample은 복제 대상이 아니라 evaluation rule의 기준으로 사용한다
- validation은 “같아야 한다”보다 “어긋나면 안 된다”에 초점을 둔다
---
## 향후 개선 방향
## 참고 문서
현재 이후의 개선 방향은 아래 축으로 정리됩니다.
### 1. 구조 계약 강화
- top / bottom zone의 contract를 더 구체화
- `parallel_cluster_plus_visual`, `full_text + detail_preview` 같은 recipe를 표현 범주 수준으로 강화
- recipe가 block를 끌고 가는 것이 아니라 recipe가 block를 통제하도록 정리
### 2. 표시 계약 통합
- Type B direct render가 Type B'에서 이미 검증된 bullet / indent / body-text 구조를 재사용하도록 통합
- `.rdr-*` 계열 신규 CSS를 계속 키우기보다, 기존 검증된 계약을 최대한 재사용
### 3. detail preview 개선
- popup source가 표면 `헤더 + 일부 행`
- popup source가 리스트면 `앞부분 몇 개`
- popup source가 컴포넌트면 구조화된 preview
즉 “링크만 있는 상세”가 아니라 “상세가 있다는 걸 바로 이해할 수 있는 preview”를 만드는 방향
### 4. fit loop 고도화
지금은 주로 overflow 대응 중심이지만, 앞으로는 아래까지 확장하려고 합니다.
- 빈 공간 감지
- 이미지 확대
- preview 행 수 조정
- zone 비율 재배분
### 5. 검증판과 final 일치화
- `stage_4` 보드와 실제 `final_context.json` / `final.html`의 상태 차이를 줄이기
- 검수 보드를 더 믿을 수 있는 상태로 정리
## 읽는 순서 추천
프로세스를 빠르게 파악하려면 아래 순서가 좋습니다.
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을 못 본다
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 사용
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) "디자인 인사이트" 섹션에 누적한다.
11. **모든 슬롯은 기본 optional** — 1:1 단계에서 모든 슬롯이 채워져 있다고 해서 "이 블록은 필수" 로 해석하지 않는다. 같은 블록이 사진 없는/짧은/긴 mdx에 모두 매칭되어야 한다는 가정으로 설계한다.
@@ -60,12 +60,18 @@ Figma 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고,
## 입출력
**입력:** Figma 파일 + 노드 ID (또는 현재 선택 노드)
**출력:**
- `block-tests/{slug}.html` — 변환 결과
**출력 (변환):**
- `block-tests/{slug}.html` — 1:1 변환 결과
- `block-tests/{slug}_flat.md` — 플래튼/이상 탐지/변형 축 메모
- `assets/shared/...` — 공유 자산 캐시
- `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 ← 이 파일 (에이전트 명세)
├── PROCESS.md ← 10단계 운영 절차 (변환 핸드북)
├── MATH.md ← 수학 공식 레퍼런스
├── RULES.md ← CSS 보정 규칙 (R1~R12)
├── RULES.md ← CSS 보정 규칙 (R1~R16)
├── PROCESS-CONTROL.md ← "찍어맞추기 금지" 규칙
├── PLAN.md ← 현재 진행 현황
├── README.md ← 사용법 가이드
├── blocks_index.md ← 변환 완료 도서관
│
├── scripts/
@@ -90,22 +96,22 @@ figma_to_html_agent/ ← 에이전트 작업 영역 (staging)
│ ├── shared/ ← 해시 기반 자산 캐시 (재사용)
│ └── frame_{id}/ ← 프레임 전용 자산 (legacy)
│
└── templates_staging/ ← Stage 2: Jinja2 추상화
├── {pattern_id}.html.j2
└── {pattern_id}.meta.yaml ← when/slots/min_size_px 초안
└── templates_staging/ ← Stage 2: Jinja2 추상화 (legacy, 현재 미사용)
────────────────────────────────────────────────────────
🚧 프로모션 게이트 (사용자 수동 작업) 🚧
프로모션 (사용자 승인 후 에이전트가 실행)
────────────────────────────────────────────────────────
design_agent/ ← 본체 라이브러리 (에이전트 접근 금지)
└── templates/
├── blocks/{category}/
│ └── {pattern_id}.html.j2 ← 사용자가 staging에서 이전
└── catalog.yaml ← 사용자가 when/slots 등록
design_agent/templates/
├── blocks/
│ ├── new/ ← Figma 추출 블록 (에이전트가 작성)
│ ├── svg/ ← 블록 공용 이미지 (에이전트가 정리)
│ ├── 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 각도와 border-radius 동시 변경 금지)
- **장식 요소를 이미지 슬롯(img src)으로 넣기** — gradient bar, ribbon, pill, badge, 오버레이 등은 CSS로 구현. AI가 색상/크기를 조정할 수 없는 이미지 의존 블록은 쓸 수 없다
- **사용자 승인 없이 templates/ 수정** — 프로모션은 사용자 승인 후에만
- **여러 프레임을 한 세션에 변환** (1세션 1프레임 원칙)
- **plus-darker 블렌드 사용** (Safari 전용 → multiply로 교체, RULES.md R10)
- **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 | 위반 사항 | 처리 |
|------|-------|---------|--------|------|
| [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