diff --git a/docs/architecture/IMP-19-ZONE-RATIO-REFERENCE.md b/docs/architecture/IMP-19-ZONE-RATIO-REFERENCE.md new file mode 100644 index 0000000..194adfb --- /dev/null +++ b/docs/architecture/IMP-19-ZONE-RATIO-REFERENCE.md @@ -0,0 +1,97 @@ +# IMP-19 — Phase O/Q Zone Ratio Container Pattern Reference + +**Status**: documented (reference-only, dormant) +**Scope**: doc-only. No runtime surface modified. +**Related issue**: https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/19 +**Soft dependency**: IMP-09 (Phase Z Step 8 zone-ratio solver) — IMP-19 stays dormant; activates only via the A5 gate. +**Source axis**: INSIGHT-MAP §3 / §2.8 I4 — `renderer._group_blocks_by_area` pattern reference. + +--- + +## A1 — Phase O/Q consumer pattern (read-only reference) + +Phase O/Q implements role-based block grouping inside body-side zones at the renderer layer. References (do **not** modify): + +- `src/renderer.py:210-295` — `_group_blocks_by_area(blocks, container_specs=None)` — `OrderedDict` grouping by `block["area"]`; when `container_specs` is supplied and `area ∈ {"body","left","right","hero","detail"}` enters the role-container branch (L230). +- `src/renderer.py:234` — hardcoded `role_order = ["배경", "본심"]` — two-role role-loop axis (block-level container, **not** zone geometry). +- `src/renderer.py:240-253` — topic_id-first match against `spec.topic_ids`, then fallback positional fill when topic_id match yields empty (L248-253). +- `src/renderer.py:261-274` — inline-style injection: `height:{spec.height_px}px; overflow:visible; display:flex; flex-direction:column; gap:8px; font-size:{font_size}px; --spacing-inner:{padding}px; --font-body:{font_size/16}rem;`. `font_size` / `padding` are read at `:262-263` via `spec.block_constraints.get("font_size_px", 15.2)` / `.get("padding_px", 20)` — **renderer-side defaults**, not producer-emitted (see A2). +- `src/renderer.py:277-279` — leftover (unassigned) blocks appended after role containers. +- `src/renderer.py:283-291` — non-container branch: `len(block_list)==1` → single html, else `flex-direction:column` wrapper with `gap:var(--spacing-block); height:100%`. + +Call sites: + +- `src/renderer.py:352-353` — `render_multi_page()` — passes `layout_concept.get("_container_specs")` as `container_specs` argument (Phase O activation path). +- `src/renderer.py:426` — `render_slide()` — invokes `_group_blocks_by_area(blocks_raw)` with **no** `container_specs` (legacy fallback / unit-test path). + +Classification: block/role-level container injection at render time. **Not** Phase Z zone geometry. + +## A2 — Phase O upstream producer (read-only reference) + +`ContainerSpec` payloads consumed by A1 are produced upstream. References (do **not** modify): + +- `src/space_allocator.py:445-586` — `build_containers_type_b(page_structure, slide_width=1280, slide_height=720, image_sizes=None)` — Phase X-B 유형 B container builder. +- `src/space_allocator.py:462-468` — token load (`_load_design_tokens`) + `pad`, `header_h`, `gap_block`, `gap_small`, `inner_w` derivation. +- `src/space_allocator.py:470-484` — role classification into `top_roles` / `bottom_roles` / `footer_role` by `info["zone"] ∈ {"top","bottom","bottom_left","bottom_right","footer"}`. +- `src/space_allocator.py:486-503` — usable height calculation against `slide_body_top=65` + `slide_body_h=590` with optional `footer_role` carve-out. +- `src/space_allocator.py:505-510` — `zone_overhead = zone_count * zone_title_h(28) + (zone_count-1) * zone_gap(16)`. +- `src/space_allocator.py:512-520` — `top_h` / `bottom_h` split by `weight` ratio over `usable_h`. +- `src/space_allocator.py:522-537` — image-aware top-zone width split (`img_w = min(top_h*ratio, inner_w*0.45)`). +- `src/space_allocator.py:541-556` — top-role `ContainerSpec` emission: `block_constraints = {"img_width_px": img_w, "img_height_px": top_h if img_w>0 else 0, "has_image": img_w>0}` — image-aware keys only. +- `src/space_allocator.py:562-574` — bottom-role `ContainerSpec` emission: `block_constraints = {}` (empty; no producer keys). +- `src/space_allocator.py:577-588` — footer-role `ContainerSpec` emission: `block_constraints = {}` (empty; `max_height_cost="low"` literal). + +Producer classification: block-level role container with `height_px` + `width_px` + `block_constraints` containing **only** image-aware keys (`img_width_px`, `img_height_px`, `has_image`) on top role, **empty** on bottom/footer roles. **Not** zone-level ratio geometry. `font_size_px` / `padding_px` are **renderer-side defaults** (consumed via `.get(..., 15.2)` / `.get(..., 20)` at `src/renderer.py:262-263`), **not** producer output. + +## A3 — Phase Z Step 8 solver delta (IMP-09 owned) + +The active Phase Z zone-ratio solver lives in `src/phase_z2_pipeline.py` and is **IMP-09 owned**. IMP-19 does **not** absorb, replace, or amend this surface. References (do **not** modify): + +- `src/phase_z2_pipeline.py:794-853` — `compute_zone_layout(zones_data, total_height=SLIDE_BODY_HEIGHT, gap=GRID_GAP)` — row-axis solver. Algorithm = `min_height_first + content_weight_distribution`: Step 1 reserves per-zone `min_height_px` from frame_contract `visual_hints` (with proportional scale-down on overflow), Step 2 distributes the remaining vertical budget by `content_weight.score`, Step 3 absorbs rounding residual into the last zone. Returns `heights_px` + `ratios` + reasoning trace. +- `src/phase_z2_pipeline.py:924-972` — `compute_zone_layout_cols(zones_data, total_width=SLIDE_BODY_WIDTH, gap=GRID_GAP)` — col-axis solver. Algorithm = `content_weight_distribution_cols` (weight-only; no `min_width_px` contract exists in `frame_contracts.yaml` per IMP-09 verification). Zero-weight guard splits evenly across `n` zones. Returns `widths_px` + `width_ratios`. +- `src/phase_z2_pipeline.py:1125-1452` — topology dispatch surface: + - `:1125-1152` `_build_rows_dynamic` — `topology=="rows"` (horizontal-2): dynamic row heights via `compute_zone_layout`, static fr column widths via `_parse_fr_string`. + - `:1155+` `_build_grid_dynamic_2d` — `topology ∈ {T, inverted-T, side-T-left, side-T-right, 2x2}`: per-row + per-col virtual-zone aggregation → row solver + col solver → `2d_dynamic_aggregated` computation. + - `:1444-1452` dynamic-branch dispatcher: `rows` / `cols` / 2-D / default fr. + - `:1380-1434` user-override geometry branch (`computation == "user_override_geometry"`) — preserves raw override percentages without invoking the weight solver. + +Delta vs Phase O/Q (A1+A2): + +| Axis | Phase O/Q (`renderer._group_blocks_by_area`) | Phase Z Step 8 (`compute_zone_layout` + cols) | +|---|---|---| +| Geometry level | block/role inside one zone | zone-level row/col tracks across slide_body | +| Width source | role x-anchor + `top_h` image carve-out | content_weight share (cols) / fr-string (rows) | +| Height source | producer `ContainerSpec.height_px` injection | min_height_first + content_weight remainder | +| Role axis | hardcoded `["배경","본심"]` (L234) | no role concept — zone position + frame contract | +| Min-height source | none (producer-emitted absolute px) | frame_contract `visual_hints.min_height_px` | +| Topology dispatch | none (single role-loop) | rows / cols / T / inverted-T / side-T-* / 2x2 / single | +| Inline-style injection | yes (height + font_size + spacing-inner) | no (geometry-only; styling handled downstream) | + +Conclusion: Phase O role-container pattern and Phase Z zone-ratio solver operate at **different abstraction layers** (block-in-zone vs zone-in-slide). They are **not** drop-in interchangeable; IMP-19 surfaces this delta only for design-pattern comparison. + +## A4 — IMP-09 boundary statement (soft-link) + +IMP-19 is `soft link: IMP-09`. Ownership separation: + +- **IMP-09 owns**: every algorithmic change to `compute_zone_layout`, `compute_zone_layout_cols`, the topology dispatch surface (`_build_rows_dynamic` / `_build_cols_dynamic` / `_build_grid_dynamic_2d` / `_build_fr_default`), and the frame_contract `visual_hints.min_height_px` contract. +- **IMP-19 owns**: reference-only documentation of the Phase O/Q `_group_blocks_by_area` + `build_containers_type_b` pattern (A1 + A2) and the Phase Z solver delta narrative (A3). +- **No bidirectional code flow**: IMP-19 does not move Phase O code into Phase Z, and IMP-09 does not consume Phase O `ContainerSpec` payloads. The two solvers remain isolated. +- **Reference direction is one-way**: this document points read-only at `src/renderer.py`, `src/space_allocator.py`, and `src/phase_z2_pipeline.py`. No reverse pointer is required in those source files. + +If IMP-09 alters the Phase Z solver signature, A3 must be re-verified (file:line refs); the boundary statement itself does not change. + +## A5 — Re-activation gate + guardrails + +IMP-19 is `documented` (dormant). Re-activation requires **all** of the following gate conditions: + +1. **Trigger**: Phase Z Step 8 produces a verifiable case where the active solver (`min_height_first + content_weight`) yields geometry that the Phase O role-container pattern would have handled correctly — i.e., a regression that maps cleanly to the block-level role abstraction, not the zone-level abstraction. +2. **Evidence requirement**: failing-case MDX + frame_contract trace + observed geometry vs expected geometry, attached to a new issue or this issue's reopened state. +3. **IMP-09 sign-off**: the IMP-09 owner confirms the failing case is **not** addressable inside the Phase Z solver (e.g., adding `visual_hints.min_height_px` or adjusting `content_weight.score` does not resolve it). +4. **Scope re-lock**: the new axis is scope-locked under a fresh implementation issue (not silently reopened in IMP-19) so the soft-link contract is preserved. + +Guardrails (preserved from Stage 1 + Stage 2): + +- **GR1 — No runtime integration**: this document does not authorize merging Phase O role-container code into the Phase Z runtime. Any such integration requires a new scope-locked issue with its own Stage 1/2 review. +- **GR2 — Phase O no-regression**: Phase O containers (`render_multi_page` path with `_container_specs`) must not re-enter the Phase Z render path; the `render_slide` legacy fallback at `src/renderer.py:426` (no `container_specs`) remains the unit-test entry. +- **GR3 — Reference extract stays in `docs/architecture/`**: never under `src/`. No code body copying; file:line refs only. +- **GR4 — Soft-link integrity**: IMP-19 status remains `documented` until the A5 gate fires. The IMP-09 backlog entry carries a back-reference (see u3); IMP-19 carries the forward reference here. diff --git a/docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md b/docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md index 3be3525..293dd1a 100644 --- a/docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md +++ b/docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md @@ -122,7 +122,7 @@ | B-2 verification 보조 | Step 1, 2, 14, 21, 22 | §2.7 H3 (text 추출 / 정규화 / 비교 utility) | pending | yes (UI/backend) | | IMP-17 AI repair fallback infra (carve-out — see [`IMP-17-CARVE-OUT.md`](IMP-17-CARVE-OUT.md)) | Step 12, 16, 17 | §2.6 G3 (`httpx` + SSE streaming + retry + JSON parse pattern) | pending | no (AI fallback only) | | I3 SVG 좌표 보강 | Step 0, 9 | §2.8 I3 (`renderer._preprocess_svg_data`) | pending | yes (deterministic) | -| I4 zone 비중 분배 | Step 8 | §2.8 I4 (`renderer._group_blocks_by_area`) | pending | yes (deterministic) | +| IMP-19 I4 zone 비중 분배 (reference — see [`IMP-19-ZONE-RATIO-REFERENCE.md`](IMP-19-ZONE-RATIO-REFERENCE.md)) | Step 8 | §2.8 I4 (`renderer._group_blocks_by_area`) | pending | yes (deterministic) | | H2 frame contract validation | Step 10 | §2.7 H2 (`content_verifier.verify_structure` pattern) | pending | yes (deterministic) | --- diff --git a/docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md b/docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md index b805a6d..567bd40 100644 --- a/docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md +++ b/docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md @@ -50,7 +50,7 @@ | IMP-06 | B-1 Zone-section override | Step 6 + input Step 1, 22 | §2 B-1 새로 만들기 (backend path) | medium | CLI 인자 + composition planner override path 신설 | Kei composition / Phase R' frame 보조 회귀 X / override 적용 시 composition_unit schema 정합 + trace | soft link: IMP-04 (frame 후보 ↑ 시 override 의미 ↑) | implemented | | IMP-07 | B-2 Edited HTML → MDX reverse path | Step 22 + Step 1, 2 | §2 B-2 새로 만들기 (backend path) | medium | frontend edited HTML → backend → MDX 변환 → pipeline 재진입 (글벗 `html_to_slide_mdx` 참조) | AI/Kei reverse 회귀 X / 재진입 후 step02 정합 + visual_check 통과 | hard link: IMP-02 (A-1 normalize schema 와 reverse path schema 정합 필요) | implemented | | IMP-08 | B-3 Sub-section drag drop | Step 3 | §2 B-3 새로 만들기 (backend schema) | ↓ low | Phase Z `section_id` schema 확장 (sub_sections 단위 매핑) | AI/Kei schema 회귀 X / backward compatible / step03 trace | hard link: IMP-02 (A-1 normalize sub_sections schema 의존) | implemented | -| IMP-09 | B-4 다른 layout zone-geometry | Step 8 | §2 B-4 새로 만들기 (backend layout) | ↓ low | `build_layout_css` 분기 확장 (top-1-bottom-2 / left-1-right-2 / grid-2x2 등) | Kei `build_containers_type_b` 회귀 X / step08 trace | none | implemented | +| IMP-09 | B-4 다른 layout zone-geometry | Step 8 | §2 B-4 새로 만들기 (backend layout) | ↓ low | `build_layout_css` 분기 확장 (top-1-bottom-2 / left-1-right-2 / grid-2x2 등) | Kei `build_containers_type_b` 회귀 X / step08 trace | soft back-link: IMP-19 ([reference doc](IMP-19-ZONE-RATIO-REFERENCE.md) — Phase O block-level pattern reference, no runtime integration) | implemented | | IMP-10 | D-1 filtered_section_reasons UI | Step 20, 22 | §2 D-1 frontend 신규 | ↓ low | frontend UI — backend artifact read-only 표시 | AI/Kei UI 회귀 X / backend artifact read-only | none | implemented | | IMP-11 | D-2 Frame min_height 표시 | Step 22 | §2 D-2 새로 만들기 (frontend hint + catalog 참조) | ↓ low | frontend UI — frame contract `min_height_px` read-only + resize hint | AI/Kei UI 회귀 X / catalog 참조 + resize limit | none | implemented | @@ -67,7 +67,7 @@ | IMP-16 | B-2 verification 보조 axis | Step 1, 2, 14, 21, 22 | §3 H3 Reference Only | ↓ low | B-2 reverse path 의 verification 보조. main reverse path 는 IMP-07, 본 issue 는 text/visual/trace 검증 layer | AI/Kei verification 회귀 X / utility deterministic | hard link: IMP-07 (B-2 main 활성 시점 의미) | implemented | | **IMP-17** | **AI repair fallback infra** (**carve-out — normal path 밖**) | Step 12, 16, 17 | §3 G3 | (별 axis priority — pending) | [carve-out boundary + activation gate](IMP-17-CARVE-OUT.md) (3-cond AND: User GO ∧ B4 frame_selection evidence ∧ IMP-04/05 live — full def in u2 doc) — `httpx` + SSE streaming + retry + JSON parse pattern reference — light_edit / restructure proposal | **normal path AI 호출 0 — 본 axis = fallback only, normal path 와 분리 설계** / Kei persona 단절 (Phase Q 자산과 단절) | soft link: IMP-04 + IMP-05 (catalog 확장 + V4 fallback 활성 시 의미) | documented (deferred) | | IMP-18 | I3 SVG 좌표 보강 | Step 0, 9 | §3 Reference Only | ↓ low | `renderer._preprocess_svg_data` 패턴 reference — frame_partials SVG 좌표 사전 박힘 — [gap report](IMP-18-SVG-GAP-REPORT.md) | Phase R' (renderer.py) 회귀 X | soft link: IMP-04 (frame_partials 등록 후 의미 ↑) | documented | -| IMP-19 | I4 zone 비중 분배 | Step 8 | §3 Reference Only | ↓ low | `renderer._group_blocks_by_area` 패턴 reference — zone-level ratio 분배 | Phase O 컨테이너 회귀 X / 직접 통합 X | soft link: IMP-09 (zone 비중 분배 영역 공유) | pending | +| IMP-19 | I4 zone 비중 분배 | Step 8 | §3 Reference Only | ↓ low | `renderer._group_blocks_by_area` 패턴 reference — zone-level ratio 분배 — [reference doc](IMP-19-ZONE-RATIO-REFERENCE.md) | Phase O 컨테이너 회귀 X / 직접 통합 X | soft link: IMP-09 (zone 비중 분배 영역 공유) | documented | | IMP-20 | H2 frame contract validation | Step 10 | §3 Reference Only | ↓ low | `content_verifier.verify_structure` pattern reference — Phase Z frame contract 검증 pattern | Phase Q `REQUIRED_PATTERNS` 값 회귀 X / Phase Z 자체 pattern dict 설계 | soft link: IMP-04 (확장 catalog 적용 시 검증 범위 확대) | pending | > **IMP-15 child issues note (#45–#49)** — IMP-15 (Step 14 visual_check 보강) is the parent row; child sub-axes were tracked as separate Gitea issues and are not given standalone backlog rows. Children: #45 (e9b3d2e), #46 (2827622), #47 (535c484), #48 (614c533), #49 (verification-only). Per INTEGRATION-AUDIT-01 §10.3 footnote option to avoid double-counting under IMP-15.