Skip to content

fix(ui): render dollar math next to punctuation without matching prices - #53860

Merged
opencode-agent[bot] merged 2 commits into
v2from
dollar-math-boundaries
Oct 8, 2026
Merged

opencode-agent[bot] merged 2 commits into
v2from
dollar-math-boundaries

Conversation

@opencode-agent

@opencode-agent opencode-agent Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Problem

marked-katex-extension only opens $...$ at the start of the text or after a space, and only closes it before whitespace or ? ! . , :. Agents routinely put math against punctuation, so text such as ack ($440\text{ ms}$ IPC p50, …) renders as raw LaTeX. The same rule also rejects the $x$-axis, is $x$; y, and 因此$x^2$是正数.

Its inline start also retries the regex from each $ on every text token, and that regex can scan to the end of the line. Paragraphs with many $ therefore take seconds or minutes to lex. This happens in both the markdown worker and the live streaming lexer.

Change

Replace the extension with three small marked extensions in marked-parser.tsx, and drop the marked-katex-extension dependency.

  • $...$ follows Pandoc's tex_math_dollars boundaries:
    • The opening $ must be followed by a non-space.
    • The closing $ must follow a non-space, and must not be followed by a letter, digit, or underscore.
    • The character before the opening $ does not matter.
    • Pandoc only forbids a following digit. The extra letter/underscore rule keeps $HOME/$USER and $PATH:$HOME as text. The cost is that $n$th does not render, which is already true today.
  • $$...$$ renders as display math anywhere in a line.
  • \(...\) keeps its existing tokenizer.
  • $$ / $ blocks keep the extension's block regex unchanged.

Now renders:

  • ($440\text{ ms}$), (i.e. $x$), the $x$-axis, is $x$; y, $x$'s, 因此$x^2$是正数, ($$x$$)

Stays text:

  • prices: $5 and $10., $5-$10, $5, originally $10, $5 (or $6), $5/$6., between $5 and $6 dollars
  • shell variables: $HOME and $PATH, $HOME/$USER, $PATH:$HOME
  • code spans: `$x$`
  • escaped dollars: \$x$
  • $ x $ (whitespace inside the delimiters is now rejected, as in Pandoc)

Regex performance

/\$\$(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n$]))\$\$|\$(?=[^\s$])((?:\\.|[^\\\n$])*?(?:\\\S|[^\s\\$]))\$(?!\w)/y

Single match attempt

  • The repeated alternatives cannot overlap: \\. starts with a backslash and the other class excludes it. There are no nested quantifiers, and the trailing unit is tried once per position, so backtracking is linear.
  • $...$ content cannot contain an unescaped $. A failed attempt stops at the next $.
  • A failed $$...$$ attempt stops at the end of the line. Any later reachable $$ on that line would close it successfully.
  • recheck reports the new regex as safe.

Across a paragraph

  • marked calls start(src) again for every text token, passing the rest of the paragraph. The extension's start retried the regex from every $ in that remainder, which is O(tokens × $ count × line length).
  • start now returns the next $, like the existing \( extension. When the tokenizer fails there, marked merges the text tokens back together.
  • Reference check: the lexer output from this version is identical to one using a regex-validated start, across 300k fuzzed strings and every .md file in this repo and anomalyco/anomaly.

Benchmark

Median lexer time with Bun 1.4.2 and the repo's marked:

  • none: marked with only the \( extension
  • old: marked-katex-extension 5.1.13 plus \(
  • new: this PR
input size none old new
realistic agent prose 100 KB 9.8 ms 14 ms 15 ms
`a` $5 repeated 8.7 KB 1.9 ms 15 s 4.1 ms
It costs $5, repeated 108 KB 108 ms 6.8 s 136 ms
$a repeated 100 KB 95 ms 11 s 187 ms
$$a\$ repeated 100 KB 159 ms 669 s 132 ms
$a\\\ repeated 8.6 KB 1.8 ms 13.7 s 4.4 ms
\$$a repeated 100 KB 173 ms n/a 180 ms

Across every shape tested, new stays within about 2× of none. The remaining superlinear growth on single-line 100 KB paragraphs comes from marked itself: none grows from 0.45 ms at 10 KB to about 97 ms at 100 KB.

A guard test lexes six of these shapes at 10 KB within a 500 ms budget. It takes about 36 ms with this change and about 50 s if start retries the regex from every $.

Tests

  • bun test src in packages/ui and packages/session-ui
  • tsgo typecheck for both packages
  • I could not run the Playwright component tests locally (the shared host ran out of threads). The math story only uses \( and $$ blocks.

Requested by: @Brendonovich (Brendan via Slack)

@opencode-agent
opencode-agent Bot merged commit 9e100f4 into v2 Oct 8, 2026
9 checks passed
@opencode-agent
opencode-agent Bot deleted the dollar-math-boundaries branch October 8, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant