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:
5
.changeset/fn-6532-chat-first-task-detail.md
Normal file
5
.changeset/fn-6532-chat-first-task-detail.md
Normal 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.
|
||||
@@ -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")}
|
||||
|
||||
@@ -162,7 +162,7 @@ describe("AppModals", () => {
|
||||
const mockModalManager: ModalManager = {
|
||||
// State
|
||||
detailTask: null,
|
||||
detailTaskInitialTab: "definition",
|
||||
detailTaskInitialTab: "chat",
|
||||
settingsOpen: false,
|
||||
settingsInitialSection: undefined,
|
||||
githubImportOpen: false,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user