From d87db1490ec4c912e9e3aebf514ded48cdc098ae Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 28 Jun 2026 20:02:14 -0700 Subject: [PATCH] FN-7215: compact task chat tool summaries Compact task-detail chat tool-call history so collapsed summaries stay scannable. - Tighten collapsed tool-call summary styling to one-line, ellipsis-friendly rows on desktop and mobile. - Preserve count, deduped tool names, overflow, and error indicators while keeping expanded details dense. - Document the compact task chat behavior and add regression coverage against regular Chat styling. Files changed: .changeset/fn-7215-compact-task-chat-tools.md | 7 +++ docs/dashboard-guide.md | 2 +- packages/dashboard/app/components/TaskChatTab.css | 63 +++++++++++++++------- packages/dashboard/app/components/TaskChatTab.tsx | 4 ++ .../app/components/__tests__/TaskChatTab.test.tsx | 50 ++++++++++++++++- 5 files changed, 105 insertions(+), 21 deletions(-) Fusion-Task-Id: FN-7215 Fusion-Task-Lineage: 0cec8c11-1539-4c11-96d9-ec5a7936862b Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7215-compact-task-chat-tools.md | 7 +++ docs/dashboard-guide.md | 2 +- .../dashboard/app/components/TaskChatTab.css | 63 +++++++++++++------ .../dashboard/app/components/TaskChatTab.tsx | 4 ++ .../components/__tests__/TaskChatTab.test.tsx | 50 ++++++++++++++- 5 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 .changeset/fn-7215-compact-task-chat-tools.md diff --git a/.changeset/fn-7215-compact-task-chat-tools.md b/.changeset/fn-7215-compact-task-chat-tools.md new file mode 100644 index 0000000000..2d428c074f --- /dev/null +++ b/.changeset/fn-7215-compact-task-chat-tools.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Compact collapsed tool-call summaries in task-detail chat. +category: fix +dev: Updates TaskChatTab tool-call summary styling, responsive behavior, tests, and docs. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 05169272d3..1002d0b7aa 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -1059,7 +1059,7 @@ Recommended workflow: ordinary chains stay as `Blocks N` so noise stays low, hig ### Logs → Agent Log view -The **Chat** tab sits between Definition and Logs and presents a live, chat-styled transcript of task agent output. Consecutive entries are grouped by role and labeled as Planner, Executor, Reviewer, or Merger; legacy log rows without an agent role use the neutral Agent fallback. Agent group headers and user message headers show a small muted relative timestamp (for example, “just now”, “1m ago”, or “2h ago”) based on the transcript timestamp, while agent group metadata still includes the entry count. Consecutive text/message chunks inside a role group render as one continuous markdown bubble, while consecutive tool/tool-result/tool-error rows collapse into one expandable, compact tool-call summary that stays collapsed by default; the summary counts tool invocations, lists deduped tool names with overflow, and shows an error count when failures are present, while the expanded body pairs each call with its result or error in dense entry cards. Thinking entries render in a collapsible block that starts expanded. The transcript opens at the latest output whenever the tab loads or becomes active, then follows new live output when you are already near the bottom while preserving your scroll position when you review older messages. When older task-agent history exists, scrolling to the top or selecting **Load previous messages** prepends earlier transcript entries without moving the message you were reading. When you scroll away from the bottom of a populated transcript, a sticky **Latest** button appears inside the transcript so you can jump back to the newest message and resume live follow. For non-`done` tasks, the composer sends guidance through the same steering path used by comments, including active planning/triage, `in-progress`, and `in-review` sessions, plus live CLI-agent sessions reported by the session bridge; an `in-review` Chat message or Comments-tab task comment re-engages an executor unless an open PR blocks moving the task back, and other messages are still saved as queued guidance when no session is currently live. The composer no longer renders guidance text above the entry box; active, idle, and completed task actions are communicated through the textarea placeholder while the input and Send button remain usable. On a `done` task, sending a Chat message starts a refinement task using the typed text as feedback and shows a success toast with the new task ID; the current task detail modal remains on the completed task. The task-detail Chat tab keeps the composer pinned and visible on mobile and desktop while the transcript scrolls internally; its textarea placeholder reads “Steer the currently executing agent” for steering mode and switches to refinement copy for completed tasks, with the same inline, icon-only send affordance to the right of the input at every breakpoint. In the composer, plain **Enter** sends, **Shift+Enter** inserts a newline, and **Cmd/Ctrl+Enter** remains a supported send shortcut. +The **Chat** tab sits between Definition and Logs and presents a live, chat-styled transcript of task agent output. Consecutive entries are grouped by role and labeled as Planner, Executor, Reviewer, or Merger; legacy log rows without an agent role use the neutral Agent fallback. Agent group headers and user message headers show a small muted relative timestamp (for example, “just now”, “1m ago”, or “2h ago”) based on the transcript timestamp, while agent group metadata still includes the entry count. Consecutive text/message chunks inside a role group render as one continuous markdown bubble, while consecutive tool/tool-result/tool-error rows collapse into one expandable, compact tool-call summary that stays collapsed by default and mirrors regular Chat's dense treatment; the summary stays single-line/ellipsis-friendly on desktop and mobile, counts tool invocations, lists deduped tool names with overflow, and shows an error count when failures are present, while the expanded body pairs each call with its result or error in dense entry cards. Thinking entries render in a collapsible block that starts expanded. The transcript opens at the latest output whenever the tab loads or becomes active, then follows new live output when you are already near the bottom while preserving your scroll position when you review older messages. When older task-agent history exists, scrolling to the top or selecting **Load previous messages** prepends earlier transcript entries without moving the message you were reading. When you scroll away from the bottom of a populated transcript, a sticky **Latest** button appears inside the transcript so you can jump back to the newest message and resume live follow. For non-`done` tasks, the composer sends guidance through the same steering path used by comments, including active planning/triage, `in-progress`, and `in-review` sessions, plus live CLI-agent sessions reported by the session bridge; an `in-review` Chat message or Comments-tab task comment re-engages an executor unless an open PR blocks moving the task back, and other messages are still saved as queued guidance when no session is currently live. The composer no longer renders guidance text above the entry box; active, idle, and completed task actions are communicated through the textarea placeholder while the input and Send button remain usable. On a `done` task, sending a Chat message starts a refinement task using the typed text as feedback and shows a success toast with the new task ID; the current task detail modal remains on the completed task. The task-detail Chat tab keeps the composer pinned and visible on mobile and desktop while the transcript scrolls internally; its textarea placeholder reads “Steer the currently executing agent” for steering mode and switches to refinement copy for completed tasks, with the same inline, icon-only send affordance to the right of the input at every breakpoint. In the composer, plain **Enter** sends, **Shift+Enter** inserts a newline, and **Cmd/Ctrl+Enter** remains a supported send shortcut. The **Logs** tab includes an **Agent Log** subview designed for debugging long-running and tool-heavy sessions: diff --git a/packages/dashboard/app/components/TaskChatTab.css b/packages/dashboard/app/components/TaskChatTab.css index 6036f0f41c..218b477aae 100644 --- a/packages/dashboard/app/components/TaskChatTab.css +++ b/packages/dashboard/app/components/TaskChatTab.css @@ -260,7 +260,7 @@ FN-7207 makes the right-side dock and narrow desktop detail hosts use the same r } .task-chat-tool-group { - background: var(--bg-tertiary); + background: color-mix(in srgb, var(--surface) 35%, transparent); } .task-chat-tool-group-summary, @@ -273,10 +273,18 @@ FN-7207 makes the right-side dock and narrow desktop detail hosts use the same r list-style: none; } +/* +FNXC:TaskChat 2026-06-28-00:00: +FN-7215 aligns task-detail tool-call summaries with regular Chat's compact collapsed treatment. Keep the clickable row token-sized, single-line by default, and ellipsis-friendly so completed tool history does not dominate task transcripts while details remain available through native
expansion. +*/ .task-chat-tool-group-summary { - flex-wrap: wrap; + min-width: 0; + flex-wrap: nowrap; gap: var(--space-xs); - padding: var(--space-xs) var(--space-sm); + padding: var(--space-xs); + color: var(--text-muted); + border-radius: var(--radius-sm); + font-size: calc(var(--space-md) - (var(--space-xs) + (var(--space-xs) / 4))); } .task-chat-tool-group-summary::-webkit-details-marker, @@ -298,13 +306,18 @@ FN-7207 makes the right-side dock and narrow desktop detail hosts use the same r .task-chat-tool-group-count { flex: 0 0 auto; font-weight: 600; + white-space: nowrap; } .task-chat-tool-group-names { + flex: 1 1 auto; min-width: 0; - color: var(--text-muted); - font-size: calc(var(--space-md) - (var(--space-xs) / 2)); - overflow-wrap: anywhere; + color: var(--text-dim); + font-family: var(--font-mono, monospace); + font-size: inherit; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .task-chat-tool-group-overflow { @@ -314,9 +327,11 @@ FN-7207 makes the right-side dock and narrow desktop detail hosts use the same r .task-chat-tool-group-error-count { flex: 0 0 auto; + margin-left: auto; color: var(--color-error); - font-size: calc(var(--space-md) - (var(--space-xs) / 2)); + font-size: inherit; font-weight: 600; + white-space: nowrap; } .task-chat-tool-group-entries, @@ -328,15 +343,15 @@ FN-7207 makes the right-side dock and narrow desktop detail hosts use the same r .task-chat-tool-group-entries { gap: var(--space-xs); - padding: 0 var(--space-sm) var(--space-sm); + padding: 0 var(--space-xs) var(--space-xs); } .task-chat-tool-entry { min-width: 0; - padding: var(--space-xs) var(--space-sm); - border: var(--btn-border-width) solid var(--border); - border-radius: var(--radius-md); - background: var(--surface); + padding: var(--space-xs); + border: var(--btn-border-width) solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--surface) 35%, transparent); color: var(--text); overflow-wrap: anywhere; } @@ -359,7 +374,7 @@ FN-7207 makes the right-side dock and narrow desktop detail hosts use the same r .task-chat-entry-kicker { margin-bottom: calc(var(--space-xs) / 2); color: var(--text-muted); - font-size: calc(var(--space-sm) + (var(--space-xs) / 2)); + font-size: calc(var(--space-md) - (var(--space-xs) + (var(--space-xs) / 4))); font-weight: 600; text-transform: none; letter-spacing: normal; @@ -389,7 +404,7 @@ FN-7207 makes the right-side dock and narrow desktop detail hosts use the same r .task-chat-tool-detail-label { color: var(--text-muted); - font-size: var(--space-md); + font-size: calc(var(--space-md) - (var(--space-xs) + (var(--space-xs) / 4))); font-weight: 600; } @@ -399,6 +414,7 @@ FN-7207 makes the right-side dock and narrow desktop detail hosts use the same r overflow-x: auto; white-space: pre-wrap; word-break: break-word; + font-size: calc(var(--space-md) - (var(--space-xs) + (var(--space-xs) / 4))); } .task-chat-composer { @@ -508,24 +524,33 @@ FN-6660 corrects the repeated mobile sizing misses from FN-6507, FN-6604, and FN max-width: 100%; } - .task-chat-tool-group-summary, + .task-chat-tool-group-summary { + align-items: center; + flex-direction: row; + flex-wrap: nowrap; + } + .task-chat-thinking-summary { align-items: flex-start; flex-direction: column; } - .task-chat-tool-group-names, + .task-chat-tool-group-names { + width: auto; + } + .task-chat-tool-group-error-count { - width: 100%; + width: auto; + margin-left: auto; } .task-chat-tool-group-entries, .task-chat-thinking-body { - padding-inline: var(--space-sm); + padding-inline: var(--space-xs); } .task-chat-tool-entry { - padding: var(--space-sm); + padding: var(--space-xs); } .task-chat-composer { diff --git a/packages/dashboard/app/components/TaskChatTab.tsx b/packages/dashboard/app/components/TaskChatTab.tsx index 731a6b5cb6..874480c329 100644 --- a/packages/dashboard/app/components/TaskChatTab.tsx +++ b/packages/dashboard/app/components/TaskChatTab.tsx @@ -414,6 +414,10 @@ function TaskChatToolInvocation({ row }: { row: Extract { expect(toolGroup).toHaveClass("task-chat-tool-group"); expect(summary).toHaveClass("task-chat-tool-group-summary"); expect(toolGroup).not.toHaveAttribute("open"); + expect(within(summary as HTMLElement).getByText("1 tool call")).toHaveClass("task-chat-tool-group-count"); expect(within(summary as HTMLElement).getByText("1 tool call")).toBeVisible(); + expect(within(summary as HTMLElement).getByText("bash")).toHaveClass("task-chat-tool-group-names"); expect(within(summary as HTMLElement).getByText("bash")).toBeVisible(); expect(screen.queryByText("2 tool calls")).not.toBeInTheDocument(); expect(screen.getByText("pnpm test")).not.toBeVisible(); @@ -834,8 +836,11 @@ describe("TaskChatTab", () => { expect(summary).toBeTruthy(); expect(within(summary as HTMLElement).getByText("7 tool calls")).toBeVisible(); const names = within(summary as HTMLElement).getByLabelText("Tool names"); + expect(names).toHaveClass("task-chat-tool-group-names"); expect(names).toHaveTextContent("bash, read, edit, grep, find, +1 more"); - expect(within(summary as HTMLElement).getByText(", +1 more")).toBeVisible(); + const overflow = within(summary as HTMLElement).getByText(", +1 more"); + expect(overflow).toHaveClass("task-chat-tool-group-overflow"); + expect(overflow).toBeVisible(); }); it("surfaces tool errors in the summary and paired expanded body", async () => { @@ -2550,6 +2555,49 @@ describe("TaskChatTab", () => { expect(mobileTimestampRule).toContain("white-space: normal"); }); + it("keeps task-chat tool summaries compact like regular chat on desktop and mobile", () => { + const css = readFileSync(resolve(__dirname, "../TaskChatTab.css"), "utf8"); + const chatCss = readFileSync(resolve(__dirname, "../ChatView.css"), "utf8"); + const compactCss = getCssAfter(css, "FN-7215 aligns task-detail tool-call summaries"); + const summaryRule = getCssRuleBlock(compactCss, ".task-chat-tool-group-summary"); + const namesRule = getCssRuleBlock(css, ".task-chat-tool-group-names"); + const countRule = getCssRuleBlock(css, ".task-chat-tool-group-count"); + const errorRule = getCssRuleBlock(css, ".task-chat-tool-group-error-count"); + const entriesRule = getCssRuleBlock(getCssAfter(css, ".task-chat-tool-group-entries {\n gap"), ".task-chat-tool-group-entries"); + const entryRule = getCssRuleBlock(css, ".task-chat-tool-entry"); + const detailRule = getCssRuleBlock(getCssAfter(css, ".task-chat-tool-detail {"), ".task-chat-tool-detail"); + const chatSummaryRule = getCssRuleBlock(chatCss, ".chat-tool-calls-group-summary"); + const chatNamesRule = getCssRuleBlock(chatCss, ".chat-tool-calls-names"); + const mobileCss = getCssAfter(css, "@media (max-width: 768px)"); + const mobileSummaryRule = getCssRuleBlock(mobileCss, ".task-chat-tool-group-summary"); + const mobileNamesRule = getCssRuleBlock(mobileCss, ".task-chat-tool-group-names"); + const mobileErrorRule = getCssRuleBlock(mobileCss, ".task-chat-tool-group-error-count"); + + expect(summaryRule).toContain("flex-wrap: nowrap"); + expect(summaryRule).toContain("padding: var(--space-xs)"); + expect(summaryRule).toContain("font-size: calc(var(--space-md) - (var(--space-xs) + (var(--space-xs) / 4)))"); + expect(summaryRule).toContain("border-radius: var(--radius-sm)"); + expect(summaryRule).not.toContain("px"); + expect(summaryRule).not.toContain("#"); + expect(namesRule).toContain("font-family: var(--font-mono, monospace)"); + expect(namesRule).toContain("overflow: hidden"); + expect(namesRule).toContain("text-overflow: ellipsis"); + expect(namesRule).toContain("white-space: nowrap"); + expect(countRule).toContain("white-space: nowrap"); + expect(errorRule).toContain("color: var(--color-error)"); + expect(errorRule).toContain("white-space: nowrap"); + expect(entriesRule).toContain("padding: 0 var(--space-xs) var(--space-xs)"); + expect(entryRule).toContain("border-radius: var(--radius-sm)"); + expect(detailRule).toContain("font-size: calc(var(--space-md) - (var(--space-xs) + (var(--space-xs) / 4)))"); + expect(chatSummaryRule).toContain("padding: var(--space-xs)"); + expect(chatSummaryRule).toContain("border-radius: var(--radius-sm)"); + expect(chatNamesRule).toContain("text-overflow: ellipsis"); + expect(mobileSummaryRule).toContain("flex-direction: row"); + expect(mobileSummaryRule).toContain("flex-wrap: nowrap"); + expect(mobileNamesRule).toContain("width: auto"); + expect(mobileErrorRule).toContain("width: auto"); + }); + it("keeps mobile breakpoint scaffolding for the transcript, composer, and collapsible groups", () => { const css = readFileSync(resolve(__dirname, "../TaskChatTab.css"), "utf8"); const sendRule = getCssRuleBlock(css, ".task-chat-send");