feat(IMP-08): U3 — frontend wire (zoneSections override)
Wires the frontend drag/drop zone assignment through to the backend --override-section-assignment CLI flag. PipelineOverrides gains an optional zoneSections field (Record<string, string[]>) carrying canonical ordinal section ids (e.g., "top": ["04-2-sub-1"]). Vite middleware /api/run accepts overrides.zoneSections and forwards each non-empty zone as `--override-section-assignment ZONE=sid[,sid]`. Empty arrays and non-string sids are filtered to avoid bogus assignments from a partially-built UI state. Home.tsx builds the override with a diff-vs-default guard per Codex Stage 3 R3 B3 fix : createInitialUserSelection seeds zone_sections with the auto plan, so a literal copy would pollute backend assignment-source provenance even on a fresh re-render. The diff compares each zone's section list against sourcePlan.zones[].section_ids and only emits zones that differ. Toast summary now reports zoneSections=N when forwarded. Smoke verification : python -m src.phase_z2_pipeline samples/mdx_batch/04.mdx test_imp08_smoke --override-section-assignment primary=04-2-sub-1 produces section_assignment_plan with assignment_source=cli_override and v4_selector_trace.candidates populated via the U1 alias resolver (04-2-sub-1 -> 04-2.1 V4 entry). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -300,6 +300,35 @@ export default function Home() {
|
|||||||
if (zoneGeometries && Object.keys(zoneGeometries).length > 0) {
|
if (zoneGeometries && Object.keys(zoneGeometries).length > 0) {
|
||||||
overrides.zoneGeometries = zoneGeometries;
|
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<string, string[]>();
|
||||||
|
sourcePlan.zones.forEach((z) => {
|
||||||
|
defaultByZone.set(z.zone_id, z.section_ids);
|
||||||
|
});
|
||||||
|
const zoneSectionsDiff: Record<string, string[]> = {};
|
||||||
|
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 }));
|
setState((p) => ({ ...p, isLoading: true }));
|
||||||
@@ -310,6 +339,8 @@ export default function Home() {
|
|||||||
? `(overrides: ${[
|
? `(overrides: ${[
|
||||||
overrides.layout && `layout=${overrides.layout}`,
|
overrides.layout && `layout=${overrides.layout}`,
|
||||||
overrides.frames && `frames=${Object.keys(overrides.frames).length}`,
|
overrides.frames && `frames=${Object.keys(overrides.frames).length}`,
|
||||||
|
overrides.zoneSections &&
|
||||||
|
`zoneSections=${Object.keys(overrides.zoneSections).length}`,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(", ")})`
|
.join(", ")})`
|
||||||
|
|||||||
@@ -251,6 +251,11 @@ export interface PipelineOverrides {
|
|||||||
/** zone_id (top/bottom/left/right/...) → slide-body 내부 0~1 비율.
|
/** zone_id (top/bottom/left/right/...) → slide-body 내부 0~1 비율.
|
||||||
* backend 의 build_layout_css 가 horizontal-2 / vertical-2 만 처리. */
|
* backend 의 build_layout_css 가 horizontal-2 / vertical-2 만 처리. */
|
||||||
zoneGeometries?: Record<string, { x: number; y: number; w: number; h: number }>;
|
zoneGeometries?: Record<string, { x: number; y: number; w: number; h: number }>;
|
||||||
|
/** 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<string, string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runPipeline(
|
export async function runPipeline(
|
||||||
|
|||||||
@@ -241,6 +241,9 @@ function vitePluginPhaseZApi(): Plugin {
|
|||||||
layout?: string;
|
layout?: string;
|
||||||
frames?: Record<string, string>; // unit_id → template_id
|
frames?: Record<string, string>; // unit_id → template_id
|
||||||
zoneGeometries?: Record<string, { x: number; y: number; w: number; h: number }>; // zone_id → bbox (slide-body 내부 0~1)
|
zoneGeometries?: Record<string, { x: number; y: number; w: number; h: number }>; // 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<string, string[]>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
try {
|
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(
|
console.log(
|
||||||
`[phase-z-api] spawn pipeline: run_id=${runId}, mdx=${mdxPath}, args=${JSON.stringify(cliArgs.slice(2))}`
|
`[phase-z-api] spawn pipeline: run_id=${runId}, mdx=${mdxPath}, args=${JSON.stringify(cliArgs.slice(2))}`
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user