diff --git a/packages/dashboard/app/components/__tests__/AppModals.test.tsx b/packages/dashboard/app/components/__tests__/AppModals.test.tsx
index ddca365b2a..fc3609a920 100644
--- a/packages/dashboard/app/components/__tests__/AppModals.test.tsx
+++ b/packages/dashboard/app/components/__tests__/AppModals.test.tsx
@@ -590,7 +590,11 @@ describe("AppModals", () => {
fireEvent.click(screen.getByTestId("task-detail-open-detail"));
expect(pushStateSpy).toHaveBeenCalledTimes(1);
- expect(mockModalManager.openDetailTask).toHaveBeenCalledWith({ id: "FN-2", title: "Nested" }, undefined);
+ /*
+ FNXC:TaskDetailNav 2026-07-07-09:15:
+ FN-7352 (route completed-task refine menus to detail) added a third `opts?: { origin?: DetailTaskOrigin }` argument to openDetailTask / openDetailTaskWithNav, so the modalManager call now carries three args (task, tab, opts). A task-to-task open with no explicit tab/origin passes (task, undefined, undefined).
+ */
+ expect(mockModalManager.openDetailTask).toHaveBeenCalledWith({ id: "FN-2", title: "Nested" }, undefined, undefined);
});
});
});
diff --git a/packages/dashboard/app/components/__tests__/ChangesDiffModal.test.tsx b/packages/dashboard/app/components/__tests__/ChangesDiffModal.test.tsx
index 6f004595eb..a664b5a46e 100644
--- a/packages/dashboard/app/components/__tests__/ChangesDiffModal.test.tsx
+++ b/packages/dashboard/app/components/__tests__/ChangesDiffModal.test.tsx
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { loadAllAppCss } from "../../test/cssFixture";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { ChangesDiffModal, type NormalizedFile } from "../ChangesDiffModal";
+import { ModalDismissPreferenceProvider } from "../../hooks/useOverlayDismiss";
import type { MergeDetails } from "@fusion/core";
vi.mock("lucide-react", () => ({
@@ -381,8 +382,14 @@ describe("ChangesDiffModal", () => {
it("calls onClose when clicking the modal overlay", () => {
const onClose = vi.fn();
+ /*
+ FNXC:ChangesDiffModal 2026-07-07-09:20:
+ FN-7261 (global modal dismissal setting) made backdrop dismissal default-off: useOverlayDismiss only closes when ModalDismissPreferenceProvider enables it. Wrap the render in the provider so the overlay-click dismiss path is exercised (matches AgentErrorDetailsModal.test.tsx).
+ */
const { container } = render(
- ,
+
+
+ ,
);
const overlay = container.querySelector(".modal-overlay");
diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx
index df77346e0d..8e059b84f4 100644
--- a/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx
+++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx
@@ -92,7 +92,11 @@ describe("TaskDetailModal Summary tab", () => {
expect(container.querySelector(".detail-tabs")?.firstElementChild?.textContent).toBe("Activity");
const summaryButton = screen.getByRole("button", { name: "Summary" });
expectButtonActive(summaryButton);
- expect(screen.queryByRole("button", { name: "Chat" })).toBeNull();
+ /*
+ FNXC:TaskDetailTabs 2026-07-07-09:25:
+ The planner-chat ("Chat") tab now renders unconditionally in the task-detail tab strip (both taskDetailChatFirst branches), so done tasks expose Activity, Chat, Summary, ... (see TaskDetailModal.definition-actions.test.tsx). Done tasks still land on Summary by default; Chat is present but not active.
+ */
+ expect(screen.getByRole("button", { name: "Chat" })).toBeInTheDocument();
expect(screen.getByText("Completion summary")).toBeTruthy();
expect(screen.getByText("summary")).toBeTruthy();
expect(screen.getByText("What changed")).toBeTruthy();
diff --git a/packages/dashboard/app/components/__tests__/board-no-legacy-flash.test.tsx b/packages/dashboard/app/components/__tests__/board-no-legacy-flash.test.tsx
index 814684c633..7dbfefd039 100644
--- a/packages/dashboard/app/components/__tests__/board-no-legacy-flash.test.tsx
+++ b/packages/dashboard/app/components/__tests__/board-no-legacy-flash.test.tsx
@@ -10,6 +10,11 @@ import type { Task } from "@fusion/core";
const apiMocks = vi.hoisted(() => ({
fetchBoardWorkflows: vi.fn(),
fetchWorkflowSteps: vi.fn(),
+ /*
+ FNXC:BoardNoLegacyFlash 2026-07-07-09:05:
+ ListView renders QuickEntryBox, whose quick-create path calls fetchWorkflowOptionalSteps (added FN-6304). The partial api mock must expose it or vitest throws "No fetchWorkflowOptionalSteps export" during render and the skeleton/legacy assertions never settle.
+ */
+ fetchWorkflowOptionalSteps: vi.fn(),
fetchNodes: vi.fn(),
fetchTaskDetail: vi.fn(),
batchUpdateTaskModels: vi.fn(),
@@ -23,7 +28,7 @@ const apiMocks = vi.hoisted(() => ({
vi.mock("../../api", () => ({
fetchBoardWorkflows: apiMocks.fetchBoardWorkflows,
fetchWorkflowSteps: apiMocks.fetchWorkflowSteps,
- fetchNodes: apiMocks.fetchNodes,
+ fetchWorkflowOptionalSteps: apiMocks.fetchWorkflowOptionalSteps,
fetchTaskDetail: apiMocks.fetchTaskDetail,
batchUpdateTaskModels: apiMocks.batchUpdateTaskModels,
promoteTask: apiMocks.promoteTask,
@@ -187,6 +192,7 @@ describe("no legacy-board flash before workflow lanes load (FN-6776)", () => {
beforeEach(() => {
vi.clearAllMocks();
apiMocks.fetchWorkflowSteps.mockResolvedValue([]);
+ apiMocks.fetchWorkflowOptionalSteps.mockResolvedValue([]);
apiMocks.fetchNodes.mockResolvedValue([]);
apiMocks.fetchTaskDetail.mockResolvedValue(null);
apiMocks.promoteTask.mockResolvedValue({});
@@ -276,14 +282,19 @@ describe("no legacy-board flash before workflow lanes load (FN-6776)", () => {
expectSkeleton(surface);
});
- it.each(["Board", "ListView"])("%s fetch error exits the skeleton to a terminal legacy layout", async (surface) => {
+ it.each(["Board", "ListView"])("%s keeps the skeleton on a failed first fetch instead of flashing legacy (non-authoritative failure)", async (surface) => {
mockViewport(1024);
apiMocks.fetchBoardWorkflows.mockRejectedValue(new Error("network"));
renderSurface(surface);
expectSkeleton(surface);
- await waitFor(() => expectLegacyLayout(surface));
+ /*
+ FNXC:BoardNoLegacyFlash 2026-07-07-09:30:
+ FN-7234 (preserve board workflow selections) made fetch failures non-authoritative: useBoardWorkflows keeps the current/cache-hydrated payload on a rejected fetch (empty .catch) rather than falling back to legacy. With no cached payload, boardWorkflows stays null so the skeleton persists — the board never flashes legacy on a failed first fetch. Recovery happens on the next visibility/focus/switcher-open re-fetch, not by dropping to legacy.
+ */
+ await waitFor(() => expect(apiMocks.fetchBoardWorkflows).toHaveBeenCalled());
+ expectSkeleton(surface);
});
it.each(["Board", "ListView"])("%s does not leak cached workflow layouts across project switches", async (surface) => {