wip: phase_z2 evidence 파이프라인 + matching 실험(phase2~26) + 프론트 trace 패널 진행분 스냅샷

- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규
- Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가
- templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신
- tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분)
- tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가
- docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서
- scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸
- .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외

미완성 작업의 보존용 스냅샷 커밋 (2026-07-02)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 17:03:42 +09:00
co-authored by Claude Opus 4.8
parent 97b7833a1b
commit b836e79ee1
527 changed files with 673036 additions and 717 deletions
@@ -0,0 +1,75 @@
import { describe, expect, it } from "vitest";
import { buildFrameCandidatesForUnit } from "../src/services/designAgentApi";
describe("buildFrameCandidatesForUnit (#98 Task 2)", () => {
it("merges sorted evidence, v4 candidates, all judgments, and app candidates up to six", () => {
const unit = {
unit_id: "03-1",
current_default_candidate: "frame_a",
sorted_candidate_evidence: [
{ template_id: "frame_a", frame_number: 1, label: "use_as_is", confidence: 0.9, rank: 1, coverage_state: "covered_native", candidate_status: "auto_renderable" },
],
v4_candidates: [
{ template_id: "frame_b", frame_number: 2, label: "light_edit", confidence: 0.8 },
],
v4_all_judgments: [
{ template_id: "frame_c", frame_number: 3, label: "reject", confidence: 0.7, coverage_state: "requires_adaptation", candidate_status: "ai_adaptation_required" },
{ template_id: "frame_d", frame_number: 4, label: "reject", confidence: 0.6 },
{ template_id: "frame_e", frame_number: 5, label: "reject", confidence: 0.5 },
{ template_id: "frame_f", frame_number: 6, label: "reject", confidence: 0.4 },
{ template_id: "frame_g", frame_number: 7, label: "reject", confidence: 0.3 },
],
application_candidates: [
{ template_id: "frame_b", application_mode: "same_frame_with_adjustment", auto_applicable: true },
],
};
const candidates = buildFrameCandidatesForUnit(unit);
expect(candidates.map((c) => c.id)).toEqual([
"frame_a",
"frame_b",
"frame_c",
"frame_d",
"frame_e",
"frame_f",
]);
expect(candidates).toHaveLength(6);
expect(candidates[1].applicationMode).toBe("same_frame_with_adjustment");
expect(candidates[0].coverageState).toBe("covered_native");
expect(candidates[2].coverageState).toBe("requires_adaptation");
expect(candidates[2].candidateStatus).toBe("ai_adaptation_required");
});
it("surfaces generic fallback from composition when Step 9 has no V4 sources", () => {
const unit = {
unit_id: "05-1+05-2",
current_default_candidate: null,
sorted_candidate_evidence: [],
candidate_evidence: [],
v4_candidates: [],
v4_all_judgments: [],
application_candidates: [],
};
const compositionUnit = {
source_section_ids: ["05-1", "05-2"],
frame_template_id: "three_parallel_requirements",
frame_id: "1171281190",
label: "reject",
score: 0,
phase_z_status: "fallback_candidate",
selection_path: "generic_fallback",
rationale: { ai_adaptation_required: true },
};
const candidates = buildFrameCandidatesForUnit(unit, compositionUnit);
expect(candidates).toHaveLength(1);
expect(candidates[0]).toMatchObject({
id: "three_parallel_requirements",
label: "reject",
coverageState: "requires_adaptation",
routeHint: "ai_adaptation_required",
decision: "selected",
reason: "generic_fallback",
});
});
});
@@ -64,7 +64,7 @@ describe("handleGenerate [DIAG raw overrides] (IMP-42 u4)", () => {
it("places the DIAG console.log before the runPipeline call site", () => {
const diagIdx = HANDLE_GENERATE_BODY.indexOf('console.log("[DIAG raw overrides]"');
const runPipelineIdx = HANDLE_GENERATE_BODY.indexOf(
"runPipeline(state.uploadedFile, overrides)",
"runPipeline(",
);
expect(diagIdx).toBeGreaterThan(-1);
expect(runPipelineIdx).toBeGreaterThan(-1);
@@ -108,7 +108,7 @@ describe("handleGenerate [DIAG raw overrides] (IMP-42 u4)", () => {
const flushIdx = HANDLE_GENERATE_BODY.indexOf("await flushUserOverrides()");
const diagIdx = HANDLE_GENERATE_BODY.indexOf('console.log("[DIAG raw overrides]"');
const runPipelineIdx = HANDLE_GENERATE_BODY.indexOf(
"runPipeline(state.uploadedFile, overrides)",
"runPipeline(",
);
expect(flushIdx).toBeGreaterThan(-1);
expect(diagIdx).toBeGreaterThan(flushIdx);
@@ -0,0 +1,176 @@
import { describe, expect, it } from "vitest";
import { buildAiTraceSummary, buildPipelineTraceSummary } from "../src/services/designAgentApi";
describe("#98 Task 6 pipeline trace summary", () => {
it("connects sections, units, zones, and render-blocked warnings", () => {
const trace = buildPipelineTraceSummary({
normalized: {
data: {
sections: [
{ section_id: "01-1", title: "S1" },
{ section_id: "01-2", title: "S2" },
],
},
},
compositionPlan: {
data: {
selected_units: [
{
source_section_ids: ["01-1"],
frame_template_id: "frame_a",
label: "use_as_is",
selection_path: "rank_1",
},
{
source_section_ids: ["01-2"],
frame_template_id: "frame_b",
label: "use_as_is",
selection_path: "provisional_rank_1",
},
],
},
},
applicationPlan: {
data: {
units: [
{
unit_id: "01-1",
current_default_candidate: "frame_a",
sorted_candidate_evidence: [
{ template_id: "frame_a", label: "use_as_is", confidence: 0.9 },
],
},
{
unit_id: "01-2",
current_default_candidate: "frame_b",
sorted_candidate_evidence: [
{ template_id: "frame_b", label: "use_as_is", confidence: 0.8 },
],
},
],
},
},
slotPayload: {
data: {
per_zone: [
{ position: "top", template_id: "frame_a", slot_payload: { title: "S1" } },
{ position: "bottom", template_id: "__empty__", slot_payload: {} },
],
},
},
slideStatus: {
data: {
overall: "PARTIAL_COVERAGE",
covered_section_ids: ["01-1", "01-2"],
content_rendered_section_ids: ["01-1"],
filtered_section_ids: ["01-2"],
render_blocked_section_ids: ["01-2"],
adapter_needed_count: 1,
adapter_needed_units: [
{
position: "bottom",
source_section_ids: ["01-2"],
reason: "fit_error",
},
],
},
},
});
expect(trace.sections.map((section) => [section.id, section.state])).toEqual([
["01-1", "rendered"],
["01-2", "render_blocked"],
]);
expect(trace.sections[1].reasons).toEqual([
"fit_error",
"filtered",
]);
expect(trace.units.map((unit) => [unit.unit_id, unit.candidate_count])).toEqual([
["01-1", 1],
["01-2", 1],
]);
expect(trace.zones[1]).toMatchObject({
position: "bottom",
source_section_ids: ["01-2"],
template_id: "__empty__",
slot_status: "empty",
warnings: ["__empty__", "empty_slot_payload", "fit_error"],
});
expect(trace.warnings).toContain("render_blocked:01-2");
expect(trace.warnings).toContain("adapter_needed:1");
});
});
describe("#98 Task 7 AI trace summary", () => {
it("summarizes AI called/skipped/error state from step12_ai_repair", () => {
const trace = buildAiTraceSummary(
{
step_status: "done",
data: {
per_unit: [
{
unit_index: 0,
source_section_ids: ["04-1"],
frame_template_id: "frame_reject",
route_hint: "ai_adaptation_required",
provisional: true,
ai_called: false,
skip_reason: "router_short_circuit",
apply_status: "no_proposal",
api_error_kind: null,
error: null,
},
{
unit_index: 1,
source_section_ids: ["04-2"],
frame_template_id: "frame_auto",
route_hint: "direct_render",
provisional: false,
ai_called: false,
skip_reason: "not_provisional",
apply_status: "no_proposal",
api_error_kind: null,
error: null,
},
],
coverage_invariant: { status: "ok" },
},
},
{
status: "ok",
counts: { total: 2, applied: 0, no_proposal: 2, no_zone_match: 0, unsupported_kind: 0, error: 0 },
unsupported_kind_records: [],
error_records: [],
coverage_status: "ok",
dropped_section_ids: [],
human_review_required: false,
},
);
expect(trace.artifact_present).toBe(true);
expect(trace.ai_enabled).toBe(false);
expect(trace.ai_called_count).toBe(0);
expect(trace.eligible_count).toBe(1);
expect(trace.skip_reasons).toEqual({
router_short_circuit: 1,
not_provisional: 1,
});
expect(trace.warnings).toContain("router_short_circuit");
expect(trace.units[0]).toMatchObject({
source_section_ids: ["04-1"],
route_hint: "ai_adaptation_required",
provisional: true,
ai_called: false,
skip_reason: "router_short_circuit",
});
});
it("surfaces missing step12 AI artifact as an explicit warning", () => {
const trace = buildAiTraceSummary(null, null);
expect(trace.artifact_present).toBe(false);
expect(trace.ai_enabled).toBeNull();
expect(trace.status).toBe("missing_artifact");
expect(trace.warnings).toEqual(["step12_ai_repair.json missing"]);
});
});
@@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const HOME_TSX = readFileSync(
resolve(__dirname, "..", "src", "pages", "Home.tsx"),
"utf-8",
);
const SLIDE_CANVAS_TSX = readFileSync(
resolve(__dirname, "..", "src", "components", "SlideCanvas.tsx"),
"utf-8",
);
function sliceHandleGenerateBody(source: string): string {
const startMarker = "const handleGenerate = useCallback(async () =>";
const startIdx = source.indexOf(startMarker);
if (startIdx === -1) throw new Error("handleGenerate declaration not found");
const afterStart = source.slice(startIdx + startMarker.length);
const nextDeclIdx = afterStart.search(/\n {2}const [A-Za-z]/);
return nextDeclIdx === -1 ? afterStart : afterStart.slice(0, nextDeclIdx);
}
const HANDLE_GENERATE_BODY = sliceHandleGenerateBody(HOME_TSX);
describe("#98 Task 4 render stale guard", () => {
it("clears prior runMeta before invoking runPipeline", () => {
const clearIdx = HANDLE_GENERATE_BODY.indexOf("setRunMeta(null)");
const loadingIdx = HANDLE_GENERATE_BODY.indexOf("isLoading: true");
const runPipelineIdx = HANDLE_GENERATE_BODY.indexOf("runPipeline(");
expect(clearIdx).toBeGreaterThan(-1);
expect(loadingIdx).toBeGreaterThan(-1);
expect(runPipelineIdx).toBeGreaterThan(-1);
expect(clearIdx).toBeLessThan(runPipelineIdx);
expect(loadingIdx).toBeLessThan(runPipelineIdx);
});
it("does not pass a finalHtmlUrl to SlideCanvas while a pipeline run is loading", () => {
expect(HOME_TSX).toContain(
"finalHtmlUrl={state.isLoading ? undefined : runMeta?.final_html_url}",
);
});
it("forces iframe remounts by keying the iframe on embeddedSrc", () => {
expect(SLIDE_CANVAS_TSX).toContain("key={embeddedSrc}");
});
it("resets iframe-derived overlay state on render url, loading, or pending-layout changes", () => {
expect(SLIDE_CANVAS_TSX).toContain(
"}, [finalHtmlUrl, isPipelineRunning, isPendingLayout]);",
);
expect(SLIDE_CANVAS_TSX).toContain("setMeasuredZones({});");
expect(SLIDE_CANVAS_TSX).toContain("setMeasuredImages({});");
expect(SLIDE_CANVAS_TSX).toContain("setSelectedImageId(null);");
expect(SLIDE_CANVAS_TSX).toContain("setDragOverZoneId(null);");
expect(SLIDE_CANVAS_TSX).toContain("setEditMode(\"off\");");
});
});
@@ -181,6 +181,23 @@ describe("runPipeline reuseFromRunId forwarding (IMP-43 #72 u6)", () => {
expect(res.success).toBe(true);
expect(res.run_id).toBe("test_run_id_20260524");
});
it("includes ignoreUserOverrides only when clean generate requests it", async () => {
fetchMock.mockResolvedValueOnce(mockResponse(SUCCESS_BODY));
await runPipeline(
makeFakeFile("03.mdx", "# title"),
undefined,
undefined,
{ ignoreUserOverrides: true },
);
const cleanBody = lastPostBody();
expect(cleanBody.ignoreUserOverrides).toBe(true);
fetchMock.mockResolvedValueOnce(mockResponse(SUCCESS_BODY));
await runPipeline(makeFakeFile("03.mdx", "# title"));
const normalBody = lastPostBody();
expect("ignoreUserOverrides" in normalBody).toBe(false);
});
});
// ============================================================================
@@ -196,9 +213,13 @@ describe("/api/run handler reuseFromRunId source-slice (IMP-43 #72 u6)", () => {
expect(VITE_CONFIG_SOURCE).toMatch(/reuseFromRunId\?:\s*string\s*;/);
});
it("declares ignoreUserOverrides?: boolean on the /api/run payload type", () => {
expect(VITE_CONFIG_SOURCE).toMatch(/ignoreUserOverrides\?:\s*boolean\s*;/);
});
it("destructures reuseFromRunId from payload alongside filename/content/overrides", () => {
expect(VITE_CONFIG_SOURCE).toMatch(
/const\s*\{\s*filename\s*,\s*content\s*,\s*overrides\s*,\s*reuseFromRunId\s*\}\s*=\s*payload\s*;/,
/const\s*\{\s*filename\s*,\s*content\s*,\s*overrides\s*,\s*reuseFromRunId\s*,\s*ignoreUserOverrides\s*\}\s*=\s*payload\s*;/,
);
});
@@ -247,4 +268,11 @@ describe("/api/run handler reuseFromRunId source-slice (IMP-43 #72 u6)", () => {
/cliArgs\.push\(\s*"--reuse-from"\s*,\s*reuseFromRunId\s*\)/,
);
});
it("forwards --ignore-user-overrides only for clean generate requests", () => {
const ignoreIdx = VITE_CONFIG_SOURCE.indexOf('"--ignore-user-overrides"');
expect(ignoreIdx).toBeGreaterThan(-1);
const guardWindow = VITE_CONFIG_SOURCE.slice(Math.max(0, ignoreIdx - 160), ignoreIdx);
expect(guardWindow).toMatch(/ignoreUserOverrides\s*===\s*true/);
});
});
@@ -46,6 +46,7 @@ import {
applyPersistedNonFrameOverrides,
createInitialUserSelection,
deriveUserOverridesKey,
mergeSubmittedPipelineOverridesForRestore,
remapPersistedFramesToZoneFrames,
} from "../src/utils/slidePlanUtils";
import type { SlidePlan, Zone } from "../src/types/designAgent";
@@ -559,6 +560,65 @@ describe("restore-on-reopen end-to-end (IMP-52 u10)", () => {
});
});
it("post-run restore prefers the submitted Generate payload over stale persisted axes", () => {
const restoreDoc = mergeSubmittedPipelineOverridesForRestore(
{
layout: "vertical-2",
frames: { "03-1": "stale_frame" },
zone_geometries: { left: { x: 0, y: 0, w: 0.4, h: 1 } },
zone_sections: { left: ["03-2"], right: ["03-1"] },
manual_section_assignment: true,
text_overrides: { top: { "title.0": "keep text edit" } },
},
{
frames: { "03-1": "current_frame" },
},
false,
);
expect(restoreDoc.layout).toBeUndefined();
expect(restoreDoc.frames).toEqual({ "03-1": "current_frame" });
expect(restoreDoc.zone_geometries).toBeUndefined();
expect(restoreDoc.zone_sections).toBeUndefined();
expect(restoreDoc.manual_section_assignment).toBe(false);
expect(restoreDoc.text_overrides).toEqual({
top: { "title.0": "keep text edit" },
});
});
it("converts the submitted Generate payload back to persisted-axis names for restore", () => {
const restoreDoc = mergeSubmittedPipelineOverridesForRestore(
{},
{
layout: "top-1-bottom-2",
frames: { "01-intro": "three_parallel_requirements" },
zoneGeometries: {
top: { x: 0, y: 0, w: 1, h: 0.35 },
bottom: { x: 0, y: 0.35, w: 1, h: 0.65 },
},
zoneSections: {
top: ["01-intro"],
bottom: ["01-1", "01-2"],
},
},
true,
);
expect(restoreDoc.layout).toBe("top-1-bottom-2");
expect(restoreDoc.frames).toEqual({
"01-intro": "three_parallel_requirements",
});
expect(restoreDoc.zone_geometries).toEqual({
top: { x: 0, y: 0, w: 1, h: 0.35 },
bottom: { x: 0, y: 0.35, w: 1, h: 0.65 },
});
expect(restoreDoc.zone_sections).toEqual({
top: ["01-intro"],
bottom: ["01-1", "01-2"],
});
expect(restoreDoc.manual_section_assignment).toBe(true);
});
it("missing persisted file (GET returns {}) leaves the selection at backend defaults", async () => {
fetchMock.mockResolvedValueOnce(mockResponse({}));
const persisted = await getUserOverrides(deriveUserOverridesKey("new_file.mdx"));