DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

jakehemmerle /

jakehemmerle/dsh-mermaid

Verified

This plugin has no description yet.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@9e080560

dsh-mermaid

A DeepSeek Harness (DSH) web GUI plugin that renders ```mermaid code fences in chat messages as diagrams.

  • Settled mermaid fences show the diagram inside the stock code-block card. A toggle button in the card header ("Show code" / "Show diagram") switches between the diagram and its source. The Copy button still copies the source.
  • Fences that are still streaming stay as plain code until the message settles.
  • Diagrams re-render when DSH switches between light and dark (document.documentElement.style.colorScheme).
  • If a diagram has a syntax error, the code stays visible with a muted Mermaid: <error> line under it.
  • mermaid (v12, securityLevel: 'strict') lives in a lazy chunk, lib/client.mermaid.js, so it loads only after the first mermaid fence appears. Page boot loads only the small lib/client.js.

Install

The package is a DSH bundle: package.json declares dsh.bundle.patch (./cordis.patch.yml, which inserts the ui-mermaid row) and dsh.client (the web half). Install it into a web profile with dsh plugin add, then restart dsh web:

# prebuilt tarball from a GitHub release (no build step)
dsh plugin --profile web add https://github.com/jakehemmerle/dsh-mermaid/releases/download/v0.1.0-rc.1/dsh-mermaid-0.1.0-rc.1.tgz
# or a local checkout, after building it
pnpm install && pnpm build
dsh plugin --profile web add link:/path/to/dsh-mermaid

dsh plugin add records the package as a profile dependency and appends it to dsh.profile.bundles; dsh --profile web --dump-config then shows one id: ui-mermaid row with name: dsh-mermaid. Remove it with dsh plugin --profile web remove dsh-mermaid.

Without the bundle, add the row to the profile patch yourself (for example ~/.dsh/profiles/web/cordis.patch.yml; this is how nix-config installs it). The name must then be the absolute path to the Node entry file; a directory path fails with ERR_UNSUPPORTED_DIR_IMPORT. Use one way or the other, not both, or the profile composes two ui-mermaid rows.

- insert:
    - id: ui-mermaid
      name: /path/to/dsh-mermaid/lib/index.js

Disabling the plugin removes its observers, injected nodes, <style> element and data attributes.

How it detects mermaid fences

DSH renders every fence through a React CodeBlock component with root div.md-code-block. For mermaid the visible label is the generic "Code block", so the language is not in the DOM. The plugin reads it from React's fiber props instead: it takes the element's __reactFiber$… fiber, walks up at most 4 levels, and uses the first memoizedProps that has string code and lang fields (src/fiber.ts). The same props carry streaming. When a message settles, the DOM may not change at all, so a MutationObserver can miss it. While any mermaid block is still streaming, the plugin also rescans every 500 ms.

A DOM node keeps the fiber it was created with. After every other commit, that fiber is the stale alternate. So the plugin reads the props chain of both the fiber and its alternate. It keeps the props whose code matches the text React rendered into [data-code-block-content] code, and if both match, it prefers the settled one.

The per-block logic is a pure state machine in src/fence.ts. Its states are waiting, rendering, rendered and failed. It is idempotent per (code, theme), and it drops stale render completions.

Development

pnpm build        # esbuild -> lib/client.js + lib/client.mermaid.js (module-loader wrapped CJS)
pnpm typecheck
pnpm test         # all tests
pnpm test:unit    # fast-check properties of the pure core
pnpm test:e2e     # real clients: shipped bundles in Chromium + Playwright campaigns on real `dsh web` + dsh-TUI inertness

The real-client suites run against a real dsh web and a real dsh-TUI on the vendored rig (test/rig/): every run gets its own private HOME/DSH_HOME, free port and private tmux socket, and the only model is the local mock (test/rig/mock-llm.mjs). Nothing reads a live ~/.dsh or ~/.dsh-tui; set DSH_BIN, DSH_ROOT, DSH_TUI_DIR and PLAYWRIGHT_BROWSERS_PATH to pinned store builds (see flake.nix's devShell) and run the suites via nix develop. MMD_INSTALL picks how the web and TUI suites install the plugin (test/install.ts): row (default, an explicit lib/index.js row as nix-config writes it), link (dsh plugin add link:<checkout>) or tarball (dsh plugin add of the pnpm pack output).

  • test/web/bundle-install.e2e.test.ts — dsh plugin add of the checkout and of the packed tarball: one composed ui-mermaid row, one bundle entry (also after a repeated add), and a rendered, visible diagram whose arrowhead resolves.
  • test/web/mermaid.web.property.test.ts — fast-check generated action sequences (send / held send / toggle / theme / reload) against the real web client, invariants checked after every step; replay with NUM_RUNS, FC_SEED, FC_PATH.
  • test/web/streaming.e2e.test.ts — paced-chunk streaming: the fence stays plain code while the turn streams, then renders once after settle; prints RELEASE_TO_DIAGRAM_MS=….
  • test/web/duplicate-ids.test.ts — two identical fences get distinct SVG element ids.
  • test/render.property.test.ts, test/client.dispose.test.ts — the shipped lazy chunk and browser bundle in headless Chromium (render properties incl. hostile labels; dispose restores the DOM and stops reacting).
  • test/tui/mermaid.tui.inertness.test.ts — the plugin is web-only; on a real dsh-TUI the row composes, loads without warnings and full turns render byte-identically to a TUI without the plugin.
  • test/contract/dsh-seam.contract.test.ts — pins the DSH seams (CodeBlock props, banner, fiber keying, module loader) against the exact DSH_ROOT build.

Suites write screenshots to test/artifacts/ on failure.

Nix

The flake builds the plugin for x86_64-linux, aarch64-linux and aarch64-darwin. packages.<system>.default contains lib/, package.json and cordis.patch.yml. Its build runs tsc --noEmit and the unit vitest project, and checks.<system>.default is the same package.

nix build github:jakehemmerle/dsh-mermaid   # result/lib/index.js
nix flake check -L

As a flake input, use ${dsh-mermaid.packages.${system}.default}/lib/index.js as the name in the profile patch row above. After a pnpm-lock.yaml change, update hash in nix/package.nix with the value Nix reports.

CI (.github/workflows/nix.yml) runs nix flake check -L on Linux and macOS runners, so the package build with its tsc + unit checks and the DSH-seam contract check is exercised on both OSes. The real-client web/TUI suites need the pinned dsh web, dsh-TUI and Nix Chromium of the flake devShell, so they run locally or in the repo environment (nix run .#e2e).

Limitations

  • The plugin depends on DSH CodeBlock internals: the .md-code-block, [data-code-block-banner] and [data-code-block-content] hooks, and the React fiber props { code, lang, streaming }. If DSH changes them, the plugin finds no fences and you see the stock code block. Nothing breaks.
  • The diagram replaces the code view only visually. React still owns and renders the code block. The plugin adds a single container as the last child of the card and a single button in the header actions.
  • lib/client.mermaid.js is about 5 MB (about 1.5 MB gzipped), because the full mermaid build is bundled, including elk/cytoscape/katex. It loads once, on demand.
—/ 5

No ratings yet

Verified DSH bundle

Commit 9e0805607c1b

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout