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:
@@ -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();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user