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:
gsxdsm
2026-04-02 19:33:12 -07:00
parent bd2431c8e1
commit 24d04ca63a
2 changed files with 70 additions and 2 deletions

View File

@@ -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>

View File

@@ -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} />);