diff --git a/frontend/src/components/Chat/MessageList.styles.ts b/frontend/src/components/Chat/MessageList.styles.ts
index 278b7732c9..6d2c44480d 100644
--- a/frontend/src/components/Chat/MessageList.styles.ts
+++ b/frontend/src/components/Chat/MessageList.styles.ts
@@ -3,6 +3,7 @@ import { makeStyles, tokens } from '@fluentui/react-components'
export const useMessageListStyles = makeStyles({
root: {
flex: 1,
+ minWidth: 0,
overflowY: 'auto',
padding: tokens.spacingVerticalXXL,
display: 'flex',
@@ -12,7 +13,8 @@ export const useMessageListStyles = makeStyles({
message: {
display: 'flex',
gap: tokens.spacingHorizontalM,
- maxWidth: '800px',
+ minWidth: 0,
+ maxWidth: 'min(800px, 100%)',
alignSelf: 'flex-start',
},
userMessage: {
@@ -25,6 +27,8 @@ export const useMessageListStyles = makeStyles({
padding: tokens.spacingVerticalM,
borderRadius: tokens.borderRadiusMedium,
flex: 1,
+ minWidth: 0,
+ maxWidth: '100%',
},
userMessageContent: {
backgroundColor: tokens.colorBrandBackground2,
@@ -80,13 +84,20 @@ export const useMessageListStyles = makeStyles({
flexWrap: 'wrap',
gap: tokens.spacingHorizontalS,
marginTop: tokens.spacingVerticalS,
+ minWidth: 0,
+ maxWidth: '100%',
+ },
+ attachmentItem: {
+ minWidth: 0,
+ maxWidth: '100%',
},
imageContainer: {
position: 'relative' as const,
display: 'inline-block',
- maxWidth: '400px',
+ width: 'fit-content',
+ maxWidth: 'min(400px, 100%)',
maxHeight: '400px',
- minWidth: '100px',
+ minWidth: 'min(100px, 100%)',
minHeight: '60px',
},
imageSpinner: {
@@ -96,14 +107,17 @@ export const useMessageListStyles = makeStyles({
transform: 'translate(-50%, -50%)',
},
attachmentPreview: {
- maxWidth: '400px',
+ display: 'block',
+ width: 'auto',
+ height: 'auto',
+ maxWidth: '100%',
maxHeight: '400px',
borderRadius: tokens.borderRadiusMedium,
objectFit: 'contain',
border: `1px solid ${tokens.colorNeutralStroke1}`,
},
attachmentPreviewHidden: {
- maxWidth: '400px',
+ maxWidth: '100%',
maxHeight: '400px',
borderRadius: tokens.borderRadiusMedium,
objectFit: 'contain',
@@ -113,11 +127,18 @@ export const useMessageListStyles = makeStyles({
height: 0,
},
videoPreview: {
- maxWidth: '400px',
+ display: 'block',
+ width: 'auto',
+ height: 'auto',
+ maxWidth: 'min(400px, 100%)',
maxHeight: '300px',
borderRadius: tokens.borderRadiusMedium,
border: `1px solid ${tokens.colorNeutralStroke1}`,
},
+ audioPreview: {
+ width: '300px',
+ maxWidth: '100%',
+ },
attachmentFile: {
display: 'flex',
alignItems: 'center',
@@ -126,6 +147,8 @@ export const useMessageListStyles = makeStyles({
backgroundColor: tokens.colorNeutralBackground1,
borderRadius: tokens.borderRadiusMedium,
border: `1px solid ${tokens.colorNeutralStroke1}`,
+ minWidth: 0,
+ maxWidth: '100%',
},
attachmentFileName: {
flex: 1,
diff --git a/frontend/src/components/Chat/MessageList.tsx b/frontend/src/components/Chat/MessageList.tsx
index f0e0a7d49e..64a2d4caa4 100644
--- a/frontend/src/components/Chat/MessageList.tsx
+++ b/frontend/src/components/Chat/MessageList.tsx
@@ -78,7 +78,7 @@ function MediaWithFallback({ type, src, className }: { type: 'video' | 'audio';
if (type === 'video') {
return
}
- return
+ return
}
/**
@@ -152,7 +152,7 @@ export default function MessageList({ messages, onCopyToInput, onCopyToNewConver
}
return (
-
+
{messages.map((message, index) => {
if (message.role === 'system') return null
const isUser = message.role === 'user'
@@ -209,10 +209,10 @@ export default function MessageList({ messages, onCopyToInput, onCopyToNewConver
{message.originalAttachments && message.originalAttachments.length > 0 && (
{message.originalAttachments.map((att, i) => (
-
+
{att.type === 'image' &&
}
{att.type === 'video' &&
}
- {att.type === 'audio' &&
}
+ {att.type === 'audio' &&
}
{att.type === 'file' &&
📄 {att.name}
}
))}
@@ -263,7 +263,7 @@ export default function MessageList({ messages, onCopyToInput, onCopyToNewConver
{message.attachments && message.attachments.length > 0 && (
{message.attachments.map((att, attIndex) => (
-
+
{att.type === 'image' && (
)}
{att.type === 'audio' && (
-
+
)}
{att.type === 'file' && (
From a653206e6d9a1cd6ce3b014aedcfc68a5ae1e8f9 Mon Sep 17 00:00:00 2001
From: Copilot <223556219+Copilot@users.noreply.github.com>
Date: Sat, 18 Jul 2026 21:39:54 -0700
Subject: [PATCH 2/2] FIX: Stabilize responsive image E2E assertion
Retry layout assertions while the image transitions from its loading state.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
frontend/e2e/chat.spec.ts | 104 ++++++++++++++++++++------------------
1 file changed, 56 insertions(+), 48 deletions(-)
diff --git a/frontend/e2e/chat.spec.ts b/frontend/e2e/chat.spec.ts
index 4140e971f3..3a13fdb8a0 100644
--- a/frontend/e2e/chat.spec.ts
+++ b/frontend/e2e/chat.spec.ts
@@ -516,57 +516,65 @@ test.describe("Multi-modal: Image response", () => {
const actions = bubble.locator('[data-testid^="message-actions-"]');
await expect(bubble).toBeVisible();
await expect(actions).toBeVisible();
- const layoutBounds = await img.evaluate((image) => {
- const bubbleElement = image.closest('[data-testid^="message-bubble-"]');
- const actionsElement = bubbleElement?.querySelector('[data-testid^="message-actions-"]');
- if (!bubbleElement || !actionsElement) {
- return null;
- }
-
- const imageRect = image.getBoundingClientRect();
- const bubbleRect = bubbleElement.getBoundingClientRect();
- const actionsRect = actionsElement.getBoundingClientRect();
- return {
- image: {
- x: imageRect.x,
- width: imageRect.width,
- height: imageRect.height,
- },
- bubble: {
- x: bubbleRect.x,
- width: bubbleRect.width,
- },
- actions: {
- x: actionsRect.x,
- width: actionsRect.width,
- },
- };
- });
- if (!layoutBounds) {
- throw new Error("Expected image message layout bounds");
- }
- const { image: imageBounds, bubble: bubbleBounds, actions: actionBounds } = layoutBounds;
-
- expect(imageBounds.x).toBeGreaterThanOrEqual(bubbleBounds.x);
- expect(imageBounds.x + imageBounds.width).toBeLessThanOrEqual(
- bubbleBounds.x + bubbleBounds.width + 1,
- );
- expect(Math.abs(imageBounds.width / imageBounds.height - 4 / 3)).toBeLessThan(0.02);
- expect(actionBounds.x + actionBounds.width).toBeLessThanOrEqual(
- bubbleBounds.x + bubbleBounds.width + 1,
- );
+ await expect(async () => {
+ const layoutBounds = await img.evaluate((image) => {
+ const bubbleElement = image.closest('[data-testid^="message-bubble-"]');
+ const actionsElement = bubbleElement?.querySelector('[data-testid^="message-actions-"]');
+ if (!bubbleElement || !actionsElement) {
+ return null;
+ }
- const hasHorizontalOverflow = await page.getByTestId("message-list").evaluate(
- (element) => element.scrollWidth > element.clientWidth + 1,
- );
- expect(hasHorizontalOverflow).toBe(false);
+ const imageRect = image.getBoundingClientRect();
+ const bubbleRect = bubbleElement.getBoundingClientRect();
+ const actionsRect = actionsElement.getBoundingClientRect();
+ return {
+ image: {
+ x: imageRect.x,
+ width: imageRect.width,
+ height: imageRect.height,
+ },
+ bubble: {
+ x: bubbleRect.x,
+ width: bubbleRect.width,
+ },
+ actions: {
+ x: actionsRect.x,
+ width: actionsRect.width,
+ },
+ };
+ });
+ if (!layoutBounds) {
+ throw new Error("Expected image message layout bounds");
+ }
+ const { image: imageBounds, bubble: bubbleBounds, actions: actionBounds } = layoutBounds;
+
+ expect(imageBounds.width).toBeGreaterThan(0);
+ expect(imageBounds.height).toBeGreaterThan(0);
+ expect(imageBounds.x).toBeGreaterThanOrEqual(bubbleBounds.x);
+ expect(imageBounds.x + imageBounds.width).toBeLessThanOrEqual(
+ bubbleBounds.x + bubbleBounds.width + 1,
+ );
+ expect(Math.abs(imageBounds.width / imageBounds.height - 4 / 3)).toBeLessThan(0.02);
+ expect(actionBounds.x + actionBounds.width).toBeLessThanOrEqual(
+ bubbleBounds.x + bubbleBounds.width + 1,
+ );
+
+ const hasHorizontalOverflow = await page.getByTestId("message-list").evaluate(
+ (element) => element.scrollWidth > element.clientWidth + 1,
+ );
+ expect(hasHorizontalOverflow).toBe(false);
+ }).toPass({ timeout: 10000 });
await page.setViewportSize({ width: 1024, height: 768 });
- const desktopImageBounds = await img.boundingBox();
- if (!desktopImageBounds) {
- throw new Error("Expected desktop image layout bounds");
- }
- expect(desktopImageBounds.width).toBeLessThanOrEqual(400);
+ await expect(async () => {
+ const desktopImageBounds = await img.boundingBox();
+ if (!desktopImageBounds) {
+ throw new Error("Expected desktop image layout bounds");
+ }
+ expect(desktopImageBounds.width).toBeGreaterThan(0);
+ expect(desktopImageBounds.height).toBeGreaterThan(0);
+ expect(desktopImageBounds.width).toBeLessThanOrEqual(400);
+ }).toPass({ timeout: 10000 });
});
});