From a0602d0d26e17e91d5b8e7b5fac068fad3fdf0ea Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 28 Jun 2026 02:17:25 -0700 Subject: [PATCH] FN-7169: Open board tasks in the right sidebar Add a default-off project setting that routes board task-card opens into the right dock when available. - Add the openTasksInRightSidebar project setting, defaults, docs, and release changeset. - Wire Appearance settings to save the toggle and refresh embedded settings closes. - Render board-opened task detail inside the right dock with mobile/dock-inactive fallback to the full panel. - Cover the setting defaults, board routing, right-dock task surface, and Appearance toggle with tests. Files changed: .changeset/fn-7169-open-tasks-in-right-sidebar.md | 7 ++ docs/dashboard-guide.md | 2 + docs/settings-reference.md | 1 + .../core/src/__tests__/settings-defaults.test.ts | 13 +++ packages/core/src/settings-schema.ts | 1 + packages/core/src/types.ts | 7 ++ packages/dashboard/app/App.tsx | 29 +++++- .../__tests__/App.openTasksInRightSidebar.test.ts | 16 +++ packages/dashboard/app/components/RightDock.tsx | 41 ++++++-- .../dashboard/app/components/SettingsModal.tsx | 1 + .../app/components/__tests__/RightDock.test.tsx | 109 +++++++++++++++++++++ .../app/components/dashboard/MainContent.tsx | 13 ++- .../__tests__/MainContent.graph-popout.test.tsx | 29 ++++++ .../dashboard/app/components/dashboard/types.ts | 2 + .../settings/sections/AppearanceSection.tsx | 9 +- .../sections/__tests__/AppearanceSection.test.tsx | 63 ++++++++++++ .../app/components/useRightDockController.tsx | 64 +++++++++++- packages/dashboard/app/hooks/useAppSettings.ts | 5 + packages/dashboard/vitest.config.ts | 1 + 19 files changed, 397 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-7169 Fusion-Task-Lineage: b135a1ac-b661-4774-9a04-91aaf5184394 Co-authored-by: Fusion (runfusion.ai) --- .../fn-7169-open-tasks-in-right-sidebar.md | 7 ++ docs/dashboard-guide.md | 2 + docs/settings-reference.md | 1 + .../src/__tests__/settings-defaults.test.ts | 13 +++ packages/core/src/settings-schema.ts | 1 + packages/core/src/types.ts | 7 ++ packages/dashboard/app/App.tsx | 29 ++++- .../App.openTasksInRightSidebar.test.ts | 16 +++ .../dashboard/app/components/RightDock.tsx | 41 +++++-- .../app/components/SettingsModal.tsx | 1 + .../components/__tests__/RightDock.test.tsx | 109 ++++++++++++++++++ .../app/components/dashboard/MainContent.tsx | 13 ++- .../MainContent.graph-popout.test.tsx | 29 +++++ .../app/components/dashboard/types.ts | 2 + .../settings/sections/AppearanceSection.tsx | 9 +- .../__tests__/AppearanceSection.test.tsx | 63 ++++++++++ .../app/components/useRightDockController.tsx | 64 +++++++++- .../dashboard/app/hooks/useAppSettings.ts | 5 + packages/dashboard/vitest.config.ts | 1 + 19 files changed, 397 insertions(+), 16 deletions(-) create mode 100644 .changeset/fn-7169-open-tasks-in-right-sidebar.md create mode 100644 packages/dashboard/app/__tests__/App.openTasksInRightSidebar.test.ts create mode 100644 packages/dashboard/app/components/settings/sections/__tests__/AppearanceSection.test.tsx diff --git a/.changeset/fn-7169-open-tasks-in-right-sidebar.md b/.changeset/fn-7169-open-tasks-in-right-sidebar.md new file mode 100644 index 0000000000..2fffc03a4c --- /dev/null +++ b/.changeset/fn-7169-open-tasks-in-right-sidebar.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add a project setting to open task details in the right sidebar instead of the full panel. +category: feature +dev: New project setting `openTasksInRightSidebar` (default false). When true and the right dock is available, board card clicks render the task in the right dock; falls back to the full-panel view on mobile / when the dock is inactive. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index ab38cbfa03..039fb4493b 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -52,6 +52,8 @@ The **Right Dock Panel** experiment is enabled by default. To disable it, open * When enabled on desktop or tablet project screens, the right dock is a persistent far-right tools sidebar in the project content row. By default it opens as an overlay so the main content does not reflow. Use the dock toolbar pin action to switch into push mode, where the dock becomes an in-flow pane that shrinks the main content beside it; unpinning returns to overlay mode. The selected tool, open/closed state, pinned push-mode state, width, and expanded modal size persist across reloads. +If **Settings → Appearance → Open tasks in the right sidebar** is enabled, board task-card clicks open task detail inside this right dock and keep the board visible. The setting is default off; mobile or hidden/inactive dock states automatically fall back to the existing full-panel task detail, and non-board task-open paths keep their existing behavior. + The dock toolbar has built-in inline tool panels for **Files**, **Chat**, **Activity Log**, **Git Manager**, **Dev Server** when enabled, **Secrets**, **Todos** when enabled, and **Pull Requests**. These tools render in embedded mode inside the dock instead of opening fixed popup overlays; **Files** 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 for more room. The right-dock **Files** viewer and its expanded pop-out match the Files modal for browser-previewable file types: image, video/movie, audio, and PDF selections render as native browser previews, while editable text files keep the editor and save flow. Plugin overflow views may add additional right-dock tool tabs, except plugin destinations that explicitly belong in the left sidebar. diff --git a/docs/settings-reference.md b/docs/settings-reference.md index 01c6658062..c049d5a464 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -455,6 +455,7 @@ Sandbox backend precedence is: | `buildCommand` | `string` | `undefined` | Merge-time build command (hard gate). | | `recycleWorktrees` | `boolean` | `false` | Default: off (opt-in). Reuse worktrees from a pool for faster startup. | | `showWorktreeGrouping` | `boolean` | `false` | Default: off. When off, WIP/processing columns render plain task cards without worktree group shells or worktree-name labels in both legacy and workflow-mode boards. When on, every WIP/processing column groups tasks by worktree and shows worktree names, including workflow-mode columns flagged as counting toward WIP. | +| `openTasksInRightSidebar` | `boolean` | `false` | Default: off. When off, board task-card clicks keep the existing full-panel task detail that replaces the board. When on and the right dock is active on desktop/tablet, board task-card clicks open the task detail in the right sidebar so the board stays visible; mobile or hidden/inactive right-dock states automatically fall back to the full-panel behavior. Non-board task-open paths, including list split detail, right-dock task cards, floating pop-outs, graph/plugin opens, and deep `changes`/`retries`/`workflow` opens, keep their existing behavior. | | `executorAllowSiblingBranchRename` | `boolean` | `false` | Opt back into the legacy executor behavior that silently allocates sibling branches (`fusion/-2`, `-2-2`, …) when the canonical task branch is already checked out elsewhere. When disabled (default), branch conflicts fail loudly and leave the task in `todo` with `status: "failed"` so operators can resolve conflicting branches/worktrees with git tooling before retrying. See [Task Management → Branch conflict handling](./task-management.md#branch-conflict-handling). The dashboard Settings modal exposes the same toggle with warning copy because this legacy mode is discouraged. | | `worktreeNaming` | `"random" \| "task-id" \| "task-title"` | `"random"` | Naming mode for new worktree directories. | diff --git a/packages/core/src/__tests__/settings-defaults.test.ts b/packages/core/src/__tests__/settings-defaults.test.ts index df69e7e262..2a309736a8 100644 --- a/packages/core/src/__tests__/settings-defaults.test.ts +++ b/packages/core/src/__tests__/settings-defaults.test.ts @@ -137,6 +137,19 @@ describe("settings defaults invariants", () => { }); }); + describe("openTasksInRightSidebar default", () => { + it("keeps openTasksInRightSidebar explicitly false in project defaults", () => { + expect(DEFAULT_PROJECT_SETTINGS.openTasksInRightSidebar).toBe(false); + expect("openTasksInRightSidebar" in DEFAULT_PROJECT_SETTINGS).toBe(true); + expect(PROJECT_SETTINGS_KEYS).toContain("openTasksInRightSidebar"); + }); + + it("keeps openTasksInRightSidebar project-scoped only", () => { + expect("openTasksInRightSidebar" in DEFAULT_GLOBAL_SETTINGS).toBe(false); + expect(GLOBAL_SETTINGS_KEYS).not.toContain("openTasksInRightSidebar"); + }); + }); + describe("mergeIntegrationWorktree default", () => { it("defaults project settings to reuse-task-worktree", () => { expect(DEFAULT_PROJECT_SETTINGS.mergeIntegrationWorktree).toBe("reuse-task-worktree"); diff --git a/packages/core/src/settings-schema.ts b/packages/core/src/settings-schema.ts index 3391e3721e..28bb4f6fe7 100644 --- a/packages/core/src/settings-schema.ts +++ b/packages/core/src/settings-schema.ts @@ -316,6 +316,7 @@ export const DEFAULT_PROJECT_SETTINGS = { buildCommand: undefined, recycleWorktrees: false, showWorktreeGrouping: false, + openTasksInRightSidebar: false, executorAllowSiblingBranchRename: false, worktreeNaming: "random", worktrunk: { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 70a3cd0798..2cae44f741 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -3679,6 +3679,13 @@ export interface ProjectSettings { * This is an explicit show/hide project setting. The default-off state hides worktree grouping and labels in both legacy and workflow-mode WIP columns; when enabled, operators see grouping in every WIP/processing column, including workflow-mode columns flagged as counting toward WIP. */ showWorktreeGrouping?: boolean; + /** + * When true, board task-card clicks open task detail in the right dock when that dock surface is active; otherwise board clicks keep the full main-panel task detail. Default: false. + * + * FNXC:OpenTasksInRightSidebar 2026-06-28-00:00: + * This project-scoped setting is default-off so current board navigation is unchanged. When enabled, only Board card clicks may route to the tablet/desktop right dock; all non-board task-open paths and dock-inactive/mobile states must preserve the full-panel or existing modal behavior. + */ + openTasksInRightSidebar?: boolean; /** When true, restores the legacy behavior of silently creating sibling * branches like `fusion/FN-123-2` when the canonical task branch is already * checked out elsewhere. Default: false. */ diff --git a/packages/dashboard/app/App.tsx b/packages/dashboard/app/App.tsx index b33776124d..8471104c00 100644 --- a/packages/dashboard/app/App.tsx +++ b/packages/dashboard/app/App.tsx @@ -181,6 +181,10 @@ function prefetchLazyViews() { registerBundledPluginViews(); +export function shouldOpenBoardTaskInDock(openTasksInRightSidebar: boolean, rightDockActive: boolean, initialTab?: DetailTaskTab): boolean { + return !initialTab && openTasksInRightSidebar && rightDockActive; +} + function AppInner() { const { t } = useTranslation("app"); const { toasts, addToast, removeToast } = useToast(); @@ -534,6 +538,7 @@ function AppInner() { staleHighFanoutBlockerAgeThresholdMs, capacityRiskBannerEnabled, capacityRiskTodoThreshold, + openTasksInRightSidebar, quickChatButtonMode, maxTotalRetriesBeforeFail, prAuthAvailable, @@ -1104,6 +1109,26 @@ function AppInner() { // Props for the extracted switch (see components/dashboard/MainContent.tsx). // Every value is passed by its App name; the switch renders the same subtrees as before. + 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, onMoveTask: moveTask, onDeleteTask: deleteTask, onArchiveTask: archiveTask, onMergeTask: mergeTask, onRetryTask: retryTask, onResetTask: resetTask, onDuplicateTask: duplicateTask, onTaskUpdated: (task: Task) => ingestCreatedTasks([task]), openSettings: (section?: string) => openSettingsWithNav(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]), prAuthAvailable, autoMerge, visibilityOptions: { experimentalFeatures: { insights: insightsEnabled, memoryView: memoryEnabled, devServerView: devServerEnabled, researchView: researchEnabled, evalsView: evalsEnabled, goalsView: goalsEnabled }, showSkillsTab: skillsEnabled, todosEnabled, pluginDashboardViews }, footerVisible: executorFooterVisible }); + + /* + FNXC:OpenTasksInRightSidebar 2026-06-28-00:00: + Board card clicks are the only task-open path governed by openTasksInRightSidebar. When the project setting is enabled and the tablet/desktop right dock is active, the board keeps its current view and asks the dock controller to render task detail; otherwise the existing full main-panel replacement remains the fallback, including mobile and hidden-footer states. + */ + const openBoardTaskDetail = useCallback((task: Task | TaskDetail, initialTab?: DetailTaskTab) => { + if (!shouldOpenBoardTaskInDock(openTasksInRightSidebar, rightDockActive, initialTab)) { + openTaskDetailInMainPanel(task, initialTab); + return; + } + rightDock.openTaskInDock(task); + }, [openTaskDetailInMainPanel, openTasksInRightSidebar, rightDock, rightDockActive]); + + useEffect(() => { + if (!openTasksInRightSidebar) { + rightDock.closeDockTask(); + } + }, [openTasksInRightSidebar, rightDock]); + const mainContentProps: MainContentProps = { showBackendConnectionErrorPage, projectsError, @@ -1114,6 +1139,7 @@ function AppInner() { taskView, modalManager, handleChangeTaskView, + refreshAppSettings, addToast, currentProject, themeMode, @@ -1190,6 +1216,7 @@ function AppInner() { showWorktreeGrouping, moveTask, pauseTask, + openBoardTaskDetail, openTaskDetailInMainPanel, openGroupModalWithNav, handleBoardQuickCreate, @@ -1303,8 +1330,6 @@ function AppInner() { markGitHubStarPromptShown, setShowGitHubStarPrompt, }; - 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) => openSettingsWithNav(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]), 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__/App.openTasksInRightSidebar.test.ts b/packages/dashboard/app/__tests__/App.openTasksInRightSidebar.test.ts new file mode 100644 index 0000000000..111996b26c --- /dev/null +++ b/packages/dashboard/app/__tests__/App.openTasksInRightSidebar.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { shouldOpenBoardTaskInDock } from "../App"; + +describe("openTasksInRightSidebar board routing", () => { + it("opens board card clicks in the dock only when the setting and dock surface are both active", () => { + expect(shouldOpenBoardTaskInDock(true, true)).toBe(true); + expect(shouldOpenBoardTaskInDock(false, true)).toBe(false); + expect(shouldOpenBoardTaskInDock(true, false)).toBe(false); + }); + + it("keeps deep-tab opens on the existing main-panel path", () => { + expect(shouldOpenBoardTaskInDock(true, true, "changes")).toBe(false); + expect(shouldOpenBoardTaskInDock(true, true, "retries")).toBe(false); + expect(shouldOpenBoardTaskInDock(true, true, "workflow")).toBe(false); + }); +}); diff --git a/packages/dashboard/app/components/RightDock.tsx b/packages/dashboard/app/components/RightDock.tsx index 74feff4b82..9d99f637d5 100644 --- a/packages/dashboard/app/components/RightDock.tsx +++ b/packages/dashboard/app/components/RightDock.tsx @@ -1,5 +1,6 @@ -import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from "react"; -import { Maximize2, Pin, PinOff } from "lucide-react"; +import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from "react"; +import type { Task, TaskDetail } from "@fusion/core"; +import { ArrowLeft, Maximize2, Pin, PinOff } from "lucide-react"; import { useTranslation } from "react-i18next"; import { findOverflowViewEntry, @@ -99,6 +100,9 @@ export interface RightDockProps { footerVisible?: boolean; pinned: boolean; onTogglePin: () => void; + dockTask?: Task | TaskDetail | null; + dockTaskContent?: ReactNode; + onCloseDockTask?: () => void; } /* @@ -122,6 +126,9 @@ export function RightDock({ footerVisible = false, pinned, onTogglePin, + dockTask = null, + dockTaskContent = null, + onCloseDockTask, }: RightDockProps) { const { t } = useTranslation("app"); const entries = useMemo(() => getVisibleOverflowViewEntries(visibilityOptions), [visibilityOptions]); @@ -152,9 +159,14 @@ export function RightDock({ return; } if (!entry?.render) return; + /* + FNXC:OpenTasksInRightSidebar 2026-06-28-00:00: + Selecting any normal right-dock tab leaves the task-detail overlay surface and restores the last overflow-view body. This avoids stacking task detail over Files/Goals and prevents orphaned task headers after the user intentionally switches dock context. + */ + onCloseDockTask?.(); setSelectedKey(key); persistRightDockView(key); - }, [renderProps, visibilityOptions]); + }, [onCloseDockTask, renderProps, visibilityOptions]); const handleResizeStart = useCallback((event: React.PointerEvent) => { event.preventDefault(); @@ -224,8 +236,10 @@ export function RightDock({ } const SelectedIcon = selectedEntry.icon; + const showingDockTask = Boolean(dockTask && dockTaskContent); const dockWidth = `${width}px`; const expandSelectedViewLabel = t("rightDock.expandView", "Expand {{label}}", { label: selectedEntry.label }); + const closeDockTaskLabel = t("rightDock.closeTaskDetail", "Back to right dock views"); const pinLabel = pinned ? t("rightDock.unpin", "Unpin sidebar (overlay content)") : t("rightDock.pin", "Pin sidebar (push content)"); @@ -291,7 +305,18 @@ export function RightDock({ > - {open && selectedEntry.render ? ( + {showingDockTask ? ( + + ) : open && selectedEntry.render ? ( + + + {controller.dock} + + ); + } + + const { rerender } = render(); + expect(screen.getByTestId("right-dock-files-view")).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId("open-first")); + expect(screen.getByTestId("dock-task-detail")).toHaveTextContent("First task"); + + fireEvent.click(screen.getByTestId("open-second")); + expect(screen.getByTestId("dock-task-detail")).toHaveTextContent("Second task"); + expect(screen.queryByText("First task")).toBeNull(); + + fireEvent.click(screen.getByTestId("close-dock-task")); + expect(screen.queryByTestId("dock-task-detail")).toBeNull(); + expect(screen.getByTestId("right-dock-files-view")).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId("open-first")); + expect(screen.getByTestId("dock-task-detail")).toHaveTextContent("First task"); + rerender(); + expect(screen.queryByTestId("right-dock")).toBeNull(); + rerender(); + expect(screen.queryByTestId("dock-task-detail")).toBeNull(); + expect(screen.getByTestId("right-dock-files-view")).toBeInTheDocument(); + }); + it("renders the pin affordance for both states and delegates the toggle", () => { const onTogglePin = vi.fn(); const { rerender } = render(); diff --git a/packages/dashboard/app/components/dashboard/MainContent.tsx b/packages/dashboard/app/components/dashboard/MainContent.tsx index b43711f541..a0806cb14d 100644 --- a/packages/dashboard/app/components/dashboard/MainContent.tsx +++ b/packages/dashboard/app/components/dashboard/MainContent.tsx @@ -36,6 +36,7 @@ export function MainContent({ taskView, modalManager, handleChangeTaskView, + refreshAppSettings, addToast, currentProject, themeMode, @@ -112,6 +113,7 @@ export function MainContent({ showWorktreeGrouping, moveTask, pauseTask, + openBoardTaskDetail, openTaskDetailInMainPanel, openGroupModalWithNav, handleBoardQuickCreate, @@ -188,11 +190,15 @@ export function MainContent({ /* FNXC:Settings 2026-06-22-00:00: Settings renders ahead of the overview branch so the header gear opens the embedded Settings view even when no project is selected (viewMode === "overview"), matching the prior modal which opened regardless of view mode. + + FNXC:OpenTasksInRightSidebar 2026-06-28-00:00: + Embedded Settings closes must refresh App-scoped settings before returning to the board. The openTasksInRightSidebar routing hook reads project settings through useAppSettings, so saving the Appearance toggle needs the same refresh path as the modal settings close to make board-card routing change immediately without a reload. */ if (taskView === "settings") { const closeSettingsView = () => { modalManager.closeSettings(); handleChangeTaskView("board"); + void refreshAppSettings(); }; return ( @@ -662,6 +668,9 @@ export function MainContent({ /* FNXC:Navigation 2026-06-22-00:00: Board-opened task detail renders as a full main-content view that replaces the board. A Back-to-board button sits above an embedded TaskDetailContent (same props ListView passes to its split-detail pane). The live task is preferred from `tasks` by id so the detail updates on revalidation; the stored snapshot is the fallback. If neither resolves (snapshot cleared), fall back to the board so the panel is never blank. + + FNXC:OpenTasksInRightSidebar 2026-06-28-00:00: + Both Board render sites use App's setting-aware board-open handler. That keeps this switch presentational while ensuring only Board card clicks can route into the right dock; deep-tab, list, plugin, and modal task-open paths continue to call their existing handlers. */ if (taskView === "task-detail") { const liveDetailTask = mainPanelDetailTask @@ -677,7 +686,7 @@ export function MainContent({ showWorktreeGrouping={showWorktreeGrouping} onMoveTask={moveTask} onPauseTask={pauseTask} - onOpenDetail={openTaskDetailInMainPanel} + onOpenDetail={openBoardTaskDetail} onOpenGroupModal={openGroupModalWithNav} addToast={addToast} onQuickCreate={handleBoardQuickCreate} @@ -773,7 +782,7 @@ export function MainContent({ showWorktreeGrouping={showWorktreeGrouping} onMoveTask={moveTask} onPauseTask={pauseTask} - onOpenDetail={openTaskDetailInMainPanel} + onOpenDetail={openBoardTaskDetail} onOpenGroupModal={openGroupModalWithNav} addToast={addToast} onQuickCreate={handleBoardQuickCreate} diff --git a/packages/dashboard/app/components/dashboard/__tests__/MainContent.graph-popout.test.tsx b/packages/dashboard/app/components/dashboard/__tests__/MainContent.graph-popout.test.tsx index 437ba42bf3..bda2c76805 100644 --- a/packages/dashboard/app/components/dashboard/__tests__/MainContent.graph-popout.test.tsx +++ b/packages/dashboard/app/components/dashboard/__tests__/MainContent.graph-popout.test.tsx @@ -51,6 +51,9 @@ const otherTask = { } as unknown as Task; const LazyStub = lazy(async () => ({ default: () => null })); +const LazySettingsCloseStub = lazy(async () => ({ + default: ({ onClose }: { onClose: () => void }) => , +})); function mainContentProps(overrides: Partial = {}): MainContentProps { return { @@ -67,6 +70,7 @@ function mainContentProps(overrides: Partial = {}): MainConten openWorkflowEditor: vi.fn(), } as unknown as MainContentProps["modalManager"], handleChangeTaskView: vi.fn(), + refreshAppSettings: vi.fn(async () => undefined), addToast: vi.fn(), currentProject: { id: "project-1", name: "Project 1" } as MainContentProps["currentProject"], themeMode: "system", @@ -205,6 +209,31 @@ function mainContentProps(overrides: Partial = {}): MainConten } describe("MainContent graph task pop-out wiring", () => { + it("refreshes app settings when the embedded Settings view closes", async () => { + const closeSettings = vi.fn(); + const handleChangeTaskView = vi.fn(); + const refreshAppSettings = vi.fn(async () => undefined); + + render( + , + ); + + await screen.findByText("Close settings view"); + screen.getByText("Close settings view").click(); + + expect(closeSettings).toHaveBeenCalledTimes(1); + expect(handleChangeTaskView).toHaveBeenCalledWith("board"); + expect(refreshAppSettings).toHaveBeenCalledTimes(1); + }); + it("routes dependency-graph bridge and rendered task-card opens to the shared pop-out", () => { hostContexts.length = 0; const openDetailTask = vi.fn(); diff --git a/packages/dashboard/app/components/dashboard/types.ts b/packages/dashboard/app/components/dashboard/types.ts index 47ea89ec38..6b4a79ec5a 100644 --- a/packages/dashboard/app/components/dashboard/types.ts +++ b/packages/dashboard/app/components/dashboard/types.ts @@ -69,6 +69,7 @@ export interface MainContentProps { taskView: TaskView; modalManager: ModalManager; handleChangeTaskView: (newView: TaskView) => void; + refreshAppSettings: () => Promise; addToast: (message: string, type?: ToastType) => void; currentProject: ProjectInfo | null; themeMode: ThemeMode; @@ -156,6 +157,7 @@ export interface MainContentProps { optionsOrPosition?: { preserveProgress?: boolean } | number, ) => Promise; pauseTask: (id: string) => Promise; + openBoardTaskDetail: (task: Task | TaskDetail, initialTab?: DetailTaskTab) => void; openTaskDetailInMainPanel: (task: Task | TaskDetail, initialTab?: DetailTaskTab) => void; openGroupModalWithNav: (groupId: string) => void; handleBoardQuickCreate: (input: TaskCreateInput) => Promise; diff --git a/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx b/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx index 6e292b90cc..33226333ee 100644 --- a/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx +++ b/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx @@ -18,7 +18,7 @@ export interface AppearanceSectionProps extends SectionBaseProps { sessionBannersHidden: boolean; setSessionBannersHidden: (hidden: boolean) => void; } -export function AppearanceSection({ scopeBanner, setForm, themeMode, colorTheme, dashboardFontScalePct, shadcnCustomColors = {}, resolvedThemeMode, onThemeModeChange, onColorThemeChange, onDashboardFontScaleChange, onShadcnCustomColorsChange, sessionBannersHidden, setSessionBannersHidden, }: AppearanceSectionProps) { +export function AppearanceSection({ scopeBanner, form, setForm, themeMode, colorTheme, dashboardFontScalePct, shadcnCustomColors = {}, resolvedThemeMode, onThemeModeChange, onColorThemeChange, onDashboardFontScaleChange, onShadcnCustomColorsChange, sessionBannersHidden, setSessionBannersHidden, }: AppearanceSectionProps) { const { t } = useTranslation("app"); return (<> {scopeBanner} @@ -37,6 +37,13 @@ export function AppearanceSection({ scopeBanner, setForm, themeMode, colorTheme, onShadcnCustomColorsChange?.(colors); }}/> +
+ + {t("settings.appearance.openTasksInRightSidebarHelp", "When enabled, board task cards open detail in the right sidebar when it is available; mobile and hidden-sidebar states keep the full task panel.")} +