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:
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user