dsh-pele-mermaid
简体中文 | English · GitHub · Issues
git clone https://github.com/lsy1998/dsh-pele-mermaid.git
cd dsh-pele-mermaid
npm install
npm run build
Render Mermaid fenced code blocks in the DeepSeek Harness (DSH) Web GUI as SVG diagrams with Pele — no mermaid.js runtime is bundled or loaded.
Topics: deepseek-harness · dsh-plugin · mermaid · plugin
Pele is the lightweight Mermaid-to-SVG renderer from the
Obsidian team (pele@1.6.0, MIT). It supports the diagram types built into Mermaid 12.1.0
and is themed with CSS variables.
Every Mermaid code block gets a Code / Diagram toggle, a fullscreen zoom viewer, standalone SVG export, DSW theme adaptation, and a clean fallback to source when a diagram cannot be rendered.
- Package:
dsh-pele-mermaid0.1.0 - Intended profile:
desktop, owned by the DSH Desktop application - Renderer:
pele@1.6.0, served from a same-origin plugin route
Features
- Renders
mermaid,mermaidjsandmmdfenced code blocks, and also recovers DSH/Shiki's plain fallback (a code element with nolanguage-class) when Pele recognizes a Mermaid keyword at the start of the block. - Never enhances a code block labelled with another
language-*class, and never touches inlinecode. - Code / Diagram toggle buttons with
aria-pressed; diagram view is the default, and the originalpreelement is preserved for code view. - Toolbar badge that names the detected diagram type next to "Pele", plus fullscreen and Download SVG actions.
- Zoom and pan in place: the Zoom in / Zoom out / Reset zoom buttons in the toolbar, hold
Ctrl(orCmd) and scroll the wheel to zoom at the pointer, drag to pan, and use+,-,0and the arrow keys. A plain wheel still scrolls the page. The state is per block and resets when the diagram re-renders. - Fullscreen viewer: zoom in / out / reset / fit, drag to pan,
CtrlorCmd+ wheel to zoom at the pointer,Escapeto close, focus trapped while open and returned to the trigger on close,role="dialog"andaria-modal="true". It opens at the inline zoom and restores the inline scroll position and zoom when you close it. - Exports a standalone
.svgfile namedpele-<type>-YYYYMMDD-HHmmss.svgwhose root carries the resolved theme variables, so it looks the same outside DSH. The export is always the whole diagram at its natural size — zooming or panning first does not crop it. - Adapts to the DSW light and dark themes by mapping DSW design tokens onto Pele CSS variables. Colour changes need no re-render; geometry-only changes re-render the affected diagrams.
- Falls back to the diagram source with a localized error message — including Pele's line and column when it provides them — instead of leaving a broken diagram.
- Rejects individual diagrams over 50,000 characters or 2,000 lines before rendering and keeps them as source with a localized notice.
- Localizes every plugin-owned string in English and Simplified Chinese, and switches live when the document language changes.
- Renders through a serial queue, yields between diagrams to keep the page responsive, and discards stale asynchronous results while a response is still streaming.
- Idempotent and disposable: repeated enable/disable cycles leave no duplicate toolbars, observers or listeners behind.
- Accessible: localized
aria-labels, accessible names on rendered diagrams, politearia-livestatus announcements, and preservedfocus-visibleoutlines.
Requirements
- The DSH Web GUI with the
desktopprofile. This release was verified against the bundled desktop runtime0.2.1-rc.2(@deepseek-ai/dsh-desktopand@deepseek-ai/dsh0.2.1-rc.2) and the standalonedshCLI0.1.7-rc.1. - Node.js 22.18 or later for building from source (the version this release was built and tested with); the package declares
engines.node >= 22.18.0. - DSH is a developer preview. Its plugin loader and Markdown DOM may change between releases.
Install
The plugin ships as a DSH profile plugin bundle. Its package.json declares
dsh.bundle.patch = ./cordis.patch.yml, which inserts the bundle entry ui-pele-mermaid.
Recommended: install from the DSH app (desktop profile)
The desktop profile is managed by the DSH Desktop / DSH Web application, so install from the
app:
Clone the repository and build it:
git clone https://github.com/lsy1998/dsh-pele-mermaid.git cd dsh-pele-mermaid npm install npm run buildOpen the DSH Web GUI and select Plugins in the sidebar.
Install a plugin and point it at the absolute package directory you just cloned, for example
C:\path\to\dsh-pele-mermaid(Resolve-Pathprints it).Let the run finish and enable the bundle it added.
Restart DSH Web, or refresh the page, and open a session that contains a Mermaid block.
The app performs the package-manager call itself and registers the new bundle. The equivalent invocation is:
dsh plugin --profile desktop add -w --save-exact <absolute-path-to-dsh-pele-mermaid>
Verified caveat. A standalone
dshCLI refuses thedesktopprofile:dsh plugin --profile desktop --helpexits with status 1 and printserror: profile "desktop" is managed exclusively by the Electron application; the guard is present in both the globally installed@deepseek-ai/dsh0.1.7-rc.1and the bundled0.2.1-rc.2. Run that form from the app-managed context (the Plugins page), not from a plain terminal.--profile webis the CLI-installable alternative.
Command line (web profile)
dsh plugin --profile web add -w --save-exact <absolute-path-to-dsh-pele-mermaid>
Without a global dsh command, invoke the published CLI through npx (needs network access):
npx.cmd --yes @deepseek-ai/dsh plugin --profile web add -w --save-exact <absolute-path-to-dsh-pele-mermaid>
Restart DSH Web or refresh the page after installing.
Installer script
bin/dsh-pele-mermaid.mjs (also exposed as the dsh-pele-mermaid bin) wraps the same command:
# run from the package directory
node bin/dsh-pele-mermaid.mjs install
node bin/dsh-pele-mermaid.mjs install --profile web --source .
node bin/dsh-pele-mermaid.mjs uninstall --profile web
node bin/dsh-pele-mermaid.mjs --help
install [--profile <name>] [--source <spec>]anduninstall [--profile <name>].--profiledefaults toweb, because the CLI cannot managedesktop;--profile desktopis refused with an explanatory message.--sourceaccepts a package spec, an absolute path, or a./-relative path resolved against the current directory; without it the script installs the package directory it lives in.--helpprints bilingual English / Simplified Chinese usage.- The script forwards to
dsh plugin --profile <name> add -w --save-exact <spec>(uninstall:remove -w <name>) and exits non-zero with a clear message whendshis missing.
For the desktop profile, use the app Plugins page instead: the CLI cannot manage it.
Update and uninstall
Web profile:
dsh plugin --profile web update -w dsh-pele-mermaid
dsh plugin --profile web remove -w dsh-pele-mermaid
For the desktop profile, update or remove the bundle from the app's Plugins page.
Restart DSH Web, or refresh the page, after updating or uninstalling.
Usage
Ask a model to output a Mermaid fenced code block:
```mermaid
flowchart LR
A[Code] --> B[Diagram]
```
The block becomes a card that wraps the original pre. Diagram view is shown first; the
Code button reveals the untouched source, and Diagram switches back. Fullscreen and
Download SVG are available whenever a diagram rendered successfully.
Behaviour and limits
| Area | Behaviour |
|---|---|
| Detection | pre > code blocks whose class contains language-mermaid, language-mermaidjs or language-mmd (case-insensitive). Blocks with any other language-* class are never enhanced. |
| Plain fallback | A code element with no language- class is treated as Mermaid only when Pele's detectType() returns a type and the first token is a known Mermaid keyword. |
| Idempotence | A pre already inside [data-dsh-pele-mermaid] is skipped; processed blocks are marked with data-dsh-pele-mermaid-block, while only the wrapper carries data-dsh-pele-mermaid. Re-enhancing the same block is a no-op. |
| Toggle | Toolbar with a type badge and Code/Diagram buttons (aria-pressed). Diagram is the default view; the original pre is preserved. |
| Rendering | Through window.pele.mount(element, source, options) so the layout adapts to the container width. Options: maxWidth, autoDirection, padding, icons: () => null, links: true, linkRel: 'noopener'. |
| Theme | DSW tokens are mapped to Pele CSS variables on our container and re-applied when the dark theme or the document language changes. |
| Errors | A PeleError (or any thrown value) shows the source and a localized message, with line/column when available. Error text is inserted as text, never as HTML. |
| Limits | 50,000 characters and 2,000 lines per block. Over-limit blocks stay as source with a localized notice and are never rendered. |
| Streaming | A monotonic generation counter per block; results are discarded when the source changed or the node was detached. Renders run through a serial queue with a yield between diagrams. |
| Inline zoom | The inline pane zooms with Ctrl/Cmd + wheel at the pointer, pans by drag, and takes +/-/0/arrow keys. A plain wheel is not swallowed. The state is per block and resets on re-render. |
| Viewer | Fullscreen overlay with zoom in/out/reset/fit, drag pan, Ctrl/Cmd + wheel zoom, Escape to close, trapped focus, and localized labels. It opens at the inline zoom and restores the inline scroll and zoom on close. |
| Export | A standalone .svg whose root carries the resolved theme variables. |
| i18n | Every user-visible string comes from the message catalog; en and zh-CN; language from document.documentElement.lang or navigator.language, switched live. |
| Disposal | Observers and listeners are registered in the plugin's effect scope and released on cleanup; a repeated apply/cleanup/apply cycle leaves no duplicates. |
Note that Pele itself also defaults its source limit to 50,000 characters and rejects an
over-limit source with a limit error, so both checks agree.
Theming
The plugin maps DSW design tokens onto Pele's CSS variables and emits them on its .dsh-pm
container, where Pele inherits them. The first defined token wins; when a token is empty, Pele's
own default is left in place.
| Pele variable | DSW token (first defined wins) |
|---|---|
--pele-bg |
--dsw-alias-bg-layer-2 → --dsw-alias-bg-base → transparent |
--pele-fg |
--dsw-alias-label-primary → currentColor |
--pele-muted |
--dsw-alias-label-tertiary |
--pele-line |
--dsw-alias-border-l1 → --dsw-alias-label-secondary |
--pele-surface |
--dsw-alias-bg-layer-1 |
--pele-surface-alt |
--dsw-alias-bg-layer-2 |
--pele-border |
--dsw-alias-border-l2 → --dsw-alias-border-l1 |
--pele-accent |
--dsw-alias-button-primary-fill → --dsw-alias-brand-primary |
--pele-font |
--ds-font-family-default → --ds-font-family-interface → sans-serif |
--pele-error |
--dsw-alias-state-error-primary |
Pele variables that are not mapped — for example --pele-critical and --pele-series-1
through --pele-series-8 — keep Pele's built-in defaults. Diagram-level style,
classDef and linkStyle colours are fixed by the diagram and do not follow the theme.
Our own chrome (toolbar, badge, buttons, viewer) uses DSW design tokens — --dsw-alias-*,
--ds-font-family-code and --dsw-font-markdown-code-block — with a literal fallback for every
value, and respects prefers-reduced-motion.
Architecture
The package is a dual-face DSH plugin bundle:
- Host face —
lib/index.jsis a Cordis (Node) plugin that injectswebServerand registers one exact route,/dsh-pele-mermaid/pele-runtime.js. AGETorHEADrequest gets the vendored Pele script astext/javascriptwith an immutable cache header; anything else gets 404 or 405. The route is registered insidectx.effect(...)so it is removed on disposal. - Client face —
lib/client.jsregisters itself with the DSH client module loader asdsh-pele-mermaid. It does not bundle Pele. The first detected Mermaid block injects<script data-dsh-pele-runtime src="/dsh-pele-mermaid/pele-runtime.js">; the loader is idempotent, dedupes concurrent calls, andwindow.pelebecomes the Pele instance. - Runtime copy —
lib/pele-runtime.jsis an unmodified copy ofnode_modules/pele/dist/pele.global.jsfrompele@1.6.0(682,005 bytes, sha256b82d1bcd435302779a945e1e0f4a54ea56fb26e010ce3f2122f4216fcfc095d7). The build copies it byte-for-byte; it is generated, never hand-edited.
Published package contents (11 files): package.json, cordis.patch.yml, lib/index.js,
lib/client.js, lib/pele-runtime.js, bin/dsh-pele-mermaid.mjs, icon.svg, README.md,
README.zh-CN.md, CHANGELOG.md and LICENSE.
Security
Pele escapes diagram text and renders it as SVG shapes, text, links and images; it does not turn
diagram source into scripts, event handlers or HTML labels, and JavaScript callbacks in click
statements are ignored. Links are limited to http, https, mailto and tel and images to
http and https; javascript:, data: and vbscript: are always blocked, and links get
rel="noopener".
This plugin's responsibilities:
- Error messages and diagram source are always written with
textContent, neverinnerHTML, because Pele error messages embed the offending source line. - The
iconsoption returnsnull, so the plugin never injects third-party icon markup. - Pele's own size limits stay in force (50,000-character source by default).
- The Pele runtime is served from the plugin's own same-origin route; no CDN is used.
Compatibility
- Pele
1.6.0(MIT, obsidianmd/pele). - Mermaid
12.1.0syntax coverage, which is what Pele implements. Diagram types Pele does not recognize fall back to source with anunsupported-diagrammessage. - DSH developer preview. Verified against the bundled desktop runtime
0.2.1-rc.2and thedshCLI0.1.7-rc.1on Windows, Node.js v22.18.0, npm 10.9.3 and pnpm 11.21.0.
Finding plugins
This repository carries the deepseek-harness and dsh-plugin topics, so it is listed with the
other DeepSeek Harness plugins — browse them with
GitHub topic: deepseek-harness and
GitHub topic: dsh-plugin.
More DSH plugins and a one-click installer live at dshmarket.com.
Develop from source
npm install
npm run build
npm run check
npm test
npm run test:offline
npm run buildrunsscripts/build.mjs: esbuild bundlessrc/client.jstolib/client.js(ES2022, minified, wrapped in thewindow.__ModuleLoader__.load(...)contract) and copies the Pele global script tolib/pele-runtime.js, recording its sha256.lib/client.jsandlib/pele-runtime.jsare generated bynpm run build, but they are committed on purpose: a DSH profile can install this plugin straight from the GitHub source (github:lsy1998/dsh-pele-mermaid), and that install path runs no build step, so the browser bundle and the Pele runtime have to be in the repository. Re-runnpm run buildand commit the result whenever you change anything undersrc/.lib/index.jsis hand-written source and the build fails fast if it is missing.npm run checkrunsnode --checkon the JavaScript it ships and builds (including the builtlib/client.jsand the installer), thennpm run test:offline.npm testrunsnode --test test/. It uses happy-dom and needs no network or browser, but node starts one child process per test file.npm run test:offlineruns the same suite in one process withnode --test --experimental-test-isolation=none "test/**/*.test.mjs". Use this in sandboxes that block node's per-file test child processes, wherenpm testfails with aspawn EPERMerror;npm run checkalready uses it.
On PowerShell hosts that block npm.ps1, use npm.cmd instead. Sandboxed environments that
block npm lifecycle scripts may need --ignore-scripts (the esbuild binary ships prebuilt).
Troubleshooting
- A block stays as source. Check that it is over the 50,000-character or 2,000-line limit, or that it is valid Mermaid 12.1.0 syntax; both cases deliberately keep the source and show a localized message instead of a diagram.
- Nothing renders at all. Confirm the
ui-pele-mermaidbundle is enabled in the profile and that the app was restarted or the page refreshed after installation. - Colours or text geometry look stale after a theme change. Colours are inherited and update without a re-render; font or geometry changes re-render the diagrams through Pele.
- The installer rejects a path. On Windows it refuses
--profileor--sourcevalues that contain& | < > ^ % ! ( )or quotes; use a path without those characters.
License
MIT for this plugin, Copyright (c) 2026 dsh-pele-mermaid contributors.
lib/pele-runtime.js is an unmodified copy of pele@1.6.0 dist/pele.global.js, MIT,
Copyright (c) 2026 kepano — obsidianmd/pele, by the Obsidian
team. See LICENSE for the full third-party notice.
No comments yet. Be the first to write one.