FN-6532: make Chat the default task detail tab

Chat now opens first in task detail while explicit tab entrypoints continue to work.

- Default task detail modal state and open-detail calls to the Chat tab.
- Reorder the task detail tab strip so Chat appears before Definition.
- Extend modal and tab tests to cover Chat-first defaults and explicit tab preservation.
- Add a patch changeset for the published Fusion package.

Files changed:
 .changeset/fn-6532-chat-first-task-detail.md       |  5 ++
 .../dashboard/app/components/TaskDetailModal.tsx   | 24 ++++---
 .../app/components/__tests__/AppModals.test.tsx    |  2 +-
 .../TaskDetailModal.attachments-and-tabs.test.tsx  | 81 +++++++++++++++++-----
 .../TaskDetailModal.definition-actions.test.tsx    | 77 ++++++++++++++++----
 .../app/hooks/__tests__/useModalManager.test.ts    |  4 +-
 packages/dashboard/app/hooks/useModalManager.ts    | 13 +++-
 7 files changed, 162 insertions(+), 44 deletions(-)

Fusion-Task-Id: FN-6532

Fusion-Task-Lineage: 7824f253-2a54-4e2d-af64-ed082ce8fbf9
This commit is contained in:
gsxdsm
2026-06-17 01:45:36 -07:00
parent d7a9dfe398
commit 914842fb4f
7 changed files with 162 additions and 44 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Make Chat the first tab and default active view in the task detail modal while preserving explicit initial tab requests.

View File

