FN-8197: move merge details to summary tab
Keep completed task merge metadata with its completion summary. - Relocate the Merge Details card from Definition to the done-only Summary tab. - Cover merge-detail states and Definition/mobile containment with dashboard tests. - Document the Summary tab behavior and add a patch changeset. Files changed: .changeset/fn-8197-merge-details-summary-tab.md | 7 +++ docs/dashboard-guide.md | 2 +- packages/dashboard/app/components/TaskDetailModal.tsx | 4 +- packages/dashboard/app/components/TaskSummaryTab.tsx | 9 +++ packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx | 49 ++++++++++------ packages/dashboard/app/components/__tests__/TaskDetailModal.summary-tab.test.tsx | 65 ++++++++++++++++++++++ 6 files changed, 115 insertions(+), 21 deletions(-) Fusion-Task-Id: FN-8197 Fusion-Task-Lineage: f4aac014-1e8d-4b8f-91f1-ba40ae879508 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8197-merge-details-summary-tab.md
Normal file
7
.changeset/fn-8197-merge-details-summary-tab.md
Normal file
@@ -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.
|
||||
@@ -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`.
|
||||
<!-- FNXC:Settings-ThinkingLevel 2026-07-13-00:27: The task-detail Models tab now persists validatorThinkingLevel and planningThinkingLevel separately so Reviewer and Planning lanes can choose reasoning effort without changing the Executor lane's task.thinkingLevel. -->
|
||||
- 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.
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 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. */}
|
||||
<MergeDetails task={task} />
|
||||
{/* 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 && (
|
||||
<div className="detail-section detail-retries-section">
|
||||
<div className="detail-source-header">
|
||||
|
||||
@@ -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)
|
||||
</section>
|
||||
) : 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.
|
||||
*/}
|
||||
<MergeDetails task={task} />
|
||||
|
||||
{hasAgentWork ? (
|
||||
<section className="task-summary-section task-summary-section--agent-work">
|
||||
<h4>{t("taskDetail.summaryTab.agentWorkHeading", "Work done by agents")}</h4>
|
||||
|
||||
@@ -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(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
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 },
|
||||
})}
|
||||
initialTab="summary"
|
||||
task={task}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -1583,9 +1584,23 @@ describe("TaskDetailModal", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
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(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
task={task}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText("Merge Details")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows PR automation waiting label instead of Merge & Close when awaiting PR checks", () => {
|
||||
|
||||
@@ -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(<TaskSummaryTab task={linkedTask} />);
|
||||
|
||||
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(
|
||||
<TaskSummaryTab
|
||||
task={doneTask({
|
||||
mergeDetails: { commitSha: "abcdef1234567890", mergeConfirmed: false, prNumber: 7 },
|
||||
prInfo: undefined,
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
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(<TaskSummaryTab task={doneTask({ mergeDetails: { commitSha: "abcdef1234567890" } })} />);
|
||||
expect(screen.queryByText("PR")).toBeNull();
|
||||
expect(screen.queryByText("Merged at")).toBeNull();
|
||||
expect(screen.queryByText("Message")).toBeNull();
|
||||
|
||||
view.rerender(<TaskSummaryTab task={doneTask({ mergeDetails: undefined })} />);
|
||||
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(<TaskSummaryTab task={doneTask({ mergeDetails: { commitSha: "abcdef1234567890", prNumber: 42 } })} />);
|
||||
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(
|
||||
<TaskDetailModal
|
||||
|
||||
Reference in New Issue
Block a user