diff --git a/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.test.tsx b/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.test.tsx index 55c78e53..a718f797 100644 --- a/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.test.tsx +++ b/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.test.tsx @@ -31,4 +31,27 @@ describe("EmbeddableScrollArea", () => { ); expect(ref.current).toBeInstanceOf(HTMLElement); }); + + it.each([false, true])( + "constrains the content width when asked (embedded=%s)", + (embedded) => { + const ref = createRef(); + renderWithMantine( + +
constrained content
+
, + ); + expect(screen.getByText("constrained content")).toBeInTheDocument(); + // The Mantine ScrollArea `content` slot has its default + // `min-width: max-content` relaxed to 0 so a long non-wrapping row + // (e.g. a network URL) can't stretch the list past its viewport (#1623). + const content = document.querySelector(".mantine-ScrollArea-content"); + expect(content).not.toBeNull(); + expect((content as HTMLElement).style.minWidth).toBe("0"); + }, + ); }); diff --git a/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.tsx b/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.tsx index 476b2db5..814bc7f7 100644 --- a/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.tsx +++ b/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.tsx @@ -17,8 +17,23 @@ export interface EmbeddableScrollAreaProps { embedded: boolean; viewportRef: Ref; children: ReactNode; + /** + * Constrain the scrolled content to the viewport width instead of letting it + * grow to its own `max-content`. Mantine's ScrollArea `content` slot defaults + * to `min-width: max-content`, so a row with non-wrapping content (e.g. a long + * network URL) stretches every card past the column and it bleeds out (#1623). + * When true, the content can shrink to the viewport and each row must manage + * its own overflow (the Network URL scrolls inside its own inner ScrollArea). + * Left off for panels whose rows already wrap/truncate (Logs, Protocol), where + * the default lets a long line scroll the list horizontally instead. + */ + constrainContentWidth?: boolean; } +// Relax the `content` slot's default `min-width: max-content` so the list can't +// grow wider than its viewport; see `constrainContentWidth`. +const CONSTRAIN_CONTENT_STYLES = { content: { minWidth: 0 } } as const; + /** * The scroll region shared by the Logs / Protocol / Network stream panels, which * render both full-size (their own tab) and embedded (the monitoring column). @@ -28,7 +43,9 @@ export function EmbeddableScrollArea({ embedded, viewportRef, children, + constrainContentWidth = false, }: EmbeddableScrollAreaProps) { + const styles = constrainContentWidth ? CONSTRAIN_CONTENT_STYLES : undefined; if (embedded) { return ( @@ -37,6 +54,7 @@ export function EmbeddableScrollArea({ mah="100%" type="scroll" offsetScrollbars + styles={styles} > {children} @@ -49,6 +67,7 @@ export function EmbeddableScrollArea({ mah={FULLSIZE_MAH} type="scroll" offsetScrollbars + styles={styles} > {children} diff --git a/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.stories.tsx b/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.stories.tsx index 56f88610..a4248d1e 100644 --- a/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.stories.tsx +++ b/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.stories.tsx @@ -57,3 +57,39 @@ export const WithEntries: Story = { export const Empty: Story = { args: { entries: [] }, }; + +const longUrlSample: FetchRequestEntry[] = [ + { + id: "n-long-1", + timestamp: new Date("2026-03-17T10:00:00Z"), + method: "GET", + url: "https://example-server.modelcontextprotocol.io/.well-known/oauth-protected-resource/mcp", + requestHeaders: {}, + responseStatus: 404, + duration: 84, + category: "auth", + }, + { + id: "n-long-2", + timestamp: new Date("2026-03-17T10:00:05Z"), + method: "GET", + url: "https://example-server.modelcontextprotocol.io/.well-known/oauth-authorization-server", + requestHeaders: {}, + responseStatus: 200, + duration: 79, + category: "auth", + }, +]; + +// Reproduces the pinned-column overflow: a long URL must scroll horizontally +// inside its own area rather than widening the card past the column (#1623). +export const EmbeddedLongUrls: Story = { + args: { entries: longUrlSample, embedded: true }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; diff --git a/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.tsx b/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.tsx index 509f5a98..9aa7f6e8 100644 --- a/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.tsx +++ b/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.tsx @@ -140,7 +140,11 @@ export function NetworkStreamPanel({ {!hasResults ? ( No network requests ) : ( - + {filteredEntries.map((entry) => (