fix(#75): IMP-47A mdx03 frontend execution stabilization (u1~u4)

u1: SlideCanvas iframe sandbox += allow-scripts (allow-same-origin preserved)
    → embedded-mode script in slide_base.html now applies html.embedded
    → standalone CSS reset deactivates inside iframe; no clipping
u2: designAgentApi.loadRun merges candidate_evidence + v4_all_judgments
    + v4_candidates via Map<template_id|id|frame_id> dedup,
    LABEL_PRIORITY (use_as_is<light_edit<restructure<reject) then
    confidence desc, capped TOP_N_FRAMES=6
u3: Home.handleGenerate useCallback deps = [uploadedFile, slidePlan,
    userSelection, pendingZones, pendingLayout] (5-tuple, stale-closure fix)
u4: tests/manual/imp47a_e2e.md — mdx03 manual e2e spec (5 axes)

Frontend-only. Backend src/ untouched. No template/catalog edits.
Determinism preserved (no LLM in frontend merge logic).
Baseline: pytest -q tests → 623 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 14:56:56 +09:00
parent c864fe0479
commit 15ef7c65e9
4 changed files with 103 additions and 8 deletions

View File

@@ -293,7 +293,7 @@ export default function SlideCanvas({
title="Phase Z 렌더 결과"
className="w-full h-full border-0 block"
scrolling="no"
sandbox="allow-same-origin"
sandbox="allow-same-origin allow-scripts"
style={{ pointerEvents: isEditMode ? "auto" : "none" }}
onLoad={(e) => {
// IMP-14 (Step 13 A-4) — embedded vs standalone CSS reset 은 backend

View File

@@ -377,7 +377,7 @@ export default function Home() {
);
setState((p) => ({ ...p, isLoading: false }));
}
}, [state.uploadedFile]);
}, [state.uploadedFile, state.slidePlan, state.userSelection, pendingZones, pendingLayout]);
// ── 섹션 드래그 앤 드롭 (Zone으로 재배치) ──
const handleSectionDrop = useCallback((sectionId: string, zoneId: string) => {

View File

@@ -511,12 +511,17 @@ export async function loadRun(runId: string): Promise<LoadRunResult> {
const candidateEvidence = Array.isArray(unit.candidate_evidence)
? unit.candidate_evidence
: [];
const rawSource =
candidateEvidence.length > 0
? candidateEvidence
: (unit.v4_all_judgments?.length > 0
? unit.v4_all_judgments
: (unit.v4_candidates ?? []));
const candidateMap = new Map<string, any>();
const pushCandidate = (c: any) => {
if (!c) return;
const key = c.template_id ?? c.id ?? c.frame_id;
if (!key) return;
if (!candidateMap.has(key)) candidateMap.set(key, c);
};
candidateEvidence.forEach(pushCandidate);
(unit.v4_all_judgments ?? []).forEach(pushCandidate);
(unit.v4_candidates ?? []).forEach(pushCandidate);
const rawSource = Array.from(candidateMap.values());
const v4Source = [...rawSource].sort((a: any, b: any) => {
const lp = (LABEL_PRIORITY[a.label] ?? 99) - (LABEL_PRIORITY[b.label] ?? 99);
if (lp !== 0) return lp;