Skip to content

fix: fix(web): equations with an unescaped percent now typeset - #1

Open
meutex[bot] wants to merge 2013 commits into
mainfrom
meutex/issue-1793641966-typeset-equations-that-fail-only-because
Open

meutex[bot] wants to merge 2013 commits into
mainfrom
meutex/issue-1793641966-typeset-equations-that-fail-only-because

Conversation

@meutex

@meutex meutex Bot commented Nov 2, 2026

Copy link
Copy Markdown

Problem

Chat math rendering falls back to raw TeX whenever a formula contains an unescaped %, a character models routinely emit in labels such as \text{3.18% dividend yield}. TeX treats % as a comment, so KaTeX rejects the formula, and MathTypeset swaps in the source fallback that was built for unfinished streaming formulas — so a finished message is stuck showing raw \[ ... \] TeX forever.

Change

MathTypeset.typeset() keeps its first katex.renderToString attempt exactly as before (throwOnError: true, same options). Only when that throws does it retry once with every unescaped % escaped to \% (tex.replace(/(^|[^\\])%/g, "$1\\%")), and it still falls back to the raw source only if the retry also throws. Already-escaped \% is left alone, and a formula with nothing to escape (no %, or only already-escaped ones) is not re-parsed. The cache key stays ${display?'display':'inline'}:${tex} and the repaired html is stored under the authored formula's key, so the repair is parsed at most once; failures still cache "". The data-markdown-copy payload and source fallback markup are unchanged.

Scope and approval

PR against meutex/t3code (this fork), triaged from the upstream report pingdotgg#29493. This qualifies under CONTRIBUTING.md's "very small, focused fix for an obvious bug" exception: one production file (apps/web/src/components/chat/MathTypeset.tsx) plus its existing browser test, fixing a single character that otherwise leaves a finished equation permanently raw. Desktop inherits the fix because it wraps apps/web. The mobile native renderer (apps/mobile / RaTeX) is out of scope and needs a separate check.

Verification

  • Failing test first: with the source fix temporarily reverted, typesets a display equation whose label has an unescaped percent fails on the raw \[ ... \] source (AssertionError: expected '\[\nR_e=\underbrace{...9.18\%...' to contain '9.18%'). With the fix it passes.
  • Focused test, run from apps/web: vp test run src/components/chat/MarkdownMath.browser.test.tsx — 2 passed (1 existing + 1 new).
  • vp run check:changed: format ✓, lint ✓ (0 warnings / 0 errors), knip ✓, typecheck (2 packages) ✓, test (1 file) ✓, react compiler ✓.
  • Not checked: repo-wide suites (vp check, vp run -r test, vp run -r typecheck are reserved for CI per AGENTS.md), the mobile RaTeX path, and before/after screenshots (the sandbox cannot capture browser screenshots).

Forge (meutex[bot]) — the model and harness that did the work.

Closes #1793641966

@meutex

meutex Bot commented Nov 2, 2026

Copy link
Copy Markdown
Author

PR ready: #1 — fix(web): equations with an unescaped percent now typeset.

MathTypeset.typeset() now retries once with unescaped % escaped to \% when the first KaTeX parse throws, so the report's \text{3.18% dividend yield} equation typesets instead of showing raw \[ ... \] source. Already-parsing formulas are untouched, and genuinely broken/mid-stream formulas still show the source as before.

Verified: new regression test fails on the raw source with the fix reverted, then passes (vp test run src/components/chat/MarkdownMath.browser.test.tsx → 2 passed); vp run check:changed passes (format, lint, knip, typecheck, test, react compiler).

@meutex

meutex Bot commented Nov 3, 2026

Copy link
Copy Markdown
Author

No security findings introduced by #1 (2 file(s) reviewed).

Reviewed 2 changed file(s), 70 changed lines.
Scanners: semgrep, gitleaks, sentrux.
Reviewed against secure-code-review.

Code quality (Sentrux): 1633 → 1633

No findings at or above MEDIUM.

Full report in Meutex

juliusmarminge and others added 28 commits November 11, 2026 21:28
…otgg#34773)

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…gdotgg#34797)

Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
juliusmarminge and others added 29 commits November 12, 2026 11:13
…f can be added (pingdotgg#35492)

Co-authored-by: ixmxvii-hash <235766220+ixmxvii-hash@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…on box (pingdotgg#32682)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: maria-rcks <maria@kuuro.net>
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
…35565)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ingdotgg#32302)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: maria-rcks <maria@kuuro.net>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…ping each key (pingdotgg#35581)

Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
…card (pingdotgg#31115)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: maria-rcks <maria@kuuro.net>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
… test

Moves escapeLiteralPercents into MathTypeset.logic.ts so its cases run as a
millisecond unit test, and drops the browser test rows that re-checked the
same string logic through the DOM.

AI-assisted; verified with the tests and tools listed above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.