Adds an opt-in project setting so open task-detail popups stay attached to the Board or List view where they were opened, instead of floating over every main-content view. - New project setting taskPopupsBoardListOnly (default: off) in settings-schema.ts and ProjectSettings type, with default preserved via settings-defaults tests. - usePoppedOutTasks now stores each popup's originating TaskView alongside its task snapshot (PoppedOutTaskEntry), keeping legacy tasks output for existing callers. - App.tsx adds isTaskPopupVisibleForView() gating helper and filters popped-out entries to the current view for rendering/keyboard-close handling, while hidden popups remain mounted in hook state (not cleared) so switching back to the originating view restores them with shared persisted geometry. - Settings -> Appearance gets a new "Keep task popups on their Board/List view" checkbox (AppearanceSection.tsx) with i18n strings and updated settings search text in SettingsModal. - Documentation updated in docs/dashboard-guide.md and docs/settings-reference.md to describe the render-only hide/restore behavior. - New/updated tests: App.taskPopupViewGating.test.tsx, usePoppedOutTasks.test.ts, AppearanceSection.test.tsx, settings-default-descriptions.test.tsx, settings-defaults.test.ts. Files changed: docs/dashboard-guide.md | 5 +- docs/settings-reference.md | 1 + .../core/src/__tests__/settings-defaults.test.ts | 13 +++ packages/core/src/settings-schema.ts | 5 + packages/core/src/types.ts | 7 ++ packages/dashboard/app/App.tsx | 49 +++++++-- .../app/__tests__/App.taskPopupViewGating.test.tsx | 113 +++++++++++++++++++++ .../dashboard/app/components/SettingsModal.tsx | 3 +- .../settings/sections/AppearanceSection.tsx | 8 ++ .../sections/__tests__/AppearanceSection.test.tsx | 21 ++++ .../settings-default-descriptions.test.tsx | 1 + .../app/hooks/__tests__/usePoppedOutTasks.test.ts | 14 +++ packages/dashboard/app/hooks/useAppSettings.ts | 4 + packages/dashboard/app/hooks/usePoppedOutTasks.ts | 27 +++-- packages/i18n/locales/en/app.json | 2 + 15 files changed, 255 insertions(+), 18 deletions(-) Fusion-Task-Id: FN-7944 Fusion-Task-Lineage: 4b8ced0e-1853-429f-8482-163821a35ae6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
114 lines
4.4 KiB
TypeScript
114 lines
4.4 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import { describe, expect, it } from "vitest";
|
|
import type { Task } from "@fusion/core";
|
|
import { isTaskPopupVisibleForView, TASK_DETAIL_FLOATING_GEOMETRY_KEY } from "../App";
|
|
import { FloatingWindow } from "../components/FloatingWindow";
|
|
import type { PoppedOutTaskEntry } from "../hooks/usePoppedOutTasks";
|
|
import type { TaskView } from "../hooks/useViewState";
|
|
|
|
function task(id: string): Task {
|
|
return { id, title: id, status: "todo" } as Task;
|
|
}
|
|
|
|
function PopupGateHarness({
|
|
entries,
|
|
taskView,
|
|
taskPopupsBoardListOnly,
|
|
}: {
|
|
entries: PoppedOutTaskEntry[];
|
|
taskView: TaskView;
|
|
taskPopupsBoardListOnly: boolean;
|
|
}) {
|
|
return (
|
|
<>
|
|
{entries
|
|
.filter((entry) => isTaskPopupVisibleForView({ taskPopupsBoardListOnly, taskView, originTaskView: entry.originTaskView }))
|
|
.map(({ task: snapshot }) => (
|
|
<FloatingWindow
|
|
key={snapshot.id}
|
|
windowKey={`task-detail-${snapshot.id}`}
|
|
title={snapshot.id}
|
|
onClose={() => {}}
|
|
hideHeader
|
|
dragHandleSelector=".task-detail-content--embedded > .modal-header"
|
|
className="floating-window--task-detail"
|
|
persistGeometryKey={TASK_DETAIL_FLOATING_GEOMETRY_KEY}
|
|
layer="task-detail"
|
|
>
|
|
<div className="task-detail-content--embedded">
|
|
<div className="modal-header">{snapshot.id}</div>
|
|
<div>{snapshot.title}</div>
|
|
</div>
|
|
</FloatingWindow>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function expectNoTaskPopupShell(taskId: string) {
|
|
expect(screen.queryByTestId(`floating-window-task-detail-${taskId}`)).not.toBeInTheDocument();
|
|
expect(screen.queryByTestId(`floating-window-overlay-task-detail-${taskId}`)).not.toBeInTheDocument();
|
|
}
|
|
|
|
describe("App task popup view gating", () => {
|
|
it("keeps default/off popups visible regardless of the active view", () => {
|
|
render(
|
|
<PopupGateHarness
|
|
taskView="command-center"
|
|
taskPopupsBoardListOnly={false}
|
|
entries={[{ task: task("FN-7944-A"), originTaskView: "board" }]}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByTestId("floating-window-task-detail-FN-7944-A")).toBeInTheDocument();
|
|
expect(screen.getByTestId("floating-window-overlay-task-detail-FN-7944-A")).toBeInTheDocument();
|
|
});
|
|
|
|
it("attaches enabled popups to the Board/List view where they were opened", () => {
|
|
const entries: PoppedOutTaskEntry[] = [
|
|
{ task: task("FN-7944-board"), originTaskView: "board" },
|
|
{ task: task("FN-7944-list"), originTaskView: "list" },
|
|
];
|
|
|
|
const { rerender } = render(<PopupGateHarness taskView="board" taskPopupsBoardListOnly entries={entries} />);
|
|
|
|
expect(screen.getByTestId("floating-window-task-detail-FN-7944-board")).toBeInTheDocument();
|
|
expectNoTaskPopupShell("FN-7944-list");
|
|
|
|
rerender(<PopupGateHarness taskView="list" taskPopupsBoardListOnly entries={entries} />);
|
|
|
|
expectNoTaskPopupShell("FN-7944-board");
|
|
expect(screen.getByTestId("floating-window-task-detail-FN-7944-list")).toBeInTheDocument();
|
|
});
|
|
|
|
it("hides all attached popups on non-task views without leaving shells or overlays, then re-shows the same entry", () => {
|
|
const entries: PoppedOutTaskEntry[] = [
|
|
{ task: task("FN-7944-board"), originTaskView: "board" },
|
|
{ task: task("FN-7944-list"), originTaskView: "list" },
|
|
];
|
|
|
|
const { rerender } = render(<PopupGateHarness taskView="board" taskPopupsBoardListOnly entries={entries} />);
|
|
expect(screen.getByTestId("floating-window-task-detail-FN-7944-board")).toBeInTheDocument();
|
|
|
|
rerender(<PopupGateHarness taskView="agents" taskPopupsBoardListOnly entries={entries} />);
|
|
expectNoTaskPopupShell("FN-7944-board");
|
|
expectNoTaskPopupShell("FN-7944-list");
|
|
|
|
rerender(<PopupGateHarness taskView="board" taskPopupsBoardListOnly entries={entries} />);
|
|
expect(screen.getByTestId("floating-window-task-detail-FN-7944-board")).toBeInTheDocument();
|
|
expect(screen.getByTestId("floating-window-body-task-detail-FN-7944-board")).toHaveTextContent("FN-7944-board");
|
|
});
|
|
|
|
it("does not render popups opened away from Board/List when attachment is enabled", () => {
|
|
render(
|
|
<PopupGateHarness
|
|
taskView="command-center"
|
|
taskPopupsBoardListOnly
|
|
entries={[{ task: task("FN-7944-command"), originTaskView: "command-center" }]}
|
|
/>,
|
|
);
|
|
|
|
expectNoTaskPopupShell("FN-7944-command");
|
|
});
|
|
});
|