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) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7974-collapse-thinking.md
Normal file
7
.changeset/fn-7974-collapse-thinking.md
Normal file
@@ -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 <details>; StandardChatSurface already collapses thinking. Regression tests assert collapsed-by-default + expand-on-click across persisted, streaming, and Task Detail chat surfaces (FN-7974).
|
||||
@@ -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 (
|
||||
<details className="task-chat-thinking" data-testid="task-chat-thinking" open>
|
||||
<details className="task-chat-thinking" data-testid="task-chat-thinking">
|
||||
<summary className="task-chat-thinking-summary">
|
||||
<span>{t("taskChat.thinking", "Thinking")}</span>
|
||||
<TaskChatTimestamp timestamp={getLatestEntryTimestamp(entries)} label="Thinking block timestamp" />
|
||||
|
||||
@@ -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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
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");
|
||||
|
||||
@@ -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(<TaskChatTab task={makeTask()} active addToast={vi.fn()} />);
|
||||
|
||||
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", () => {
|
||||
|
||||
Reference in New Issue
Block a user