fix(site): add bottom spacing for sources-only assistant messages (#24202)

Closes CODAGT-123

Assistant messages containing only source parts (no markdown or
reasoning)
were missing the bottom spacer that normally fills the gap left by the
hidden
action bar, causing them to sit flush against the next user bubble.

The existing fallback spacer guarded on `Boolean(parsed.reasoning)`, so
it
only fired for thinking-only replies. Replace that guard with the
broader
`hasRenderableContent` flag (which covers blocks, tools, and sources)
and
extract a named `needsAssistantBottomSpacer` boolean so future content
types
inherit consistent spacing without re-reading compound conditions.

Adds a `SourcesOnlyAssistantSpacing` Storybook story mirroring the
existing
`ThinkingOnlyAssistantSpacing` pattern for regression coverage.
This commit is contained in:
Jaayden Halko
2026-04-10 19:09:23 +07:00
committed by GitHub
parent 1a3a92bd1b
commit 76d89f59af
2 changed files with 64 additions and 6 deletions

View File

@@ -939,3 +939,59 @@ export const ThinkingOnlyAssistantSpacing: Story = {
expect(canvas.getByText("Any progress?")).toBeInTheDocument();
},
};
/**
* Regression: sources-only assistant messages must have consistent
* bottom spacing before the next user bubble. A spacer div fills the
* gap that would normally come from the hidden action bar.
*/
export const SourcesOnlyAssistantSpacing: Story = {
args: {
...defaultArgs,
parsedMessages: buildMessages([
{
...baseMessage,
id: 1,
role: "user",
content: [{ type: "text", text: "Can you share your sources?" }],
},
{
...baseMessage,
id: 2,
role: "assistant",
content: [
{
type: "source",
url: "https://example.com/docs",
title: "Documentation",
},
{
type: "source",
url: "https://example.com/api",
title: "API Reference",
},
],
},
{
...baseMessage,
id: 3,
role: "user",
content: [{ type: "text", text: "Thanks!" }],
},
]),
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByText("Can you share your sources?")).toBeInTheDocument();
expect(canvas.getByText("Thanks!")).toBeInTheDocument();
await userEvent.click(
canvas.getByRole("button", { name: /searched 2 results/i }),
);
expect(
canvas.getByRole("link", { name: "Documentation" }),
).toBeInTheDocument();
expect(
canvas.getByRole("link", { name: "API Reference" }),
).toBeInTheDocument();
},
};

View File

@@ -516,6 +516,11 @@ const ChatMessageItem = memo<{
userInlineContent.length > 0 || Boolean(parsed.markdown?.trim());
const hasFileBlocks = userFileBlocks.length > 0;
const hasCopyableContent = Boolean(parsed.markdown.trim());
const needsAssistantBottomSpacer =
!hideActions &&
!isUser &&
!hasCopyableContent &&
(Boolean(parsed.reasoning) || parsed.sources.length > 0);
const conversationItemProps: { role: "user" | "assistant" } = {
role: isUser ? "user" : "assistant",
@@ -670,12 +675,9 @@ const ChatMessageItem = memo<{
</div>
)}
{/* Spacer for assistant messages without an action bar
(e.g. thinking-only) so they have consistent bottom
padding before the next user bubble. */}
{!hideActions &&
!isUser &&
!hasCopyableContent &&
Boolean(parsed.reasoning) && <div className="min-h-6" />}
(e.g. reasoning-only or sources-only) so they have
consistent bottom padding before the next user bubble. */}
{needsAssistantBottomSpacer && <div className="min-h-6" />}
{previewImage && (
<ImageLightbox
src={previewImage}