Fix(ui): Support Markdown Math Rendering#381
Fix(ui): Support Markdown Math Rendering#381jollyxenon wants to merge 2 commits intoNeuralNomadsAI:devfrom
Conversation
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
PR builds are available as GitHub Actions artifacts: https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25030077463 Artifacts expire in 7 days. |
There was a problem hiding this comment.
Pull request overview
Adds KaTeX-based math rendering support to the UI’s Markdown pipeline to address #379 (“Support Markdown Formula”).
Changes:
- Introduces custom
markedextensions to tokenize/render inline and block math via KaTeX. - Adds KaTeX styling (CSS import + markdown-specific layout tweaks for KaTeX output).
- Adds
katexas a UI dependency and refines Markdown component typing for cached render snapshots.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| packages/ui/src/styles/markdown.css | Adds KaTeX-specific Markdown styles (scrolling display math, font sizing). |
| packages/ui/src/lib/markdown.ts | Implements math tokenizers/renderers and registers them with marked. |
| packages/ui/src/components/markdown.tsx | Imports KaTeX CSS and tightens snapshot typing used by the render/cache pipeline. |
| packages/ui/package.json | Adds katex dependency required for rendering. |
| import { useI18n } from "../lib/i18n" | ||
| import "katex/dist/katex.min.css" | ||
|
|
There was a problem hiding this comment.
Importing katex/dist/katex.min.css here makes KaTeX styles unlayered and globally applied, which also prevents the layered overrides in styles/markdown.css from taking effect (unlayered styles outrank layered ones). Consider moving the KaTeX CSS import into styles/markdown.css using a named layer and declaring layer order, so KaTeX base styles and your overrides compose predictably.
There was a problem hiding this comment.
The judgment is true, but the suggestion sucks. I'll fix the problem in my next commit.
| function canOpenInlineMath(src: string): boolean { | ||
| const nextCharacter = src[1] | ||
| return Boolean(nextCharacter) && !/\s/.test(nextCharacter) | ||
| } |
There was a problem hiding this comment.
The $...$ inline math tokenizer currently refuses to open when the character after $ is whitespace (canOpenInlineMath), which means inputs like $ \mathbb{R}$ / $ FORMULA $ won’t be recognized even though normalizeMathContent() trims whitespace (and the issue/PR description explicitly calls out this format). Consider allowing optional leading/trailing spaces inside the delimiters and rely on trimming + additional heuristics (e.g., avoid parsing when adjacent to digits/word chars) to reduce currency false-positives instead of hard-rejecting whitespace.
There was a problem hiding this comment.
A problem caused by my mistake. I'll fix it later.
|
|
||
| const blockMathPattern = /^\$\$([\s\S]+?)\$\$(?:\n+|$)/ | ||
| const bracketBlockMathPattern = /^\\\[([\s\S]+?)\\\](?:\n+|$)/ | ||
| const inlineParenMathPattern = /^\\\(([\s\S]+?)\\\)/ |
There was a problem hiding this comment.
inlineParenMathPattern (\\((...[\s\S]+?)\\)) can match across newlines, so \(...\) could accidentally consume large multi-line spans. This is inconsistent with the $...$ matcher (which rejects newlines) and can lead to surprising parsing. Consider restricting this inline pattern to a single line (e.g., disallow \n).
| const inlineParenMathPattern = /^\\\(([\s\S]+?)\\\)/ | |
| const inlineParenMathPattern = /^\\\(([^\n]+?)\\\)/ |
There was a problem hiding this comment.
Good Insight Good Suggestion. I'll fix it.
| @layer components { | ||
| .markdown-body .katex-display { | ||
| overflow-x: auto; | ||
| overflow-y: hidden; | ||
| padding: 0.25rem 0; | ||
| } | ||
|
|
||
| .markdown-body .katex { | ||
| font-size: 1.05em; | ||
| } | ||
|
|
There was a problem hiding this comment.
These KaTeX overrides are inside @layer components, but KaTeX’s own stylesheet is imported unlayered (from components/markdown.tsx). In the cascade-layer model, unlayered styles outrank layered ones regardless of selector specificity, so KaTeX’s base .katex / .katex-display declarations will override these adjustments. To ensure these rules apply, either move them out of a layer, or import KaTeX CSS into a named layer and order layers so components comes after it.
| @layer components { | |
| .markdown-body .katex-display { | |
| overflow-x: auto; | |
| overflow-y: hidden; | |
| padding: 0.25rem 0; | |
| } | |
| .markdown-body .katex { | |
| font-size: 1.05em; | |
| } | |
| .markdown-body .katex-display { | |
| overflow-x: auto; | |
| overflow-y: hidden; | |
| padding: 0.25rem 0; | |
| } | |
| .markdown-body .katex { | |
| font-size: 1.05em; | |
| } | |
| @layer components { |
There was a problem hiding this comment.
The patch is a baloney though the problem exists indeed.
| function renderMathToken(content: string, displayMode: boolean): string { | ||
| const normalizedContent = normalizeMathContent(content) | ||
| if (!normalizedContent) { | ||
| return escapeHtml(content) | ||
| } |
There was a problem hiding this comment.
renderMathToken falls back to escapeHtml(content) on empty/invalid formulas, but content is the inner math text (without $...$ / \(...\) delimiters). That makes failures less obvious and changes what the user typed. Consider falling back to the original raw token (including delimiters) or wrapping the fallback in a consistent error element so users can still see the exact input.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
PR builds are available as GitHub Actions artifacts: https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25031698002 Artifacts expire in 7 days. |
Add
KaTeXextension for math formula renderingIt solves the issue #379 .
Now the interface acts like this when processing formulas:
$ \mathbb{R}$:$$ \mathbb{R} $$::

Closes #379