From ddf2f3d9564d03c55099aa7bff05b29dcb83c28a Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 12 Jul 2026 18:13:33 -0700 Subject: [PATCH] FN-7888: wire onDeleteTask through right-dock Tasks list Restores the Delete affordance in the right-dock Tasks list, which previously rendered TaskCard hosts without a delete handler so the menu action silently did nothing. - Thread onDeleteTask prop through DockTaskList into TaskCard - Pass onDeleteTask through overflowViewRegistry's Tasks view render props - Wire onDeleteTask from useRightDockController into both DockTaskList call sites - Add regression coverage for desktop context-menu and mobile pointer-up delete flows in TaskCard, DockTaskList, and RightDock tests - Add a patch changeset documenting the fix Files changed: .changeset/fn-7888-task-delete.md | 7 ++++ packages/dashboard/app/components/DockTaskList.tsx | 9 ++++- .../app/components/__tests__/DockTaskList.test.tsx | 16 +++++++- .../app/components/__tests__/RightDock.test.tsx | 16 +++++++- .../app/components/__tests__/TaskCard.test.tsx | 44 ++++++++++++++++++++++ .../app/components/overflowViewRegistry.tsx | 4 +- .../app/components/useRightDockController.tsx | 2 + 7 files changed, 93 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-7888 Fusion-Task-Lineage: 22767aba-fb92-4ac3-8348-ee0cea4345c2 Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7888-task-delete.md | 7 +++ .../dashboard/app/components/DockTaskList.tsx | 9 +++- .../__tests__/DockTaskList.test.tsx | 16 ++++++- .../components/__tests__/RightDock.test.tsx | 16 ++++++- .../components/__tests__/TaskCard.test.tsx | 44 +++++++++++++++++++ .../app/components/overflowViewRegistry.tsx | 4 +- .../app/components/useRightDockController.tsx | 2 + 7 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 .changeset/fn-7888-task-delete.md diff --git a/.changeset/fn-7888-task-delete.md b/.changeset/fn-7888-task-delete.md new file mode 100644 index 0000000000..1ce34e5387 --- /dev/null +++ b/.changeset/fn-7888-task-delete.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Restore task deletion from the right-dock Tasks list. +category: fix +dev: Threads the shared delete handler through right-dock task-card hosts and adds regression coverage for delete menu activation. diff --git a/packages/dashboard/app/components/DockTaskList.tsx b/packages/dashboard/app/components/DockTaskList.tsx index c6cf84bf5b..f161c7bd01 100644 --- a/packages/dashboard/app/components/DockTaskList.tsx +++ b/packages/dashboard/app/components/DockTaskList.tsx @@ -1,5 +1,5 @@ import { useCallback, useMemo, useState } from "react"; -import type { Task, TaskDetail } from "@fusion/core"; +import type { GithubIssueAction, Task, TaskDetail } from "@fusion/core"; import type { ToastType } from "../hooks/useToast"; import { TaskCard } from "./TaskCard"; import "./DockTaskList.css"; @@ -8,6 +8,7 @@ export interface DockTaskListProps { tasks: Array; projectId?: string; onOpenTask?: (task: Task | TaskDetail) => void; + onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean; removeLineageReferences?: boolean; githubIssueAction?: GithubIssueAction; allowResurrection?: boolean }) => Promise; addToast?: (message: string, type?: ToastType) => void; prAuthAvailable?: boolean; autoMergeEnabled?: boolean; @@ -24,6 +25,7 @@ export function DockTaskList({ tasks, projectId, onOpenTask, + onDeleteTask, addToast = () => {}, prAuthAvailable = false, autoMergeEnabled = false, @@ -75,6 +77,11 @@ export function DockTaskList({ task={task as Task} projectId={projectId} onOpenDetail={handleOpenTask} + /* + FNXC:TaskDeletion 2026-07-12-18:04: + Every task Delete affordance must reach the shared confirm→delete flow. The right-dock Tasks list is a TaskCard host, so it must pass onDeleteTask instead of rendering cards that silently lack/delete-disable the destructive path. + */ + onDeleteTask={onDeleteTask} addToast={addToast} disableDrag={true} prAuthAvailable={prAuthAvailable} diff --git a/packages/dashboard/app/components/__tests__/DockTaskList.test.tsx b/packages/dashboard/app/components/__tests__/DockTaskList.test.tsx index c42a807a87..a81df08b50 100644 --- a/packages/dashboard/app/components/__tests__/DockTaskList.test.tsx +++ b/packages/dashboard/app/components/__tests__/DockTaskList.test.tsx @@ -4,11 +4,12 @@ import { describe, expect, it, vi } from "vitest"; import { DockTaskList } from "../DockTaskList"; vi.mock("../TaskCard", () => ({ - TaskCard: ({ task, onOpenDetail, disableDrag }: { task: Task | TaskDetail; onOpenDetail: (task: Task | TaskDetail) => void; disableDrag?: boolean }) => ( + TaskCard: ({ task, onOpenDetail, onDeleteTask, disableDrag }: { task: Task | TaskDetail; onOpenDetail: (task: Task | TaskDetail) => void; onDeleteTask?: (id: string) => Promise; disableDrag?: boolean }) => ( ), @@ -164,6 +164,18 @@ describe("RightDock", () => { expect(screen.getByTestId("right-dock-files-view")).toHaveAttribute("data-layout", "two-pane"); }); + it("threads delete into the compact Tasks tab cards", () => { + const tasks = [ + { id: "FN-DELETE", title: "Right dock delete", column: "triage" }, + ]; + const onDeleteTask = vi.fn(); + + render(); + fireEvent.click(screen.getByTestId("right-dock-tab-tasks")); + + expect(screen.getByTestId("mock-task-card-FN-DELETE")).toHaveAttribute("data-has-delete", "true"); + }); + it("renders the filtered Tasks tab list at both narrow and wide dock widths", () => { const tasks = [ { id: "FN-ACTIVE", title: "Active dock task", column: "todo" }, diff --git a/packages/dashboard/app/components/__tests__/TaskCard.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.test.tsx index f847aa4721..3ec12dd43a 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.test.tsx @@ -1079,6 +1079,50 @@ describe("TaskCard", () => { }); }); + it("runs the delete flow from the desktop task context menu", async () => { + const onDeleteTask = vi.fn(async () => makeTask()); + mockConfirm.mockResolvedValueOnce(true); + + render( + , + ); + + fireEvent.contextMenu(document.querySelector(".card")!, { clientX: 24, clientY: 28 }); + fireEvent.click(screen.getByRole("menuitem", { name: "Delete" })); + + await waitFor(() => { + expect(mockConfirm).toHaveBeenCalledWith(expect.objectContaining({ title: "Delete Task" })); + expect(onDeleteTask).toHaveBeenCalledWith("FN-001"); + }); + }); + + it("runs the delete flow from the mobile pointer-up task context menu", async () => { + const onDeleteTask = vi.fn(async () => makeTask()); + mockConfirm.mockResolvedValueOnce(true); + + render( + , + ); + + fireEvent.contextMenu(document.querySelector(".card")!, { clientX: 24, clientY: 28 }); + fireEvent.pointerUp(screen.getByRole("menuitem", { name: "Delete" }), { pointerType: "touch", pointerId: 7 }); + + await waitFor(() => { + expect(mockConfirm).toHaveBeenCalledWith(expect.objectContaining({ title: "Delete Task" })); + expect(onDeleteTask).toHaveBeenCalledWith("FN-001"); + }); + }); + it("preserves githubIssueAction on dependency-conflict retry", async () => { const conflict = new Error("Cannot delete task FN-001: still referenced as a dependency by FN-002.") as Error & { status: number; details: { code: string; dependentIds: string[] } }; conflict.status = 409; diff --git a/packages/dashboard/app/components/overflowViewRegistry.tsx b/packages/dashboard/app/components/overflowViewRegistry.tsx index de961a6957..5ad51f6845 100644 --- a/packages/dashboard/app/components/overflowViewRegistry.tsx +++ b/packages/dashboard/app/components/overflowViewRegistry.tsx @@ -11,7 +11,7 @@ import { Monitor, type LucideProps, } from "lucide-react"; -import type { Task, TaskDetail, WorkflowStep } from "@fusion/core"; +import type { GithubIssueAction, Task, TaskDetail, WorkflowStep } from "@fusion/core"; import type { PluginDashboardViewEntry } from "../api"; import type { ToastType } from "../hooks/useToast"; import { buildPluginTaskViewId } from "../plugins/pluginViewRegistry"; @@ -79,6 +79,7 @@ export interface OverflowViewRenderProps { onOpenSettings?: (section?: string) => void; onOpenTaskDetail?: (taskId: string) => void; onOpenTaskInDock?: (task: Task | TaskDetail) => void; + onDeleteTask?: (id: string, options?: { removeDependencyReferences?: boolean; removeLineageReferences?: boolean; githubIssueAction?: GithubIssueAction; allowResurrection?: boolean }) => Promise; onOpenDetail?: (task: Task | TaskDetail, initialTab?: DetailTaskTab) => void; onSendSelectionToTask?: (description: string) => void; onCreateTaskFromInsight?: (payload: { insightId: string; title: string; description: string }) => Promise | void; @@ -157,6 +158,7 @@ export const STATIC_OVERFLOW_VIEW_ENTRIES: readonly OverflowViewEntry[] = [ tasks={props.tasks ?? []} projectId={props.projectId} onOpenTask={props.onOpenTaskInDock} + onDeleteTask={props.onDeleteTask} addToast={props.addToast} prAuthAvailable={false} autoMergeEnabled={false} diff --git a/packages/dashboard/app/components/useRightDockController.tsx b/packages/dashboard/app/components/useRightDockController.tsx index a7950c38e8..5c5509bd4d 100644 --- a/packages/dashboard/app/components/useRightDockController.tsx +++ b/packages/dashboard/app/components/useRightDockController.tsx @@ -168,6 +168,7 @@ export function useRightDockController(input: RightDockControllerInput): RightDo task={task} projectId={input.projectId} onOpenDetail={(value: Task | TaskDetail) => input.openDetailTask(value)} + onDeleteTask={input.onDeleteTask} addToast={input.addToast} disableDrag={true} prAuthAvailable={input.prAuthAvailable} @@ -212,6 +213,7 @@ export function useRightDockController(input: RightDockControllerInput): RightDo DockTaskList rows must open through the controller's ordinary right-dock task route, not TaskCard's canonical full task modal. Thread one controller-level handler into registry render props so both compact and expanded Tasks lists share popup-setting routing and setting-off dock-detail behavior. */ onOpenTaskInDock: openTaskFromDockList, + onDeleteTask: input.onDeleteTask, onOpenDetail: input.openDetailTask, onSendSelectionToTask: input.onSendSelectionToTask, onCreateTaskFromInsight: input.onCreateTaskFromInsight,