From c33e7bf2b2b9a509fd44329d63a544502e7bc6ad Mon Sep 17 00:00:00 2001 From: Aaron Trowbridge Date: Wed, 7 Oct 2026 18:39:37 -0400 Subject: [PATCH 1/2] =?UTF-8?q?fix(ui):=20render=20digit-led=20single-$=20?= =?UTF-8?q?math=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 --- packages/ui/src/context/marked-math.test.ts | 23 +++++++++++++++++++++ packages/ui/src/context/marked.tsx | 13 +++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/context/marked-math.test.ts b/packages/ui/src/context/marked-math.test.ts index 05338bcb2c..313cd6e2b4 100644 --- a/packages/ui/src/context/marked-math.test.ts +++ b/packages/ui/src/context/marked-math.test.ts @@ -17,6 +17,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 () => { + // #375 — the open-side digit guard rejected any math starting with a digit, + // which is 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"') @@ -66,6 +85,10 @@ test("renderMathInText renders single-$ inline and spares prose dollars", () => expect(prices).toContain("$5") expect(prices).toContain("$10") + // #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/ui/src/context/marked.tsx b/packages/ui/src/context/marked.tsx index e4a00db7da..47fda78461 100644 --- a/packages/ui/src/context/marked.tsx +++ b/packages/ui/src/context/marked.tsx @@ -420,12 +420,15 @@ registerCustomTheme("OpenCode", () => Promise.resolve(OpenCodeTheme)) // 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 = /(? Date: Wed, 7 Oct 2026 18:49:33 -0400 Subject: [PATCH 2/2] fix(test): widen header lookup in harmoniqs idempotency tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #374 indexes Idempotency-Key/X-Session-Id on the static per-provider header union, which only carries the harmoniqs branch — the test file typechecks red at the branch base. Widen through a Record so the suite (and the pre-push typecheck gate) passes. --- .../test/provider/harmoniqs-no-tools.test.ts | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/opencode/test/provider/harmoniqs-no-tools.test.ts b/packages/opencode/test/provider/harmoniqs-no-tools.test.ts index b3b25aeecb..2227ef6e59 100644 --- a/packages/opencode/test/provider/harmoniqs-no-tools.test.ts +++ b/packages/opencode/test/provider/harmoniqs-no-tools.test.ts @@ -99,25 +99,33 @@ describe("LLMRequestPrep.prepare - harmoniqs idempotency", () => { } } + // The per-provider header union carries the static shape only — harmoniqs + // injects Idempotency-Key at runtime (#374) — so tests index through a + // widened record rather than the narrowed type. + async function prepareHeaders(model: any) { + const result = await Effect.runPromise(LLMRequestPrep.prepare(baseInput(model))) + return result.headers as Record + } + test("sends Idempotency-Key header for harmoniqs provider", async () => { - const result = await Effect.runPromise(LLMRequestPrep.prepare(baseInput(harmoniqsModel))) - expect(result.headers["Idempotency-Key"]).toBeDefined() - expect(result.headers["Idempotency-Key"]).toMatch(/^amicode:/) + const headers = await prepareHeaders(harmoniqsModel) + expect(headers["Idempotency-Key"]).toBeDefined() + expect(headers["Idempotency-Key"]).toMatch(/^amicode:/) }) test("sends X-Session-Id header for harmoniqs provider", async () => { - const result = await Effect.runPromise(LLMRequestPrep.prepare(baseInput(harmoniqsModel))) - expect(result.headers["X-Session-Id"]).toBe(sessionID) + const headers = await prepareHeaders(harmoniqsModel) + expect(headers["X-Session-Id"]).toBe(sessionID) }) test("does not send Idempotency-Key for non-harmoniqs providers", async () => { - const result = await Effect.runPromise(LLMRequestPrep.prepare(baseInput(anthropicModel))) - expect(result.headers["Idempotency-Key"]).toBeUndefined() + const headers = await prepareHeaders(anthropicModel) + expect(headers["Idempotency-Key"]).toBeUndefined() }) test("generates unique Idempotency-Key per request", async () => { - const a = await Effect.runPromise(LLMRequestPrep.prepare(baseInput(harmoniqsModel))) - const b = await Effect.runPromise(LLMRequestPrep.prepare(baseInput(harmoniqsModel))) - expect(a.headers["Idempotency-Key"]).not.toBe(b.headers["Idempotency-Key"]) + const a = await prepareHeaders(harmoniqsModel) + const b = await prepareHeaders(harmoniqsModel) + expect(a["Idempotency-Key"]).not.toBe(b["Idempotency-Key"]) }) })