Skip to main content

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/15 with rounded-[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 ChatBubbleActionsContext populated by StreamingChatPanel that calls setInput to 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:

PackageVersionPurpose
remark-math^6.0.0Parse $...$ and $$...$$ math in markdown
rehype-katex^7.0.1Render parsed math with KaTeX
katex^0.16.xKaTeX library + CSS
@uiw/react-codemirror^4.25.9CodeMirror React wrapper
@codemirror/lang-javascript^6.2.5JS/TS/JSX syntax
@codemirror/lang-python^6.2.xPython syntax
@codemirror/lang-json^6.0.xJSON syntax
@codemirror/lang-sql^6.xSQL syntax
@codemirror/lang-html^6.xHTML/XML syntax
@codemirror/lang-css^6.xCSS/SCSS syntax