diff --git a/.changeset/fn-8197-merge-details-summary-tab.md b/.changeset/fn-8197-merge-details-summary-tab.md new file mode 100644 index 0000000000..bc0802c9fb --- /dev/null +++ b/.changeset/fn-8197-merge-details-summary-tab.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Move task Merge Details from Plan to the done-only Summary tab. +category: fix +dev: Keeps completion and merge metadata together without adding a new task-detail tab. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index a43deb6f81..0f7acbfd2f 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -1310,7 +1310,7 @@ Inspect task definition, logs, review feedback, comments, artifacts, workflow ou - Editable tasks with descriptions show **Summarize as title** beside the read-mode title; it asks AI to generate a concise title from the description and saves it without opening the edit form. - The top-level **Chat** tab appears first for active task details and is the default landing tab for non-`done` tasks. It uses the task's effective planning model, but opening the tab is lookup-only: Fusion creates the task-scoped planner chat only after you send a composer message, starter prompt, or planner-question answer. Once a user message exists, the resumable planner chat can appear in the global Chat list; interacted chats are kept when the task reaches `done` and removed when the task is archived. Each send includes bounded server-built task context so the planner can answer current status, progress, recent activity, dependency, and task definition questions. It shows starter prompts for common planning questions, can render structured planner questions, and converts only explicit operator steering intent through the scoped steering tool. The composer stays pinned while the transcript, loading, error, starter, history, and streaming states scroll internally; on mobile/narrow task detail, the default focused Chat layout hides nonessential title/metadata/tab/action rows until you collapse it from the in-view expand control. - The **Activity → Live**, **Feed**, and **Raw Logs** segments remain immediately after **Chat** and share an expand/collapse control that lets the active Activity segment fill the task-detail modal, then restores the normal header, tabs, and action footer when collapsed. -- The **Summary** tab appears for `done` tasks and remains their default landing tab. It shows the recorded completion summary, changed-file/merge stats when available, completed steps, workflow results, retry counts, and a token usage & cost section broken down by model from the already-loaded task detail; unpriced models show cost as unavailable rather than `$0`. +- The **Summary** tab appears for `done` tasks and remains their default landing tab. It shows the recorded completion summary, the **Merge Details** card (merge status, commit, PR, timestamp, and message), changed-file/merge stats when available, completed steps, workflow results, retry counts, and a token usage & cost section broken down by model from the already-loaded task detail; unpriced models show cost as unavailable rather than `$0`. - The **Cost** tab is available for tasks in every column and sits immediately after **Comments → Terminal** in the tab strip. It shows the read-time derived per-model cost breakdown (input, output, cached, cache-write, total tokens, derived USD) and a task total; no token usage shows an explicit empty state, while unpriced or zero-usage rows use `—` instead of a guessed `$0`. - The **Models** tab exposes inline **Thinking Level** selectors for **Executor Model**, **Reviewer Model**, and **Planning Model**. Executor saves the shared task thinking level, while Reviewer and Planning save independent per-lane overrides; leaving either lane on **Default** inherits the shared task thinking level and then the configured workflow/project defaults. diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index cd7f513206..4a5096599b 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -41,7 +41,6 @@ import { TaskComments } from "./TaskComments"; import { TaskChatTab } from "./TaskChatTab"; import { TaskPlannerChatTab } from "./TaskPlannerChatTab"; import { TaskReviewTab } from "./TaskReviewTab"; -import { MergeDetails } from "./MergeDetails"; import { TaskChangesTab } from "./TaskChangesTab"; import { TaskSummaryTab } from "./TaskSummaryTab"; import { TaskCostTab } from "./TaskCostTab"; @@ -5246,8 +5245,7 @@ export function TaskDetailContent({ ) : ( <> - {/* FNXC:TaskDetailSummaryTab 2026-06-27-00:00: The former inline Definition-tab completion summary is intentionally removed to avoid duplicating the new done-only Summary tab; Definition keeps merge/retry/source metadata below. */} - + {/* FNXC:TaskDetailSummaryTab 2026-07-29-00:00: FN-8197 keeps Definition focused on plan, retry, and source metadata; completed merge metadata renders exclusively in the done-only Summary tab. */} {(retrySummary?.total ?? 0) > 0 && (
diff --git a/packages/dashboard/app/components/TaskSummaryTab.tsx b/packages/dashboard/app/components/TaskSummaryTab.tsx index a30ebeab0f..24edbb852c 100644 --- a/packages/dashboard/app/components/TaskSummaryTab.tsx +++ b/packages/dashboard/app/components/TaskSummaryTab.tsx @@ -7,6 +7,7 @@ import type { TaskDetail, TaskStep, WorkflowStepResult } from "@fusion/core"; import type { ModelPricingOverrides } from "../../../core/src/model-pricing"; import { createMermaidCodeComponent, sharedRehypePlugins } from "./markdownPipeline"; import { ProviderIcon } from "./ProviderIcon"; +import { MergeDetails } from "./MergeDetails"; import { linkifyFilePaths, linkifyReactChildren } from "../utils/filePathLinkify"; import { inferProviderIconKey } from "../utils/providerIconKey"; import { buildTokenCostRows, formatCost, formatCount, totalCostForRows } from "../utils/taskTokenCost"; @@ -170,6 +171,14 @@ export function TaskSummaryTab({ task, pricingOverrides }: TaskSummaryTabProps) ) : null} + {/* + FNXC:TaskDetailSummaryTab 2026-07-29-00:00: + FN-8197 / issue #2248 moves Merge Details from Definition into the done-only Summary tab. Both + surfaces apply only to completed tasks, and merge status, PR, and commit-message metadata belong + with completion data rather than the task plan. + */} + + {hasAgentWork ? (

{t("taskDetail.summaryTab.agentWorkHeading", "Work done by agents")}

diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx index 0fd566bedc..68e00a030e 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx @@ -1557,23 +1557,24 @@ describe("TaskDetailModal", () => { expect(screen.getByRole("link", { name: "#42" })).toHaveAttribute("href", "https://github.com/owner/repo/pull/42"); }); - it("shows linked PR number in merge details for done tasks", () => { - render( + it("shows linked PR number in Summary merge details, not Definition, for done tasks", () => { + const task = makeTask({ + column: "done" as Column, + prInfo: { + url: "https://github.com/owner/repo/pull/42", + number: 42, + status: "merged", + title: "Task", + headBranch: "fusion/fn-099", + baseBranch: "main", + commentCount: 0, + }, + mergeDetails: { prNumber: 42 }, + }); + const summary = render( { />, ); - const links = screen.getAllByRole("link", { name: "#42" }); - expect(links.length).toBeGreaterThan(0); - expect(links[0]).toHaveAttribute("href", "https://github.com/owner/repo/pull/42"); + expect(summary.container.querySelector(".merge-details-card a")).toHaveAttribute("href", "https://github.com/owner/repo/pull/42"); + summary.unmount(); + + render( + , + ); + + expect(screen.queryByText("Merge Details")).toBeNull(); }); it("shows PR automation waiting label instead of Merge & Close when awaiting PR checks", () => { diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx index 8b502b4a6b..5c2ad73111 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx @@ -455,6 +455,71 @@ describe("TaskDetailModal Summary tab", () => { expect(css).not.toMatch(/task-summary-token[^{}]*#[0-9a-fA-F]{3,8}/); }); + it("renders every Merge Details data state inside the done-only Summary tab", () => { + const linkedTask = doneTask({ + mergeDetails: { + commitSha: "abcdef1234567890", + mergeConfirmed: true, + prNumber: 42, + mergedAt: "2026-07-29T12:00:00Z", + mergeCommitMessage: "feat: land summary merge details", + }, + prInfo: { + url: "https://github.com/owner/repo/pull/42", + number: 42, + status: "merged", + title: "Task", + headBranch: "fusion/fn-8197", + baseBranch: "main", + commentCount: 0, + }, + }); + const view = render(); + + const summary = screen.getByTestId("task-summary-tab"); + const mergeCard = summary.querySelector(".merge-details-card"); + expect(screen.getByText("Merge Details")).toBeTruthy(); + expect(screen.getByText("Merged successfully")).toBeTruthy(); + expect(screen.getByText("Merged at")).toBeTruthy(); + expect(screen.getByText("feat: land summary merge details")).toBeTruthy(); + expect(screen.getByRole("link", { name: "#42" })).toHaveAttribute("href", "https://github.com/owner/repo/pull/42"); + expect(mergeCard?.classList.contains("pr-card")).toBe(true); + expect(summary.contains(mergeCard)).toBe(true); + + view.rerender( + , + ); + expect(screen.getByText("Recorded without local merge confirmation")).toBeTruthy(); + expect(screen.getByText("#7")).toBeTruthy(); + expect(screen.queryByRole("link", { name: "#7" })).toBeNull(); + expect(screen.queryByText("Merged at")).toBeNull(); + expect(screen.queryByText("Message")).toBeNull(); + + view.rerender(); + expect(screen.queryByText("PR")).toBeNull(); + expect(screen.queryByText("Merged at")).toBeNull(); + expect(screen.queryByText("Message")).toBeNull(); + + view.rerender(); + expect(screen.getByTestId("task-summary-tab")).toBeTruthy(); + expect(screen.queryByText("Merge Details")).toBeNull(); + }); + + it("keeps relocated Merge Details inside the existing mobile pr-card containment", () => { + const { container } = render(); + const summary = screen.getByTestId("task-summary-tab"); + const mergeCard = container.querySelector(".task-summary-tab .merge-details-card.pr-card"); + + expect(mergeCard).toBeTruthy(); + expect(summary.contains(mergeCard)).toBe(true); + expect(readDashboardStylesSource()).toMatch(/@media \(max-width: 768px\)[\s\S]*\.pr-card/); + }); + it("renders graceful empty states without orphaned changed-file headings", () => { render(