diff --git a/.changeset/fn-7935-mailbox-artifact-view-task-popout.md b/.changeset/fn-7935-mailbox-artifact-view-task-popout.md new file mode 100644 index 0000000000..9f77735673 --- /dev/null +++ b/.changeset/fn-7935-mailbox-artifact-view-task-popout.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Mailbox artifact "View task" now opens the same movable, resizable task window used elsewhere. +category: fix +dev: MainContent mailbox onOpenTask routes fetchTaskDetail -> popOutTaskDetail (floating-window--task-detail) instead of the docked openDetailTask modal, matching DocumentsView's artifact-task path. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index c4fda79385..93a6877780 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -632,7 +632,7 @@ Mailbox view shows inbox/outbox communication threads and unread state. - Inbox renders one row per message (no sender-based collapsing) - clicking a message in the Mail tab opens the task detail pane with full message content and conversation context - reply rows in the mailbox modal can expand inline to show the replied-to message context for easier thread reading -- when an agent or dashboard chat session registers an artifact with `fn_artifact_register`, Fusion sends a best-effort `system` → user inbox message announcing the new artifact (for example, `New image artifact registered: `) with metadata for `artifactId`, `artifactType`, `title`, optional `mimeType`, `authorId`, and optional `taskId`; notification delivery is informational and never blocks or rolls back the artifact registration. Artifact notifications are actionable in message detail views: image artifacts show an inline preview plus **Open artifact**, while video/audio/document/other artifacts show an **Open artifact** link to the managed media URL. When `taskId` metadata is present, the same artifact block also shows **View task** so users can open the producing task detail directly from the mailbox. +- when an agent or dashboard chat session registers an artifact with `fn_artifact_register`, Fusion sends a best-effort `system` → user inbox message announcing the new artifact (for example, `New image artifact registered: <title>`) with metadata for `artifactId`, `artifactType`, `title`, optional `mimeType`, `authorId`, and optional `taskId`; notification delivery is informational and never blocks or rolls back the artifact registration. Artifact notifications are actionable in message detail views: image artifacts show an inline preview plus **Open artifact**, while video/audio/document/other artifacts show an **Open artifact** link to the managed media URL. When `taskId` metadata is present, the same artifact block also shows **View task** so users can open the producing task detail directly from the mailbox in the shared movable/resizable task-detail window. - on first engine startup under Fusion `0.59.x`, each project receives one best-effort `system` inbox notice about the upcoming embedded-Postgres storage migration with the Discord help link; `metadata.kind = "postgres-migration-notice"` prevents duplicates across restarts. - mailbox now includes an **Approvals** tab with pending and history filters (`approved` / `denied` / `completed`), approval detail context, and inline approve/deny actions for pending requests - for approvals gated by an agent's permission policy (permanent agents and task-worker heartbeats), the Approvals detail pane renders the gated action's real payload — tool name, shell command line or structured arguments, and working directory when present — instead of only a generic "Agent gated action for `<tool>`" summary; a stateless heartbeat retrying the same gated command reuses the existing pending approval instead of creating a duplicate (FN-7609) diff --git a/packages/dashboard/app/components/dashboard/MainContent.tsx b/packages/dashboard/app/components/dashboard/MainContent.tsx index 98cc108e18..fbf762aff1 100644 --- a/packages/dashboard/app/components/dashboard/MainContent.tsx +++ b/packages/dashboard/app/components/dashboard/MainContent.tsx @@ -358,10 +358,14 @@ export function MainContent({ <MailboxView projectId={currentProject?.id} addToast={addToast} - /* FNXC:ArtifactRegistry 2026-07-12-00:00: Artifact-registration mail notifications open their producing task through the shared task-detail fetch path so the mailbox does not invent a separate deep-link scheme. */ + /* + FNXC:ArtifactRegistry 2026-07-12-00:00: Artifact-registration mail notifications open their producing task through the shared task-detail fetch path so the mailbox does not invent a separate deep-link scheme. + + FNXC:ArtifactRegistry 2026-07-13-00:00: Mailbox artifact "View task" opens the producing task in the shared movable/resizable popped-out task-detail FloatingWindow (`popOutTaskDetail`), matching DocumentsView's artifact-task path instead of the docked `openDetailTask` modal, so the modal has full resize/move parity. + */ onOpenTask={(taskId) => { void fetchTaskDetail(taskId, currentProject?.id) - .then((task) => openDetailTask(task as TaskDetail)) + .then((task) => popOutTaskDetail(task)) .catch(() => addToast?.("Failed to open task", "error")); }} onUnreadCountChange={setMailboxUnreadCount} diff --git a/packages/dashboard/app/components/dashboard/__tests__/MainContent.mailbox-view-task.test.tsx b/packages/dashboard/app/components/dashboard/__tests__/MainContent.mailbox-view-task.test.tsx new file mode 100644 index 0000000000..c2ebc3746e --- /dev/null +++ b/packages/dashboard/app/components/dashboard/__tests__/MainContent.mailbox-view-task.test.tsx @@ -0,0 +1,83 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import type { TaskDetail } from "@fusion/core"; +import { MainContent } from "../MainContent"; +import type { MainContentProps } from "../types"; + +const { fetchTaskDetailMock } = vi.hoisted(() => ({ + fetchTaskDetailMock: vi.fn(), +})); + +vi.mock("../../../api", () => ({ + fetchTaskDetail: fetchTaskDetailMock, +})); + +vi.mock("../../MailboxView", () => ({ + MailboxView: ({ onOpenTask }: { onOpenTask?: (taskId: string) => void }) => ( + <button type="button" onClick={() => onOpenTask?.("FN-7935")}>Open mailbox artifact task</button> + ), +})); + +function mainContentProps(overrides: Partial<MainContentProps> = {}): MainContentProps { + return { + showBackendConnectionErrorPage: false, + projectsError: null, + t: ((key: string, fallback?: string) => fallback ?? key) as MainContentProps["t"], + retryingProjects: false, + handleRetryProjects: vi.fn(), + shellApi: null, + taskView: "mailbox", + modalManager: {} as MainContentProps["modalManager"], + handleChangeTaskView: vi.fn(), + refreshAppSettings: vi.fn(async () => undefined), + addToast: vi.fn(), + currentProject: { id: "project-1", name: "Project 1" } as MainContentProps["currentProject"], + viewMode: "project", + tasks: [], + workflowSteps: [], + openDetailTask: vi.fn(), + popOutTaskDetail: vi.fn(), + setMailboxUnreadCount: vi.fn(), + settingsLoaded: true, + skillsEnabled: true, + insightsEnabled: true, + researchEnabled: true, + evalsEnabled: true, + memoryEnabled: true, + goalsEnabled: true, + todosEnabled: true, + nodesEnabled: true, + capacityRiskBannerEnabled: false, + capacityRiskDismissed: false, + capacityRiskSignal: { level: "low", reasons: [] } as unknown as MainContentProps["capacityRiskSignal"], + ...overrides, + } as unknown as MainContentProps; +} + +describe("MainContent mailbox artifact View task routing", () => { + it("opens mailbox artifact tasks in the shared popped-out task-detail window", async () => { + const fetchedTask = { + id: "FN-7935", + title: "Mailbox artifact task", + description: "Task opened from a mailbox artifact message", + column: "todo", + status: "todo", + dependencies: [], + createdAt: new Date(0).toISOString(), + updatedAt: new Date(0).toISOString(), + steps: [], + } as unknown as TaskDetail; + const openDetailTask = vi.fn(); + const popOutTaskDetail = vi.fn(); + + fetchTaskDetailMock.mockResolvedValueOnce(fetchedTask); + + render(<MainContent {...mainContentProps({ openDetailTask, popOutTaskDetail })} />); + + screen.getByText("Open mailbox artifact task").click(); + + await waitFor(() => expect(popOutTaskDetail).toHaveBeenCalledWith(fetchedTask)); + expect(fetchTaskDetailMock).toHaveBeenCalledWith("FN-7935", "project-1"); + expect(openDetailTask).not.toHaveBeenCalled(); + }); +});