Skip to content

feat(web): add LaTeX math rendering support to ChatMarkdown - #7739

Open
sugoidesune wants to merge 1 commit into
pingdotgg:mainfrom
sugoidesune:latex
Open

feat(web): add LaTeX math rendering support to ChatMarkdown#7739
sugoidesune wants to merge 1 commit into
pingdotgg:mainfrom
sugoidesune:latex

Conversation

@sugoidesune

@sugoidesune sugoidesune commented Aug 21, 2026

Copy link
Copy Markdown

Summary

Adds LaTeX math rendering support to ChatMarkdown in @t3tools/web using KaTeX and remark-math.

  • Parses standard Markdown math notation ($...$ and $$...$$) via remark-math and renders via rehype-katex.
  • Normalizes LLM raw bracket notation (\[ ... \] and \( ... \)) into Markdown math blocks/spans while preserving code fences and inline backticks.
  • Extends the rehype-sanitize schema to preserve math attributes and classes without stripping KaTeX markup.
  • Bundles KaTeX stylesheets in index.css.
  • Adds unit tests verifying inline math, display math, bracket notations, code fence isolation, and literal currency amounts.

Note

Medium Risk
Touches the chat markdown sanitize/rehype pipeline and renders KaTeX HTML from untrusted message text. Schema allowlists extra math class names; delimiter preprocessing is custom and easy to get wrong.

Overview
Chat messages can now show inline and display math. ChatMarkdown preprocesses LLM-style \(...\) / \[...\] into $ / $$ (skipping code), then remark-math + rehype-katex render KaTeX. Bare $ is escaped so currency and shell vars are not treated as math.

KaTeX still runs when raw HTML parsing is off. Sanitize allowlists math code classes. Copy serializes KaTeX back to \(...\) or $$...$$. Display blocks get KaTeX CSS and horizontal scroll.

Broad tests cover nested markdown, blockquotes, fences, escapes, and false positives.

Reviewed by Cursor Bugbot for commit 1841ccc. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add LaTeX math rendering to ChatMarkdown

  • preprocessLatexDelimiters in ChatMarkdown.tsx normalizes \(...\) and \[...\] delimiters to $...$ and $$...$$, and escapes stray $ outside math.
  • remark-math and rehype-katex plugins render math to KaTeX HTML, with styles and horizontal scrolling added to index.css.
  • Copy operations serialize KaTeX elements back to LaTeX delimiters via serializeKatex in markdown-clipboard.ts.
  • Risk: Literal $ in non-math text outside code spans will be backslash-escaped by preprocessLatexDelimiters, altering output for existing messages containing stray dollar signs.

