From d74018ff81f04476e8981d6ad54c4e932f68b1fd Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 15 Jul 2026 14:15:50 -0700 Subject: [PATCH] FN-7974: collapse chat thinking blocks by default Collapse Thinking reasoning blocks by default so chat transcripts stay scannable without manually closing each block. - Remove the open attribute from TaskChatTab thinking details so blocks start collapsed - Strengthen ChatView and TaskChatTab tests for collapsed-by-default and expand-on-click across persisted, streaming, and Task Detail surfaces - Add a patch changeset for the operator-facing transcript UX fix Files changed: .changeset/fn-7974-collapse-thinking.md | 7 ++++++ packages/dashboard/app/components/TaskChatTab.tsx | 6 ++++- .../__tests__/ChatView.core-interactions.test.tsx | 26 +++++++++++++++++----- .../app/components/__tests__/TaskChatTab.test.tsx | 13 +++++++---- 4 files changed, 41 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-7974 Fusion-Task-Lineage: 7cd9009f-3483-444c-8024-ed6b1cec3b89 Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7974-collapse-thinking.md | 7 +++++ .../dashboard/app/components/TaskChatTab.tsx | 6 ++++- .../ChatView.core-interactions.test.tsx | 26 ++++++++++++++----- .../components/__tests__/TaskChatTab.test.tsx | 13 +++++++--- 4 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 .changeset/fn-7974-collapse-thinking.md diff --git a/.changeset/fn-7974-collapse-thinking.md b/.changeset/fn-7974-collapse-thinking.md new file mode 100644 index 0000000000..010bdd577f --- /dev/null +++ b/.changeset/fn-7974-collapse-thinking.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Chat "Thinking" reasoning blocks now start collapsed for a cleaner transcript. +category: fix +dev: Removed the `open` attribute from TaskChatTab's task-chat-thinking
; StandardChatSurface already collapses thinking. Regression tests assert collapsed-by-default + expand-on-click across persisted, streaming, and Task Detail chat surfaces (FN-7974). diff --git a/packages/dashboard/app/components/TaskChatTab.tsx b/packages/dashboard/app/components/TaskChatTab.tsx index a9c8d6ccbf..225b13adfa 100644 --- a/packages/dashboard/app/components/TaskChatTab.tsx +++ b/packages/dashboard/app/components/TaskChatTab.tsx @@ -533,12 +533,16 @@ function TaskChatToolGroup({ entries }: { entries: AgentLogEntry[] }) { ); } +/* + FNXC:Chat-Thinking 2026-07-15-10:33: + Chat thinking (reasoning) blocks render collapsed by default so the response is scannable without manually closing each block; the summary remains an expand-on-click affordance. (FN-7974) +*/ function TaskChatThinking({ entries }: { entries: AgentLogEntry[] }) { const { t } = useTranslation("app"); const combinedThinkingText = entries.map((entry) => entry.text).join(""); return ( -
+
{t("taskChat.thinking", "Thinking")} diff --git a/packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx index 6c91e4b671..a6d3a2eedc 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx @@ -855,7 +855,8 @@ describe("ChatView core interactions", () => { expect(streamingMessage?.textContent).toContain("Typing"); }); - it("shows thinking blocks collapsed by default", async () => { + it("keeps persisted thinking blocks collapsed until expanded", async () => { + const user = userEvent.setup(); setupMockChat({ activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" }, messages: [ @@ -866,9 +867,15 @@ describe("ChatView core interactions", () => { await renderWithAct(); const message = screen.getByTestId("chat-message-msg-001"); - const details = message.querySelector("details"); + const details = message.querySelector("details") as HTMLDetailsElement; expect(details).toBeInTheDocument(); - expect(details).toHaveProperty("open", false); + expect(details).not.toHaveAttribute("open"); + expect(within(message).getByText("I need to think about this...")).not.toBeVisible(); + + await user.click(within(details).getByText("Thinking")); + + expect(details).toHaveAttribute("open"); + expect(within(message).getByText("I need to think about this...")).toBeVisible(); }); describe("streaming states", () => { @@ -995,7 +1002,8 @@ describe("ChatView core interactions", () => { expect(typingIndicator?.querySelectorAll("span").length).toBe(3); }); - it("shows thinking indicator when streaming thinking arrives before text", async () => { + it("keeps streaming thinking collapsed until expanded", async () => { + const user = userEvent.setup(); setupMockChat({ activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" }, messages: [ @@ -1014,9 +1022,15 @@ describe("ChatView core interactions", () => { expect(streamingMessage?.textContent).toContain("Thinking"); // Thinking details should be rendered - const thinkingDetails = streamingMessage?.querySelector("details.chat-message-thinking"); + const thinkingDetails = streamingMessage?.querySelector("details.chat-message-thinking") as HTMLDetailsElement; expect(thinkingDetails).toBeInTheDocument(); - expect(thinkingDetails?.querySelector(".chat-message-thinking-content")?.textContent).toContain("analyzing the request"); + expect(thinkingDetails).not.toHaveAttribute("open"); + expect(within(thinkingDetails).getByText("analyzing the request...")).not.toBeVisible(); + + await user.click(within(thinkingDetails).getByText("Thinking")); + + expect(thinkingDetails).toHaveAttribute("open"); + expect(within(thinkingDetails).getByText("analyzing the request...")).toBeVisible(); // Typing indicator dots should be rendered const typingIndicator = streamingMessage?.querySelector(".chat-typing-indicator"); diff --git a/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx b/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx index 7d7a984280..f2c53f148a 100644 --- a/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx @@ -997,7 +997,7 @@ describe("TaskChatTab", () => { expect(within(standaloneEntry).getByLabelText("Tool entry timestamp")).toHaveTextContent(expectedTime); }); - it("renders thinking in an expanded-by-default collapsible block", async () => { + it("renders thinking in a collapsed-by-default expandable block", async () => { const user = userEvent.setup(); mockLogs([ makeEntry({ agent: "triage", type: "thinking", text: "I am considering options" }), @@ -1006,13 +1006,18 @@ describe("TaskChatTab", () => { render(); const thinking = screen.getByTestId("task-chat-thinking"); - expect(thinking).toHaveAttribute("open"); + expect(thinking).not.toHaveAttribute("open"); expect(within(thinking).getByText("Thinking")).toBeVisible(); - expect(screen.getByText("I am considering options")).toBeVisible(); + expect(screen.getByText("I am considering options")).not.toBeVisible(); expect(within(thinking).getAllByTestId("task-chat-entry-thinking")).toHaveLength(1); await user.click(within(thinking).getByText("Thinking")); + expect(thinking).toHaveAttribute("open"); + expect(screen.getByText("I am considering options")).toBeVisible(); + + await user.click(within(thinking).getByText("Thinking")); + expect(thinking).not.toHaveAttribute("open"); expect(screen.getByText("I am considering options")).not.toBeVisible(); }); @@ -1055,7 +1060,7 @@ describe("TaskChatTab", () => { expect(within(toolGroups[1]).getByLabelText("Tool names")).toHaveTextContent("second tool"); expect(screen.getAllByTestId("task-chat-entry-text")).toHaveLength(1); expect(screen.getByText("plain response")).toBeVisible(); - expect(screen.getByText("thinking between tools")).toBeVisible(); + expect(screen.getByText("thinking between tools")).not.toBeVisible(); }); it("appends newly streamed entries from the hook without auto-opening tool groups", () => {