diff --git a/apps/x/apps/renderer/package.json b/apps/x/apps/renderer/package.json
index a8c67a43..d9216de1 100644
--- a/apps/x/apps/renderer/package.json
+++ b/apps/x/apps/renderer/package.json
@@ -49,6 +49,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"recharts": "^3.8.0",
+ "remark-breaks": "^4.0.0",
"sonner": "^2.0.7",
"streamdown": "^1.6.10",
"tailwind-merge": "^3.4.0",
diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx
index de75fb4a..67f3f06a 100644
--- a/apps/x/apps/renderer/src/App.tsx
+++ b/apps/x/apps/renderer/src/App.tsx
@@ -62,6 +62,8 @@ import { BrowserPane } from '@/components/browser-pane/BrowserPane'
import { VersionHistoryPanel } from '@/components/version-history-panel'
import { FileCardProvider } from '@/contexts/file-card-context'
import { MarkdownPreOverride } from '@/components/ai-elements/markdown-code-override'
+import { defaultRemarkPlugins } from 'streamdown'
+import remarkBreaks from 'remark-breaks'
import { TabBar, type ChatTab, type FileTab } from '@/components/tab-bar'
import {
type ChatMessage,
@@ -104,6 +106,11 @@ interface TreeNode extends DirEntry {
const streamdownComponents = { pre: MarkdownPreOverride }
+// Render user messages with markdown so bullets, bold, links, etc. survive the
+// round-trip from the input textarea. `remarkBreaks` turns single newlines
+// into
so typed line breaks are preserved without requiring blank lines.
+const userMessageRemarkPlugins = [...Object.values(defaultRemarkPlugins), remarkBreaks]
+
function SmoothStreamingMessage({ text, components }: { text: string; components: typeof streamdownComponents }) {
const smoothText = useSmoothedText(text)
return {smoothText}
@@ -3974,7 +3981,14 @@ function App() {
{item.content && (
- {item.content}
+
+
+ {item.content}
+
+
)}
)
@@ -3995,7 +4009,12 @@ function App() {
))}
)}
- {message}
+
+ {message}
+
)
diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx
index 852993a2..0a407d5d 100644
--- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx
+++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx
@@ -25,6 +25,8 @@ import { Suggestions } from '@/components/ai-elements/suggestions'
import { type PromptInputMessage, type FileMention } from '@/components/ai-elements/prompt-input'
import { FileCardProvider } from '@/contexts/file-card-context'
import { MarkdownPreOverride } from '@/components/ai-elements/markdown-code-override'
+import { defaultRemarkPlugins } from 'streamdown'
+import remarkBreaks from 'remark-breaks'
import { TabBar, type ChatTab } from '@/components/tab-bar'
import { ChatInputWithMentions, type StagedAttachment, type SelectedModel } from '@/components/chat-input-with-mentions'
import { ChatMessageAttachments } from '@/components/chat-message-attachments'
@@ -49,6 +51,11 @@ import {
const streamdownComponents = { pre: MarkdownPreOverride }
+// Render user messages with markdown so bullets, bold, links, etc. survive the
+// round-trip from the input textarea. `remarkBreaks` turns single newlines
+// into
so typed line breaks are preserved without requiring blank lines.
+const userMessageRemarkPlugins = [...Object.values(defaultRemarkPlugins), remarkBreaks]
+
/* ─── Billing error helpers ─── */
const BILLING_ERROR_PATTERNS = [
@@ -353,7 +360,14 @@ export function ChatSidebar({
{item.content && (
- {item.content}
+
+
+ {item.content}
+
+
)}
)
@@ -374,7 +388,12 @@ export function ChatSidebar({
))}
)}
- {message}
+
+ {message}
+
)
diff --git a/apps/x/pnpm-lock.yaml b/apps/x/pnpm-lock.yaml
index 51248fff..ac219371 100644
--- a/apps/x/pnpm-lock.yaml
+++ b/apps/x/pnpm-lock.yaml
@@ -247,6 +247,9 @@ importers:
recharts:
specifier: ^3.8.0
version: 3.8.1(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1)
+ remark-breaks:
+ specifier: ^4.0.0
+ version: 4.0.0
sonner:
specifier: ^2.0.7
version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -5808,6 +5811,9 @@ packages:
mdast-util-mdxjs-esm@2.0.1:
resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
+ mdast-util-newline-to-break@2.0.0:
+ resolution: {integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==}
+
mdast-util-phrasing@4.1.0:
resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
@@ -6768,6 +6774,9 @@ packages:
rehype-raw@7.0.0:
resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
+ remark-breaks@4.0.0:
+ resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==}
+
remark-cjk-friendly-gfm-strikethrough@1.2.3:
resolution: {integrity: sha512-bXfMZtsaomK6ysNN/UGRIcasQAYkC10NtPmP0oOHOV8YOhA2TXmwRXCku4qOzjIFxAPfish5+XS0eIug2PzNZA==}
engines: {node: '>=16'}
@@ -14414,6 +14423,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ mdast-util-newline-to-break@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-find-and-replace: 3.0.2
+
mdast-util-phrasing@4.1.0:
dependencies:
'@types/mdast': 4.0.4
@@ -15608,6 +15622,12 @@ snapshots:
hast-util-raw: 9.1.0
vfile: 6.0.3
+ remark-breaks@4.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-newline-to-break: 2.0.0
+ unified: 11.0.5
+
remark-cjk-friendly-gfm-strikethrough@1.2.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5):
dependencies:
micromark-extension-cjk-friendly-gfm-strikethrough: 1.2.3(micromark-util-types@2.0.2)(micromark@4.0.2)