Commit Graph

71 Commits

Author SHA1 Message Date
1fb973297f feat(IMP-09): PR 2 — 2-D dynamic dispatch for 5 preset families
Stage 3 lock implementation: extend build_layout_css dispatch beyond
the horizontal-2 / vertical-2 1-D dynamic paths. T / inverted-T /
side-T-left / side-T-right / 2x2 now flow through a 2-D track solver
instead of the fr_default sink, with length-locked heights_px (R) +
widths_px (C) on every return path (default and override).

PR 2 scope (u1~u5):
  - u1: _aggregate_zone_signals_per_track — per-row + per-col virtual
    zones via max(weight) + max(min_height_px) of single-span zones,
    falling back to all-span when a track has none.
  - u2: _build_grid_dynamic_2d default builder — feeds virtual zones
    into compute_zone_layout + compute_zone_layout_cols; emits
    computation="2d_dynamic_aggregated", dynamic_rows=True,
    dynamic_cols=True.
  - u3: _override_to_grid_tracks override builder — single-span
    aggregation (max h per row, max w per col), normalize, multiply
    by avail_h/avail_w, last-element diff absorb; emits
    computation="user_override_geometry"; falls back to u2 when
    total_h or total_w == 0.
  - u4: build_layout_css dispatcher wiring — topology in
    {T, inverted-T, side-T-left, side-T-right, 2x2} routes to
    _build_grid_dynamic_2d (default) or _override_to_grid_tracks
    (override); legacy [override-warning] stderr removed for the
    5 presets; step08 trace gains a 2-D-aware print line that fires
    before the dynamic_rows / dynamic_cols branches.
  - u5: PR 1 lock test test_top_1_bottom_2_fr_default_populates_geometry
    renamed to test_top_1_bottom_2_dynamic_2d_populates_geometry and
    flipped to PR 2 reality (computation="2d_dynamic_aggregated",
    dynamic_rows=True, dynamic_cols=True).

Fixtures: 10 build_layout_css (5 presets × {default, override}) +
5 retry_gate *_dynamic_2d.yaml locking the retry gate skip reason
"dynamic_cols (2-D topology) ... IMP-09 lock" for the 5 presets.

Tests: python -m pytest -q tests = 104 passed (Stage 2 baseline
10 RED → GREEN, 0 regressions). Kei archive
(build_containers_type_b / page_structure) untouched —
rg "build_containers_type_b|page_structure" src/phase_z2_pipeline.py
returns 0 hits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:51:23 +09:00
201099e53b feat(IMP-09): PR 1 — col-axis solver + per-zone geometry mapper + retry gate
Stage 3 round 4 lock implementation: extend build_layout_css beyond
the horizontal-2-only dynamic path. Every layout_css return now
carries length-locked col-axis keys (widths_px, width_ratios,
dynamic_cols) matching the parsed css_areas grid (R rows, C cols),
so 2-D layouts (T / 2x2 in PR 2) and the unified
_compute_per_zone_geometry mapper can plug in without further
contract churn.

PR 1 scope:
  - _parse_css_areas + _parse_fr_string + _compute_per_zone_geometry
    (unified — 1-D and 2-D from the same code path)
  - compute_zone_layout_cols (vertical-2 weight-only solver)
  - _build_fr_default / _build_rows_dynamic / _build_cols_dynamic
    (populate widths_px/heights_px on every return path)
  - build_layout_css override branch keeps the warn-and-fallthrough
    legacy for unsupported presets (PR 2 promotes to strict raise)
  - retry gate in _attempt_zone_ratio_retry skips when dynamic_cols=True
    or dynamic_rows=False, with explicit retry_skipped_reason
  - Step 8 artifact gains zone_widths_px_planned /
    zone_col_ratios_planned (top-level) + zone_width_px_planned /
    zone_col_ratio_planned (per-zone)
  - debug_zones width injection via _compute_per_zone_geometry
    (replaces the legacy row-only zip)

Tests: tests/phase_z2/ — 47 new cases (parse / fr-string / cols solver /
per-zone geometry / build_layout_css contract / retry gate +
6 build_layout_css YAML fixtures + 3 retry_gate fixtures).

Verification: python -m pytest -q tests = 89 passed (was 42).
horizontal-2 grid CSS strings (areas/cols/rows) byte-identical to
legacy; only additive col-axis keys are introduced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:03:23 +09:00
8f6cffc2a7 fix(IMP-08): Stage 5 R2 — aligner force-drill on sub-id override targets
Codex #1 (Stage 5) reproduced a smoke regression on the actual checkout :
when V4 carries the parent exact key (e.g., `04-2`) AND the drag/drop
override targets a sub-id (`primary=04-2-sub-1`), the aligner kept the
parent at parent granularity and emit `['04-1', '04-2']`, so the override
flag failed with `unknown section_id(s) ['04-2-sub-1']`.

Fix : `align_sections_to_v4_granularity` gains an optional
`override_target_section_ids` keyword. From each canonical
`${parent}-sub-N` target it derives the parent id and adds it to a
`force_drill_parents` set. Sections in that set are drilled into
sub-sections regardless of whether V4 carries the parent exact key.
Top-level override targets (no derived parent) do not trigger
force-drill, so backward-compat is preserved for parent-granularity
overrides.

The call site in `run_phase_z2_mvp1` collects sub-ids from
`override_section_assignments` and forwards them to the aligner.

Generalization (RULE 0) :
- Trigger is the override schema (`X-sub-N`), not a specific MDX / section /
  frame id. Applies to all 32-frame MDX uniformly.
- Decision is deterministic on the override target shape, independent of
  V4 yaml content.
- Default (no override) path is unchanged byte-for-byte.

Side fixes (forward-only RULE 1 cleanup, no history rewrite) :
- `align_sections_to_v4_granularity` docstring rewritten in English
  (overwrites the Korean docstring committed in 5191aca).
- Step 9 diagnostic comment quoted-string rewritten in English
  (overwrites `"V4 entry 없음"` committed in a422d72).

Tests : 3 new cases in `test_phase_z2_subsection_schema.py` —
`test_align_parent_v4_exact_keeps_section_when_no_override_targets_sub`
(backward-compat axis), `test_align_force_drills_when_override_targets_sub_id_with_parent_in_v4`
(blocker regression), `test_align_top_level_override_target_does_not_force_drill_other_sections`
(force-drill scope guard). Pytest scope-qualified result :
`test_phase_z2_subsection_schema.py` + `_section_assignment_override.py` +
`_v4_fallback.py` = 40 / 40 PASS.

Smoke (axis = sub-id override -> aligner -> assignment plan, both V4 yaml
shapes) :
- HEAD V4 yaml (`04-1`, `04-2.1`, `04-2.2` only) :
  `--override-section-assignment primary=04-2-sub-1` ->
  `aligned_section_ids=['04-1', '04-2-sub-1', '04-2-sub-2']`,
  `plan[0].assignment_source='cli_override'`,
  `plan[0].source_section_ids=['04-2-sub-1']`.
