diff --git a/Front/client/src/pages/Home.tsx b/Front/client/src/pages/Home.tsx index 04adf53..da29b96 100644 --- a/Front/client/src/pages/Home.tsx +++ b/Front/client/src/pages/Home.tsx @@ -300,6 +300,35 @@ export default function Home() { if (zoneGeometries && Object.keys(zoneGeometries).length > 0) { overrides.zoneGeometries = zoneGeometries; } + + // IMP-08 B-3 : zoneSections forward only when the user diverged from + // the auto plan. Codex Stage 3 R3 B3 fix : `createInitialUserSelection` + // seeds `zone_sections` with the default placement, so a literal copy + // would pollute backend assignment-source provenance even on a fresh + // re-render. Diff against `sourcePlan.zones[].section_ids` per zone and + // only emit zones whose section list differs. + const userZoneSections = state.userSelection.overrides.zone_sections; + if (userZoneSections) { + const defaultByZone = new Map(); + sourcePlan.zones.forEach((z) => { + defaultByZone.set(z.zone_id, z.section_ids); + }); + const zoneSectionsDiff: Record = {}; + for (const [zoneId, sids] of Object.entries(userZoneSections)) { + if (!Array.isArray(sids)) continue; + const cleaned = sids.filter((s) => typeof s === "string" && s.trim()); + const defaults = defaultByZone.get(zoneId) ?? []; + const sameAsDefault = + cleaned.length === defaults.length && + cleaned.every((sid, i) => sid === defaults[i]); + if (!sameAsDefault) { + zoneSectionsDiff[zoneId] = cleaned; + } + } + if (Object.keys(zoneSectionsDiff).length > 0) { + overrides.zoneSections = zoneSectionsDiff; + } + } } setState((p) => ({ ...p, isLoading: true })); @@ -310,6 +339,8 @@ export default function Home() { ? `(overrides: ${[ overrides.layout && `layout=${overrides.layout}`, overrides.frames && `frames=${Object.keys(overrides.frames).length}`, + overrides.zoneSections && + `zoneSections=${Object.keys(overrides.zoneSections).length}`, ] .filter(Boolean) .join(", ")})` diff --git a/Front/client/src/services/designAgentApi.ts b/Front/client/src/services/designAgentApi.ts index 8d8fb2d..accb166 100644 --- a/Front/client/src/services/designAgentApi.ts +++ b/Front/client/src/services/designAgentApi.ts @@ -251,6 +251,11 @@ export interface PipelineOverrides { /** zone_id (top/bottom/left/right/...) → slide-body 내부 0~1 비율. * backend 의 build_layout_css 가 horizontal-2 / vertical-2 만 처리. */ zoneGeometries?: Record; + /** IMP-08 B-3 : zone_id -> list of section_id assignments + * (canonical ordinal `${parent}-sub-${n}`). Only forwarded when the + * user explicitly diverges from the auto plan; default placements + * are not echoed back to avoid polluting override provenance. */ + zoneSections?: Record; } export async function runPipeline( diff --git a/Front/vite.config.ts b/Front/vite.config.ts index d3d151a..789d708 100644 --- a/Front/vite.config.ts +++ b/Front/vite.config.ts @@ -241,6 +241,9 @@ function vitePluginPhaseZApi(): Plugin { layout?: string; frames?: Record; // unit_id → template_id zoneGeometries?: Record; // zone_id → bbox (slide-body 내부 0~1) + // IMP-08 B-3 : zone_id -> list of canonical section_id assignments + // (e.g., "top": ["03-1-sub-1"]). Forwarded as --override-section-assignment. + zoneSections?: Record; }; }; try { @@ -322,6 +325,21 @@ function vitePluginPhaseZApi(): Plugin { } } } + // IMP-08 B-3 — zoneSections override forward to CLI. + // Each entry becomes `--override-section-assignment ZONE=sid[,sid]`. + // Empty arrays and non-string sids are filtered out so the backend + // never receives bogus assignments from a partially-built UI state. + if (overrides?.zoneSections && typeof overrides.zoneSections === "object") { + for (const [zoneId, sids] of Object.entries(overrides.zoneSections)) { + if (!Array.isArray(sids)) continue; + const cleaned = sids.filter((s) => typeof s === "string" && s.trim()); + if (cleaned.length === 0) continue; + cliArgs.push( + "--override-section-assignment", + `${zoneId}=${cleaned.join(",")}` + ); + } + } console.log( `[phase-z-api] spawn pipeline: run_id=${runId}, mdx=${mdxPath}, args=${JSON.stringify(cliArgs.slice(2))}` );