614c53358ecaa92e9f6fc979b2399da028e15d41
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 614c53358e |
feat(IMP-15): 실행-4 — debug.json event surfacing + spec taxonomy row
Issue: #48 (IMP-15 실행-4, axis 4: debug.json + spec doc trace). Parent: #15. Depends on 실행-1/2/3 (events + classifier outputs). Surfaces the image/table event streams that 실행-1/2/3 already produced and consumed, mirroring the existing `zone_geometries_px` top-level precedent (no new pattern introduced). Adds the matching taxonomy row to the Phase Z fit-classifier/router spec. src/phase_z2_pipeline.py (+3): - write_debug_json now lifts `image_events` and `table_events` to top-level of `debug.json` via `(visual_runtime_check or {}).get(<k>, [])`, exactly mirroring the immediately preceding `zone_geometries_px` surfacing line. Defaults to `[]` when `visual_runtime_check` is None — additive, no consumer-visible breakage. docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md (+1): - §3.1 taxonomy adds `image_aspect_mismatch` row. Row text explicitly marks the signal as post-render `fail_reasons` from Step 14 visual_runtime_check (rendered vs declared aspect ratio mismatch), NOT a router-routed fit_classifier output, and notes the separate `image_events` stream surface. Prevents future readers from wiring this taxonomy into §3.2 priority list or §4 router action map. tests/phase_z2/test_debug_json_event_surfacing.py (new, 2 tests): - `test_write_debug_json_surfaces_image_and_table_events` invokes write_debug_json with synthetic visual_runtime_check containing both event lists; reads back the on-disk debug.json and asserts both keys are present at top level with the exact payloads. - `test_write_debug_json_defaults_when_visual_runtime_check_none` asserts both new keys default to `[]` when visual_runtime_check is None — guards the defensive `(… or {})` pattern. tests/phase_z2/test_spec_taxonomy_image_aspect_mismatch.py (new, 2 tests): - `test_spec_has_image_aspect_mismatch_row` opens the spec file and asserts exactly one `^\| image_aspect_mismatch \|` row exists inside the §3.1 table block (no markdown-parser dependency). - `test_spec_row_marks_post_render_fail_reasons_semantic` asserts the row text carries both "Post-render" and "fail_reasons" tokens — enforces the Stage 1 guardrail wording. Verification (Stage 4 PASS, Claude + Codex independent): - pytest -q tests/phase_z2/test_debug_json_event_surfacing.py \ tests/phase_z2/test_spec_taxonomy_image_aspect_mismatch.py → 4 passed in 0.07s. - git diff scope: 4 files, +148 insertions / 0 deletions. Scope-locked: no edits to classifier (실행-3), event generation (실행-1/2), Step 21 viewer, §3.2 priority list, §4 router action mapping, or `table_self_overflow` taxonomy row. Pre-existing dirty/untracked working-tree files left untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 535c4848fd |
feat(IMP-15): 실행-3 — classifier consumes image+table events
Issue #47 (IMP-15 실행-3 axis 3): extend `classify_visual_runtime_check` to consume the `image_events[]` and `table_events[]` arrays produced by `run_overflow_check` (실행-1/2) and widen `visual_check_passed`. Changes (src/phase_z2_classifier.py): - Remove `overflow.passed=True` early-return so image/table event scans always run, even when zone-level overflow was clean. - Deferred import of `IMAGE_ASPECT_DELTA_TOL` and `TABLE_SCROLL_TOL_PX` from `phase_z2_pipeline` (circular-safe SSoT; no duplicate literals). - New `image_events` scan emits `image_aspect_mismatch` when `delta is not None AND |delta| > IMAGE_ASPECT_DELTA_TOL` (delta=None ⇒ skip, image not loaded). - New `table_events` scan emits `tabular_overflow` when `wrapper_clipped_index is None AND (excess_x or excess_y > TABLE_SCROLL_TOL_PX)` (wrapper-clipped tables deduped against the existing zone cascade). - `visual_check_passed = overflow.passed AND not classifications` — any image/table classification now flips the gate. Guardrails preserved: - §3.2 8-rule zone cascade (clipped_inner / zone-self) untouched — the new emitters are ADDITIONAL. - `placement_diagnostics`, `categories_seen`, `unclassified_signals` return-shape preserved. - No `pipeline.py` production changes; no router action or `debug.json` passthrough changes. Tests (tests/phase_z2/test_phase_z2_visual_classifier.py — new): - `test_image_aspect_mismatch_emits_classification` (|delta|>TOL fires) - `test_image_aspect_delta_below_tol_no_classification` (≤TOL skipped) - `test_standalone_table_overflow_emits_classification` (wrapper_clipped_index=None, excess>TOL fires) - `test_table_dedup_when_wrapper_clipped` (wrapper_clipped_index set ⇒ no `tabular_overflow` emit) All 4 pure-dict (no Selenium / chromedriver / pipeline execution). Tolerances imported from `phase_z2_pipeline` (SSoT enforced via test import — no classifier-local literals). Verification (Stage 4): - New classifier tests: 4/4 PASS. - Regression `tests/phase_z2/` excluding new file: 93/93 PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 2827622858 |
feat(IMP-16): Step 14 table_self_overflow detection
Add table self-overflow detection with element-identity wrapper dedup, mirroring the image_aspect_mismatch axis pattern (#45). JS layer: TABLE_SCROLL_TOL_PX=5 module constant; clippedWrapperMap built as Map<Element,int> keyed by DOM node reference (NOT className) so two wrappers with identical class strings remain distinguishable; table_events collected via querySelectorAll('table').forEach with closest()-ancestor walk resolving wrapper_clipped_index = int|null. Py layer: aggregate result['table_events'] and append fail_reason 'table_self_overflow' only when (excess_x>TOL OR excess_y>TOL) AND wrapper_clipped_index is None; wrapper-clipped path continues to fail via existing clipped_inner reporting. Tests (Selenium, chromedriver guard mirrored from image_check): - Fixture D: standalone <table> overflow → table_self_overflow fail - Fixture E: <table> in clipped wrapper → dedup suppresses table fail - Fixture F (F1 acceptance): two wrappers with identical className f13b-cell, W1 clipped by non-table child, W2 hosts self-overflow <table> with W2 itself NOT clipped → element-identity ensures W2's table is not suppressed by W1's class; both fails emitted. Out of scope: image_events behavior (intact from #45), classifier pass/fail consumer (→실행-3), debug.json surfacing (→실행-4). Refs: #46 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| e9b3d2e9c0 |
feat(IMP-15): 실행-1 — Step 14 image_aspect_mismatch detection
Issue: #45 (IMP-15 실행-1, image axis only). Adds Selenium-based <img> aspect ratio measurement to Step 14 run_overflow_check + numeric tolerance gate. Tolerance lives as module-scope constant so tests can import it. src/phase_z2_pipeline.py (+73/-2): - L131-L135 IMAGE_ASPECT_DELTA_TOL = 0.05 (module scope, importable) - L2216-L2261 JS payload extension: image_events[] per <img> (src, zone_position via closest('.zone') with 'unknown' fallback, zone_template_id, natural/rendered w+h+ratio, delta, slide-rel bbox) - L2262 run_overflow_check return extended with image_events - L2302-L2320 Python aggregation: abs(delta) > TOL ⇒ fail_reasons append 'image aspect mismatch in zone--<pos>: natural=<n> rendered=<r> delta=<+d> (template=<tid>, tol=0.05, src=<src>)'. Null-delta entries (image not loaded) are skipped — no false positive. Branch placed AFTER existing non-image branches; ordering & strings for slide/slide-body/zone/clipped_inner unchanged. - L4425-L4429 Step 14 note: image half closed, table half deferred to 실행-2. tests/phase_z2/test_phase_z2_step14_image_check.py (+196, new): - 3-tier chromedriver resolver mirroring pipeline (PROJECT_ROOT/ chromedriver{,.exe} → PATH → Selenium Manager probe). - pytestmark: skip when chromedriver unresolvable AND PHASE_Z_REQUIRE_SELENIUM != '1'; xfail(strict=True) opt-in when =='1'. - Fixture A: 200×100 img rendered 200×100 → aspect_delta < 0.05, passed. - Fixture B: 200×100 intrinsic forced to 200×200 → delta > 0.30, fail_reason present. - Fixture C: <img> with no .zone ancestor → zone_position == 'unknown'. Verification (Stage 4 PASS, Claude + Codex independent): - pytest -q tests/phase_z2/test_phase_z2_step14_image_check.py → 3 passed - PHASE_Z_REQUIRE_SELENIUM=1 same suite → 3 passed (strict opt-in) - pytest -q tests/phase_z2 → 90 passed (no regression) - pytest -q --ignore=tests/matching → 174 passed Scope-locked: no slide_base.html / catalog / classifier / debug.json / spec-doc changes. table_events (실행-2), visual_check_passed flip (실행-3), debug.json image_events surfacing + PHASE-Z spec doc row (실행-4) remain queued as separate IMP-15 child execution issues. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 7a52cebfaa |
feat(IMP-14): A-4 — slide_base embedded vs standalone mode contract
Step 13 owns iframe-vs-standalone CSS contract in slide_base.html via
3-valued embedded_mode enum (auto / embedded / standalone). Removes
SlideCanvas.tsx runtime CSS injection workaround; frontend now passes
?embedded=1 query so auto-mode script attaches html.embedded class and
scopes the standalone body centering/min-height/padding reset.
- templates/phase_z2/slide_base.html: conditional html.embedded class +
CSP-safe auto-mode <script> + additive html.embedded body/.slide rules
- src/phase_z2_pipeline.py: render_slide gains keyword-only embedded_mode
("auto" default) + ValueError guard; 3 existing call sites unchanged
- Front/client/src/components/SlideCanvas.tsx: derive embeddedSrc with
?embedded=1 (query-preserving), drop reset CSS injection block
- tests/phase_z2/test_slide_base_embedded_mode.py: 6 cases — auto script,
CSS rules, embedded/standalone explicit modes, byte-determinism,
invalid-mode guard
|
|||
| 56619a0239 |
feat(IMP-12): Step 16/17 retry refinement — multi-donor + 3-stage salvage cascade
Extend Step 17 deterministic action surface so donor_slack_insufficient no longer abort-terminates at zone_ratio_retry. AI is NOT invoked on the normal salvage path. Source changes (4 files, scope-locked): - src/phase_z2_retry.py — plan_zone_ratio_retry: single-primary-donor → multi-donor greedy aggregation (donors_used / aggregate_slack_used / aggregate_slack_available); new plan/apply pairs: cross_zone_redistribute (wraps fit_verifier.redistribute, data-role scoped CSS), glue_compression (wraps space_allocator.compute_glue_css_overrides, data-zone-position scoped), font_step_compression (wraps find_fitting_font_size, zone-scoped, defensive feasible=False on missing text_metrics). - src/phase_z2_failure_router.py — classifier inspects salvage_steps[-1] via SALVAGE_FAILURE_TYPE_BY_ACTION; NEXT_ACTION_BY_FAILURE rewired into donor_slack_insufficient/no_donor_candidates → cross_zone_redistribute → glue → font_step → layout_adjust; 3 IMPLEMENTED salvage status rows added. - src/phase_z2_router.py — ACTION_IMPLEMENTATION_STATUS registers 3 new salvage actions as IMPLEMENTED; ACTION_BY_CATEGORY untouched (cascade-only labels). - src/phase_z2_pipeline.py — new _attempt_salvage_chain() iterates router next_proposed_action with retry_budget=1 per action; honors IMP-09 dynamic_cols / fr_default gate; preserves (b)-revert on all-fail; wires Step 17 telemetry (salvage_steps / salvage_passed). Tests (6 new pytest modules): - test_phase_z2_retry_multi_donor.py — single sufficient (regression), 1st insufficient + 2nd sufficient (multi-donor PASS), aggregate insufficient FAIL. - test_phase_z2_cross_zone_redistribute.py — multi-role zone feasible, single-role zone short-circuits infeasible. - test_phase_z2_glue_compression.py — feasible asserts emitted CSS contains [data-zone-position=...] selector and NO global :root/body/.slide rule. - test_phase_z2_font_step_compression.py — 15.2 → 13 closes excess; 8px floor; missing text_metrics → defensive infeasible reason. - test_phase_z2_failure_router_cascade.py — donor_slack_insufficient → cross_zone (impl=IMPLEMENTED); 3 new failure types → expected next actions; rerender_still_fails preserves frame_reselect terminus. - test_phase_z2_step17_salvage_chain.py — end-to-end (a) cross_zone PASS promotes final.html, (b) cross_zone FAIL + glue PASS promotes 2nd candidate, (c) all-3 FAIL preserves original final.html (revert). Guardrails preserved: - AI calls: 0 on normal path (feedback_ai_isolation_contract) - Spacing direction: no shrink-common-margin; resolve via donor/glue/font-step within frame envelope (feedback_phase_z_spacing_direction) - All CSS overrides scoped to [data-role=...] or [data-zone-position=...] - IMP-09 dynamic_cols / fr_default gate honored in cascade - (b)-revert preserved if all 3 salvage actions fail Refs: gitea#12 IMP-12 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 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>
|
|||
| 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>
|