fix(FN-737): prevent horizontal scrolling in agent log panel
- Add overflow-x: hidden and word-break constraints to AgentLogViewer - Apply overflow-hidden to pre/code blocks and ANSI span containers - Add unit tests verifying overflow constraint styles on log elements
This commit is contained in:
@@ -3,6 +3,35 @@ import { ProviderIcon } from "./ProviderIcon";
|
|||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
import type { Components } from "react-markdown";
|
||||||
|
|
||||||
|
const markdownComponents: Components = {
|
||||||
|
pre: ({ children, ...props }) => (
|
||||||
|
<pre
|
||||||
|
{...props}
|
||||||
|
style={{
|
||||||
|
overflowX: "auto",
|
||||||
|
maxWidth: "100%",
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
wordBreak: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</pre>
|
||||||
|
),
|
||||||
|
table: ({ children, ...props }) => (
|
||||||
|
<table
|
||||||
|
{...props}
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
overflowX: "auto",
|
||||||
|
maxWidth: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</table>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
interface ModelInfo {
|
interface ModelInfo {
|
||||||
provider?: string;
|
provider?: string;
|
||||||
@@ -78,12 +107,14 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
|||||||
fontSize: "13px",
|
fontSize: "13px",
|
||||||
lineHeight: "1.5",
|
lineHeight: "1.5",
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
|
overflowX: "hidden",
|
||||||
maxHeight: "500px",
|
maxHeight: "500px",
|
||||||
padding: "12px",
|
padding: "12px",
|
||||||
background: "var(--bg-secondary)",
|
background: "var(--bg-secondary)",
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
whiteSpace: "pre-wrap",
|
whiteSpace: "pre-wrap",
|
||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
|
overflowWrap: "break-word",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Model info header */}
|
{/* Model info header */}
|
||||||
@@ -91,6 +122,7 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
|||||||
className="agent-log-model-header"
|
className="agent-log-model-header"
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
gap: "16px",
|
gap: "16px",
|
||||||
padding: "8px 12px",
|
padding: "8px 12px",
|
||||||
marginBottom: "12px",
|
marginBottom: "12px",
|
||||||
@@ -98,6 +130,8 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
|||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
color: "var(--text-muted, #888)",
|
color: "var(--text-muted, #888)",
|
||||||
|
overflow: "hidden",
|
||||||
|
minWidth: 0,
|
||||||
}}
|
}}
|
||||||
data-testid="agent-log-model-header"
|
data-testid="agent-log-model-header"
|
||||||
>
|
>
|
||||||
@@ -193,7 +227,7 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{agentBadge}
|
{agentBadge}
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||||
{entry.text}
|
{entry.text}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</span>
|
</span>
|
||||||
@@ -264,7 +298,7 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
|||||||
return (
|
return (
|
||||||
<span key={i} className="agent-log-text">
|
<span key={i} className="agent-log-text">
|
||||||
{agentBadge}
|
{agentBadge}
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||||
{entry.text}
|
{entry.text}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -331,6 +331,40 @@ describe("AgentLogViewer", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("horizontal overflow prevention", () => {
|
||||||
|
it("sets overflowX hidden on the viewer container", () => {
|
||||||
|
const longString = "A".repeat(300);
|
||||||
|
const entries = [makeEntry({ text: longString })];
|
||||||
|
const { container } = render(<AgentLogViewer entries={entries} loading={false} />);
|
||||||
|
const viewer = container.querySelector("[data-testid='agent-log-viewer']") as HTMLElement;
|
||||||
|
expect(viewer.style.overflowX).toBe("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets overflow-wrap break-word on the viewer container", () => {
|
||||||
|
const entries = [makeEntry({ text: "x".repeat(250) })];
|
||||||
|
const { container } = render(<AgentLogViewer entries={entries} loading={false} />);
|
||||||
|
const viewer = container.querySelector("[data-testid='agent-log-viewer']") as HTMLElement;
|
||||||
|
expect(viewer.style.overflowWrap).toBe("break-word");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders pre elements with overflow-x auto for internal scrolling", () => {
|
||||||
|
const longLine = "const x = " + "'a'.repeat(500)";
|
||||||
|
const entries = [makeEntry({ text: "```\n" + longLine + "\n```" })];
|
||||||
|
const { container } = render(<AgentLogViewer entries={entries} loading={false} />);
|
||||||
|
const pre = container.querySelector("pre") as HTMLElement;
|
||||||
|
expect(pre).toBeTruthy();
|
||||||
|
expect(pre.style.overflowX).toBe("auto");
|
||||||
|
expect(pre.style.maxWidth).toBe("100%");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("wraps model header on narrow widths via flex-wrap", () => {
|
||||||
|
const entries = [makeEntry()];
|
||||||
|
const { container } = render(<AgentLogViewer entries={entries} loading={false} />);
|
||||||
|
const header = container.querySelector("[data-testid='agent-log-model-header']") as HTMLElement;
|
||||||
|
expect(header.style.flexWrap).toBe("wrap");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("auto-scroll behavior", () => {
|
describe("auto-scroll behavior", () => {
|
||||||
it("scrolls to top when new entries arrive and user is near the top", () => {
|
it("scrolls to top when new entries arrive and user is near the top", () => {
|
||||||
const { rerender, container } = render(<AgentLogViewer entries={[makeEntry({ text: "first" })]} loading={false} />);
|
const { rerender, container } = render(<AgentLogViewer entries={[makeEntry({ text: "first" })]} loading={false} />);
|
||||||
|
|||||||
Reference in New Issue
Block a user