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 ReactMarkdown from "react-markdown";
|
||||
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 {
|
||||
provider?: string;
|
||||
@@ -78,12 +107,14 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
||||
fontSize: "13px",
|
||||
lineHeight: "1.5",
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
maxHeight: "500px",
|
||||
padding: "12px",
|
||||
background: "var(--bg-secondary)",
|
||||
borderRadius: "6px",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
{/* Model info header */}
|
||||
@@ -91,6 +122,7 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
||||
className="agent-log-model-header"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "16px",
|
||||
padding: "8px 12px",
|
||||
marginBottom: "12px",
|
||||
@@ -98,6 +130,8 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
||||
borderRadius: "4px",
|
||||
fontSize: "12px",
|
||||
color: "var(--text-muted, #888)",
|
||||
overflow: "hidden",
|
||||
minWidth: 0,
|
||||
}}
|
||||
data-testid="agent-log-model-header"
|
||||
>
|
||||
@@ -193,7 +227,7 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
||||
}}
|
||||
>
|
||||
{agentBadge}
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
{entry.text}
|
||||
</ReactMarkdown>
|
||||
</span>
|
||||
@@ -264,7 +298,7 @@ export function AgentLogViewer({ entries, loading, executorModel, validatorModel
|
||||
return (
|
||||
<span key={i} className="agent-log-text">
|
||||
{agentBadge}
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
{entry.text}
|
||||
</ReactMarkdown>
|
||||
</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", () => {
|
||||
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} />);
|
||||
|
||||
Reference in New Issue
Block a user