FN-6964: Close task details before browser Back navigation
Dashboard Back navigation now dismisses task detail surfaces before leaving the current context. - Add full-panel task-detail history entries that restore board/list state or the previous nested detail. - Route modal task-detail Back handling through the same close path as explicit dismissal so deep-link cleanup runs. - Cover board-opened, nested, and modal popstate flows and document the Back behavior. - Add a patch changeset for the published Fusion dashboard behavior. Files changed: .changeset/fn-6964-dashboard-back-navigation.md | 7 ++ docs/dashboard-guide.md | 3 +- packages/dashboard/app/App.tsx | 50 ++++++++++-- packages/dashboard/app/components/AppModals.tsx | 31 ++++++-- .../app/components/__tests__/App.test.tsx | 90 ++++++++++++++++++++++ .../app/components/__tests__/AppModals.test.tsx | 5 +- .../app/components/dashboard/MainContent.tsx | 6 +- .../dashboard/app/components/dashboard/types.ts | 1 - 8 files changed, 173 insertions(+), 20 deletions(-) Fusion-Task-Id: FN-6964 Fusion-Task-Lineage: 2308b603-a6ac-4978-9a6a-a6028b9bc38c
This commit is contained in:
7
.changeset/fn-6964-dashboard-back-navigation.md
Normal file
7
.changeset/fn-6964-dashboard-back-navigation.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Make browser and Android Back close dashboard task detail before leaving the current view.
|
||||||
|
category: fix
|
||||||
|
dev: Updates dashboard task-detail history entries for full-panel and modal detail flows.
|
||||||
@@ -15,7 +15,8 @@ The installed mobile/PWA home-screen icons are generated from `packages/dashboar
|
|||||||
## Browser Navigation
|
## Browser Navigation
|
||||||
|
|
||||||
The dashboard now handles browser back navigation consistently on desktop and mobile.
|
The dashboard now handles browser back navigation consistently on desktop and mobile.
|
||||||
Using Back will first dismiss open modals and then step back through in-app view changes (for example, task detail → board) before leaving the app.
|
Using Back will first dismiss open modals and then step back through in-app view changes before leaving the app.
|
||||||
|
When task detail is open from a board card, mobile list row, right-dock/activity/onboarding link, deep link, or another task detail link, one browser/Android Back action closes the current detail first and restores the prior dashboard context (for example, nested task detail → previous task detail, or task detail → board).
|
||||||
This behavior used to be mobile-only, and now applies across all viewports.
|
This behavior used to be mobile-only, and now applies across all viewports.
|
||||||
Task Detail modal opens from onboarding, activity log, and task-to-task navigation now all register navigation history entries, so Android back swipe/button dismisses them consistently.
|
Task Detail modal opens from onboarding, activity log, and task-to-task navigation now all register navigation history entries, so Android back swipe/button dismisses them consistently.
|
||||||
|
|
||||||
|
|||||||
@@ -377,6 +377,7 @@ function AppInner() {
|
|||||||
*/
|
*/
|
||||||
const { task: mainPanelDetailTask, initialTab: mainPanelDetailInitialTab, setTask: setMainPanelDetailTask, setInitialTab: setMainPanelDetailInitialTab } = useMainPanelTaskDetail();
|
const { task: mainPanelDetailTask, initialTab: mainPanelDetailInitialTab, setTask: setMainPanelDetailTask, setInitialTab: setMainPanelDetailInitialTab } = useMainPanelTaskDetail();
|
||||||
const { capture: captureCurrentBoardScrollSnapshot, requestRestore } = useBoardScrollRestore(taskView);
|
const { capture: captureCurrentBoardScrollSnapshot, requestRestore } = useBoardScrollRestore(taskView);
|
||||||
|
const mainPanelDetailNavRevertRef = useRef<(() => void) | null>(null);
|
||||||
/*
|
/*
|
||||||
FNXC:FloatingWindow 2026-06-22-20:45:
|
FNXC:FloatingWindow 2026-06-22-20:45:
|
||||||
Open popped-out task-detail windows. Each entry is a task snapshot rendered inside its own movable, resizable, non-blocking FloatingWindow. Several can be open at once and coexist with the right-dock pop-out and terminal (all click-through overlays). Snapshots survive a tasks revalidation; rendering prefers the live row by id and falls back to the snapshot. Pop-out dedupes by task id — re-popping an already-open task is a no-op (its window stays; focus-to-front in FloatingWindow handles re-raising on click).
|
Open popped-out task-detail windows. Each entry is a task snapshot rendered inside its own movable, resizable, non-blocking FloatingWindow. Several can be open at once and coexist with the right-dock pop-out and terminal (all click-through overlays). Snapshots survive a tasks revalidation; rendering prefers the live row by id and falls back to the snapshot. Pop-out dedupes by task id — re-popping an already-open task is a no-op (its window stays; focus-to-front in FloatingWindow handles re-raising on click).
|
||||||
@@ -806,21 +807,59 @@ function AppInner() {
|
|||||||
/*
|
/*
|
||||||
FNXC:Navigation 2026-06-22-00:00:
|
FNXC:Navigation 2026-06-22-00:00:
|
||||||
Board card clicks open task detail as a full main-content view that replaces the board (design: "Full main panel (replaces board)"), instead of the TaskDetailModal overlay. We store a snapshot of the clicked task and navigate to the registered `task-detail` view; renderMainContent renders TaskDetailContent embedded with a Back-to-board button. Only the Board uses this handler — list-view split-detail, right-dock cards, and other openDetail callers keep the modal behavior.
|
Board card clicks open task detail as a full main-content view that replaces the board (design: "Full main panel (replaces board)"), instead of the TaskDetailModal overlay. We store a snapshot of the clicked task and navigate to the registered `task-detail` view; renderMainContent renders TaskDetailContent embedded with a Back-to-board button. Only the Board uses this handler — list-view split-detail, right-dock cards, and other openDetail callers keep the modal behavior.
|
||||||
|
|
||||||
|
FNXC:TaskDetailBack 2026-06-25-00:00:
|
||||||
|
Browser and Android Back must close the currently viewed full-panel task detail before leaving the prior dashboard view. The history entry owns an idempotent revert callback that clears stale snapshot state for board/list origins or restores the previous task snapshot for nested task-detail links, and explicit Back-to-board consumes that same entry without pushing a contradictory view entry during popstate.
|
||||||
*/
|
*/
|
||||||
const openTaskDetailInMainPanel = useCallback((task: Task | TaskDetail, initialTab: DetailTaskTab = "chat") => {
|
const openTaskDetailInMainPanel = useCallback((task: Task | TaskDetail, initialTab: DetailTaskTab = "chat") => {
|
||||||
captureCurrentBoardScrollSnapshot();
|
const previousView = taskView;
|
||||||
|
const previousDetailTask = mainPanelDetailTask;
|
||||||
|
const previousDetailTab = mainPanelDetailInitialTab;
|
||||||
|
|
||||||
|
if (previousView === "task-detail" && previousDetailTask?.id === task.id && previousDetailTab === initialTab) {
|
||||||
|
setMainPanelDetailTask(task);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousView !== "task-detail") {
|
||||||
|
captureCurrentBoardScrollSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
const revertMainPanelDetail = () => {
|
||||||
|
if (previousView === "task-detail" && previousDetailTask) {
|
||||||
|
setMainPanelDetailTask(previousDetailTask);
|
||||||
|
setMainPanelDetailInitialTab(previousDetailTab);
|
||||||
|
handleChangeTaskView("task-detail");
|
||||||
|
mainPanelDetailNavRevertRef.current = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestRestore();
|
||||||
|
setMainPanelDetailTask(null);
|
||||||
|
setMainPanelDetailInitialTab("chat");
|
||||||
|
handleChangeTaskView(previousView);
|
||||||
|
mainPanelDetailNavRevertRef.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
setMainPanelDetailTask(task);
|
setMainPanelDetailTask(task);
|
||||||
setMainPanelDetailInitialTab(initialTab);
|
setMainPanelDetailInitialTab(initialTab);
|
||||||
handleTaskViewChange("task-detail");
|
handleChangeTaskView("task-detail");
|
||||||
}, [captureCurrentBoardScrollSnapshot, handleTaskViewChange]);
|
mainPanelDetailNavRevertRef.current = revertMainPanelDetail;
|
||||||
|
pushNav({ type: "view", revert: revertMainPanelDetail });
|
||||||
|
}, [captureCurrentBoardScrollSnapshot, handleChangeTaskView, mainPanelDetailInitialTab, mainPanelDetailTask, pushNav, requestRestore, taskView]);
|
||||||
|
|
||||||
// FNXC:Navigation 2026-06-22-00:00: Leaving task-detail clears the snapshot so a stale task never lingers if the view is reopened empty.
|
// FNXC:Navigation 2026-06-22-00:00: Leaving task-detail clears the snapshot so a stale task never lingers if the view is reopened empty.
|
||||||
const closeTaskDetailMainPanel = useCallback(() => {
|
const closeTaskDetailMainPanel = useCallback(() => {
|
||||||
|
const revert = mainPanelDetailNavRevertRef.current;
|
||||||
|
if (revert) {
|
||||||
|
removeNav(revert);
|
||||||
|
mainPanelDetailNavRevertRef.current = null;
|
||||||
|
}
|
||||||
requestRestore();
|
requestRestore();
|
||||||
setMainPanelDetailTask(null);
|
setMainPanelDetailTask(null);
|
||||||
setMainPanelDetailInitialTab("chat");
|
setMainPanelDetailInitialTab("chat");
|
||||||
handleTaskViewChange("board");
|
handleChangeTaskView("board");
|
||||||
}, [handleTaskViewChange]);
|
}, [handleChangeTaskView, removeNav, requestRestore]);
|
||||||
|
|
||||||
const handleOpenDetailWithTab = useCallback((task: Task | TaskDetail, initialTab: "changes" | "retries" | "workflow") => {
|
const handleOpenDetailWithTab = useCallback((task: Task | TaskDetail, initialTab: "changes" | "retries" | "workflow") => {
|
||||||
if (initialTab === "changes") {
|
if (initialTab === "changes") {
|
||||||
@@ -1205,7 +1244,6 @@ function AppInner() {
|
|||||||
mainPanelDetailInitialTab,
|
mainPanelDetailInitialTab,
|
||||||
closeTaskDetailMainPanel,
|
closeTaskDetailMainPanel,
|
||||||
setMainPanelDetailTask,
|
setMainPanelDetailTask,
|
||||||
setMainPanelDetailInitialTab,
|
|
||||||
mergeTask,
|
mergeTask,
|
||||||
resetTask,
|
resetTask,
|
||||||
duplicateTask,
|
duplicateTask,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState, lazy, Suspense } from "react";
|
import { useCallback, useEffect, useRef, useState, lazy, Suspense } from "react";
|
||||||
import type { ProjectInfo } from "../api";
|
import type { ProjectInfo } from "../api";
|
||||||
import type { ColorTheme, Column, MergeResult, Task, TaskCreateInput, ThemeMode, GithubIssueAction } from "@fusion/core";
|
import type { ColorTheme, Column, MergeResult, Task, TaskCreateInput, ThemeMode, GithubIssueAction } from "@fusion/core";
|
||||||
import type { UseProjectActionsResult } from "../hooks/useProjectActions";
|
import type { UseProjectActionsResult } from "../hooks/useProjectActions";
|
||||||
@@ -117,6 +117,7 @@ export function AppModals({
|
|||||||
}: AppModalsProps) {
|
}: AppModalsProps) {
|
||||||
const { pushNav, removeNav } = useNavigationHistoryContext();
|
const { pushNav, removeNav } = useNavigationHistoryContext();
|
||||||
const [firstCreatedTask, setFirstCreatedTask] = useState<Task | null>(null);
|
const [firstCreatedTask, setFirstCreatedTask] = useState<Task | null>(null);
|
||||||
|
const detailNavCloseRef = useRef<(() => void) | null>(null);
|
||||||
const detailTask = modalManager.detailTask
|
const detailTask = modalManager.detailTask
|
||||||
? (() => {
|
? (() => {
|
||||||
const liveTask = tasks.find((task) => task.id === modalManager.detailTask?.id);
|
const liveTask = tasks.find((task) => task.id === modalManager.detailTask?.id);
|
||||||
@@ -140,10 +141,20 @@ export function AppModals({
|
|||||||
// Use the override handler if provided, otherwise fall back to modalManager.closeSettings
|
// Use the override handler if provided, otherwise fall back to modalManager.closeSettings
|
||||||
const handleSettingsClose = onSettingsClose ?? modalManager.closeSettings;
|
const handleSettingsClose = onSettingsClose ?? modalManager.closeSettings;
|
||||||
|
|
||||||
const closeDetailWithNav = useCallback(() => {
|
/*
|
||||||
removeNav(modalManager.closeDetailTask);
|
FNXC:TaskDetailBack 2026-06-25-00:00:
|
||||||
|
Modal task detail uses the same idempotent close path for explicit Close and browser/Android Back so deep-link URL cleanup is not skipped during popstate. Each open records the pushed history callback because nested task-detail links can create multiple detail entries with otherwise identical close behavior.
|
||||||
|
*/
|
||||||
|
const closeDetailFromHistory = useCallback(() => {
|
||||||
|
modalManager.closeDetailTask();
|
||||||
deepLink.handleDetailClose();
|
deepLink.handleDetailClose();
|
||||||
}, [deepLink, modalManager.closeDetailTask, removeNav]);
|
detailNavCloseRef.current = null;
|
||||||
|
}, [deepLink, modalManager]);
|
||||||
|
|
||||||
|
const closeDetailWithNav = useCallback(() => {
|
||||||
|
removeNav(detailNavCloseRef.current ?? closeDetailFromHistory);
|
||||||
|
closeDetailFromHistory();
|
||||||
|
}, [closeDetailFromHistory, removeNav]);
|
||||||
|
|
||||||
const closeGroupWithNav = useCallback(() => {
|
const closeGroupWithNav = useCallback(() => {
|
||||||
removeNav(modalManager.closeGroupModal);
|
removeNav(modalManager.closeGroupModal);
|
||||||
@@ -234,9 +245,17 @@ export function AppModals({
|
|||||||
tab?: Parameters<typeof modalManager.openDetailTask>[1],
|
tab?: Parameters<typeof modalManager.openDetailTask>[1],
|
||||||
) => {
|
) => {
|
||||||
modalManager.openDetailTask(task, tab);
|
modalManager.openDetailTask(task, tab);
|
||||||
pushNav({ type: "modal", close: modalManager.closeDetailTask });
|
const closeFromHistory = () => {
|
||||||
|
modalManager.closeDetailTask();
|
||||||
|
deepLink.handleDetailClose();
|
||||||
|
if (detailNavCloseRef.current === closeFromHistory) {
|
||||||
|
detailNavCloseRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
detailNavCloseRef.current = closeFromHistory;
|
||||||
|
pushNav({ type: "modal", close: closeFromHistory });
|
||||||
},
|
},
|
||||||
[modalManager, pushNav],
|
[deepLink, modalManager, pushNav],
|
||||||
);
|
);
|
||||||
|
|
||||||
const openGroupModalWithNav = useCallback((groupId: string) => {
|
const openGroupModalWithNav = useCallback((groupId: string) => {
|
||||||
|
|||||||
@@ -292,6 +292,13 @@ vi.mock("../../components/TaskDetailModal", () => ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
TaskDetailContent: ({ task, onBackToBoard, onOpenDetail }: { task: { id: string; title?: string }; onBackToBoard?: () => void; onOpenDetail?: (task: { id: string; title?: string }) => void }) => (
|
||||||
|
<section data-testid="main-panel-task-detail">
|
||||||
|
<button type="button" onClick={onBackToBoard}>Back to board</button>
|
||||||
|
<h2>{task.title ?? task.id}</h2>
|
||||||
|
<button type="button" onClick={() => onOpenDetail?.({ id: "FN-6965", title: "Nested task" })}>Open nested task</button>
|
||||||
|
</section>
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("../../components/GitHubImportModal", () => ({
|
vi.mock("../../components/GitHubImportModal", () => ({
|
||||||
@@ -1705,6 +1712,89 @@ describe("App deep link handling", () => {
|
|||||||
expect(window.history.replaceState).not.toHaveBeenCalled();
|
expect(window.history.replaceState).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("closes board-opened main-panel task detail on one browser back", async () => {
|
||||||
|
const boardTask = { id: "FN-6964", title: "Back nav task", description: "x", status: "todo", column: "todo", dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" };
|
||||||
|
mockUseTasks.mockImplementation(() => ({
|
||||||
|
tasks: [boardTask],
|
||||||
|
isStale: false,
|
||||||
|
createTask: mockCreateTask,
|
||||||
|
moveTask: vi.fn(),
|
||||||
|
pauseTask: vi.fn(),
|
||||||
|
unpauseTask: vi.fn(),
|
||||||
|
deleteTask: vi.fn(),
|
||||||
|
mergeTask: vi.fn(),
|
||||||
|
retryTask: vi.fn(),
|
||||||
|
resetTask: vi.fn(),
|
||||||
|
updateTask: vi.fn(),
|
||||||
|
duplicateTask: vi.fn(),
|
||||||
|
archiveTask: vi.fn(),
|
||||||
|
unarchiveTask: vi.fn(),
|
||||||
|
archiveAllDone: vi.fn(),
|
||||||
|
loadArchivedTasks: vi.fn(),
|
||||||
|
refreshTasks: vi.fn(),
|
||||||
|
ingestCreatedTasks: vi.fn(),
|
||||||
|
lastFetchTimeMs: Date.now(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
render(<App />);
|
||||||
|
await waitForAppShell();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Back nav task"));
|
||||||
|
expect(await screen.findByTestId("main-panel-task-detail")).toBeTruthy();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
window.dispatchEvent(new PopStateEvent("popstate", { state: { navIndex: 0 } }));
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId("main-panel-task-detail")).toBeNull();
|
||||||
|
expect(screen.getByText("Back nav task")).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restores the previous main-panel task detail on nested detail browser back", async () => {
|
||||||
|
const boardTask = { id: "FN-6964", title: "Back nav task", description: "x", status: "todo", column: "todo", dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" };
|
||||||
|
mockUseTasks.mockImplementation(() => ({
|
||||||
|
tasks: [boardTask],
|
||||||
|
isStale: false,
|
||||||
|
createTask: mockCreateTask,
|
||||||
|
moveTask: vi.fn(),
|
||||||
|
pauseTask: vi.fn(),
|
||||||
|
unpauseTask: vi.fn(),
|
||||||
|
deleteTask: vi.fn(),
|
||||||
|
mergeTask: vi.fn(),
|
||||||
|
retryTask: vi.fn(),
|
||||||
|
resetTask: vi.fn(),
|
||||||
|
updateTask: vi.fn(),
|
||||||
|
duplicateTask: vi.fn(),
|
||||||
|
archiveTask: vi.fn(),
|
||||||
|
unarchiveTask: vi.fn(),
|
||||||
|
archiveAllDone: vi.fn(),
|
||||||
|
loadArchivedTasks: vi.fn(),
|
||||||
|
refreshTasks: vi.fn(),
|
||||||
|
ingestCreatedTasks: vi.fn(),
|
||||||
|
lastFetchTimeMs: Date.now(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
render(<App />);
|
||||||
|
await waitForAppShell();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Back nav task"));
|
||||||
|
expect(await screen.findByText("Open nested task")).toBeTruthy();
|
||||||
|
fireEvent.click(screen.getByText("Open nested task"));
|
||||||
|
expect(await screen.findByText("Nested task")).toBeTruthy();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
window.dispatchEvent(new PopStateEvent("popstate", { state: { navIndex: 1 } }));
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText("Nested task")).toBeNull();
|
||||||
|
expect(screen.getByTestId("main-panel-task-detail")).toBeTruthy();
|
||||||
|
expect(screen.getByText("Back nav task")).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("does not reopen deep-linked task after dismissal and re-render", async () => {
|
it("does not reopen deep-linked task after dismissal and re-render", async () => {
|
||||||
Object.defineProperty(window, "location", {
|
Object.defineProperty(window, "location", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|||||||
@@ -542,12 +542,14 @@ describe("AppModals", () => {
|
|||||||
settings: mockSettings,
|
settings: mockSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
it("pushes history for activity-log open and closes on popstate", async () => {
|
it("pushes history for activity-log open and closes with deep-link cleanup on popstate", async () => {
|
||||||
const pushStateSpy = vi.spyOn(window.history, "pushState");
|
const pushStateSpy = vi.spyOn(window.history, "pushState");
|
||||||
const closeDetailTask = vi.fn();
|
const closeDetailTask = vi.fn();
|
||||||
|
const handleDetailClose = vi.fn();
|
||||||
render(
|
render(
|
||||||
<AppModals
|
<AppModals
|
||||||
{...commonProps}
|
{...commonProps}
|
||||||
|
deepLink={{ handleDetailClose }}
|
||||||
modalManager={{ ...mockModalManager, activityLogOpen: true, closeDetailTask }}
|
modalManager={{ ...mockModalManager, activityLogOpen: true, closeDetailTask }}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
@@ -557,6 +559,7 @@ describe("AppModals", () => {
|
|||||||
|
|
||||||
window.dispatchEvent(new PopStateEvent("popstate", { state: { navIndex: 0 } }));
|
window.dispatchEvent(new PopStateEvent("popstate", { state: { navIndex: 0 } }));
|
||||||
await waitFor(() => expect(closeDetailTask).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(closeDetailTask).toHaveBeenCalledTimes(1));
|
||||||
|
expect(handleDetailClose).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("pushes history for onboarding view-task open and closes on popstate", async () => {
|
it("pushes history for onboarding view-task open and closes on popstate", async () => {
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ export function MainContent({
|
|||||||
mainPanelDetailInitialTab,
|
mainPanelDetailInitialTab,
|
||||||
closeTaskDetailMainPanel,
|
closeTaskDetailMainPanel,
|
||||||
setMainPanelDetailTask,
|
setMainPanelDetailTask,
|
||||||
setMainPanelDetailInitialTab,
|
|
||||||
mergeTask,
|
mergeTask,
|
||||||
resetTask,
|
resetTask,
|
||||||
duplicateTask,
|
duplicateTask,
|
||||||
@@ -722,10 +721,7 @@ export function MainContent({
|
|||||||
onBackToBoard={closeTaskDetailMainPanel}
|
onBackToBoard={closeTaskDetailMainPanel}
|
||||||
/* FNXC:FloatingWindow 2026-06-22-21:10: Popping out from the board's full-panel detail also returns the main panel to the board, so the board (not the emptied detail) sits behind the floating window. */
|
/* FNXC:FloatingWindow 2026-06-22-21:10: Popping out from the board's full-panel detail also returns the main panel to the board, so the board (not the emptied detail) sits behind the floating window. */
|
||||||
onPopOut={(task) => { popOutTaskDetail(task); closeTaskDetailMainPanel(); }}
|
onPopOut={(task) => { popOutTaskDetail(task); closeTaskDetailMainPanel(); }}
|
||||||
onOpenDetail={(value) => {
|
onOpenDetail={(value) => openTaskDetailInMainPanel(value, "chat")}
|
||||||
setMainPanelDetailTask(value);
|
|
||||||
setMainPanelDetailInitialTab("chat");
|
|
||||||
}}
|
|
||||||
onMoveTask={moveTask}
|
onMoveTask={moveTask}
|
||||||
onDeleteTask={deleteTask}
|
onDeleteTask={deleteTask}
|
||||||
onMergeTask={mergeTask}
|
onMergeTask={mergeTask}
|
||||||
|
|||||||
@@ -198,7 +198,6 @@ export interface MainContentProps {
|
|||||||
mainPanelDetailInitialTab: DetailTaskTab;
|
mainPanelDetailInitialTab: DetailTaskTab;
|
||||||
closeTaskDetailMainPanel: () => void;
|
closeTaskDetailMainPanel: () => void;
|
||||||
setMainPanelDetailTask: Dispatch<SetStateAction<Task | TaskDetail | null>>;
|
setMainPanelDetailTask: Dispatch<SetStateAction<Task | TaskDetail | null>>;
|
||||||
setMainPanelDetailInitialTab: (tab: DetailTaskTab) => void;
|
|
||||||
mergeTask: (id: string) => Promise<MergeResult>;
|
mergeTask: (id: string) => Promise<MergeResult>;
|
||||||
resetTask: (id: string) => Promise<Task>;
|
resetTask: (id: string) => Promise<Task>;
|
||||||
duplicateTask: (id: string) => Promise<Task>;
|
duplicateTask: (id: string) => Promise<Task>;
|
||||||
|
|||||||
Reference in New Issue
Block a user