From fd58a6a17d241c353dd4a6de3c088db367abab21 Mon Sep 17 00:00:00 2001 From: Aaron Trowbridge Date: Wed, 7 Oct 2026 19:15:40 -0400 Subject: [PATCH] =?UTF-8?q?fix(app-bundle):=20digit-led=20single-$=20math?= =?UTF-8?q?=20=E2=80=94=20move=20currency=20guard=20to=20close=20side?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port of harmoniqs/opencode#376 (closes #1725). The single-$ inline-math rule rejected any math whose opening $ was followed by a digit; move the digit guard to the close side (Pandoc's actual currency rule). Overlay is post-absorption source of truth, so the fix lands directly in marked-parser.tsx with the manifest refreshed (851 files, drift gate green). Regression tests: the two reported digit-led snippets + tight $30-and-$50 currency pair, verified in the materialized tree (10/10). --- packages/app-bundle/manifest.json | 6 ++--- .../ui/src/context/marked-math.test.ts | 23 +++++++++++++++++++ .../packages/ui/src/context/marked-parser.tsx | 13 +++++++---- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/app-bundle/manifest.json b/packages/app-bundle/manifest.json index 8e65c7f34..0679c1a54 100644 --- a/packages/app-bundle/manifest.json +++ b/packages/app-bundle/manifest.json @@ -584,8 +584,8 @@ "packages/ui/src/components/toast.css": "60c06eda9f0ccc5641c03c002a6080db99c00d797dbdf7511c789089efc1c546", "packages/ui/src/components/tooltip.css": "a615df9b86e64c1565384baa8054c2f80ebad11856d33f5e6f53902541eacef2", "packages/ui/src/context/dialog.tsx": "876f8a5dc26ebb98f1f5e9e528e7d0bd7aa79c0a107b62027dd9fe78ed2cc620", - "packages/ui/src/context/marked-math.test.ts": "6edcbe85175366da339d8d921b0f98834e95ea9cf500e91dfae7c2c738bdf902", - "packages/ui/src/context/marked-parser.tsx": "90b79a684f9c0b7afff63bb5776512779eb3912e76f85a7bb2e2d97bb575d4b9", + "packages/ui/src/context/marked-math.test.ts": "0a53b2701d79ef16abe3132a514831ada60a535324991e4d72ee4e0ced9a0ae6", + "packages/ui/src/context/marked-parser.tsx": "2d8d0c2e5e28278f54296640d1e851fff4ef3b2902c7f70d02cdbcd82545bf39", "packages/ui/src/i18n/ar.ts": "8113d320fe4a5300f1bd70150ae1695d68cb783e1c6e2be43b8a429b7dbdb711", "packages/ui/src/i18n/br.ts": "cbde2da43ef820fe2664cb96cb973fd722156a6c8a3c5e16d18638bf014e5196", "packages/ui/src/i18n/bs.ts": "23ea0ae34a4edaa4599b13f63f481be861e28da3b0d898987e291c7e91a9f11e", @@ -873,7 +873,7 @@ "packages/ui/src/v2/components/text-shimmer-v2.tsx" ], "exceptions": [], - "extracted_at": "2026-09-24T04:46:38.771Z", + "extracted_at": "2026-10-07T23:13:00.379Z", "per_package": { "packages/app": { "A": 186, diff --git a/packages/app-bundle/overlay/packages/ui/src/context/marked-math.test.ts b/packages/app-bundle/overlay/packages/ui/src/context/marked-math.test.ts index c9e97a37e..405b06d5b 100644 --- a/packages/app-bundle/overlay/packages/ui/src/context/marked-math.test.ts +++ b/packages/app-bundle/overlay/packages/ui/src/context/marked-math.test.ts @@ -18,6 +18,25 @@ test("renders tight and loose-inner single-$ math", async () => { expect(await parse("set $\\delta/2\\pi = 0.1$ MHz")).toContain('class="katex"') }) +test("renders digit-led single-$ inline math", async () => { + // opencode#375 — the open-side digit guard rejected any math starting with + // a digit, the standard physics reporting shape ($3.53\times10^{-5}$) + expect(await parse("nice determinism check ($3.53\\times10^{-5}$ — free)")).toContain('class="katex"') + expect(await parse("floor through the mock seam ($3.1\\times10^{-3}$, T1-dominated)")).toContain( + 'class="katex"', + ) + expect(await parse("infidelity $10^{-5}$ at best")).toContain('class="katex"') + expect(await parse("$0.99$-style fidelity")).toContain('class="katex"') +}) + +test("keeps tight digit-pair currency literal under the close-side guard", async () => { + // Pandoc rule: the closing $ must not be followed by a digit — the first $ + // in "$30-and-$50" closes against "3", so the pair stays literal + const html = await parse("prices: $30-and-$50 pairs") + expect(html).not.toContain('class="katex"') + expect(html).toContain("$30-and-$50") +}) + test("keeps currency and env-var dollars literal", async () => { const prices = await parse("costs $5 and $10 total") expect(prices).not.toContain('class="katex"') @@ -67,6 +86,10 @@ test("renderMathInText renders single-$ inline and spares prose dollars", () => expect(prices).toContain("$5") expect(prices).toContain("$10") + // opencode#375 — digit-led math through the global replace, tight pairs still literal + expect(renderMathInText("check ($3.53\\times10^{-5}$ — free)")).toContain('class="katex"') + expect(renderMathInText("prices: $30-and-$50 pairs")).not.toContain('class="katex"') + expect(renderMathInText("$$\n\\hat H\n$$")).toContain("katex-display") expect(renderMathInText("inline \\(\\Omega_x\\) math")).toContain('class="katex"') }) diff --git a/packages/app-bundle/overlay/packages/ui/src/context/marked-parser.tsx b/packages/app-bundle/overlay/packages/ui/src/context/marked-parser.tsx index 327abb601..cc1acc26f 100644 --- a/packages/app-bundle/overlay/packages/ui/src/context/marked-parser.tsx +++ b/packages/app-bundle/overlay/packages/ui/src/context/marked-parser.tsx @@ -35,12 +35,15 @@ export function createMarkdownParser(highlight: (code: string, language: string) // Single-$ inline math — restored after #34850 removed it for currency false // positives. Pandoc-style tight delimiters (no whitespace just inside either -// $), no digit-led content (so $5, and $30-and-$50 pairs, stay literal), no -// $$ adjacency, no escaped \$. One regex family, three shapes: the tokenizer -// start hint, the anchored tokenizer match, and the global replace below. +// $): the opening $ drops the digit-led guard so $3.53\times10^{-5}$ renders; +// instead the closing $ must not be followed by a digit (Pandoc's actual +// currency rule — $30-and-$50 pairs stay literal because the first closing $ +// abuts a digit). No $$ adjacency, no escaped \$. One regex family, three +// shapes: the tokenizer start hint, the anchored tokenizer match, and the +// global replace below. const singleDollarStartRegex = /(?