diff --git a/packages/dashboard/app/__tests__/component-sse-resync.test.tsx b/packages/dashboard/app/__tests__/component-sse-resync.test.tsx index abc2e3bf2e..054bf0d5b4 100644 --- a/packages/dashboard/app/__tests__/component-sse-resync.test.tsx +++ b/packages/dashboard/app/__tests__/component-sse-resync.test.tsx @@ -61,6 +61,7 @@ vi.mock("../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => "desktop", isMobileViewport: () => false, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "desktop", })); diff --git a/packages/dashboard/app/components/__tests__/AgentsView.orgchart.test.tsx b/packages/dashboard/app/components/__tests__/AgentsView.orgchart.test.tsx index 2aa06bee64..e9e5442f41 100644 --- a/packages/dashboard/app/components/__tests__/AgentsView.orgchart.test.tsx +++ b/packages/dashboard/app/components/__tests__/AgentsView.orgchart.test.tsx @@ -12,6 +12,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => mockViewportMode(), isMobileViewport: () => mockViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => mockViewportMode(), })); vi.mock("../../hooks/useConfirm", () => ({ useConfirm: () => ({ confirm: vi.fn().mockResolvedValue(true) }) })); diff --git a/packages/dashboard/app/components/__tests__/AgentsView.test.tsx b/packages/dashboard/app/components/__tests__/AgentsView.test.tsx index b5e7d91792..556a7172f8 100644 --- a/packages/dashboard/app/components/__tests__/AgentsView.test.tsx +++ b/packages/dashboard/app/components/__tests__/AgentsView.test.tsx @@ -83,6 +83,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => mockViewportMode(), isMobileViewport: () => mockViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => mockViewportMode(), })); diff --git a/packages/dashboard/app/components/__tests__/App.test.tsx b/packages/dashboard/app/components/__tests__/App.test.tsx index a052441ab3..6c4a204f13 100644 --- a/packages/dashboard/app/components/__tests__/App.test.tsx +++ b/packages/dashboard/app/components/__tests__/App.test.tsx @@ -646,6 +646,7 @@ vi.mock("../../hooks/useMobileKeyboard", () => ({ const mockUseViewportMode = vi.fn(() => "desktop"); vi.mock("../../hooks/useViewportMode", () => ({ MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: (...args: unknown[]) => mockUseViewportMode(...args), getViewportMode: () => mockUseViewportMode(), isMobileViewport: () => mockUseViewportMode() === "mobile", diff --git a/packages/dashboard/app/components/__tests__/ExecutorStatusBar.test.tsx b/packages/dashboard/app/components/__tests__/ExecutorStatusBar.test.tsx index e89142c06d..bf7abc0f6c 100644 --- a/packages/dashboard/app/components/__tests__/ExecutorStatusBar.test.tsx +++ b/packages/dashboard/app/components/__tests__/ExecutorStatusBar.test.tsx @@ -11,6 +11,7 @@ const mockFetchScripts = vi.hoisted(() => vi.fn()); vi.mock("../../hooks/useViewportMode", () => ({ isFullScreenSheetViewport: () => false, isShortViewport: () => false, +isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => viewportModeMock.value, })); diff --git a/packages/dashboard/app/components/__tests__/FileBrowserModal.test.tsx b/packages/dashboard/app/components/__tests__/FileBrowserModal.test.tsx index df65fd6e63..a6416ad7e9 100644 --- a/packages/dashboard/app/components/__tests__/FileBrowserModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/FileBrowserModal.test.tsx @@ -18,6 +18,7 @@ vi.mock("../../hooks/useViewportMode", () => { isShortViewport: () => false, getViewportMode: mode, isMobileViewport: () => mode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: mode, }; }); diff --git a/packages/dashboard/app/components/__tests__/Header.mobile-project-favorites.test.tsx b/packages/dashboard/app/components/__tests__/Header.mobile-project-favorites.test.tsx index 86df8fbb40..5f88738be6 100644 --- a/packages/dashboard/app/components/__tests__/Header.mobile-project-favorites.test.tsx +++ b/packages/dashboard/app/components/__tests__/Header.mobile-project-favorites.test.tsx @@ -12,6 +12,7 @@ vi.mock("../../api", async (importOriginal) => ({ })); vi.mock("../../hooks/useViewportMode", () => ({ + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "mobile", })); diff --git a/packages/dashboard/app/components/__tests__/MailboxView.test.tsx b/packages/dashboard/app/components/__tests__/MailboxView.test.tsx index 4fa1f9ba70..9a8d364f0d 100644 --- a/packages/dashboard/app/components/__tests__/MailboxView.test.tsx +++ b/packages/dashboard/app/components/__tests__/MailboxView.test.tsx @@ -39,6 +39,7 @@ vi.mock("../../hooks/useViewportMode", () => { MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)", getViewportMode: () => useViewportMode(), isMobileViewport: () => useViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode, }; }); diff --git a/packages/dashboard/app/components/__tests__/MilestoneSliceInterviewModal.test.tsx b/packages/dashboard/app/components/__tests__/MilestoneSliceInterviewModal.test.tsx index e4f18e2262..48b7d88419 100644 --- a/packages/dashboard/app/components/__tests__/MilestoneSliceInterviewModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/MilestoneSliceInterviewModal.test.tsx @@ -54,6 +54,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => "mobile", isMobileViewport: () => true, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "mobile", })); vi.mock("lucide-react", () => ({ diff --git a/packages/dashboard/app/components/__tests__/MissionManager.delete-confirm.test.tsx b/packages/dashboard/app/components/__tests__/MissionManager.delete-confirm.test.tsx index 1c7fbd6947..ccab02b103 100644 --- a/packages/dashboard/app/components/__tests__/MissionManager.delete-confirm.test.tsx +++ b/packages/dashboard/app/components/__tests__/MissionManager.delete-confirm.test.tsx @@ -26,6 +26,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => mockViewportMode(), isMobileViewport: () => mockViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => mockViewportMode(), })); diff --git a/packages/dashboard/app/components/__tests__/MissionManager.swipe-back.test.tsx b/packages/dashboard/app/components/__tests__/MissionManager.swipe-back.test.tsx index 6ce4945390..d11d1bcafd 100644 --- a/packages/dashboard/app/components/__tests__/MissionManager.swipe-back.test.tsx +++ b/packages/dashboard/app/components/__tests__/MissionManager.swipe-back.test.tsx @@ -23,6 +23,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => mockViewportMode(), isMobileViewport: () => mockViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => mockViewportMode(), })); diff --git a/packages/dashboard/app/components/__tests__/NewTaskModal.shared-cache.test.tsx b/packages/dashboard/app/components/__tests__/NewTaskModal.shared-cache.test.tsx index e80687c450..0e6d3de5e7 100644 --- a/packages/dashboard/app/components/__tests__/NewTaskModal.shared-cache.test.tsx +++ b/packages/dashboard/app/components/__tests__/NewTaskModal.shared-cache.test.tsx @@ -32,6 +32,7 @@ vi.mock("../../hooks/useViewportMode", () => { MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)", getViewportMode: () => useViewportMode(), isMobileViewport: () => useViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode, }; }); diff --git a/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx b/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx index d0122d19f0..9eb0b68716 100644 --- a/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx +++ b/packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx @@ -9,7 +9,7 @@ const mockPlanningSse = vi.hoisted(() => ({ events: null as Record ({ useOptionalToast: () => null, useToast: () => ({ addToast: vi.fn(), removeToast: vi.fn(), toasts: [] }) })); vi.mock("../../hooks/useNavigationHistory", () => ({ useNavigationHistoryContext: () => ({ pushNav: vi.fn(), replaceCurrent: vi.fn() }) })); -vi.mock("../../hooks/useViewportMode", () => ({ MOBILE_MEDIA_QUERY: "(max-width: 768px)", isFullScreenSheetViewport: () => false, isShortViewport: () => false, getViewportMode: () => mockViewportMode(), isMobileViewport: () => mockViewportMode() === "mobile", useViewportMode: () => mockViewportMode() })); +vi.mock("../../hooks/useViewportMode", () => ({ MOBILE_MEDIA_QUERY: "(max-width: 768px)", isFullScreenSheetViewport: () => false, isShortViewport: () => false, getViewportMode: () => mockViewportMode(), isMobileViewport: () => mockViewportMode() === "mobile", isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => mockViewportMode() })); vi.mock("../../hooks/useMobileKeyboard", () => ({ useMobileKeyboard: () => ({ keyboardOverlap: 0, viewportHeight: null, viewportOffsetTop: 0, keyboardOpen: false }) })); vi.mock("../../hooks/useConfirm", () => ({ useConfirm: () => ({ confirm: vi.fn().mockResolvedValue(true) }) })); vi.mock("../../sse-bus", () => ({ diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.cliBinaryVisibility.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.cliBinaryVisibility.test.tsx index 11d72c86cb..1c98b93b38 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.cliBinaryVisibility.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.cliBinaryVisibility.test.tsx @@ -32,6 +32,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)", isFullScreenSheetViewport: () => false, isShortViewport: () => false, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "desktop", getViewportMode: () => "desktop", isMobileViewport: () => false, @@ -94,21 +95,22 @@ describe("SettingsModal CLI Binary visibility", () => { it("keeps CLI Binary reachable from the Basic-mode desktop nav", async () => { const user = userEvent.setup(); - const { container } = await renderBasicSettings(); + await renderBasicSettings(); expect(screen.getByRole("checkbox", { name: "Advanced settings" })).not.toBeChecked(); - expect(container.querySelector(".settings-mobile-section-picker")).toBeNull(); + // FNXC:SettingsModalTests 2026-07-28-17:20: FN-8606 portals the modal branch (FloatingWindow) to document.body, so modal-internal nodes resolve from the document root, not the render container. + expect(document.querySelector(".settings-mobile-section-picker")).toBeNull(); await user.click(screen.getByRole("button", { name: /CLI Binary$/ })); expect(await screen.findByText(/Installing the global CLI lets you run fn and fusion/)).toBeInTheDocument(); - expect(container.querySelector(".cli-binary-panel")).toBeTruthy(); + expect(document.querySelector(".cli-binary-panel")).toBeTruthy(); }); it("returns CLI Binary for a Basic-mode search and lets operators open it", async () => { const user = userEvent.setup(); - const { container } = await renderBasicSettings(); + await renderBasicSettings(); - expect(container.querySelector(".settings-mobile-section-picker")).toBeNull(); + expect(document.querySelector(".settings-mobile-section-picker")).toBeNull(); await user.type(screen.getByTestId("settings-search-input"), "binary check"); const cliBinaryNav = await screen.findByRole("button", { name: /CLI Binary$/ }); diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx index 81ed975db3..678846c182 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx @@ -178,6 +178,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => viewportMode, isMobileViewport: () => viewportMode === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => viewportMode, })); vi.mock("lucide-react", async (importOriginal) => { @@ -523,9 +524,15 @@ describe("SettingsModal", () => { viewportOffsetTop: 50, }); - const { container } = renderModal(); + renderModal(); await waitForSettingsModalReady(); - const modal = container.querySelector(".settings-modal"); + /* + FNXC:SettingsModalTests 2026-07-28-17:00: + FN-8606 migrated the modal branch to the shared FloatingWindow, which portals the + `.settings-modal` panel to document.body. Container-scoped queries no longer see it, so + resolve the keyboard-styled panel from the document root. + */ + const modal = document.querySelector(".settings-modal"); expect(mockUseMobileKeyboard).toHaveBeenCalledWith({ enabled: true }); expect(modal?.getAttribute("style")).toContain("--keyboard-overlap: 250px"); @@ -730,11 +737,18 @@ describe("SettingsModal", () => { it("keeps the overlay and Escape-to-close in modal mode", async () => { const onClose = vi.fn(); - const { container } = renderModal({ onClose }); + renderModal({ onClose }); await waitForSettingsModalReady(); - expect(container.querySelector(".settings-modal-overlay")).not.toBeNull(); - expect(container.querySelector(".settings-modal--embedded")).toBeNull(); + /* + FNXC:SettingsModalTests 2026-07-28-17:00: + FN-8606 migrated the modal branch to the shared FloatingWindow: the dialog overlay is now + `.floating-window-overlay` portaled to document.body (not the legacy `.settings-modal-overlay`). + Escape-to-close is still owned by SettingsModal's own keydown handler, so the dismissal + contract is unchanged. + */ + expect(document.querySelector(".floating-window-overlay")).not.toBeNull(); + expect(document.querySelector(".settings-modal--embedded")).toBeNull(); fireEvent.keyDown(document, { key: "Escape" }); expect(onClose).toHaveBeenCalled(); }); @@ -2044,10 +2058,14 @@ describe("SettingsModal", () => { ["footer Close", async (container: HTMLElement) => settingsModalUser.click(container.querySelector(".modal-actions-right button") as HTMLButtonElement)], ["header close", async (container: HTMLElement) => settingsModalUser.click(container.querySelector(".modal-close") as HTMLButtonElement)], ["Escape", async () => { fireEvent.keyDown(document, { key: "Escape" }); }], - ["backdrop", async (container: HTMLElement) => { - const overlay = container.querySelector(".settings-modal-overlay") as HTMLElement; - fireEvent.mouseDown(overlay); - fireEvent.mouseUp(overlay); + /* + FNXC:SettingsModalTests 2026-07-28-17:00: + FN-8606's FloatingWindow migration replaced the click-through backdrop element with an + opt-in outside-pointerdown dismissal (see FloatingWindow `closeOnOutsidePointerDown`). Dismiss + by firing a document-level pointerdown outside the panel instead of clicking a `.settings-modal-overlay`. + */ + ["backdrop", async () => { + fireEvent.pointerDown(document.body); }], ])("flushes the latest edit through %s without a leave warning", async (path, dismiss) => { const onClose = vi.fn(); diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.keyboardShortcuts.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.keyboardShortcuts.test.tsx index 922f672906..a0bfd51f8a 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.keyboardShortcuts.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.keyboardShortcuts.test.tsx @@ -32,6 +32,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)", isFullScreenSheetViewport: () => false, isShortViewport: () => false, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "desktop", getViewportMode: () => "desktop", isMobileViewport: () => false, diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx index a222474ae0..bb50e4c19c 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx @@ -162,6 +162,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => "mobile", isMobileViewport: () => true, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "mobile", })); vi.mock("lucide-react", async (importOriginal) => { @@ -1129,10 +1130,10 @@ describe("SettingsModal", () => { return 1 as unknown as ReturnType; }); - const { container } = renderModal(); + renderModal(); await waitForSettingsModalReady(); - const settingsContent = container.querySelector(".settings-content") as HTMLDivElement; + const settingsContent = document.querySelector(".settings-content") as HTMLDivElement; expect(settingsContent).toBeInTheDocument(); const scrollToSpy = vi.fn(); Object.defineProperty(settingsContent, "scrollTo", { @@ -1566,10 +1567,10 @@ describe("SettingsModal", () => { providers: [{ id: "openai", name: "OpenAI", authenticated: true, type: "api_key" }], }); - const { container } = renderModal(); + renderModal(); await waitForSettingsModalReady(); - const settingsContent = container.querySelector(".settings-content") as HTMLDivElement; + const settingsContent = document.querySelector(".settings-content") as HTMLDivElement; expect(settingsContent).toBeInTheDocument(); const scrollToSpy = vi.fn(); Object.defineProperty(settingsContent, "scrollTo", { @@ -1593,9 +1594,9 @@ describe("SettingsModal", () => { }); mockSaveApiKey.mockResolvedValueOnce({ success: true, modelsRefreshed: 4 }); - const { container } = renderModal(); + renderModal(); await waitForSettingsModalReady(); - const settingsContent = container.querySelector(".settings-content") as HTMLDivElement; + const settingsContent = document.querySelector(".settings-content") as HTMLDivElement; Object.defineProperty(settingsContent, "scrollTo", { value: vi.fn(), writable: true }); const card = screen.getByTestId("auth-provider-icon-opencode-go").closest(".auth-provider-card") as HTMLElement; @@ -1611,9 +1612,9 @@ describe("SettingsModal", () => { }); mockSaveApiKey.mockResolvedValueOnce({ success: true, modelsRefreshed: 0, refreshReason: "no-models-from-cli" }); - const { container } = renderModal(); + renderModal(); await waitForSettingsModalReady(); - const settingsContent = container.querySelector(".settings-content") as HTMLDivElement; + const settingsContent = document.querySelector(".settings-content") as HTMLDivElement; Object.defineProperty(settingsContent, "scrollTo", { value: vi.fn(), writable: true }); const card = screen.getByTestId("auth-provider-icon-opencode-go").closest(".auth-provider-card") as HTMLElement; @@ -1629,9 +1630,9 @@ describe("SettingsModal", () => { }); mockSaveApiKey.mockResolvedValueOnce({ success: true, refreshError: "spawn opencode ENOENT" }); - const { container } = renderModal(); + renderModal(); await waitForSettingsModalReady(); - const settingsContent = container.querySelector(".settings-content") as HTMLDivElement; + const settingsContent = document.querySelector(".settings-content") as HTMLDivElement; Object.defineProperty(settingsContent, "scrollTo", { value: vi.fn(), writable: true }); const card = screen.getByTestId("auth-provider-icon-opencode-go").closest(".auth-provider-card") as HTMLElement; diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.navResize.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.navResize.test.tsx index e5567298e8..945b1c56bd 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.navResize.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.navResize.test.tsx @@ -152,6 +152,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => viewportMock.mode, isMobileViewport: () => viewportMock.mode === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => viewportMock.mode, })); diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx index 88f68cc263..eb7fd30134 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx @@ -154,6 +154,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => "mobile", isMobileViewport: () => true, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "mobile", })); vi.mock("lucide-react", async (importOriginal) => { @@ -499,22 +500,23 @@ describe("SettingsModal", () => { it("renders remote-status-bar with stopped state and omits share block when not running", async () => { mockFetchRemoteStatus.mockResolvedValue({ provider: null, state: "stopped", url: null, lastError: null }); - const { container } = await renderModalSection("remote", "Remote Access"); + await renderModalSection("remote", "Remote Access"); - const statusBar = container.querySelector(".remote-status-bar"); + // FNXC:SettingsModalTests 2026-07-28-17:20: FN-8606 portals the modal branch (FloatingWindow) to document.body, so modal-internal nodes resolve from the document root, not the render container. + const statusBar = document.querySelector(".remote-status-bar"); expect(statusBar).toBeInTheDocument(); expect(statusBar?.className).toContain("remote-status-bar--stopped"); - expect(container.querySelector(".remote-share-block")).not.toBeInTheDocument(); + expect(document.querySelector(".remote-share-block")).not.toBeInTheDocument(); }); it("renders remote-share-block when tunnel is running with a URL", async () => { mockFetchRemoteStatus.mockResolvedValue({ provider: "tailscale", state: "running", url: "https://machine.ts.net/", lastError: null }); - const { container } = await renderModalSection("remote", "Remote Access"); + await renderModalSection("remote", "Remote Access"); - const statusBar = container.querySelector(".remote-status-bar"); + const statusBar = document.querySelector(".remote-status-bar"); expect(statusBar).toBeInTheDocument(); expect(statusBar?.className).toContain("remote-status-bar--running"); - expect(container.querySelector(".remote-share-block")).toBeInTheDocument(); + expect(document.querySelector(".remote-share-block")).toBeInTheDocument(); }); it("updates provider selection via radio and shows provider status", async () => { diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.scheduling-merge.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.scheduling-merge.test.tsx index e8a4b34a03..6c44e0e997 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.scheduling-merge.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.scheduling-merge.test.tsx @@ -155,6 +155,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => "mobile", isMobileViewport: () => true, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "mobile", })); vi.mock("lucide-react", async (importOriginal) => { diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.testMode.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.testMode.test.tsx index 49d9fc1a56..bcc14d0a97 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.testMode.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.testMode.test.tsx @@ -20,6 +20,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)", isFullScreenSheetViewport: () => false, isShortViewport: () => false, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "desktop", getViewportMode: () => "desktop", isMobileViewport: () => false, diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.worktrunk.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.worktrunk.test.tsx index bd55d33bd9..c1a9756d5d 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.worktrunk.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.worktrunk.test.tsx @@ -34,6 +34,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)", isFullScreenSheetViewport: () => false, isShortViewport: () => false, + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: () => "desktop", getViewportMode: () => "desktop", isMobileViewport: () => false, diff --git a/packages/dashboard/app/components/__tests__/SettingsModalNodeRouting.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModalNodeRouting.test.tsx index a4cbb55828..a319db718e 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModalNodeRouting.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModalNodeRouting.test.tsx @@ -66,6 +66,25 @@ vi.mock("../../api", () => ({ loginProvider: vi.fn(), logoutProvider: vi.fn(), fetchProjects: vi.fn(() => Promise.resolve([])), + /* + FNXC:DashboardMocks 2026-07-28-17:35: + The auto-update / restart-supervision work (system-info probe + update install/restart) added new + `../api` runtime exports that SettingsModal reads on mount. This hardcoded api mock must expose them + or vitest throws "No is defined on the mock" the moment Settings renders. Type-only exports + are erased at runtime and intentionally omitted. + */ + fetchSystemInfo: vi.fn(() => Promise.resolve({ supervised: true, restartSupported: true })), + installUpdate: vi.fn(() => Promise.resolve({ ok: true })), + requestSystemRestart: vi.fn(() => Promise.resolve({ ok: true })), + installCloudflared: vi.fn(() => Promise.resolve({ ok: true })), + submitProviderManualCode: vi.fn(() => Promise.resolve({ ok: true })), + fetchPlugins: vi.fn(() => Promise.resolve({ plugins: [] })), + cancelProviderLogin: vi.fn(() => Promise.resolve(undefined)), + saveApiKey: vi.fn(() => Promise.resolve({ ok: true })), + clearApiKey: vi.fn(() => Promise.resolve({ ok: true })), + testNotification: vi.fn(() => Promise.resolve({ ok: true })), + fetchGitRemotes: vi.fn(() => Promise.resolve({ remotes: [] })), + fetchGitBranches: vi.fn(() => Promise.resolve({ branches: [] })), })); vi.mock("../../hooks/useNodes", () => ({ diff --git a/packages/dashboard/app/components/__tests__/TaskDetail.mobile-transition.board-panel.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetail.mobile-transition.board-panel.test.tsx index b0c4494c88..165ea0e0cf 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetail.mobile-transition.board-panel.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetail.mobile-transition.board-panel.test.tsx @@ -238,6 +238,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => mockUseViewportMode(), isMobileViewport: () => mockUseViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args), })); diff --git a/packages/dashboard/app/components/__tests__/TaskDetail.swipe-back.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetail.swipe-back.test.tsx index f8d4c36668..529c7150d0 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetail.swipe-back.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetail.swipe-back.test.tsx @@ -312,6 +312,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => mockUseViewportMode(), isMobileViewport: () => mockUseViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args), })); diff --git a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx index e98bd10358..dc1ffc2913 100644 --- a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx +++ b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx @@ -353,6 +353,7 @@ vi.mock("../../hooks/useViewportMode", () => ({ isShortViewport: () => false, getViewportMode: () => mockUseViewportMode(), isMobileViewport: () => mockUseViewportMode() === "mobile", + isTabletTouchViewport: (mode?: string) => mode === "tablet", useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args), }));