fix(webui): compact spacing between auxiliary trace rows

Thinking and Used tools are both auxiliary trace rows, but the thread list
was applying the same large gap used between full chat turns. That made
alternating Thinking / Used tools sequences look uneven and too airy.

Move row spacing from a fixed flex gap to per-row margins: full chat turns
keep mt-5, while consecutive auxiliary rows use mt-2. Add coverage for
Thinking -> Used tools -> Thinking spacing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Xubin Ren 2026-05-13 08:05:34 +00:00
parent c7ec5d3b75
commit 82ba63e148
2 changed files with 77 additions and 4 deletions

View File

@ -1,4 +1,5 @@
import { MessageBubble } from "@/components/MessageBubble";
import { cn } from "@/lib/utils";
import type { UIMessage } from "@/lib/types";
interface ThreadMessagesProps {
@ -7,10 +8,30 @@ interface ThreadMessagesProps {
export function ThreadMessages({ messages }: ThreadMessagesProps) {
return (
<div className="flex w-full flex-col gap-5">
{messages.map((message) => (
<MessageBubble key={message.id} message={message} />
))}
<div className="flex w-full flex-col">
{messages.map((message, index) => {
const prev = messages[index - 1];
const compact = isAuxiliaryRow(message) && prev && isAuxiliaryRow(prev);
return (
<div
key={message.id}
className={cn(index > 0 && (compact ? "mt-2" : "mt-5"))}
>
<MessageBubble message={message} />
</div>
);
})}
</div>
);
}
function isAuxiliaryRow(message: UIMessage): boolean {
return (
message.kind === "trace"
|| (
message.role === "assistant"
&& message.content.trim().length === 0
&& (!!message.reasoning || !!message.reasoningStreaming)
)
);
}

View File

@ -0,0 +1,52 @@
import { render } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { ThreadMessages } from "@/components/thread/ThreadMessages";
import type { UIMessage } from "@/lib/types";
describe("ThreadMessages", () => {
it("uses compact spacing between consecutive auxiliary rows", () => {
const messages: UIMessage[] = [
{
id: "r1",
role: "assistant",
content: "",
reasoning: "thinking",
reasoningStreaming: false,
isStreaming: true,
createdAt: Date.now(),
},
{
id: "t1",
role: "tool",
kind: "trace",
content: "search()",
traces: ["search()"],
createdAt: Date.now(),
},
{
id: "r2",
role: "assistant",
content: "",
reasoning: "more thinking",
reasoningStreaming: false,
isStreaming: true,
createdAt: Date.now(),
},
{
id: "a1",
role: "assistant",
content: "final answer",
createdAt: Date.now(),
},
];
const { container } = render(<ThreadMessages messages={messages} />);
const rows = Array.from(container.firstElementChild?.children ?? []);
expect(rows[0]).not.toHaveClass("mt-2", "mt-5");
expect(rows[1]).toHaveClass("mt-2");
expect(rows[2]).toHaveClass("mt-2");
expect(rows[3]).toHaveClass("mt-5");
});
});