diff --git a/.changeset/fn-6882-toolbar-to-right-sidebar.md b/.changeset/fn-6882-toolbar-to-right-sidebar.md new file mode 100644 index 0000000000..6e8c859571 --- /dev/null +++ b/.changeset/fn-6882-toolbar-to-right-sidebar.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": minor +--- + +Move desktop toolbar tools into the right sidebar tools rail. The right dock now hosts Activity, Activity Log, Import from GitHub, Git Manager, Files, and Automation, and no longer duplicates left-sidebar content views. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 92bca0e32e..6cf3eeecec 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -35,9 +35,11 @@ On mobile viewports (`<=768px`), the sidebar is not rendered even when the defau The **Right Dock Panel** experiment is enabled by default. To disable it, open **Settings → Experimental Features** and turn off **Right Dock Panel**. -When enabled on desktop or tablet project screens, the Header **More views** three-dots control becomes a right-panel toggle. It opens a persistent dock on the right side of the project content instead of the overflow dropdown, and the icon changes to communicate the panel toggle behavior. If Left Sidebar Navigation is also enabled and the Header view-toggle row is hidden, the same right-dock toggle remains available as a standalone Header icon. +When enabled on desktop or tablet project screens, the Header right-panel toggle opens a persistent dock on the right side of the project content. If Left Sidebar Navigation is also enabled and the Header view-toggle row is hidden, the same right-dock toggle remains available as a standalone Header icon. -The dock toolbar includes Files plus the same overflow destinations that would appear in the Header overflow menu, including gated experimental/plugin views only when their own flags or plugins are active. The dock opens to **Files** by default, then restores the last selected dock view from browser storage on later visits. Each docked view has an expand button that opens the same view in a resizable modal, and both the dock width and expanded modal size persist across reloads. +The dock toolbar is a tools rail with exactly six destinations: **Activity**, **Activity Log**, **Import from GitHub**, **Git Manager**, **Files**, and **Automation**. The launcher tools reuse the same handlers as the former desktop Header toolbar buttons; **Files** remains the inline dock view, opens by default, and is the fallback when browser storage points at a removed dock key. Inline dock views have an expand button that opens the same view in a resizable modal, while action-only tools launch directly and are not expandable. Dock width and expanded modal size persist across reloads. + +Content views such as Artifacts, Research, Insights, Skills, Memory, Secrets, Evals, Goals, Todos, and Dev Server live in the left sidebar (or compact mobile navigation) rather than the right dock. The six tool buttons are no longer duplicated in the desktop top Header toolbar, and the former desktop overflow trigger is removed when it would otherwise be empty. On mobile viewports, the Right Dock never renders. The compact Header overflow and bottom `MobileNavBar` keep their existing behavior even when the experiment is enabled. @@ -525,8 +527,8 @@ Todo View is an experimental full-height dashboard surface for managing per-proj > Available when `experimentalFeatures.todoView` is enabled. Navigation: -- Desktop/tablet with Right Dock enabled: **Right Dock → Todos** -- Desktop/tablet without the right dock / with the left-sidebar opt-out layout: **Header → More views → Todos** +- Desktop/tablet with Left Sidebar Navigation enabled: **Left sidebar → Todos** +- Desktop/tablet with the left-sidebar opt-out layout: **Header → More views → Todos** - Mobile: **More** sheet → **Todos** For full behavior, API contracts, and storage details, use the canonical [Todo View guide](./todo-view.md). diff --git a/packages/dashboard/app/App.tsx b/packages/dashboard/app/App.tsx index c65eadbb3e..8506d9977a 100644 --- a/packages/dashboard/app/App.tsx +++ b/packages/dashboard/app/App.tsx @@ -1946,7 +1946,7 @@ function AppInner() { // Top progress bar reflects any in-flight revalidation: projects, current-project, or tasks. // Add new sources here, not inside TopProgressBar. const isRevalidating = projectsLoading || currentProjectLoading || isStale; - const rightDock = useRightDockController({ active: rightDockActive, projectId: currentProject?.id, addToast, settingsLoaded, researchReadinessVersion, goalAnchorId, tasks: isRemote && remoteData.tasks.length > 0 ? remoteData.tasks : tasks, workflowSteps, subscribePluginEvents, openDetailTask, openFileInBrowser, openSettings: (section?: string) => modalManager.openSettings(section as SectionId), onSendSelectionToTask: modalManager.openNewTaskWithDescription, onCreateTaskFromInsight: handleInsightTaskCreate, onNavigateToMission: handleOpenMission, onTaskCreated: (task: Task) => ingestCreatedTasks([task]), workflowStepNameLookup, prAuthAvailable, autoMerge, visibilityOptions: { experimentalFeatures: { insights: insightsEnabled, memoryView: memoryEnabled, devServerView: devServerEnabled, researchView: researchEnabled, evalsView: evalsEnabled, goalsView: goalsEnabled }, showSkillsTab: skillsEnabled, todosEnabled, pluginDashboardViews }, footerVisible: executorFooterVisible }); + const rightDock = useRightDockController({ active: rightDockActive, projectId: currentProject?.id, addToast, settingsLoaded, researchReadinessVersion, goalAnchorId, tasks: isRemote && remoteData.tasks.length > 0 ? remoteData.tasks : tasks, workflowSteps, subscribePluginEvents, openDetailTask, openFileInBrowser, openSettings: (section?: string) => modalManager.openSettings(section as SectionId), onOpenUsage: openUsageWithNav, onOpenActivityLog: openActivityLogWithNav, onOpenGitHubImport: openGitHubImportWithNav, onOpenGitManager: openGitManagerWithNav, onOpenSchedules: openSchedulesWithNav, onSendSelectionToTask: modalManager.openNewTaskWithDescription, onCreateTaskFromInsight: handleInsightTaskCreate, onNavigateToMission: handleOpenMission, onTaskCreated: (task: Task) => ingestCreatedTasks([task]), workflowStepNameLookup, prAuthAvailable, autoMerge, visibilityOptions: { experimentalFeatures: { insights: insightsEnabled, memoryView: memoryEnabled, devServerView: devServerEnabled, researchView: researchEnabled, evalsView: evalsEnabled, goalsView: goalsEnabled }, showSkillsTab: skillsEnabled, todosEnabled, pluginDashboardViews }, footerVisible: executorFooterVisible }); return ( diff --git a/packages/dashboard/app/__tests__/tablet-header-controls.test.tsx b/packages/dashboard/app/__tests__/tablet-header-controls.test.tsx index 497298b9c2..31c767c35d 100644 --- a/packages/dashboard/app/__tests__/tablet-header-controls.test.tsx +++ b/packages/dashboard/app/__tests__/tablet-header-controls.test.tsx @@ -452,7 +452,7 @@ describe("tablet header controls", () => { expect(screen.getByTestId("project-selector-trigger")).toBeDefined(); }); - // ── Desktop still shows everything inline ────────────────────── + // ── Desktop keeps primary controls inline while tool actions move to the right dock ────────────────────── describe("desktop regression (contrasted with tablet)", () => { it("renders settings inline on desktop", () => { @@ -460,9 +460,9 @@ describe("tablet header controls", () => { expect(screen.getByTitle("Settings")).toBeDefined(); }); - it("renders import from GitHub inline on desktop", () => { + it("does not render import from GitHub inline on desktop", () => { renderDesktopHeader(); - expect(screen.getByTitle("Import from GitHub")).toBeDefined(); + expect(screen.queryByTitle("Import from GitHub")).toBeNull(); }); it("renders terminal inline on desktop", () => { diff --git a/packages/dashboard/app/components/Header.css b/packages/dashboard/app/components/Header.css index 6b051edcdd..96dee6a773 100644 --- a/packages/dashboard/app/components/Header.css +++ b/packages/dashboard/app/components/Header.css @@ -326,9 +326,6 @@ non-notched devices, so this is a no-op there. Pair with viewport-fit=cover (ind border-color: var(--todo); } -.header-git-manager-btn { - position: relative; -} .btn-badge { position: absolute; diff --git a/packages/dashboard/app/components/Header.tsx b/packages/dashboard/app/components/Header.tsx index 156a7167cb..a88e9d2a13 100644 --- a/packages/dashboard/app/components/Header.tsx +++ b/packages/dashboard/app/components/Header.tsx @@ -140,7 +140,6 @@ export function Header({ onRunScript, onToggleTerminal, onOpenFiles, - filesOpen, todosEnabled, view = "board", onChangeView, @@ -197,7 +196,6 @@ export function Header({ const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false); const [isMobileProjectSwitchOpen, setIsMobileProjectSwitchOpen] = useState(false); const [isViewOverflowOpen, setIsViewOverflowOpen] = useState(false); - const [isDesktopOverflowOpen, setIsDesktopOverflowOpen] = useState(false); const [isScriptsOpen, setIsScriptsOpen] = useState(false); const [scripts, setScripts] = useState>({}); const [scriptsLoading, setScriptsLoading] = useState(false); @@ -207,8 +205,6 @@ export function Header({ const [overflowScriptsLoading, setOverflowScriptsLoading] = useState(false); const overflowButtonRef = useRef(null); const overflowMenuRef = useRef(null); - const desktopOverflowTriggerRef = useRef(null); - const desktopOverflowRef = useRef(null); const mobileSearchRef = useRef(null); const mobileSearchInputRef = useRef(null); const terminalSubmenuOpenRef = useRef(false); @@ -570,25 +566,6 @@ export function Header({ return () => document.removeEventListener("mousedown", handleClickOutside); }, [isOverflowMenuOpen]); - // Close desktop overflow menu on outside click - useEffect(() => { - if (!isDesktopOverflowOpen) return; - - const handleClickOutside = (e: MouseEvent) => { - if ( - desktopOverflowRef.current && - !desktopOverflowRef.current.contains(e.target as Node) && - desktopOverflowTriggerRef.current && - !desktopOverflowTriggerRef.current.contains(e.target as Node) - ) { - setIsDesktopOverflowOpen(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, [isDesktopOverflowOpen]); - // Close node selector on outside click useEffect(() => { if (!isNodeSelectorOpen) return; @@ -611,7 +588,6 @@ export function Header({ const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { setIsViewOverflowOpen(false); - setIsDesktopOverflowOpen(false); if (terminalSubmenuOpenRef.current) { setIsTerminalSubmenuOpen(false); return; @@ -1306,33 +1282,10 @@ export function Header({ )} - {/* Usage button - desktop only (moved to overflow on mobile/tablet) */} - {!isCompact && onOpenUsage && ( - - )} - - {/* Activity Log button - desktop only (moved to overflow on mobile/tablet) */} - {!isCompact && onOpenActivityLog && ( - - )} - - {/* Desktop actions */} - {!isCompact && !isDesktopShell && ( - - )} - {/* + FNXC:Navigation 2026-06-21-20:20: + FN-6882 moves desktop tool actions (Activity, Activity Log, GitHub Import, Git Manager, Files, Automation) out of the Header toolbar into the right-dock tools rail while compact overflow keeps those tools for mobile/tablet. + FNXC:Navigation 2026-06-21-00:00: FN-6886 removes the header Lightbulb affordances because Planning Mode is now a primary left-sidebar destination after Command Center and a single canonical MobileNavBar More item on compact breakpoints. */} @@ -1448,30 +1401,6 @@ export function Header({ )} - {/* Files button - desktop only (moved to overflow on mobile/tablet) */} - {!isCompact && onOpenFiles && ( - - )} - - {/* Git Manager button - desktop only (moved to overflow on mobile/tablet) */} - {!isCompact && onOpenGitManager && ( - - )} {/* Workflows - desktop only (moved to overflow on mobile/tablet) */} {!isCompact && onOpenWorkflowEditor && ( @@ -1485,44 +1414,6 @@ export function Header({ )} - {/* Desktop overflow menu for Nodes and Schedules */} - {!isCompact && ( -
- - {isDesktopOverflowOpen && ( -
- -
- )} -
- )} {/* FNXC:Navigation 2026-06-21-13:48: diff --git a/packages/dashboard/app/components/RightDock.tsx b/packages/dashboard/app/components/RightDock.tsx index 85d30bca36..34d63c7f67 100644 --- a/packages/dashboard/app/components/RightDock.tsx +++ b/packages/dashboard/app/components/RightDock.tsx @@ -41,10 +41,15 @@ export function persistRightDockOpen(open: boolean): void { } } +function isInlineOverflowViewKey(key: string, options: OverflowViewVisibilityOptions): key is OverflowViewKey { + const entry = findOverflowViewEntry(key as OverflowViewKey, options); + return Boolean(entry?.render); +} + function readStoredRightDockView(options: OverflowViewVisibilityOptions): OverflowViewKey { if (typeof window === "undefined") return "files"; const stored = window.localStorage.getItem(RIGHT_DOCK_VIEW_STORAGE_KEY); - return stored && isOverflowViewKeyVisible(stored, options) ? stored : "files"; + return stored && isOverflowViewKeyVisible(stored, options) && isInlineOverflowViewKey(stored, options) ? stored : "files"; } function persistRightDockWidth(width: number): void { @@ -75,6 +80,9 @@ export interface RightDockProps { /* FNXC:Navigation 2026-06-21-00:00: The right dock is an auxiliary tablet/desktop surface: it remembers the last overflow destination, starts on Files when none is valid, and resizes from its left edge without changing the canonical Header/MobileNavBar active navigation state. + +FNXC:Navigation 2026-06-21-20:14: +FN-6882 splits right-dock entries into launcher actions and inline views. Action tabs invoke their existing Header handlers without replacing the Files body; only inline entries persist selection or expand into the modal. */ export function RightDock({ open, @@ -89,18 +97,26 @@ export function RightDock({ const [width, setWidth] = useState(readStoredRightDockWidth); useEffect(() => { - if (!isOverflowViewKeyVisible(selectedKey, visibilityOptions)) { + if (!isOverflowViewKeyVisible(selectedKey, visibilityOptions) || !isInlineOverflowViewKey(selectedKey, visibilityOptions)) { setSelectedKey("files"); persistRightDockView("files"); } }, [selectedKey, visibilityOptions]); - const selectedEntry = findOverflowViewEntry(selectedKey, visibilityOptions) ?? entries[0]; + const selectedEntry = (findOverflowViewEntry(selectedKey, visibilityOptions)?.render + ? findOverflowViewEntry(selectedKey, visibilityOptions) + : findOverflowViewEntry("files", visibilityOptions)) ?? entries.find((entry) => entry.render); const selectEntry = useCallback((key: OverflowViewKey) => { + const entry = findOverflowViewEntry(key, visibilityOptions); + if (entry?.onActivate) { + entry.onActivate(renderProps); + return; + } + if (!entry?.render) return; setSelectedKey(key); persistRightDockView(key); - }, []); + }, [renderProps, visibilityOptions]); const closeDock = useCallback(() => { persistRightDockOpen(false); @@ -181,7 +197,7 @@ export function RightDock({
{entries.map((entry) => { const Icon = entry.icon; - const selected = entry.key === selectedEntry.key; + const selected = Boolean(entry.render && entry.key === selectedEntry.key); return ( + {selectedEntry.render ? ( + + ) : null}
- {selectedEntry.render(renderProps)} + {selectedEntry.render?.(renderProps)}
); diff --git a/packages/dashboard/app/components/RightDockExpandModal.tsx b/packages/dashboard/app/components/RightDockExpandModal.tsx index 0f93ca5e55..fd8c382323 100644 --- a/packages/dashboard/app/components/RightDockExpandModal.tsx +++ b/packages/dashboard/app/components/RightDockExpandModal.tsx @@ -1,12 +1,14 @@ import { useEffect, useRef, type RefObject } from "react"; import { Maximize2, X } from "lucide-react"; -import { findOverflowViewEntry, type OverflowViewKey, type OverflowViewRenderProps, type OverflowViewVisibilityOptions } from "./overflowViewRegistry"; +import { findOverflowViewEntry, type OverflowViewEntry, type OverflowViewKey, type OverflowViewRenderProps, type OverflowViewVisibilityOptions } from "./overflowViewRegistry"; import { useModalResizePersist } from "../hooks/useModalResizePersist"; import { useOverlayDismiss } from "../hooks/useOverlayDismiss"; import "./RightDock.css"; const RIGHT_DOCK_EXPAND_MODAL_SIZE_STORAGE_KEY = "fusion:right-dock-expand-modal-size"; +type RenderableOverflowViewEntry = OverflowViewEntry & Required>; + export interface RightDockExpandModalProps { viewKey: OverflowViewKey | null; renderProps: OverflowViewRenderProps; @@ -18,6 +20,9 @@ export interface RightDockExpandModalProps { /* FNXC:Navigation 2026-06-21-00:00: Expanded right-dock views reuse the same overflow registry render function as the dock body, so expanding changes only available space and never swaps to a divergent component or prop contract. + +FNXC:Navigation 2026-06-21-20:16: +FN-6882 makes most right-dock entries launcher actions. The expand modal is restricted to inline view entries so action-only tools cannot open an empty modal body. */ export function RightDockExpandModal({ viewKey, @@ -27,7 +32,8 @@ export function RightDockExpandModal({ returnFocusRef, }: RightDockExpandModalProps) { const modalRef = useRef(null); - const entry = viewKey ? findOverflowViewEntry(viewKey, visibilityOptions) : undefined; + const resolvedEntry = viewKey ? findOverflowViewEntry(viewKey, visibilityOptions) : undefined; + const entry: RenderableOverflowViewEntry | undefined = resolvedEntry?.render ? { ...resolvedEntry, render: resolvedEntry.render } : undefined; const closeAndRestoreFocus = () => { onClose(); window.setTimeout(() => returnFocusRef?.current?.focus(), 0); diff --git a/packages/dashboard/app/components/__tests__/Header.test.tsx b/packages/dashboard/app/components/__tests__/Header.test.tsx index be90ed69bb..2ff270dff9 100644 --- a/packages/dashboard/app/components/__tests__/Header.test.tsx +++ b/packages/dashboard/app/components/__tests__/Header.test.tsx @@ -74,9 +74,9 @@ describe("Header", () => { expect(container.querySelector(".shell-connection-status")).toBeNull(); }); - it("renders action buttons", () => { + it("renders desktop non-tool action buttons without toolbar tools", () => { renderHeader(); - expect(screen.getByTitle("Import from GitHub")).toBeDefined(); + expect(screen.queryByTitle("Import from GitHub")).toBeNull(); expect(screen.getByTitle("Settings")).toBeDefined(); }); @@ -104,9 +104,10 @@ describe("Header", () => { expect(screen.queryByTitle("Import from GitHub")).toBeNull(); }); - it("keeps GitHub import for mobile shell host", () => { - renderHeader({ shellHost: { kind: "mobile-shell" } }); - expect(screen.getByTitle("Import from GitHub")).toBeDefined(); + it("keeps GitHub import in compact overflow for mobile shell host", () => { + renderHeader({ shellHost: { kind: "mobile-shell" } }, "mobile"); + fireEvent.click(screen.getByTitle("More header actions")); + expect(screen.getByText("Import from GitHub")).toBeDefined(); }); it("calls onOpenSettings when settings button is clicked", () => { @@ -116,28 +117,19 @@ describe("Header", () => { expect(onOpenSettings).toHaveBeenCalled(); }); - it("calls onOpenFiles with zero arguments from desktop files button", () => { - const onOpenFiles = vi.fn(); - renderHeader({ onOpenFiles }, "desktop"); - - fireEvent.click(screen.getByTestId("files-toggle-btn")); - - expect(onOpenFiles).toHaveBeenCalledTimes(1); - expect(onOpenFiles.mock.calls[0]).toEqual([]); + it("does not render the desktop files button", () => { + renderHeader({ onOpenFiles: vi.fn() }, "desktop"); + expect(screen.queryByTestId("files-toggle-btn")).toBeNull(); }); - it("calls onOpenGitHubImport when import button is clicked", () => { - const onOpenGitHubImport = vi.fn(); - renderHeader({ onOpenGitHubImport }); - fireEvent.click(screen.getByTitle("Import from GitHub")); - expect(onOpenGitHubImport).toHaveBeenCalled(); + it("does not render the desktop GitHub import button", () => { + renderHeader({ onOpenGitHubImport: vi.fn() }, "desktop"); + expect(screen.queryByTitle("Import from GitHub")).toBeNull(); }); - it("shows the stash orphan badge on the desktop Git Manager button", () => { + it("does not render the desktop Git Manager button", () => { renderHeader({ onOpenGitManager: noop, stashOrphanCount: 5 }, "desktop"); - const button = screen.getByTestId("git-manager-btn"); - expect(button).toHaveTextContent("5"); - expect(button.querySelector(".btn-badge")?.textContent).toBe("5"); + expect(screen.queryByTestId("git-manager-btn")).toBeNull(); }); it("shows the stash orphan badge on the compact Git Manager overflow item", () => { @@ -610,9 +602,10 @@ describe("Header", () => { }); describe("files button", () => { - it("renders files button on desktop when handler is provided", () => { + it("does not render files button on desktop when handler is provided", () => { renderHeader({ onOpenFiles: vi.fn() }, "desktop"); - expect(screen.getByTitle("Browse files")).toBeDefined(); + expect(screen.queryByTitle("Browse files")).toBeNull(); + expect(screen.queryByTestId("files-toggle-btn")).toBeNull(); }); it("does not render files button on desktop when handler is omitted", () => { @@ -620,16 +613,16 @@ describe("Header", () => { expect(screen.queryByTitle("Browse files")).toBeNull(); }); - it("calls onOpenFiles when desktop files button is clicked", () => { + it("does not call onOpenFiles from the removed desktop files button", () => { const onOpenFiles = vi.fn(); renderHeader({ onOpenFiles }, "desktop"); - fireEvent.click(screen.getByTitle("Browse files")); - expect(onOpenFiles).toHaveBeenCalled(); + expect(screen.queryByTitle("Browse files")).toBeNull(); + expect(onOpenFiles).not.toHaveBeenCalled(); }); - it("applies active class when files modal is open", () => { + it("does not render an active files shell when files modal is open on desktop", () => { renderHeader({ onOpenFiles: vi.fn(), filesOpen: true }, "desktop"); - expect(screen.getByTitle("Browse files").className).toContain("btn-icon--active"); + expect(screen.queryByTitle("Browse files")).toBeNull(); }); it("shows files action in mobile overflow menu", () => { @@ -705,10 +698,10 @@ describe("Header", () => { expect(screen.queryByTitle("View usage")).toBeNull(); }); - it("renders usage button with correct title when onOpenUsage is provided on desktop", () => { + it("does not render usage button inline on desktop when onOpenUsage is provided", () => { renderHeader({ onOpenUsage: vi.fn() }, "desktop"); - expect(screen.getByTitle("View usage")).toBeDefined(); - expect(screen.getByTestId("desktop-header-usage-btn")).toBeDefined(); + expect(screen.queryByTitle("View usage")).toBeNull(); + expect(screen.queryByTestId("desktop-header-usage-btn")).toBeNull(); }); it("does not render usage button inline on mobile when onOpenUsage is provided", () => { @@ -724,26 +717,11 @@ describe("Header", () => { expect(screen.getByTestId("overflow-usage-btn")).toBeDefined(); }); - it("calls onOpenUsage with button bounds when usage button is clicked on desktop", () => { + it("does not call onOpenUsage from the removed desktop toolbar button", () => { const onOpenUsage = vi.fn(); renderHeader({ onOpenUsage }, "desktop"); - - const usageButton = screen.getByTestId("desktop-header-usage-btn") as HTMLButtonElement; - const mockRect = { - top: 12, - bottom: 52, - left: 820, - right: 860, - width: 40, - height: 40, - x: 820, - y: 12, - toJSON: () => ({}), - } as DOMRect; - usageButton.getBoundingClientRect = vi.fn(() => mockRect); - - fireEvent.click(usageButton); - expect(onOpenUsage).toHaveBeenCalledWith(mockRect); + expect(screen.queryByTestId("desktop-header-usage-btn")).toBeNull(); + expect(onOpenUsage).not.toHaveBeenCalled(); }); it("calls onOpenUsage with button bounds when usage button in overflow menu is clicked", () => { @@ -781,9 +759,9 @@ describe("Header", () => { expect(screen.queryByTitle("View Activity Log")).toBeNull(); }); - it("renders activity log button with correct title when onOpenActivityLog is provided on desktop", () => { + it("does not render activity log button inline on desktop when onOpenActivityLog is provided", () => { renderHeader({ onOpenActivityLog: vi.fn() }, "desktop"); - expect(screen.getByTitle("View Activity Log")).toBeDefined(); + expect(screen.queryByTitle("View Activity Log")).toBeNull(); }); it("does not render activity log button inline on mobile when onOpenActivityLog is provided", () => { @@ -798,11 +776,11 @@ describe("Header", () => { expect(screen.getByTestId("overflow-activity-log-btn")).toBeDefined(); }); - it("calls onOpenActivityLog when activity log button is clicked on desktop", () => { + it("does not call onOpenActivityLog from the removed desktop toolbar button", () => { const onOpenActivityLog = vi.fn(); renderHeader({ onOpenActivityLog }, "desktop"); - fireEvent.click(screen.getByTitle("View Activity Log")); - expect(onOpenActivityLog).toHaveBeenCalled(); + expect(screen.queryByTitle("View Activity Log")).toBeNull(); + expect(onOpenActivityLog).not.toHaveBeenCalled(); }); it("calls onOpenActivityLog when activity log button in overflow menu is clicked", () => { @@ -1035,10 +1013,9 @@ describe("Header", () => { }); describe("nodes button", () => { - it("omits Nodes button from desktop overflow because Nodes lives in Command Center", () => { + it("omits the empty desktop overflow trigger after Nodes and Automation moved elsewhere", () => { renderHeader({}, "desktop"); - expect(screen.getByTestId("desktop-overflow-trigger")).toBeDefined(); - fireEvent.click(screen.getByTestId("desktop-overflow-trigger")); + expect(screen.queryByTestId("desktop-overflow-trigger")).toBeNull(); expect(screen.queryByTestId("desktop-overflow-nodes-btn")).toBeNull(); }); @@ -1274,11 +1251,10 @@ describe("Header", () => { }); describe("automation button", () => { - it("renders automation button in desktop overflow", () => { + it("does not render automation in a desktop overflow shell", () => { renderHeader({ onOpenSchedules: vi.fn() }, "desktop"); - expect(screen.getByTestId("desktop-overflow-trigger")).toBeDefined(); - fireEvent.click(screen.getByTestId("desktop-overflow-trigger")); - expect(screen.getByTestId("desktop-overflow-schedules-btn")).toBeDefined(); + expect(screen.queryByTestId("desktop-overflow-trigger")).toBeNull(); + expect(screen.queryByTestId("desktop-overflow-schedules-btn")).toBeNull(); }); it("does not render automation button inline on mobile", () => { @@ -1286,18 +1262,16 @@ describe("Header", () => { expect(screen.queryByTitle("Automation")).toBeNull(); }); - it("calls onOpenSchedules when automation button is clicked from desktop overflow", () => { + it("does not call onOpenSchedules from the removed desktop overflow", () => { const onOpenSchedules = vi.fn(); renderHeader({ onOpenSchedules }, "desktop"); - fireEvent.click(screen.getByTestId("desktop-overflow-trigger")); - fireEvent.click(screen.getByTestId("desktop-overflow-schedules-btn")); - expect(onOpenSchedules).toHaveBeenCalled(); + expect(screen.queryByTestId("desktop-overflow-schedules-btn")).toBeNull(); + expect(onOpenSchedules).not.toHaveBeenCalled(); }); - it("has correct data-testid for testing on desktop", () => { + it("removes the desktop automation data-testid with the empty overflow trigger", () => { renderHeader({ onOpenSchedules: vi.fn() }, "desktop"); - fireEvent.click(screen.getByTestId("desktop-overflow-trigger")); - expect(screen.getByTestId("desktop-overflow-schedules-btn")).toBeDefined(); + expect(screen.queryByTestId("desktop-overflow-schedules-btn")).toBeNull(); }); it("includes automation in overflow menu on mobile", () => { diff --git a/packages/dashboard/app/components/__tests__/RightDock.test.tsx b/packages/dashboard/app/components/__tests__/RightDock.test.tsx index a5264cd166..69c51b23fd 100644 --- a/packages/dashboard/app/components/__tests__/RightDock.test.tsx +++ b/packages/dashboard/app/components/__tests__/RightDock.test.tsx @@ -11,22 +11,34 @@ vi.mock("../../api", async (importOriginal) => { }; }); -vi.mock("../DocumentsView", () => ({ DocumentsView: () =>
})); -vi.mock("../ResearchView", () => ({ ResearchView: () =>
})); -vi.mock("../InsightsView", () => ({ InsightsView: () =>
})); -vi.mock("../EvalsView", () => ({ EvalsView: () =>
})); -vi.mock("../SkillsView", () => ({ SkillsView: () =>
})); -vi.mock("../MemoryView", () => ({ MemoryView: () =>
})); -vi.mock("../SecretsView", () => ({ SecretsView: () =>
})); -vi.mock("../DevServerView", () => ({ DevServerView: () =>
})); -vi.mock("../TodoView", () => ({ TodoView: () =>
})); -vi.mock("../GoalsView", () => ({ GoalsView: () =>
})); - const renderProps = { addToast: vi.fn(), projectId: "project-1", }; +const toolTabIds = [ + "right-dock-tab-usage", + "right-dock-tab-activity-log", + "right-dock-tab-github-import", + "right-dock-tab-git-manager", + "right-dock-tab-files", + "right-dock-tab-automation", +]; + +const removedViewTabIds = [ + "right-dock-tab-documents", + "right-dock-tab-research", + "right-dock-tab-insights", + "right-dock-tab-skills", + "right-dock-tab-memory", + "right-dock-tab-secrets", + "right-dock-tab-evals", + "right-dock-tab-goals", + "right-dock-tab-todos", + "right-dock-tab-devserver", + "right-dock-tab-stash-recovery", +]; + describe("RightDock", () => { beforeEach(() => { window.localStorage.clear(); @@ -37,7 +49,7 @@ describe("RightDock", () => { window.localStorage.clear(); }); - it("renders Files by default and restores a persisted selected view", () => { + it("renders Files by default and restores only persisted inline views", () => { const onOpenChange = vi.fn(); const { unmount } = render( , @@ -46,15 +58,24 @@ describe("RightDock", () => { expect(screen.getByTestId("right-dock-tab-files")).toHaveAttribute("aria-selected", "true"); expect(screen.getByTestId("right-dock-files-view")).toBeInTheDocument(); - fireEvent.click(screen.getByTestId("right-dock-tab-secrets")); - expect(window.localStorage.getItem(RIGHT_DOCK_VIEW_STORAGE_KEY)).toBe("secrets"); + fireEvent.click(screen.getByTestId("right-dock-tab-automation")); + expect(window.localStorage.getItem(RIGHT_DOCK_VIEW_STORAGE_KEY)).toBeNull(); unmount(); render(); - expect(screen.getByTestId("right-dock-tab-secrets")).toHaveAttribute("aria-selected", "true"); + expect(screen.getByTestId("right-dock-tab-files")).toHaveAttribute("aria-selected", "true"); }); - it("hides entries gated off by their matching Header flags", () => { + it("falls back to Files when storage points at a removed right-dock view", () => { + window.localStorage.setItem(RIGHT_DOCK_VIEW_STORAGE_KEY, "documents"); + render(); + + expect(screen.getByTestId("right-dock-tab-files")).toHaveAttribute("aria-selected", "true"); + expect(screen.getByTestId("right-dock-files-view")).toBeInTheDocument(); + expect(screen.queryByTestId("right-dock-tab-documents")).toBeNull(); + }); + + it("renders exactly the six right-dock tool entries and no removed content-view tabs", () => { render( { renderProps={renderProps} visibilityOptions={{ experimentalFeatures: { - insights: false, - memoryView: false, - devServerView: false, - researchView: false, - evalsView: false, - goalsView: false, + insights: true, + memoryView: true, + devServerView: true, + researchView: true, + evalsView: true, + goalsView: true, }, - showSkillsTab: false, - todosEnabled: false, + showSkillsTab: true, + todosEnabled: true, }} />, ); - expect(screen.getByTestId("right-dock-tab-files")).toBeInTheDocument(); - expect(screen.getByTestId("right-dock-tab-documents")).toHaveAttribute("aria-label", "Artifacts"); - expect(screen.getByTestId("right-dock-tab-documents")).toHaveAttribute("title", "Artifacts"); - expect(screen.getByTestId("right-dock-tab-secrets")).toBeInTheDocument(); - expect(screen.queryByTestId("right-dock-tab-stash-recovery")).toBeNull(); - expect(screen.queryByTestId("right-dock-tab-research")).toBeNull(); - expect(screen.queryByTestId("right-dock-tab-insights")).toBeNull(); + expect(screen.getAllByRole("tab").map((tab) => tab.getAttribute("data-testid"))).toEqual(toolTabIds); + expect(screen.getByTestId("right-dock-tab-usage")).toHaveAttribute("aria-label", "Activity"); + expect(screen.getByTestId("right-dock-tab-activity-log")).toHaveAttribute("aria-label", "Activity Log"); + expect(screen.getByTestId("right-dock-tab-github-import")).toHaveAttribute("aria-label", "Import from GitHub"); + expect(screen.getByTestId("right-dock-tab-git-manager")).toHaveAttribute("aria-label", "Git Manager"); + expect(screen.getByTestId("right-dock-tab-files")).toHaveAttribute("aria-label", "Files"); + expect(screen.getByTestId("right-dock-tab-automation")).toHaveAttribute("aria-label", "Automation"); + for (const removedId of removedViewTabIds) { + expect(screen.queryByTestId(removedId)).toBeNull(); + } + }); + + it("clicking action tabs invokes handlers without replacing the inline Files body", () => { + const onOpenUsage = vi.fn(); + const onOpenActivityLog = vi.fn(); + const onOpenGitHubImport = vi.fn(); + const onOpenGitManager = vi.fn(); + const onOpenSchedules = vi.fn(); + render( + , + ); + + const actionAssertions: Array<[string, () => void, unknown[]]> = [ + ["right-dock-tab-usage", onOpenUsage, [null]], + ["right-dock-tab-activity-log", onOpenActivityLog, []], + ["right-dock-tab-github-import", onOpenGitHubImport, []], + ["right-dock-tab-git-manager", onOpenGitManager, []], + ["right-dock-tab-automation", onOpenSchedules, []], + ]; + + for (const [tabId, handler, args] of actionAssertions) { + fireEvent.click(screen.getByTestId(tabId)); + expect(handler).toHaveBeenCalledWith(...args); + expect(screen.getByTestId("right-dock-files-view")).toBeInTheDocument(); + expect(screen.getByTestId("right-dock-tab-files")).toHaveAttribute("aria-selected", "true"); + } + + fireEvent.click(screen.getByTestId("right-dock-tab-files")); + expect(screen.getByTestId("right-dock-files-view")).toBeInTheDocument(); }); it("closes internally and clamps then persists resize width", () => { @@ -110,47 +174,6 @@ describe("RightDock", () => { expect(screen.getByTestId("right-dock-resize-handle")).toHaveAttribute("aria-valuenow", "400"); }); - it("mounts every visible static registry view in the dock body without crashing", async () => { - render( - , - ); - - const expectedViews: Array<[string, string]> = [ - ["right-dock-tab-files", "right-dock-files-view"], - ["right-dock-tab-documents", "mock-documents-view"], - ["right-dock-tab-research", "mock-research-view"], - ["right-dock-tab-insights", "mock-insights-view"], - ["right-dock-tab-skills", "mock-skills-view"], - ["right-dock-tab-memory", "mock-memory-view"], - ["right-dock-tab-secrets", "mock-secrets-view"], - ["right-dock-tab-evals", "mock-evals-view"], - ["right-dock-tab-goals", "mock-goals-view"], - ["right-dock-tab-todos", "mock-todos-view"], - ["right-dock-tab-devserver", "mock-devserver-view"], - ]; - - for (const [tabId, bodyId] of expectedViews) { - fireEvent.click(screen.getByTestId(tabId)); - expect(await screen.findByTestId(bodyId)).toBeInTheDocument(); - } - }); - it("renders the expanded modal through the same registry and restores focus on close", async () => { const onClose = vi.fn(); const focusButton = document.createElement("button"); @@ -159,7 +182,7 @@ describe("RightDock", () => { render( { focusButton.remove(); }); + it("does not render the expanded modal for action entries", () => { + render( + , + ); + + expect(screen.queryByTestId("right-dock-expand-modal")).toBeNull(); + }); + it("restores the expanded modal's persisted size", () => { window.localStorage.setItem("fusion:right-dock-expand-modal-size", JSON.stringify({ width: 640, height: 480 })); render( , @@ -191,11 +226,11 @@ describe("RightDock", () => { }); }); - it("fires expand for the selected entry", () => { + it("fires expand for the selected inline entry only", () => { const onExpand = vi.fn(); render(); - fireEvent.click(screen.getByTestId("right-dock-tab-secrets")); + fireEvent.click(screen.getByTestId("right-dock-tab-automation")); fireEvent.click(screen.getByTestId("right-dock-expand")); - expect(onExpand).toHaveBeenCalledWith("secrets"); + expect(onExpand).toHaveBeenCalledWith("files"); }); }); diff --git a/packages/dashboard/app/components/__tests__/overflowViewRegistry.test.tsx b/packages/dashboard/app/components/__tests__/overflowViewRegistry.test.tsx index 4ed204a117..f72b834948 100644 --- a/packages/dashboard/app/components/__tests__/overflowViewRegistry.test.tsx +++ b/packages/dashboard/app/components/__tests__/overflowViewRegistry.test.tsx @@ -3,32 +3,44 @@ import { getVisibleOverflowViewEntries, STATIC_OVERFLOW_VIEW_ENTRIES } from "../ import type { PluginDashboardViewEntry } from "../../api"; describe("overflowViewRegistry", () => { - it("keeps Files as the default first entry and Artifacts visible without a viewport gate", () => { + it("exposes exactly the six static right-dock tool destinations", () => { const entries = getVisibleOverflowViewEntries(); - expect(entries[0]?.key).toBe("files"); - const documentsEntry = entries.find((entry) => entry.key === "documents"); - expect(documentsEntry?.label).toBe("Artifacts"); - expect(documentsEntry?.testId).toBe("right-dock-tab-documents"); + const keys = entries.map((entry) => entry.key); + + expect(keys).toEqual(["usage", "activity-log", "github-import", "git-manager", "files", "automation"]); + expect(entries.map((entry) => entry.label)).toEqual([ + "Activity", + "Activity Log", + "Import from GitHub", + "Git Manager", + "Files", + "Automation", + ]); + expect(entries.filter((entry) => entry.render).map((entry) => entry.key)).toEqual(["files"]); + expect(entries.filter((entry) => entry.onActivate).map((entry) => entry.key)).toEqual([ + "usage", + "activity-log", + "github-import", + "git-manager", + "automation", + ]); }); - it("matches Header feature gates for flag-controlled overflow destinations", () => { - const disabled = getVisibleOverflowViewEntries({ - experimentalFeatures: { - insights: false, - memoryView: false, - devServerView: false, - researchView: false, - evalsView: false, - goalsView: false, - }, - showSkillsTab: false, - todosEnabled: false, - }).map((entry) => entry.key); - - expect(disabled).toEqual(["files", "documents", "secrets"]); - expect(disabled).not.toContain("stash-recovery"); - - const enabled = getVisibleOverflowViewEntries({ + it("does not expose left-sidebar content views in the right-dock registry", () => { + const removedKeys = [ + "documents", + "research", + "insights", + "skills", + "memory", + "secrets", + "stash-recovery", + "evals", + "goalsView", + "todos", + "devserver", + ]; + const keys = getVisibleOverflowViewEntries({ experimentalFeatures: { insights: true, memoryView: true, @@ -41,10 +53,13 @@ describe("overflowViewRegistry", () => { todosEnabled: true, }).map((entry) => entry.key); - expect(enabled).toEqual(STATIC_OVERFLOW_VIEW_ENTRIES.map((entry) => entry.key)); + expect(keys).toEqual(STATIC_OVERFLOW_VIEW_ENTRIES.map((entry) => entry.key)); + for (const removedKey of removedKeys) { + expect(keys).not.toContain(removedKey); + } }); - it("adds only non-primary plugin views after static entries", () => { + it("adds only non-primary plugin views after static tool entries", () => { const pluginDashboardViews: PluginDashboardViewEntry[] = [ { pluginId: "plugin-a", @@ -61,7 +76,13 @@ describe("overflowViewRegistry", () => { ]; const entries = getVisibleOverflowViewEntries({ pluginDashboardViews }); - expect(entries.slice(-2).map((entry) => entry.key)).toEqual([ + expect(entries.map((entry) => entry.key)).toEqual([ + "usage", + "activity-log", + "github-import", + "git-manager", + "files", + "automation", "plugin:plugin-b:audit", "plugin:plugin-a:tools", ]); diff --git a/packages/dashboard/app/components/overflowViewRegistry.tsx b/packages/dashboard/app/components/overflowViewRegistry.tsx index 92c3a2b631..8988711806 100644 --- a/packages/dashboard/app/components/overflowViewRegistry.tsx +++ b/packages/dashboard/app/components/overflowViewRegistry.tsx @@ -1,15 +1,11 @@ -import { lazy, Suspense, type ComponentType, type ReactNode } from "react"; +import { Suspense, type ComponentType, type ReactNode } from "react"; import { - Brain, - CheckSquare, - FileText, + Activity, + Clock, Folder, - Lock, - Monitor, - Search, - Sparkles, - Target, - Zap, + GitBranch, + GitPullRequestArrow, + History, type LucideProps, } from "lucide-react"; import type { Task, TaskDetail, WorkflowStep } from "@fusion/core"; @@ -23,29 +19,13 @@ import { FileBrowser } from "./FileBrowser"; import { PageErrorBoundary } from "./ErrorBoundary"; import { getPluginNavIcon } from "./pluginNavIcon"; -const DocumentsView = lazy(() => import("./DocumentsView").then((m) => ({ default: m.DocumentsView }))); -const InsightsView = lazy(() => import("./InsightsView").then((m) => ({ default: m.InsightsView }))); -const ResearchView = lazy(() => import("./ResearchView").then((m) => ({ default: m.ResearchView }))); -const EvalsView = lazy(() => import("./EvalsView").then((m) => ({ default: m.EvalsView }))); -const SkillsView = lazy(() => import("./SkillsView").then((m) => ({ default: m.SkillsView }))); -const MemoryView = lazy(() => import("./MemoryView").then((m) => ({ default: m.MemoryView }))); -const SecretsView = lazy(() => import("./SecretsView").then((m) => ({ default: m.SecretsView }))); -const DevServerView = lazy(() => import("./DevServerView").then((m) => ({ default: m.DevServerView }))); -const TodoView = lazy(() => import("./TodoView").then((m) => ({ default: m.TodoView }))); -const GoalsView = lazy(() => import("./GoalsView").then((m) => ({ default: m.GoalsView }))); - export type OverflowViewKey = + | "usage" + | "activity-log" + | "github-import" + | "git-manager" | "files" - | "documents" - | "research" - | "insights" - | "skills" - | "memory" - | "secrets" - | "evals" - | "goalsView" - | "todos" - | "devserver" + | "automation" | `plugin:${string}:${string}`; export interface OverflowViewFeatureState { @@ -77,6 +57,11 @@ export interface OverflowViewRenderProps { renderTaskCard?: (task: Task | TaskDetail) => ReactNode; subscribePluginEvents?: PluginDashboardViewContext["subscribePluginEvents"]; openFile?: PluginDashboardViewContext["openFile"]; + onOpenUsage?: (anchorRect?: DOMRect | null) => void; + onOpenActivityLog?: () => void; + onOpenGitHubImport?: () => void; + onOpenGitManager?: () => void; + onOpenSchedules?: () => void; } export interface OverflowViewEntry { @@ -84,7 +69,8 @@ export interface OverflowViewEntry { label: string; icon: ComponentType; testId: string; - render: (props: OverflowViewRenderProps) => ReactNode; + render?: (props: OverflowViewRenderProps) => ReactNode; + onActivate?: (props: OverflowViewRenderProps) => void; isVisible?: (options: OverflowViewVisibilityOptions) => boolean; } @@ -126,8 +112,39 @@ function InlineFilesView({ projectId, openFile }: Pick props.onOpenUsage?.(null), + }, + { + key: "activity-log", + label: "Activity Log", + icon: History, + testId: "right-dock-tab-activity-log", + onActivate: (props) => props.onOpenActivityLog?.(), + }, + { + key: "github-import", + label: "Import from GitHub", + icon: GitPullRequestArrow, + testId: "right-dock-tab-github-import", + onActivate: (props) => props.onOpenGitHubImport?.(), + }, + { + key: "git-manager", + label: "Git Manager", + icon: GitBranch, + testId: "right-dock-tab-git-manager", + onActivate: (props) => props.onOpenGitManager?.(), + }, { key: "files", label: "Files", @@ -136,130 +153,11 @@ export const STATIC_OVERFLOW_VIEW_ENTRIES: readonly OverflowViewEntry[] = [ render: (props) => wrapOverflowView(), }, { - key: "documents", - /* - FNXC:Navigation 2026-06-21-18:25: - Top-level Documents was renamed to Artifacts for FN-6890; keep the documents key, route, and test id stable while changing only the displayed label. - */ - label: "Artifacts", - icon: FileText, - testId: "right-dock-tab-documents", - render: (props) => wrapOverflowView( - props.onOpenDetail?.(task)} - onSendSelectionToTask={props.onSendSelectionToTask} - />, - ), - }, - { - key: "research", - label: "Research", - icon: Search, - testId: "right-dock-tab-research", - isVisible: ({ experimentalFeatures }) => experimentalFeatures?.researchView === true, - render: (props) => props.settingsLoaded === false ? null : wrapOverflowView( - props.onOpenSettings?.(section)} - readinessVersion={props.readinessVersion ?? 0} - />, - ), - }, - { - key: "insights", - label: "Insights", - icon: Sparkles, - testId: "right-dock-tab-insights", - isVisible: ({ experimentalFeatures }) => experimentalFeatures?.insights === true, - render: (props) => props.settingsLoaded === false ? null : wrapOverflowView( - undefined} - onCreateTask={async (payload) => { - await props.onCreateTaskFromInsight?.(payload); - }} - />, - ), - }, - { - key: "skills", - label: "Skills", - icon: Zap, - testId: "right-dock-tab-skills", - isVisible: ({ showSkillsTab }) => showSkillsTab === true, - render: (props) => wrapOverflowView( undefined} />), - }, - { - key: "memory", - label: "Memory", - icon: Brain, - testId: "right-dock-tab-memory", - isVisible: ({ experimentalFeatures }) => experimentalFeatures?.memoryView === true, - render: (props) => props.settingsLoaded === false ? null : wrapOverflowView( - , - ), - }, - { - key: "secrets", - label: "Secrets", - icon: Lock, - testId: "right-dock-tab-secrets", - render: (props) => wrapOverflowView(), - }, - { - key: "evals", - label: "Evals", - icon: Target, - testId: "right-dock-tab-evals", - isVisible: ({ experimentalFeatures }) => experimentalFeatures?.evalsView === true, - render: (props) => props.settingsLoaded === false ? null : wrapOverflowView( - props.onOpenSettings?.(section)} - onOpenTaskDetail={(taskId) => props.onOpenTaskDetail?.(taskId)} - />, - ), - }, - { - key: "goalsView", - label: "Goals", - icon: Target, - testId: "right-dock-tab-goals", - isVisible: ({ experimentalFeatures }) => experimentalFeatures?.goalsView === true, - render: (props) => props.settingsLoaded === false ? null : wrapOverflowView( - props.onNavigateToMission?.(missionId)} />, - ), - }, - { - key: "todos", - label: "Todos", - icon: CheckSquare, - testId: "right-dock-tab-todos", - isVisible: ({ todosEnabled }) => todosEnabled === true, - render: (props) => wrapOverflowView( - , - ), - }, - { - key: "devserver", - label: "Dev Server", - icon: Monitor, - testId: "right-dock-tab-devserver", - isVisible: ({ experimentalFeatures }) => experimentalFeatures?.devServerView === true, - render: (props) => props.settingsLoaded === false ? null : wrapOverflowView(), + key: "automation", + label: "Automation", + icon: Clock, + testId: "right-dock-tab-automation", + onActivate: (props) => props.onOpenSchedules?.(), }, ]; diff --git a/packages/dashboard/app/components/useRightDockController.tsx b/packages/dashboard/app/components/useRightDockController.tsx index 0061a69893..3f10d48148 100644 --- a/packages/dashboard/app/components/useRightDockController.tsx +++ b/packages/dashboard/app/components/useRightDockController.tsx @@ -22,6 +22,11 @@ export interface RightDockControllerInput { openDetailTask: (task: Task | TaskDetail, initialTab?: DetailTaskTab) => void; openFileInBrowser: (path: string, opts?: { workspace?: string; line?: number; col?: number }) => void; openSettings: (section?: string) => void; + onOpenUsage?: (anchorRect?: DOMRect | null) => void; + onOpenActivityLog?: () => void; + onOpenGitHubImport?: () => void; + onOpenGitManager?: () => void; + onOpenSchedules?: () => void; onSendSelectionToTask: (description: string) => void; onCreateTaskFromInsight: (payload: { insightId: string; title: string; description: string }) => Promise | void; onNavigateToMission: (missionId: string) => void; @@ -101,6 +106,11 @@ export function useRightDockController(input: RightDockControllerInput): RightDo addToast: input.addToast, }, onOpenSettings: input.openSettings, + onOpenUsage: input.onOpenUsage, + onOpenActivityLog: input.onOpenActivityLog, + onOpenGitHubImport: input.onOpenGitHubImport, + onOpenGitManager: input.onOpenGitManager, + onOpenSchedules: input.onOpenSchedules, onOpenTaskDetail: (taskId: string) => { void fetchTaskDetail(taskId, input.projectId) .then((task) => input.openDetailTask(task as TaskDetail))