From fee920d366d6866fb8cb06e690f634445b080772 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 22 Jul 2026 00:22:42 -0700 Subject: [PATCH] FN-8478: open board deep tabs in task popups Make board task deep-tab actions preserve their requested popup tab when task popups are enabled. - Route Changes, Retries, and Workflow board actions through the popup preference. - Store and refresh each popup's requested initial tab for its task and view. - Add regression coverage, documentation, and a patch changeset. Files changed: .changeset/task-card-popup-deep-tabs.md | 7 +++++ docs/dashboard-guide.md | 4 +-- docs/settings-reference.md | 2 +- packages/dashboard/app/App.tsx | 25 ++++++++++----- .../__tests__/App.openTasksInRightSidebar.test.ts | 36 ++++++++++++---------- .../__tests__/navigation-history.test.tsx | 25 ++++++++++++++- .../settings/sections/AppearanceSection.tsx | 4 +-- .../app/hooks/__tests__/usePoppedOutTasks.test.ts | 13 ++++++++ packages/dashboard/app/hooks/usePoppedOutTasks.ts | 15 ++++++--- 9 files changed, 97 insertions(+), 34 deletions(-) Fusion-Task-Id: FN-8478 Fusion-Task-Lineage: e12452c0-24d5-4f19-b50c-39526d72d7ab Co-authored-by: Fusion (runfusion.ai) --- .changeset/task-card-popup-deep-tabs.md | 7 ++++ docs/dashboard-guide.md | 4 +-- docs/settings-reference.md | 2 +- packages/dashboard/app/App.tsx | 25 ++++++++----- .../App.openTasksInRightSidebar.test.ts | 36 ++++++++++--------- .../__tests__/navigation-history.test.tsx | 25 ++++++++++++- .../settings/sections/AppearanceSection.tsx | 4 +-- .../hooks/__tests__/usePoppedOutTasks.test.ts | 13 +++++++ .../dashboard/app/hooks/usePoppedOutTasks.ts | 15 +++++--- 9 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 .changeset/task-card-popup-deep-tabs.md diff --git a/.changeset/task-card-popup-deep-tabs.md b/.changeset/task-card-popup-deep-tabs.md new file mode 100644 index 0000000000..f280a7e9d4 --- /dev/null +++ b/.changeset/task-card-popup-deep-tabs.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Open task card files-changed links in the task popup when Open tasks as popups is enabled. +category: fix +dev: Board TaskCard deep-tab opens (changes/retries/workflow) honor openMobileTasksInPopup and pass initialTab into FloatingWindow TaskDetailContent. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index cf281f4f23..6519ab8d66 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -136,12 +136,12 @@ When enabled on desktop or tablet project screens, the right dock is a persisten 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 unless the task-popup setting below is enabled, and non-board task-open paths keep their existing behavior. - -**Settings → Appearance → Open tasks as popups** changes ordinary board task-card clicks, List row/card opens, and right-dock Tasks-list clicks across desktop, tablet, and mobile viewports. When enabled, those clicks use the existing task popup/FloatingWindow surface on the board/task-detail layer instead of the full-panel task detail, List split-detail/docked detail, or right-dock task detail, keeping the board, List view, or dock list visible in the background. Overlapping Quick Chat and task popups interleave by the most-recent pointer or focus interaction; other utility windows retain their higher global stacking. On desktop and tablet, task popups restore the last saved popup size and position between tasks; on mobile, task popups stay full-screen sheets. Deep `changes`/`retries`/`workflow` opens, List context-menu/refine actions, task-detail links, plugin/graph opens, and explicit pop-out actions keep their existing paths. +**Settings → Appearance → Open tasks as popups** changes ordinary board task-card clicks, List row/card opens, and right-dock Tasks-list clicks across desktop, tablet, and mobile viewports. When enabled, those clicks use the existing task popup/FloatingWindow surface on the board/task-detail layer instead of the full-panel task detail, List split-detail/docked detail, or right-dock task detail, keeping the board, List view, or dock list visible in the background. Overlapping Quick Chat and task popups interleave by the most-recent pointer or focus interaction; other utility windows retain their higher global stacking. On desktop and tablet, task popups restore the last saved popup size and position between tasks; on mobile, task popups stay full-screen sheets. Board task-card `changes`/`retries`/`workflow` chips open the popup with the requested tab. List context-menu/refine actions, task-detail links, plugin/graph opens, and explicit pop-out actions keep their existing paths. **Settings → Appearance → Keep task popups on the view where they were opened** is enabled by default. Every task-detail popup is attached to its exact originating view, including Planning, Agents, Command Center, Documents, Missions, and plugin views: navigating elsewhere hides it without closing it, and returning re-shows it in the same saved position. You can open the same task independently in more than one view; closing or pressing Escape on one popup does not affect the other. Disable this setting only to restore legacy globally shared popups. Legacy saved popups without an origin remain visible everywhere for compatibility. diff --git a/docs/settings-reference.md b/docs/settings-reference.md index f44675514b..329376f8e1 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -531,7 +531,7 @@ When `pushAfterMerge` is enabled, a completed direct merge first runs `git pull | `recycleWorktrees` | `boolean` | `false` | Default: off (opt-in). Reuse worktrees from a pool for faster startup. **Mutually exclusive with `worktreeNaming: "task-id"`** (task-pinned worktrees) — enabling both is rejected by the settings API/store, because pinning each task to its own directory is incompatible with the cross-task pool. Recycling is fully functional under `"random"` and `"task-title"` naming. | | `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, floating pop-outs, graph/plugin opens, and deep `changes`/`retries`/`workflow` opens, keep their existing behavior; ordinary right-dock Tasks-list cards are governed by `openMobileTasksInPopup` first and otherwise use embedded dock detail. | -| `openMobileTasksInPopup` | `boolean` | `false` | Default: off. When off, ordinary board task-card clicks keep the existing fallback behavior: the full-panel task detail, or the right dock when `openTasksInRightSidebar` is on and the dock is active; List row/card opens keep the desktop split-detail pane or the mobile/tablet docked detail; ordinary right-dock Tasks-list clicks open embedded dock detail with the normal back-to-list controls. When on, ordinary board task-card clicks, List row/card opens, and right-dock Tasks-list clicks open the task in the existing task popup/FloatingWindow surface on desktop, tablet, and mobile so the board, List view, or dock list remains visible; this popup route takes precedence over right-dock routing for those ordinary clicks. Desktop/tablet task popups restore the last saved popup size and position across task IDs and use the board/task-detail layer rather than the global utility layer, while their Activity dropdown stays above and attached during popup drag/resize; mobile task popups remain full-screen sheets. Deep `changes`/`retries`/`workflow` opens, context-menu/refine/detail links, graph/plugin opens, nested task-detail opens, and explicit pop-out actions keep their existing behavior. | +| `openMobileTasksInPopup` | `boolean` | `false` | Default: off. When off, ordinary board task-card clicks keep the existing fallback behavior: the full-panel task detail, or the right dock when `openTasksInRightSidebar` is on and the dock is active; List row/card opens keep the desktop split-detail pane or the mobile/tablet docked detail; ordinary right-dock Tasks-list clicks open embedded dock detail with the normal back-to-list controls. When on, ordinary board task-card clicks, List row/card opens, and right-dock Tasks-list clicks open the task in the existing task popup/FloatingWindow surface on desktop, tablet, and mobile so the board, List view, or dock list remains visible; this popup route takes precedence over right-dock routing for those ordinary clicks. Desktop/tablet task popups restore the last saved popup size and position across task IDs and use the board/task-detail layer rather than the global utility layer, while their Activity dropdown stays above and attached during popup drag/resize; mobile task popups remain full-screen sheets. Board task-card deep `changes`/`retries`/`workflow` chips also open the popup with their requested tab; context-menu/refine/detail links, graph/plugin opens, nested task-detail opens, and explicit pop-out actions keep their existing behavior. | | `taskPopupsBoardListOnly` | `boolean` | `false` | Project-scoped Appearance setting. Default: off, so open task popups remain visible over every main-content view. When on, each open task-detail popup is attached to the Board or List view where it was opened: switching to Command Center, Agents, Settings, another task view, or the other Board/List view hides it without closing or clearing popup state; returning to the originating Board/List view re-renders the same popup with its shared persisted size/position. | | `showCostBadgeOnCards` | `boolean` | `false` | Default: off. When enabled from Settings → Appearance, board cards with positive recorded token usage show a read-time derived model-cost badge beside the execution-time badge. Unpriced models display `—`, and tasks with no token usage render no badge shell. | | `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. | diff --git a/packages/dashboard/app/App.tsx b/packages/dashboard/app/App.tsx index c80f92d8ad..ada3e300ea 100644 --- a/packages/dashboard/app/App.tsx +++ b/packages/dashboard/app/App.tsx @@ -206,7 +206,7 @@ export function getBoardTaskOpenRoute(options: { rightDockActive: boolean; initialTab?: DetailTaskTab; }): BoardTaskOpenRoute { - if (!options.initialTab && options.openMobileTasksInPopup) { + if (options.openMobileTasksInPopup) { return "popup"; } if (shouldOpenBoardTaskInDock(options.openTasksInRightSidebar, options.rightDockActive, options.initialTab)) { @@ -588,7 +588,7 @@ function AppInner() { closePoppedOutTask(taskId, originTaskView); }, [closePoppedOutTask, removeNav]); - const popOutTaskDetailForCurrentView = useCallback((task: Task | TaskDetail) => { + const popOutTaskDetailForCurrentView = useCallback((task: Task | TaskDetail, initialTab?: DetailTaskTab) => { const identityKey = taskPopupIdentityKey(task.id, taskView); const alreadyOpen = poppedOutTaskEntries.some((entry) => entry.task.id === task.id && entry.originTaskView === taskView); if (isMobile && !alreadyOpen) { @@ -599,7 +599,7 @@ function AppInner() { popupNavCloseRef.current.set(identityKey, closeFromHistory); pushNav({ type: "modal", close: closeFromHistory }); } - popOutTaskDetail(task, taskView); + popOutTaskDetail(task, taskView, initialTab); }, [isMobile, poppedOutTaskEntries, popOutTaskDetail, pushNav, closePoppedOutTask, taskView]); const boardSourceTasks = isRemote && remoteData.tasks.length > 0 ? remoteData.tasks : tasks; @@ -1134,14 +1134,22 @@ function AppInner() { handleChangeTaskView("board"); }, [handleChangeTaskView, removeNav, requestRestore]); + /* + FNXC:TaskPopupDeepTabs 2026-07-21-00:00: + FN-8478 makes every board TaskCard deep-tab action, including files changed, honor Open tasks as popups. Route once to the popup with its requested tab so a click never opens both a FloatingWindow and a main-panel/modal detail surface. + */ const handleOpenDetailWithTab = useCallback((task: Task | TaskDetail, initialTab: "changes" | "retries" | "workflow") => { + if (openMobileTasksInPopup) { + popOutTaskDetailForCurrentView(task, initialTab); + return; + } if (initialTab === "changes") { openTaskDetailInMainPanel(task, "changes"); return; } modalManager.openDetailTask(task, initialTab); pushNav({ type: "modal", close: modalManager.closeDetailTask }); - }, [modalManager, openTaskDetailInMainPanel, pushNav]); + }, [modalManager, openMobileTasksInPopup, openTaskDetailInMainPanel, popOutTaskDetailForCurrentView, pushNav]); /* FNXC:Settings 2026-06-22-00:00: @@ -1482,8 +1490,8 @@ function AppInner() { 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. - FNXC:MobileTaskPopups 2026-07-01-12:00: - Board-card clicks on every viewport may opt into the existing task pop-out path, but only for ordinary task opens with no deep initial tab. The route is intentionally ordered as all-viewport popup, then tablet/desktop right dock, then main-panel fallback so the popup setting keeps the board visible when requested while deep-tab opens and non-board handlers keep their existing behavior. + FNXC:MobileTaskPopups 2026-07-21-00:00: + FN-8478 makes board TaskCard deep-tab opens use the all-viewport popup route when enabled, preserving the requested tab. Popup routing remains first so neither dock nor main-panel detail can double-open behind the FloatingWindow. */ const openBoardTaskDetail = useCallback((task: Task | TaskDetail, initialTab?: DetailTaskTab) => { const route = getBoardTaskOpenRoute({ @@ -1495,7 +1503,7 @@ function AppInner() { }); if (route === "popup") { - popOutTaskDetailForCurrentView(task); + popOutTaskDetailForCurrentView(task, initialTab); return; } @@ -2023,7 +2031,7 @@ function AppInner() { FNXC:TaskPopupViewGating 2026-07-15-15:20: Rendering uses only the active view's scoped entries; state keeps hidden snapshots so returning remounts them with shared geometry. Each FloatingWindow key includes its origin so identical task ids never collide across views. */} - {visiblePoppedOutTaskEntries.map(({ task: snapshot, originTaskView }) => { + {visiblePoppedOutTaskEntries.map(({ task: snapshot, originTaskView, initialTab }) => { const liveTask = tasks.find((candidate) => candidate.id === snapshot.id) ?? snapshot; const popupKey = taskPopupIdentityKey(snapshot.id, originTaskView); const close = () => closePoppedOutTaskWithNav(snapshot.id, originTaskView); @@ -2043,6 +2051,7 @@ function AppInner() { > { })).toBe("popup"); }); - it("keeps deep-tab opens off the all-viewport popup path", () => { + it("routes every board-card deep tab to the popup when enabled", () => { for (const initialTab of ["changes", "retries", "workflow"] as const) { - expect(getBoardTaskOpenRoute({ - isMobile: true, - openMobileTasksInPopup: true, - openTasksInRightSidebar: true, - rightDockActive: true, - initialTab, - })).toBe("main-panel"); - - expect(getBoardTaskOpenRoute({ - isMobile: false, - openMobileTasksInPopup: true, - openTasksInRightSidebar: true, - rightDockActive: true, - initialTab, - })).toBe("main-panel"); + for (const isMobile of [true, false]) { + expect(getBoardTaskOpenRoute({ + isMobile, + openMobileTasksInPopup: true, + openTasksInRightSidebar: true, + rightDockActive: true, + initialTab, + })).toBe("popup"); + } } }); + + it("keeps a popup-disabled changes deep link in the main panel", () => { + expect(getBoardTaskOpenRoute({ + isMobile: false, + openMobileTasksInPopup: false, + openTasksInRightSidebar: true, + rightDockActive: true, + initialTab: "changes", + })).toBe("main-panel"); + }); }); diff --git a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx index b02cd655c7..e98bd10358 100644 --- a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx +++ b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx @@ -196,7 +196,7 @@ vi.mock("../../components/TaskDetailModal", () => ({ onBackToBoard?: () => void; initialTab?: string; }) => ( -
+
{onBackToBoard && (