Files
fusion/packages/dashboard/app/hooks/usePoppedOutTasks.ts
gsxdsm f9f2832936 fix(dashboard): dismiss popped-out/main-panel/dock task detail and Quick Chat on project switch
Task detail does not always render through modalManager.detailTask: with
Open tasks as popups it opens as FloatingWindows, with the right-sidebar
setting it opens in the right dock, and changes-tab opens use the
main-panel task-detail view. None of those were closed by
closeProjectScopedModals, so switching projects left the previous
project's task windows over the new project — and the Quick Chat
floating window (App state) stayed open the same way.

useProjectActions now receives a composite App-level reset (via a stable
ref, since the dock and main-panel pieces are constructed later in App)
that closes project-scoped modals, all popped-out task windows (new
usePoppedOutTasks.closeAll), main-panel task detail when present, the
right-dock task, and Quick Chat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 22:53:04 -07:00

66 lines
3.0 KiB
TypeScript

/*
FNXC:FloatingWindow 2026-07-15-14:55:
Popped-out task-detail windows are movable, resizable, non-blocking FloatingWindows. Each entry is a task snapshot; several can be open at once. Reopening the same task from the same origin refreshes its snapshot without collapsing an independently opened popup in another view. Extracted from AppInner.
*/
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, initialTab?: DetailTaskTab) => void;
close: (taskId: string, originTaskView?: TaskView) => void;
/*
FNXC:ProjectSwitchModalReset 2026-07-23-00:00:
Popped-out task windows are task-detail surfaces for the active project. A project swap
must dismiss them all — they bypass modalManager.detailTask, so closeProjectScopedModals
alone left the previous project's task popups floating over the new project.
*/
closeAll: () => void;
}
export function usePoppedOutTasks(): UsePoppedOutTasksResult {
const [entries, setEntries] = useState<PoppedOutTaskEntry[]>([]);
/*
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);
const entry = { task, originTaskView, ...(initialTab ? { initialTab } : {}) };
if (existingIndex === -1) return [...current, entry];
const upgraded = [...current];
upgraded[existingIndex] = entry;
return upgraded;
});
}, []);
const close = useCallback((taskId: string, originTaskView?: TaskView) => {
setEntries((current) => current.filter((entry) => entry.task.id !== taskId || entry.originTaskView !== originTaskView));
}, []);
const closeAll = useCallback(() => {
setEntries([]);
}, []);
/*
FNXC:TaskPopupViewGating 2026-07-15-15:20:
FN-8016 scopes popup identity to task id plus opening view. Every new pop-out has an origin; undefined origins are retained only for legacy snapshots and remain globally visible for compatibility. Closing receives the same identity so a task open on two views stays independent.
*/
const tasks = useMemo(() => entries.map((entry) => entry.task), [entries]);
return { entries, tasks, popOut, close, closeAll };
}