Skip to content

feat(den-web): render LaTeX math in user message bubbles#2528

Open
PriyeshPandey2000 wants to merge 1 commit into
different-ai:devfrom
PriyeshPandey2000:feat/user-message-latex-rendering
Open

feat(den-web): render LaTeX math in user message bubbles#2528
PriyeshPandey2000 wants to merge 1 commit into
different-ai:devfrom
PriyeshPandey2000:feat/user-message-latex-rendering

Conversation

@PriyeshPandey2000

@PriyeshPandey2000 PriyeshPandey2000 commented Jul 7, 2026

Copy link
Copy Markdown

Summary

  • Replace the full markdown-parser approach with a dedicated math-only renderer for user message bubbles.
  • Renderer handles $$...$$ display math and $...$ inline math. Inline $ requires the next character to be non-digit and non-whitespace; closer $ must not be followed by a digit — prevents "$5 and $10" pairing as a math span.
  • Plain text is HTML-escaped verbatim with whitespace-pre-wrap, so formatting characters (#, *, _, >) are shown literally — no markdown surprises.
  • Output runs through the same sanitizeMarkdownHtml DOMPurify config as assistant messages.
  • Falls back to plain-text + skill-chip renderer when skill tokens are present.

Why

KaTeX was already wired for assistant messages but user messages bypassed the markdown renderer entirely, causing LaTeX to appear as raw $$...$$ text in user bubbles.

Issue

Closes #2501

Scope

  • apps/app/src/components/markdown/markdown.tsx — renderUserMath function + mathOnly prop on MarkdownBlock
  • apps/app/src/components/ui/message.tsx — propagate mathOnly through MessageContent
  • apps/app/src/components/chat/message-list.tsx — use mathOnly for user bubbles

Out of scope

Segment-based rendering to support LaTeX inside skill-chip messages.

Testing

Ran

  • pnpm typecheck — pass

Manual

  1. Send What is $E = mc^2$? — inline math renders in user bubble ✓
  2. Send $$\int_0^\infty e^{-x^2}dx = \frac{\sqrt{\pi}}{2}$$ — display math renders ✓
  3. Send $$\begin{cases} x+y=1 \\ x-y=0 \end{cases}$$ — multi-line environment renders ✓ (this was the known failure case in the DevTools workaround from the issue)
  4. Send plain multi-line text — line breaks preserved ✓
  5. Ask assistant to show LaTeX — assistant rendering unaffected ✓

Evidence

Screenshot 2026-07-07 at 12 29 33 PM Screenshot 2026-07-07 at 12 30 56 PM Screenshot 2026-07-07 at 12 31 05 PM Screenshot 2026-07-08 at 10 45 04 PM Screenshot 2026-07-08 at 10 46 56 PM Screenshot 2026-07-08 at 10 47 02 PM

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-landing Ready Ready Preview, Comment, Open in v0 Jul 8, 2026 5:37pm

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@PriyeshPandey2000 is attempting to deploy a commit to the Different AI Team on Vercel.

A member of the Team first needs to authorize it.

@Pablosinyores Pablosinyores left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to see math rendering in the bubbles. Two things worth weighing before merge, both stemming from user messages moving from inert whitespace-pre-wrap text to full markdown:

  1. Single-$ inline math vs currency. markedKatex({ throwOnError: false }) uses the default delimiters, so $...$ is live inline math. In user-typed chat that false-triggers on ordinary dollar amounts — e.g. "it costs $5 and $10 more" parses $5 and $ as a math span, typesetting "5 and " and eating the literal dollar signs. Prices in chat are common enough that this will bite regularly. Options: drop the single-$ inline delimiter and keep only $$...$$ for display math, or configure the extension so a $ immediately followed by a digit is not treated as an opener. Worth trying a couple of price messages.

  2. Trust boundary on user text. Routing user messages through the markdown renderer means their text is now interpreted as markdown/HTML rather than shown verbatim. Two sub-points: (a) formatting surprises — a user who types *, _, #, or > now gets it reformatted instead of shown literally, a behavior change from the old bubble; (b) security — [x](...) links become active, so please confirm this path runs the same DOMPurify config as assistant markdown and that it rejects javascript:/data: URL schemes, since this is the first place raw user input gets HTML-rendered. The aria-hidden ADD_ATTR and throwOnError: false are both the right calls.

The documented skill-chip fallback tradeoff reads reasonable — agree chips plus math in one message is rare today.

Replace the markdown-parser approach with a dedicated math-only renderer
for user message bubbles. This avoids false-positives on currency ($5),
markdown formatting surprises (#headings, *italic*, _underscores_), and
the trust-boundary concerns of routing raw user input through a full
markdown parser.

The renderer handles $$...$$ display math and $...$ inline math. Inline
opener requires the character after $ to be non-digit and non-whitespace,
and the closer $ must not be followed by a digit — this prevents currency
like "$5 and $10" from pairing into a math span. Plain text is HTML-escaped
verbatim; output runs through the same DOMPurify config as assistant messages.

Falls back to plain-text + skill-chip rendering when skill tokens are present.

Closes different-ai#2501
@PriyeshPandey2000

PriyeshPandey2000 commented Jul 8, 2026

Copy link
Copy Markdown
Author

Good to see math rendering in the bubbles. Two things worth weighing before merge, both stemming from user messages moving from inert whitespace-pre-wrap text to full markdown:

  1. Single-$ inline math vs currency. markedKatex({ throwOnError: false }) uses the default delimiters, so $...$ is live inline math. In user-typed chat that false-triggers on ordinary dollar amounts — e.g. "it costs $5 and $10 more" parses $5 and $ as a math span, typesetting "5 and " and eating the literal dollar signs. Prices in chat are common enough that this will bite regularly. Options: drop the single-$ inline delimiter and keep only $$...$$ for display math, or configure the extension so a $ immediately followed by a digit is not treated as an opener. Worth trying a couple of price messages.
  2. Trust boundary on user text. Routing user messages through the markdown renderer means their text is now interpreted as markdown/HTML rather than shown verbatim. Two sub-points: (a) formatting surprises — a user who types *, _, #, or > now gets it reformatted instead of shown literally, a behavior change from the old bubble; (b) security — [x](...) links become active, so please confirm this path runs the same DOMPurify config as assistant markdown and that it rejects javascript:/data: URL schemes, since this is the first place raw user input gets HTML-rendered. The aria-hidden ADD_ATTR and throwOnError: false are both the right calls.

The documented skill-chip fallback tradeoff reads reasonable — agree chips plus math in one message is rare today.

Thanks for the detailed review , reworked the approach to address both points.

  1. Approach: replaced full markdown parser with a math-only renderer (renderUserMath) that handles only $$...$$ and $...$ — everything else is HTML-escaped verbatim.

  2. Currency false-positives: inline opener requires the char after $ to be non-digit and non-whitespace; closer $ must not be followed by a digit."it costs $5 and $10 more" stays plain text; $E=mc^2$ renders correctly.

  3. Formatting surprises: eliminated entirely — #, *, _, > are now escaped as plain text, same behaviour as the old bubble.

Security: renderer never produces links or user-controlled HTML. Plain text goes through escapeHtml(), KaTeX output is from a trusted library, and the full output runs through sanitizeMarkdownHtml() (same DOMPurify config as assistant messages) before hitting the DOM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Native LaTeX/MathJax rendering for chat messages

2 participants