IMP-11 D-2 Frame min_height 표시 #11
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
관련 step: Step 22 (frontend UI + catalog 참조)
source: INSIGHT-MAP §2 (D-2 새로 만들기 — frontend hint + catalog 참조)
priority: ↓ low
scope:
min_height_pxread-only + resize hintguardrail / validation:
dependency:
nonecross-ref:
review loop:
[Claude #1] Stage 1 problem-review Round #1
Root cause (현황 사실 정리)
문제 진술 (issue body 기준)
visual_hints.min_height_px) 시각화 없음.현재 데이터 경로 (verified)
templates/phase_z2/catalog/frame_contracts.yaml의 각 frame entry →visual_hints.min_height_px(frame_contracts.yaml:40,89,218,280,351,417,481,544,601,668).zone_region_plans[].min_height_px로 zone 단위 노출 (src/phase_z2_pipeline.py:3771).v4_all_judgments[](src/phase_z2_pipeline.py:4007). 현재 노출 필드 :template_id, frame_id, frame_number, v4_rank, confidence, label, catalog_registered.min_height_px미포함 = root cause.FrameCandidate(Front/client/src/types/designAgent.ts:120) :id, name, score, confidence, thumbnailUrl, label, catalogRegistered—minHeightPx필드 부재.v4_all_judgments→FrameCandidate변환 시catalog_registered만 추출 —min_height_px추출 코드 부재.minSize=0.05만 사용. frame contract 의min_height_px참조 없음.Root cause: backend Step 9
v4_all_judgments가 candidate 별min_height_px를 노출하지 않아 frontend 가 frame 별 최소 가독 높이를 알 길이 없음. 따라서 zone overlay 가 한계 비교 / 시각 hint 를 띄울 수 없음.Scope-lock (5 axes, additive only)
IN scope
Backend additive field —
src/phase_z2_pipeline.py:4007 v4_all_judgments각 entry 에min_height_px추가.get_contract(c.template_id).visual_hints.min_height_px(catalog read, no write).None(graceful).catalog_registered가 이미get_contract(c.template_id)를 호출하므로 같은 lookup 재사용 가능 (성능 동일).Frontend type —
Front/client/src/types/designAgent.ts FrameCandidate에minHeightPx?: number추가 (camelCase 관례 유지, optional).Frontend mapper —
Front/client/src/services/designAgentApi.ts:514frameCandidatesbuilder 에minHeightPx: c.min_height_px ?? undefined추가.v4_candidatesfallback path (rawSource 분기) 는undefined(graceful, no regression).Frontend SlideCanvas hint —
Front/client/src/components/SlideCanvas.tsxzone overlay (408-460):minHeightPx결정. priority :userSelection.overrides.zone_frames[region.id] → defaultFrameId → undefined. 이 frameId 로region.frame_candidates에서minHeightPxlookup.localGeom.h * SLIDE_H(즉localGeom.h * 720). slide-body 가 720 보다 작으므로 실제 비교 기준은 slide-body 안 px =localGeom.h * SLIDE_BODY_UNSCALED_H. 단, catalog 의 min_height_px 는 slide-body 좌표계 (compute_zone_layout) 기준 (src/phase_z2_pipeline.py:791, 3091 검증) — slide-body 절대 px.localGeom.h * measuredSlideBody.h * SLIDE_H✗ (scaled coord). unscaled 1280×720 좌표계는measuredSlideBody.h * 720이 slide-body 절대 height px. 즉 zone height unscaled px =localGeom.h * measuredSlideBody.h * SLIDE_H.zone_height_px < minHeightPx이면 zone overlay div 에 빨간 outline class 추가 (pendingLayout모드에서만, normal mode 영향 X).minSize변경 X. drag block 없음. 시각 hint 만.scope 외 (defer): LayoutPanel 카드의 min_height_px tooltip 표시 — 본 backlog 가 "한계 도달 시 붉은 outline" 을 우선 요구. LayoutPanel surface 는 별 axis 로 분리 (필요 시 후속 issue).
OUT of scope
frame_contracts.yaml) modification — 참조만, 변경 X.v4_candidatesfallback path 의min_height_px보강 — graceful undefined.Guardrails / Validation
Guardrails
frame_contracts.yamlSHA 불변.v4_all_judgmentsentry 는 기존 필드 보존 +min_height_px추가만 — downstream consumer crash X (모두 dict lookup).isPendingLayout && minHeightPx != null && zone_height_px < minHeightPx조합에서만).minSize=0.05(resize handler) 변경 X — 사용자 자유 resize 보장.v4_candidatesfallback path (catalog_registered === undefined) :minHeightPx === undefined→ outline 안 띄움 (no false positive).Validation (3-axis status)
Tests (stage 2 에서 확정)
tests/phase_z2/test_application_plan_min_height.py(예) : F13, F29, F20 unit 에 대해v4_all_judgmentsentry 가min_height_px노출 (각각 230/345/350) 검증. catalog unregistered candidate 는min_height_px is None.tsc --noEmitPASS.Dependency
none— backlog 명시. backend Step 9 출력 + frontend 5 file 변경 self-contained.Cross-ref (factual, file:line)
templates/phase_z2/catalog/frame_contracts.yaml:40,89,218,280,351,417,481,544,601,668(10 frame × min_height_px).src/phase_z2_pipeline.py:4007-4018(v4_all_judgments list comprehension).catalog_registeredprecedent :src/phase_z2_pipeline.py:4015(same get_contract() lookup).Front/client/src/types/designAgent.ts:120-130(FrameCandidate).Front/client/src/services/designAgentApi.ts:514-530(frameCandidates builder).Front/client/src/components/SlideCanvas.tsx:408-535(zone overlay + resize handler).src/phase_z2_pipeline.py:791,3091,3771(min_height_px = slide-body absolute px).Risks
measuredSlideBody.h는 정규화 0~1 (1280×720 기준). 변환 :zone_height_slide_body_px = localGeom.h * measuredSlideBody.h * 720. stage 2 plan 에서 unit test (수치) 로 확정.region.frame_candidateslookup 으로 동기 (이미 frame_candidates 에 모든 후보 들어있음).Stage 2 plan 입력 (다음 stage 가 받을 input)
src/phase_z2_pipeline.py:4007(+1 field).Front/client/src/types/designAgent.ts:120(+1 field).Front/client/src/services/designAgentApi.ts:514(+1 line).Front/client/src/components/SlideCanvas.tsx(outline class + minHeightPx lookup).Front/client/src/components/SlideCanvas.tsx안 zone overlay style — Tailwind classborder-red-500또는 inline style.tests/phase_z2/test_application_plan_min_height.py.=== EVIDENCE ===
Commands run :
Grep min_height_px→ 10 frame entry in frame_contracts.yaml + 23 reference in src/phase_z2_pipeline.py.Read frame_contracts.yaml:1-100→ F13/F29 entry 확인 (visual_hints.min_height_px = 230/345).Read src/phase_z2_pipeline.py:3995-4028→ v4_all_judgments shape 확인 (현재 catalog_registered 까지만).Read src/phase_z2_pipeline.py:3740-3790→ zone_region_plans 의 min_height_px 출처 (visual_hints.min_height_px) 확인.Read Front/client/src/types/designAgent.ts:1-159→ FrameCandidate 현재 shape 확인 (minHeightPx 부재).Read Front/client/src/services/designAgentApi.ts:490-576→ frameCandidates builder 확인 (catalog_registered 만 propagate).Read Front/client/src/components/SlideCanvas.tsx:1-90, 390-596→ zone overlay + resize handler 확인 (minHeightPx 사용처 부재).Facts established :
v4_all_judgments에 추가가 self-contained / minimal-touch.catalog_registered가 이미get_contract(c.template_id)lookup 사용 →min_height_px추가 = 같은 lookup 의.visual_hints.min_height_pxchain 1줄.Unknown surface (stage 2 plan 이 해소) :
FINAL_CONSENSUS: YES
[Codex #1] Stage 1 problem-review Round #1
Scope-qualified review: I verified the root cause and the proposed scope with grep/find only. No pytest was run, per the current stage instruction.
Root cause verified:
visual_hints.min_height_pxintemplates/phase_z2/catalog/frame_contracts.yaml; the current catalog has min-height values on the registered contract entries found at lines 40, 89, 218, 280, 351, 417, 481, 544, 601, and 668.zone_region_plans[].min_height_pxatsrc/phase_z2_pipeline.py:3771.application_plan[].v4_all_judgments[]currently emitstemplate_id,frame_id,frame_number,v4_rank,confidence,label, andcatalog_registered, but does not emitmin_height_pxatsrc/phase_z2_pipeline.py:4007-4016.FrameCandidatecurrently has nominHeightPxfield atFront/client/src/types/designAgent.ts:117-126.v4_all_judgments/v4_candidatescurrently mapscatalogRegisteredonly and does not mapmin_height_pxatFront/client/src/services/designAgentApi.ts:514-530.SlideCanvaspending layout resize currently clamps with only normalizedminSize = 0.05, with no frame contract lookup or visual warning atFront/client/src/components/SlideCanvas.tsx:471-521.Scope lock verified, with one explicit implementation note:
min_height_pxtov4_all_judgments[]fromget_contract(c.template_id).visual_hints.min_height_pxwhen the contract exists, otherwiseNone. This does not require changing catalog files.minHeightPx?: numberandminHeightPx: c.min_height_px ?? undefinedon thev4_all_judgmentspath; fallbackv4_candidatesremains gracefully undefined.Front/client/src/components/SlideCanvas.tsx:594-606.SLIDE_BODY_HEIGHT = 585andSLIDE_H = 720atsrc/phase_z2_pipeline.py:110-114; frontend currently defines onlySLIDE_W = 1280andSLIDE_H = 720atFront/client/src/components/SlideCanvas.tsx:55-56. Stage 2/3 should introduce or derive the frontend slide-body unscaled height (585) forlocalGeom.h * slideBodyHeight, notlocalGeom.h * SLIDE_H.Missing files / wrong assumptions:
720) for min-height comparison. The contract is slide-body based (585height), matchingcompute_zone_layoutand Step 8/9 body coordinates.=== EVIDENCE ===
Commands run:
Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\.orchestrator\tmp\codex_prompt.txt'rg -n "visual_hints|min_height_px|get_contract\(|v4_all_judgments|catalog_registered" templates/phase_z2/catalog/frame_contracts.yaml src/phase_z2_pipeline.pyrg -n "interface FrameCandidate|type FrameCandidate|minHeightPx|catalogRegistered|frameCandidates|v4_all_judgments|v4_candidates" Front/client/src/types/designAgent.ts Front/client/src/services/designAgentApi.tsrg -n "pendingLayout|resize|frame_candidates|zone_frames|defaultFrameId|SLIDE_H|SLIDE_BODY_UNSCALED_H|minSize" Front/client/src/components/SlideCanvas.tsx src/phase_z2_pipeline.pyrg -n "v4_all_judgments|v4_candidates|min_height_px|catalog_registered" Front/client/src src tests -g "*.ts" -g "*.tsx" -g "*.py"rg -n "SLIDE_BODY_HEIGHT|SLIDE_BODY_UNSCALED_H|SLIDE_BODY" src/phase_z2_pipeline.py Front/client/src/components/SlideCanvas.tsx Front/client/src -g "*.ts" -g "*.tsx"Get-Contentline-range reads forsrc/phase_z2_pipeline.py,Front/client/src/types/designAgent.ts,Front/client/src/services/designAgentApi.ts,Front/client/src/components/SlideCanvas.tsx, andtests/test_phase_z2_v4_fallback.pyTest-Pathchecks for referenced docs and implementation filesgit status --shortFiles checked:
D:\ad-hoc\kei\design_agent\templates\phase_z2\catalog\frame_contracts.yamlD:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.pyD:\ad-hoc\kei\design_agent\Front\client\src\types\designAgent.tsD:\ad-hoc\kei\design_agent\Front\client\src\services\designAgentApi.tsD:\ad-hoc\kei\design_agent\Front\client\src\components\SlideCanvas.tsxD:\ad-hoc\kei\design_agent\tests\test_phase_z2_v4_fallback.pyD:\ad-hoc\kei\design_agent\docs\architecture\PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.mdD:\ad-hoc\kei\design_agent\docs\architecture\PHASE-Q-INSIGHT-TO-22STEP-MAP.mdD:\ad-hoc\kei\design_agent\docs\architecture\PHASE-Z-PIPELINE-OVERVIEW.mdTest results: Not run; current stage explicitly says NO pytest.
Verified facts:
v4_all_judgmentsis the frontend-preferred raw source when present.min_height_pxis present in backend zone-region planning but absent from Step 9 candidate entries.FrameCandidateand the frontend mapper do not currently expose candidate min-height.SlideCanvaspending resize currently has only a normalized 0.05 minimum and no frame min-height hint/limit.FINAL_CONSENSUS: YES
[Claude #1] Stage 2 simulation-plan Round #2 — IMP-11 D-2 Frame min_height 표시
Axis coverage checklist (all 5 axes from Stage 1 scope_lock)
axis_1_backend_field— Step 9v4_all_judgments[]+=min_height_pxaxis_2_frontend_type—FrameCandidate.minHeightPx?axis_3_frontend_mapper—min_height_px→minHeightPxaxis_4_slidecanvas_hint— visual hint on resize (no clamp change)axis_5_active_frame_lookup— override frame id first, default secondPer Stage 1 §guardrails: 1 commit = 1 axis → 5 units, frontend chain u2→u3→u4→u5 sequential dependency.
Per-axis detail
Axis 1 (u1) — Backend field
v4_all_judgments[]entry keys =template_id, frame_id, frame_number, v4_rank, confidence, label, catalog_registered(src/phase_z2_pipeline.py:4007-4016). Frontend cannot read min readable height per candidate."min_height_px": get_contract(c.template_id).get("visual_hints", {}).get("min_height_px")(None when contract unregistered — mirrorscatalog_registered=Falsesemantics).src/phase_z2_pipeline.py(one key inserted in dict literal). Reuseget_contractcall already present at line 4015 (bind to local var to avoid double lookup — Stage 3 micro-decision).tests/test_phase_z2_step9_v4_all_judgments_min_height.py— synth unit with one registered + one unregisteredtemplate_id, assert registered entry hasmin_height_px == contract.visual_hints.min_height_pxand unregistered hasmin_height_px is None.Axis 2 (u2) — Frontend type
FrameCandidateinterface hasid, name, score, confidence, thumbnailUrl?, label, catalogRegistered?(Front/client/src/types/designAgent.ts:120-130).minHeightPx?: numberwith JSDoc — "frame contract visual_hints.min_height_px; pendingLayout resize hint reference. undefined when contract unregistered or v4_candidates fallback path."Front/client/src/types/designAgent.ts.cd Front/client && npx tsc --noEmit(type-only).Axis 3 (u3) — Frontend mapper
catalogRegistered: c.catalog_registeredonly (Front/client/src/services/designAgentApi.ts:514-530).minHeightPx: c.min_height_px(undefined on v4_candidates legacy path — graceful fallback).Front/client/src/services/designAgentApi.ts.tsc --noEmit(no unit test file exists for this mapper; verified).Axis 5 (u4) — Active-frame lookup helper (precedes u5)
previewCandidateonly when override differs from default. No symmetric "active frame" concept (override ?? default).activeFrameId = overrideFrameId ?? defaultFrameIdandactiveCandidate = region?.frame_candidates?.find(c => c.id === activeFrameId). Mirrors Stage 1 §axis_5 source_existing semantics.Front/client/src/components/SlideCanvas.tsx.tsc --noEmit+ dev smoke.Axis 4 (u5) — Visual hint
isPendingLayout && activeCandidate?.minHeightPx != null && (height * H_SCALED * (measuredSlideBody?.h ?? 1)) < activeCandidate.minHeightPx:ring-2 ring-red-500/70to outer div className.<span className="...bg-red-600 text-white">min H {N}px</span>.minSize=0.05resize clamp unchanged (Stage 1 §axis_4resize_minsize_unchanged: true).Front/client/src/components/SlideCanvas.tsx.tsc --noEmit+ manual resize smoke (resize zone below threshold, observe red ring + badge).min_height_px, nomin_width_px).Test strategy
tests/test_phase_z2_step9_v4_all_judgments_min_height.py(registered + unregistered fixtures)cd Front/client && npx tsc --noEmitGlobal rollback
Five axes independent; each unit revertable in isolation. No DB migration, no catalog write, no protocol change.
=== IMPLEMENTATION_UNITS ===
Each unit ≤ 50 lines, ≤ 3 files, atomic. u1 = backend (independent). u2→u3→u4→u5 = frontend chain (type → mapper → helper → consumer). Stage 3 executes one unit per turn; uses
continue_sameuntil u5 completes, thenFINAL_CONSENSUS: YES.[Codex #2] Stage 2 simulation-plan Round #1
Scope-qualified verifier result: NO. I verified the latest Stage 2 plan comment only, cross-checked it against the binding Stage 1 exit report and current files, and ran the required baseline pytest command.
Coverage status:
=== IMPLEMENTATION_UNITS ===block exists and lists u1 through u5. The listed estimates are all <= 50 lines and each listed implementation file set is <= 3 files.Blocking plan defects:
Axis 1 null-handling is internally inconsistent. The plan says add
"min_height_px": get_contract(c.template_id).get("visual_hints", {}).get("min_height_px")and also says unregistered contracts should produceNone. In current code,get_contract(c.template_id)may beNone; calling.get(...)onNonewill raise. Current backend entry construction is a list comprehension atsrc/phase_z2_pipeline.py:4007-4017and currently only checksget_contract(c.template_id) is not Noneforcatalog_registeredat line 4015. The Stage 3 plan must specify a concrete None-safe implementation, for example bindingcontract = get_contract(c.template_id)before building the entry or using a helper/walrus expression that does not double-call and does not dereferenceNone.Axis 4 height comparison formula is wrong for the catalog coordinate system. The plan proposes comparing
(height * H_SCALED * (measuredSlideBody?.h ?? 1)) < activeCandidate.minHeightPx. In currentSlideCanvas,heightis already full-slide-normalized after slide-body conversion (overrideGeom.h * measuredSlideBody.horzone.position.height * measuredSlideBody.h) atFront/client/src/components/SlideCanvas.tsx:438-455. Multiplying bymeasuredSlideBody.hagain double-applies the body ratio, and multiplying byH_SCALEDproduces viewport-scaled pixels. Catalogmin_height_pxis backend slide-body unscaled px; backend constants showSLIDE_H = 720andSLIDE_BODY_HEIGHT = 585atsrc/phase_z2_pipeline.py:109-114. The plan should compare unscaled px, e.g.localGeom.h * SLIDE_BODY_UNSCALED_H(585) or equivalentlyheight * SLIDE_Hif using the already body-converted full-slide-normalizedheight. It must not useH_SCALEDor multiply bymeasuredSlideBody.htwice.Frontend test command path is wrong. The plan repeatedly says
cd Front/client && npx tsc --noEmit, butFront/client/package.jsondoes not exist. The package isFront/package.json, and it defines"check": "tsc --noEmit". The plan should usecd Front && npm run checkorcd Front && npx tsc --noEmit.The implementation-units block separates
filesandtests, but u1 creates/modifies a test file while the file count line only listssrc/phase_z2_pipeline.py. Because this stage explicitly asks to verify each unit <= 50 lines / <= 3 files, the next plan should make the accounting explicit: include test files in the touched-file count or state that the <=50-line limit applies only to implementation code and give separate test line estimates.The issue body and binding exit report do not need a scope rewind to Stage 1; the scope is sound. The simulation plan needs correction before Stage 3 because the current u1 expression can crash for unregistered contracts and the u5 formula will fire the hint at the wrong threshold.
=== EVIDENCE ===
Commands run:
Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\.orchestrator\tmp\codex_prompt.txt'git status --shortrg -n "v4_all_judgments|min_height_px|get_contract|FrameCandidate|minHeightPx|frame_id_override|pendingLayout|implementation_units|u1|u2|u3|u4|u5" src Front templates tests docs .orchestrator -g '!Front/client/node_modules/**'pytest -q testsInvoke-RestMethod .../issues/11/comments?limit=5&page=1to fetch only recent issue comments, including latest Stage 2 plansrc/phase_z2_pipeline.py,Front/client/src/components/SlideCanvas.tsx,Front/client/src/types/designAgent.ts,Front/client/src/services/designAgentApi.ts,Front/package.json, andFront/client/package.jsonexistencerg -n "H_SCALED|SLIDE_H|SLIDE_BODY_HEIGHT|measuredSlideBody|previewCandidate|defaultFrameId|overrideFrameId|frame_candidates|zone_frames" Front/client/src/components/SlideCanvas.tsx src/phase_z2_pipeline.py Front/client/src -g "*.tsx" -g "*.ts" -g "*.py"Files checked:
D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.pyD:\ad-hoc\kei\design_agent\Front\client\src\components\SlideCanvas.tsxD:\ad-hoc\kei\design_agent\Front\client\src\types\designAgent.tsD:\ad-hoc\kei\design_agent\Front\client\src\services\designAgentApi.tsD:\ad-hoc\kei\design_agent\Front\package.jsonD:\ad-hoc\kei\design_agent\Front\client\package.json(missing)D:\ad-hoc\kei\design_agent\templates\phase_z2\catalog\frame_contracts.yamlTest results:
pytest -q tests=> 104 passed in 7.64sVerified facts:
v4_all_judgmentslacksmin_height_pxand usesget_contract(c.template_id) is not Noneonly forcatalog_registered.FrameCandidatelacksminHeightPxand mapper lacksmin_height_pxpropagation.SlideCanvasstoresheightas full-slide-normalized after multiplying bymeasuredSlideBody.h; resize math usesH_SCALED * measuredSlideBody.hfor mouse deltas, but catalog comparison must be unscaled slide-body px.Front/clientis not the package root;Frontis the package root.rewind_target: stage_2_plan
FINAL_CONSENSUS: NO
[Claude #1] Stage 2 simulation-plan Round #1 (rewind from Round #1 NO)
Codex #2 rewind point = Axis 1 null-safety.
get_contract(c.template_id)may returnNone(signature:dict | None—src/phase_z2_mapper.py:56-57). Original draft usedget_contract(...).get("visual_hints", {}).get(...)which crashes when contract is missing. Fixed: bind contract once, branch on None, reuse same binding forcatalog_registered(no extra lookup cost).═══════════════════════════════════════════════════════════════
ISSUE AXIS COVERAGE CHECKLIST (from Stage 1 binding contract)
═══════════════════════════════════════════════════════════════
v4_all_judgments[].min_height_px(None-safe)src/phase_z2_pipeline.pyFrameCandidate.minHeightPx?: numberFront/client/src/types/designAgent.tsmin_height_px → minHeightPxFront/client/src/services/designAgentApi.tsFront/client/src/components/SlideCanvas.tsxFront/client/src/components/SlideCanvas.tsxAll 5 Stage 1 axes covered. u4 sequenced before u5 because u5 hint logic depends on u4's active-frame resolution.
═══════════════════════════════════════════════════════════════
PER-AXIS BEFORE/AFTER + FILE CHANGES
═══════════════════════════════════════════════════════════════
────────── u1 : axis_1_backend_field ──────────
Before (
src/phase_z2_pipeline.py:4007-4018):After (None-safe; bind contract once, derive both fields from one lookup). Inline pattern with single helper to keep the list-comprehension shape:
Null-safety guarantees:
contract is None→visual_hints = {}(early bail) →visual_hints.get("min_height_px")returnsNone. No.getever called onNone.contract present but no visual_hints key→.get("visual_hints", {})returns{}→.get("min_height_px")returnsNone.contract present, visual_hints present, no min_height_px key→ returnsNone.min_height_px: Nonein output. Frontend interprets undefined identically.Lookup cost: single
get_contract(c.template_id)per candidate (was already 1 call → same cost). Existing consumers ofv4_all_judgments[]keys (catalog_registered,template_id, …) unchanged. Helper placed as module-local nested function above the list emit (same enclosing function), not in module namespace.Side effects: schema additive only. JSON serialization handles
None(becomesnull).────────── u2 : axis_2_frontend_type ──────────
Before (
Front/client/src/types/designAgent.ts:120-130):After (optional additive field):
Side effects: optional field → no breaking change.
v4_candidatesfallback path leavesminHeightPxundefined (graceful).────────── u3 : axis_3_frontend_mapper ──────────
Before (
Front/client/src/services/designAgentApi.ts:514-530): mapper extractscatalog_registeredonly.After (additive line in the
.map((c: any) => ({...}))object literal):typeof === "number"guards againstnull/undefinedfrom backend (per CLAUDE.md "validate at system boundaries"). Comment in code explains v4_candidates fallback yields undefined.Side effects: no test change required (mapper is type-only addition). Build typecheck verifies.
────────── u4 : axis_5_active_frame_lookup ──────────
Before (
Front/client/src/components/SlideCanvas.tsx:601-602):After (existing
defaultFrameIdkept; add active-frame resolution that respects override). Insert immediately AFTER line 602:Note: line 603-605
overrideFrameIdblock for preview thumbnail stays as-is — different concern (preview vs active). Both blocks coexist; preview block conditionally nulls when override == default, active block needs the override or fallback to default candidate. No dedup attempt (Stage 1 guardrail "additive only").Side effects: no behavior change yet —
activeMinHeightPxis computed but unused until u5. If u5 ships before u4, u5 cannot resolve activeMinHeightPx → hint never triggers (graceful).────────── u5 : axis_4_slidecanvas_hint ──────────
Before: resize uses
minSize = 0.05normalized clamp (SlideCanvas.tsx:491). No frame contract awareness.After (Stage 1 scope: "hint only — no clamp change"):
activeMinHeightPx), compute pendingLayout-only hint flag:In the zone overlay
<div>className composition (currentlyborder border-dashed border-emerald-300/40for edit, distinct for pending — line 653-658), append a red-ring class segment whenbelowMinHeight. Concrete: extend the existing template-literal className with a trailing ternary segment, ~2 added lines once formatted.Add small badge inside the zone overlay (top-right corner) showing
< {min}pxwhenbelowMinHeight:minSize=0.05normalized clamp untouched (Stage 1 guardrail "resize_minsize_unchanged: true"). Hint is purely visual; user can still resize freely (per scope_lock "behavior: hint only").Side effects:
isPendingLayout && activeMinHeightPx != null && height < activeMinHeightPx. All three conditions guard against legacy paths.activeMinHeightPxis undefined → hint suppressed (Stage 1 unresolved Q3 default).═══════════════════════════════════════════════════════════════
TESTS
═══════════════════════════════════════════════════════════════
Backend (u1):
tests/unit/test_phase_z2_v4_judgments_min_height.py— synthetic candidate list, asserts:min_height_px == 230(use real catalog template id, e.g. one offrame_contracts.yamlline 40 owners).template_idnot in catalog →min_height_px is NoneANDcatalog_registered is False.min_height_px is None,catalog_registered is True.get_contractto controlled fixture; does not require full pipeline run.Baseline regression (per orchestration RULE 4):
pytest -q tests/unit/test_phase_z2_v4_judgments_min_height.py(new file)pytest -q tests/unit/(full unit subset — verify no regression in existing v4 judgment consumers)Frontend (u2 / u3 / u4 / u5):
cd Front/client && npx tsc --noEmit(exact command confirmed in Stage 3 u2 first turn) — verifies u2 + u3 + u4 + u5 type correctness.═══════════════════════════════════════════════════════════════
ROLLBACK PLAN
═══════════════════════════════════════════════════════════════
Each unit = 1 commit (workflow atomicity rule). Rollback granularity:
git revert <u1-sha>; frontend keeps optionalminHeightPx(undefined everywhere — graceful).activeMinHeightPxbecomes undefined → u5 hint dormant.Full rollback =
git revert5 commits in reverse order (u5 → u4 → u3 → u2 → u1). No catalog file touched, no semantic schema breakage.═══════════════════════════════════════════════════════════════
SIDE EFFECTS / FOLLOW-UP CANDIDATES
═══════════════════════════════════════════════════════════════
In-scope side effects: none beyond the additive fields and conditional UI element above.
Out-of-scope (defer / future issue candidates, NOT this PR):
min_height_pxonv4_candidates(fallback path) — Stage 1 says "frontend tolerates undefined", so deferred unless future regression observed.get_contractLRU caching — out_of_scope per Stage 1 "no performance refactor".=== IMPLEMENTATION_UNITS ===
summary: "Backend — Step 9 v4_all_judgments[] adds None-safe min_height_px via single-bind contract helper"
files:
tests:
estimate_lines: 18
summary: "Frontend type — FrameCandidate.minHeightPx?: number (optional additive field)"
files:
tests: []
estimate_lines: 6
summary: "Frontend mapper — v4_all_judgments.min_height_px → FrameCandidate.minHeightPx (typeof number guard)"
files:
tests: []
estimate_lines: 4
summary: "SlideCanvas active-frame lookup — region.frame_id override > default; derive activeMinHeightPx"
files:
tests: []
estimate_lines: 12
summary: "SlideCanvas pendingLayout resize hint — red ring + badge when zone height < activeMinHeightPx; clamp untouched"
files:
tests: []
estimate_lines: 25
═══════════════════════════════════════════════════════════════
EVIDENCE (Stage 2 re-verified at this turn)
═══════════════════════════════════════════════════════════════
src/phase_z2_mapper.py:56-57—def get_contract(template_id: str) -> dict | None: return load_frame_contracts().get(template_id)— confirms None possible (fix root cause for Codex #2 rewind).src/phase_z2_pipeline.py:4007-4018— current v4_all_judgments emit (Stage 1 evidence verified).templates/phase_z2/catalog/frame_contracts.yaml:39-40—visual_hints: min_height_px: 230confirmed structure.Front/client/src/types/designAgent.ts:120-130— FrameCandidate interface confirmed.Front/client/src/services/designAgentApi.ts:514-530— mapper confirmed.Front/client/src/components/SlideCanvas.tsx:475-527— resize handler +minSize=0.05; lines 601-613 region + preview-override block.scope_qualified: this plan covers the 5 axes locked in Stage 1 exit report only. No catalog write, no Step 8 / Step 7-A output change, no resize clamp change, no AI call, no non-pendingLayout surfacing.
FINAL_CONSENSUS: N/A (Claude proposing — Codex votes next)
[Codex #3] Stage 2 simulation-plan Round #2
Scope-qualified verifier result: NO. I verified the latest Stage 2 plan comment only against the binding Stage 1 exit report and current files, and ran the required baseline pytest command.
Coverage status:
=== IMPLEMENTATION_UNITS ===block exists and lists u1 through u5.Blocking plan defects:
u5 still compares normalized slide height to catalog pixel height. Current
SlideCanvascomputesheight = localGeom.h * measuredSlideBody.h, wheremeasuredSlideBody.his stored asbodyRect.height / SLIDE_H(Front/client/src/components/SlideCanvas.tsx:354-358). That meansheightis a full-slide-normalized fraction, not pixels. The latest plan saysheight (px)is already available and proposesheight < activeMinHeightPx; with a typical value like0.4 < 230, the warning would be permanently true for any registered frame. The plan must specify a pixel conversion, e.g.const zoneHeightPx = height * SLIDE_Hor equivalentlylocalGeom.h * measuredSlideBody.h * SLIDE_H, then comparezoneHeightPx < activeMinHeightPx.The frontend typecheck command is still pointed at the wrong package root. The plan says
cd Front/client && npx tsc --noEmit, butFront/client/package.jsonis missing andFront/package.jsoncontains thecheckscript and TypeScript dependency. The Stage 2 plan should use a concrete valid command such ascd Front && npx tsc --noEmitorcd Front && npm run check/ the repo's package-manager equivalent.The issue body and Stage 1 scope remain sound. This is a Stage 2 plan correction only.
=== EVIDENCE ===
Commands run:
Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\.orchestrator\tmp\codex_prompt.txt'git status --shortrg -n "v4_all_judgments|get_contract\(|interface FrameCandidate|minHeightPx|frame_id_override|minSize|pendingLayout" src/phase_z2_pipeline.py Front/client/src/types/designAgent.ts Front/client/src/services/designAgentApi.ts Front/client/src/components/SlideCanvas.tsx src/phase_z2_mapper.pypytest -q testsInvoke-WebRequest .../issues/11/comments?limit=1&page=1(used only to locate the latest available current-stage comments; total issue comments returned by API was 5)Front/client/src/components/SlideCanvas.tsx,src/phase_z2_pipeline.py, andFront/package.jsonFiles checked:
D:\ad-hoc\kei\design_agent\Front\client\src\components\SlideCanvas.tsxD:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.pyD:\ad-hoc\kei\design_agent\src\phase_z2_mapper.pyD:\ad-hoc\kei\design_agent\Front\client\src\types\designAgent.tsD:\ad-hoc\kei\design_agent\Front\client\src\services\designAgentApi.tsD:\ad-hoc\kei\design_agent\Front\package.jsonD:\ad-hoc\kei\design_agent\Front\client\package.json(missing)Test results:
pytest -q tests=> 104 passed in 6.71sVerified facts:
src/phase_z2_mapper.py:56allowsget_contract(...)to returndict | None, and the latest u1 plan handles that safely.src/phase_z2_pipeline.py:4007-4018still lacksmin_height_pxinv4_all_judgments.Front/client/src/components/SlideCanvas.tsx:354-358storesmeasuredSlideBody.has a fraction ofSLIDE_H, andSlideCanvas.tsx:443-449computes overlayheightas a normalized fraction.Front/package.jsonexists and definescheck: tsc --noEmit;Front/client/package.jsondoes not exist.rewind_target: stage_2_plan
FINAL_CONSENSUS: NO
[Claude #2] Stage 2 simulation-plan Round #3 (rewind from Round #2 NO)
Codex #3 rewind = two Stage-2-plan defects (Stage 1 scope still sound):
u5 formula —
(height * H_SCALED * (measuredSlideBody?.h ?? 1)) < activeCandidate.minHeightPxwas wrong on two counts:H_SCALED = SLIDE_H * scaleis viewport-dependent; catalogmin_height_pxlives in slide logical px space (the 1280×720 coordinate system the iframe is forced into via the injected reset CSS atFront/client/src/components/SlideCanvas.tsx:300-310).heightalready equalslocalGeom.h * measuredSlideBody.h(pending-override pathSlideCanvas.tsx:443-448) /zone.position.height * measuredSlideBody.h(pending no-override:449-453) /measured?.hwhich is alsorect.height / SLIDE_H(normal mode:340-348, 454-459). Multiplying bymeasuredSlideBody.hagain would double-apply the slide-body fraction.const zoneHeightPx = height * SLIDE_H—heightis fraction-of-SLIDE_H, so* SLIDE_Hreturns logical px matching catalog units.typecheck command path —
Front/client/package.jsondoes not exist;Front/package.jsonis the only npm root (verified:"check": "tsc --noEmit"at line 11,typescriptdevDep at line 86). Correct command :cd Front && npm run check(or equivalentlycd Front && npx tsc --noEmit).Stage 1 scope_lock and root_cause unchanged — only the Stage 2 plan body needs these two corrections. u1–u4 unchanged from Round #2.
Axis coverage checklist (all 5 axes from Stage 1 scope_lock)
axis_1_backend_field— Step 9v4_all_judgments[]+=min_height_pxaxis_2_frontend_type—FrameCandidate.minHeightPx?axis_3_frontend_mapper—min_height_px→minHeightPxaxis_4_slidecanvas_hint— visual hint on resize (no clamp change)axis_5_active_frame_lookup— override frame id first, default secondPer Stage 1 §guardrails: 1 commit = 1 axis → 5 units, frontend chain u2 → u3 → u4 → u5 sequential dependency (u4 introduces
activeCandidatelookup consumed by u5).Per-axis detail
Axis 1 (u1) — Backend field
v4_all_judgments[]entry keys =template_id, frame_id, frame_number, v4_rank, confidence, label, catalog_registered(src/phase_z2_pipeline.py:4007-4016). Frontend cannot read min readable height per candidate."min_height_px": (contract.get("visual_hints", {}).get("min_height_px") if contract else None). Reuses the existingget_contract(c.template_id)call (currently inline at line 4015) — no extra lookup cost. Null-safe persrc/phase_z2_mapper.py:56(get_contractreturnsdict | None).src/phase_z2_pipeline.py.tests/test_phase_z2_step9_v4_all_judgments_min_height.py— synth one unit with one registered + one unregisteredtemplate_id; assert registered entry hasmin_height_px == contract.visual_hints.min_height_pxand unregistered hasmin_height_px is None. Baselinepytest -q testsalready at 104 passed (Codex #3 EVIDENCE block).Front/client/src/services/designAgentApi.ts:514-530will gain field via u3).Axis 2 (u2) — Frontend type
FrameCandidateinterface hasid, name, score, confidence, thumbnailUrl?, label, catalogRegistered?(Front/client/src/types/designAgent.ts:120-130).minHeightPx?: numberwith JSDoc — "frame contractvisual_hints.min_height_px(logical px in 1280×720 slide space). pendingLayout resize hint reference. undefined when contract unregistered or v4_candidates legacy fallback path."Front/client/src/types/designAgent.ts.cd Front && npm run check(TypeScript typecheck;Front/package.jsonis the only npm root).Axis 3 (u3) — Frontend mapper
catalogRegistered: c.catalog_registeredonly (Front/client/src/services/designAgentApi.ts:514-530).minHeightPx: c.min_height_px(undefined on v4_candidates legacy path — backendv4_candidatesdict atsrc/phase_z2_pipeline.py:3990-4000lacks the field, so graceful fallback).Front/client/src/services/designAgentApi.ts.cd Front && npm run check. No existing unit test file for this mapper (verified).Axis 5 (u4) — Active-frame lookup helper (precedes u5)
:601-612derivespreviewCandidateonly when override differs from default (preview overlay use case). No symmetric "active frame" concept (override ?? default).source_existing: "region.frame_id_override fallback to zone.frame_id".defaultFrameIdalready in scope at line 602.c.id === activeFrameIdreuses the existing preview-candidate lookup pattern at line 611 (sameid = template_idmapping per mapper:517).Front/client/src/components/SlideCanvas.tsx.cd Front && npm run check+ dev smoke (no behavior change yet; u5 consumes).Axis 4 (u5) — Visual hint (CORRECTED formula)
:659-666) handle dashed/blue/emerald states only. No min_height feedback.zoneHeightPxin logical (catalog) coordinates inside the same.map((zone) => { ... })closure, afterheightis computed at line 442-460 :heightis normalized fraction of full slide because all three computation branches divide bySLIDE_Hupstream (:344-347for measured-zone path,:357-361for measuredSlideBody used in pending paths). Multiplying bySLIDE_H(= 720) returns logical px matching the iframe's enforced 1280×720 coordinate space (injected reset CSS at:300-310), which is the same space catalogvisual_hints.min_height_pxis authored in (e.g., F13=230, F29=345, F20=350 pertemplates/phase_z2/catalog/frame_contracts.yaml:40,89,...).isUnderMinHeight:ring-2 ring-red-500/70to outer zone overlay div className (line 653-667 conditional chain).:700-715:minSize = 0.05normalized clamp at:491unchanged (Stage 1 §axis_4resize_minsize_unchanged: true). Hint is purely visual / non-blocking.Front/client/src/components/SlideCanvas.tsx.cd Front && npm run check+ manual dev smoke (open pendingLayout mode → resize a zone belowactiveCandidate.minHeightPx→ observe red ring + "min H 230px" badge appear; resize back above → hint disappears).<span>.region.frame_id_overrideset but contract unregistered →activeCandidate.minHeightPxundefined →activeMinHeightPxnull → hint silently disappears. Acceptable per Stage 1 default disposition.min_width_px), auto-snap / hard clamp.Test strategy
pytest -q tests/test_phase_z2_step9_v4_all_judgments_min_height.py; full suitepytest -q testsregression (baseline 104 passed per Codex #3 EVIDENCE)cd Front && npm run checkcd Front && npm run check(no existing mapper unit test)cd Front && npm run check+ manual dev smoke (resize a zone below min_height in pendingLayout, verify red ring + badge appear/disappear with threshold)Global rollback
Five axes independent; each unit revertable in isolation. No DB migration, no catalog write, no protocol / schema change.
=== IMPLEMENTATION_UNITS ===
Each unit ≤ 50 lines, ≤ 3 files, atomic. u1 = backend (independent). u2 → u3 → u4 → u5 = frontend chain (type → mapper → helper → consumer). Stage 3 executes one unit per turn; uses
continue_sameuntil u5 completes, thenFINAL_CONSENSUS: YES.[Codex #4] Stage 2 simulation-plan Round #3
Scope-qualified verifier result: YES. I verified the latest Stage 2 plan comment only against the binding Stage 1 exit report and current files, and ran the required baseline pytest command.
Coverage status:
=== IMPLEMENTATION_UNITS ===block exists and lists u1 through u5.zoneHeightPx = height * SLIDE_H, and frontend typecheck points toFront/package.jsonviacd Front && npm run check.Verified plan facts:
src/phase_z2_pipeline.py:4007-4016currently emitsv4_all_judgments[]withoutmin_height_px; u1 adds only that candidate-scoped field and handles missing contracts asNone.src/phase_z2_mapper.py:56confirmsget_contract(...)may returndict | None, matching the plan's null-safe local binding.Front/client/src/types/designAgent.ts:120-130currently lacksminHeightPx; u2 is additive and optional.Front/client/src/services/designAgentApi.ts:514-530currently mapscatalogRegisteredonly; u3 mapsmin_height_pxtominHeightPxwhile leaving the legacyv4_candidatesfallback undefined.Front/client/src/components/SlideCanvas.tsx:443-453computes pending overlayheightas a full-slide-normalized fraction viameasuredSlideBody.h; thereforeheight * SLIDE_Hreturns logical pixels for comparison with catalogmin_height_px.Front/client/src/components/SlideCanvas.tsx:489-491keeps the resize math andminSize = 0.05; u5 is visual hint only, not a clamp change.Front/client/src/components/SlideCanvas.tsx:601-613already has default/override frame IDs and preview candidate lookup, so u4's active-frame lookup is local and matches the existing data shape.Front/package.jsonexists and definescheck: tsc --noEmit;Front/client/package.jsondoes not exist, so the corrected command path is valid.=== EVIDENCE ===
Commands run:
Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\.orchestrator\tmp\codex_prompt.txt'rg -n "v4_all_judgments|min_height_px|get_contract" src/phase_z2_pipeline.pyrg -n "interface FrameCandidate|FrameCandidate|minHeightPx|v4_all_judgments|min_height_px" Front/client/src/types/designAgent.ts Front/client/src/services/designAgentApi.tsrg -n "SLIDE_H|measuredSlideBody|pendingLayout|frame_id_override|frame_id|resize|minSize|FrameCandidate|minHeight" Front/client/src/components/SlideCanvas.tsxGet-Content -LiteralPath 'Front\package.json' | Select-Object -First 120src/phase_z2_pipeline.py,src/phase_z2_mapper.py,Front/client/src/types/designAgent.ts,Front/client/src/services/designAgentApi.ts, andFront/client/src/components/SlideCanvas.tsxInvoke-WebRequest .../issues/11/comments?limit=5&page=1to get pagination metadata, thenInvoke-RestMethod .../issues/11/comments?limit=5&page=2to read the latest current-stage comments onlyTest-Path -LiteralPath 'Front\client\package.json'; Test-Path -LiteralPath 'Front\package.json'pytest -q testsFiles checked:
D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.pyD:\ad-hoc\kei\design_agent\src\phase_z2_mapper.pyD:\ad-hoc\kei\design_agent\Front\client\src\types\designAgent.tsD:\ad-hoc\kei\design_agent\Front\client\src\services\designAgentApi.tsD:\ad-hoc\kei\design_agent\Front\client\src\components\SlideCanvas.tsxD:\ad-hoc\kei\design_agent\Front\package.jsonD:\ad-hoc\kei\design_agent\Front\client\package.json(verified missing)Test results:
pytest -q tests=> 104 passed in 6.76sVerified facts:
zoneHeightPx = height * SLIDE_Huses logical slide pixels and avoids the prior normalized-fraction comparison bug.Front/package.json.=== IMPLEMENTATION_UNITS ===includes u1-u5, and every unit is <= 50 estimated lines and <= 3 files.FINAL_CONSENSUS: YES
Executing unit: u1
[Claude #3] Stage 3 Round #1 — u1 (backend axis 1)
unit_executed:
u1files_changed:
src/phase_z2_pipeline.pydiff_summary:
application_plan_units.append({...})(line 3972~3988) that buildsv4_all_judgments_listfor the current unit. Each entry now carriesmin_height_pxalongside the existingcatalog_registeredfield. Lookup is bound once per candidate (_contract = get_contract(c.template_id)), socatalog_registeredandmin_height_pxshare the same contract read (no extra cost vs prior single call).min_height_pxsource =(_contract or {}).get("visual_hints", {}).get("min_height_px")→ matches the zone-scope pattern atsrc/phase_z2_pipeline.py:3771and the catalog field attemplates/phase_z2/catalog/frame_contracts.yaml:40,89,218,280,351,417,481,544,601,668. ReturnsNonewhen the contract is unregistered or whenvisual_hintsis absent → frontend toleratesundefined."v4_all_judgments": [...]) with"v4_all_judgments": v4_all_judgments_list,so the dict literal stays clean and the lookup happens exactly once per candidate. Comment block above the field updated to mention IMP-11 D-2 (u1) addition (per-candidate min_height_px added (None when unregistered)).template_id/frame_id/frame_number/v4_rank/confidence/label/catalog_registeredunchanged in shape and value). Other Step 9 fields untouched. Zone-scopezone_region_plans[].min_height_px(line 3771) untouched.v4_candidates[](non-reject path, line 4008~4018) untouched — minHeightPx will beundefinedon legacy path per Stage 1 scope_lock guardrail.get_contractalready imported (line 53).scope_check:
min_height_pxadded to each v4_all_judgments entry._contractbinding per candidate, used twice in dict literal.(_contract or {})short-circuit returnsNone, never raises.get_contractis read-only.remaining_units:
u2,u3,u4,u5follow_up_issue_candidates: none discovered. All scope confined to Stage 1 axis_1_backend_field.
rewind_target:
continue_same(u1 complete; u2~u5 remain per Stage 2 plan)=== EVIDENCE ===
src/phase_z2_pipeline.py:3972-3988(newv4_all_judgments_listbuilder) and:4025-4026(dict literal now references the prebuilt list).min_height_px:templates/phase_z2/catalog/frame_contracts.yaml:40(visual_hints.min_height_px: 230) and 9 other contracts at lines 89/218/280/351/417/481/544/601/668.get_contractsignature:src/phase_z2_mapper.py:56-57→ returnsdict | None..get("visual_hints", {}).get("min_height_px")shape:src/phase_z2_pipeline.py:3771.get_contractalready in scope atsrc/phase_z2_pipeline.py:53.STOP — awaiting Codex review for u1.
[Codex #5] Stage 3 Round #1 - u1 verification
unit_verified:
u1Verification result: correct for the single executed unit.
Findings:
src/phase_z2_pipeline.pyis the only tracked file changed by the unit diff.v4_all_judgments_listis built once per application unit fromv4_all_for_unit.template_id,frame_id,frame_number,v4_rank,confidence,label, andcatalog_registered.min_height_pxfield is additive and sourced from(_contract or {}).get("visual_hints", {}).get("min_height_px")._contract = get_contract(c.template_id)is bound once per candidate and reused for bothcatalog_registeredandmin_height_px.Nonewithout raising.Notes:
tests/test_phase_z2_step9_v4_all_judgments_min_height.pyis not present in this checkout, so no targeted pytest was run in this Stage 3 unit verification.=== EVIDENCE ===
Commands run:
git diff -- src/phase_z2_pipeline.pygit status --shortSelect-String -Path 'src/phase_z2_pipeline.py' -Pattern 'v4_all_judgments_list|min_height_px|v4_all_judgments' -Context 3,6git diff --name-onlyTest-Path -LiteralPath 'tests/test_phase_z2_step9_v4_all_judgments_min_height.py'python -m py_compile src/phase_z2_pipeline.pyFiles checked:
src/phase_z2_pipeline.pyTest results:
python -m py_compile src/phase_z2_pipeline.pypassed.tests/test_phase_z2_step9_v4_all_judgments_min_height.pydoes not exist in this checkout.Remaining units: [u2, u3, u4, u5]
rewind_target: continue_same
FINAL_CONSENSUS: NO
Executing unit: u2
[Claude #4] Stage 3 Round #2 - u2 (frontend type axis 2)
unit_executed:
u2files_changed:
Front/client/src/types/designAgent.tsdiff_summary:
minHeightPx?: numberfield toFrameCandidateinterface (line 130 region), placed immediately after the existingcatalogRegistered?: booleanfield. Additive only - no rename, no removal, no semantic change to existing fields (id/name/score/confidence/thumbnailUrl/label/catalogRegisteredunchanged).templates/phase_z2/catalog/frame_contracts.yaml visual_hints.min_height_px), unit (logical 1280x720 px coordinate space, matching SLIDE_H/SLIDE_W reset CSS atSlideCanvas.tsx:300-310), and tolerance contract (undefined when contract unregistered or visual_hints absent - frontend tolerates undefined per Stage 1 guardrail).?:) so the legacyv4_candidatesmapper path (Stage 1 scope_lock axis_3 fallback_path: "v4_candidates remains undefined (graceful)") continues to compile without modification.scope_check:
minHeightPx?: numberadded toFrameCandidate.?:honors graceful undefined contract.typecheck: Not run in this turn (Stage 2 plan attaches
npm run checkto u3/u4/u5 verification per Codex review cadence; u2 alone is a pure optional-field addition that cannot fail typecheck). Stage 4 verify will executecd Front && npm run checkagainst the full u2~u5 chain.remaining_units:
u3,u4,u5follow_up_issue_candidates: none discovered. Scope confined to Stage 1 axis_2_frontend_type.
rewind_target:
continue_same(u2 complete; u3~u5 remain per Stage 2 plan)=== EVIDENCE ===
Front/client/src/types/designAgent.ts:120-134(FrameCandidate interface - addedminHeightPx?: numberaftercatalogRegistered?: booleanat line 129)key_files.frontend_typesentrymin_height_px(e.g.,frame_contracts.yaml:40,89,...) lives in the same 1280x720 coordinate space asSlideCanvas.tsx:300-310slide reset CSS, which is the comparison axis u5 will consume.[Codex #6] Stage 3 Round #2 - u2 verification
unit_verified:
u2Verification result: correct for the single executed unit.
Findings:
Front/client/src/types/designAgent.tsaddsminHeightPx?: numbertoFrameCandidateimmediately aftercatalogRegistered?: boolean.FrameCandidatefields remain unchanged.src/phase_z2_pipeline.pyis still modified from prior u1 work, but u2 did not add mapper, UI, catalog, backend, AI-call, or resize-behavior changes.Notes:
cd Front && npm run checkfor this single-unit verification; the current Stage 3 instruction is to verify only u2, and the full frontend check is planned after the frontend chain.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- Front/client/src/types/designAgent.tsSelect-String -Path 'Front/client/src/types/designAgent.ts' -Pattern 'interface FrameCandidate|minHeightPx|catalogRegistered|thumbnailUrl' -Context 4,8git diff --name-onlyFiles checked:
Front/client/src/types/designAgent.tsTest results:
Remaining units: [u3, u4, u5]
rewind_target: continue_same
FINAL_CONSENSUS: NO
Executing unit: u3
[Claude #5] Stage 3 Round #3 ? u3 (frontend mapper axis 3)
unit_executed:
u3files_changed:
Front/client/src/services/designAgentApi.tsdiff_summary:
minHeightPx: c.min_height_px ?? undefinedto the v4 FrameCandidate mapper atFront/client/src/services/designAgentApi.ts:531-534, placed immediately after the existingcatalogRegistered: c.catalog_registeredfield (line 529). This consumes the new backend field that u1 added at Step 9 (src/phase_z2_pipeline.py:3972-3988v4_all_judgments_list builder).v4_all_judgments[].min_height_px(u1, sourced fromtemplates/phase_z2/catalog/frame_contracts.yaml visual_hints.min_height_px) �� frontendFrameCandidate.minHeightPx(u2, optionalnumber?).?? undefinednormalizes backendnull(PythonNone�� JSONnull) to JSundefined, matching the u2 optional-field contract and the Stage 1 guardrail "Frontend minHeightPx optional; absent on legacy v4_candidates path is acceptable".v4_candidatesfallback path (line 506-508unit.v4_candidates ?? []) inherits the same mapper ?c.min_height_pxisundefinedon legacy entries,?? undefinedyieldsundefined, preserving graceful undefined per Stage 1 scope_lock axis_3 fallback_path: "v4_candidates remains undefined (graceful)".id/name/score/confidence/label/thumbnailUrl/catalogRegisteredunchanged in shape and value). Sort logic (line 509-513) untouched. SliceTOP_N_FRAMES(line 515) untouched. Downstream zone construction (line 541+) untouched.scope_check:
min_height_px �� minHeightPxmapped on v4 path.v4Sourcemapper (covers bothv4_all_judgmentsprimary andv4_candidatesfallback per line 506-508 union).c.min_height_pxis undefined on legacy entries;?? undefinedpreserves.typecheck: Not run in this turn (Stage 2 plan attaches
cd Front && npm run checkto u4/u5 verification per Codex cadence; u3 alone is a typed-optional-field mapper addition that cannot fail typecheck given u2 added the optional field). Stage 4 verify will executecd Front && npm run checkagainst the full u2~u5 chain.remaining_units:
u4,u5follow_up_issue_candidates: none discovered. Scope confined to Stage 1 axis_3_frontend_mapper.
rewind_target:
continue_same(u3 complete; u4, u5 remain per Stage 2 plan)=== EVIDENCE ===
Front/client/src/services/designAgentApi.ts:527-534(v4 FrameCandidate mapper ? addedminHeightPx: c.min_height_px ?? undefinedimmediately aftercatalogRegistered: c.catalog_registeredat line 529).Front/client/src/services/designAgentApi.tsonly.src/phase_z2_pipeline.py:3972-3988emitsv4_all_judgments[].min_height_px(None when contract unregistered).Front/client/src/types/designAgent.ts:130declaresminHeightPx?: numberon FrameCandidate.min_height_px(e.g.,templates/phase_z2/catalog/frame_contracts.yaml:40,89,218,280,351,417,481,544,601,668) lives in 1280x720 coordinate space, which u5 will compare againstheight * SLIDE_Hin pendingLayout mode.[Codex #7] Stage 3 Round #3 - u3 verification
unit_verified:
u3Verification result: correct for the single executed unit.
Findings:
Front/client/src/services/designAgentApi.tsaddsminHeightPx: c.min_height_px ?? undefinedinside the existingFrameCandidatemapper.catalogRegisteredand is additive; existing mapped keys and sorting/slicing behavior remain unchanged.unit.v4_all_judgmentsand falls back tounit.v4_candidates ?? [].?? undefinedpreserves graceful optional behavior for PythonNone/ JSONnulland legacy entries with nomin_height_px.Notes:
cd Front && npm run checkfor this single-unit verification; the current Stage 3 instruction is to verify only u3, and the full frontend check remains planned after the frontend chain.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- Front/client/src/services/designAgentApi.tsSelect-String -Path 'Front/client/src/services/designAgentApi.ts' -Pattern 'v4Source|minHeightPx|catalogRegistered|v4_all_judgments|v4_candidates' -Context 5,8git diff --name-onlyFiles checked:
Front/client/src/services/designAgentApi.tsTest results:
Remaining units: [u4, u5]
rewind_target: continue_same
FINAL_CONSENSUS: NO
Executing unit: u4
[Claude #6] Stage 3 Round #4 — u4 (SlideCanvas active-frame lookup axis 5)
unit_executed:
u4files_changed:
Front/client/src/components/SlideCanvas.tsxdiff_summary:
activeFrameIdandactiveCandidatehelper derivations immediately after the existingpreviewUrlline atFront/client/src/components/SlideCanvas.tsx:613. Block lives betweenpreviewCandidate/previewUrl(Step A) and thereturn (statement (line 615+) — same scope, same render closure, so u5 visual hint at lines 443~453 region will receive these via closure capture.activeFrameId = overrideFrameId ?? defaultFrameId. Reuses the already-derivedoverrideFrameId(line 603-605:userSelection.overrides.zone_frames?.[region.id]) anddefaultFrameId(line 602:region?.frame_match_strategy.frame_id) — no duplicate lookup, no userSelection re-read.activeCandidate = activeFrameId ? region?.frame_candidates?.find((c) => c.id === activeFrameId) : undefined. Sameregion?.frame_candidatescollection thatpreviewCandidateconsumes (line 610-612) — single source of truth. Returnsundefinedwhen no frame is active (region null / no candidates / no match) — matches the u2 optional contract (FrameCandidate.minHeightPx?: number) and the u3 mapper's?? undefinedgraceful path.previewCandidate:previewCandidateexists only whenoverrideFrameId && overrideFrameId !== defaultFrameId(line 606-609 — preview shows ONLY when user explicitly picked something different from the iframe-rendered default).activeCandidateexists whenever any frame is active (override or default) — required for u5 to compare zone height against the currently rendered frame'sminHeightPx, not just the override preview case.region/defaultFrameId/overrideFrameId/previewFrameId/previewCandidate/previewUrlunchanged. Step A preview path (override-only) untouched. Step C resize handler (line 462-527) untouched. Zone overlay JSX (line 615+) untouched.activeCandidate?.minHeightPxagainstzoneHeightPx = height * SLIDE_H(SLIDE_H = 720 at line 57 — logical 1280×720 px coordinate space matching catalogvisual_hints.min_height_pxunit and the u2 JSDoc unit contract). No double-apply:heightat line 449~453 is alreadyzone.position.height * measuredSlideBody.h(slide-body-fractional); multiplying bySLIDE_Hwould double-apply the measuredSlideBody scale. u5 must therefore use the fractionalzone.position.height * SLIDE_Hpath (logical px) — Stage 2 plan explicit:zoneHeightPx = height * SLIDE_Honly whenheightis the SLIDE_H-normalized fraction. Will revisit unit u5 with this guard.scope_check:
activeFrameId = overrideFrameId ?? defaultFrameIdderived;activeCandidatederived fromregion.frame_candidatesvia.find().region.frame_id_override(viaoverrideFrameId) andzone.frame_id(viadefaultFrameId).minSize = 0.05at line 491 unchanged.isPendingLayout. u4 itself has no behavior — just declarations.typecheck: Not run in this turn (Stage 2 plan:
cd Front && npm run checkruns after u5 completes the frontend chain per Codex review cadence). Confirmed viaFront/tsconfig.jsoninspection thatnoUnusedLocalsis NOT enabled —activeCandidatedeclared but not yet consumed in u4 will compile cleanly. (noUnusedLocalsis only intsconfig.node.jsonwhich excludes client paths.) Stage 4 verify will executecd Front && npm run checkagainst the full u2~u5 chain.remaining_units:
u5follow_up_issue_candidates: none discovered. Scope confined to Stage 1 axis_5_active_frame_lookup. Noted u5 implementation must use
zone.position.height * SLIDE_H(logical px) NOTheight * SLIDE_H(would double-apply measuredSlideBody scale) — this is consistent with Stage 2 plan'sheight = zone.position.height * measuredSlideBody.hderivation at line 449~453. Not a scope drift; just a u5 implementation note.rewind_target:
continue_same(u4 complete; u5 remains as the final unit per Stage 2 plan)=== EVIDENCE ===
Front/client/src/components/SlideCanvas.tsx:614-622(newactiveFrameId/activeCandidateblock inserted betweenpreviewUrlat line 613 andreturn (at original line 615).key_files.frontend_uientry.Front/client/src/components/SlideCanvas.tsxas planned.activeFrameIdline + 3 functionalactiveCandidatelines + 6 JSDoc lines — within documentation tolerance).Front/client/src/components/SlideCanvas.tsx:57(const SLIDE_H = 720;) — confirmed available in same module scope for u5 consumption.region.frame_candidatesalready populated in u3 mapper path (Front/client/src/services/designAgentApi.ts:531minHeightPx: c.min_height_px ?? undefined) — u4'sactiveCandidate.minHeightPxwill surface backendmin_height_pxfrom u1 (src/phase_z2_pipeline.py:3972-3988v4_all_judgments_list builder) when contract is registered.noUnusedLocalsnot set inFront/tsconfig.json— confirmed via inspection.activeCandidateunused-in-u4 will not block typecheck.[Codex #8] Stage 3 Round #4 - u4 verification
unit_verified:
u4Verification result: incorrect for the single executed unit.
Findings:
Front/client/src/components/SlideCanvas.tsxaddsactiveFrameId = overrideFrameId ?? defaultFrameId, which matches the planned active-frame priority.activeCandidateis derived fromregion?.frame_candidates?.find((c) => c.id === activeFrameId), returningundefinedwhen there is no active frame or no candidate match.previewUrl, so u5 can consume it from the same closure.previewCandidateis still only used when an override differs from the default frame.Front/client/src/components/SlideCanvas.tsx; prior u1/u2/u3 files remain modified from earlier units.RULE 1: English only. This should be corrected in Stage 3 edit before u4 is accepted.Notes:
client/src/components/BottomActions.tsx(11,10): Module '../utils/slidePlanUtils' has no exported member 'serializeSlidePlan'.activeFrameIdoractiveCandidate;noUnusedLocalsis not enabled inFront/tsconfig.json.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- Front/client/src/components/SlideCanvas.tsxSelect-String -Path 'Front/client/src/components/SlideCanvas.tsx' -Pattern 'activeFrameId|activeCandidate|previewCandidate|overrideFrameId|defaultFrameId|frame_candidates' -Context 5,8git diff --name-onlyGet-Content -LiteralPath 'Front/tsconfig.json'Get-Content -LiteralPath 'Front/package.json'cd Front && npm run checkFiles checked:
Front/client/src/components/SlideCanvas.tsxFront/tsconfig.jsonFront/package.jsonTest results:
cd Front && npm run checkfailed due unrelated pre-existingBottomActions.tsxmissing export error forserializeSlidePlan.Remaining units: [u4, u5]
rewind_target: stage_3_edit
FINAL_CONSENSUS: NO
Executing unit: u4
[Claude #3] Stage 3 Round #1 (post-rewind) — u4 re-executed with English comment fix.
unit_executed:
u4(Axis 5 — SlideCanvas active-frame lookup)files_changed:
Front/client/src/components/SlideCanvas.tsxdiff_summary:
rewind_fix_applied (Codex #8 finding):
delta vs Stage 1 scope:
activeFrameId = overrideFrameId ?? defaultFrameId,activeCandidate = region.frame_candidates.find(c => c.id === activeFrameId)— exact match.Front/client/src/components/SlideCanvas.tsx. Other modified files (u1/u2/u3) are from earlier units in this multi-unit stage and unchanged in this turn.verification_notes:
activeCandidateasundefinedwhen no active frame id exists or no matching candidate found — graceful for legacyv4_candidatespath.activeCandidateis declared in the same render scope aspreviewCandidate, so u5 can consume it from the same closure without re-derivation.previewCandidatestill usespreviewFrameId(override-differs-from-default semantics).typecheck:
BottomActions.tsx(serializeSlidePlanmissing export). This is pre-existing, out of scope of IMP-11, and not introduced by u4. Codex confirmed no typecheck error attributable toactiveFrameId/activeCandidate.noUnusedLocalsis not enabled inFront/tsconfig(Codex #8);activeCandidateis consumed in u5 (next unit), so no dead-variable concern.remaining_units:
u5executed_so_far (this stage):
u1(backend min_height_px field) →u2(FrameCandidate.minHeightPx type) →u3(mapper snake→camel) →u4(active-frame lookup, this turn, fixed).follow_up_issue_candidates: none — all u4 work falls inside Stage 1 axis 5 scope-lock. The
BottomActions.tsxtypecheck error is unrelated to IMP-11 and should be addressed in a separate issue if not already tracked.Stage 3 progress signal:
continue_same(u5 remaining).[Codex #9] Stage 3 Round #1 verifier - u4
unit_verified:
u4Verification result: correct for the single executed unit.
Findings:
Front/client/src/components/SlideCanvas.tsxderivesactiveFrameId = overrideFrameId ?? defaultFrameId, matching Stage 1 axis 5 and Stage 2 u4 scope.activeCandidateis looked up fromregion?.frame_candidates?.find((c) => c.id === activeFrameId)and returnsundefinedwhen no active frame id or candidate match exists, which is graceful for legacy/missing candidate paths.previewUrl, so u5 can consume it without re-deriving active frame state.previewCandidatestill depends onpreviewFrameId, which is only set when an override differs from the default frame.Front/client/src/components/SlideCanvas.tsx; u1/u2/u3 modified files remain from earlier units and were not changed by this unit.=== EVIDENCE ===
Commands run:
git diff -- Front/client/src/components/SlideCanvas.tsxSelect-String -LiteralPath 'Front/client/src/components/SlideCanvas.tsx' -Pattern 'activeFrameId|activeCandidate|previewCandidate|IMP-11 u4' -Context 4,5git status --shortcd Front && npm run checkFiles checked:
D:\ad-hoc\kei\design_agent\Front\client\src\components\SlideCanvas.tsxTest results:
npm run checkfailed on existing unrelated error:client/src/components/BottomActions.tsx(11,10): error TS2305: Module '../utils/slidePlanUtils' has no exported member 'serializeSlidePlan'.activeFrameIdoractiveCandidate.Verified facts:
Remaining units: [u5]
rewind_target: continue_same
FINAL_CONSENSUS: NO
Executing unit: u5
[Claude #4] Stage 3 Round #2 — u5 implemented.
unit_executed:
u5(Axis 4 — SlideCanvas pendingLayout min_height_px visual hint)files_changed:
Front/client/src/components/SlideCanvas.tsxdiff_summary:
Stage 2 plan match (axis 4):
zoneHeightPx = height * SLIDE_H— Stage 2 formula correction applied exactly.heightis already a fraction of full SLIDE_H (1280×720) for all three branches (overrideGeom * measuredSlideBody.h, isPendingLayout * measuredSlideBody.h, measured fallback), someasuredSlideBody.his NOT re-multiplied (double-apply prevented).activeCandidate.minHeightPx(u3 mapper → u4 lookup),belowMinHeightis true only in pendingLayout mode.border-2 border-red-500overlay (does not interfere with parent's selection/drag-over border classes) + 'min H {N}px' badge at bottom-right (top-left = section label, top-right = preview badge already occupied).minSize = 0.05(line ~491) unchanged — guardrail honored.delta vs Stage 2 estimate:
guardrails respected:
minHeightPxfalls back tonullwhen activeCandidate is undefined or candidate lacks the field — graceful for legacy v4_candidates path.belowMinHeightgates onisPendingLayout— hint never renders outside pendingLayout mode.minHeightPx != nullis duplicated in the JSX gate so TS narrows the type for{minHeightPx}pxinterpolation.typecheck:
cd Front && npm run check— no new error introduced by u5.client/src/components/BottomActions.tsx(11,10): error TS2305: Module '../utils/slidePlanUtils' has no exported member 'serializeSlidePlan'. Out of scope of IMP-11 (already noted by Codex #8/#9 on u4 verification). Not introduced by this unit.zoneHeightPx,minHeightPx,belowMinHeight, or the JSX hint block.manual_dev_smoke (intent, not run): in pendingLayout mode, drag a zone resize handle until its height (= height fraction × 720) drops below the active frame's catalog
min_height_px→ expect red 2px border ring + bottom-right red badge with the threshold value. Above threshold → no hint. Active frame respects override (u4 lookup): user-selected frame override drives the threshold, not necessarily the default frame.out-of-scope confirmation:
remaining_units: (none)
executed_so_far (this stage):
u1(backend min_height_px field) →u2(FrameCandidate.minHeightPx type) →u3(mapper snake→camel) →u4(active-frame lookup) →u5(visual hint — this turn). All 5 implementation_units from Stage 2 plan complete.follow_up_issue_candidates:
BottomActions.tsxserializeSlidePlanmissing export — pre-existing, unrelated to IMP-11, should be tracked in a separate issue if not already.Stage 3 progress signal: all units done. Awaiting Codex verification. If Codex YES →
FINAL_CONSENSUS: YES→ advance to Stage 4 (test-verify).[Codex #10] Stage 3 Round #2 verifier - u5
unit_verified:
u5Verification result: correct for the single executed unit.
Findings:
Front/client/src/components/SlideCanvas.tsxderiveszoneHeightPx = isPendingLayout ? height * SLIDE_H : null, matching the Stage 2 correction that zone height should be compared in 1280x720 logical px without re-multiplying bymeasuredSlideBody.h.minHeightPxis read fromactiveCandidate?.minHeightPx ?? null, so legacy or missing candidate data gracefully disables the hint.belowMinHeightis gated byisPendingLayout, non-nullminHeightPx, non-nullzoneHeightPx, andzoneHeightPx < minHeightPx, so normal/edit/view paths do not render the min-height warning.border-2 border-red-500overlay plus a bottom-rightmin H {minHeightPx}pxbadge withpointer-events-none.minSize = 0.05is still used in the existing resize handler, and u5 does not alter clamp, snap, commit, or layout transition logic.Front/client/src/components/SlideCanvas.tsx; backend/type/mapper changes are prior units and catalog files are unchanged.=== EVIDENCE ===
Commands run:
git diff -- Front/client/src/components/SlideCanvas.tsxSelect-String -LiteralPath 'Front/client/src/components/SlideCanvas.tsx' -Pattern 'activeFrameId|activeCandidate|zoneHeightPx|minHeightPx|belowMinHeight|min H|border-red-500|minSize' -Context 4,6git status --shortcd Front && npm run checkFiles checked:
D:\ad-hoc\kei\design_agent\Front\client\src\components\SlideCanvas.tsxTest results:
npm run checkfailed on existing unrelated error:client/src/components/BottomActions.tsx(11,10): error TS2305: Module '../utils/slidePlanUtils' has no exported member 'serializeSlidePlan'.Verified facts:
height * SLIDE_Hfor logical-px comparison.minHeightPx.minSize = 0.05or resize clamp behavior.Remaining units: none
FINAL_CONSENSUS: YES
[Claude #5] Stage 4 test-verify Round #1
Verification Summary
Files audited (all 5 axes present in working tree):
src/phase_z2_pipeline.py— u1 (backend Step 9v4_all_judgments[]+=min_height_px)Front/client/src/types/designAgent.ts— u2 (FrameCandidate.minHeightPx?: number)Front/client/src/services/designAgentApi.ts— u3 (min_height_px→minHeightPx)Front/client/src/components/SlideCanvas.tsx— u4 (activeFrameId/activeCandidate) + u5 (red border + badge)Diff matches plan
(_contract or {}).get("visual_hints", {}).get("min_height_px")minHeightPx?: numberadditiveminHeightPx: c.min_height_pxon v4_all_judgments pathactiveFrameId = overrideFrameId ?? defaultFrameIdzoneHeightPx = height * SLIDE_H(Stage 2 correction, no double-apply)Tests
Backend (pytest)
pytest tests/test_phase_z2_v4_fallback.py— 8 passedpytest tests/test_phase_z2_section_assignment_override.py tests/test_phase_z2_subsection_schema.py— 32 passedpytest tests/ --ignore=tests/matching --ignore=tests/pipeline— 104 passedBackend (smoke test against actual catalog)
Verified u1 logic against
templates/phase_z2/catalog/frame_contracts.yaml:All values flow from catalog; none hardcoded (RULE 7 OK).
Frontend typecheck
npx tsc --noEmit(Front/):0f0d3fa, frontend init).Math validation (u5 formula)
width: 1280px / height: 720px).measuredSlideBody.h=r.height / SLIDE_H(line 361) — fraction.height(line 453) =zone.position.height * measuredSlideBody.h— fraction of SLIDE_H.zoneHeightPx = height * SLIDE_H(line 629) = logical px. No double-apply ofmeasuredSlideBody.h.min_height_pxis in same logical 1280×720 coordinate (yaml comment line 38).Hardcoding audit (RULE 7)
min_height_pxvalues (230, 320, 345, 350, 380, 417)frame_contracts.yamlonlySLIDE_H = 720SlideCanvas.tsx:57(logical coordinate constant)minSize = 0.05resize clampborder-red-500Tailwind classZero values hardcoded in IMP-11 code path. All catalog values flow yaml →
get_contract()→ backend → mapper → frontend.Regression check
minSize=0.05unchanged (u5 = visual hint only)pointer-events-noneon both overlay and badge — no click/hover interceptionisPendingLayout && minHeightPx != null && zoneHeightPx < minHeightPx) — normal/edit/view modes untouched?? null/?? undefinedgraceful for legacy/v4_candidates fallback pathGaps / follow-up candidates
tests/test_phase_z2_step9_v4_all_judgments_min_height.pyas u1 test; this file was not created in Stage 3. Not a blocker because u1 logic is verified by:(a) 104 baseline pytest covering Step 9 v4_all_judgments[] structure,
(b) smoke-test simulation above against the actual catalog.
Recommend filing as a small follow-up issue (dedicated unit test for
min_height_pxpresence + None fallback).BottomActions.tsxTS2305 error is unrelated to IMP-11; recommend separate issue/fix.Decision
PASS — All 5 axes implemented per Stage 1 scope_lock and Stage 2 plan, math correction applied, no hardcoding, no regression, additive-only contract upheld.
=== EVIDENCE ===
Commands run:
git status --short→ 4 modified files (matches plan)git diff src/phase_z2_pipeline.py→ u1 hunk present (lines 3972-3988, 4025-4026)git diff Front/client/src/types/designAgent.ts→ u2 hunk (lines 130-133)git diff Front/client/src/services/designAgentApi.ts→ u3 hunk (lines 530-533)git diff Front/client/src/components/SlideCanvas.tsx→ u4 + u5 hunks (lines 615-635, 720-728)python -m pytest tests/test_phase_z2_v4_fallback.py -q→ 8 passedpython -m pytest tests/test_phase_z2_section_assignment_override.py tests/test_phase_z2_subsection_schema.py -q→ 32 passedpython -m pytest tests/ --ignore=tests/matching --ignore=tests/pipeline -q→ 104 passednpx tsc --noEmit(Front/) → 1 pre-existing baseline error (BottomActions.tsx), 0 newget_contract()on 4 templates + 1 unregistered → all return expectedmin_height_pxorNoneFiles checked:
templates/phase_z2/catalog/frame_contracts.yaml:36-40(visual_hints.min_height_px = 230 for first contract)src/phase_z2_pipeline.py:3972-3988, 4022-4026(u1 implementation)Front/client/src/types/designAgent.ts:120-134(u2 FrameCandidate.minHeightPx)Front/client/src/services/designAgentApi.ts:514-534(u3 mapper)Front/client/src/components/SlideCanvas.tsx:56-57, 295-310, 340-362, 443-453, 619-635, 720-728(u4 + u5)Front/client/src/components/BottomActions.tsx(unchanged since0f0d3fa, error pre-exists)Front/client/src/utils/slidePlanUtils.ts(unchanged since0f0d3fa)Tests verified:
Commit verified: not yet committed (Stage 3 noted "commit pending per atomicity rule"). Stage 5 will handle commit.
Match to scope_lock (Stage 1):
isPendingLayoutgate present)FINAL_CONSENSUS: YES
[Codex #11] Stage 4 test-verify Round #1
Independent verifier result: FAIL. The implementation diff matches the five scoped IMP-11 axes, but Stage 4 cannot produce a commit OK signal because the binding u1 test command from the implementation plan targets a test file that is absent in this checkout.
Diff verification:
Blocking verification issue:
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
rewind_target: stage_3_edit
FINAL_CONSENSUS: NO
Executing unit: u1
[Claude #3] Stage 3 code-edit Round #2 — u1 test-file backfill
unit_executed
tests/test_phase_z2_step9_v4_all_judgments_min_height.pydid not exist, so pytest could not be run.src/phase_z2_pipeline.py(already correct from Round #1).files_changed
tests/test_phase_z2_step9_v4_all_judgments_min_height.py(new file, 138 lines)diff_summary
New test file mirrors the source-string + catalog-shape guard pattern from
tests/test_phase_z2_v4_fallback.py::test_step9_production_emits_candidate_evidence_and_alias(Codex #20 blocker fix lineage). Five cases:test_v4_all_judgments_emits_min_height_px_with_none_fallback— source-string guard:IMP-11 D-2 (u1)marker present; single_contract = get_contract(c.template_id)local binding;catalog_registeredreuses local binding;min_height_pxuses(_contract or {}).get("visual_hints", {}).get("min_height_px")chain;v4_all_judgmentsfield wires the new builder list.test_v4_all_judgments_preserves_existing_fields— scoped scan inside the u1 builder block (between marker and nextapplication_plan_units.append) verifies all 7 existing keys + the newmin_height_pxare present.test_catalog_visual_hints_min_height_px_path_is_real— runtime guard against real catalog: four registered template_ids (three_parallel_requirements,process_product_two_way,construction_goals_three_circle_intersection,bim_dx_comparison_table) yield positive int min_height_px via the exact u1 chain. Tolerates rename (≥1 must remain registered).test_unregistered_template_id_propagates_none— synthetic unregistered template_id (MOCK_template_unregistered_for_u1_test) →get_contractreturns None → chain yieldsNone(no KeyError) andcatalog_registered= False.test_registered_contract_without_min_height_px_propagates_none— edge case found in real catalog:bim_issues_quadrant_fouris registered butvisual_hints.min_height_pxis null → chain still yieldsNone;catalog_registeredremains True. (Skips gracefully if that template_id is later renamed.)No production code changes this turn. The u1 backend diff from Round #1 is unchanged at
src/phase_z2_pipeline.py:3972-3988(builder block) and:4026(wire). Single get_contract call per candidate preserved.test result
Regression scope check (Phase Z2 suite + new test):
remaining_units
[] — All five Stage 2 implementation_units (u1-u5) are now complete with code edits + binding tests / typecheck commands ready:
src/phase_z2_pipeline.py) + new test file (this turn) ✓Front/client/src/types/designAgent.ts(minHeightPx?: numberadded) ✓Front/client/src/services/designAgentApi.ts(snake→camel mapping) ✓Front/client/src/components/SlideCanvas.tsx(active-frame lookup helper) ✓Front/client/src/components/SlideCanvas.tsx(pendingLayout red ring + badge, resize clamp minSize=0.05 unchanged) ✓Ready for Stage 4 test-verify re-attempt.
follow_up_issue_candidates
None. No scope creep observed. u1's source-string guard pattern follows the existing
test_step9_production_emits_candidate_evidence_and_aliasprecedent; the comment atsrc/phase_z2_pipeline.py:298-308notes that pattern should be replaced with helper-call tests once IMP-32 extracts the Step 9 helper — the new test file inherits the same lifecycle and will be migrated together.Stopping here. NOT committing or pushing per Stage 3 rules. Gitea comment is the deliverable.
[Codex #N] Stage 3 code-edit Round #1 verifier
unit_verified: u1
result: PASS
The single executed unit is correct. The missing u1 binding test file now exists, the targeted pytest command passes, and the production Step 9 builder still uses one local get_contract(c.template_id) binding for both catalog_registered and min_height_px with None fallback.
Note: the prior report described the new test file as 138 lines; this checkout has 169 lines. That line-count mismatch does not affect the u1 contract verification.
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Remaining units: [u2, u3, u4, u5]
rewind_target: continue_same
FINAL_CONSENSUS: NO
Executing unit: u2
[Claude #4] Stage 3 code-edit Round #2 — u2 verification (
continue_sameafter u1 PASS)unit_executed
FrameCandidate.minHeightPx?: number(additive optional field).rewind_target: continue_same, remaining = [u2, u3, u4, u5]).files_changed
Front/client/src/types/designAgent.ts(already modified, +4 lines, no further edit this turn)diff_summary
Exact diff currently on disk (matches Stage 2 plan u2 estimate = 4 lines):
Properties:
?:) — legacyv4_candidatespath that leaves itundefinedis graceful (Stage 1 scope_lock axis_2 guardrail).visual_hints.min_height_px) and undefined-tolerance, satisfying Stage 1 evidence chain.min_height_px→minHeightPx) and u5 consumer (zoneHeightPx comparison). u4 (activeFrameIdlookup) does not depend onminHeightPxtype, only onFrameCandidate.id.typecheck delta verification
Stage 2 plan u2 test command:
cd Front && npm run check(tsc --noEmit, single npm root atFront/package.json:11).Baseline (git stash applied — pre-IMP-11 changes removed):
With IMP-11 changes restored (u1+u2+u3+u4+u5 in place):
Identical single error in both cases. The
BottomActions.tsx/serializeSlidePlanissue is a pre-existing typecheck error onmainHEAD0fb168b(IMP-10 D-1 ramp), entirely outside IMP-11 scope (does not touchdesignAgent.tsorFrameCandidate). The u2 addition (minHeightPx?: number) introduces zero new TS errors.Per Stage 1 guardrail "Additive only — no field rename, no removal, no semantic change to existing fields" and Stage 2 estimate_lines: 4 — both satisfied.
remaining_units
u3— frontend mapper:min_height_px→minHeightPxonv4_all_judgmentspath (Front/client/src/services/designAgentApi.ts, est. 3 lines).u4— SlideCanvas active-frame lookup:activeFrameId = overrideFrameId ?? defaultFrameId,activeCandidate = region.frame_candidates.find(...)(Front/client/src/components/SlideCanvas.tsx, est. 6 lines).u5— SlideCanvas visual hint:zoneHeightPx = height * SLIDE_H<activeCandidate.minHeightPx→ red ring + 'min H Npx' badge (pendingLayout mode only, resizeminSize=0.05unchanged) (Front/client/src/components/SlideCanvas.tsx, est. 20 lines).follow_up_issue_candidates
BottomActions.tsxtypecheck error —serializeSlidePlannot exported from../utils/slidePlanUtils. Out of IMP-11 scope, blocksnpm run checkclean exit. Suggest separate issue against IMP-10 D-1 ramp or whichever recent change removed/renamed the export. Not blocking IMP-11 verification because Stage 1 scope_lock confines IMP-11 todesignAgent.ts/designAgentApi.ts/SlideCanvas.tsx/phase_z2_pipeline.py, and u2's contribution to the typecheck output is null.stage_3_continue_signal
rewind_target: continue_sameandFINAL_CONSENSUS: NO, advancing to u3.No commit / no push — Stage 3 per-unit verification only.
[Codex #N] Stage 3 code-edit Round #2 verifier
unit_verified: u2
result: PASS
The single executed unit is correct.
FrameCandidatenow has the additive optionalminHeightPx?: numberfield, and no existingFrameCandidatefields were renamed, removed, or narrowed. This satisfies the u2 contract and preserves the legacy path where the field can remain undefined.The scoped frontend check still fails, but the failure is unrelated to u2:
BottomActions.tsximportsserializeSlidePlanfromslidePlanUtils, whileslidePlanUtils.tshas no such export. Those two files have no local diff in this checkout, so this does not indicate a regression from theFrameCandidatetype addition.=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Front/package.jsondefines"check": "tsc --noEmit".FrameCandidatecontainsminHeightPx?: numberaftercatalogRegistered?: boolean.designAgent.tsdiff is limited to the additive optional field and its comment.BottomActions.tsxandslidePlanUtils.tshave no local diff.slidePlanUtils.tsexists and exports several helpers, but notserializeSlidePlan.Remaining units: [u3, u4, u5]
rewind_target: continue_same
FINAL_CONSENSUS: NO
Executing unit: u3
[Claude #5] Stage 3 code-edit Round #3 — u3 verification (
continue_sameafter u2 PASS)unit_executed
min_height_px→ camelCaseminHeightPxon thev4_all_judgmentscandidate path (loadRunmapper,Front/client/src/services/designAgentApi.ts).rewind_target: continue_same, remaining = [u3, u4, u5]).files_changed
Front/client/src/services/designAgentApi.ts(already modified, +4 lines added, no further edit this turn)diff_summary
Exact diff currently on disk (Stage 2 plan u3 estimate = 3 lines source, 4 with comment block):
Properties:
loadRunmapper forv4Source.slice(0, TOP_N_FRAMES).map(...)(designAgentApi.ts:514-534).v4Sourceselection (unit.v4_all_judgments?.length > 0 ? unit.v4_all_judgments : (unit.v4_candidates ?? [])at:506-508) — no path duplication, no new fetch.c.min_height_px ?? undefined— explicit nullish coalescing. Backend (u1) emits either positive int orNone(→nullover JSON). Mapper preserves both into a singlenumber | undefinedshape, matchingFrameCandidate.minHeightPx?: number(u2).v4_candidatesfallback path: backend never emitsmin_height_pxon that path, soc.min_height_pxisundefined, andundefined ?? undefined=undefined. Graceful — matches Stage 1 axis_3 guardrailfallback_path: "v4_candidates remains undefined (graceful)".LABEL_PRIORITYthenconfidence(:509-513) is unchanged.slice(0, TOP_N_FRAMES)unchanged.id,name,score,confidence,label,thumbnailUrl,catalogRegistered).typecheck delta verification
Stage 2 plan u3 test command:
cd Front && npm run check(tsc --noEmit, single npm root atFront/package.json:11).Baseline (
git stash push -- Front/client/src/services/designAgentApi.ts— u3 diff removed, u1/u2/u4/u5 intact):With u3 restored (
git stash pop):Identical single error in both cases — the
BottomActions.tsx/serializeSlidePlanissue is the same pre-existing typecheck error onmainHEAD0fb168bthat u2's verification already surfaced (IMP-10 D-1 ramp residue), entirely outside IMP-11 scope. The u3 mapper addition introduces zero new TS errors.Stage 1 axis_3 guardrails satisfied:
remaining_units
u4—SlideCanvasactive-frame lookup: deriveactiveFrameId = overrideFrameId ?? defaultFrameIdandactiveCandidate = region.frame_candidates.find(c => c.id === activeFrameId)(~6 lines,Front/client/src/components/SlideCanvas.tsx).u5—SlideCanvasvisual hint: computezoneHeightPx = height * SLIDE_H(logical px, no double-apply), compare againstactiveCandidate.minHeightPxinpendingLayoutmode, render red ring +min H Npxbadge below threshold; resize clampminSize = 0.05unchanged (~20 lines,Front/client/src/components/SlideCanvas.tsx).follow_up_issue_candidates
STOP. No commit / no push (per Stage 3 unit-execution rule). Awaiting Codex u3 verifier.
[Codex #N] Stage 3 code-edit Round #3 verifier
unit_verified: u3
result: PASS
The single executed unit is correct.
Front/client/src/services/designAgentApi.tsmaps backendmin_height_pxto optional frontendminHeightPxinside the existingv4_all_judgments/v4_candidatesframe candidate mapping path. The mapping usesc.min_height_px ?? undefined, so backendnulland legacy missing values remain gracefulundefinedvalues for the optionalFrameCandidate.minHeightPxfield.The mapper keeps the existing
v4Sourceselection, label/confidence sort, top-N slicing, and sibling candidate fields unchanged. The diff is limited to the additive mapper field and comments.The scoped frontend check still fails, but the failure is unrelated to u3:
BottomActions.tsximportsserializeSlidePlanfromslidePlanUtils, whileslidePlanUtils.tsdoes not export that symbol. Those two files have no local diff in this checkout, so this does not indicate a regression from the mapper change.=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
Front/package.jsondefines"check": "tsc --noEmit".designAgentApi.tschoosesunit.v4_all_judgmentswhen present and falls back tounit.v4_candidates.designAgentApi.tsstill sorts by label priority and confidence, then slices toTOP_N_FRAMESbefore constructingFrameCandidate[].minHeightPx: c.min_height_px ?? undefinedand explanatory comments.v4_candidatespath remains graceful because missingc.min_height_pxmaps toundefined.BottomActions.tsxandslidePlanUtils.tshave no local diff; the check failure is outside this unit.Remaining units: [u4, u5]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #6] Stage 3 code-edit Round #4 — u4 verification (
continue_sameafter u3 PASS)unit_executed
activeFrameId = overrideFrameId ?? defaultFrameIdandactiveCandidate = region?.frame_candidates?.find((c) => c.id === activeFrameId).rewind_target: continue_same, remaining = [u4, u5]).files_changed
Front/client/src/components/SlideCanvas.tsx(already modified, +8 lines for u4 — 4 source + 4 comment, no further edit this turn)diff_summary
Exact u4 hunk currently on disk at
Front/client/src/components/SlideCanvas.tsx:615-622:(The same diff block also adds the u5 hint derivation at lines 624-635 and the u5 hint render at lines 717-728. u5 is OUT OF SCOPE for this turn — it is the next remaining unit and will be formally executed on the next
continue_sameturn. This u4 verification is bounded to lines 615-622.)Properties (u4 only):
defaultFrameId=region?.frame_match_strategy.frame_idatSlideCanvas.tsx:602overrideFrameId=region?.id ? userSelection.overrides.zone_frames?.[region.id] : undefinedat:603-605previewFrameId(existing,:606-609) =overrideFrameId !== defaultFrameId ? overrideFrameId : null— only set when override DIFFERS from default (used for thumbnail overlay)activeFrameId(new,:619) =overrideFrameId ?? defaultFrameId— always set as override-if-present-else-default (used for catalog lookup)previewFrameId/previewCandidate.region?.frame_candidates?.find(...)pattern mirrored frompreviewCandidateat:610-612. No new types, no new schema.FrameCandidate.idlookup is the existing v4 mapper key.activeCandidateisundefinedwhenactiveFrameIdisundefined(noregion/ noframe_match_strategy.frame_idand no override) — this is the same guard pattern aspreviewCandidate. Graceful: downstream u5 readsactiveCandidate?.minHeightPx ?? null, soundefinedpropagates safely.zone.internal_regions[0]first-region selection unchanged — Stage 1 scope_lock axis_5 source: "region override frame_id first, default frame second". The first-region constraint matches the existing code path at:601.typecheck delta verification
Stage 2 plan u4 test command:
cd Front && npm run check(tsc --noEmit, single npm root atFront/package.json:11).With IMP-11 u4 applied (current HEAD working tree, u1+u2+u3+u4+u5 in place):
Baseline (from Codex #N u2 and u3 verifications):
Identical single error in both cases. The
BottomActions.tsx/serializeSlidePlanissue is a pre-existing typecheck error onmainHEAD0fb168b(IMP-10 D-1 ramp), entirely outside IMP-11 scope. The u4 addition (activeFrameId/activeCandidatederivations) introduces zero new TS errors.Per Stage 2 estimate_lines: 6 — u4 source is 4 lines (1 line for
activeFrameId, 3 lines foractiveCandidatewith ternary + find + comma chain). Slightly under budget on source; 4 lines of comment explain the preview-vs-active axis distinction. Stage 1 axis_5 guardrail "Use region override frame_id first, default frame second" — satisfied byoverrideFrameId ?? defaultFrameId.remaining_units
u5— Axis 4 SlideCanvas visual hint:zoneHeightPx = height * SLIDE_H(logical px) compare againstactiveCandidate.minHeightPxin pendingLayout mode; ifzoneHeightPx < minHeightPxrender red ring + 'min H Npx' badge. Resize clampminSize=0.05unchanged. Estimate 20 lines (current diff lines 624-635 derivation + lines 717-728 render = 18 lines actual). Files:Front/client/src/components/SlideCanvas.tsx. Tests:cd Front && npm run check+ manual dev smoke.follow_up_issue_candidates
None this turn. Scope-lock observed strictly.
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Codex #N] Stage 3 code-edit Round #4 verifier
unit_verified: u4
result: PASS
The single executed unit is correct.
Front/client/src/components/SlideCanvas.tsxderivesactiveFrameIdasoverrideFrameId ?? defaultFrameIdand then derivesactiveCandidateby finding the matching candidate id inregion?.frame_candidates. This is separate from the existing preview path, wherepreviewFrameIdis only set when the override exists and differs from the default frame.The u4 lookup reuses the existing
defaultFrameId,overrideFrameId, andregion?.frame_candidates?.find(...)pattern. It does not rename or remove the preview variables, does not change resize behavior, and does not alter the commit path. The same SlideCanvas diff also contains the already-present u5 hint code, but that is outside this turn's verification scope and remains the next unit.The scoped frontend check still fails, but the failure is unrelated to u4:
BottomActions.tsximportsserializeSlidePlanfromslidePlanUtils, whileslidePlanUtils.tsdoes not export that symbol. Those two files have no local diff in this checkout, so this does not indicate a regression from the active-frame lookup.=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
defaultFrameIdis read fromregion?.frame_match_strategy.frame_id.overrideFrameIdis read fromuserSelection.overrides.zone_frames?.[region.id]whenregion.idexists.previewFrameIdremains preview-only and is set only when the override exists and differs from the default.activeFrameIdisoverrideFrameId ?? defaultFrameId, matching the u4 contract.activeCandidateis found withregion?.frame_candidates?.find((c) => c.id === activeFrameId)and isundefinedwhen no active id exists.BottomActions.tsxandslidePlanUtils.tshave no local diff; the check failure is pre-existing and unrelated to u4.Remaining units: [u5]
rewind_target: continue_same
FINAL_CONSENSUS: NO
Executing unit: u5
[Claude #7] Stage 3 code-edit Round #5 — u5 verification (
continue_sameafter u4 PASS)unit_executed
zoneHeightPx = height * SLIDE_H(logical px), compare againstactiveCandidate.minHeightPxin pendingLayout mode; ifzoneHeightPx < minHeightPxrender red ring +min H Npxbadge. resize clampminSize = 0.05unchanged.rewind_target: continue_same, remaining = [u5]).files_changed
Front/client/src/components/SlideCanvas.tsx(already modified, +22 lines for u5 — 10 derivation + 12 render block, no further edit this turn)diff_summary
Exact u5 hunks currently on disk at
Front/client/src/components/SlideCanvas.tsx:624-635(derivation) and:717-728(render):Properties (u5):
zoneHeightPx = isPendingLayout ? height * SLIDE_H : null— outside pendingLayout the comparison short-circuits via theisPendingLayout && minHeightPx != null && zoneHeightPx != nullguard. Stage 1 scope_lock axis_4 guardrail (pendingLayout only) satisfied.heightis set atSlideCanvas.tsx:442-460for three cases:overrideGeompresent (:443-448):height = overrideGeom.h * measuredSlideBody.hisPendingLayout && measuredSlideBody(:449-453):height = zone.position.height * measuredSlideBody.h:454-460):height = measured?.h ?? zone.position.height(skipped by u5 gate)overrideGeom.handzone.position.height∈ [0, 1] (slide-body interior fraction).measuredSlideBody.h = r.height / SLIDE_Hat:361(fraction of SLIDE_H).slide_body_interior_fraction * slide_body_fraction_of_SLIDE_H = fraction_of_SLIDE_H.zoneHeightPx = height * SLIDE_Hyields logical px in the 1280x720 coordinate space — matches catalogvisual_hints.min_height_pxunits (e.g. 230, 345, 350, 320 attemplates/phase_z2/catalog/frame_contracts.yaml:40,89,218,280,351, all sensibly within [0, 720]).measuredSlideBody.his not re-multiplied, matching Stage 2 plan's explicit double-apply prevention note.border-2 border-red-500overlay and abg-red-500badge, both withpointer-events-none. No mutation of resize handler / commit path / userSelection. Stage 1 scope_lock axis_4 guardrail (hint only (color / overlay), no clamp change) satisfied.minSize = 0.05unchanged atSlideCanvas.tsx:491. Verified by direct grep — no edit to:471-521resize handler block.activeCandidate.minHeightPxis u3 mapper output. From u3 mapper (designAgentApi.ts:530),minHeightPx = c.min_height_px ?? undefined. When backend (u1) emitsNonefor unregistered contracts, mapper producesundefined, u5 nullish-coalesces tonull,minHeightPx != nullguard fails, no hint rendered. Graceful path validated end-to-end (u1 -> u3 -> u5).typecheck delta verification
Stage 2 plan u5 test command:
cd Front && npm run check(tsc --noEmit).Baseline (
git stash push -- Front/client/src/components/SlideCanvas.tsx— u4+u5 diff removed, u1+u2+u3 intact):With u5 restored (
git stash pop— u4+u5 back in place):Identical single error in both cases.
BottomActions.tsx/serializeSlidePlanis a pre-existing typecheck error onmainHEAD0fb168b(IMP-10 D-1 ramp), entirely outside IMP-11 scope (does not touchSlideCanvas.tsx,FrameCandidate, or any IMP-11 axis). The u5 hint derivation + render block introduces zero new TS errors.Per Stage 2 plan u5 estimate_lines: 20 — actual diff is 22 lines (10 derivation + 12 render), within ±10% tolerance.
scope-lock guardrail satisfaction
minSize=0.05unchanged (:491grep verified).:442-460.min_height_pxunits (logical 1280x720 px) matchzoneHeightPxunits.None-> mapperundefined-> u5null-> guard skips hint. No KeyError / crash.remaining_units
FINAL_CONSENSUS: YESbecomes admissible for Stage 3.follow_up_issue_candidates
(none in u5 turn — IMP-11 D-2 scope_lock 5-axis surface is now fully implemented in code. Stage 2 follow_up_candidates remain registered for future issues: Step 9 외 다른 candidate-emitting path 의
min_height_px일관성 audit, catalog 미등록 frame telemetry surfacing, non-pendingLayout hint extension.)verification commands run this turn
git status --shortgit diff Front/client/src/components/SlideCanvas.tsx(u4 + u5 hunks inspected)grep -n "SLIDE_H|isPendingLayout|minSize" Front/client/src/components/SlideCanvas.tsx(dependency presence + resize clamp invariance)Read SlideCanvas.tsx :155-215, :405-465, :612-735(math derivation context)Grep min_height_px templates/phase_z2/catalog/frame_contracts.yaml(catalog unit validation: 230/345/350/320 in logical px)cd Front && npm run checkx 2 (baseline + with-u5; identical pre-existing error, zero new errors)DO NOT commit / push. Awaiting Codex verifier on u5.
rewind_target: continue_same
FINAL_CONSENSUS: NO (Claude self-report; Codex verifier must independently confirm u5 to close Stage 3.)
[Codex #N] Stage 3 code-edit Round #5 verifier
unit_verified: u5
result: PASS
The single executed unit is correct.
Front/client/src/components/SlideCanvas.tsxderiveszoneHeightPxasheight * SLIDE_H, whereheightis already the normalized logical slide fraction andSLIDE_His 720. The comparison is gated byisPendingLayout, requires a non-nullactiveCandidate.minHeightPx, and setsbelowMinHeightonly when the active frame contract minimum is greater than the current logical zone height.The visual hint is limited to a pointer-events-none red border and bottom-right
min H {minHeightPx}pxbadge. Resize behavior remains unchanged: the resize handler still usesconst minSize = 0.05, and no clamp, snap, commit, drag, drop, or geometry write path is modified by u5.The scoped frontend check still fails, but the failure is unrelated to u5:
BottomActions.tsximportsserializeSlidePlanfromslidePlanUtils, whileslidePlanUtils.tsdoes not export that symbol. Those two files have no local diff in this checkout, so this does not indicate a regression from the SlideCanvas hint.=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
SLIDE_His 720.zoneHeightPxis computed asisPendingLayout ? height * SLIDE_H : null.minHeightPxis read fromactiveCandidate?.minHeightPx ?? null.belowMinHeightrequiresisPendingLayout, non-nullminHeightPx, non-nullzoneHeightPx, andzoneHeightPx < minHeightPx.min H {minHeightPx}pxbadge.const minSize = 0.05.BottomActions.tsxandslidePlanUtils.tshave no local diff.Remaining units: none
FINAL_CONSENSUS: YES
[Claude #8] Stage 4 test-verify Round #1
Verification Summary
Stage 3 backfill (Claude #3–#7, Codex #N verifier PASS x5) created the binding u1 test file that previously blocked Codex #11. Re-running Stage 4 against the current working tree.
Files audited (all 5 axes present):
src/phase_z2_pipeline.py— u1 (Step 9v4_all_judgments[]+=min_height_px)Front/client/src/types/designAgent.ts— u2 (FrameCandidate.minHeightPx?: number)Front/client/src/services/designAgentApi.ts— u3 (min_height_px→minHeightPx)Front/client/src/components/SlideCanvas.tsx— u4 + u5 (active-frame lookup + red border/badge hint)tests/test_phase_z2_step9_v4_all_judgments_min_height.py— u1 binding test (NEW since Codex #11 FAIL)Diff matches plan
_contract = get_contract(c.template_id)binding;(_contract or {}).get("visual_hints", {}).get("min_height_px")chain; None fallbackphase_z2_pipeline.py:3972-3988minHeightPx?: numberadditive optionaldesignAgent.ts:130-133minHeightPx: c.min_height_px ?? undefinedon v4_all_judgments pathdesignAgentApi.ts:530-533activeFrameId = overrideFrameId ?? defaultFrameId;activeCandidate = region?.frame_candidates?.find(...)SlideCanvas.tsx:619-622zoneHeightPx = isPendingLayout ? height * SLIDE_H : null; pendingLayout-only red border + 'min H Npx' badge; no clamp changeSlideCanvas.tsx:624-635, 720-728Tests
Backend (binding u1 test — previously missing, now backfilled)
pytest -v tests/test_phase_z2_step9_v4_all_judgments_min_height.py→ 5 passed:test_v4_all_judgments_emits_min_height_px_with_none_fallbackPASStest_v4_all_judgments_preserves_existing_fieldsPASStest_catalog_visual_hints_min_height_px_path_is_realPASStest_registered_contract_without_min_height_px_propagates_nonePASStest_unregistered_template_id_propagates_nonePASSBackend regression
pytest -q tests/ --ignore=tests/matching→ 109 passed (no regression; full repo backend test suite).Frontend typecheck
cd Front && npm run check→ 1 error, all in unrelated file:Verified pre-existing:
git log --oneline -5 -- Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.ts→ only commit0f0d3fa feat(frontend): add Front/(frontend init).git diff -- Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.ts→ empty (no local diff).git show 0f0d3fa:Front/client/src/components/BottomActions.tsx | grep serializeSlidePlan→ import line 11 present at original commit.git show 0f0d3fa:Front/client/src/utils/slidePlanUtils.ts | grep "export.*serializeSlidePlan"→ no match at original commit.IMP-11 introduces 0 new typecheck errors in the four scoped frontend files.
Math validation (u5)
height(SlideCanvas.tsx:453) =zone.position.height * measuredSlideBody.h— fraction of SLIDE_H.zoneHeightPx = height * SLIDE_H(line 629) = logical px. NomeasuredSlideBody.hre-multiplication (double-apply avoided per Stage 2 correction).min_height_pxis logical 1280×720 px (yamlvisual_hints.min_height_px).Hardcoding audit (RULE 7)
min_height_pxvalues (230, 320, 345, 350, 380, 417)frame_contracts.yamlonlySLIDE_H = 720SlideCanvas.tsx:57(existing logical coordinate constant)minSize = 0.05resize clampborder-red-500Tailwind classZero values hardcoded in IMP-11 code path. All catalog values flow yaml →
get_contract()→ backend → mapper → frontend.Scope-lock invariants (Stage 1) — verified
git diff --name-onlyexcludes catalog)minSize = 0.05untouched)isPendingLayoutgate present on derivation AND render)pointer-events-noneon both overlay+badge: YES (line 725, 726 — no click/hover interception)Decision
PASS — All 5 axes implemented per Stage 1 scope_lock and Stage 2 plan; the binding u1 test that previously blocked Codex #11 is now present and passes 5/5; full backend regression suite (109 tests) PASS; frontend typecheck error is pre-existing and out-of-scope; no hardcoding; additive-only contract upheld.
=== EVIDENCE ===
Commands run:
git log --oneline -10→ HEAD =0fb168b feat(IMP-10): D-1 — filtered_section_reasons UI (read-only)(no Stage 3 commit yet — Stage 5 will handle).git status --short→ 4 modified files match scope:src/phase_z2_pipeline.py,Front/client/src/types/designAgent.ts,Front/client/src/services/designAgentApi.ts,Front/client/src/components/SlideCanvas.tsx; plus new test filetests/test_phase_z2_step9_v4_all_judgments_min_height.py(untracked).git diff src/phase_z2_pipeline.py Front/client/src/types/designAgent.ts Front/client/src/services/designAgentApi.ts Front/client/src/components/SlideCanvas.tsx→ matches Stage 2 plan hunks for u1–u5.python -m pytest tests/test_phase_z2_step9_v4_all_judgments_min_height.py -v→ 5 passed in 0.11s.python -m pytest tests/ --ignore=tests/matching -q→ 109 passed in 4.16s.cd Front && npm run check→ 1 error (BottomActions.tsx:11), 0 in scoped files.git log --all --oneline -5 -- Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.ts→ only0f0d3fa(initial frontend commit).git diff -- Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.ts→ empty.git show 0f0d3fa:Front/client/src/components/BottomActions.tsx | grep serializeSlidePlan→ import present at initial commit.git show 0f0d3fa:Front/client/src/utils/slidePlanUtils.ts | grep export→ noserializeSlidePlanexport at initial commit.Files checked:
src/phase_z2_pipeline.py:3969-3987(u1 builder) +:4022-4026(wiring)Front/client/src/types/designAgent.ts:127-133(u2)Front/client/src/services/designAgentApi.ts:527-533(u3)Front/client/src/components/SlideCanvas.tsx:471-491(resize handler unchanged),:601-635(u4 + u5 derivation),:717-730(u5 render)tests/test_phase_z2_step9_v4_all_judgments_min_height.py:1-170(u1 binding test)Front/client/src/components/BottomActions.tsx(unchanged since0f0d3fa)Front/client/src/utils/slidePlanUtils.ts(unchanged since0f0d3fa)Tests verified:
Commit verified: Stage 5 commit pending (per atomicity rule). No new code edit this round; Stage 3 source diff verified intact.
Match to scope_lock: all six invariants verified (additive_only, catalog read-only, resize unchanged, pendingLayout-only, None fallback, no AI).
FINAL_CONSENSUS: YES
[Codex #N] Stage 4 test-verify Round #1
Verdict: PASS for IMP-11 scoped verification. The implementation matches the five locked axes and is commit-OK from the Stage 4 verifier perspective.
Scope verification:
src/phase_z2_pipeline.pybuilds one_contract = get_contract(c.template_id)per candidate, uses it forcatalog_registered, and emitsmin_height_pxfromvisual_hints.min_height_pxwithNonefallback.Front/client/src/types/designAgent.tsadds optionalFrameCandidate.minHeightPx?: numberonly.Front/client/src/services/designAgentApi.tsmapsc.min_height_px ?? undefinedtominHeightPxonly on thev4_all_judgmentspath.Front/client/src/components/SlideCanvas.tsxderivesactiveFrameId = overrideFrameId ?? defaultFrameIdand looks upactiveCandidatefromregion.frame_candidates.SlideCanvas.tsxuseszoneHeightPx = height * SLIDE_H, compares againstactiveCandidate?.minHeightPx, and renders a pendingLayout-only red ring plusmin H Npxbadge.const minSize = 0.05remains unchanged.Frontend check note:
npm run checkfails, but only on a pre-existing unrelated issue:BottomActions.tsximportsserializeSlidePlanfromslidePlanUtils.ts, which does not export it. The two files have no local diff, and git history shows the import existed at frontend init commit0f0d3fawhile the export did not.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- src/phase_z2_pipeline.py Front/client/src/types/designAgent.ts Front/client/src/services/designAgentApi.ts Front/client/src/components/SlideCanvas.tsx tests/test_phase_z2_step9_v4_all_judgments_min_height.pyrg -n "min_height_px|FrameCandidate|minHeightPx|activeFrameId|activeCandidate|zoneHeightPx|belowMinHeight|border-red-500|min H|const minSize = 0.05" src/phase_z2_pipeline.py Front/client/src/types/designAgent.ts Front/client/src/services/designAgentApi.ts Front/client/src/components/SlideCanvas.tsx tests/test_phase_z2_step9_v4_all_judgments_min_height.pypytest -v tests/test_phase_z2_step9_v4_all_judgments_min_height.pynpm run checkfromD:\ad-hoc\kei\design_agent\Frontgit diff -- Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.tsrg -n "serializeSlidePlan|export" Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.tsgit log --oneline -5 -- Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.tsgit show 0f0d3fa:Front/client/src/components/BottomActions.tsx | Select-String -Pattern 'serializeSlidePlan'git show 0f0d3fa:Front/client/src/utils/slidePlanUtils.ts | Select-String -Pattern 'export.*serializeSlidePlan'git diff -- templates/phase_z2/catalog/frame_contracts.yamlFiles checked:
D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.pyD:\ad-hoc\kei\design_agent\Front\client\src\types\designAgent.tsD:\ad-hoc\kei\design_agent\Front\client\src\services\designAgentApi.tsD:\ad-hoc\kei\design_agent\Front\client\src\components\SlideCanvas.tsxD:\ad-hoc\kei\design_agent\tests\test_phase_z2_step9_v4_all_judgments_min_height.pyD:\ad-hoc\kei\design_agent\Front\client\src\components\BottomActions.tsxD:\ad-hoc\kei\design_agent\Front\client\src\utils\slidePlanUtils.tsD:\ad-hoc\kei\design_agent\templates\phase_z2\catalog\frame_contracts.yamlTest results:
pytest -v tests/test_phase_z2_step9_v4_all_judgments_min_height.py: 5 passed.npm run check: fails only atclient/src/components/BottomActions.tsx(11,10): error TS2305: Module '"../utils/slidePlanUtils"' has no exported member 'serializeSlidePlan'.Verified unrelated and pre-existing.Verified facts:
templates/phase_z2/catalog/frame_contracts.yamlhas no local diff.const minSize = 0.05.isPendingLayoutand uses logicalheight * SLIDE_H, notmeasuredSlideBody.h.BottomActions.tsxandslidePlanUtils.tshave no local diff;serializeSlidePlanimport existed in commit0f0d3fa, and no matching export existed there.FINAL_CONSENSUS: YES
[Claude #9] Stage 5: commit & push Round #1
📌 [Orchestrator] Stage 5: commit & push complete
■ Key decision
a79bd8bcarries all 5 axes (u1~u5) per Stage 2 plan (1 commit = 1 decision unit).slide2(Gitea) andorigin(GitHub); both refs/heads/main =a79bd8b.■ Out of scope (unchanged)
serializeSlidePlanexport gap (pre-existing since0f0d3fa; not introduced by IMP-11).■ Next
=== EXIT REPORT (binding contract) ===
[Codex #N] Stage 5 commit/push verification
Verdict: PASS.
The Stage 5 claims are verified. Local HEAD is
a79bd8bc43d4a2d77c2a50005e3c07c8e56abf28, parent is0fb168befcfd95017f3fd89bec04c156dae3a1b3, and the commit subject isfeat(IMP-11): D-2 — frame min_height_px hint (backend → UI). The commit contains exactly the five scoped IMP-11 files, with5 files changed, 231 insertions(+), 12 deletions(-).Both remote main refs match local HEAD:
slide2 refs/heads/main=a79bd8bc43d4a2d77c2a50005e3c07c8e56abf28origin refs/heads/main=a79bd8bc43d4a2d77c2a50005e3c07c8e56abf28No staged files remain after commit. The working tree still has unrelated untracked files/directories, but they are not part of commit
a79bd8b.=== EVIDENCE ===
Commands run:
git status --shortgit log -1 --pretty=format:"%H%n%P%n%s"git show --stat --name-only --pretty=format:"%H%n%P%n%s" HEADgit remote -vgit ls-remote slide2 refs/heads/maingit ls-remote origin refs/heads/maingit diff --cached --name-onlygit show --shortstat --pretty=format:"%H" HEADgit diff-tree --no-commit-id --name-status -r HEADgit rev-parse HEADgit rev-parse HEAD^Files checked:
Front/client/src/components/SlideCanvas.tsxFront/client/src/services/designAgentApi.tsFront/client/src/types/designAgent.tssrc/phase_z2_pipeline.pytests/test_phase_z2_step9_v4_all_judgments_min_height.pyCommit SHA:
a79bd8bc43d4a2d77c2a50005e3c07c8e56abf28Verified facts:
a79bd8bc43d4a2d77c2a50005e3c07c8e56abf28.0fb168befcfd95017f3fd89bec04c156dae3a1b3.5 files changed, 231 insertions(+), 12 deletions(-).slide2/mainandorigin/mainboth resolve to the exact local HEAD SHA.git diff --cached --name-onlyreturned empty output, so no files remain staged.a79bd8b.FINAL_CONSENSUS: YES
[Claude #10] Stage 6 — 최종 확인 / close Round #1
✅ Issue goal vs delivered result
min_height_pxread-onlygit show a79bd8b -- Front/client/src/services/designAgentApi.tsmin H Npxbadge, pendingLayout-only)git show a79bd8b -- Front/client/src/components/SlideCanvas.tsx🔒 Guardrails honored
[[feedback_ai_isolation_contract]]templates/phase_z2/catalog/frame_contracts.yamlread-only viaget_contract(c.template_id); no yaml mutation.const minSize = 0.05unchanged atSlideCanvas.tsx; hint ispointer-events-none, never affects drag / drop / commit / geometry write path.min_height_pxsourced fromvisual_hints.min_height_pxper contract,Nonefallback when unregistered.[[feedback_no_hardcoding]]a79bd8b.[[feedback_workflow_atomicity_rules]]📦 Commit & remotes (post-Stage 5 re-verification)
✅ Review loop checklist (issue body)
scope_lock(5 axes, additive only)implementation_unitsa79bd8b📝 Cross-ref status
a79bd8b.🔀 Follow-ups (out of IMP-11 scope, drafted only)
Front/client/src/components/BottomActions.tsximportsserializeSlidePlanfromFront/client/src/utils/slidePlanUtils.ts, butslidePlanUtils.tsdoes not export it. Pre-existing since frontend init commit0f0d3fa; not introduced by IMP-11. Causescd Front && npm run checkto fail on unrelated symbol; SlideCanvas.tsx itself typechecks clean. Recommend separate IMP issue.min_height_px == null(contract unregistered) currently shows no hint. Confirmed intended for D-2 scope (graceful None propagation). If product wants explicit "no minimum known" telemetry, schedule new axis.🎯 Close decision
Recommendation: CLOSE IMP-11 D-2 Round #1.
test_phase_z2_step9_v4_all_judgments_min_height.py5/5).a79bd8b.Evidence
Awaiting Codex final-close verdict.
source_comment_id:
[Claude #10]Stage 6 final-close Round #1[Codex #N] Stage 6 final independent verification / close Round #1
Verdict: PASS. I independently verified the Stage 6 close claims against local HEAD, the scoped commit diff, remotes, and scoped tests. IMP-11 D-2 is implemented and pushed as a single scoped commit.
Issue goal vs delivered result:
min H Npxbadge only when below activeCandidate.minHeightPx.Guardrails verified:
templates/phase_z2/catalog/frame_contracts.yamlhas no diff ina79bd8b, and the code reads it through existing get_contract lookup.const minSize = 0.05remains in SlideCanvas, and the new hint nodes are pointer-events-none.a79bd8bc43.Known out-of-scope status:
npm run checkfails onclient/src/components/BottomActions.tsx(11,10): TS2305, because BottomActions importsserializeSlidePlanfromslidePlanUtils, which does not export it.git log -SserializeSlidePlantraces that mismatch to frontend init commit0f0d3fa; it is not introduced by IMP-11.=== EVIDENCE ===
Commands run:
git rev-parse HEADgit log -1 --pretty=format:"%H%n%P%n%s"git show --stat --name-only --pretty=format:"%H%n%P%n%s" HEADgit diff-tree --no-commit-id --name-status -r HEADgit status --shortgit ls-remote slide2 refs/heads/maingit ls-remote origin refs/heads/maingit show --shortstat --pretty=format:"%H" HEADrg -n "_contract = get_contract|min_height_px|catalog_registered" src/phase_z2_pipeline.pyrg -n "minHeightPx|FrameCandidate" Front\client\src\types\designAgent.ts Front\client\src\services\designAgentApi.tsrg -n "activeFrameId|activeCandidate|zoneHeightPx|belowMinHeight|border-red-500|min H|const minSize = 0.05|pointer-events-none|isPendingLayout" Front\client\src\components\SlideCanvas.tsxpytest -q tests/test_phase_z2_step9_v4_all_judgments_min_height.pynpm run checkfromFrontgit log --oneline -SserializeSlidePlan -- Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.tsrg -n "serializeSlidePlan|export" Front\client\src\components\BottomActions.tsx Front\client\src\utils\slidePlanUtils.tsgit diff HEAD^ HEAD --name-onlygit diff HEAD^ HEAD -- templates/phase_z2/catalog/frame_contracts.yaml Front/client/src/components/BottomActions.tsx Front/client/src/utils/slidePlanUtils.tsgit grep -n "min_height_px" HEAD -- templates/phase_z2/catalog/frame_contracts.yaml | Select-Object -First 12Files checked:
src/phase_z2_pipeline.pyFront/client/src/types/designAgent.tsFront/client/src/services/designAgentApi.tsFront/client/src/components/SlideCanvas.tsxtests/test_phase_z2_step9_v4_all_judgments_min_height.pytemplates/phase_z2/catalog/frame_contracts.yamlFront/client/src/components/BottomActions.tsxFront/client/src/utils/slidePlanUtils.tsTest results:
pytest -q tests/test_phase_z2_step9_v4_all_judgments_min_height.py: 5 passed in 0.06s.npm run checkfromFront: FAIL, pre-existing out-of-scopeserializeSlidePlanexport mismatch.Commit SHA:
a79bd8bc43d4a2d77c2a50005e3c07c8e56abf28Verified facts:
a79bd8bc43d4a2d77c2a50005e3c07c8e56abf28; parent is0fb168befcfd95017f3fd89bec04c156dae3a1b3.feat(IMP-11): D-2 — frame min_height_px hint (backend → UI).5 files changed, 231 insertions(+), 12 deletions(-).src/phase_z2_pipeline.py,Front/client/src/types/designAgent.ts,Front/client/src/services/designAgentApi.ts,Front/client/src/components/SlideCanvas.tsx, andtests/test_phase_z2_step9_v4_all_judgments_min_height.py.slide2/main,origin/main, and local HEAD all point toa79bd8bc43d4a2d77c2a50005e3c07c8e56abf28.frame_contracts.yaml,BottomActions.tsx, andslidePlanUtils.tshave no diff in the IMP-11 commit.visual_hints.min_height_pxentries; IMP-11 reads them and does not mutate the catalog.v4_all_judgments_listuses_contract = get_contract(c.template_id), reuses_contractforcatalog_registered, and emitsmin_height_pxwithNonefallback.FrameCandidatehas optionalminHeightPx?: number, anddesignAgentApi.tsmapsc.min_height_px ?? undefined.const minSize = 0.05.FINAL_CONSENSUS: YES
결과보고서 — IMP-11 D-2 Frame min_height 표시
1. 왜 필요했나
Phase Z frontend에서 frame 후보를 선택하거나 pending layout을 조정할 때, catalog contract의
visual_hints.min_height_px를 보지 못하면 zone이 프레임 최소 높이보다 작아지는 문제를 사전에 알기 어렵다.2. 보완하려 한 기능
backend Step 9가 V4 후보별 frame contract min-height를 read-only로 내보내고, frontend가 pendingLayout 상태에서 현재 zone 높이가 min-height보다 낮으면 visual hint를 표시하도록 하려 했다.
3. 실제 변경 사항
src/phase_z2_pipeline.py에서v4_all_judgments[]후보에min_height_px를 additive로 추가했다.Front/client/src/types/designAgent.ts에FrameCandidate.minHeightPx?: number를 추가했다.Front/client/src/services/designAgentApi.ts에서min_height_px를minHeightPx로 mapping했다.Front/client/src/components/SlideCanvas.tsx에서 active frame candidate를 lookup하고, pendingLayout에서 zone height가minHeightPx보다 낮으면 red ring과min H Npxbadge를 표시했다.tests/test_phase_z2_step9_v4_all_judgments_min_height.py를 추가했다.4. 검증 결과
a79bd8b feat(IMP-11): D-2 — frame min_height_px hint (backend → UI).minSize = 0.05는 변경하지 않았고, hint는pointer-events-none이라 조작 동작에 영향이 없다.5. 남긴 것 / 넘긴 것
기존
npm run check실패는BottomActions.tsx의 unrelated export mismatch로 확인되어 #11 blocker가 아니다. #11은 min-height hint를 backend artifact부터 UI 표시까지 연결한 것으로 close했다.결과보고서 v2 — 프레임 최소 높이 경고 표시
한 줄 요약
선택한 디자인 프레임이 요구하는 최소 영역 높이보다 사용자가 영역을 작게 만들면 화면에서 경고를 보여주도록 했다.
왜 필요했나
디자인 프레임마다 특정 영역이 최소한 확보해야 하는 높이가 있다. 사용자가 화면에서 영역 크기를 줄이다 보면, 디자인이 깨지거나 내용이 들어가기 어려운 크기까지 줄어들 수 있다.
기존에는 이런 제한이 catalog에는 있어도 UI에서 보이지 않아, 사용자가 왜 결과가 깨지는지 알기 어려웠다.
무엇을 보완했나
backend가 프레임 후보별 최소 높이 정보를 함께 내보내고, frontend가 현재 선택된 프레임과 영역 높이를 비교해 경고를 보여주도록 했다.
사용자가 얻는 효과
사용자는 슬라이드 영역을 조정할 때 “이 프레임은 최소 높이가 이 정도 필요하다”는 신호를 바로 볼 수 있다. 결과가 깨지기 전에 미리 판단할 수 있다.
안전장치와 검증
catalog 자체는 수정하지 않고 읽기만 했다. 기존 resize 제한값도 바꾸지 않았다. 경고 UI는 조작을 막지 않고 안내만 하는 방식이다.
남은 한계 / 후속 작업
기존 frontend typecheck 실패 중 하나는 이 작업과 무관한 오래된 import/export 문제로 확인했다. #11 범위에서는 최소 높이 표시 기능만 완료했다.
기술 메모
구현 커밋은
a79bd8b다. backend 후보 데이터, frontend 타입/mapper,SlideCanvas표시 로직, 테스트가 함께 추가되었다.Kyeongmin referenced this issue2026-05-26 14:21:55 +09:00