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) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/task-card-popup-deep-tabs.md
Normal file
7
.changeset/task-card-popup-deep-tabs.md
Normal file
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
<!-- FNXC:MobileTaskPopups 2026-07-13-00:00 (FN-7945): Board-card and List row/card task opens have a separate default-off popup setting so operators on desktop, tablet, and mobile can opt into the existing FloatingWindow task popup when they want the board or List view visible; ordinary popup routing takes precedence over the right dock, while deep-tab, context-menu, and non-board/List opens keep their existing paths.
|
||||
<!-- FNXC:MobileTaskPopups 2026-07-21-00:00 (FN-8478): Board-card and List row/card task opens have a separate default-off popup setting so operators on desktop, tablet, and mobile can opt into the existing FloatingWindow task popup when they want the board or List view visible; board-card deep-tab chips now preserve their requested tab in the popup, while context-menu and non-board/List opens keep their existing paths.
|
||||
FNXC:TaskPopupGeometry 2026-07-03-00:00: Desktop/tablet task popups share a persisted geometry key across task IDs so operators can size and place the popup once, then open other tasks without repeating that setup. Mobile remains a full-screen sheet regardless of saved desktop geometry.
|
||||
FNXC:RightDockTaskPopup 2026-07-03-00:00: The same task-popup preference also applies to ordinary clicks in the right-dock Tasks list so that opted-in operators never lose the list to embedded detail by clicking a dock task row.
|
||||
FNXC:TaskPopupLayer 2026-07-17-15:55: Task popups and Quick Chat share the board/task-detail interaction stack, so the most recently clicked or focused overlapping surface appears on top. Terminal, Files, New Task, workflow editor, and other utility windows retain their separate higher utility stacking.
|
||||
FNXC:TaskPopupViewGating 2026-07-15-15:20: FN-8016 makes popup view scoping default-on for every dashboard view. Hidden popups are render-only hidden, not closed, so snapshots and shared geometry survive navigation; same-task popups remain independently addressable by origin view. -->
|
||||
**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.
|
||||
|
||||
|
||||
@@ -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/<task-id>-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. |
|
||||
|
||||
@@ -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() {
|
||||
>
|
||||
<TaskDetailContent
|
||||
task={liveTask}
|
||||
initialTab={initialTab}
|
||||
projectId={currentProject?.id}
|
||||
tasks={tasks}
|
||||
embedded
|
||||
|
||||
@@ -64,23 +64,27 @@ describe("board task detail routing", () => {
|
||||
})).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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -196,7 +196,7 @@ vi.mock("../../components/TaskDetailModal", () => ({
|
||||
onBackToBoard?: () => void;
|
||||
initialTab?: string;
|
||||
}) => (
|
||||
<div data-testid="task-detail-main-panel-content">
|
||||
<div data-testid={onBackToBoard ? "task-detail-main-panel-content" : "task-detail-popup-content"}>
|
||||
{onBackToBoard && (
|
||||
<button type="button" onClick={onBackToBoard}>
|
||||
Back to board
|
||||
@@ -392,6 +392,7 @@ describe("Navigation history integration", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
defaultSettings.openMobileTasksInPopup = false;
|
||||
mockSubscribeSse.mockReset();
|
||||
mockSubscribeSse.mockReturnValue(vi.fn());
|
||||
mockCreateTask.mockReset();
|
||||
@@ -805,6 +806,28 @@ describe("Navigation history integration", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("opens board files-changed in the popup Changes tab when task popups are enabled", async () => {
|
||||
defaultSettings.openMobileTasksInPopup = true;
|
||||
const task = {
|
||||
...makeTask("FN-popup", "Popup Changes Detail"),
|
||||
modifiedFiles: ["packages/dashboard/app/App.tsx"],
|
||||
};
|
||||
mockUseTasks.mockImplementation(() => ({
|
||||
tasks: [task], createTask: mockCreateTask, moveTask: vi.fn(), deleteTask: vi.fn(), mergeTask: vi.fn(),
|
||||
retryTask: vi.fn(), updateTask: vi.fn(), duplicateTask: vi.fn(), archiveTask: vi.fn(),
|
||||
unarchiveTask: vi.fn(), archiveAllDone: vi.fn(), refreshTasks: vi.fn(),
|
||||
}));
|
||||
|
||||
await renderMobileAppAndWait();
|
||||
fireEvent.click(screen.getByTestId("open-task-changes-FN-popup"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("floating-window-task-detail-FN-popup-board")).toBeTruthy();
|
||||
expect(screen.getByTestId("task-detail-popup-content")).toHaveTextContent("tab:changes");
|
||||
});
|
||||
expect(screen.queryByTestId("task-detail-main-panel-content")).toBeNull();
|
||||
});
|
||||
|
||||
// 5. Verify useNavigationHistory is called with enabled=true on mobile
|
||||
it("calls useNavigationHistory with enabled=true on mobile", async () => {
|
||||
mockUseViewportMode.mockReturnValue("mobile");
|
||||
|
||||
@@ -53,12 +53,12 @@ export function AppearanceSection({ form, setForm, themeMode, colorTheme, dashbo
|
||||
value={form.openTasksInRightSidebar === true}
|
||||
onChange={(v) => setForm((f) => ({ ...f, openTasksInRightSidebar: v === true }))}
|
||||
/>
|
||||
{/* FNXC:MobileTaskPopups 2026-07-13-00:00 (FN-7945): Keep the stored openMobileTasksInPopup key for compatibility, but present the setting as all-viewport ordinary task popup routing because desktop operators also need the board, List view, or right-dock Tasks list visible behind task detail. */}
|
||||
{/* FNXC:MobileTaskPopups 2026-07-21-00:00 (FN-8478): Keep the stored openMobileTasksInPopup key for compatibility and explain that board-card deep-tab chips now use the same popup routing, preserving the board behind Changes, Retries, or Workflow detail. */}
|
||||
<SettingsToggleRow
|
||||
descriptor={{
|
||||
key: "openMobileTasksInPopup",
|
||||
label: t("settings.appearance.openMobileTasksInPopup", "Open tasks as popups"),
|
||||
help: t("settings.appearance.openMobileTasksInPopupHelp", "When enabled, ordinary board task-card, List row/card, and right-dock Tasks-list clicks open the existing movable task popup so the board or list remains visible. Deep-tab and other task opens keep their current behavior. Default: disabled."),
|
||||
help: t("settings.appearance.openMobileTasksInPopupHelp", "When enabled, board task-card clicks including Changes, Retries, and Workflow chips, plus ordinary List row/card and right-dock Tasks-list clicks, open the existing movable task popup so the board or list remains visible. Other task opens keep their current behavior. Default: disabled."),
|
||||
scope: "project",
|
||||
}}
|
||||
value={form.openMobileTasksInPopup === true}
|
||||
|
||||
@@ -22,6 +22,19 @@ describe("usePoppedOutTasks", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("refreshes the requested tab when reopening the same task and view", () => {
|
||||
const { result } = renderHook(() => usePoppedOutTasks());
|
||||
|
||||
act(() => {
|
||||
result.current.popOut(task("1"), "board", "changes");
|
||||
result.current.popOut(task("1"), "board", "workflow");
|
||||
});
|
||||
|
||||
expect(result.current.entries).toEqual([
|
||||
{ task: task("1"), originTaskView: "board", initialTab: "workflow" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps the same task independently open on different origin views", () => {
|
||||
const { result } = renderHook(() => usePoppedOutTasks());
|
||||
|
||||
|
||||
@@ -6,29 +6,36 @@ Popped-out task-detail windows are movable, resizable, non-blocking FloatingWind
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import type { Task, TaskDetail } from "@fusion/core";
|
||||
import type { TaskView } from "./useViewState";
|
||||
import type { DetailTaskTab } from "./useModalManager";
|
||||
|
||||
export interface PoppedOutTaskEntry {
|
||||
task: Task | TaskDetail;
|
||||
originTaskView?: TaskView;
|
||||
initialTab?: DetailTaskTab;
|
||||
}
|
||||
|
||||
export interface UsePoppedOutTasksResult {
|
||||
entries: PoppedOutTaskEntry[];
|
||||
tasks: Array<Task | TaskDetail>;
|
||||
popOut: (task: Task | TaskDetail, originTaskView?: TaskView) => void;
|
||||
popOut: (task: Task | TaskDetail, originTaskView?: TaskView, initialTab?: DetailTaskTab) => void;
|
||||
close: (taskId: string, originTaskView?: TaskView) => void;
|
||||
}
|
||||
|
||||
export function usePoppedOutTasks(): UsePoppedOutTasksResult {
|
||||
const [entries, setEntries] = useState<PoppedOutTaskEntry[]>([]);
|
||||
|
||||
const popOut = useCallback((task: Task | TaskDetail, originTaskView?: TaskView) => {
|
||||
/*
|
||||
FNXC:TaskPopupDeepTabs 2026-07-21-00:00:
|
||||
FN-8478 requires board card deep-tab actions to keep the board visible when Open tasks as popups is enabled. Store the requested tab with the popup snapshot so reopening an existing task-and-view pair refreshes both its data and destination.
|
||||
*/
|
||||
const popOut = useCallback((task: Task | TaskDetail, originTaskView?: TaskView, initialTab?: DetailTaskTab) => {
|
||||
setEntries((current) => {
|
||||
const existingIndex = current.findIndex((entry) => entry.task.id === task.id && entry.originTaskView === originTaskView);
|
||||
if (existingIndex === -1) return [...current, { task, originTaskView }];
|
||||
const entry = { task, originTaskView, ...(initialTab ? { initialTab } : {}) };
|
||||
if (existingIndex === -1) return [...current, entry];
|
||||
|
||||
const upgraded = [...current];
|
||||
upgraded[existingIndex] = { task, originTaskView };
|
||||
upgraded[existingIndex] = entry;
|
||||
return upgraded;
|
||||
});
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user