Skip to content

fix(chat): render LaTeX \\[..\\] / \\(..\\) delimiters (Issue #50)#191

Open
MarkSiqiZhang wants to merge 4 commits intomainfrom
fix/issue-50-latex-brackets
Open

fix(chat): render LaTeX \\[..\\] / \\(..\\) delimiters (Issue #50)#191
MarkSiqiZhang wants to merge 4 commits intomainfrom
fix/issue-50-latex-brackets

Conversation

@MarkSiqiZhang
Copy link
Copy Markdown
Collaborator

Summary

Root cause

Two bugs stacked:

  1. remark-math@6 recognizes only $ delimiters, so \[..\] / \(..\) never became math nodes. Worse, CommonMark treats \[ / \] / \( / \) as valid backslash-escapes of ASCII punctuation, so remark silently stripped the backslashes, leaving [ r_t(...) ] rendered as prose.
  2. The existing unescapeWithMathProtection protects math blocks from its own \\t → \t replacement via a mask/unmask trick — but the mask regex only matched $..$ / $$..$$. Any \theta / \tau / \n*-command inside bracket-delimited math lost the backslash when its \t was turned into a literal TAB character.

Changes

File Purpose
src/utils/latexNormalizer.ts (new) Rewrites \[..\]$$..$$ and \(..\)$..$ outside fenced code blocks and inline code. Single-pass alternation regex, fast-path short-circuit when no brackets present, returns same string reference on the no-op path (preserves useMemo identity).
src/components/chat/utils/chatFormatting.ts Calls the normalizer at the top of unescapeWithMathProtection, so bracket math is rewritten to $-form before the existing mask/replace/unmask runs — bringing it under the same protection umbrella.

Why hook into unescapeWithMathProtection instead of the 5 ReactMarkdown call sites

All 10+ chat render paths already funnel through unescapeWithMathProtection (via messageTransforms.ts and useChatRealtimeHandlers.ts). A 2-line patch there covers the whole chat surface without touching any React component. The non-chat renderers don't call this function and are deferred to a follow-up PR.

Test plan

  • Ask the assistant to emit a display formula explicitly using \[ r_t(\theta) = \frac{\pi_\theta(a|s)}{\pi_{\theta_{old}}(a|s)} \] — verify KaTeX renders the fraction (DOM check: <span class="katex"> present, with both katex-mathml and katex-html children).
  • Ask for inline math with \( y = mx + b \) — verify KaTeX inline rendering.
  • Verify existing $..$ / $$..$$ messages still render (no regression).
  • Send a fenced code block containing arr\[0\] (e.g. a Rust sample) — verify the code is NOT mangled into arr$$0$$.
  • Verify \theta / \tau / \pi_{new} inside bracket-delimited math are intact (no <TAB>heta corruption).
  • npm run typecheck — passes
  • npm run build — passes

Wire normalizeLatexDelimiters into unescapeWithMathProtection so bracket-delimited math is rewritten to $-delimiters before the mask/unescape step. Without this, \[..\] and \(..\) rendered as literal text, and the \\t → \t replacement inside unescapeWithMathProtection corrupted \theta / \tau / \n-commands that sat inside bracket-delimited blocks (issue #50).
…dering across components

- Added normalizeLatexDelimiters to CodeEditor, ResearchLab, ChatContextFilePreview, and SurveyPage components to ensure LaTeX content is properly formatted before rendering.
- This change enhances the handling of LaTeX delimiters, improving the overall user experience when displaying mathematical content.
Comment thread src/components/ResearchLab.jsx Fixed
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
MarkSiqiZhang added a commit that referenced this pull request Apr 17, 2026
Add a MermaidBlock component that renders ```mermaid fences inline as
SVG via a lazy-loaded mermaid@11 import. The chat Markdown CodeBlock
short-circuits to MermaidBlock when language === 'mermaid', bypassing
syntax highlighting. Streaming partial sources are debounced 150ms and
fall back to a <pre> of the raw source on parse errors, so users never
see a flashing error during a live stream. Theme is driven by useTheme()
so diagrams repaint live on dark-mode toggle without remount.

Also tighten formatFileTreeInContent so it no longer mis-wraps non-tree
content in ```text fences — the likely cause of the "fence marker / text
label leak" symptom in issue #50. Two guardrails:

  1. Skip detection inside fenced code blocks (insideFence toggle).
  2. Only commit the wrap when the collected block contains at least
     one strong signal (├── or └──); lone-│ clusters (ASCII pipelines,
     Mermaid-ish art) pass through unwrapped.

i18n: add codeBlock.rendering / codeBlock.renderError in en/zh-CN/ko;
reuse existing codeBlock.copy / codeBlock.copied for the diagram copy
button.

Scope: chat surface only, mirroring PR #191. ChatContextFilePreview,
ResearchLab, and CodeEditor are deferred to a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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]: Markdown rendering issues: Mermaid / LaTeX / preformatted ASCII blocks are shown as raw or broken text

1 participant