diff --git a/ui/desktop/package-lock.json b/ui/desktop/package-lock.json
index ae8fe89745..23394805a6 100644
--- a/ui/desktop/package-lock.json
+++ b/ui/desktop/package-lock.json
@@ -48,6 +48,7 @@
"react-select": "^5.10.2",
"react-syntax-highlighter": "^15.6.1",
"react-toastify": "^11.0.5",
+ "remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
"split-type": "^0.3.4",
"swr": "^2.3.6",
@@ -13417,6 +13418,20 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-newline-to-break": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
+ "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-find-and-replace": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdast-util-phrasing": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
@@ -16386,6 +16401,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/remark-breaks": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",
+ "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-newline-to-break": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-gfm": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
diff --git a/ui/desktop/package.json b/ui/desktop/package.json
index b8dd418f4f..91465afcd1 100644
--- a/ui/desktop/package.json
+++ b/ui/desktop/package.json
@@ -78,6 +78,7 @@
"react-select": "^5.10.2",
"react-syntax-highlighter": "^15.6.1",
"react-toastify": "^11.0.5",
+ "remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
"split-type": "^0.3.4",
"swr": "^2.3.6",
diff --git a/ui/desktop/src/components/MarkdownContent.test.tsx b/ui/desktop/src/components/MarkdownContent.test.tsx
index 163ecb2afd..889fb62496 100644
--- a/ui/desktop/src/components/MarkdownContent.test.tsx
+++ b/ui/desktop/src/components/MarkdownContent.test.tsx
@@ -260,4 +260,66 @@ Unclosed code block`;
});
});
});
+
+ describe('Line Break Functionality', () => {
+ it('preserves single line breaks with remark-breaks plugin', async () => {
+ const content = `First line
+Second line
+Third line`;
+
+ const { container } = render();
+
+ await waitFor(() => {
+ // Check that all text content is present (text may be split by
tags)
+ expect(container).toHaveTextContent('First line');
+ expect(container).toHaveTextContent('Second line');
+ expect(container).toHaveTextContent('Third line');
+ });
+
+ // Check that line breaks are preserved (rendered as
tags)
+ const brElements = container.querySelectorAll('br');
+ expect(brElements.length).toBeGreaterThan(0);
+ });
+
+ it('handles mixed content with line breaks', async () => {
+ const content = `# Header
+Paragraph with
+line breaks.
+
+- List item 1
+- List item 2`;
+
+ const { container } = render();
+
+ await waitFor(() => {
+ expect(screen.getByRole('heading', { level: 1, name: 'Header' })).toBeInTheDocument();
+
+ // Check that text content is present (text may be split by
tags)
+ expect(container).toHaveTextContent('Paragraph with');
+ expect(container).toHaveTextContent('line breaks.');
+ expect(screen.getByText('List item 1')).toBeInTheDocument();
+ expect(screen.getByText('List item 2')).toBeInTheDocument();
+ });
+ });
+
+ it('maintains existing markdown features with line breaks', async () => {
+ const content = `**Bold text**
+with line break
+
+\`code\` and
+more text`;
+
+ const { container } = render();
+
+ await waitFor(() => {
+ // Bold text should still work
+ const boldElement = container.querySelector('strong');
+ expect(boldElement).toBeInTheDocument();
+ expect(boldElement).toHaveTextContent('Bold text');
+
+ // Code should still work
+ expect(screen.getByText('code')).toBeInTheDocument();
+ });
+ });
+ });
});
diff --git a/ui/desktop/src/components/MarkdownContent.tsx b/ui/desktop/src/components/MarkdownContent.tsx
index 734af41716..68d05d8849 100644
--- a/ui/desktop/src/components/MarkdownContent.tsx
+++ b/ui/desktop/src/components/MarkdownContent.tsx
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
+import remarkBreaks from 'remark-breaks';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { Check, Copy } from './icons';
@@ -126,7 +127,7 @@ export default function MarkdownContent({ content, className = '' }: MarkdownCon
prose-li:m-0prose-li:font-sans ${className}`}
>
,
code: MarkdownCode,