fix(web): wrap long lines in markdown chat messages (#469)

This commit is contained in:
Carlos Florêncio
2026-03-25 23:00:15 +00:00
committed by GitHub
parent 116895b4ff
commit 1b0df388ef
5 changed files with 123 additions and 4 deletions
+8 -1
View File
@@ -600,6 +600,7 @@
font-size: 13px;
line-height: 1.625;
color: var(--foreground);
overflow-wrap: anywhere;
font-family:
-apple-system, "system-ui", "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji";
@@ -637,6 +638,11 @@
line-height: 1.625;
}
.markdown-body pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.markdown-body ul {
list-style-type: disc;
padding-left: 1.25rem;
@@ -1345,7 +1351,8 @@
.shiki-code-block pre.shiki {
margin: 0;
padding: 8px 12px;
overflow-x: auto;
white-space: pre-wrap;
overflow-wrap: anywhere;
font-size: var(--font-mono-size);
line-height: 1.5;
background-color: var(--background) !important;
@@ -40,7 +40,7 @@ export function CodeMirrorCodeBlock({ children, className }: CodeBlockProps) {
};
return (
<div className="relative group/code-block my-4 w-fit max-w-full min-w-[50%]">
<div className="relative group/code-block my-4 max-w-full">
{/* Copy button */}
<button
onClick={handleCopy}
@@ -28,7 +28,7 @@ export function MonacoCodeBlock({ children, className }: MonacoCodeBlockProps) {
const height = Math.min(Math.max(lineCount * 16 + 8, 32), 500);
return (
<div className="relative group/code-block my-4 w-fit max-w-full min-w-[50%]">
<div className="relative group/code-block my-4 max-w-full">
<button
onClick={() => copy(code)}
className={cn(
@@ -31,7 +31,7 @@ export function ShikiCodeBlock({ children, className }: ShikiCodeBlockProps) {
}, [code, lang, isDark]);
return (
<div className="relative group/code-block my-4 w-fit max-w-full min-w-[50%]">
<div className="relative group/code-block my-4 max-w-full">
<button
onClick={() => copy(code)}
className={cn(
+112
View File
@@ -0,0 +1,112 @@
import { test, expect } from "../fixtures/test-base";
import { KanbanPage } from "../pages/kanban-page";
import { SessionPage } from "../pages/session-page";
const LONG_WORD = "abcdefghij".repeat(30); // 300 chars, no spaces
/** Create a task with a mock agent message containing the given text. */
async function createTaskWithMessage(
testPage: import("@playwright/test").Page,
apiClient: import("../helpers/api-client").ApiClient,
seedData: import("../fixtures/test-base").SeedData,
title: string,
messageText: string,
): Promise<SessionPage> {
await apiClient.createTaskWithAgent(seedData.workspaceId, title, seedData.agentProfileId, {
description: `e2e:message("${messageText}")`,
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
});
const kanban = new KanbanPage(testPage);
await kanban.goto();
const card = kanban.taskCardByTitle(title);
await expect(card).toBeVisible({ timeout: 30_000 });
await card.click();
await expect(testPage).toHaveURL(/\/s\//, { timeout: 15_000 });
const session = new SessionPage(testPage);
await session.waitForLoad();
return session;
}
/** Assert no .markdown-body element overflows horizontally. */
async function expectNoMarkdownOverflow(testPage: import("@playwright/test").Page) {
const overflows = await testPage.evaluate(() => {
const els = document.querySelectorAll(".markdown-body");
return Array.from(els).some((el) => el.scrollWidth > el.clientWidth + 1);
});
expect(overflows).toBe(false);
}
test.describe("Markdown text wrapping", () => {
test("long plain text wraps within the chat message container", async ({
testPage,
apiClient,
seedData,
}) => {
test.setTimeout(90_000);
const session = await createTaskWithMessage(
testPage,
apiClient,
seedData,
"Wrap Plain Text",
`Here is a finding: ${LONG_WORD} - end of finding`,
);
await expect(session.chat.getByText("end of finding").last()).toBeVisible({
timeout: 30_000,
});
await expectNoMarkdownOverflow(testPage);
});
test("long inline code wraps within the chat message container", async ({
testPage,
apiClient,
seedData,
}) => {
test.setTimeout(90_000);
const session = await createTaskWithMessage(
testPage,
apiClient,
seedData,
"Wrap Inline Code",
`Check this path: \`${LONG_WORD}\` for details`,
);
await expect(session.chat.getByText("for details").last()).toBeVisible({
timeout: 30_000,
});
await expectNoMarkdownOverflow(testPage);
});
test("long lines in code blocks do not overflow the message container", async ({
testPage,
apiClient,
seedData,
}) => {
test.setTimeout(90_000);
// Fenced code block with a long line — should be contained via
// horizontal scroll (shiki) or line wrapping (codemirror)
const session = await createTaskWithMessage(
testPage,
apiClient,
seedData,
"Wrap Code Block",
`Code block:\\n\`\`\`\\nconst x = "${LONG_WORD}";\\n\`\`\`\\nAfter code block`,
);
await expect(session.chat.getByText("After code block").last()).toBeVisible({
timeout: 30_000,
});
await expectNoMarkdownOverflow(testPage);
});
});