Files
C.E.L_Slide_test2/docs/architecture/IMP-19-ZONE-RATIO-REFERENCE.md
kyeongmin e60aacc3dc docs(IMP-19): zone ratio reference + cross-link -- documented-axis close
Stage 5 commit for IMP-19 (gitea #19) — docs-only, no runtime surface.

- new: docs/architecture/IMP-19-ZONE-RATIO-REFERENCE.md (header + A1 consumer
  + A2 producer + A3 Phase Z solver delta + A4 IMP-09 boundary + A5
  re-activation gate / GR1-GR4).
- update: PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md — IMP-19 row pending ->
  documented + reference doc link; IMP-09 row carries soft back-link to the
  IMP-19 reference doc.
- update: PHASE-Q-INSIGHT-TO-22STEP-MAP.md §3 I4 row — prepend IMP-19 anchor
  + reference doc link (step/classification preserved).

Guardrails (Stage 1/2 binding contract): src/ untouched, no role-based
["배경","본심"] hardcoding into Phase Z, IMP-09 solver ownership preserved,
soft-link integrity holds. IMP-19 remains dormant until the A5 gate fires.

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

10 KiB

IMP-19 — Phase O/Q Zone Ratio Container Pattern Reference

Status: documented (reference-only, dormant) Scope: doc-only. No runtime surface modified. Related issue: #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-353render_multi_page() — passes layout_concept.get("_container_specs") as container_specs argument (Phase O activation path).
  • src/renderer.py:426render_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-586build_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-510zone_overhead = zone_count * zone_title_h(28) + (zone_count-1) * zone_gap(16).
  • src/space_allocator.py:512-520top_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-853compute_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-972compute_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_dynamictopology=="rows" (horizontal-2): dynamic row heights via compute_zone_layout, static fr column widths via _parse_fr_string.
    • :1155+ _build_grid_dynamic_2dtopology ∈ {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.

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.