From 763049c21d8bb6de11862f98492ae20280df4fbd Mon Sep 17 00:00:00 2001 From: arigatoexpress <95630102+arigatoexpress@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:58:43 -0600 Subject: [PATCH] feat(copilot): render assistant replies as markdown Gemini returns markdown (bold counts, numbered steps, bullet lists) that the chat panel was showing as raw text with literal ** and -. Assistant bubbles now render via the existing SafeMarkdown component (sanitized, error-boundary fallback to plain text). User bubbles stay plain. prose hardcodes gray text that would be unreadable on the dark assistant bubble, so the prose typography colors are mapped onto the app theme tokens (--cp-text/-muted/-accent) for correct light + dark rendering. react-markdown is already bundled (shared vendor chunk), so the page chunk grows only ~0.4 kB. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/pages/OpsCopilot.jsx | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/OpsCopilot.jsx b/frontend/src/pages/OpsCopilot.jsx index a96ee51..0fbf85e 100644 --- a/frontend/src/pages/OpsCopilot.jsx +++ b/frontend/src/pages/OpsCopilot.jsx @@ -10,6 +10,15 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Bot, Send, Loader2, Lock, User, Sparkles } from 'lucide-react'; import adminFetch from '../adminFetch'; +import SafeMarkdown from '../components/SafeMarkdown'; + +// Map the prose typography colors onto the app theme tokens so markdown stays +// readable in both light and dark mode (prose otherwise hardcodes gray text, +// which is unreadable on the dark assistant bubble). +const MD_THEME = + '[--tw-prose-body:var(--cp-text)] [--tw-prose-headings:var(--cp-text)] ' + + '[--tw-prose-bold:var(--cp-text)] [--tw-prose-bullets:var(--cp-muted)] ' + + '[--tw-prose-counters:var(--cp-muted)] [--tw-prose-links:var(--cp-accent)]'; const SUGGESTIONS = [ 'How many new leads do we have?', @@ -34,13 +43,19 @@ function Bubble({ role, content }) { )}
- {content} + {isUser ? ( + content + ) : ( +
+ +
+ )}
);