FN-7935: route mailbox artifact View task to the popped-out task-detail window

Mailbox artifact "View task" now opens the producing task in the same shared, movable/resizable floating task-detail window used elsewhere in the dashboard, instead of the docked task-detail modal.

- MainContent's MailboxView onOpenTask handler now calls popOutTaskDetail(task) after fetchTaskDetail resolves, instead of openDetailTask(task), matching DocumentsView's artifact-task open path
- add regression test verifying mailbox artifact "View task" clicks resolve the task and route to popOutTaskDetail (not openDetailTask)
- update docs/dashboard-guide.md to describe the shared movable/resizable task-detail window behavior
- add changeset (patch) documenting the fix for @runfusion/fusion

Files changed:
 .changeset/fn-7935-mailbox-artifact-view-task-popout.md                        |  7 ++
 docs/dashboard-guide.md                                                        |  2 +-
 packages/dashboard/app/components/dashboard/MainContent.tsx                    |  8 ++-
 .../MainContent.mailbox-view-task.test.tsx                                     | 83 ++++++++++++++++++++++
 4 files changed, 97 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-7935

Fusion-Task-Lineage: 51374962-aa36-4390-a6b5-b519e7fc2bf2

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-13 00:50:30 -07:00
parent 29560021d3
commit 1f9dcea4b6
4 changed files with 97 additions and 3 deletions

View File

@@ -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.

View File

@@ -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: <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.
- 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)

View File

@@ -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}

View File

@@ -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();
});
});