Macroscope summarized 1841ccc.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d4c7405-5941-4a88-bed7-33929697045d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 21, 2026
"dataInlineCode",
"className",
],
span: [...(defaultSchema.attributes?.span ?? []), "className"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High components/ChatMarkdown.tsx:205

Raw chat Markdown can preserve arbitrary className values on div and span, so <div class="fixed inset-0 z-50 bg-background"> can cover the application and spoof or block its UI. Because parseRawHtml enables these elements, replace unrestricted className access with an allowlist or pattern limited to the KaTeX-generated class names.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatMarkdown.tsx around line 205:

Raw chat Markdown can preserve arbitrary `className` values on `div` and `span`, so `<div class="fixed inset-0 z-50 bg-background">` can cover the application and spoof or block its UI. Because `parseRawHtml` enables these elements, replace unrestricted `className` access with an allowlist or pattern limited to the KaTeX-generated class names.

Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated
Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the KaTeX/math rendering changes for chat-markdown rendering ownership and layout preservation. Four findings, all in the changed lines: the default single-dollar inline math delimiter changes how ordinary agent prose ($VAR, currency) renders, the raw-string delimiter normalization collides with markdown escapes and unterminated/alternate code fences, the sanitize allowlist now lets message content inject arbitrary classes into app-owned markdown DOM, and the imported KaTeX stylesheet is a different major than the KaTeX that renders the markup (plus no overflow containment for display math).

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/index.css
Comment on lines 1 to +2
@import "tailwindcss";
@import "katex/dist/katex.min.css";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two issues with this import:

  1. Stylesheet/renderer version mismatch. apps/web/package.json pins katex: ^0.18.4 (this import resolves to 0.18.4's CSS), but the markup is produced by rehype-katex@7, which renders with its own katex@0.16.47 per pnpm-lock.yaml. KaTeX requires the CSS to match the JS version that generated the HTML; two majors apart risks wrong metrics/fonts and ships a duplicate copy. Consider aligning the direct katex dependency with the version rehype-katex resolves (or importing the CSS from that copy).
  2. No overflow containment for display math. Every other wide generated-markdown block in this file is contained — .chat-markdown pre uses overflow-x: auto, tables use the scroll-fade container — but .katex-display has none, so a long equation pushes past the fixed message column. Consider adding .chat-markdown .katex-display { overflow-x: auto; overflow-y: hidden; } next to the other chat-markdown rules.

Posted via Macroscope — UI Consistency

Comment on lines +215 to +217
const CHAT_MARKDOWN_REMARK_PLUGINS = [
remarkGfm,
remarkMath,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

remarkMath defaults to singleDollarTextMath: true, so any line containing two $ now parses as inline math. In a coding-agent chat that is common prose: export $HOME and $PATH first or it costs $5, not $10 will render as KaTeX italics instead of literal text, changing how existing messages display (bare $VAR outside backticks is frequent). Consider passing { singleDollarTextMath: false } to remarkMath in both plugin lists and requiring $$…$$ / \(…\) for inline math (the \(…\) normalization would then need to emit $$…$$). The new test only covers a single $ on the line, so it does not catch this case.

Posted via Macroscope — UI Consistency

Comment on lines +203 to +206
"className",
],
span: [...(defaultSchema.attributes?.span ?? []), "className"],
div: [...(defaultSchema.attributes?.div ?? []), "className"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Adding a bare "className" allows any class on code, span, and div, so with parseRawHtml message content can apply arbitrary Tailwind utilities inside .chat-markdown (e.g. <div class="fixed inset-0 z-50 bg-background">) and take over app-owned styling and layout; it also lets content-supplied language-* / chat-markdown-* classes reach extractFenceLanguage and the codeblock chrome. rehypeKatex runs after rehypeSanitize, so KaTeX's own output is never sanitized — only remark-math's wrapper classes need to survive. Consider restricting the additions to the value-list form (["className", "math", "math-inline", "math-display"]) instead of allowing every class.

Posted via Macroscope — UI Consistency

Comment on lines +179 to +192
export function normalizeMathDelimiters(text: string): string {
// Normalize LaTeX display \[ ... \] to $$ ... $$ and inline \( ... \) to $ ... $
// without touching fenced code blocks (``` ... ``` or ` ... `)
const segments = text.split(/(```[\s\S]*?```|`[^`\n]*`)/g);
return segments
.map((segment, index) => {
// Odd indices are code fences or inline code
if (index % 2 === 1) return segment;
return segment
.replace(/\\\[([\s\S]*?)\\\]/g, (_, math) => `$$\n${math.trim()}\n$$`)
.replace(/\\\(([\s\S]*?)\\\)/g, (_, math) => `$${math.trim()}$`);
})
.join("");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rewriting the raw source string has two behavior problems:

  • \[, \], \(, \) are also standard markdown escapes for literal brackets/parens, so existing content like see \[1\] or match \(group\) now becomes math (or a red KaTeX parse error) instead of the literal characters it used to render.
  • The split only protects triple-backtick fences and single-backtick spans. ~~~ fences, 4+ backtick fences, 4-space indented code, and — during streaming — a fence whose closing ``` has not arrived yet are all rewritten, so a code block visibly flips presentation mid-stream and settles back when the fence closes.

Consider doing this as a small remark plugin that visits only text nodes (then code, inlineCode, and html nodes are untouched by construction, regardless of fence style or streaming state) and requiring \[ to open a line before treating it as display math.

Posted via Macroscope — UI Consistency

Comment thread apps/web/package.json Outdated
Comment thread apps/web/src/components/ChatMarkdown.tsx
@macroscopeapp

macroscopeapp Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds significant new LaTeX rendering capability with complex text preprocessing logic. Multiple unresolved review comments identify substantive issues: a High severity security concern allowing arbitrary className values to spoof UI, plus Medium severity issues with text/URL corruption. These findings warrant human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the LaTeX rendering path for markdown/UI consistency. The earlier concerns (single-$ math, source-string rewriting outside the AST, the broad className allowance, and the katex CSS/renderer version mismatch) all look addressed. Three remaining issues in the changed lines: two mangle the rendered equation for common LLM output shapes, and one leaves display math without the horizontal-overflow containment every other wide chat-markdown surface has.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/index.css
Comment on lines +273 to +276
const value = node.children
.map((child: Text) => originalNodeText(child, source) ?? child.value)
.join("");
const standaloneDisplay = /^(?:\\\[|\[)\s*\n([\s\S]*?)\n\s*(?:\\\]|\])$/.exec(value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rebuilding the value from source offsets brings the container markers back with it. For > \[\n> E = mc^2\n> \] the paragraph's source span is \[\n> E = mc^2\n> \]: the standalone pattern cannot match (\s* can't cross > ), the \[...\] fallback wins, and the math value becomes > E = mc^2, so the equation renders with a stray > relation in front. The list-item case only works because its continuation prefix is whitespace, and the new blockquote test passes because it only asserts the katex-display class.

Smallest fix: strip the block-quote continuation prefix per line before matching, e.g. value.replace(/\n[ \t]*>[ \t]?/g, "\n") when the paragraph's ancestor is a blockquote, and assert the rendered math text (not just the class) in the blockquote test so it stays covered.

Separately, this pattern also accepts unescaped [ … ], so a plain bracketed block on its own lines (a pasted JSON array, for example) now renders as display math; requiring \[/\] here would keep non-math brackets rendering as prose.

Posted via Macroscope — UI Consistency

Comment on lines +241 to +242
// react-markdown doubles backslashes in the vfile source used by positions.
return source.slice(start, end).replace(/\\\\/g, "\\");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This collapse rewrites every \\ in the sliced source, including LaTeX row separators, so \[ \begin{aligned} x &= 1 \\ y &= 2 \end{aligned} \] reaches KaTeX as ... x &= 1 \y &= 2 ... and renders as a red katex-error instead of the equation (matrices/aligned blocks are very common in model output).

The doubling being compensated for is not from react-markdown: the JSX fixtures pass delimiters as string attributes (text="... \\(E = mc^2\\)"), and JSX attribute literals do not process escape sequences, so those fixtures genuinely contain two backslashes. The array-based fixtures in the same test file ("> \\[\n...", i.e. real single backslashes) render fine without this replace. Suggest dropping it and changing the JSX fixtures to String.raw / {"..."} so they carry the same single backslashes real messages do.

Suggested change
// react-markdown doubles backslashes in the vfile source used by positions.
return source.slice(start, end).replace(/\\\\/g, "\\");
return source.slice(start, end);

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated
Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated
Comment thread apps/web/src/components/ChatMarkdown.tsx
Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two findings on the math rendering path. The KaTeX overflow point from the previous run (apps/web/src/index.css:2) is still open and is not repeated here.

Posted via Macroscope — UI Consistency

const CHAT_MARKDOWN_REHYPE_PLUGINS = [
rehypeRaw,
[rehypeSanitize, CHAT_MARKDOWN_SANITIZE_SCHEMA],
rehypeKatex,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

KaTeX output breaks this surface's clipboard contract. .chat-markdown installs handleCopychatMarkdownClipboardPayloadserializeNode (apps/web/src/markdown-clipboard.ts), which unwraps unknown elements and skips aria-hidden="true" subtrees. For <span class="katex"> that drops the visual .katex-html and walks .katex-mathml, emitting both the MathML glyph text and the <annotation encoding="application/x-tex"> source, so copying The formula is \(E = mc^2\) yields The formula is E=mc2E = mc^2 instead of the formula.

Smallest fix that keeps ownership in the serializer: handle .katex explicitly there by emitting the annotation[encoding="application/x-tex"] text wrapped in \(…\) / $$…$$. Alternatively set data-markdown-copy on the rendered math (the serializer already honors that attribute, and it wins over the aria-hidden skip).

Posted via Macroscope — UI Consistency

const value = node.children
.map((child: Text) => originalNodeText(child, source) ?? child.value)
.join("");
const standaloneDisplay = /^(?:\\\\|\\)\[\s*\n([\s\S]*?)\n\s*(?:\\\\|\\)\]$/.exec(value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The bare-[...] half of this pattern is fixed, but blockquote continuation markers still leak into the math value. For > \[\n> E = mc^2\n> \] the paragraph's source span is \[\n> E = mc^2\n> \]; \s* cannot cross > , so the standalone pattern fails, the \[...\] fallback below wins, and the math value becomes > E = mc^2 — KaTeX renders a stray > relation in front of the equation. The new blockquote test passes because it only asserts the katex-display class.

Smallest fix: strip the blockquote continuation prefix per line before matching (e.g. value.replace(/\n[ \t]*>[ \t]?/g, "\n") when the paragraph is inside a blockquote), and assert the rendered math text — not just the class — in the blockquote tests so it stays covered.

Posted via Macroscope — UI Consistency

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 21, 2026
Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One rendering regression found in the new LaTeX transform: blockquote continuation markers (and character references) leak into the non-math text that surrounds inline/display math, because those text tokens are rebuilt from the raw source slice. Details inline.

The KaTeX CSS additions (.katex-display margin in the shared block-spacing rule, overflow container + scrollbar treatment mirroring .chat-markdown pre) are correctly scoped to the generated markdown surface and consistent with the existing .chat-markdown ownership, and the clipboard serializer now round-trips math, so no findings there.

Posted via Macroscope — UI Consistency

Comment on lines +369 to +429
const nodes: PhrasingContent[] = [];
for (const token of tokens) {
if (token.type === "text") {
const val = unescapeMarkdownText(token.raw);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Blockquote continuation markers leak into the prose around inline math. Because a text node containing \( is re-derived from the raw source, its > line prefixes come with it: for

> Text \(x\) and more
> next line

the text node spans Text \(x\) and more\n> next line, so the tokens are Text and and more\n> next line, and the rendered quote shows a literal > after the equation. cleanMathFormula strips markers only from the math value, and the new blockquote tests only assert the math side (katex-display, the annotation), so this slips through. The same applies to the text tokens in splitParagraphDisplay (line 420).

Smallest fix: thread inBlockquote into transformPhrasing (the paragraph/heading/tableCell cases already know it) and strip \n[ \t]*>[ \t]? from each text token's raw before unescapeMarkdownText — without trimming, so the spaces adjacent to the math survive.

Same raw-source path, worth handling together: character references are no longer decoded for these tokens, so Costs &amp; \(x\) renders the literal &amp; next to the equation.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated

function unescapeMarkdownText(raw: string): string {
const unescapedBackslashes = raw.replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g, "$1");
return decodeCharacterReferences(unescapedBackslashes);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/ChatMarkdown.tsx:294

When display math triggers reconstruction, unescapeMarkdownText changes adjacent text: α remains literal instead of becoming α, and escaped \& becomes & instead of remaining &. The hand-written entity table omits valid named references, while decoding after removing backslash escapes incorrectly treats escaped references as Markdown entities; reuse the parser’s already-decoded text or a Markdown-compliant decoder that preserves escaped references.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatMarkdown.tsx around line 294:

When display math triggers reconstruction, `unescapeMarkdownText` changes adjacent text: `α` remains literal instead of becoming `α`, and escaped `\&` becomes `&` instead of remaining `&`. The hand-written entity table omits valid named references, while decoding after removing backslash escapes incorrectly treats escaped references as Markdown entities; reuse the parser’s already-decoded text or a Markdown-compliant decoder that preserves escaped references.

Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated
Comment thread apps/web/src/components/ChatMarkdown.tsx Outdated
Comment thread apps/web/src/markdown-clipboard.ts
Support inline LaTeX \(...\) and block display LaTeX \[...\] / 79210...79210 delimiters in ChatMarkdown without mutating code blocks or ambient prose.

Includes clean markdown math preprocessing, KaTeX styling and overflow containment, and markdown clipboard copy serialization.
@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 21, 2026
]);
/* eslint-enable react/no-unstable-nested-components */

const preprocessedText = useMemo(() => preprocessLatexDelimiters(text), [text]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/ChatMarkdown.tsx:1931

preprocessLatexDelimiters(text) rewrites $ inside Markdown link destinations, so [docs](https://example.test/search?q=$term) renders an anchor with a different URL (for example, an encoded backslash) and navigation breaks. Apply the delimiter preprocessing only to text content, or otherwise preserve link destinations while transforming math delimiters.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatMarkdown.tsx around line 1931:

`preprocessLatexDelimiters(text)` rewrites `$` inside Markdown link destinations, so `[docs](https://example.test/search?q=$term)` renders an anchor with a different URL (for example, an encoded backslash) and navigation breaks. Apply the delimiter preprocessing only to text content, or otherwise preserve link destinations while transforming math delimiters.

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the LaTeX rendering changes for UI/layout consistency in apps/web/src. Three findings, all in changed lines: mid-line \[…\] promotion breaks the inline markup and list structure it sits inside, the new .katex-display scroll container clips the left half of over-wide equations, and blanket $ escaping corrupts indented code blocks. Two of the new tests that look like they cover these cases are vacuous because of JSX/JS backslash escaping.

Posted via Macroscope — UI Consistency

Comment on lines +295 to +297
// Escape unescaped $ that is not math delimiter so remark-math does not parse currency/variables
if (raw[i] === "$" && !isEscaped) {
out += "\\$";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

codeRegex protects fences and inline spans but not 4-space indented code blocks, and every unescaped $ in the unprotected text becomes \$, so an indented code sample renders with visible backslashes: echo $HOME displays as echo \$HOME (and \(…\) in such a block becomes real math).

The it.each case that looks like it covers this is vacuous: " const x = \\\\(notMath\\\\);" is a JS string literal, so the component receives two backslashes and the delimiter is skipped as escaped regardless of the code context.

Consider having preprocessLatexDelimiters also skip indented code blocks (4-space indent at a block boundary, not list-item continuation) before escaping $.

Posted via Macroscope — UI Consistency

Comment on lines +252 to +264
const lineStart = Math.max(0, raw.lastIndexOf("\n", i) + 1);
const linePrefix = raw.slice(lineStart, i);
const isBlockquote = /^[ \t]*>[ \t]?/.test(linePrefix);

if (isBlockquote) {
const cleanedFormula = formula.replace(/\n[ \t]*>[ \t]?/g, "\n").trim();
const blockLines = cleanedFormula
.split("\n")
.map((line) => `> ${line}`)
.join("\n");
out += `\n>\n> $$\n${blockLines}\n> $$\n>\n`;
} else {
out += "\n\n$$\n" + formula.trim() + "\n$$\n\n";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mid-line \[…\] is unconditionally promoted to a block, which breaks whatever inline markup it sits inside.

  • **Result: \[x^2\]** is emitted as **Result: \n\n$$\nx^2\n$$\n\n**, so the strong never closes and the message shows a literal **.
  • [Formula \[x^2\]](https://example.com) loses the link entirely and renders ](https://example.com) as text; a table row is split the same way.
  • Inside a list item the block is emitted at column 0 (- Item:\n \[\n x = 1\n \] → blank line + $$ at column 0), which terminates the list: the equation is no longer indented under the item and following items begin a new list.

The new test meant to cover the first case is vacuous — JSX attribute strings do not process escapes, so text="**Result: \\[x^2\\]**" reaches the component with two backslashes and is treated as escaped, never entering the math path (use String.raw in a template expression, as the other tests do).

Smallest fix: promote to a $$ block only when the \[ owns its line (whitespace or blockquote prefix before it, nothing but whitespace after the closing \]), reusing that line's leading indentation for the emitted block lines; otherwise keep the math inline.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/index.css
Comment on lines +2184 to +2189
.chat-markdown .katex-display {
max-width: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--border) 78%, transparent) transparent;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

KaTeX sets text-align: center on both .katex-display and .katex-display > .katex (with white-space: nowrap), and inline-start overflow is not part of the scrollable overflow region, so an equation wider than the container is centered and its left portion is permanently unreachable — the new scrollbar only exposes the right side. .chat-markdown pre avoids this because its content is start-aligned.

Smallest fix: start-align the scroll content, e.g. add text-align: left here plus .chat-markdown .katex-display > .katex { text-align: left; }, or center with display: flex; justify-content: safe center; on the container so narrow equations stay centered while wide ones scroll from their left edge.

Posted via Macroscope — UI Consistency

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1841ccc. Configure here.

}

result += transformLatexDelimitersInText(text.slice(lastIndex));
return result;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Indented code still becomes math

Low Severity

preprocessLatexDelimiters only skips fenced (``` / ~~~) and inline backtick spans. Four-space indented code blocks are still run through transformLatexDelimitersInText, so literal `(...)` or `[...]` inside indented code can be rewritten into `$` / `$$` math before remark parses the fence.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1841ccc. Configure here.

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant