LibreChat/client/src
Danny Avila 7c071e244b
🔢 fix: Prevent "approximately" tildes from rendering as markdown subscript (#13743)
* 🔢 fix: Prevent "approximately" tildes from rendering as markdown subscript

`remark-supersub` splits text nodes on every `~`; an even number of tildes
wraps the in-between text in `<sub>`. "Approximately" usage like
`~50% ... ~10%` pairs up and subscripts everything between the two tildes.

A backslash escape cannot fix this: micromark resolves `\~` back to a bare
`~` before supersub runs. Instead, `preprocessTilde` swaps approximation
tildes (a `~` prefixing a number, not attached to a word) for the Unicode
tilde operator `∼` (U+223C), which renders as a tilde but is not split by
supersub. Mirrors `preprocessLaTeX`: early return, single regex pass,
code-region skipping. Genuine subscripts (`H~2~O`, `a ~2~ b`), strikethrough,
escaped tildes, and home paths are preserved.

* 🔢 fix: Harden tilde preprocessing — escaped tildes, URLs, math, MarkdownLite

Addresses Codex review findings:
- Convert escaped approximation tildes too (`\~50%`): markdown decodes `\~`
  to `~` before supersub, so the escape still pairs into a subscript.
- Anchor matches to a prose boundary (start / whitespace / open bracket) and
  exclude `\(`/`\[`/`\{`, so URL path tildes (`/~50`) and math delimiters
  (`$~10$`, `$$~10$$`, `\(~10\)`) are left untouched.
- Apply preprocessTilde in MarkdownLite (user messages + search/subagent/
  code-analysis displays), which also enables remark-supersub.

* 🔧 refactor: Neutralize approximation tildes via remark plugin, not raw-text

Replaces the string-level preprocessTilde with `remarkApproxTilde`, a remark
plugin that rewrites "approximately" tildes (`~50%` → `∼`) on parsed text nodes
before remark-supersub runs.

Because it operates on the AST, code spans, fenced code blocks (backtick *and*
~~~), inline code with any backtick count, link destinations, and math spans are
structurally excluded — none are `text` nodes — resolving every raw-text edge
case Codex flagged without region-scanning heuristics. Escaped `\~` is covered
for free (markdown decodes it before the plugin runs).

- New client/src/utils/tilde.ts: `normalizeApproxTildes` (pure, per-text-node)
  + `remarkApproxTilde` plugin.
- Wired into both renderers (markdownConfig + MarkdownLite), before supersub.
- latex.ts / Markdown.tsx reverted to original; preprocessTilde removed.
- tilde.spec.ts: pure-function cases + a hand-built-tree test proving code,
  math, and link URLs are untouched while text (incl. link text) is converted.

* 🔧 fix: Cover quoted approximations and the markdown error-boundary fallback

- Broaden the boundary to any non-word, non-tilde char (`(?<![\w~])`), which
  now includes quotes — `"~50%" ... "~10%"` was still subscripting because `"`
  was not a recognized prose boundary. Safe to widen because the plugin runs on
  text nodes, so code / links / math / URLs are already excluded structurally
  (the earlier allowlist only existed to dodge URL paths in raw text).
- Add remarkApproxTilde to MarkdownErrorBoundary's fallback remark pipeline so
  the fix holds when a render error falls back to the minimal renderer.

* 🔧 fix: Preserve autolink URL labels when normalizing tildes

A GFM autolink renders the URL as its own label (a text node equal to the href),
so the broadened boundary was rewriting `~50` inside a displayed URL to `∼50`
even though the href stayed correct. Skip text nodes that are an autolink's label
(value matches the destination, allowing for the implied scheme on www/email
links), so the visible URL is preserved verbatim. Regular link labels (prose)
are still normalized.

Note: a single URL containing two `~<digit>` segments is still subscripted by
remark-supersub itself — that's pre-existing behavior (reproduces with no plugin)
and out of scope here.

* 🔧 refactor: Drop unist-util-visit runtime dep from tilde plugin

Replace the `unist-util-visit` import with a small self-contained recursive walk
over text nodes (tracking the parent for the autolink-label check). This removes
reliance on a transitively-hoisted runtime package — addressing the dependency-
hygiene concern without adding a dependency or churning the lockfile. The
type-only `unist` import remains (erased at build, no runtime resolution).

Behavior is unchanged; verified against nested emphasis and list/paragraph trees.
2026-06-14 16:59:12 -04:00
..
@types ⚙️ refactor: Lazy load locale resources (#13640) 2026-06-10 08:48:58 -04:00
a11y 👷 ci: Type-check the Client Workspace (#13560) 2026-06-06 18:40:31 -04:00
common ⚙️ refactor: lazy-load React Query Devtools (#13639) 2026-06-10 13:06:20 -04:00
components 🔢 fix: Prevent "approximately" tildes from rendering as markdown subscript (#13743) 2026-06-14 16:59:12 -04:00
constants 🔐 feat: Granular Role-based Permissions + Entra ID Group Discovery (#7804) 2025-08-13 16:24:17 -04:00
data-provider 📊 feat: Real-Time Context Window & Token Usage Tracking (#13670) 2026-06-13 19:38:28 -04:00
hooks 💾 feat: Persist Context Breakdown & Branch/Total Usage Cost (#13734) 2026-06-14 10:48:07 -04:00
lib/rum 👷 ci: Type-check the Client Workspace (#13560) 2026-06-06 18:40:31 -04:00
locales 🪙 feat: Context Gauge UX, Hover Snapshot, Click Breakdown, Currency, Cost-On-By-Default (#13739) 2026-06-14 13:38:27 -04:00
polyfills chore: Upgrade Vite For Node 24 (#13450) 2026-06-01 15:47:58 -04:00
Providers 🧊 perf: Memoize Completed Markdown Blocks During Streaming (#13576) 2026-06-07 20:31:56 -04:00
routes 🪶 fix: Prevent Soft Default Model Spec from Overriding User Selections (#13642) 2026-06-10 08:52:28 -04:00
store 💾 feat: Persist Context Breakdown & Branch/Total Usage Cost (#13734) 2026-06-14 10:48:07 -04:00
utils 🔢 fix: Prevent "approximately" tildes from rendering as markdown subscript (#13743) 2026-06-14 16:59:12 -04:00
App.jsx ⚙️ refactor: lazy-load React Query Devtools (#13639) 2026-06-10 13:06:20 -04:00
main.jsx 🛟 fix: Auto-Recover from Stale Service Worker Assets After Deploys (#13686) 2026-06-11 11:57:06 -04:00
mobile.css 🎨 refactor: Redesign Sidebar with Unified Icon Strip Layout (#12013) 2026-03-22 01:15:20 -04:00
style.css 📊 fix: Contain Markdown Table Overflow (#13543) 2026-06-05 21:49:54 -04:00
vite-env.d.ts