@@ -375,7 +375,7 @@ export interface TaskDetailModalProps {
prAuthAvailable?: boolean;
autoMergeEnabled?: boolean;
onOpenWorkflowEditor?: () => void;
/** Open the modal with this tab active instead of "definition" */
/** Open the modal with this tab active instead of the default Chat view. */
initialTab?: TabId;
/** Mobile-only header affordance mode. */
mobileHeaderMode?: "close" | "back";
@@ -555,7 +555,11 @@ export function TaskDetailContent({
prAuthAvailable,
autoMergeEnabled: autoMergeEnabledProp,
onOpenWorkflowEditor,
initialTab = "definition",
/**
* FNXC:TaskDetailTabs 2026-06-17-00:00:
* FN-6532 makes Chat the default task-detail view when no caller supplies an explicit initial tab.
*/
initialTab = "chat",
mobileHeaderMode = "close",
embedded = false,
onRequestClose,
@@ -3063,18 +3067,22 @@ export function TaskDetailContent({
{!isEditing && (
<>
<div className="detail-tabs">
<button
className={`detail-tab${activeTab === "definition" ? " detail-tab-active" : ""}`}
onClick={() => setActiveTab("definition")}
>
{t("taskDetail.tabs.definition", "Definition")}
</button>
{/*
FNXC:TaskDetailTabs 2026-06-17-00:00:
FN-6532 requires Chat to be the first task-detail tab while preserving every explicit tab entrypoint.
*/}
<button
className={`detail-tab${activeTab === "chat" ? " detail-tab-active" : ""}`}
onClick={() => setActiveTab("chat")}
>
{t("taskDetail.tabs.chat", "Chat")}
</button>
<button
className={`detail-tab${activeTab === "definition" ? " detail-tab-active" : ""}`}
onClick={() => setActiveTab("definition")}
>
{t("taskDetail.tabs.definition", "Definition")}
</button>
<button
className={`detail-tab${activeTab === "logs" ? " detail-tab-active" : ""}`}
onClick={() => setActiveTab("logs")}

View File

@@ -162,7 +162,7 @@ describe("AppModals", () => {
const mockModalManager: ModalManager = {
// State
detailTask: null,
detailTaskInitialTab: "definition",
detailTaskInitialTab: "chat",
settingsOpen: false,
settingsInitialSection: undefined,
githubImportOpen: false,

View File

@@ -75,6 +75,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask()}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -114,6 +115,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask()}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -202,6 +204,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ dependencies: [] })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -223,6 +226,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ dependencies: ["FN-001", "FN-002"] })}
initialTab="definition"
tasks={allTasks}
onClose={noop}
onMoveTask={noopMove}
@@ -258,6 +262,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ dependencies: [] })}
initialTab="definition"
tasks={allTasks}
onClose={noop}
onMoveTask={noopMove}
@@ -288,6 +293,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ dependencies: ["FN-001", "FN-002"] })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -379,6 +385,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ dependencies: [] })}
initialTab="definition"
tasks={allTasks}
onClose={noop}
onMoveTask={noopMove}
@@ -408,6 +415,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ dependencies: [] })}
initialTab="definition"
tasks={allTasks}
onClose={noop}
onMoveTask={noopMove}
@@ -427,7 +435,7 @@ describe("TaskDetailModal", () => {
});
describe("tab toggle", () => {
it("defaults to the Definition tab", () => {
it("defaults to the Chat tab", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ prompt: "# Hello\n\nContent" })}
@@ -442,18 +450,19 @@ describe("TaskDetailModal", () => {
expect(screen.getByText("Definition")).toBeTruthy();
expect(screen.getByText("Logs")).toBeTruthy();
// Activity and Agent Log are subviews inside the Logs tab, not top-level tabs
// They should NOT be visible on the Definition tab
// Activity and Agent Log are subviews inside the Logs tab, not top-level tabs.
// They should NOT be visible on the default Chat tab.
expect(screen.queryByText("Activity")).toBeNull();
expect(screen.queryByText("Agent Log")).toBeNull();
// Definition content should be visible
expect(container.querySelector(".markdown-body")).toBeTruthy();
// Activity section should NOT be visible initially
// Chat content should be visible by default.
expect(container.querySelector(".detail-section--chat")).toBeTruthy();
expect(container.querySelector("[data-testid='task-chat-tab']")).toBeTruthy();
// Activity section should NOT be visible initially.
expect(container.querySelector(".detail-activity")).toBeNull();
// Agent log viewer should not be visible
// Agent log viewer should not be visible.
expect(container.querySelector("[data-testid='agent-log-viewer']")).toBeNull();
// After clicking Logs tab, the subview toggle buttons should appear
// After clicking Logs tab, the subview toggle buttons should appear.
fireEvent.click(screen.getByText("Logs"));
const logSubviewToggle = container.querySelector(".log-subview-toggle");
expect(logSubviewToggle).toBeTruthy();
@@ -601,6 +610,7 @@ describe("TaskDetailModal", () => {
prompt: "# Hello\n\nContent",
log: [{ timestamp: "2026-01-01T00:00:00Z", action: "Test" }],
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -691,7 +701,7 @@ describe("TaskDetailModal", () => {
/>,
);
// Default: Definition tab active → enabled should be false
// Default: Chat tab active → enabled should be false
const initialCall = mockUseAgentLogs.mock.calls[mockUseAgentLogs.mock.calls.length - 1];
expect(initialCall[1]).toBe(false);
@@ -744,15 +754,15 @@ describe("TaskDetailModal", () => {
);
// For an in-progress task (no workflow steps, no merge commit), the
// top-level tabs are: Definition, Chat, Logs, Changes, Review, Comments,
// top-level tabs are: Chat, Definition, Logs, Changes, Review, Comments,
// Documents, Model, Workflow, Stats, Routing.
const tabTexts = ["Definition", "Chat", "Logs", "Changes", "Review", "Comments", "Documents", "Model", "Workflow", "Stats", "Routing"];
const tabTexts = ["Chat", "Definition", "Logs", "Changes", "Review", "Comments", "Documents", "Model", "Workflow", "Stats", "Routing"];
const tabs = screen.getAllByRole("button").filter((b) =>
tabTexts.includes(b.textContent || "")
);
expect(tabs.map((tab) => tab.textContent)).toEqual(tabTexts);
expect(tabs[0].textContent).toBe("Definition");
expect(tabs[1].textContent).toBe("Chat");
expect(tabs[0].textContent).toBe("Chat");
expect(tabs[1].textContent).toBe("Definition");
expect(tabs[2].textContent).toBe("Logs");
// Activity and Agent Log are NOT top-level tabs (they are subviews inside Logs)
@@ -937,6 +947,47 @@ describe("TaskDetailModal", () => {
expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
});
it("FN-6532 defaults to Chat first while preserving explicit tab requests", () => {
const { container, rerender } = render(
<TaskDetailModal
task={makeTask({ prompt: "# Hello\n\nContent" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const tabs = Array.from(container.querySelectorAll<HTMLButtonElement>(".detail-tab"));
expect(tabs.map((tab) => tab.textContent)).toEqual(expect.arrayContaining(["Chat", "Definition"]));
expect(tabs[0]).toHaveTextContent("Chat");
const chatTab = screen.getByRole("button", { name: "Chat" });
const definitionTab = screen.getByRole("button", { name: "Definition" });
expect(chatTab.compareDocumentPosition(definitionTab) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(chatTab).toHaveClass("detail-tab-active");
expect(definitionTab).not.toHaveClass("detail-tab-active");
expect(container.querySelector(".detail-section--chat [data-testid='task-chat-tab']")).toBeTruthy();
rerender(
<TaskDetailModal
task={makeTask({ prompt: "# Hello\n\nContent" })}
initialTab="logs"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(screen.getByRole("button", { name: "Logs" })).toHaveClass("detail-tab-active");
expect(screen.getByRole("button", { name: "Chat" })).not.toHaveClass("detail-tab-active");
expect(container.querySelector(".detail-section--chat")).toBeNull();
});
it("FN-6347 applies chat modifiers only while the Chat tab is active", () => {
const { container } = render(
<TaskDetailModal
@@ -950,10 +1001,6 @@ describe("TaskDetailModal", () => {
/>,
);
expect(container.querySelector(".detail-body--chat")).toBeNull();
expect(container.querySelector(".detail-section--chat")).toBeNull();
fireEvent.click(screen.getByRole("button", { name: "Chat" }));
const chatBody = container.querySelector(".detail-body--chat");
const chatSection = container.querySelector(".detail-section--chat");
expect(chatBody).toBeTruthy();

View File

@@ -24,6 +24,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ prompt: "# Test\n\nSpec content." })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -40,6 +41,7 @@ describe("TaskDetailModal", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ prompt: "# Test\n\nSpec content." })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -65,6 +67,7 @@ describe("TaskDetailModal", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ prompt: "# Test Task\n\nTest specification." })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -94,6 +97,7 @@ describe("TaskDetailModal", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ id: "FN-099", prompt: "# Original" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -121,6 +125,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ prompt: "# Test" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -146,6 +151,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-099", column: "todo", prompt: "# Test" })}
initialTab="definition"
onClose={onClose}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -173,6 +179,7 @@ describe("TaskDetailModal", () => {
const { container } = render(
<TaskDetailModal
task={makeTask()}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -183,11 +190,11 @@ describe("TaskDetailModal", () => {
);
// In-progress tasks show exactly 11 tabs:
// Definition, Chat, Logs, Changes, Review, Comments, Documents, Model, Workflow, Stats, Routing
// Chat, Definition, Logs, Changes, Review, Comments, Documents, Model, Workflow, Stats, Routing
const tabs = container.querySelectorAll(".detail-tab");
expect(tabs.length).toBe(11);
expect(tabs[0].textContent).toBe("Definition");
expect(tabs[1].textContent).toBe("Chat");
expect(tabs[0].textContent).toBe("Chat");
expect(tabs[1].textContent).toBe("Definition");
expect(tabs[2].textContent).toBe("Logs");
expect(tabs[3].textContent).toBe("Changes");
expect(tabs[4].textContent).toBe("Review");
@@ -205,6 +212,7 @@ describe("TaskDetailModal", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ enabledWorkflowSteps: ["WS-001"] })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -217,8 +225,8 @@ describe("TaskDetailModal", () => {
// In-progress task with workflow steps: 11 tabs (Review after Changes, Workflow after Model)
const tabs = container.querySelectorAll(".detail-tab");
expect(tabs.length).toBe(11);
expect(tabs[0].textContent).toBe("Definition");
expect(tabs[1].textContent).toBe("Chat");
expect(tabs[0].textContent).toBe("Chat");
expect(tabs[1].textContent).toBe("Definition");
expect(tabs[2].textContent).toBe("Logs");
expect(tabs[3].textContent).toBe("Changes");
expect(tabs[4].textContent).toBe("Review");
@@ -237,6 +245,7 @@ describe("TaskDetailModal", () => {
column: "done",
mergeDetails: { commitSha: "abc1234567890", filesChanged: 3 },
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -246,11 +255,11 @@ describe("TaskDetailModal", () => {
/>,
);
// Done task with commit SHA: Definition, Chat, Logs, Changes, Review, Comments, Documents, Model, Workflow, Stats, Routing (11 tabs, no Commits)
// Done task with commit SHA: Chat, Definition, Logs, Changes, Review, Comments, Documents, Model, Workflow, Stats, Routing (11 tabs, no Commits)
const tabs = container.querySelectorAll(".detail-tab");
expect(tabs.length).toBe(11);
expect(tabs[0].textContent).toBe("Definition");
expect(tabs[1].textContent).toBe("Chat");
expect(tabs[0].textContent).toBe("Chat");
expect(tabs[1].textContent).toBe("Definition");
expect(tabs[2].textContent).toBe("Logs");
expect(tabs[3].textContent).toBe("Changes");
expect(tabs[4].textContent).toBe("Review");
@@ -272,6 +281,7 @@ describe("TaskDetailModal", () => {
mergeDetails: { commitSha: "abc1234567890", filesChanged: 3 },
enabledWorkflowSteps: ["WS-001"],
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -284,8 +294,8 @@ describe("TaskDetailModal", () => {
// Done task with workflow steps and commit SHA: 11 tabs including Review (no Commits)
const tabs = container.querySelectorAll(".detail-tab");
expect(tabs.length).toBe(11);
expect(tabs[0].textContent).toBe("Definition");
expect(tabs[1].textContent).toBe("Chat");
expect(tabs[0].textContent).toBe("Chat");
expect(tabs[1].textContent).toBe("Definition");
expect(tabs[2].textContent).toBe("Logs");
expect(tabs[3].textContent).toBe("Changes");
expect(tabs[4].textContent).toBe("Review");
@@ -303,6 +313,7 @@ describe("TaskDetailModal", () => {
const { container: triageContainer } = render(
<TaskDetailModal
task={makeTask({ column: "triage" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -313,14 +324,15 @@ describe("TaskDetailModal", () => {
);
const triageTabs = triageContainer.querySelectorAll(".detail-tab");
expect(triageTabs.length).toBe(10); // Definition, Chat, Logs, Review, Comments, Documents, Model, Workflow, Stats, Routing
expect(triageTabs.length).toBe(10); // Chat, Definition, Logs, Review, Comments, Documents, Model, Workflow, Stats, Routing
expect(Array.from(triageTabs).map(t => t.textContent)).toEqual([
"Definition", "Chat", "Logs", "Review", "Comments", "Documents", "Model", "Workflow", "Stats", "Routing",
"Chat", "Definition", "Logs", "Review", "Comments", "Documents", "Model", "Workflow", "Stats", "Routing",
]);
const { container: todoContainer } = render(
<TaskDetailModal
task={makeTask({ column: "todo" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -331,9 +343,9 @@ describe("TaskDetailModal", () => {
);
const todoTabs = todoContainer.querySelectorAll(".detail-tab");
expect(todoTabs.length).toBe(10); // Definition, Chat, Logs, Review, Comments, Documents, Model, Workflow, Stats, Routing
expect(todoTabs.length).toBe(10); // Chat, Definition, Logs, Review, Comments, Documents, Model, Workflow, Stats, Routing
expect(Array.from(todoTabs).map(t => t.textContent)).toEqual([
"Definition", "Chat", "Logs", "Review", "Comments", "Documents", "Model", "Workflow", "Stats", "Routing",
"Chat", "Definition", "Logs", "Review", "Comments", "Documents", "Model", "Workflow", "Stats", "Routing",
]);
});
@@ -341,6 +353,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ prompt: "" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -364,6 +377,7 @@ describe("TaskDetailModal", () => {
status: "awaiting-approval",
prompt: "# Task Spec",
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -385,6 +399,7 @@ describe("TaskDetailModal", () => {
status: "awaiting-approval",
prompt: "# Task Spec",
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -406,6 +421,7 @@ describe("TaskDetailModal", () => {
status: "planning",
prompt: "# Task Spec",
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -427,6 +443,7 @@ describe("TaskDetailModal", () => {
status: "awaiting-approval",
prompt: "",
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -454,6 +471,7 @@ describe("TaskDetailModal", () => {
status: "awaiting-approval",
prompt: "# Task Spec",
})}
initialTab="definition"
onClose={onClose}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -489,6 +507,7 @@ describe("TaskDetailModal", () => {
status: "awaiting-approval",
prompt: "# Task Spec",
})}
initialTab="definition"
onClose={onClose}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -534,6 +553,7 @@ describe("TaskDetailModal", () => {
status: "awaiting-approval",
prompt: "# Task Spec",
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -566,6 +586,7 @@ describe("TaskDetailModal", () => {
status: "awaiting-approval",
prompt: "# Task Spec",
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -600,6 +621,7 @@ describe("TaskDetailModal", () => {
status: "awaiting-approval",
prompt: "# Task Spec",
})}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -623,6 +645,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask()}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -644,6 +667,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask()}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -665,6 +689,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -697,6 +722,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001" })}
initialTab="definition"
onClose={onClose}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -729,6 +755,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -759,6 +786,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -788,6 +816,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -821,6 +850,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ column })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -839,6 +869,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ column: "triage" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -854,6 +885,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ column: "triage", paused: true })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -870,6 +902,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ column: "triage", paused: true })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -891,6 +924,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "todo", paused: undefined, userPaused: true })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -917,6 +951,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ column: "triage", paused: true, assignedAgentId: "agent-1" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -946,6 +981,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ column: "triage", paused: true, assignedAgentId: "agent-1", pausedByAgentId: "agent-1" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -968,6 +1004,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ column: "triage", paused: false, status: "todo" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -984,6 +1021,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1007,6 +1045,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1029,6 +1068,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1056,6 +1096,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1079,6 +1120,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1109,6 +1151,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={onClose}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1136,6 +1179,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1168,6 +1212,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={onClose}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1204,6 +1249,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1234,6 +1280,7 @@ describe("TaskDetailModal", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1267,6 +1314,7 @@ describe("TaskDetailModal", () => {
render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -1300,6 +1348,7 @@ describe("TaskDetailModal", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "done" })}
initialTab="definition"
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}

View File

@@ -132,7 +132,7 @@ describe("useModalManager", () => {
});
expect(result.current.detailTask?.id).toBe("FN-123");
expect(result.current.detailTaskInitialTab).toBe("definition");
expect(result.current.detailTaskInitialTab).toBe("chat");
act(() => {
result.current.openDetailWithChangesTab(task);
@@ -203,7 +203,7 @@ describe("useModalManager", () => {
expect(result.current.detailTask?.id).toBe("FN-456");
// Should not have prompt field (plain Task)
expect("prompt" in (result.current.detailTask as unknown as Record<string, unknown>)).toBe(false);
expect(result.current.detailTaskInitialTab).toBe("definition");
expect(result.current.detailTaskInitialTab).toBe("chat");
});
it.each([

View File

@@ -5,6 +5,7 @@ import type { SectionId } from "../components/SettingsModal";
import type { ToastType } from "./useToast";
export type DetailTaskTab =
| "chat"
| "definition"
| "logs"
| "changes"
@@ -162,7 +163,11 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
const [subtaskResumeSessionId, setSubtaskResumeSessionId] = useState<string | undefined>(undefined);
// Can be Task (optimistic open) or TaskDetail (full data with prompt)
const [detailTask, setDetailTask] = useState<(Task | TaskDetail) | null>(null);
const [detailTaskInitialTab, setDetailTaskInitialTab] = useState<DetailTaskTab>("definition");
/**
* FNXC:TaskDetailTabs 2026-06-17-00:00:
* FN-6532 makes Chat the default task-detail view whenever a task opens without an explicit tab request.
*/
const [detailTaskInitialTab, setDetailTaskInitialTab] = useState<DetailTaskTab>("chat");
const [detailTaskOrigin, setDetailTaskOrigin] = useState<DetailTaskOrigin | null>(null);
const [groupModalGroupId, setGroupModalGroupId] = useState<string | null>(null);
const [settingsOpen, setSettingsOpen] = useState(false);
@@ -250,9 +255,13 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
setSubtaskResumeSessionId(undefined);
}, []);
/**
* FNXC:TaskDetailTabs 2026-06-17-00:00:
* Open-detail callers that omit initialTab should land on Chat; explicit tab requests preserve caller intent.
*/
const openDetailTask = useCallback((
task: Task | TaskDetail,
initialTab: DetailTaskTab = "definition",
initialTab: DetailTaskTab = "chat",
options?: { origin?: DetailTaskOrigin },
) => {
setDetailTask(task);