mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix: fix 4px layout shift on streaming commit in chat (#24203)
Closes CODAGT-124 When a streaming assistant response finishes and moves from the live stream tail into the conversation timeline, the message jumps 4px upward. This happens because the outer layout wrapper and live-stream section both used `gap-3` (12px), while the committed-message list used `gap-2` (8px). Unify all three containers to `gap-2` so the gap between messages stays at 8px regardless of whether they're streaming or committed, eliminating the layout shift. A Storybook story with play-function assertions locks the invariant: it renders both committed messages and an active stream, then verifies both the outer and inner containers report `rowGap === "8px"`.
This commit is contained in:
@@ -474,6 +474,16 @@ const buildStoreWithMessages = (
|
||||
return store;
|
||||
};
|
||||
|
||||
const gapTestStore = createChatStore();
|
||||
gapTestStore.replaceMessages([
|
||||
buildMessage(1, "user", "Explain the layout."),
|
||||
buildMessage(2, "assistant", "Here is the explanation."),
|
||||
buildMessage(3, "user", "Can you elaborate?"),
|
||||
]);
|
||||
gapTestStore.applyMessageParts([
|
||||
{ type: "text", text: "Certainly, here are more details..." },
|
||||
]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Editing flow stories
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -521,6 +531,26 @@ export const EditingSaving: Story = {
|
||||
),
|
||||
};
|
||||
|
||||
export const ConsistentGapBetweenTimelineAndStream: Story = {
|
||||
render: () => <StoryAgentChatPageView store={gapTestStore} />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const wrapper = canvasElement.querySelector(
|
||||
'[data-testid="chat-timeline-wrapper"]',
|
||||
);
|
||||
expect(wrapper).not.toBeNull();
|
||||
|
||||
const outerGap = window.getComputedStyle(wrapper!).rowGap;
|
||||
expect(outerGap).toBe("8px");
|
||||
|
||||
const timeline = wrapper!.querySelector(
|
||||
'[data-testid="conversation-timeline"]',
|
||||
);
|
||||
expect(timeline).not.toBeNull();
|
||||
const innerGap = window.getComputedStyle(timeline!).rowGap;
|
||||
expect(innerGap).toBe("8px");
|
||||
},
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AgentChatPageNotFoundView stories
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1041,7 +1041,7 @@ export const ConversationTimeline = memo<ConversationTimelineProps>(
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div data-testid="conversation-timeline" className="flex flex-col gap-2">
|
||||
{parsedMessages.map(({ message, parsed }, msgIdx) => {
|
||||
if (message.role === "user") {
|
||||
return (
|
||||
|
||||
@@ -70,7 +70,7 @@ export const LiveStreamTailContent = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
{shouldRenderEmptyState && (
|
||||
<div className="py-12 text-center text-content-secondary">
|
||||
<p className="text-sm">Start a conversation with your agent.</p>
|
||||
|
||||
@@ -86,7 +86,10 @@ export const ChatPageTimeline: FC<ChatPageTimelineProps> = ({
|
||||
|
||||
return (
|
||||
<Profiler id="AgentChat" onRender={onRenderProfiler}>
|
||||
<div className="mx-auto flex w-full max-w-3xl flex-col gap-3 py-6">
|
||||
<div
|
||||
data-testid="chat-timeline-wrapper"
|
||||
className="mx-auto flex w-full max-w-3xl flex-col gap-2 py-6"
|
||||
>
|
||||
{/* VNC sessions for completed agents may already be
|
||||
terminated, so inline desktop previews are disabled
|
||||
via showDesktopPreviews={false} to avoid a perpetual
|
||||
|
||||
Reference in New Issue
Block a user