- {content.split('\n').map((line, i) => {
- // Bold text - use module-level regex, reset lastIndex for each line
- BOLD_REGEX.lastIndex = 0
- const parts = []
- let lastIndex = 0
- let match
-
- while ((match = BOLD_REGEX.exec(line)) !== null) {
- if (match.index > lastIndex) {
- parts.push(line.slice(lastIndex, match.index))
- }
- parts.push(
-
- {match[1]}
-
- )
- lastIndex = match.index + match[0].length
- }
-
- if (lastIndex < line.length) {
- parts.push(line.slice(lastIndex))
- }
-
- return (
-
- {parts.length > 0 ? parts : line}
- {i < content.split('\n').length - 1 && '\n'}
-
- )
- })}
+
+
+ {content}
+
)}
diff --git a/ui/src/styles/globals.css b/ui/src/styles/globals.css
index 035bffe..257b93b 100644
--- a/ui/src/styles/globals.css
+++ b/ui/src/styles/globals.css
@@ -1271,6 +1271,186 @@
margin: 2rem 0;
}
+/* ============================================================================
+ Chat Prose Typography (for markdown in chat bubbles)
+ ============================================================================ */
+
+.chat-prose {
+ line-height: 1.6;
+ color: inherit;
+}
+
+.chat-prose > :first-child {
+ margin-top: 0;
+}
+
+.chat-prose > :last-child {
+ margin-bottom: 0;
+}
+
+.chat-prose h1 {
+ font-size: 1.25rem;
+ font-weight: 700;
+ margin-top: 1.25rem;
+ margin-bottom: 0.5rem;
+}
+
+.chat-prose h2 {
+ font-size: 1.125rem;
+ font-weight: 700;
+ margin-top: 1rem;
+ margin-bottom: 0.5rem;
+}
+
+.chat-prose h3 {
+ font-size: 1rem;
+ font-weight: 600;
+ margin-top: 0.75rem;
+ margin-bottom: 0.375rem;
+}
+
+.chat-prose h4,
+.chat-prose h5,
+.chat-prose h6 {
+ font-size: 0.875rem;
+ font-weight: 600;
+ margin-top: 0.75rem;
+ margin-bottom: 0.25rem;
+}
+
+.chat-prose p {
+ margin-bottom: 0.5rem;
+}
+
+.chat-prose ul,
+.chat-prose ol {
+ margin-bottom: 0.5rem;
+ padding-left: 1.25rem;
+}
+
+.chat-prose ul {
+ list-style-type: disc;
+}
+
+.chat-prose ol {
+ list-style-type: decimal;
+}
+
+.chat-prose li {
+ margin-bottom: 0.25rem;
+}
+
+.chat-prose li > ul,
+.chat-prose li > ol {
+ margin-top: 0.25rem;
+ margin-bottom: 0;
+}
+
+.chat-prose pre {
+ background: var(--muted);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 0.75rem;
+ overflow-x: auto;
+ margin-bottom: 0.5rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ line-height: 1.5;
+}
+
+.chat-prose code:not(pre code) {
+ background: var(--muted);
+ padding: 0.1rem 0.3rem;
+ border-radius: 0.25rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+}
+
+.chat-prose table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 0.5rem;
+ font-size: 0.8125rem;
+}
+
+.chat-prose th {
+ background: var(--muted);
+ font-weight: 600;
+ text-align: left;
+ padding: 0.375rem 0.5rem;
+ border: 1px solid var(--border);
+}
+
+.chat-prose td {
+ padding: 0.375rem 0.5rem;
+ border: 1px solid var(--border);
+}
+
+.chat-prose blockquote {
+ border-left: 3px solid var(--primary);
+ padding-left: 0.75rem;
+ margin-bottom: 0.5rem;
+ font-style: italic;
+ opacity: 0.9;
+}
+
+.chat-prose a {
+ color: var(--primary);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+.chat-prose a:hover {
+ opacity: 0.8;
+}
+
+.chat-prose strong {
+ font-weight: 700;
+}
+
+.chat-prose hr {
+ border: none;
+ border-top: 1px solid var(--border);
+ margin: 0.75rem 0;
+}
+
+.chat-prose img {
+ max-width: 100%;
+ border-radius: var(--radius);
+}
+
+/* User message overrides - need contrast against primary-colored bubble */
+.chat-prose-user pre {
+ background: rgb(255 255 255 / 0.15);
+ border-color: rgb(255 255 255 / 0.2);
+}
+
+.chat-prose-user code:not(pre code) {
+ background: rgb(255 255 255 / 0.15);
+}
+
+.chat-prose-user th {
+ background: rgb(255 255 255 / 0.15);
+}
+
+.chat-prose-user th,
+.chat-prose-user td {
+ border-color: rgb(255 255 255 / 0.2);
+}
+
+.chat-prose-user blockquote {
+ border-left-color: rgb(255 255 255 / 0.5);
+}
+
+.chat-prose-user a {
+ color: inherit;
+ text-decoration: underline;
+}
+
+.chat-prose-user hr {
+ border-top-color: rgb(255 255 255 / 0.2);
+}
+
/* ============================================================================
Scrollbar Styling
============================================================================ */
diff --git a/ui/vite.config.ts b/ui/vite.config.ts
index 979dbfe..a8b1b7e 100644
--- a/ui/vite.config.ts
+++ b/ui/vite.config.ts
@@ -36,6 +36,8 @@ export default defineConfig({
'@radix-ui/react-slot',
'@radix-ui/react-switch',
],
+ // Markdown rendering
+ 'vendor-markdown': ['react-markdown', 'remark-gfm'],
// Icons and utilities
'vendor-utils': [
'lucide-react',