feat(messages): add optional LaTeX math rendering#587
feat(messages): add optional LaTeX math rendering#587mhulden wants to merge 10 commits intoDimillian:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1db15a49be
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (MARKDOWN_FENCE_PATTERN.test(line)) { | ||
| flushNonFenceChunk(); | ||
| inFence = !inFence; | ||
| output.push(line); |
There was a problem hiding this comment.
Track fence marker length when toggling fence state
The fence-skip logic flips inFence on any line that starts with or `~~~`, but Markdown fences only close when the marker character and length match the opener. With a 4-backtick fence (used to show literal triple backticks), an inner line containing incorrectly toggles out of fence mode, so later \(...\)/\[...\] text inside that code block is rewritten to $...$/$$...$$. That mutates code examples that should stay literal when math rendering is enabled.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — fixed by matching fence marker + minimum length from opener, and added regression test for long-fence/nested-shorter-fence case.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6339dd924
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }, | ||
| ); | ||
| const withInlineMath = withBlockMath.replace( | ||
| /\\\(([\s\S]*?)\\\)/g, |
There was a problem hiding this comment.
Restrict LaTeX delimiter rewrite to message text only
With math rendering enabled, normalizeLatexMathDelimitersInChunk rewrites every \(...\) match before Markdown parsing, so it also mutates link destinations and other syntax content. For example, [wiki](https://en.wikipedia.org/wiki/Function_\(mathematics\)) is transformed to a URL containing $mathematics$, which breaks the original link target. This should be limited to plain text/math contexts (or done after parsing on text nodes) instead of applying a global regex over the raw Markdown source.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Thanks. I pushed a follow-up fix that addresses this.
What changed:
- Kept the fence handling fix (marker + minimum length from opener).
- Updated delimiter normalization so
\(...\)/\[...\]conversion no longer mutates markdown link destinations. - Specifically, link destination segments are masked before delimiter conversion and restored afterward, so URLs like:
[wiki](https://en.wikipedia.org/wiki/Function_\(mathematics\))
remain unchanged.
- Fenced and inline code are still protected from delimiter rewriting.
Tests added/updated:
- Existing long-fence regression still passes.
- Added regression test for escaped LaTeX delimiters inside link URLs to ensure no URL rewriting.
Markdown.test.tsxnow passes with the new cases, andnpm run typecheckis clean.
So this should close both concerns:
- fence marker length correctness
- no accidental rewriting of non-text markdown syntax (URL destinations).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9be38ee3fd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const fenceOpener = parseFenceOpener(line); | ||
| if (fenceOpener) { | ||
| flushNonFenceChunk(); | ||
| activeFence = fenceOpener; | ||
| output.push(line); |
There was a problem hiding this comment.
Skip indented code blocks during delimiter normalization
normalizeLatexMathDelimiters only excludes fenced blocks, so any 4-space-indented code block is treated as normal text and passed through normalizeLatexMathDelimitersInChunk; with math rendering enabled this rewrites literals like \(x^2\) to $x^2$. The block still renders as code, but the source text is mutated, which corrupts copied examples for users who paste indented Markdown code blocks.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 2d48c9e. normalizeLatexMathDelimiters now skips indented code blocks (4-space/tab), so \\(...\\) / \\[...\\] inside indented code stays literal.
| const body = blockLines.join("\n").trim(); | ||
| if (body) { | ||
| output.push("$$", body, "$$"); | ||
| } else { |
There was a problem hiding this comment.
Preserve container indentation when converting [...] blocks
Block-delimiter rewriting drops leading indentation by emitting raw $$ lines, so \[/\] math inside nested contexts (for example, list items or blockquotes) is lifted out of that container after normalization. This changes Markdown structure and renders equations in the wrong place whenever nested block math is used.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 2d48c9e. \\[...\\] block conversion now preserves container prefixes (e.g., list/blockquote context), so nested markdown structure is retained.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2d48c9eeba
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| INLINE_LATEX_MATH_PATTERN, | ||
| (match, body: string, offset: number, source: string) => { |
There was a problem hiding this comment.
Ignore escaped \(...\) delimiters during rewrite
The inline rewrite currently matches \(...\) anywhere, even when the opener is itself escaped (for example \\(x\\) used to display literal LaTeX delimiters). In that case the regex matches starting at the second backslash and rewrites the text to escaped dollars (\$x\$), which changes the rendered/copyable message content even though the author explicitly escaped the delimiter. This should skip matches preceded by an escape backslash sequence with even/odd handling instead of rewriting them.
Useful? React with 👍 / 👎.
| function isIndentedCodeLine(line: string) { | ||
| return /^(?: {4}|\t)/.test(line); |
There was a problem hiding this comment.
Treat quoted indented code blocks as non-math text
Indented-code detection only checks for lines that begin with four spaces or a tab, so container-prefixed code blocks like > \(x^2\) (and similar list/blockquote indented code contexts) are not recognized as code before normalization. Those lines then go through delimiter rewriting and become $...$, mutating code examples that should remain literal when math rendering is enabled. The indented-code guard needs to account for Markdown container prefixes, not just column-0 indentation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 26ec05c609
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| (before && INLINE_MATH_BOUNDARY_PATTERN.test(before)) || | ||
| (after && INLINE_MATH_BOUNDARY_PATTERN.test(after)) | ||
| ) { |
There was a problem hiding this comment.
Stop suppressing \(...\) math beside
% and /
The replaceInlineLatexMathDelimiters guard rejects any \\(...\\) match when the surrounding character matches INLINE_MATH_BOUNDARY_PATTERN, and that pattern includes % and /. This means valid inline math like Success: \\(p\\)% (or formulas followed by /) is left as literal text even with math rendering enabled, because remark-math only receives \\(...\\) after this rewrite step and does not render it. Narrow the boundary check to actual URL/link contexts so normal punctuation-adjacent math still converts.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43c429cee5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const MARKDOWN_FENCE_OPENER_PATTERN = /^ {0,3}(`{3,}|~{3,})(.*)$/; | ||
| const MARKDOWN_FENCE_CLOSER_PATTERN = /^ {0,3}(`{3,}|~{3,})[ \t]*$/; |
There was a problem hiding this comment.
Handle container-prefixed fences during math normalization
When math rendering is enabled, fence detection only matches lines that start with optional spaces followed by backticks/tilde. Valid Markdown fenced blocks inside containers (for example > ```...````, or list-item fences) are not recognized as code regions, so (...)and[...]inside those blocks are rewritten to$...
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3343a39854
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const { masked, restore } = maskMarkdownLinkDestinations(withMaskedInlineCode); | ||
| const withBlockMath = replaceBlockLatexMathDelimiters(masked); | ||
| const withInlineMath = replaceInlineLatexMathDelimiters(withBlockMath); |
There was a problem hiding this comment.
Skip URL literals before inline delimiter rewrite
normalizeLatexMathDelimitersInChunk only masks [label](destination) links before calling replaceInlineLatexMathDelimiters, so plain autolinks and reference-style link definitions are still rewritten. With math rendering enabled, inputs like https://example.com/\(foo\) (or [ref]: https://example.com/\(foo\)) are transformed to $foo$, which changes the actual link target. Although inline-link destinations are masked, this still mutates non-text URL contexts and breaks links.
Useful? React with 👍 / 👎.
| if (blockCloseMatch && (blockCloseMatch[1] ?? "") === activeBlockPrefix) { | ||
| if (blockLines.some((bodyLine) => bodyLine.trim().length > 0)) { |
There was a problem hiding this comment.
Normalize quote prefix before matching block math closer
The block \[ ... \] conversion requires the closer prefix string to exactly equal the opener prefix. In Markdown blockquotes, > may be written with or without a following space, so an opener like > \[ and closer like >\] are equivalent but fail this equality check; the block never closes and remains literal instead of rendering math. This causes valid quoted block-math syntax to be missed whenever quote spacing differs between open/close lines.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a930fea28b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| activeBlockPrefixNormalized = activeBlockPrefix.replace(/>\s*/g, ">"); | ||
| activeOpenLine = line; |
There was a problem hiding this comment.
Normalize leading quote indent when closing \[...\] blocks
Even after normalizing > spacing, the closer match still depends on the opener’s exact leading indentation because activeBlockPrefixNormalized preserves spaces before >. In Markdown, blockquote markers may be indented by 0–3 spaces per line, so an opener like " > \\[" and closer like ">\\]" are equivalent but won’t match here; the block never closes and stays literal instead of rendering math. This causes valid quoted \\[...\\] math to fail in mixed-indentation blockquotes.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb9be1270a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const BLOCK_LATEX_SINGLE_LINE_PATTERN = /^([ \t]*(?:>\s*)*)\\\[\s*(.*?)\s*\\\]\s*$/; | ||
| const BLOCK_LATEX_OPEN_PATTERN = /^([ \t]*(?:>\s*)*)\\\[\s*$/; | ||
| const BLOCK_LATEX_CLOSE_PATTERN = /^([ \t]*(?:>\s*)*)\\\]\s*$/; |
There was a problem hiding this comment.
Support list-marker-prefixed \[...\] block delimiters
The block-delimiter matcher only accepts optional whitespace and blockquote prefixes before \\[ / \\], so list-item forms like - \\[, 1. \\[, and - \\[ ... \\] never enter the conversion path. With math rendering enabled, those equations remain literal instead of rendering, even though they are valid “delimiter-on-its-own-line” math blocks inside list items.
Useful? React with 👍 / 👎.
| if ( | ||
| (before && INLINE_MATH_BOUNDARY_PATTERN.test(before)) || | ||
| (after && INLINE_MATH_BOUNDARY_PATTERN.test(after)) | ||
| ) { |
There was a problem hiding this comment.
Convert \(...\) delimiters next to alphanumeric text
The inline rewrite rejects \\(...\\) whenever the preceding or following character is alphanumeric/underscore, which suppresses valid inline math in common text such as f\\(x\\) or \\(n\\)th. Because URL/link/code contexts are already masked earlier in normalization, this boundary gate creates false negatives where math rendering is enabled but LaTeX delimiters are left unrendered.
Useful? React with 👍 / 👎.
xkonjin
left a comment
There was a problem hiding this comment.
Automated review, focused on bugs, security, and test coverage:\n\n- Potential secret-handling risk: the diff appears to touch credentials or token-like values. Please verify no secrets are committed, logged, or exposed in client-visible paths.\n\n- Auth-sensitive area: this PR touches authentication/authorization-related code. I\u2019d want explicit tests for privilege boundaries, expired/invalid session handling, and negative authorization cases before merging.\n\n- Test coverage gap: this PR adds non-trivial code, but the diff does not obviously include tests. Please add targeted coverage for the primary success path and at least one failure/edge case.
Closes #446
Summary
Add optional LaTeX math rendering to Codex chat messages.
What changed
remark-mathrehype-katexkatexSupported syntax
$...$and\(...\)$$...$$and\[...\]Safety behavior
Validation
npm run typechecknpm run test -- src/features/messages/components/Markdown.test.tsxnpm run test -- src/features/messages/components/Messages.test.tsxnpm run test -- src/features/settings/components/sections/SettingsDisplaySection.test.tsxcd src-tauri && cargo checkAll passing.