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>
This commit is contained in:
@@ -4,4 +4,4 @@
|
||||
|
||||
summary: Switching projects now fully resets Planning, Chat, Missions, subtask breakdown, GitHub import, and open modals.
|
||||
category: fix
|
||||
dev: New `closeProjectScopedModals()` on the modal manager, invoked by project select/view-all/setup-complete; PlanningModeModal, ChatView, MissionManager, SubtaskBreakdownModal, and GitHubImportModal are keyed by project id so running streams, session lists, and per-project persisted drafts/active sessions no longer leak or mis-file across projects (subtask/mission drafts save on unmount under their own project key).
|
||||
dev: New `closeProjectScopedModals()` on the modal manager plus an App-level composite reset invoked by project select/view-all/setup-complete that also dismisses popped-out task FloatingWindows, main-panel task detail, the right-dock task, and the Quick Chat window; PlanningModeModal, ChatView, MissionManager, SubtaskBreakdownModal, and GitHubImportModal are keyed by project id so running streams, session lists, and per-project persisted drafts/active sessions no longer leak or mis-file across projects (subtask/mission drafts save on unmount under their own project key).
|
||||
|
||||
@@ -568,7 +568,7 @@ function AppInner() {
|
||||
FNXC:FloatingWindow 2026-07-15-15:20:
|
||||
FN-8016 identifies a popped-out task detail by task id plus origin view. The same task can therefore coexist in separate view-scoped FloatingWindows while re-opening it on one view refreshes only that entry.
|
||||
*/
|
||||
const { entries: poppedOutTaskEntries, popOut: popOutTaskDetail, close: closePoppedOutTask } = usePoppedOutTasks();
|
||||
const { entries: poppedOutTaskEntries, popOut: popOutTaskDetail, close: closePoppedOutTask, closeAll: closeAllPoppedOutTasks } = usePoppedOutTasks();
|
||||
const popupNavCloseRef = useRef(new Map<string, () => void>());
|
||||
|
||||
/*
|
||||
@@ -959,6 +959,18 @@ function AppInner() {
|
||||
openSettings: modalManager.openSettings,
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:ProjectSwitchModalReset 2026-07-23-00:00:
|
||||
Project-scoped task-detail surfaces are not all owned by modalManager: depending on
|
||||
settings, tasks open as popped-out FloatingWindows, in the main panel (task-detail view),
|
||||
or in the right dock, and Quick Chat is an App-level floating window. A project swap must
|
||||
dismiss ALL of them, so useProjectActions receives this composite via a ref — the pieces
|
||||
it closes (main-panel detail, right dock) are constructed later in this component, and
|
||||
the ref keeps the callback stable while always invoking the latest wiring.
|
||||
*/
|
||||
const closeProjectScopedUiRef = useRef<() => void>(() => {});
|
||||
const closeProjectScopedUi = useCallback(() => closeProjectScopedUiRef.current(), []);
|
||||
|
||||
const {
|
||||
handleSelectProject,
|
||||
handleViewAllProjects,
|
||||
@@ -985,7 +997,7 @@ function AppInner() {
|
||||
openSetupWizard: modalManager.openSetupWizard,
|
||||
closeSetupWizard: modalManager.closeSetupWizard,
|
||||
closeModelOnboarding: modalManager.closeModelOnboarding,
|
||||
closeProjectScopedModals: modalManager.closeProjectScopedModals,
|
||||
closeProjectScopedModals: closeProjectScopedUi,
|
||||
});
|
||||
|
||||
const { handleDetailClose } = useDeepLink({
|
||||
@@ -1522,6 +1534,24 @@ function AppInner() {
|
||||
}
|
||||
}, [openTasksInRightSidebar, rightDock]);
|
||||
|
||||
/*
|
||||
FNXC:ProjectSwitchModalReset 2026-07-23-00:00:
|
||||
The composite project-switch reset. Beyond modalManager's project-scoped modals, dismiss
|
||||
every task-detail surface the open-task routing can target (popped-out FloatingWindows,
|
||||
main-panel task-detail view, right-dock task) and close the Quick Chat floating window —
|
||||
all of these showed the previous project's content over the new project. Assigned each
|
||||
render so the ref-stable callback handed to useProjectActions always sees current state.
|
||||
*/
|
||||
closeProjectScopedUiRef.current = () => {
|
||||
modalManager.closeProjectScopedModals();
|
||||
closeAllPoppedOutTasks();
|
||||
if (mainPanelDetailTask) {
|
||||
closeTaskDetailMainPanel();
|
||||
}
|
||||
rightDock.closeDockTask();
|
||||
setQuickChatOpen(false);
|
||||
};
|
||||
|
||||
const mainContentProps: MainContentProps = {
|
||||
showBackendConnectionErrorPage,
|
||||
projectsError,
|
||||
|
||||
@@ -60,4 +60,21 @@ describe("usePoppedOutTasks", () => {
|
||||
|
||||
expect(result.current.entries).toEqual([{ task: task("1"), originTaskView: "board" }]);
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:ProjectSwitchModalReset 2026-07-23-00:00:
|
||||
A project swap dismisses every popped-out task window regardless of origin view — they
|
||||
are task-detail surfaces for the previous project.
|
||||
*/
|
||||
it("closeAll dismisses every popped-out task across origin views", () => {
|
||||
const { result } = renderHook(() => usePoppedOutTasks());
|
||||
|
||||
act(() => {
|
||||
result.current.popOut(task("1"), "board");
|
||||
result.current.popOut(task("2"), "planning");
|
||||
result.current.closeAll();
|
||||
});
|
||||
|
||||
expect(result.current.entries).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,13 @@ export interface UsePoppedOutTasksResult {
|
||||
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 {
|
||||
@@ -44,11 +51,15 @@ export function usePoppedOutTasks(): UsePoppedOutTasksResult {
|
||||
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 };
|
||||
return { entries, tasks, popOut, close, closeAll };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user