# IMP-47A — mdx03 frontend stabilization manual e2e Scope: frontend-only. Backend pipeline must NOT be modified during this test. Path under test: `mdx=03` (default sample loaded on page open). ## Preconditions - Backend running on `http://localhost:8001` (`uvicorn src.main:app --port 8001`). - Frontend dev server running (`cd Front && npm run dev`). - Working tree at IMP-47A Stage 3 HEAD (u1+u2+u3 applied to `Front/client/src/components/SlideCanvas.tsx`, `Front/client/src/services/designAgentApi.ts`, `Front/client/src/pages/Home.tsx`). - Browser opens `http://localhost:5173/?mdx=03` (or default route, which auto-loads `mdx=03`). ## Section 1 — iframe rendering (axis 1) Goal: verify u1 sandbox change lets `slide_base.html` script apply `html.embedded` class so the slide is not clipped inside the iframe. Steps: 1. Open the app; wait for `mdx=03` auto-load and initial `final.html` render. 2. Click the "슬라이드 플랜 생성하기" button; wait for `run "" 완료` toast. 3. Open browser DevTools → Elements; locate the iframe inside `SlideCanvas`; switch context to the iframe document. 4. Confirm `` is present (not just ``). 5. Confirm the rendered slide content fills the 1280×720 frame with no top padding offset and no clipping at the bottom. Pass: `html.embedded` class present AND no visible vertical shift/clipping. Fail signal: iframe content pushed downward, footer cut off, or `html` lacks `embedded` class (means script never ran → sandbox regression). ## Section 2 — multi-source frame candidates (axis 2) Goal: verify u2 3-source merge surfaces candidates from `candidate_evidence`, `v4_all_judgments`, and `v4_candidates` with deterministic dedup and cap. Steps: 1. After Section 1 success, click any zone in the canvas; right panel switches to the "frame" tab. 2. In the frame candidate list, count visible candidates. Expect ≤ `TOP_N_FRAMES` (=6). 3. Open DevTools → Network → reload `/api/run/`; inspect the JSON response and confirm at least two of `candidate_evidence`, `v4_all_judgments`, `v4_candidates` are non-empty. 4. Cross-check: union of `template_id ?? id ?? frame_id` keys from all three arrays (deduped, capped at 6) equals the UI list count and order. 5. Confirm the order respects LABEL_PRIORITY (`use_as_is` < `light_edit` < `restructure` < `reject`) then descending confidence. Pass: union/dedup/cap/order all match. Fail signal: only candidates from a single source visible, duplicates by template_id, more than 6 items, or order violates LABEL_PRIORITY. ## Section 3 — frame / layout override regeneration (axis 3) Goal: verify u3 5-dep `handleGenerate` callback delivers the latest override state to backend (no stale closure). Steps: 1. From Section 2, pick a non-default frame candidate (one whose label is not `use_as_is`); click "이 프레임 적용". 2. Confirm bottom-left button transforms to "선택대로 재생성하기" with amber pulse dot (hasPendingChanges = true). 3. Without any extra clicks, click "선택대로 재생성하기". 4. Open DevTools → Network → inspect the POST `/api/pipeline` body; confirm `overrides.frames` contains the chosen `{unit_id: frame_id}` mapping. 5. After success toast, confirm new `run_id` differs from the previous run, and the rendered iframe reflects the chosen frame (frame DOM class / id matches selection). 6. Repeat with a layout-card "적용하기" → pending overlay enters → "선택대로 재생성하기"; confirm POST body includes `overrides.layout` with the chosen preset id. Pass: every override (frames / layout / zoneSections / zoneGeometries when applicable) reaches backend on first click; new `run_id` returned. Fail signal: POST body lacks the override, or backend re-renders with the previous selection (stale closure regression). ## Section 4 — pending overlay enter / cancel / clear (axis 4) Goal: verify pendingLayout overlay enters on "적용하기", exits on "취소", and auto-clears on successful regenerate. Steps: 1. Click any non-current layout card "적용하기" button. 2. Confirm amber dashed overlay appears over `.slide-body` area with `PENDING BODY LAYOUT` label and chosen layout id. 3. Confirm "취소" button (top-right of canvas) is visible. 4. Click "취소"; confirm overlay disappears, `userSelection` resets, and `hasPendingChanges` indicator clears. 5. Re-enter pending mode (apply a layout again), this time click "선택대로 재생성하기"; confirm overlay disappears on success (Home.tsx clears `pendingLayout` + `hasPendingChanges` before pipeline call) and the new `final.html` renders inline (no overlay). Pass: enter → cancel → re-enter → regenerate cycle leaves no overlay, no stuck pending state, no leftover `hasPendingChanges` flag. Fail signal: overlay persists after regenerate, button stays in amber state, or cancel does not restore the canvas. ## Section 5 — mdx03 end-to-end pass (axis 5) Goal: smoke run combining Sections 1–4 in one session to validate mdx03 demo path. Steps: 1. Fresh reload `http://localhost:5173/?mdx=03`. 2. Click "슬라이드 플랜 생성하기" → wait for run completion → confirm iframe renders cleanly (Section 1 pass). 3. Click any zone → inspect 2+ candidates in frame list (Section 2 pass). 4. Apply a non-default frame → "선택대로 재생성하기" → new run id + iframe reflects override (Section 3 pass). 5. Apply a non-default layout → confirm overlay → "선택대로 재생성하기" → overlay clears (Section 4 pass). 6. Capture: run_id chain, final.html path under `data/runs//final.html`, and DOM screenshot of the rendered iframe content. Pass: all five sections green, no console errors, no toast errors, three distinct `run_id`s produced across the session. Fail signal: any earlier section regression OR backend pipeline failure (out-of-scope for IMP-47A — log separately). ## Out of scope (not tested here) - AI fallback activation in `Step 12` (`light_edit` / `restructure`) — IMP-47B. - Frame cache (#62 / IMP-46). - mdx04 / mdx05 path-specific axes. - Automated Playwright e2e replacement — future work.