From b438347d2d582f354bd57f6d97f5e10d62c5df31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Tue, 12 May 2026 13:15:54 +0800 Subject: [PATCH] Prevent long chat URLs from breaking message layout Long uninterrupted URLs can otherwise force user message bubbles beyond the chat column. The fix keeps the bubble constrained to its parent and allows safe breaks inside continuous URL text, with the same guard applied to markdown paragraphs and links. Constraint: Issue #378 reports desktop chat overflow when a conversation contains a long URL Rejected: Add horizontal scrolling to user messages | this preserves the overflow instead of fixing normal chat readability Confidence: high Scope-risk: narrow Tested: cd desktop && bunx vitest run src/components/chat/UserMessage.test.tsx src/components/chat/MessageList.test.tsx Tested: cd desktop && bun run lint Tested: cd desktop && bun run build Tested: bun run check:desktop Tested: browser layout fixture PASS shell=619 bubbleClient=619 bubbleScroll=619 Not-tested: full root quality gate Related: https://github.com/NanmiCoder/cc-haha/issues/378 --- .../src/components/chat/UserMessage.test.tsx | 21 +++++++++++++++++++ desktop/src/components/chat/UserMessage.tsx | 8 +++++-- .../components/markdown/MarkdownRenderer.tsx | 6 +++--- 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 desktop/src/components/chat/UserMessage.test.tsx diff --git a/desktop/src/components/chat/UserMessage.test.tsx b/desktop/src/components/chat/UserMessage.test.tsx new file mode 100644 index 00000000..efb519e9 --- /dev/null +++ b/desktop/src/components/chat/UserMessage.test.tsx @@ -0,0 +1,21 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import { UserMessage } from './UserMessage' + +describe('UserMessage', () => { + it('keeps long URLs inside the message bubble', () => { + const longUrl = `https://cn.bing.com/search?q=${'encoded'.repeat(60)}` + + const { container } = render() + + const shell = container.querySelector('[data-message-shell="user"]') + const bubble = screen.getByText(longUrl) + + expect(shell?.className).toContain('min-w-0') + expect(bubble.className).toContain('min-w-0') + expect(bubble.className).toContain('max-w-full') + expect(bubble.className).toContain('whitespace-pre-wrap') + expect(bubble.style.overflowWrap).toBe('anywhere') + expect(bubble.style.wordBreak).toBe('break-word') + }) +}) diff --git a/desktop/src/components/chat/UserMessage.tsx b/desktop/src/components/chat/UserMessage.tsx index 9162384e..d66dadea 100644 --- a/desktop/src/components/chat/UserMessage.tsx +++ b/desktop/src/components/chat/UserMessage.tsx @@ -22,8 +22,12 @@ export function UserMessage({ content, attachments }: Props) { {hasText && (
{content}
diff --git a/desktop/src/components/markdown/MarkdownRenderer.tsx b/desktop/src/components/markdown/MarkdownRenderer.tsx index d9d0f236..f710de05 100644 --- a/desktop/src/components/markdown/MarkdownRenderer.tsx +++ b/desktop/src/components/markdown/MarkdownRenderer.tsx @@ -105,13 +105,13 @@ function parseMarkdown(content: string): { html: string; codeBlocks: CodeBlock[] return { html, codeBlocks } } -const BASE_PROSE_CLASSES = `markdown-prose prose prose-sm max-w-none text-[var(--color-text-primary)] +const BASE_PROSE_CLASSES = `markdown-prose prose prose-sm min-w-0 max-w-none break-words [overflow-wrap:anywhere] text-[var(--color-text-primary)] prose-headings:text-[var(--color-text-primary)] prose-headings:font-semibold prose-p:my-2 prose-p:leading-relaxed - prose-p:break-words + prose-p:break-words prose-p:[overflow-wrap:anywhere] prose-code:text-[13px] prose-code:text-[var(--color-code-fg)] prose-code:font-[var(--font-mono)] prose-code:bg-[var(--color-code-bg)] prose-code:border prose-code:border-[var(--color-border)] prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:before:hidden prose-code:after:hidden prose-pre:!bg-transparent prose-pre:!p-0 prose-pre:!shadow-none - prose-a:text-[var(--color-text-accent)] prose-a:no-underline hover:prose-a:underline + prose-a:text-[var(--color-text-accent)] prose-a:no-underline prose-a:[overflow-wrap:anywhere] hover:prose-a:underline prose-strong:text-[var(--color-text-primary)] prose-ul:my-2 prose-ol:my-2 prose-li:my-0.5