🩹 fix: Restore the Client Build by Parsing Single-Dollar Math via Micromark (#15638)

`markdownToHtml` (#15087) imported `preprocessLaTeX` from `client/src/utils/latex.ts`,
but #15181 replaced that preprocessing pass with the currency-safe `singleDollarMath`
micromark construct and removed the function. The symbol exists nowhere in the repo, so
`dev` fails `tsc --noEmit` with TS2305 and the Vite build with
`[MISSING_EXPORT] "preprocessLaTeX" is not exported by "src/utils/latex.ts"`, which reds
out the client typecheck, the build verification, and every job downstream of the client
build on `dev` and on every open pull request.

Register `singleDollarMath` in the extension list when `mode.latex` is set instead. That
is exactly what the message renderers do — `markdownConfig.ts` adds `remarkSingleDollarMath`
only when `latexParsing` is on, and that plugin does nothing but push `singleDollarMath`
into `micromarkExtensions` — so clipboard HTML keeps mirroring the rendered message, which
is the parity this helper is built around. `markdownToHtml` parses with `fromMarkdown`
directly rather than through unified, so the extension array is the equivalent seam.

Both specs already pinned the intended behavior and pass unchanged: `$E=mc^2$` becomes
inline math under `latex: true`, `$5 to $10` stays currency. Their titles said
"preprocesses", which no longer names a mechanism that exists; they now say "parses".
This commit is contained in:
Marco Beretta 2026-09-05 14:16:17 +02:00 • committed by GitHub
parent f174da6159
commit f4e967bf22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 12 additions and 10 deletions

View file

@ -113,14 +113,14 @@ describe('markdownToHtml', () => {
);
});
it('renders single-dollar math when the renderer preprocesses LaTeX', () => {
it('renders single-dollar math when the renderer parses LaTeX', () => {
expect(markdownToHtml('Energy is $E=mc^2$ inline.', { variant: 'full', latex: true })).toBe(
'<p>Energy is E=mc^2 inline.</p>',
);
expect(markdownToHtml('Energy is $E=mc^2$ inline.')).toBe('<p>Energy is $E=mc^2$ inline.</p>');
});
it('still leaves currency alone with LaTeX preprocessing on', () => {
it('still leaves currency alone with LaTeX parsing on', () => {
expect(markdownToHtml('Costs rose from $5 to $10.', { variant: 'full', latex: true })).toBe(
'<p>Costs rose from $5 to $10.</p>',
);

View file

@ -20,7 +20,7 @@ import type {
import type { Extension as MicromarkExtension } from 'micromark-util-types';
import { mcpUIResourcePlugin } from '~/components/MCPUIResource/plugin';
import { remarkApproxTilde } from './tilde';
import { preprocessLaTeX } from './latex';
import { singleDollarMath } from './latex';
/**
* Which message renderer this copy has to match. `Markdown` (assistant turns)
@ -404,10 +404,11 @@ function serializeChildren(nodes: readonly SerializableNode[], context: Serializ
* the clipboard as `text/html`. Paste targets that ignore Markdown (Teams,
* Outlook, Word) strip stylesheets, so every visual cue has to be inline.
*
* Parsing mirrors the message renderers: the same LaTeX preprocessing, the same
* micromark extensions with `singleDollarTextMath` off so currency such as
* `$5 to $10` stays currency, then the same `remarkApproxTilde` and
* `remark-supersub` transforms they apply to the parsed tree.
* Parsing mirrors the message renderers: the same currency-safe single-dollar
* math extension, the same `micromark-extension-math` with `singleDollarTextMath`
* off so currency such as `$5 to $10` stays currency, then the same
* `remarkApproxTilde` and `remark-supersub` transforms they apply to the parsed
* tree.
*/
export function markdownToHtml(
markdown: string,
@ -417,10 +418,11 @@ export function markdownToHtml(
if (mode.variant === 'full') {
extensions.push(directive());
}
if (mode.latex) {
extensions.push(singleDollarMath);
}
const source = mode.latex ? preprocessLaTeX(markdown) : markdown;
const tree = fromMarkdown(source, {
const tree = fromMarkdown(markdown, {
extensions,
mdastExtensions: [gfmFromMarkdown(), directiveFromMarkdown(), mathFromMarkdown()],
});