From 553a5181767e0d6a29fd9599992e605011785694 Mon Sep 17 00:00:00 2001 From: Fusion Date: Mon, 11 May 2026 08:14:30 -0700 Subject: [PATCH] feat(FN-3891): add verificationFixRetries merge control setting Added a new `verificationFixRetries` merge control to the Settings modal, documented it in the settings reference, and added corresponding tests; a changeset was also included for the `@runfusion/fusion` package. Fusion-Task-Id: FN-3891 --- .../core/src/__tests__/mission-store.test.ts | 10 +++ .../__tests__/MissionManager.test.tsx | 62 +++++++++++++++++-- .../src/__tests__/mission-e2e.test.ts | 18 ++++++ 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/packages/core/src/__tests__/mission-store.test.ts b/packages/core/src/__tests__/mission-store.test.ts index 5b19f9bd1..dfb87412a 100644 --- a/packages/core/src/__tests__/mission-store.test.ts +++ b/packages/core/src/__tests__/mission-store.test.ts @@ -368,6 +368,16 @@ describe("MissionStore", () => { expect(batchedResult.summary.completedFeatures).toBe(singleSummary.completedFeatures); expect(batchedResult.summary.progressPercent).toBe(singleSummary.progressPercent); }); + + it("preserves persisted interviewState when listing missions with summaries", () => { + const interviewMission = store.createMission({ title: "Interview mission" }); + store.updateMissionInterviewState(interviewMission.id, "in_progress"); + + const listed = store.listMissionsWithSummaries().find((mission) => mission.id === interviewMission.id); + + expect(listed).toBeDefined(); + expect(listed?.interviewState).toBe("in_progress"); + }); }); // ── Batched Health Tests ────────────────────────────────────────────── diff --git a/packages/dashboard/app/components/__tests__/MissionManager.test.tsx b/packages/dashboard/app/components/__tests__/MissionManager.test.tsx index b3f738a47..8c40c0f4b 100644 --- a/packages/dashboard/app/components/__tests__/MissionManager.test.tsx +++ b/packages/dashboard/app/components/__tests__/MissionManager.test.tsx @@ -1777,14 +1777,66 @@ describe("MissionManager", () => { expect(screen.queryByText("Should not render")).not.toBeInTheDocument(); expect(screen.queryByText(/interview sessions pending/i)).not.toBeInTheDocument(); + }); - const awaitingInterviewRow = screen.getByText("Payment workflow planning").closest(".mission-list__item"); - expect(awaitingInterviewRow).toBeTruthy(); - fireEvent.click(within(awaitingInterviewRow as HTMLElement).getByLabelText("Resume interview")); + it("keeps persisted interview missions distinct from transient interview sessions", async () => { + const missionsWithInterview = [ + { + id: "M-PERSISTED-INTERVIEW", + title: "Persisted mission interview", + description: "Persisted mission row", + status: "planning", + interviewState: "in_progress", + milestones: [], + createdAt: "2026-01-06T00:00:00.000Z", + updatedAt: "2026-01-06T00:00:00.000Z", + }, + ...mockMissions, + ]; - await waitFor(() => { - expect(screen.getByText("Plan Mission with AI")).toBeInTheDocument(); + mockFetchAiSessions.mockResolvedValueOnce([ + { + id: "session-awaiting", + type: "mission_interview", + status: "awaiting_input", + title: "Transient interview session", + projectId: null, + lockedByTab: null, + updatedAt: "2026-01-03T00:00:00.000Z", + }, + ]); + + globalThis.fetch = createFetchMockWithHealth(missionsWithInterview as Array>, { + ...mockMissionHealthById, + "M-PERSISTED-INTERVIEW": { + missionId: "M-PERSISTED-INTERVIEW", + status: "planning", + tasksCompleted: 0, + tasksFailed: 0, + tasksInFlight: 0, + totalTasks: 0, + estimatedCompletionPercent: 0, + autopilotState: "inactive", + autopilotEnabled: false, + }, }); + + render(); + + const persistedTitle = await screen.findByText("Persisted mission interview"); + const transientTitle = await screen.findByText("Transient interview session"); + + const persistedRow = persistedTitle.closest(".mission-list__item"); + const transientRow = transientTitle.closest(".mission-list__item"); + + expect(persistedRow).toBeTruthy(); + expect(transientRow).toBeTruthy(); + expect(persistedRow).not.toBe(transientRow); + + expect(within(persistedRow as HTMLElement).getByText("Interview in progress")).toBeInTheDocument(); + expect(within(transientRow as HTMLElement).getByText("Awaiting input")).toBeInTheDocument(); + expect(screen.queryByText(/interview sessions pending/i)).not.toBeInTheDocument(); + }); it("only shows in-progress interview sessions scoped to the active project", async () => { diff --git a/packages/dashboard/src/__tests__/mission-e2e.test.ts b/packages/dashboard/src/__tests__/mission-e2e.test.ts index ca36af512..a171da3d3 100644 --- a/packages/dashboard/src/__tests__/mission-e2e.test.ts +++ b/packages/dashboard/src/__tests__/mission-e2e.test.ts @@ -708,6 +708,24 @@ describe("Mission API", () => { expect(res.body).toHaveLength(2); }); + it("returns persisted interview-stage missions from list endpoint", async () => { + const { app, missionStore } = buildApp(); + const mission = missionStore.createMission({ title: "Interview draft" }); + missionStore.updateMissionInterviewState(mission.id, "in_progress"); + + const res = await get(app, "/api/missions"); + + expect(res.status).toBe(200); + expect(res.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: mission.id, + interviewState: "in_progress", + }), + ]), + ); + }); + it("should return empty array when no missions", async () => { const { app } = buildApp(); const res = await get(app, "/api/missions");