Files
AFFiNE-Mirror/blocksuite/affine
Anand Hegde 42457ef6f7 fix(latex): keep inline math that starts with a digit (#15588) (#15596)
Fixes #15588.

## Root cause

`preprocessLatex` escapes any `$` that is followed by a digit, assuming
it is a price:

```ts
// Escape dollar signs that are likely currency indicators
preprocessedContent = preprocessedContent.replace(/\$(?=\d)/g, '\\$');
```

The step above it shields existing LaTeX from that escape — but only
`$$…$$`, `\[…\]` and `\(…\)`. **Single-dollar inline math was never
protected.**

So for `$4\vee 6=12$`:

1. the protection pass does not match it,
2. the currency pass sees `$4` and rewrites it to `\$4`,
3. the expression never parses as math, and the closing `$` is left
orphaned, which throws off the parse of everything after it.

That last part is why a single such expression breaks the rest of the
document, as the report describes. It also explains the reporter's `{ }`
workaround: `${4}` is `$` followed by `{`, not a digit, so the escape
never fires.

## The fix

Protect single-dollar inline math too, using the usual rule for telling
math from prices:

- the opening `$` is not followed by whitespace,
- the closing `$` is not preceded by whitespace,
- the closing `$` is not followed by a digit.

```ts
/(\$\$[\s\S]*?\$\$|\\\[[\s\S]*?\\\]|\\\(.*?\\\)|\$(?!\s)[^\n$]*?(?<!\s)\$(?!\d))/g
```

Currency is unaffected, which is the part worth checking:

- `costs $5 and $10 today` — the only closing candidate is the `$`
before `10`, and it is preceded by a space, so no match; both escape as
before.
- `$100$200` — the closing candidate is followed by `2`, so no match;
both escape as before.
- `it costs $5.00 total` — no closing `$` at all.

`$$…$$` stays first in the alternation, so display math is still matched
as display math.

## Verification

New
`blocksuite/affine/all/src/__tests__/adapters/latex-preprocessor.unit.spec.ts`,
11 cases: the digit-first expressions from the issue, the currency cases
above, and guards for display math, a `$` followed by whitespace,
`\(…\)` rewriting, and code spans.

Reverting **only** the regex (keeping the export so the module still
loads) fails exactly the four digit-first tests and leaves the other
seven green:

```
× leaves binary operators untouched
× leaves relation untouched
× leaves single digit untouched
× keeps every expression in a sentence mixing math and prose
  Tests  4 failed | 7 passed (11)
```

That the currency and existing-behaviour cases pass **without** the fix
is the point — they are regression guards, not props for it.

With the fix:

```
 Test Files  14 passed (14)
      Tests  234 passed (234)
```

(the whole `blocksuite/affine/all` suite, which includes the 193
existing adapter tests).

## Notes

- `preprocessLatex` is now exported so the delimiter rule can be tested
directly. It was previously module-private with only the extension
exported; `adapters/markdown/index.ts` already does `export * from
'./preprocessor.js'`, so nothing else changed.
- I kept the change to the one regex and did not reformat the rest of
the file — there is no Prettier config in the repo, and running Prettier
with its defaults rewrites quotes and trailing commas across the whole
file.
- The lookbehind `(?<!\s)` is ES2018; the file already targets modern
runtimes.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Improved Markdown LaTeX preprocessing for inline expressions preceded
by even-length backslash runs.
- Continued to distinguish escaped dollar signs from valid math
delimiters, while preserving currency values, display math, `\( \)`
expressions, whitespace, mixed content, and code spans.

- **Tests**
- Added comprehensive coverage for inline and display math, delimiter
escaping, currency values, whitespace, conversions, mixed content,
digit-leading expressions, and code-span protection.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-19 19:27:06 +08:00
..