In-conversation generative UI: the model renders interactive HTML cards into the chat stream, with streaming preview and sandboxed rendering.
Install
# from npm (prebuilt)
dsh plugin --profile web add @nagi-ovo/dsh-visualize
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Nagi-ovo/dsh-visualize
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README

DSH does not have to answer with text alone. When the model calls visualize, the Web UI renders an interactive card inside the conversation for simulators, charts, comparison panels, and UI mockups.
Install
Desktop: open 插件 (Plugins) in the sidebar, choose 添加插件 (Add plugin), enter @nagi-ovo/dsh-visualize, then restart DSH. You can also run dsh plugin --profile desktop add @nagi-ovo/dsh-visualize.
Known issue: on Desktop, Open in new window does nothing, and neither does Open page on page cards. Use Show here instead.
Web: install from npm into the web profile:
dsh plugin --profile web add @nagi-ovo/dsh-visualize
# If dsh web is running, restart it and refresh the page.
GitHub also works: dsh plugin --profile web add github:Nagi-ovo/dsh-visualize. Run dsh --profile web --dump-config to confirm that the plugin is present in the final configuration. For local development, clone the repository and run dsh plugin --profile web add . from its root; committed build output means no separate build step is required. Users of the community plugin-registry can also install it from Settings → Plugins.
Compatible with DSH 0.1.1-rc.2 through 0.2.2-rc.1.
Use it
Tell the model what you want to explore, for example, “make an adjustable visualization of a sorting algorithm.” The model calls visualize with the whole HTML fragment as its fragment argument (plus an optional title and mode), and the card starts rendering while the model is still writing it. Side-by-side comparisons can use mode: "wide". To correct a card it already drew, the model calls action: "update" with the card's path and one exact old_str → new_str replacement instead of resending the markup; path is only used by update.
When the thing to build is bigger than a chat card, such as a dashboard with filters, a multi-view tool, a full-screen simulation, or a complete desktop mockup, the model builds a standalone page with mode: "page". The conversation shows a launcher card with the title, a one-line description (the summary argument), a live thumbnail of the page, and an Open page button that opens the page full-size in a new tab. If the browser blocks the tab, Show here displays the page in the conversation instead. After an update, a tab you already opened keeps the old version; click Open page again to see the change.
Cards follow the DSH light or dark theme and, in the default style, its whale-blue palette (other styles below). When the chat column is too narrow, the Open in new window button at the right of the card header opens the card full-size in its own tab, themed as DSH was when you opened it and still running inside the same sandboxed iframe. Session replay restores cards from the persistent tool result, so the original fragment file does not need to remain on disk.
A copy of each card's fragment is saved to ~/.dsh/visualize/sessions/<session id>/<title>-<hash>.<style>.html (under $DSH_HOME/visualize/ when DSH_HOME is set), one directory per conversation and never inside your project, so git status stays free of viz/. update patches that copy. Cards that older versions wrote under viz/ in the workspace can still be updated in place. The plugin does not clean these files up; you can delete ~/.dsh/visualize/ at any time and existing conversations still replay their cards.
Cards can show images they could not load on their own: a figure the agent just saved in the workspace (a matplotlib PNG, a paper figure) or any public web image. The model writes <img data-viz-src="figs/loss.png"> or <img data-viz-src="https://…">, and when the call runs the plugin reads the file or downloads the image, checks that it really is a PNG, JPEG, GIF, WebP, or SVG (at most 5 MB each, 12 MB and 16 images per card), and stores it next to the card under ~/.dsh/visualize/sessions/<session id>/images/. The card fetches it from there when it renders, so neither the model's context nor the session log carries the image bytes; the log only records a short reference per image. An image that fails shows a placeholder box with its alt text, the rest of the card still renders, and the tool result tells the model which image failed and why so it can fix the path. Patching a card reuses downloaded images and reads workspace files again, so a regenerated figure shows up after the next patch. This needs DSH 0.1.7 or later; on older versions the card shows placeholders and the result says why.
By default DSH folds a finished turn's tool process. While it does, the turn's cards also appear under the final answer, so you see them without opening the process; with Work details set to Verbose they stay in place only.
Cards can hand something back to the conversation. When you click a button in a quiz, a choice, or a form, the card puts a sentence such as "I picked B — explain why A is wrong" into the composer. Nothing is sent for you: edit it and send it, or don't. Changing your pick replaces the sentence the card wrote before; if the composer holds anything you typed or edited, the card leaves it alone and shows a notice instead. This needs DSH 0.1.7 or later and does not work in a card opened in a new window, where the card hides those buttons.
Cards remember what you set in them: sliders you dragged, options you ticked, text you typed, the tab you picked, and other choices the model keeps with dshViz.state (a selected node, the current step). After a page refresh, when you reopen the session, and after the model patches the card with update, the card comes back as you left it; a newly drawn card starts from its defaults. The state stays in this browser, in the DSH page's localStorage under keys starting with dsh-visualize:state:, kept per session and per card. It is never uploaded and does not follow you to another device or browser; DSH on another port counts as another site. Each card keeps at most 16 KB and all cards together at most 256 KB; past that, the cards changed longest ago are dropped first. To clear it, delete those keys in your browser's developer tools (Application → Local Storage), or clear the site's data. A page card's thumbnail and a card opened in a new window show the saved state, but changes made in the new window are not saved. When the browser refuses storage (some private modes), state lasts only for the current page: theme switches and patches keep it, a refresh does not.
Sometimes the model ends its reply before a card actually reaches you: its last visualize call failed and it never retried; the card rendered but still carries a lint warning that breaks it (the script looks up an element id that does not exist, or a relative URL that cannot load in the sandbox); it pasted HTML/SVG into the reply as a code block; or it said "let me draw a card" and stopped. In those cases the plugin steers one short reminder into the same turn before it closes, so the model fixes the card or makes the call. Detection is conservative: no reminder when you asked for HTML code or a file, when the turn wrote project files, or when the turn was cancelled or failed; style-only warnings such as hard-coded colours do not count; at most 2 reminders per turn, and each problem at most once. To turn it off, add selfRepair: false under this plugin's config in cordis.patch.yml.
Card styles
The plugin ships five card styles; the default is whale. In DSH, open More (bottom left) → Settings → Visualize: each style shows a live preview card rendered in the current light or dark theme, and one click saves it. The next card uses it, without a restart.
The page needs DSH 0.1.7 or later (the host's settings forms, configForms, and its /api routes; in 0.1.7, Settings sits at the bottom of the sidebar). Earlier versions either lack the page or show only the snippet below; add this to <DSH_HOME>/profiles/web/cordis.patch.yml (~/.dsh/profiles/web/cordis.patch.yml by default) instead:
- id: dsh-visualize
name: "@nagi-ovo/dsh-visualize"
config:
style: sketch
The settings page writes the same entry: it goes through the host's dsh-settings into that cordis.patch.yml, keeping the file's other entries and comments.
style |
Look | Good for |
|---|---|---|
whale |
Whale blue (default): the DSH palette and system type | anything |
editorial |
Serif headline that states the finding, one highlighted line with the rest in grey, direct labels | trends, comparisons, "what does this number say" |
paper |
Serif type, L-shaped axes, colour-blind-safe palette, colour plus marker double encoding, "Figure 1 ·" captions | results, learning curves, explanations with formulas |
sketch |
Handwritten type, rough.js strokes, highlighter notes | concepts, intuition, flows |
terminal |
Monospace, square corners, dashed grid, key–value labels | latency, throughput, step-by-step algorithms |
A style has two layers. The card shell injects the colours, fonts, radius and chart defaults (grid, axis lines, line width), each tuned for both light and dark themes, with the background left transparent. The layout and writing conventions (how titles read, captions, rough.js) are a short guide appended to the skill for the model. That guide replaces only the taste advice in design.md: fragment rules, the CSP, theme variables, the 11px floor and lint still apply. Product UI mockups ignore the style and follow the product being drawn.
Each card records the style it was created with in its persisted result and in its saved copy's file name, so changing the setting later leaves existing cards alone, on replay, in "Open in new window", and when the model patches one with update. Cards saved before styles existed have no style in their name, and an update re-renders them in the current setting. The preview that renders while the model is still writing a card reads the setting when the DSH page loads; switching on the settings page updates the current page too, and after editing the YAML by hand, reload open pages. editorial, paper, sketch and terminal load their fonts from Google Fonts; paper loads KaTeX only when a card contains formulas.
Custom styles
Write your preferences in plain language in a Markdown file, e.g. "dark green accent, titles under eight words, one decimal place". The plugin appends it after the built-in guide:
- Global:
~/.dsh/visualize/style.md($DSH_HOME/visualize/style.mdwhenDSH_HOMEis set), for every session. - Workspace:
<workspace>/.dsh/visualize-style.md, for sessions in that workspace only.
Both files can also be viewed and edited at the bottom of Settings → Visualize (also DSH 0.1.7+), one per workspace; saving an empty file deletes it.
When they conflict, what you say in the conversation wins, then the workspace file, then the global file, then the built-in style. Only the first 4 KB of each file is read, and a missing file is skipped. Edits apply the next time the model loads the visualize skill (usually the first card of a new session). A custom style changes how the model writes cards, not the colours and fonts the shell injects; to change colours, say so in the file and the model overrides the matching theme variables on the card's root element.
Security
Each card runs in a sandboxed iframe with an opaque origin and cannot access the host page. Opening a card in a new window, including a page's Open page, keeps that: the new tab carries no model code of its own, and the fragment still runs only inside an iframe with the same sandbox and CSP, so it cannot read DSH's cookies, localStorage, or login token. Its CSP blocks network requests, nested pages, and form submissions, while allowing static assets from a fixed set of CDNs. Fragments are limited to 1000000 bytes by default; change maxFragmentBytes to use a different limit. Images reach a card without loosening that CSP: the plugin fetches them on the server side and hands them to the frame as data: URLs. Workspace images must stay inside the session workspace, both by path and after following symlinks, and are read through DSH's own filesystem service. Web images are fetched only from public addresses: every connection, redirects included (at most 3), is checked after DNS resolution and refused for loopback, private, link-local and similar ranges, so a card cannot be used to reach localhost, your LAN, or cloud metadata. Each download has a 10-second limit and a size cap, and the bytes must match an image format; SVG is kept as is, since it only ever renders through <img>, where scripts and external loads are off. The stored images are served only to the authenticated DSH page, as downloads rather than renderable pages. When a card fills the composer, the DSH page does the checking: it accepts the request only from that card's own iframe and only after your click, limits its length and rate, writes only into an empty composer or over the card's own last sentence, and never sends on your behalf. Saved state gets the same treatment: the DSH page accepts state only from the card's own iframe, decides which card it is stored under (a card cannot write another card's state), and caps its size.
Limitations
Interactive cards currently render only in the Web UI. TUI and headless clients show the standard tool result instead. A card can fill the composer but cannot send a message itself.
Inspired by /visualize in the Codex desktop app. The layered skill references and Chart.js-first approach draw from himself65/finance-skills.
Links
More in this category
zhu1090093659/dsh-web#packages/dsh-task-board★ 10357
Task board for the dsh web GUI: a sidebar multi-column kanban whose cards run in real DSH agent sessions and can also be scheduled with cron expressions, executed host-side even with the browser closed.
zhu1090093659/dsh-web#packages/dsh-web-all★ 10357
Plugin and skin collection for the DSH Web UI: task board, Git graph, right-side panel, remote mobile UI, pet, live token stats, and a skin center.
ccch1mneyyy/dsh-TUI★ 7530
Claude Code-style full-screen terminal UI: pixel-whale header, live status line, and streaming thought expansion.
MeteorNOX/DeepSeek-Balance-Whale-Widget★ 7157
A fixed-corner whale widget for the DSH web GUI — balance, today's usage and per-turn cost with peak/off-peak pricing, editable balance-alert and daily-budget bubbles, a module-based custom bubble queue with A/B weighted choices and random lines or images, 30+ vendor templates (OpenAI, OpenRouter, Kimi, SiliconFlow, Ark, Zhipu, MiniMax and more) with per-model balance and subscription quota, plus task-end sound, imported audio, custom roles and a resource manager. Local-only, no telemetry.
omdsh-dev/DSH-better-sidebar★ 5092
Full sidebar workbench with file rendering and editing, terminal, Git, and subagents; third-party plugins can register new tabs.
Devin-AXIS/deepseek-design#deepseek-idesign★ 1808
Visual design studio for websites, app prototypes, posters, cards, reports, and magazines, with templates, direct element editing, selection-aware AI draft handoff, and export.

Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.