- V4 yaml with `04-2` exact key (Codex's stress case) : identical
  aligned output and identical assignment plan.

Downstream `composition_planner` abort
(`phase_z_status_not_allowed:extract_matched_zone`) is IMP-05 territory,
unchanged in both shapes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:28:46 +09:00
5191acad85 feat(IMP-08): U2 — aligner canonical sub-id + N-R5 decimal alias guard
align_sections_to_v4_granularity now emits canonical sub-section ids
of the form ${section_id}-sub-${ordinal} (e.g., "04-2-sub-1"), matching
the frontend drag/drop schema. Each drilled sub-section populates
heading_number (decimal "2.1" / integer "1" / None for undecorated)
and v4_alias_keys for legacy V4 keys.

N-R5 decimal-only alias guard : v4_alias_keys is populated only when
heading_number matches re.fullmatch(r"\d+\.\d+", ...). Integer-only
H3 headings (e.g., MDX 05's "### 1", "### 2") and bare H3 headings
produce no alias to avoid sibling-parent V4 collisions (RULE 0
generalization — applies to all 32-frame MDX, not MDX 05-specific).

The drill regex is broadened from r"^###\s+(\d+\.\d+)\s+..." to
r"^###\s+(?:(\d+(?:\.\d+)?)\s+)?(.+?)$" so integer-only and bare H3
headings are now recognised as sub-sections; they previously failed
the regex and were silently kept under the parent section.

Tests : 7 new cases (MdxSection default 4-positional callers, V4 exact
passthrough, decimal drill with alias, integer-only no-alias guard,
bare H3 no-alias, no-H3 passthrough, end-to-end aligner -> resolver
round-trip with legacy V4 alias). 15/15 in test_phase_z2_subsection_schema
+ 14 override + 8 fallback baseline = 37/37 PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:33:49 +09:00
a422d72c0b feat(IMP-08): U1 — schema helper + V4 alias resolver (4 lookup sites)
Adds sub-section schema fields (heading_number / v4_alias_keys /
sub_sections) to MdxSection with defaults so existing 4-positional
constructions remain valid. Introduces _resolve_v4_section_key helper
that resolves a V4 mdx_sections key in exact > alias > None order with
no parent/sibling promotion (axis 7 hybrid lock).

Rewires four runtime V4 lookup sites (lookup_v4_match,
lookup_v4_match_with_fallback, lookup_v4_all_judgments,
lookup_v4_candidates) to accept an optional alias_keys kwarg and go
through the resolver. U1 callers pass empty alias lists so behaviour
is byte-identical to the previous exact-match path; U2 will populate
aliases from MDX heading_number metadata.

Closure callers in run_phase_z2 build section_alias_by_id from
MdxSection.v4_alias_keys and forward into lookup_fn /
candidates_lookup_fn / lookup_v4_all_judgments (Step 7-A trace) and
into _select_template_for_overrides single-section selector.

Step 9 candidate report (post-decision diagnostic) is marked with an
inline English exemption comment per N-R6 — runtime selection goes
through _resolve_v4_section_key, the report path stays a direct
dict-shape lookup to avoid debug_zones schema plumbing.

derive_parent_id now recognises canonical ordinal ids
("03-1-sub-2" -> "03-1") first and keeps the legacy decimal fallback
("04-2.1" -> "04-2") for V4 alias compatibility.

Tests : 8 synthetic cases in tests/test_phase_z2_subsection_schema.py
covering derive_parent_id ordinal/decimal/none and the resolver
exact/alias/no-promote/miss cases. 30/30 PASS combined with the 14
override + 8 fallback baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:28:59 +09:00
52ccb7fc8b fix(IMP-06): Stage 4 blocker-fix — render_records + plan-aware traces
Three Codex #13 blockers in a single coherent commit.

Blocker 1 (units None hazard) — drop None placeholders from `units` list.
Replace with a separate `render_records` layer built AFTER frame_overrides
apply. units = canonical renderable list (list[CompositionUnit] only);
render_records = canonical per-position view including empty / collision-
skipped / cli_override entries. Downstream loops (Step 6 print, frame_
overrides, zones_data/debug_zones, Step 9 application_plan, compute_slide_
status covered loop) no longer need None guards.

Blocker 2 (no integration test) — add end-to-end pipeline integration
test: `--override-section-assignment top=03-2` on sample 03 MDX produces
zones_data[top].source_section_ids = ['03-2'], debug_zones[top].assignment
_source = 'cli_override', debug_zones[bottom].v4_template_id = '__empty__'
(override_collision whole-skip), step20 filtered_section_ids contains
'03-1', and filtered_section_reasons carries a section_assignment_override
_uncovered entry. Proves the render path — not only comp_debug — reflects
the CLI override.

Blocker 3 (Step 9/20 not plan-aware) — surface plan-aware additive fields
in both render-path debug_zones/zones_data and Step 9 application_plan
units: position, assignment_source, section_assignment_override,
replaced_auto_unit, skipped_collided_auto_units, uncovered_section_ids,
skipped_reason. compute_slide_status appends Codex #10 Catch O list-shaped
filtered_section_reasons entries for override-uncovered sections and
folds them into filtered_section_ids so full_coverage is re-evaluated
post-override.

Exact-id-only collision semantics enforced (Codex #14/#15/#16/#17): S3
and S3-1 are distinct ids; no prefix hierarchy, no parent cascade. Three
new section-id invariant tests added (parent-like vs child-like, exact
duplicate collision detected, distinct ids coexist).

Test : 24 pytest pass (9 helper + 9 case + 3 invariant + 1 case 9b +
1 integration + 1 from v4_fallback baseline) ; smoke 11/11 PASS.

Register `integration` pytest marker in pyproject.toml.
2026-05-14 07:41:12 +09:00
1f15495117 feat(IMP-06): Stage 4 Part 2 — render-path integration (units rebuild + empty zone + Catch K fix)
Refs #6

After `position_assignment_plan` is built, rebuild the `units` list to be
plan-aligned so downstream `zones_data` / `debug_zones` / mapper / render
all see the post-override sequence. This resolves the long-standing
trace-only gap and closes Codex Catch K naturally because the helper now
actually drives downstream materialization.

- run_phase_z2_mvp1: after `_build_position_assignment_plan`, rebuild
  `units` ordered by `position_assignment_plan`. `cli_override` entries
  synthesize a CompositionUnit (resolved template_id + concatenated
  section raw_content + contract frame_id + selection_path="cli_override"
  + override audit in rationale). `auto` entries reuse the original
  planner unit. empty/collision-skipped entries become None placeholders
  so the downstream zone loop can emit an explicit empty zone record
  without distorting layout allocation.
- zones_data / debug_zones loop: handle `unit is None` by appending an
  explicit empty record with template_id="__empty__", content_weight=0,
  min_height_px=0, plus the plan's skipped_reason / replaced_auto_unit /
  skipped_collided_auto_units / uncovered_section_ids audit fields.
- partial render loop: `template_id == "__empty__"` short-circuits to
  `partial_html = ""` so the slide_base zones loop preserves grid identity
  without raising TemplateNotFound.
- Update the helper-invocation comment so it now describes the actual
  Part 2 behavior (units rebuild + empty zone handling). Catch K is no
  longer a future-tense placeholder.

Stage 4 Part 3 (follow-up commit) will add: Step 9 application_plan
plan-aware additive fields, Step 20 list-shaped filtered_section_reasons
entries for override-uncovered sections (Codex #10 Catch O schema), and
integration tests proving zones_data["top"] actually contains the
overridden section ids when --override-section-assignment is supplied.

Regression: 20/20 unit tests pass (9 IMP-06 helper + 8 IMP-05 fallback +
2 catalog invariant + 1 dedicated replaced_auto_unit test), smoke
self-check 11/11 (IMP-04 F17 calibration intact).

No AI, no calculate_fit, no full planner rerun, no frontend, no sample
hardcoding. plan_composition() signature preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 06:50:35 +09:00
b81e564f65 feat(IMP-06): Stage 4 part 1 — replaced_auto_unit field + comment fix
Refs #6

Stage 4 split per Codex #10 acceptance: this commit lands the schema +
trace refinements required before the render-path rewiring. The actual
units/zones_data/Step 9/Step 20 plan-driven materialization remains in
Stage 4 part 2 (follow-up commit) so each commit is reviewable on its
own and regression-safe.

- _build_position_assignment_plan: add replaced_auto_unit field. Populated
  only when the explicitly overridden position already held an auto unit
  AND that auto unit had different source_section_ids than the override.
  Documents a same-position override replacement as a distinct audit fact,
  separate from skipped_collided_auto_units which captures cross-position
  whole-skips per the locked collision policy.
- Backfill replaced_auto_unit = None on the empty/collision/auto branches
  for schema-stable consumers.
- Update the override-application comment near the helper invocation so it
  no longer claims the helper "reorders units"; Stage 4 part 2 will be the
  commit that wires the plan into the actual render path.
- Helper unit tests: assert replaced_auto_unit shape in the collision
  scenario and add a dedicated case that distinguishes same-sections
  (template swap via --override-frame -> None) from different-sections
  same-position replacement (populated, reason="same_position_override_replacement").

No AI, no calculate_fit, no full planner rerun, no frontend, no sample
hardcoding. plan_composition() signature preserved. helper remains pure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 06:10:43 +09:00
d596fabde0 feat(IMP-06): zone-section assignment override CLI + plan helper (trace-only)
Refs #6

Backend / CLI / composition path only — frontend bridge remains #38.

- Add `--override-section-assignment ZONE_ID=section_id[,section_id]` to the
  Phase Z entry parser. Parse-time hard errors for malformed payloads, empty
  zone id, empty section list, duplicate zone id, and duplicate section across
  zones (a section may belong to at most one zone).
- Add `_build_position_assignment_plan` helper (pure function, resolved
  `positions` injected). Builds a per-position assignment plan with the
  Codex-locked template_id ladder: (1) `--override-frame` exact unit_id wins,
  (2) exact existing auto unit reuse, (3) single-section direct-executable V4
  selector via `lookup_v4_match_with_fallback(..., raw_content=section.raw_content)`,
  (4) ad-hoc multi-section override without exact auto + without explicit
  override-frame yields `skipped_reason='ad_hoc_merged_no_template'`.
- Lock the collision policy: explicit override wins per position, sections
  appear in at most one position, overlapping auto units are skipped whole
  (no split, no cascade, no replan), uncovered sections from the previous
  same-position auto unit are recorded in `uncovered_section_ids`.
- Additive trace fields on each plan entry: `previous_source_section_ids`,
  `skipped_collided_auto_units`, `uncovered_section_ids`, `v4_selector_trace`,
  `section_assignment_override`. Top-level `comp_debug["section_assignment_plan"]`
  + `comp_debug["section_assignment_summary"]` so Step 9 / debug artifacts can
  derive from a single source of truth.
- Wire `run_phase_z2_mvp1(override_section_assignments=...)` after final layout
  preset resolution: validate ZONE_IDs against active layout positions and
  validate section_ids against aligned sections (fail-fast). The plan is
  attached to `comp_debug` for downstream artifacts. Actual `zones_data` /
  unit-list rewiring is deferred to a follow-up commit so this change stays
  regression-safe; trace artifacts already surface override intent and
  collision impact.
- Add 9 helper unit tests with fully synthetic MOCK_ ids (no real catalog
  / no v4_full32_result.yaml): non-conflicting auto retention, collision
  whole-skip + uncovered tracing, template ladder steps 1/2/4, unit_id
  naming convention, previous_source_section_ids position history,
  empty-position case, summary aggregation invariants.

No AI, no `calculate_fit`, no full planner rerun, no frontend, no sample
hardcoding, no `restructure`/`reject` silent promotion. `plan_composition()`
signature is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 01:51:20 +09:00
21476ae000 fix(IMP-05): complete V4 fallback evidence and dedup qualifiers
Refs #5

- Add runtime template_id dedup in lookup_v4_match_with_fallback with
  first-occurrence reservation; duplicate ranks become audit evidence,
  not new fallback candidates.
- Add Step 9 candidate_evidence as the primary per-unit evidence field
  while keeping fallback_chain as a compat alias for legacy readers.
- Add Step 20 fallback_selection_count and selection_paths derived from
  comp_debug.v4_fallback_summary with defensive defaults; top-level
  overall enum unchanged.
- Tighten synthetic fallback tests for duplicate handling (rank-1 reject A
  + rank-2 use_as_is A + rank-3 distinct B → rank-3 wins) and add tests
  for candidate_evidence + alias equality and Step 20 qualifier presence
  with defensive defaults.
- Verify with pytest (10 passed) and smoke_frame_render --self-check
  (11/11 partials, IMP-04 F17 calibration intact).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:59:49 +09:00
15c5b9ae00 IMP-05 deterministic V4 candidate bridge — pre-render rank-2/3 fallback + trace schema + dedup invariant test
round 55~73 review-loop lock per Codex #11 final + Claude #13 6-axis L1~L9.

Scope (deterministic only) :
- pre-render rank-2/3 fallback via lookup_v4_match_with_fallback (selector only,
  no calculate_fit migration, no AI, no full planner rerun, no layout topology change,
  no abort behavior change)
- Step 9 informative candidate_evidence schema (additive) — v4_label / phase_z_status
  / catalog_registered / filtered_for_direct_execution / route_hint / decision / reason
- Step 20 qualifier fields (additive) — fallback_used / fallback_selection_count
  / selection_paths[] — top-level enum unchanged
- restructure / reject candidates preserved as non-direct evidence with route hints
  (design_reference_only / ai_adaptation_required) — deferred actual handlers IMP-29/IMP-31
- catalog 1:1 invariant test (separate file tests/test_catalog_invariant.py) —
  fails fast if template_id/frame_id 1:1 mapping ever breaks
- 6 behavior tests fully synthetic with MOCK_ prefix (no real catalog IDs,
  no v4_full32_result.yaml dependency) — monkeypatch get_contract +
  compute_capacity_fit (selector has no DI, function signature unchanged)

Deferred to follow-up issues :
- IMP-30 first-render invariant + abort bypass (zero-unit + section status filter)
- IMP-29 frontend zone-level override (deterministic only)
- IMP-31 AI-assisted frame-aware adaptation

Guardrails locked : no calculate_fit / no AI / no frontend / no full rerun /
no layout topology / no abort behavior change / no 1-2 sample hardcoding.

Tests : 8/8 pass (6 selector behavior + 2 catalog invariant).
Smoke regression : 11/11 partials pass (IMP-04 F17 calibration intact).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:06:39 +09:00
73a98b8ad1 IMP-04 F17 schema correction — paired_rows_4x2 + pill alternation + source-faithful theme
source = 8 atomic issues (4 paired rows × 2 cells per texts.md), 이전 strict-4
가 source 의 절반 누락. round 55~73 review-loop 의 calibration frame.

- contract : source_shape=top_bullets / layout_variant=paired_rows_4x2_alternating_pills
  / strict 8 (no pad/truncate) / role_order row_{1..4}_{left,right} / visual_hints
  pill_positions + row_gap_after / builder paired_rows_4x2_slots
- builder : new _build_paired_rows_4x2_slots — 2-axis (row × side) deterministic
  index mapping, strict 8 raises before render, quadrant_item parser 재사용
- partial : 4-row × 2-cell flex, pill alternation (row 1/3 top, row 2/4 bottom
  via column-reverse), row 2-3 visual gap, source-faithful color (rgb(204,82,0)
  →rgb(136,55,0) title + #60A451 row border + rgba(250,237,203,0.15) bg + #0c271e
  body + 2px dashed #60A451 cell 분할선), pill = CSS approximation (asset crop
  variant single-pass 비용 高 → fallback per Codex round 62/68 scope cap, pill
  shape + alternation + green/cream/brown theme 보존), no row headers (source
  부재, inference 금지)
- fixture : flat 8 top-bullet (texts.md 8 issues 그대로)
- smoke + R3 : PASS (11/11 self-check, 5535 chars partial, 8 units rendered,
  pill alternation 정합, row 2-3 gap, no invented row headers)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:13:46 +09:00
f7a9240fe5 fix(IMP-04): F18 F1 follow-ups — defaults + narrow alias + cardinality clarify
Same-frame F1 follow-up per Codex round 43 (#15527). matrix §4.1 Fix 7
4-class F1 path (no Track A pause, small fixes + Codex re-review).

Three fixes :

1. F1-a — explicit col_a/col_b label defaults
   - Previous (c7b0f5b) used empty defaults `col_a_label_default: ""` /
     `col_b_label_default: ""`, so an upstream MDX path without explicit
     column headers would render blank header cells.
   - Fix : set `col_a_label_default: "BIM"` and `col_b_label_default: "DX"`
     in F18 contract. Frame intent is the BIM-vs-DX comparison, so the
     headers are semantic and must not silently become blank.

2. F1-b — narrow prefix-stripping aliases (parser → builder option)
   - Previous parser used a broad regex
     `^[A-Za-z가-힣]{1,8}\s*:\s*(.+)$` to strip any short prefix before
     `:`. That could accidentally remove meaningful Korean/English
     prefixes from real cell content.
   - Fix : remove auto-stripping from `parse_compare_row_2col_item`.
     Stripping is now configurable via builder option
     `strip_col_prefix_aliases: [<list of exact aliases>]` and applied
     by `_build_compare_table_2col`. F18 contract uses `["BIM", "DX"]`,
     so only `BIM:` / `DX:` (with optional fullwidth `:`) prefixes are
     stripped; other Korean/English colons in real content stay intact.
   - Parser signature unchanged. Builder is the single place that owns
     the stripping policy.

3. F1-c — cardinality semantic clarification
   - Previous top-level `cardinality.strict: 2` was ambiguous: it could
     be read as `row strict: 2`. Rows are actually `1..12` via
     `sub_zones.rows.cardinality`.
   - Fix : add YAML comment that the top-level strict 2 = column count
     (col_a / col_b), not row count. Per-sub_zone cardinality remains
     authoritative for rows.

Verification :
- python -m py_compile src/phase_z2_mapper.py : PASS
- python scripts/smoke_frame_render.py --self-check : PASS 7/7 (F18
  fixture rendered unchanged at 4211 chars; smoke harness only loaded
  the partial, builder/parser logic not directly exercised in smoke)
- Manual builder/parser invocation test with synthetic units :
  - col_a_label / col_b_label resolve to "BIM" / "DX" defaults.
  - `BIM: Only 3D` → `Only 3D` (alias-stripped).
  - `DX: BIM << DX (ENG. 포함)` → `BIM << DX (ENG. 포함)` (alias-stripped).
  - `분야별 단절` → `분야별 단절` (no BIM/DX prefix, untouched).
  - This matches the F1-b narrow-alias intent.
- python scripts/smoke_frame_render.py bim_dx_comparison_table
  --render-to data/runs/imp04_f18_visual_r2 : PASS, R3 artifact written
  with same character count, generic viewer title.

scope-lock honored : no V4 logic, no new builder/parser added (only
behavior refinement of existing F18 builder/parser/contract), no other
partial, no Phase Z production render, no Phase R'/AI/Kei changes.

4-class status (F18 post-F1) :
- class 1 readiness : adapter cleanup complete — defaults explicit,
  aliases narrow, cardinality semantically clear.
- class 2 content-fit : still a watch item (long Korean wrapping,
  6+ rows). max_rows=12 protection unchanged.
- class 3 / 4 : N/A.

Refs Gitea #4 (IMP-04 Track A frame 4 — F1 follow-up per Codex round 43)
2026-05-13 12:21:02 +09:00
c7b0f5bde1 feat(catalog): activate bim_dx_comparison_table (IMP-04 Track A 4/16)
Reason : V4 UAI=1 (01-2 "용어간 상호관계") — UAI tier strongest after F12/F11.
Track A frame 4 per Codex round 41 V4-priority acceptance.

3-layer architecture (matrix §0) :
- V4 = matching authority — V4 ranked this frame use_as_is for 01-2.
- figma_to_html (1171281195) = source/evidence — analysis/texts/index.html/
  flat/assets all present.
- Phase Z = runtime orchestration — adds catalog + new builder + new parser +
  new partial + smoke fixture.

NEW builder + NEW parser (Codex round 41 mandatory review path) :

1. src/phase_z2_mapper.py — NEW `compare_row_2col_item` parser in ITEM_PARSERS
   - input : (top_line, nested_lines)
   - output : {label, col_a, col_b}
   - label = bold from top_line
   - col_a / col_b = first 2 nested bullets, optional prefix stripping ("BIM:"/
     "DX:" or similar ≤8-char tag with colon)
   - inline emphasis preserved as <strong>

2. src/phase_z2_mapper.py — NEW `compare_table_2col` PAYLOAD_BUILDERS entry
   - payload : title + col_a_label + col_b_label + rows[]
   - builder_options : item_parser, col_a/b_label_default, max_rows (default 999)
   - max_rows truncation tracked via _truncated_count

3. templates/phase_z2/families/bim_dx_comparison_table.html — NEW partial
   - 3-column grid (category / col_a / col_b) with header row + N data rows
   - PROMOTED CSS : title gradient (#000#883700, zone-title family), header
     brown bg (rgba(50,31,9,0.85-0.95)), zebra striping, brown family bullet
     accent, subtle border (#A5BBB4 F11 family).
   - NOT PROMOTED (P1 case-by-case + preservation guardrail) : Figma column
     header raster icons, color emphasis variants, hanja deco. figma_to_html
     source evidence remains preserved.
   - ADAPTED : Figma absolute positioning + zoom → Phase Z flex/grid 3-col
     table, typography → token-fixed, row heights auto content-fit.

4. templates/phase_z2/catalog/frame_contracts.yaml — F18 contract appended
   - frame_id=1171281195, family=table, source_shape=top_bullets, strict 2
     (2 columns), role_order=[col_a, col_b].
   - visual_hints.min_height_px = 350 (title 30 + header 30 + 6 rows×35 +
     padding 30 = 300 + 50 buffer; F14-class).
   - accepted_content_types = [text_block].
   - sub_zones : col_a_header / col_b_header (strict 1 each) + rows (min 1,
     max 12 category rows).

5. scripts/smoke_frame_render.py — bundled fixture for F18 self-check (6
   category rows : 범위 / S/W / 프로세스 / 성과물 / 활용 / 수행개념).

Verification :
- python -m py_compile src/phase_z2_mapper.py scripts/smoke_frame_render.py
  : PASS
- python scripts/smoke_frame_render.py --self-check : PASS 7/7 (F18 added
  at 4211 chars CSS-only)
- python scripts/smoke_frame_render.py bim_dx_comparison_table --render-to
  data/runs/imp04_f18_visual : PASS, R3 artifact, 0 raster refs (CSS-only)
- python run_mdx03_pipeline.py --phase-z2 --run-id imp04_f18_regression :
  PASS (MDX 03 V4 rank-1 still F13/F29; F18 only routes 01-2 per V4)

scope-lock honored (3-layer + 4-class) :
- V4 logic / V4 evidence yaml : unchanged
- Existing PAYLOAD_BUILDERS (4 builders) : unchanged. compare_table_2col added
  as NEW entry.
- Existing ITEM_PARSERS (2 parsers) : unchanged. compare_row_2col_item added
  as NEW entry.
- Existing 6 partials : unchanged.
- Composition planner / production render / Phase R' / AI/Kei : unchanged.

4-class status :
- class 1 readiness :  contract + new builder + new parser + partial +
  smoke fixture + R3 artifact aligned.
- class 2 content-fit : watch — cell content single-line; long Korean
  sentences may wrap. Row height auto handles wrap; max_rows=12 limit
  protects vertical overflow.
- class 3/4 : N/A.

Codex review mandatory per scope-lock §5 (new builder pattern first
introduction : compare_table_2col).

Refs Gitea #4 (IMP-04 Track A frame 4 — V4 UAI tier, NEW builder)
2026-05-13 12:13:11 +09:00
c67609c083 feat(catalog): activate construction_goals_three_circle_intersection (IMP-04 Track A 2/16)
Reason : V4 strongest UAI tier candidate (use_as_is=1 for 02-1, light_edit=1
for 01-1, restructure=1). Track A frame 2 per Codex rounds 30/33/35 V4-
priority rule. F14 clean pass completed at 834ed39; this is the next
Track A activation.

3-layer architecture context (matrix §0) :
- V4 = matching authority — V4 ranked this frame as use_as_is for the
  "DX의 궁극적 목표" section (02-1) and light_edit for "용어 정의" (01-1).
- figma_to_html (1171281189) = rich source/evidence — 510-line index.html
  base, full analysis/flat/texts/assets present (A+T+I+F+S).
- Phase Z = runtime orchestration — this commit adds the runtime contract,
  builder, partial, and fixture so the V4 candidate can be assembled.

New runtime additions :

1. src/phase_z2_mapper.py — new `cycle_intersect_3` PAYLOAD_BUILDERS entry
   - Reuses existing `quadrant_item` ITEM_PARSERS (label only, body
     ignored) — F16 parser reused, no new parser.
   - Produces flat keys : circle_1_label / circle_2_label / circle_3_label
     + intersection text (optional) — distinct from F16's quadrant_N_body
     structure since this frame's 3 main circles use labels only.
   - pad_to=3, truncate_at=3, configurable via builder_options.

2. templates/phase_z2/families/construction_goals_three_circle_intersection.html
   - Adapted from figma_to_html_agent/blocks/1171281189/index.html.
   - Slot mapping : title + 3 circle labels + optional intersection text.
   - PROMOTED CSS : 3 circle gradients (safety #BC652B/#A24200, productivity
     #897445/#3E3523, trust #296B55/#123328) + outer multiply blend +
     title gradient (#000#883700, F13/F14 zone-title family) + main
     label typography (white text + shadow).
   - NOT PROMOTED (P1 case-by-case, compact zone fit) : 6 accent hanja
     circles (安/質/速/利/通/信), 6 side labels (안전성 제고 etc.), 3
     decoration rects, 3 arc images, bg-texture multiply image. These
     are Figma-side decorative content not in MDX and would clutter a
     Phase Z zone of ~320 px.
   - ADAPTED : Figma 70/50/40 px → token-fixed font sizes, 350×350
     absolute-positioned overlapping circles → 110×110 flex row (cycle
     intent expressed via intersection text instead of geometric overlap).

3. templates/phase_z2/catalog/frame_contracts.yaml — append F12 contract
   - template_id, frame_id 1171281189, family=diagram, source_shape=
     top_bullets, strict cardinality 3, role_order [safety, productivity,
     trust].
   - visual_hints.min_height_px = 320, derived from 3 circle row 80 +
     title 30 + label area 60 + intersection 30 + padding 40 = 240
     + 80 safety buffer (lighter than F14's 350 since CSS-only).
   - accepted_content_types = [text_block] only.
   - 4 sub_zones declared (circle_1/2/3 main_text + intersection emphasis).

4. scripts/smoke_frame_render.py — add bundled fixture for F12 self-check.

Verification :
- python -m py_compile src/phase_z2_mapper.py scripts/smoke_frame_render.py
  : PASS
- python scripts/smoke_frame_render.py --self-check : PASS 5/5 (F12 added
  at 3691 chars CSS-only)
- python scripts/smoke_frame_render.py construction_goals_three_circle_intersection
  --render-to data/runs/imp04_f12_visual : PASS, R3 artifact written. 0
  raster refs (CSS-only partial); copy_assets ran successfully and
  produced data/runs/imp04_f12_visual/assets/construction_goals_three_circle_intersection/
  with the frame's 4 PNG files (unused since partial is CSS-only — assets
  remain available for future raster promotion if visual inspection
  flags fidelity loss).
- python run_mdx03_pipeline.py --phase-z2 --run-id imp04_f12_regression
  : PASS (MDX 03 V4 rank-1 still F13/F29, F12 not selected — F12 only
  triggered by 02-1 / 01-1 sections per V4 evidence)

scope-lock honored : V4 logic / V4 evidence / mapper existing builders /
composition planner / Phase R' / pipeline production render path / AI/Kei
all unchanged. New builder added without modifying existing 3 (mixed
strategy per scope-lock §4).

Calibration status (matrix §4.1 Fix 7 4-class) :
- class 1 adapter readiness : new builder registered, partial loadable,
  contract valid, smoke passing.
- class 2 content-fit : compact 110×110 circles + label, watch for label
  overflow if MDX bullets exceed ~12 chars.
- class 3/4 mapping/routing : not applicable for this commit.
- Codex review mandatory per scope-lock §5 (new builder pattern
  cycle_intersect_3 first introduction).

Refs Gitea #4 (IMP-04 Track A frame 2 — V4 strongest UAI tier)
2026-05-13 11:50:44 +09:00
fc3f7d8826 feat(step2+step3): slide-level rich ContentObject trace (IMP-03 #3)
- Add extract_rich_content_objects(normalized_assets, mdx_id) in
  phase_z2_content_extractor.py emitting slide-level rich ContentObjects
  for SPEC v1 §1.2 types: details (popups), image, table
- Extend ContentObject dataclass with optional scope/mdx_id/section_id
  metadata fields (additive, default None — v0 unchanged)
- _stage0_chained_adapter() returns 5-tuple adding normalized_assets
  ({popups, images, tables}); empty on env=OFF / hard fallback
- Step 2 artifact gains additive stage0_normalized_assets nested field
  (env=OFF / fallback → empty lists). Existing 7 fields preserved.
- Step 3 emits root-level rich_content_objects once at slide scope
  with rich_content_objects_enabled / scope / source / disabled_reason /
  skips / invariant_warnings. per_zone list still references v0 only.
- PHASE_Z_STEP3_RICH_OBJECTS_ENABLED env flag, default OFF (canary,
  matches PHASE_Z_STAGE0_ADAPTER_ENABLED / PHASE_Z_B4_*). Enable
  requires flag=1 AND non-empty normalized_assets; otherwise records
  disabled_reason = FLAG_OFF or NO_NORMALIZED_ASSETS.
- transform_table dedup: arrow glyph detection in normalized table
  rows/headers → skip with reason=skipped_transform_table_duplicate.
  v0 _capture_3col_transform_table remains the sole transform_table
  source; generic table only for non-transform tables.
- ID pattern {mdx_id}.{details,image,table}-N (slide-level namespace).
- plan_placement() input unchanged (v0 content_objects only) — rich
  list never feeds placement/region planning in this issue.
- self-test extended with 5 rich extractor cases (popup/image/table
  /arrow-skip/empty); v0 self-test unchanged and still PASS.
- mapper / V4 / composition / Step 6+ / AI/Kei / pipeline_path_connected
  unchanged. trace fidelity only.

env OFF + rich OFF: legacy PASS, no regression
env OFF + rich=1   : disabled_reason=NO_NORMALIZED_ASSETS, rich list empty
env=1   + rich=1   : Step 2 stage0_normalized_assets populated (1 table on
                     MDX 03, invariant match adapter_counts). Step 3 write
                     blocked by inherited IMP-02 composition_planner abort
                     (downstream gap, not IMP-03 scope).

Refs Gitea #3 (IMP-03 A-1 popup/image/table trace)
2026-05-13 01:18:25 +09:00
bac13c09c4 feat(step2): chained adapter for Stage 0 normalize (IMP-02 #2)
- Add _stage0_chained_adapter() helper near parse_mdx() chaining
  mdx_normalizer.normalize_mdx_content + section_parser.extract_major_sections
  + section_parser.extract_conclusion_text → reconstructed MdxSection list
- PHASE_Z_STAGE0_ADAPTER_ENABLED env flag, default OFF (canary, matches
  PHASE_Z_B4_SOURCE_SHAPE_ENABLED / PHASE_Z_B4_GATEKEEPER pattern)
- Section ID reconstruction priority: raw_heading (pre-scan raw MDX
  `## N. Title` heading → title→num map lookup) → raw_heading_inline
  → order_fallback. All paths logged in id_reconstruction_log
- 5 hard fallback enums: ADAPTER_EXCEPTION / NO_USABLE_SECTIONS /
  MISSING_INVALID_IDS / DUPLICATE_IDS / NON_POSITIVE_SECTION_NUM
- Additive step02_normalized.json field stage0_adapter_diagnostics
  (enabled / used / fallback_reason / id_reconstruction_log /
  adapter_counts / diff_vs_legacy / legacy_counts)
- Preserve Step 2 existing 7 fields (slide_title / slide_footer /
  sections_count / sections / orphans / details) — additive only
- Defer mdx_normalizer / section_parser imports so legacy default-OFF
  path does not depend on those modules
- V4 / align_sections_to_v4_granularity / composition / AI/Kei /
  frame selection / status semantics unchanged

env OFF: legacy path PASS on MDX 03, no regression
env=1 : adapter path activates, IDs 03-1/03-2, raw_heading reuse
        triggered, downstream composition_planner abort surfaces
        as canary finding (out of scope for IMP-02)

Refs Gitea #2 (IMP-02 A-1 Stage 0 normalize chained adapter)
2026-05-13 00:22:57 +09:00
1dc81e0692 feat(step14+step21): add zone_geometries_px artifact (IMP-01 #1)
- Add slide-relative bbox export of .zone elements via getBoundingClientRect
- Inline Selenium JS collects zone_geometries_px = [{position, template_id, x, y, w, h}]
- write_debug_json adds top-level additive zone_geometries_px field
- Existing visual_runtime_check / zones / frame_slot_metrics / PASS/FAIL logic unchanged
- AI/Kei/V4/frame selection paths not touched

Refs Gitea #1 (IMP-01 A-6 Zone DOM 좌표 export)
2026-05-12 22:20:30 +09:00
b56fd20ae5 feat: add Phase Z override CLI and trace support
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:06:06 +09:00
85c680f02a docs + V4 catalog + samples + Phase Q legacy 보존
전체 26 files (20 추가 + 6 수정), 10507 insertions.

Phase Z 문서 :
- docs/architecture/PHASE-Z-CHANGE-LOG.md (신설) — axis-by-axis 의사결정 history
  (newest-on-top). Step 7-A 부터 6 entry 박힘 + 2026-05-08 / 2026-05-08 #2
  (compat 매트릭스 폐기 / 6-B 폐기 / F14 표현 정정 / label gate policy 분리).
- docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md (수정) — Step 5/6/9 Gap note
  append (구조 무변, append-only). 6-B 폐기 사실 + Refinement F.
- docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md (수정) — snapshot date
  2026-05-08 갱신. §3 핵심 missing item 5 (Step 5/6/9 boundary axis breakdown
  + 폐기 기록). §6 한 줄 갱신 — 다음 axis 후보 A~F.

Project root docs :
- PLAN.md / PROGRESS.md / README.md (수정) — 토큰 체계 / 폴더 구조 / 설계 문서 /
  역할 분리 반영.
- IMPROVEMENT-REDESIGN.md (신설) — Phase Z 설계 핵심 문서.
- PROCESS_OVERVIEW.html (신설) — 파이프라인 개요 시각.
- docs/tasks/* (신설) — Phase Z task 문서.

V4 catalog (Phase Z runtime 필수 의존성) :
- tests/matching/v4_full32_result.yaml (신설, 4888 줄) — V4 매칭 결과 32 frame
  × 10 MDX section. lookup_v4_match() / lookup_v4_candidates() 가 본 파일 read.
  Phase Z runtime 이 *없으면 즉시 abort* — clone 후 즉시 동작 가능 보장.

Samples :
- samples/mdx_batch/04.mdx (신설) — MDX04 기본 sample.
- samples/mdx/04. DX 지연 요인.mdx (신설) — MDX04 원본.

Phase Q legacy 보존 (별 axis "Phase Q audit & salvage" 영역) :
- src/block_matcher_tfidf.py / catalog_blocks.py / frame_extractor.py /
  pipeline_v2.py — Phase Q (옛 파이프라인) src 신규 untracked 파일들.
  Phase Z runtime 와 의존성 0. Phase Q audit axis 에서 검토 예정.
- scripts/eval_block_matcher.py / fetch_all_frame_screenshots.py /
  match_17_units_my_matcher.py / match_mdx_strict.py / match_mdx_to_frames_tfidf.py /
  ocr_augment_texts.py / run_pipeline_v2.py / previews/ — Phase Q 작업 시
  사용한 옛 script. 같이 보존.
- run_mdx03_pipeline.py (수정) — Phase Q 진입점 (no flag) + Phase Z 진입점
  (--phase-z2 flag) 동시 wrapper. Phase Z 만 사용 시 `python -m
  src.phase_z2_pipeline samples/mdx_batch/03.mdx <run_id>` 직접 호출.

비-scope :
- tests/matching/ (v4_full32_result.yaml 외 ~63MB) — V4 진화 history /
  reports / DECK / ATTACH. Phase Q audit axis 에서 검토.
- tests/pipeline/ (~15MB) — pipeline data. Phase Q audit 영역.
- templates/catalog/blocks.yaml — 옛 block catalog. Phase Q audit.
- templates/phase_z2/frames/ — 옛 frame partial 위치. Phase Q audit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:47:58 +09:00
ec83405770 phase z pipeline: Step 5 / 6-A / 7-conn / 8-conn / Step 9 v0 axis 박힘
사용자 lock 2026-05-08 — Step 5/6/9 boundary reframe.
V4 가 frame 선택, Step 6 은 V4 rank-1 default 전사, Step 9 는 application_plan
번역. compat 매트릭스 안 폐기.

src/phase_z2_pipeline.py 변경 :
- lookup_v4_candidates(v4, section_id, max_n=6) 추가 — V4 non-reject max-6
  후보 list. raw 32 entry 는 v4_full32_result.yaml 영속, step05 = 정제 list.
  lookup_v4_match() (rank-1) 유지 — Step 6 backward compat. (Step 5 보완)
- step05_v4_evidence.json schema 확장 — evidence_per_section[i] =
  {section_id, v4_candidates: [...], candidate_status: "ok" |
  "no_non_reject_v4_candidate"}. (Step 5 보완)
- candidates_lookup_fn 정의 + plan_composition() 에 주입.
  V4 raw dict 는 composition module 안 봄 — fn injection. (Step 6-A)
- Step 6 artifact 의 selected_units[i] 에 v4_candidates 필드 추가. (Step 6-A)
- step07_layout.json data 에 unit_count + layout_candidates 필드 추가
  (Step 7-B 의 select_layout_candidates 결과). step07_selected_layout.html
  에 Layout Candidates 섹션 추가 (default / alternative / selected badge).
  (Step 7-conn)
- step08_zone_region_ratios.json 의 per_zone_plan[i] 에 region_layout_candidates
  + display_strategy_candidates 필드 추가 (Step 8-B-1/2 후보 함수 호출).
  step8_conn_placeholder_signals 명시 — Step 3/4 부재 종속 placeholder
  (region_count=1, content_type="text_block"). step08 HTML 에 candidates pill +
  placeholder caveat. (Step 8-conn)
- APPLICATION_MODE_BY_V4_LABEL 상수 추가 — V4 label → application_mode 변환.
- Step 9 v0 artifact block 신설 (~180 줄) — step09_application_plan.json +
  .html. unit 별 layout_candidates / region_layout_candidates /
  display_strategy_candidates / v4_candidates / candidate_status /
  application_status / current_default_candidate / application_candidates 박힘.
  invariant 5 가지 자연 만족 (status.md §4). (Step 9 v0)

src/phase_z2_composition.py 변경 :
- CompositionUnit 에 v4_candidates: list field 추가 (additive, logic 무변).
  duck typed (V4Match-shape) — circular import 회피. (Step 6-A)
- collect_candidates() 에 v4_candidates_lookup_fn 인자 추가. 3 분기
  (single / parent_merged / parent_merged_inferred) 에서 v4_candidates 채움.
  (Step 6-A)
- plan_composition() 도 v4_candidates_lookup_fn pass-through. (Step 6-A)
- stale "pipeline 호출처 X" 주석 정정 (3 곳: select_layout_candidates /
  select_region_layout_candidates / select_display_strategy_candidates +
  catalog header 2 곳). Step 7-conn / 8-conn 으로 호출처 박혀 *호출처 X* 사실
  위배. (cleanup-1)

axis 닫힘 : Step 5 보완 + 6-A + 7-conn + 8-conn + Step 9 v0 + cleanup-1.
폐기 : 6-B (frame ownership transfer — misframed axis. PHASE-Z-CHANGE-LOG.md
2026-05-08 #2 entry 참조).

regression 0 : MDX03 fresh run 검증 — final.html / step10/12/13/20 byte-동일.
schema 확장만 (step05/06/07/08/09).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:47:11 +09:00
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
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
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
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
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
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
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
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
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
05d43a7999 Refine README and detail preview layout 2026-04-15 17:59:46 +09:00
66c00924ed Add Type B slide pipeline and recipe rendering updates 2026-04-15 16:39:50 +09:00
076aeb0403 Type B'' 추가: 참고 이미지 스타일 (색상바+여백, border 없음)
- block_assembler_b2.py: B'' 전용 조립 함수 (별도 파일)
- 상단: 색상 바 제목 + 소제목(accent 색상) + 불릿(들여쓰기)
- 하단: 색상 바 제목 + 표(있으면) + 불릿
- border/gradient 박스 없음, 여백과 폰트로 구분
- 이제부터 스타일 세부 조정은 하나씩 반영 예정

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:51:10 +09:00
3d1194a562 03번 들여쓰기 계층 적용: D1=bold 1단 들여쓰기, D2=불릿 2단 들여쓰기
- block_assembler B': D1(<strong>) → padding 0.5x, bold, 불릿 없음
- block_assembler B': D2(일반) → padding 1x, 불릿, 폰트 -2px
- 남은 문제: 상단/하단 콘텐츠 잘림 (공간 부족)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:26:11 +09:00
cba2ec2be7 03번 B' 개선: 카드 flex:1 균등 + 결론 텍스트 중복 제거
- block_assembler B': 카드 div에 flex:1 추가 → 가로 균등 높이/폭
- block_assembler: [핵심요약:] 이후 라인 break → 결론 텍스트가 하단에 섞이는 문제 해결
- 남은 문제: 하단 좌 콘텐츠 잘림, 상단 자연(여건) 불릿 잘림

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:15:26 +09:00
b13df8b176 03번 B' 정상 동작: 가로 3단 카드 + overflow 해소 + 하단 균형
- block_assembler B': 카드 3개 이상 + 이미지 없음 → 가로(row) 배치
- block_assembler B': section title이 카드 제목, D1은 카드 내 bold 불릿
- block_assembler: overflow:auto → overflow:hidden, [핵심요약:] 마커 필터
- block_assembler: \x01 바이트 수정
- pipeline: Selenium 실측 기반 zone 간 재배분 (allocated-scrollHeight로 slack 계산)
- pipeline: surplus 최대 50%만 이전 (하단 최소 공간 보장)
- pipeline: bottom_left/bottom_right → Selenium bottom zone 매핑
- kei_client: 상단은 팝업 대상 제외, 하단에서만 팝업 분리

결과: 02번/03번 모두 overflow 없이 정상 출력

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:32:14 +09:00
bc7c08e575 B' overflow 루프 동작: Kei popup 결정 → 상단 소제목만 유지 + 하위불릿 팝업
- kei_client: 에스컬레이션 prompt 개선 — 소제목 유지 필수, overflow 영역만 대상
- block_assembler B': 상단 popup_roles 체크 추가 — 소제목만 남기고 하위불릿 제거
- block_assembler: \x01 바이트 수정 (r-string 역참조)
- 결과: 03번 top overflow 358px → 143px (루프 2회차에서 popup 반영)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:20:23 +09:00
571b057f19 Selenium 실측 overflow 정보를 에스컬레이션 report에 추가
- build_escalation_report 결과에 Selenium overflow px 정보 추가
- Kei가 실측 overflow를 인식하고 popup 결정을 내림 (7건)
- 미완: Kei popup 결정이 상단/하단좌 조립에 미반영 + 결정 과격 (전부 팝업)
- 다음: popup 결정 조립 반영 + Kei 가이드 개선 (일부만 팝업, 핵심은 유지)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:05:47 +09:00
51f61012c3 WIP: overflow 루프 구현 + Selenium 기반 에스컬레이션 트리거
- pipeline: filled→측정→Kei 재판단→재조립 루프 (최대 3회)
- pipeline: Selenium overflow가 있으면 calculate_fit과 무관하게 에스컬레이션
- 문제: build_escalation_report가 Selenium 측정 결과를 포함하지 않아 Kei가 빈 결정 반환
- 다음: content family 기반 범용 파이프라인 설계 필요 (Phase X-C)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:59:28 +09:00
b2a49f55ef Type B' 추가: 03번 MDX용 레이아웃 (표 렌더링 + 불릿 전용 하단)
- block_assembler: _assemble_slide_html_type_b_prime 추가
  - 하단 좌: normalized.tables를 표로 렌더링 (셀 중복 불릿 제거)
  - 하단 우: 불릿만 (table_summaries 미사용)
- pipeline: layout_template 체크를 in ("B", "B'")로 확장
- 결과: 03번 표가 표로 렌더링됨, 하단 우에 잘못된 표 제거

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:28:10 +09:00
f568e5c95d 이미지 마크다운 필터 추가: ![alt](path) 패턴도 content_lines에서 제거
- block_assembler + assemble_stage2: 기존 [이미지:] 패턴에 ![markdown image 패턴 추가
- 02번 상단 "소통과 신뢰" 카드에 이미지 경로가 불릿으로 표시되던 문제 해결

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:45:32 +09:00
095abdf9af XBX-2 완료: overflow 프로세스 정상 동작
- slide_measurer: data URI → 임시파일 방식 (대용량 HTML 측정 가능)
- pipeline: Type B zone 간 재배분 (top↔bottom 공간 이전)
- pipeline: overflow 분기에 top/bottom zone 추가
- kei_client: 에스컬레이션 prompt 개선
  - 텍스트 원문 보존 원칙 명시 (삭제/요약/압축 금지)
  - action을 popup만으로 제한
  - 실제 역할명 목록을 prompt에 전달
- block_assembler: Kei popup 결정 반영 (해당 역할 콘텐츠 → 팝업 링크)

결과: 02번 상단 카드 3개 모두 표시, 하단 우측 표 → 팝업 분리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:39:02 +09:00
17e77e310f Phase X-BX' XBX-1,3,5,6 완료: 유형 B 파이프라인 정상 동작
- XBX-1: normalizer 불릿 depth 보존 (D1/D2 마커) + 조립 로직 계층 반영
- XBX-3: 하단 구조 개선 — 하나의 큰 박스 안에 중제목 헤더 + 세로 구분선 2분할
- XBX-5: before→filled→after 파이프라인 연결 확인 (filled 2.2MB, 측정/재배분 정상)
- XBX-6: Type B에서 Sonnet 재구성 + renderer 스킵 — code_assembled 직접 사용
- final.html: 4,934 bytes → 2.2MB (Type B 정상 출력)
- Type A 코드 한 글자도 안 건드림

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 06:00:18 +09:00
d4eaec694c 유형 B 파이프라인 연결: block_assembler type B 조립 + zone 기반 전환 시작
- block_assembler: _assemble_slide_html_type_b 추가 (filled/after용 HTML 생성)
- fit_verifier: redistribute()가 ROLE_ZONE_MAP 대신 containers zone 사용
- renderer: render_slide_from_html()에 zone 기반 높이 탐색 추가
- pipeline: 팝업 HTML CSS를 콘텐츠 유형별(table/list/text) 분기
- run_from_stage1b: MDX 파일 하드코딩 제거 + layout_template 전달 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 04:39:02 +09:00
ef9bae7711 03번 분석: 하단 좌/우 내용 풍부, 현재 조립 부족
03번 하단 구조:
- 좌(2.1): 3개 소주제(Digital화+표, GIS+BIM, Solution) 각각 불릿 있음
- 우(2.2): 3개 소주제(품질향상, 정보물추가, 효율화) 각각 불릿 있음
- 결론: 원본 그대로

문제: _assemble_type_b가 내용을 제대로 조립 못 함
다음 세션: 하단 좌/우 내용 정확히 조립 + 렌더링 확인

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:40:37 +09:00
4f0105926d 03번 MDX sections 매핑 수정: 상단 level=2 합침 + 하단 대목차 정확히 찾기
- _assemble_type_b: 상단에 해당하는 모든 level=2 section을 합침
  (03번처럼 기술/사람/자연이 별도 section으로 분리된 경우 대응)
- 하단 대목차: level=3 바로 앞의 level=2 section으로 정확히 찾기
- 03번 결과: 상단 카드(기술/사람/자연) + 하단(과정혁신/결과변화) 정상
- 02번 영향 없음

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:33:05 +09:00
3719704d75 X' 핵심 수정: MDX sections에서 직접 텍스트 가져오기 + normalizer ### 지원
핵심 변경:
- mdx_normalizer: ### (h3) 소목차도 section으로 분리 (기존 ## 만)
- _assemble_type_b: Kei structured_text 대신 normalized.sections에서 직접 텍스트
- 대목차/소목차 계층 구조 그대로 반영

결과:
- 슬라이드 제목: 원본 MDX frontmatter 그대로
- 대목차: "DX 기반 Process 혁신에 따른 주체별 기대효과"
- 소목차 좌: "업무 수행 과정(Process)의 변화"
- 소목차 우: "DX 시행 주체별 기대효과" + 팝업 링크 + Kei 요약 표
- 캡션: normalized.images alt text

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 12:22:09 +09:00
6b17f448eb Phase X'-6 추가: 본문 표 요약 프로세스 (미완성)
- pipeline.py: normalized.tables에서 본문 표 감지 → Kei 요약 요청
- assemble_stage2: _assemble_type_b 하단 우측에 table_summaries 표출
- 검증: 4열x3행 표 생성 확인

미해결:
- 들여쓰기 계층이 PNG와 다름 (대제목→소제목→본문 indent)
- 상단 컨테이너 내용 잘림
- 하단 우측: 표를 불릿으로 풀지 말고 팝업 링크 + 요약 표로
- [DX 시행 주체별 기대효과 바로가기 →] 팝업 처리

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:57:46 +09:00
56fd9fa71e Phase X'-1~5 완료: 제목/들여쓰기/캡션/빈칸/카드 디자인
X'-1: 제목 원본 MDX frontmatter에서 가져오기 (Kei가 바꾸지 않음)
X'-2: 들여쓰기 계층 (소제목→불릿 indent 적용)
X'-3: 이미지 캡션 normalized.images alt text에서 추출
X'-4: 상단 컨테이너 justify-content:space-between
X'-5: 카드 디자인 다크 그라데이션 + 밝은 텍스트

X'-6 미완료: 본문 표(팝업 아닌)를 하단 우측에 Kei 요약 배치 → 다음 세션

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:40:53 +09:00
a8fe20e08e Phase X-B 진행중: 유형 B 조립 + 텍스트 보존 강화 + 원본 MDX 복구
X-B-3~5 완료:
- space_allocator: build_containers_type_b() 추가
- assemble_stage2: _assemble_type_b() 추가 (소제목 카드형)
- pipeline.py: layout_template 분기 (A/B)
- pipeline_context: Analysis.layout_template 필드
- validators: 유형 B 검증 완화

텍스트 보존 강화:
- KEI_PROMPT: 제목 원본 그대로, 텍스트 재작성 금지
- KEI_STRUCTURED_TEXT_PROMPT: 소제목 유지, 원본 문장 그대로

원본 MDX 복구:
- samples/mdx_batch/02.mdx: 표 데이터 누락 수정 (원본에서 재복사)

미해결 (다음 세션):
- 들여쓰기: 대제목→중제목→소제목→본문 계층 구조
- 이미지 캡션: [그림 제목] 형식 (대괄호 포함)
- 상단 컨테이너: 빈칸 위로 붙이기
- 카드 디자인: 안전과품질/생산성향상/소통과신뢰 디자인 개선
- 제목: Kei가 원본 제목 바꾸는 문제 잔존

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:28:03 +09:00