Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/app-bundle/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"')
Expand Down Expand Up @@ -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"')
})
Original file line number Diff line number Diff line change
Expand Up @@ -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 = /(?<![\\$])\$(?!\$)/
const singleDollarTokenizerRegex = /^\$(?!\$|\s|\d)((?:\\.|[^$\\\n])+?)(?<![\\\s])\$(?!\$)/
const singleDollarInlineRegex = /(?<![\\$])\$(?!\$|\s|\d)((?:\\.|[^$\\\n])+?)(?<![\\\s])\$(?!\$)/g
const singleDollarTokenizerRegex = /^\$(?!\$|\s)((?:\\.|[^$\\\n])+?)(?<![\\\s])\$(?!\$|\d)/
const singleDollarInlineRegex = /(?<![\\$])\$(?!\$|\s)((?:\\.|[^$\\\n])+?)(?<![\\\s])\$(?!\$|\d)/g

export function renderMathInText(text: string): string {
let result = text
Expand Down
Loading