Page-level LaTeX math rendering for DeepSeek Harness Web: a KaTeX post-processor that renders TeX math on any message text surface — user messages, tool results, and streaming text included.
The DSH conversation already renders $...$ math through MarkdownText once a message settles, but several text surfaces never reach that pipeline: user messages render through the literal MessageText, tool results and other blocks carry plain text, and the streaming arm parses without math. This plugin is a DOM-level KaTeX post-processor that covers those surfaces.
- Renders TeX math anywhere in the chat:
$...$(inline),$$...$$(block),\(...\),\[...\]. - Streaming-safe: in-flight messages (
data-streaming) are left for settled MarkdownText; code blocks (pre/code) are never touched. - React-safe: the original text node is emptied but never removed, so React's fiber references stay valid; rendered spans are inserted as siblings and re-applied automatically when React rewrites the source text.
- Zero runtime dependencies: KaTeX is inlined into the bundle at build time; the browser side needs nothing beyond the DSH shell's platform modules.
- No changes to DSH core — installed as an ordinary plugin, survives
@deepseek-ai/dshupdates.
dsh plugin --profile web add dsh-katex# clone, then install into your profile
dsh plugin --profile web add /path/to/dsh-katexThen restart dsh web and refresh the page. The plugin appears in the
browser roster (window.__DSH_BOOT__) and starts rendering math immediately.
Plugin-set changes take effect on restart — the
client-hmrrow only hot-reloads rows that already exist.
┌────────────────────────────┐ MutationObserver ┌──────────────────────────────┐
│ DSH Web shell (React) │ ────────────────────▶ │ KatexPostProcessor (browser)│
│ user msg / tool result / │ text nodes appear │ find MathRuns → katex │
│ streaming text │ │ renderToString → span │
└────────────────────────────┘ └──────────────────────────────┘
The browser half (src/client.js, bundled to lib/client.js):
- Observes
document.bodywith aMutationObserver, coalescing passes per animation frame. - Walks text nodes; skips
pre/code/script/style/textareaand containers markeddata-streaming. - Matches TeX delimiters with display-mode precedence (
$$…$$/\[…\]before$…$/\(…\),\$escapes excluded). - Renders each run with KaTeX, empties the original text node (keeping it in the tree), and inserts rendered
<span data-dsh-katex-part>siblings before it. - Marks the parent
data-dsh-katex; when React rewrites the source under a still-marked parent, the observer clears stale rendered siblings and re-applies.
The host half (src/index.js → lib/index.js) is an empty apply so the plugin row exists in the host Loader; all behavior lives in the browser.
pnpm install # dev deps: esbuild + katex (both used only at build time)
pnpm run build # regenerates lib/client.js, lib/index.js, cordis.patch.ymlThe generated artifacts are committed (like dsh-system-control /
dsh-theme-palettes), so users can install from a clone without a build step.
prepublishOnly rebuilds them before publishing.
- Start DSH web with the patch:
pnpm dsh web --patch ./cordis.patch.yml(or install into a profile). - Send a message containing
$E = mc^2$,$$x^2 + y^2 = z^2$$, or\(\partial\Gamma \cong \Gamma \times (\Gamma \to \Gamma)\). - The math renders as KaTeX; user messages and tool results render too.
dsh-katex/
├── package.json # dsh.bundle + dsh.client declarations
├── cordis.patch.yml # insert row (generated by build.mjs)
├── build.mjs # esbuild bundler: inlines KaTeX into lib/client.js
├── src/
│ ├── client.js # browser half: KatexPostProcessor (source)
│ └── index.js # host half: empty apply (source)
├── lib/ # committed build artifacts
│ ├── client.js # window.__ModuleLoader__.load({id, factory}) bundle
│ └── index.js # host half ESM
└── .github/workflows/ci.yml
MIT