FN-6339: combine consecutive thinking entries

Render grouped task-chat thinking logs as one continuous section.

- Concatenate grouped thinking entry text before markdown rendering.
- Keep the thinking disclosure summary stable as "Thinking" instead of showing entry counts.
- Move spacing to tool groups and remove multi-thinking divider styling.
- Cover the combined thinking rendering behavior in TaskChatTab tests.

Files changed:
 packages/dashboard/app/components/TaskChatTab.css  | 10 ++++-----
 packages/dashboard/app/components/TaskChatTab.tsx  | 25 ++++++++++------------
 .../app/components/__tests__/TaskChatTab.test.tsx  | 23 +++++++++++++++++++-
 3 files changed, 37 insertions(+), 21 deletions(-)

Fusion-Task-Id: FN-6339

Fusion-Task-Lineage: 51a2ea27-a56a-4844-a27b-389d1d24613f
This commit is contained in:
gsxdsm
2026-06-13 00:07:47 -07:00
parent 0765913db9
commit 2ade5f8877
3 changed files with 37 additions and 21 deletions

View File

@@ -147,10 +147,13 @@
.task-chat-thinking-body {
display: flex;
flex-direction: column;
gap: var(--space-sm);
padding: 0 var(--space-md) var(--space-md);
}
.task-chat-tool-group-entries {
gap: var(--space-sm);
}
.task-chat-tool-entry {
min-width: 0;
padding: var(--space-sm) var(--space-md);
@@ -176,11 +179,6 @@
font-weight: 600;
}
.task-chat-thinking-markdown + .task-chat-thinking-markdown {
padding-top: var(--space-sm);
border-top: var(--btn-border-width) solid color-mix(in srgb, var(--color-warning) 24%, var(--border));
}
.task-chat-entry-kicker {
margin-bottom: var(--space-xs);
color: var(--text-muted);

View File

@@ -301,23 +301,20 @@ function TaskChatToolGroup({ entries }: { entries: AgentLogEntry[] }) {
}
function TaskChatThinking({ entries }: { entries: AgentLogEntry[] }) {
const combinedThinkingText = entries.map((entry) => entry.text).join("");
return (
<details className="task-chat-thinking" data-testid="task-chat-thinking" open>
<summary className="task-chat-thinking-summary">
{entries.length === 1 ? "Thinking" : `${entries.length} thinking entries`}
</summary>
<summary className="task-chat-thinking-summary">Thinking</summary>
<div className="task-chat-thinking-body">
{entries.map((entry, entryIndex) => (
<div
className="markdown-body task-chat-markdown task-chat-thinking-markdown"
data-testid="task-chat-entry-thinking"
key={getEntryKey(entry, entryIndex)}
>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
{entry.text}
</ReactMarkdown>
</div>
))}
<div
className="markdown-body task-chat-markdown task-chat-thinking-markdown"
data-testid="task-chat-entry-thinking"
>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
{combinedThinkingText}
</ReactMarkdown>
</div>
</div>
</details>
);

View File

@@ -377,8 +377,9 @@ describe("TaskChatTab", () => {
const thinking = screen.getByTestId("task-chat-thinking");
expect(thinking).toHaveAttribute("open");
expect(screen.getByText("Thinking")).toBeVisible();
expect(within(thinking).getByText("Thinking")).toBeVisible();
expect(screen.getByText("I am considering options")).toBeVisible();
expect(within(thinking).getAllByTestId("task-chat-entry-thinking")).toHaveLength(1);
await user.click(within(thinking).getByText("Thinking"));
@@ -386,6 +387,25 @@ describe("TaskChatTab", () => {
expect(screen.getByText("I am considering options")).not.toBeVisible();
});
it("renders consecutive thinking entries as one continuous section", () => {
mockLogs([
makeEntry({ agent: "triage", type: "thinking", text: "First" }),
makeEntry({ agent: "triage", type: "thinking", text: "Second", timestamp: "2026-06-12T00:00:01.000Z" }),
]);
render(<TaskChatTab task={makeTask()} active addToast={vi.fn()} />);
const thinking = screen.getByTestId("task-chat-thinking");
const summary = thinking.querySelector("summary");
expect(summary).toBeTruthy();
expect(within(summary as HTMLElement).getByText("Thinking")).toBeVisible();
expect(screen.queryByText("2 thinking entries")).not.toBeInTheDocument();
const thinkingBlocks = within(thinking).getAllByTestId("task-chat-entry-thinking");
expect(thinkingBlocks).toHaveLength(1);
expect(thinkingBlocks[0]).toHaveTextContent("FirstSecond");
expect(thinkingBlocks[0].nextElementSibling).toBeNull();
});
it("creates distinct tool segments when text or thinking entries are interleaved", () => {
mockLogs([
makeEntry({ agent: "executor", type: "tool", text: "first tool", detail: "first detail" }),
@@ -820,5 +840,6 @@ describe("TaskChatTab", () => {
expect(css).toContain(".task-chat-tool-group-names");
expect(css).toContain(".task-chat-tool-group-error-count");
expect(css).toContain(".task-chat-thinking-summary");
expect(css).not.toContain(".task-chat-thinking-markdown + .task-chat-thinking-markdown");
});
});