dsh-markstream
No description
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 26, 2026
- Updated
- Aug 26, 2026
Introduction
dsh-markstream
A DeepSeek Harness (DSH) web plugin that renders assistant messages in the conversation stream with markstream-react — the low-jitter streaming Markdown renderer for AI chat (streamed code blocks, Mermaid, KaTeX, safe HTML).
English | 中文
What it does
- Replaces the
assistant-steprenderer in theconversation.chat.nodekeyed slot with a markstream-based implementation, registered withpriority: -10(slot priority is ascending — lowest renders) to shadow the shipped renderer's same-key entry at default priority 0. Registering the same key at the same priority throws:- Text blocks render through
<MarkdownRender content final fade>with low-jitter incremental streaming, including intermediate states of incomplete Markdown; - Streamed code blocks (via the optional
stream-diffspackage), Mermaid (optionalmermaid), KaTeX (inlined with the dependency tree in the current build), and safe HTML; - Reasoning blocks keep the Think disclosure row, image groups keep the attachment gallery, unknown blocks keep the JSON fallback, and interrupted messages keep the "stopped" marker — matching the shipped renderer;
- The turn-tail node (
turn-tail, including IconActions / produced-file rows) and tool-row rendering remain the product's own.
- Text blocks render through
- The renderer's
tlocale seat is bound to theconversationnamespace by the slot declaration, so it reuses ui-conversation's dictionary (row.running/message.stopped/message.unknownBlock/json.truncated) without registering a new namespace.
Floating config panel
The config surface is modeled on the demo site markstream-react.pages.dev (CODE THEME / DARK MODE etc.), the component docs, and the recommended code-block config, delivered as two layers:
- Composition layer: the plugin row's
config(full template in cordis.patch.yml) — deployment defaults, validated by theConfigschema and used as the settings namespacebase. - User layer: a floating gear button at the frame's bottom-right (
shell.overlayentrydsh-markstream-config) opens a non-modal floating panel (demo-site style: side drawer, non-blocking, the rest of the page stays interactive); the header × closes it back to the gear. Fields override and persist to$DSH_HOME/settings.yaml; the "reset" affordance next to a field clears the user override and falls back to the composition value (a field's presence in the user layer marks it as overridden). Writes go through a revision-fenced settings scope and take effect immediately (the renderer re-installs itself on config changes).
Settings → Plugins → Config no longer shows a Markstream card; the gear panel is the single config entry point.
| Field | Default | Meaning |
|---|---|---|
enabled | true | Enable switch: false restores the shipped Markdown renderer (the gear panel stays reachable) |
theme | auto | auto follows the DSH theme (body[data-ds-dark-theme]) / light / dark (maps to MarkdownRender isDark) |
fade | true | Fade-in animation for streamed non-code nodes |
typewriter | false | Blinking typewriter cursor while content streams |
Code blocks (codeBlockProps header toggles + codeBlockOptions, following the stream-diffs surface recommendation; effective only with stream-diffs, falls back to a plain <pre> without it):
| Field | Default | Meaning |
|---|---|---|
codeBlockShowHeader | true | Show the code-block header |
codeBlockShowTooltips | true | Show tooltips |
codeBlockShowFontSizeButtons | true | Show font-size buttons |
codeBlockShowCollapseButton | false | Show collapse button |
codeBlockDiffStyle | unified | unified / split |
codeBlockOverflow | wrap | wrap / scroll |
codeBlockExpandUnchanged | false | Collapse unchanged diff regions |
codeBlockEnableLineSelection | true | Enable line selection |
codeBlockDisableLineNumbers | false | Hide line numbers |
codeBlockFontSize | 13 | Code font size in px (13 recommended) |
codeBlockTabSize | 2 | Tab width |
codeBlockPadding | 12 | Symmetric code padding in px |
codeBlockMaxHeight | 480 | Max height in px (0 = unlimited) |
codeBlockLightTheme | vitesse-light | Light code theme (Shiki registration name) |
codeBlockDarkTheme | vitesse-dark | Dark code theme |
Diagrams (each needs its optional dependency; without it the source is shown as fallback):
| Field | Default | Meaning |
|---|---|---|
mermaidEnabled | false | Forward mermaidProps (needs mermaid) |
mermaidIsStrict | true | mermaidProps.isStrict |
mermaidMaxHeight | 480 | mermaidProps.maxHeight in px (0 = unlimited) |
d2Enabled | false | Forward d2Props (needs @terrastruct/d2) |
d2MaxHeight | 480 | d2Props.maxHeight in px (0 = unlimited) |
Directory layout
dsh-markstream/
├── package.json # dsh.client manifest + dsh.bundle.patch declaration
├── cordis.patch.yml # plugin-row insert + composition config template (bundle channel)
├── tsconfig.json # typecheck (@deepseek-ai/* types from npm peers 0.1.0-rc.8)
├── tsconfig.build.json # tsc full emit to lib/types (JS + d.ts)
├── tsdown.config.ts # emits lib/client.js (browser half)
├── scripts/stage-node.mjs # stages the Node-half files from lib/types to lib/
└── src/
├── index.ts # Node half: registers the dsh-markstream settings namespace (base = row config)
├── schema.ts # flat schema (schemastery z; row Config = namespace schema)
├── config.ts # shared field definitions / defaults / option tables
├── invariant.ts # invariant companion
└── client/
├── index.ts # browser half: shell.overlay floating panel + assistant-step renderer
├── card-store.ts # snapshot store (settings scope binding + set/unset)
├── ConfigSurface.tsx # bottom-right gear + non-modal floating panel
├── ConfigSurface.module.css
├── ConfigFields.tsx # shared field rows (demo-site style: section titles + hairline dividers)
├── ConfigFields.module.css
├── AssistantMarkstream.tsx # config-driven assistant-step renderer (markstream-react)
├── AssistantMarkstream.module.css
├── locales.ts # dsh-markstream dictionaries (zh/en)
└── css-modules.d.ts
Build
pnpm install
pnpm build # tsc -p tsconfig.build.json && node scripts/stage-node.mjs && tsdown
Artifacts:
lib/client.js— the browser half in the DSH client-bundle format (window.__ModuleLoader__.load({ id, factory })):markstream-react/markstream-core/stream-markdown-parser/@floating-ui/*/katexare all inlined into one file (inlineDynamicImports; the module loader serves only/plugins/<id>/client.js);react/react-dom/react/jsx-runtime/@deepseek-ai/dsh-client-ui-primitivesstay as module-table externals;markstream-react/index.cssis compiled and injected as a taggeddata-plugin-cssstyle.lib/index.js/lib/invariant.js— the Node halves (plusconfig.js/schema.jsstaged by stage-node; Node resolves the plain.jsrelative imports;@deepseek-ai/dsh-settings/@deepseek-ai/schemasterystay external).lib/types/**/*.d.ts— declarations.
@deepseek-ai/* types come from the auto-installed npm peers (0.1.0-rc.8, the same release the deployed GUI runs); at runtime the DSH client module table provides them.
Install into a DSH profile (web)
This package declares dsh.bundle.patch, so it installs through the official CLI channel:
dsh plugin --profile web add dsh-markstream
For an unpublished local package, wire it manually (local ~/.dsh/profiles/web):
-
Add the package to the profile dependencies and install (heal node_modules). For local development use
link::file:makes pnpm pack and copy the package, so every plugin edit needs a reinstall;link:is a symlink, so afterpnpm buildrebuildslib/client.jsthe server serves the new file directly (client-hmr picks up the change and hot-reloads without a restart):# edit ~/.dsh/profiles/web/package.json # "dependencies": { "dsh-markstream": "link:D:/path/to/dsh-markstream" } # "dsh": { "profile": { "bundles": [ ..., "dsh-markstream" ] } } cd ~/.dsh/profiles/web && pnpm install -
Restart
dsh weband refresh the page. The plugin row mounts through theinsertincordis.patch.yml; you can also append it manually to the profile'scordis.patch.yml:- insert: - id: markstream name: 'dsh-markstream'
Optional features (markstream optional peers)
| Feature | Package | Notes |
|---|---|---|
| Enhanced code blocks | stream-diffs (bundled dependency) | File/FileDiff surface + syntax highlighting; registered theme names resolve exact colors, unregistered names fall back through the documented seam (--markstream-code-fallback-*) to the selected theme's bg/fg |
| Mermaid diagrams | mermaid | Mermaid blocks degrade when absent |
| KaTeX math | katex | Inlined with the @deepseek-ai/dsh-client-ui-primitives dependency tree in the current build |
| D2 diagrams | @terrastruct/d2 | Unresolved at build time, treated as external (only needed when rendering d2 blocks) |
| Infographics | @antv/infographic | Same, treated as external |
Known limitations
- Per-block rendering: each text block is handed to
MarkdownRenderindependently (matching the shipped per-block renderer); Markdown structures split across text blocks (e.g. a code fence spanning two blocks) are not merged. - No file-mention enhancement: the shipped
MarkdownText'sfileMentionsinline-code highlighting is not replicated in v1 (markstream renders them as plain inline code). - No tail-follow scrolling for Think rows: the collapsed summary follows the latest line, but without the shipped renderer's automatic horizontal follow scroll.
- Replaces the shipped renderer:
assistant-stepis a keyed slot, so registering the same key replaces the shipped implementation wholesale; this plugin replicates text / reasoning / image / unknown-block / stopped-marker presentation, but later product changes are not synced automatically. - Panel renders settings only: the bottom-right gear panel edits and persists rendering fields (
enabled/theme/ …); writes persist to$DSH_HOME/settings.yaml.