diff --git a/packages/dashboard/app/components/ConfirmDialog.tsx b/packages/dashboard/app/components/ConfirmDialog.tsx index 2d94b10181..ca67b607fe 100644 --- a/packages/dashboard/app/components/ConfirmDialog.tsx +++ b/packages/dashboard/app/components/ConfirmDialog.tsx @@ -1,6 +1,8 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; import { useTranslation } from "react-i18next"; import type { ConfirmOptions } from "../hooks/useConfirm"; +import { nextFloatingZ } from "./floatingWindowStack"; import "./ConfirmDialog.css"; export interface ConfirmDialogProps { @@ -28,6 +30,16 @@ export function ConfirmDialog({ }: ConfirmDialogProps) { const { t } = useTranslation("app"); const cancelButtonRef = useRef(null); + /* + FNXC:Confirm 2026-06-23-01:30: + The confirm dialog (e.g. the "discard changes" prompt when cancelling New Task) MUST sit above the floating modal stack. Floating windows (New Task, pop-outs) live at the shared floating z-band (nextFloatingZ) and are portaled to document.body, so a confirm rendered inline at the page .modal-overlay z (~10000) paints BEHIND them. Portal the confirm to body and claim the TOP of the shared stack each time it opens so it always appears over whatever floating window triggered it. + */ + const [overlayZ, setOverlayZ] = useState(undefined); + useEffect(() => { + if (isOpen) { + setOverlayZ(nextFloatingZ()); + } + }, [isOpen]); useEffect(() => { if (!isOpen) { @@ -51,8 +63,8 @@ export function ConfirmDialog({ return null; } - return ( -
+ return createPortal( +
event.stopPropagation()} @@ -95,6 +107,7 @@ export function ConfirmDialog({
-
+ , + document.body, ); } diff --git a/packages/dashboard/app/components/LeftSidebarNav.tsx b/packages/dashboard/app/components/LeftSidebarNav.tsx index b893f43081..048fa49257 100644 --- a/packages/dashboard/app/components/LeftSidebarNav.tsx +++ b/packages/dashboard/app/components/LeftSidebarNav.tsx @@ -299,6 +299,28 @@ export function LeftSidebarNav({ onSelect: () => onChangeView("list"), }, ...(graphPluginEntry ? [mapPluginEntry(graphPluginEntry)] : []), + /* + FNXC:Navigation 2026-06-23-01:30: + Planning and Missions sit directly below Graph and above Agents (moved up from after Memory) per user request, so the planning/mission destinations sit next to the structural Board/List/Graph group. + */ + { + id: "planning", + label: t("nav.planning", "Planning"), + view: "planning", + isActive: view === "planning", + icon: Lightbulb, + testId: "sidebar-nav-planning", + onSelect: () => onChangeView("planning"), + }, + { + id: "missions", + label: t("nav.missions", "Missions"), + view: "missions", + isActive: view === "missions", + icon: Target, + testId: "sidebar-nav-missions", + onSelect: () => onChangeView("missions"), + }, ...(showAgentsTab ? [ { @@ -343,28 +365,6 @@ export function LeftSidebarNav({ ...(experimentalFeatures?.memoryView ? [{ id: "memory", label: t("header.memoryView", "Memory"), view: "memory" as TaskView, isActive: view === "memory", icon: Brain, testId: "sidebar-nav-memory", onSelect: () => onChangeView("memory") }] : []), - { - id: "planning", - /* - FNXC:Navigation 2026-06-21-00:00: - FN-6886 makes Planning Mode a first-class sidebar destination. - */ - label: t("nav.planning", "Planning"), - view: "planning", - isActive: view === "planning", - icon: Lightbulb, - testId: "sidebar-nav-planning", - onSelect: () => onChangeView("planning"), - }, - { - id: "missions", - label: t("nav.missions", "Missions"), - view: "missions", - isActive: view === "missions", - icon: Target, - testId: "sidebar-nav-missions", - onSelect: () => onChangeView("missions"), - }, { id: "documents", /* diff --git a/packages/dashboard/app/components/__tests__/ConfirmDialog.test.tsx b/packages/dashboard/app/components/__tests__/ConfirmDialog.test.tsx index d759172605..9825879c82 100644 --- a/packages/dashboard/app/components/__tests__/ConfirmDialog.test.tsx +++ b/packages/dashboard/app/components/__tests__/ConfirmDialog.test.tsx @@ -65,7 +65,7 @@ describe("ConfirmDialog", () => { it("calls onCancel when overlay clicked", () => { const onCancel = vi.fn(); - const { container } = render( + render( { />, ); - const overlay = container.querySelector(".modal-overlay"); + // FNXC: ConfirmDialog portals to document.body, so query from document (not the render container). + const overlay = document.querySelector(".modal-overlay"); expect(overlay).toBeTruthy(); fireEvent.click(overlay as Element); expect(onCancel).toHaveBeenCalledTimes(1); @@ -110,7 +111,7 @@ describe("ConfirmDialog", () => { }); it("uses compact mobile override classes on overlay and dialog surface", () => { - const { container } = render( + render( { />, ); - expect(container.querySelector(".confirm-dialog-overlay")).toBeTruthy(); - expect(container.querySelector(".confirm-dialog.modal")).toBeTruthy(); + // FNXC: portaled to document.body — query from document. + expect(document.querySelector(".confirm-dialog-overlay")).toBeTruthy(); + expect(document.querySelector(".confirm-dialog.modal")).toBeTruthy(); }); it("does not render checkbox when checkboxLabel is omitted", () => { diff --git a/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx b/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx index 0c2cd72f78..c6a8a19db5 100644 --- a/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx +++ b/packages/dashboard/app/components/__tests__/LeftSidebarNav.test.tsx @@ -270,13 +270,13 @@ describe("LeftSidebarNav", () => { "sidebar-nav-command-center", "sidebar-nav-board", "sidebar-nav-list", + "sidebar-nav-planning", + "sidebar-nav-missions", "sidebar-nav-agents", "sidebar-nav-chat", "sidebar-nav-mailbox", "sidebar-nav-skills", "sidebar-nav-memory", - "sidebar-nav-planning", - "sidebar-nav-missions", "sidebar-nav-documents", "sidebar-nav-goals", "sidebar-nav-automations", @@ -290,11 +290,14 @@ describe("LeftSidebarNav", () => { expect(orderedIndices).toEqual([...orderedIndices].sort((a, b) => a - b)); expect(orderedIndices.every((index) => index >= 0)).toBe(true); expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-command-center"))).toBeLessThan(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-agents"))); - expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-documents"))).toBe(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-missions")) + 1); - // Skills and Memory sit immediately after Mailbox and before Planning. + // FNXC:Navigation 2026-06-23-01:30: Planning + Missions now sit directly after List and before Agents; Documents (Artifacts) follows Memory. + expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-planning"))).toBe(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-list")) + 1); + expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-missions"))).toBe(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-planning")) + 1); + expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-agents"))).toBe(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-missions")) + 1); + expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-documents"))).toBe(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-memory")) + 1); + // Skills and Memory sit immediately after Mailbox. expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-skills"))).toBe(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-mailbox")) + 1); expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-memory"))).toBe(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-skills")) + 1); - expect(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-planning"))).toBe(primaryButtons.indexOf(screen.getByTestId("sidebar-nav-memory")) + 1); const sidebar = screen.getByTestId("left-sidebar-nav"); const footer = screen.getByTestId("sidebar-nav-settings").closest(".left-sidebar-nav__footer");