vecnode/vncode--packages-dsh-diagrams ↗★ 3
dsh-diagrams
Mermaid与TikZ图表解析、渲染与导出 适合需要在DSH中生成、预览、编辑和导出专业图表与学术公式的用户。
安裝
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:vecnode/vncode#b3a6a0d53b96ef29d452af3962e8d726a122db3a&path:packages/dsh-diagrams說明文件
閱讀完整 README ↗dsh-diagrams (alpha.6)
Mermaid and TikZ diagrams as a first-class surface of the harness: the model writes them as tools, the host validates every write with a real parser or a real TeX engine, they render inline in the conversation, and each one lives in its own right-bar tab with a zoom ladder, a source drawer and an export menu that saves to the Desktop of the machine running the harness.
A diagram can live in two places. By default it belongs to the conversation
that drew it; publish it and it joins the LIBRARY, one shared store every
conversation reads, whose address names no conversation
(dsh-resource://diagram/library/) - so jepa-model means the same diagram
in every chat, and survives the chat it was drawn in.
- Row
diagrams, bundledsh-diagrams, tab kindsdiagram(one tab per diagram, in either scope) anddiagrams(the index, reachable from the tab strip's+/ Start page, showing the library and this conversation). - Tools:
diagram_write,diagram_patch,diagram_read,diagram_verify,diagram_publish,diagram_delete- each taking an optionalscope(conversation|library) and resolving a bare id in the LIBRARY first, exceptdiagram_publish, which takes an id from THIS conversation and copies it in. - Skills:
mermaid-diagrams,tikz-diagrams(authored inskills/, copied into$DSH_HOME/skillsby the installer), each with areference/complex-diagrams.mdfor pictures too big for the syntax summary. - No core patch, no forked bundle, no npm dependency, no network.
Alpha. 0.1.0-alpha.6.
1. What the user sees
In the conversation. Every diagram tool call renders its picture inline -
Mermaid drawn by the browser from its own source, TikZ drawn from the SVG the
host compiled - under a header naming the diagram (kind, title, id, status
pill) with two links: Open tab (opens or reveals that diagram's tab) and
Show/Hide (the picture). The card is built from the tool call itself, so it
is already correct on replay, before any request returns.
The card reads the shell's own block model: a call still running is a call block
with no kind (ui-tool reads done = "kind" in block), and a settled call
is a tool-result block carrying call.argsRaw plus the meta view the host
declared. That second channel is what names the diagram for a diagram_write -
the write never carries an id, because the host derives one from the title - so
the settled card can name the diagram, show its status, refresh the conversation
store and offer a live Open tab link.
In the right bar.
| Surface | What it is |
|---|---|
dsh-resource://diagram/session// (a library diagram is dsh-resource://diagram/library/) | one tab per diagram: the rendered picture laid out at 80% of the pane with a - / + / Fit zoom ladder (25%-400%), drag-to-pan when it overflows, a Recompile button for TikZ, Copy, and Export ▾ |
sidebar://diagrams | the index: every diagram of the conversation and of the shared library, with kind, status and size; New Mermaid / New TikZ; picking a row opens its tab |
80% is the 100% rung: a diagram is read whole first. The zoom moves the
layout BOX rather than a CSS transform, so a zoomed box stays scrollable to
its edge; every child of a zoomed box is stretched to it, because a column flex
container sizes its children to their content on the cross axis; panning is the
canvas' own scrollLeft/scrollTop; and a zoom keeps the point the reader was
looking at.
The index type carries the package's one guide entry (order: 40, after
Files 10, Editor 20 and History 30), which is what the + control and the Start
page list.
Both pane bodies re-read the conversation from the host when they mount and whenever the tab becomes visible again, so a tab that stayed mounted while the model wrote diagrams shows them the moment the user returns to it.
The source drawer. Source opens a monospace drawer beside the picture:
edit, then Apply (validated and stored exactly like a model write, and
recompiled when it is TikZ) or Revert. It is deliberately a plain textarea:
the package depends on no editor, and the document that this drawer edits is a
diagram, not a program. For a real editor, export the .mmd/.tex and open it
in dsh-editor.
Export. Export ▾ saves every format to the Desktop of the machine
running the harness (POST /api/dsh-diagrams/export, create-exclusive: the
first free name wins and an existing file is never replaced), and reports the
absolute path it wrote. The client names a format, never a path, so there is no
traversal surface and no way to overwrite a file the user already had. The
second half of the menu downloads through the browser instead - the fallback for
a profile whose host row is not mounted.
| Kind | Formats | Where the bytes come from |
|---|---|---|
| Mermaid | mmd, md, svg, png | source for mmd/md; the browser's own render for svg, rasterized at 2x for png |
| TikZ | tex, pdf, svg, png | source for tex; the host's compiled artifacts for the rest |
2. What the model gets
Six tools with raw JSON-Schema parameters (the registry validates both the arguments and the returned canonical value):
| Tool | Purpose |
|---|---|
diagram_write | create or replace one diagram (kind, source, optional id, title, scope, note) and validate it |
diagram_patch | literal oldString/newString replacement inside one diagram - the cheap iteration path for a long TikZ picture; ambiguous matches are refused (AMBIGUOUS), a miss is refused (NO_MATCH) |
diagram_read | one diagram's full source plus its last diagnostics, warnings and browser verdict, or both indexes (library and conversation) |
diagram_verify | re-validate the stored source without writing: it never bumps the revision and never discards the browser's render report |
diagram_publish | copy a conversation diagram into the shared LIBRARY, so every chat can read it and cite its id |
diagram_delete | remove one diagram from whichever scope holds it |
Five rules make this more than a text box:
- Every write is validated before it is stored, so a broken diagram
returns the parser's or the compiler's own line-accurate error rather than a
broken picture.
- Mermaid: the source goes to a child process that loads the vendored
engine behind a DOM stub and calls
mermaid.parse(); the parse error comes back with the offending line, the caret and the parser's "Expecting" list. - TikZ: the source is compiled by the machine's own TeX engine and the
compiler's lines come back line-accurate
(
diagram.tex:12: Package pgf Error: No shape named ...).
- Mermaid: the source goes to a child process that loads the vendored
engine behind a DOM stub and calls
- The status is the contract. A tool result carries
status: ok | error | unavailable.unavailablemeans "stored but NOT verified" (no TeX engine on this host, or the validator itself failed) and says so in the result text, because telling the model its diagram is wrong when the checker is what broke would be a lie. - Warnings are advisory, never a refusal. After a successful validation the
host lints what a parser cannot refuse but a reader pays for: a picture with
nodes and no edges, more nodes than a person takes in at once, an
unclosed-looking label, a Mermaid source whose first line names a different
diagram type than the engine parsed, a TikZ document that compiled to several
pages or to a canvas too wide to read. They ride the same result, marked as
advisory, and never change
status. - Two degenerate sources are refused in plain words. An empty (or comments-only) source, and a TikZ document with no drawing command - both of which the engines handle with a success and a blank page.
- "It parses" and "it draws" are separate verdicts. See §3.
The returned address is the diagram's tab, so the model can point the user at
it in prose as well.
diagram_verify re-runs the whole validation against the stored source
without writing: it never bumps the revision, never claims an id, and never
discards the browser's render report. It is the right call when a person edited
the diagram in its panel, when the model's context was compacted, or before
describing a diagram's contents - and it is why re-checking does not have to
cost a rewrite.
Skills carry the craft the tools cannot: which Mermaid diagram type fits
which question, the syntax traps that actually break diagrams, layout and
readability budgets; the TikZ preamble the host supplies, node/edge/plot
recipes, sizing, the host's hard limits (no shell escape, no file access, no
package installation) and how to read each compile error. They also document the
verdicts above - what status, warnings and the Browser: line each mean, and
which one to act on. They are registered at runtime from skills/ and copied
into $DSH_HOME/skills by the installer, so the catalog finds them however the
bundle was installed.
3. How it is put together
lib/index.js host row: 6 tools, 2 skills, the /api/dsh-diagrams/* routes
lib/store.js per-conversation state (one JSON file, atomic writes)
lib/cache.js content-addressed artifact cache (svg/png/pdf/tex + meta)
lib/latex.js engine probe, source normalization, compile, convert
lib/mermaid-check.mjs CHILD process: DOM stub + vm-loaded engine + parse + lint
lib/client.js browser half: 2 tab types, their bodies/titles, tool cards
lib/vendor/mermaid.min.js GENERATED single-file mermaid build (~3.4 MB)
skills//SKILL.md the two skills (copied to $DSH_HOME/skills on install)
vendor/build.mjs generates lib/vendor/mermaid.min.js + VERSION.json
Routes (connection.fetch, exact paths, GET/HEAD/POST only)
The harness's Connection registry registers exact routes and its method
vocabulary is GET | HEAD | POST. Two consequences are visible in the design:
the vendored engine is one self-contained file served from one route (the
chunked ESM build would have needed 104 routes), and every write - including
delete - is a POST.
| Route | Behavior |
|---|---|
GET /health | the vendored Mermaid version, the TeX capability (engine, svg, png), cache entry count. ?refresh=1 re-probes the engines |
GET /state?session= | the conversation's diagrams and the shared library (each with source, revision, scope, warnings and last render report) + the capability block |
GET /diagram?session=&id=&scope= | one diagram, source included. Without scope the id resolves library-first |
POST /diagram | create/replace ({session, id?, kind?, title?, source, scope?, create?, recompile?}), or delete ({session, id, scope?, delete: true}). Used by the panel; the model goes through the tools |
GET /artifact?session=&id=&scope=&format= | svg/png/pdf/tex from the cache (Mermaid: mmd/source only - its picture exists in the browser) |
POST /export | save one format to the Desktop of the machine running the harness, create-exclusively, and answer with the absolute path, the folder and the name it wrote |
POST /render-report | what the BROWSER did with one revision ({session, id, scope?, revision, ok, phase?, error?, theme?, ms?}). Deliberately forgiving: a report about a deleted diagram or an older revision answers 200, because a verification channel must not fail loudly |
GET /vendor/mermaid.js | the vendored engine (ETag, immutable) |
State: one file per conversation, and one for the library
$DSH_HOME/dsh-diagrams/sessions/.json - {order, diagrams{id → {kind, title, source, status, diagnostics, warnings, render, artifact, revision, history}}}, one atomic write per change, capped (64 diagrams, 256 KiB per
source, 4 MiB of source per conversation, 16 MiB per file). A conversation
source budget is enforced on write AND on patch (replacing a diagram is charged
once) and refuses with a typed BUDGET error the model can act on. The browser
reads the file through the routes; the model reads it through diagram_read,
which is what makes a diagram survive compaction, a reload or the browser
closing.
$DSH_HOME/dsh-diagrams/library.json is the same shape in one file for the
whole harness, written by the same store class with a fixed name instead of a
name per conversation. It has its own 4 MiB source budget, its own render
reports and its own revisions, so the library copy of a diagram is an
independent entry that happens to share its source - and therefore its artifact,
because the cache is content-addressed. Nothing about a conversation leaks into
it: the file knows no session.
Four rules are load-bearing:
- A panel edit writes back where the diagram already is - otherwise editing a library diagram in its tab would quietly fork a conversation-only copy.
- Deleting a diagram does not drop its cached artifact blindly, because the cache is content-addressed and shared: an identical diagram elsewhere is the same file.
- State is never a session event.
@deepseek-ai/dsh-session-persistencerefuses to load a log containing an event type outsideKNOWN_SESSION_EVENT_TYPESunless the envelope carriesignorable: true, andSession.append()has no way to set that marker - a plugin-owned event type would make the conversation unreadable. - State is never a projection. A
sessionProjectionsunit requireszodschemas, and this pack ships no npm dependencies (the profile installs bundles as live links, so a package dependency would not be installed). It would also fold the same events the rule above refuses.
render is the browser's own report about the revision it drew
({revision, ok, phase, error, theme, at}), stored by recordRender and read
back as one of four states by verificationOf: drawn (a renderer reported
success for THIS revision), failed (it reported failure, with its own error),
stale (the newest report names a DIFFERENT revision, so this one has never been
drawn) or pending (no report at all). The verdict is ONE function
(lib/store.js: verificationOf) used by the tool result, the state route and the
tab pill, so the three cannot disagree; it carries both revision numbers, so
stale is read rather than guessed - a report about revision 4 is never
evidence about revision 5 - and it omits its error/at keys rather than
nulling them, because the tool registry refuses a value that does not survive a
JSON round trip. A write sets render back to null, because the old picture
was of different text.
Rendering
Mermaid, in the browser. The engine is fetched once from the plugin's own
route and evaluated as a classic script (its last line is
globalThis["mermaid"] = ...), exactly how the editor loads CodeMirror. Renders
are cached per (source, theme) (LRU, 24 entries) and re-drawn when the app's
light/dark scheme flips.
Every render goes through renderMermaidSafe, and the order inside it is
load-bearing:
mermaid.parse(source)first. It is the engine's own syntax check and it throws with the offending line, sorender()is only ever reached by a source the parser accepted. A broken source therefore cannot produce a picture at all - clean or broken.suppressErrorRendering: true. Ifrender()fails anyway (a renderer bug on a source the parser accepted), the engine throws instead of drawing its 2412x512 "Syntax error in text" diagram.- A container the plugin owns.
render(id, source)with no container builds#dondocument.bodyand removes it only on success, so a failure would leave one behind - and one of those divs holds the error picture. PassingmermaidHost()- attached, laid out at zero size, offscreen- keeps every fixture out of the interface. A `