DSH plugin

The agent writes TSX.
dsh renders it live.

Generative UI for DeepSeek Harness. The model answers with a live React interface instead of prose — and it streams, the component rendering while the model is still typing it.

$dsh plugin --profile web add dsh-generative-ui

Where it shows up

One format, two places, and a default of not using it.

Inline

````ui4a/tsx

A fenced block in the reply

Renders in place, between the paragraphs. For a chart, a form, a set of options to click, a calculation the reader wants to change a number in — something that is part of reading the answer.

Canvas

canvases/tarot.ui4a.tsx

A file at .dsh/ui4a/canvases/

Opens in the panel beside the conversation and stays there across turns. For a tool the user comes back to — a tracker, a browser, an instrument. It is a file they own.

Prose

—

Most of the time

Closures, CAP theorem, what day it is. An interface the reader has to decode is worse than a sentence, and the default is that prose is enough.

Live, not a screenshot

Every card below is ordinary React, running on this page.

No schema, no component allowlist, no DSL. The model writes a component that export defaults — it can import anything on npm — and the runtime compiles it in the browser and mounts it.

$rsync -avz --delete ./dist/ deploy@box:/srv/www
-aarchive — recurse, and keep permissions, times, links
-vverbose — print each file as it transfers
-zcompress in transit (skip it on a fast LAN)
--deletedelete anything on the server that is not here

The trailing slash on ./dist/ decides whether the directory itself is copied. Both of these are controls, not decoration.

What a card can reach

It is not stranded in a sandbox.

A card imports the harness through the same capabilities the model has, under the same access mode the user can see and change in the composer. Read Only is Read Only for the card too.

$dsh/fsread and write files, under the session's own access mode
$dsh/execrun a command where the host enables it, and check the exit code
$dsh/aiask the model, on the app's default model, without seeing a key
$dsh/websearch the web, and get sources back
$dsh/chatsend text into the transcript as the user
$dsh/stateusePersistedState — localStorage behind useState

How the streaming works

The card is compiled from text that is still arriving.

  1. 01

    The fence

    The reply carries a four-backtick ui4a/tsx block. Four because generated TSX routinely contains triple backticks.

  2. 02

    The claim

    The client matches the rendered block against the conversation snapshot — never the language tag, which the host truncates and only emits once the fence closes.

  3. 03

    The compile

    partial-tsx + a wasm compiler run in the browser, on the growing text. Half-written JSX is normalized; frames that cannot compile are transient, not errors.

  4. 04

    The mount

    The component renders against the host's own React singleton, so hooks work and the card takes the app's colours, theme and container width.