Chat Formatting Fixes — 2026-05-13
Summary
Fixed four formatting issues in the Quantago AI Chat (packages/chat-ui/src/StreamingChatPanel.tsx).
1. LaTeX / Math Rendering
Problem: Inline and block math expressions (e.g. \text{ATR%} = \left(\frac{\text{ATR}}{\text{Price}}\right) \times 100) were rendered as raw text.
Fix: Added remark-math + rehype-katex + katex dependencies and enabled them in MarkdownBubble:
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeKatex as any]}
components={components}
>
KaTeX CSS is imported directly: import 'katex/dist/katex.min.css'.
2. Code Block Syntax Highlighting
Problem: Code blocks had no syntax highlighting — plain monochrome text.
Fix: Replaced the <pre> implementation with @uiw/react-codemirror (same library used in the Bot Code Tab) with language-aware extensions:
@codemirror/lang-javascript— JS, TS, JSX, TSX@codemirror/lang-python— Python@codemirror/lang-json— JSON@codemirror/lang-sql— SQL@codemirror/lang-html— HTML/XML@codemirror/lang-css— CSS/SCSS
Uses theme="dark" with editable={false}, line numbers, and fold gutter to match the bot code tab experience.
3. Source Citation Rendering
Problem: Sources sometimes appeared as raw clickable links [source](#qg=...long URL...) instead of being silently handled.
Fix: Updated the a component handler to always intercept #qg=-prefixed hrefs:
a: ({ href, children, ... }) => {
if (href?.startsWith(GROUNDING_HREF_PREFIX)) {
const annotation = decodeGroundingHref(href);
if (annotation) return <GroundingAnnotationMarker annotation={annotation} />;
// Failed to decode — render children as plain text, not a link
return <>{children}</>;
}
return <a href={href} target="_blank" rel="noreferrer" ...>{children}</a>;
},
4. Sentence Hover Tooltip (Sources + Actions)
Problem: No per-sentence hover interaction for sources and copy/pin actions.
Fix: Redesigned GroundedSentence component:
- Yellow highlight on hover:
hover:bg-yellow-400/20 dark:hover:bg-yellow-400/15withrounded-[3px] - Sources section at the top of the tooltip: shows ALL sources at once (title, favicon, confidence, excerpt)
- Actions footer at the bottom: Copy (copies sentence text to clipboard) and Pin (appends sentence to chat input)
- Pin uses a
ChatBubbleActionsContextpopulated byStreamingChatPanelthat callssetInputto append the selected text
Context pattern
const ChatBubbleActionsContext = React.createContext<{ onPin?: (text: string) => void }>({});
// In StreamingChatPanel render:
<ChatBubbleActionsContext.Provider value={{ onPin: (text) => setInput((prev) => prev ? `${prev}\n\n${text}` : text) }}>
{turns.map(...)}
</ChatBubbleActionsContext.Provider>
// In GroundedSentence:
const { onPin } = React.useContext(ChatBubbleActionsContext);
Packages Added
All added to packages/chat-ui/package.json:
| Package | Version | Purpose |
|---|---|---|
remark-math | ^6.0.0 | Parse $...$ and $$...$$ math in markdown |
rehype-katex | ^7.0.1 | Render parsed math with KaTeX |
katex | ^0.16.x | KaTeX library + CSS |
@uiw/react-codemirror | ^4.25.9 | CodeMirror React wrapper |
@codemirror/lang-javascript | ^6.2.5 | JS/TS/JSX syntax |
@codemirror/lang-python | ^6.2.x | Python syntax |
@codemirror/lang-json | ^6.0.x | JSON syntax |
@codemirror/lang-sql | ^6.x | SQL syntax |
@codemirror/lang-html | ^6.x | HTML/XML syntax |
@codemirror/lang-css | ^6.x | CSS/SCSS syntax |