deepseek-harness-intelligent-ui 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,42 +1,178 @@
1
- # DeepSeek Harness Intelligent UI
1
+ <div align="center">
2
2
 
3
- English | [中文](README.zh-CN.md)
3
+ <img src="https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/readme/hero.svg" alt="DeepSeek Harness Intelligent UI: generative, interactive UI for DeepSeek Harness" width="100%">
4
4
 
5
- [![CI](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/deepseek-harness-intelligent-ui)](https://www.npmjs.com/package/deepseek-harness-intelligent-ui)
5
+ **English** · [中文](README.zh-CN.md)
6
+
7
+ [![CI](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/actions/workflows/ci.yml)
8
+ [![npm](https://img.shields.io/npm/v/deepseek-harness-intelligent-ui?color=4d6bfe&label=npm)](https://www.npmjs.com/package/deepseek-harness-intelligent-ui)
9
+ [![DSH](https://img.shields.io/badge/DeepSeek%20Harness-0.1.7%20%E2%80%93%200.2.x-22d3ee)](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/compatibility.md)
10
+ [![Node](https://img.shields.io/badge/node-%E2%89%A5%2022.13-34d399)](https://nodejs.org)
11
+ [![OS](https://img.shields.io/badge/OS-Windows%20%7C%20Linux%20%7C%20macOS-a78bfa)](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/compatibility.md)
12
+ [![License: MIT](https://img.shields.io/badge/license-MIT-97a1c9)](LICENSE)
13
+
14
+ [Demo](#-demo) · [Quality](#-real-model-quality-baseline) · [Quick start](#-quick-start) · [How it decides](#-how-the-agent-decides) · [Widgets](#-sandboxed-widgets-ui_render_widget) · [Security](#-security-model) · [Configuration](#%EF%B8%8F-configuration) · [Develop](#-develop-and-verify)
15
+
16
+ </div>
6
17
 
7
18
  Generative, interactive UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH). Ask in plain language and the agent decides on its own whether to answer with text, with native components (dashboards, tables, charts, timelines, live function plots, KaTeX math, Mermaid diagrams, forms), or with a fully custom **sandboxed live widget** (Three.js scenes, canvas simulations, mini-apps). The UI renders progressively in the chat while the answer streams. Sliders, rotation, zoom, sorting and tabs respond instantly in the browser. Only explicit actions (a button, a form, `Bridge.sendPrompt`) go back to the agent.
8
19
 
9
20
  It ports the ideas of CopilotKit's [OpenIntelligentUI](https://github.com/CopilotKit/OpenIntelligentUI) (Open Generative UI + A2UI-style declarative components) onto DSH's own extension points. It needs no CopilotKit, LangGraph, or external router.
10
21
 
11
- ## Demo
22
+ ## ✨ Highlights
12
23
 
13
- ![DeepSeek Harness Intelligent UI demo](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/demo.gif)
24
+ <table>
25
+ <tr>
26
+ <td width="33%" valign="top">
14
27
 
15
- 🎥 **Full-quality video (H.264, ~2 min):** [docs/assets/demo.mp4](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/v0.2.1/docs/assets/demo.mp4)
28
+ ### 🧠 Decides by itself
29
+ No command needed. Each turn the agent picks text, native components or a live widget, guided by a system-prompt section.
16
30
 
17
- > The recording comes from the real DSH Web 0.2.0-rc.1 with this plugin installed, driven by `npm run e2e:all -- --record`. The model behind it is the **scripted mock** in `scripts/e2e/` (no API key was available), so the answers are canned. Everything you see (streaming, rendering, interaction, the round trips, persistence, isolation) is the real plugin running in DSH.
18
- >
19
- > To get an inline player on GitHub, drag `docs/assets/demo.mp4` into a GitHub comment or release editor and paste the resulting `https://github.com/user-attachments/assets/…` URL on its own line here. GitHub only renders inline video for uploaded attachments, not for repository files.
31
+ </td>
32
+ <td width="33%" valign="top">
20
33
 
21
- | Dashboard streams in, then settles | Three.js widget: R slider, rotate, zoom |
22
- |:---:|:---:|
23
- | ![Dashboard](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/3-dashboard.png) | ![Sphere](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/7-widget-rotated.png) |
24
- | *Stat, Grid, Tabs, stacked bar chart with hover tooltip, sortable table, timeline, form* | *`x²+y²+z²=2Rz` via `UI.three()`: the slider re-shapes the surface locally* |
34
+ ### ⚡ Streams progressively
35
+ Components appear while the JSON streams. Widgets morph into **one** iframe and finalize in place: no rebuild, no flash.
36
+
37
+ </td>
38
+ <td width="33%" valign="top">
39
+
40
+ ### 🖱️ Local first
41
+ Sliders, rotate, zoom, sort, tabs and hover tooltips run in the browser and never call the model.
42
+
43
+ </td>
44
+ </tr>
45
+ <tr>
46
+ <td valign="top">
47
+
48
+ ### 🧩 20-component catalog
49
+ Cards, grids, tabs, tables, charts, timelines, `Plot`, KaTeX `Math`, `Mermaid`, forms. Validated before it renders.
50
+
51
+ </td>
52
+ <td valign="top">
53
+
54
+ ### 🔒 Sandboxed by default
55
+ Opaque-origin iframe, strict CSP, token-checked messages and a rate-limited bridge, all verified in a real browser.
56
+
57
+ </td>
58
+ <td valign="top">
59
+
60
+ ### 💾 State that survives
61
+ Open tabs, sort order, form values, sliders and `UI.state` survive turn folding, page reloads and stopped generations.
62
+
63
+ </td>
64
+ </tr>
65
+ </table>
25
66
 
26
- | Math, live Plot, Mermaid | Form → agent → new UI |
67
+ ## 🎬 Demo
68
+
69
+ ### The real DeepSeek model, end to end
70
+
71
+ https://github.com/user-attachments/assets/e4eb0adc-659e-4c80-b473-449aa42ca00f
72
+
73
+ <details>
74
+ <summary><b>🎞️ GIF highlights</b> (for viewers that cannot play the video, e.g. npm)</summary>
75
+ <br>
76
+
77
+ <p align="center">
78
+ <img src="https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/demo.gif" alt="Real deepseek-flash model in DSH Web, narrated by the whale girl: a form round trip, a damped-oscillation Plot with sliders, and two in-place chart edits via ui_patch" width="100%">
79
+ </p>
80
+
81
+ </details>
82
+
83
+ <img align="right" width="120" src="https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/scripts/eval/demo/assets/whale/greet.webp" alt="The DeepSeek whale girl who narrates the demo">
84
+
85
+ 🎥 **Full video (H.264, 2 min 27 s, 9.5 MB):** [docs/assets/real-model/demo.mp4](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/assets/real-model/demo.mp4). It covers all 11 browser cases, and every chapter ends on its real result: text, chart, table, form round trip, Plot, Three.js with "Ask to fix", stop → regenerate, two in-place edits, Fast 3G, CDN offline and phone width. The DeepSeek whale girl (鲸鱼娘) narrates; her art is from [dsh-whale-musume](https://github.com/Sutera-Diffusus/dsh-whale-musume) (MIT, see the [credits](#credits)).
86
+
87
+ > [!NOTE]
88
+ > Recorded on 2026-10-16 by `npm run eval:real -- --stage browser --record` and edited with `npm run eval:demo`. Every card was generated live by the real **`deepseek-flash`** model in real **DSH Web 0.2.0-rc.1** with this plugin installed. Nothing is scripted, and **failures are left in**: each chapter card is marked ✓ or ✗, and the narration says what went wrong. While the model is working the video is sped up; the stopwatch in the corner shows the real elapsed time. The results are in the [real-model quality baseline](#-real-model-quality-baseline).
89
+
90
+ | Multi-series chart: **✗** (call cut off mid-stream) | Two in-place edits with `ui_patch`: ✓ |
27
91
  |:---:|:---:|
28
- | ![Math](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/9-math.png) | ![Form](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/4-form-sent.png) |
29
- | *KaTeX formula, `Plot` with parameter sliders (no model call), Mermaid flowchart* | *Submitted values reach the agent, which answers with a forecast chart* |
92
+ | ![Chart](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/chart.png) | ![Patched](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/patched.png) |
93
+ | *The chart is right, but the card says "generation interrupted": more components were still pending* | *"Make it a line chart", then "add 小程序 30/45/60/80": one card, version 3, exact values* |
94
+ | **Form submit → the agent uses the values: ✓** | **Damped oscillation `Plot` with live sliders: ✓** |
95
+ | ![Form follow-up](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/form-followup.png) | ![Plot](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/plot.png) |
96
+ | *The submitted 全国 / 4 / 乐观 drive a quarterly forecast* | *y = A·e^(−γt)·cos(ωt) with envelopes; the sliders never call the model* |
97
+ | **Three.js: ✗, then repaired with "Ask to fix"** | **Stopped mid-stream → regenerated: ✓** |
98
+ | ![Three.js repaired](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/three-repaired.png) | ![Regenerated](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/regenerated.png) |
99
+ | *First attempt: `view is not defined`. After "Ask to fix": sphere centred at (0,0,R) with axes* | *The interrupted card keeps a static preview; Regenerate produces the full scene* |
100
+ | **Fast 3G, cache off: ✓** | **CDN unreachable: ✓ (degrades as designed)** |
101
+ | ![Weak network](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/weak-network.png) | ![Offline](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/offline.png) |
102
+ | *three.js loads slowly but still renders* | *The failure is shown on the card; the chat keeps working* |
103
+ | **Phone width (390 px): ✓** | **Sortable table: ✗ (no table within 5 min)** |
104
+ | ![Mobile](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/mobile.png) | ![Table](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/real-model/table.png) |
105
+ | *No overflow; KPI text and charts fit* | *The agent spent the whole budget researching car specs on the web* |
106
+
107
+ <details>
108
+ <summary><b>🧪 Scripted-model demo</b> (the pipeline E2E: dark mode, reload, sandbox isolation)</summary>
109
+ <br>
110
+
111
+ ![DeepSeek Harness Intelligent UI demo with the scripted model](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/demo.gif)
112
+
113
+ 🎥 [docs/assets/demo.mp4](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/v0.2.1/docs/assets/demo.mp4). This recording is from `npm run e2e:all -- --record`. The model behind it is the **scripted mock** in `scripts/e2e/`, so the answers are canned. It shows the plugin pipeline (streaming, rendering, round trips, persistence, isolation), not how a real model behaves.
30
114
 
31
- | Streaming preview (same iframe becomes final) | Stopped mid-generation |
115
+ | Dashboard streams in, then settles | Three.js widget: R slider, rotate, zoom |
32
116
  |:---:|:---:|
33
- | ![Preview](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/5-widget-preview.png) | ![Stopped](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/10-stopped.png) |
117
+ | ![Dashboard](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/3-dashboard.png) | ![Sphere](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/7-widget-rotated.png) |
34
118
 
35
119
  | Dark theme | Phone width (dark) | After reload |
36
120
  |:---:|:---:|:---:|
37
121
  | ![Dark](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/11b-dark-widget.png) | ![Mobile](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/12-mobile-dark.png) | ![Reload](https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/v0.2.1/docs/assets/screenshots/14-reload.png) |
38
122
 
39
- ## How the agent decides
123
+ </details>
124
+
125
+ ## 🚀 Quick start
126
+
127
+ You need [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) **0.1.7 – 0.2.x** (`>=0.1.7-rc.1 <0.3.0`) and Node.js ≥ 22.13, on Windows, Linux or macOS. Nothing is compiled during install. The package ships its prebuilt browser bundle and declares no install scripts.
128
+
129
+ ```sh
130
+ dsh plugin --profile web add deepseek-harness-intelligent-ui
131
+ ```
132
+
133
+ Then restart DSH Web (`dsh web`) and ask for something visual, for example *"compare Q1–Q4 revenue by channel"* or *"let me explore x²+y²+z²=2Rz in 3D"*.
134
+
135
+ To have your environment checked first, use the guided installer instead. It verifies Node, the `dsh` version, pnpm and the profile, then runs the same `dsh plugin add`:
136
+
137
+ ```sh
138
+ npx deepseek-harness-intelligent-ui install # --profile <name>, --dsh <path>, --dry-run
139
+ npx deepseek-harness-intelligent-ui doctor # diagnose only
140
+ ```
141
+
142
+ | Task | Command |
143
+ |---|---|
144
+ | Upgrade | `dsh plugin --profile web add deepseek-harness-intelligent-ui@latest` |
145
+ | Try a prerelease (only when the `next` dist-tag exists) | `dsh plugin --profile web add deepseek-harness-intelligent-ui@next` |
146
+ | Pin a version | `dsh plugin --profile web add deepseek-harness-intelligent-ui@0.2.4` |
147
+ | Uninstall | `dsh plugin --profile web remove deepseek-harness-intelligent-ui` (or `npx deepseek-harness-intelligent-ui uninstall`) |
148
+ | From a checkout | `dsh plugin --profile web add file:/path/to/deepseek-harness-intelligent-ui` |
149
+
150
+ <details>
151
+ <summary><b>🩺 Troubleshooting</b></summary>
152
+ <br>
153
+
154
+ - *`pnpm was not found`*: DSH installs plugins with pnpm. Run `corepack enable` (it ships with Node) or `npm i -g pnpm`.
155
+ - *`… is incompatible with dsh X`*: your DSH is outside the supported range. Upgrade DSH (`npm i -g @deepseek-ai/dsh@latest`) or check [compatibility](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/compatibility.md) for a plugin release that supports it.
156
+ - *Upgrading from 0.1.x (`dsh-intelligent-ui` via `file:`)*: run `dsh plugin --profile web remove dsh-intelligent-ui` first. Both packages insert the same `intelligent-ui` row, and `doctor` reports the conflict.
157
+ - *The UI does not appear*: restart DSH Web after installing, and make sure you started the profile you installed into (`dsh web`).
158
+
159
+ </details>
160
+
161
+ ### Compatibility
162
+
163
+ | DeepSeek Harness | Install smoke test | Browser E2E (24 checks) |
164
+ |---|:---:|:---:|
165
+ | `0.1.7-rc.2` | ✅ | ✅ |
166
+ | `0.2.0-rc.1` | ✅ | ✅ |
167
+ | `0.2.0-rc.2` (`latest`) | ✅ | ✅ |
168
+ | `0.2.1-alpha.2` | ✅ | ✅ |
169
+ | `0.2.1-alpha.4` (`alpha`) | ✅ | ✅ |
170
+
171
+ CI runs the matrix on Windows, Linux and macOS, nightly too, so new DSH releases are caught early. Details: [docs/compatibility.md](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/compatibility.md).
172
+
173
+ ## 🧠 How the agent decides
174
+
175
+ <img src="https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/readme/routing.svg" alt="The agent routes each prompt to plain text, ui_render or ui_render_widget; local interactions never call the model, explicit actions go back to the agent" width="100%">
40
176
 
41
177
  The plugin injects a system-prompt section (`src/guidance.js`) that makes the model route each turn itself. There is no command and no external router:
42
178
 
@@ -47,33 +183,57 @@ The plugin injects a system-prompt section (`src/guidance.js`) that makes the mo
47
183
 
48
184
  `/ui <request>` still exists to force a UI answer.
49
185
 
50
- ## Tools
51
-
52
186
  | Tool | Use it for | Rendering |
53
187
  |---|---|---|
54
188
  | `ui_render` | Structured answers | Native React components from a fixed catalog, rendered progressively as the JSON streams |
189
+ | `ui_patch` | Changing a UI shown earlier | Component-level operations on that UI; untouched components keep their state ([details](#-updating-a-ui-in-place-ui_patch)) |
55
190
  | `ui_render_widget` | Custom visuals, 3D, simulations, mini-apps | HTML/CSS/JS in an opaque-origin sandboxed iframe: CSS first, HTML morphed in live, JS last, all in **one** iframe |
56
191
 
57
- ### Component catalog (`ui_render`)
192
+ ## 🧩 Component catalog (`ui_render`)
58
193
 
59
- `Card`, `Stack`, `Grid`, `Tabs` (keyboard navigable), `Text` (markdown subset), `Code`, `Alert`, `Badge`, `Divider`, `Stat`, `Progress`, `List`, `Table` (sortable), `Chart` (bar / line / area / pie, stacked, hover tooltip, toggleable legend), `Timeline`, **`Plot`** (y = f(x) with parameter sliders that redraw instantly; expressions run in a safe compiler, never `eval`), **`Math`** (KaTeX), **`Mermaid`** (flowchart, sequence, class, state, ER, gantt, mindmap…), `Button` (sends a prompt), and `Form` (text, number, **range slider**, select, checkbox, date, textarea; submits the values as a message).
194
+ | Category | Components |
195
+ |---|---|
196
+ | Layout | `Card`, `Stack`, `Grid`, `Tabs` (keyboard navigable) |
197
+ | Text & callouts | `Text` (markdown subset), `Code`, `Alert`, `Badge`, `Divider` |
198
+ | Data | `Stat`, `Progress`, `List`, `Table` (sortable), `Timeline` |
199
+ | Charts | `Chart`: bar / line / area / pie, stacked, hover tooltip, toggleable legend |
200
+ | Math & diagrams | **`Plot`** (y = f(x) with parameter sliders that redraw instantly; expressions run in a safe compiler, never `eval`), **`Math`** (KaTeX), **`Mermaid`** (flowchart, sequence, class, state, ER, gantt, mindmap…) |
201
+ | Interaction | `Button` (sends a prompt), `Form` (text, number, **range slider**, select, checkbox, date, textarea; submits the values as a message) |
60
202
 
61
203
  A UI travels as a **flat node list with id references**, because DSH's tool-schema subset has no recursion. The Host validates it before acknowledging: unknown types or props, dangling or duplicate ids, cycles, two parents, chart length mismatches, `Plot` expressions that do not compile, slider defaults out of range, and so on. Problems come back as `ok:false` with paths, so the model can fix them and retry.
62
204
 
63
205
  `Math` and `Mermaid` render inside the same sandbox as widgets, loading KaTeX or Mermaid from the CDN allowlist. No third-party code runs in the DSH page and the host bundle stays small (~127 KB). If the CDN is unreachable, the source text is shown instead.
64
206
 
65
- ### Widgets (`ui_render_widget`)
207
+ ## 🔁 Updating a UI in place (`ui_patch`)
208
+
209
+ When the user asks to change a UI shown earlier ("make that bar chart a line chart; leave the rest"), the agent patches it by component id instead of re-sending the whole tree.
210
+
211
+ - **Identity:** every `ui_render` result tells the model the UI's `uiId` (its call id) and `version` 1. Each accepted patch reports the new version and an `id:Type` outline of the components.
212
+ - **Operations:** `setProps` (merge props, `unset` to remove), `replaceNode` (same id, new type/props/children), `insertChild` (new components under a container), and `removeNode`. They apply in order and **all-or-nothing**. The whole next version then passes the same checks as a fresh `ui_render` (types, props, references, cycles, depth, sizes, Tabs labels).
213
+ - **Concurrency:** `baseVersion` is required. A patch based on an older version is refused as stale and reports the current version, so the model re-derives the patch and retries. Patches run exclusively (never in parallel), so versions form one ordered chain.
214
+ - **Scope:** the Host stores nothing. It rebuilds the current version by replaying the conversation's own history (accepted renders, then accepted patches), so a patch can only address UIs of the same conversation. Unknown ids, other conversations' UIs and UIs dropped by context compaction are refused, and the model falls back to `ui_render`.
215
+ - **State:** components keep their ids across versions, and interaction state is keyed by `uiId` + component id. Every component a patch does not replace keeps its table sort, selected tab, form input, submitted lock and Plot slider positions. A `replaceNode`d component starts fresh; to reset any component, give it a new id. Form fields added by a patch start at their defaults.
216
+ - **Display:** while a patch streams or runs, its card is a one-line "Updating the UI…" status and the previous version stays live, so a half-applied patch is never drawn. On success the next version renders beneath the new reply and the older copy collapses to "Updated to version N". A refused, failed or stopped patch leaves the previous version untouched. The full snapshot travels in the durable result meta, so a reload shows the latest version.
217
+ - **Widgets** (`ui_render_widget`) are not patched: the agent re-renders them, and they keep their `UI.state` when it does.
218
+
219
+ ## 🪟 Sandboxed widgets (`ui_render_widget`)
220
+
221
+ <img src="https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/readme/widget-streaming.svg" alt="Widget fields stream in order: placeholder, then a live preview with morphed HTML, then the same iframe is finalized and the code runs" width="100%">
66
222
 
67
223
  Fields follow the OpenIntelligentUI streaming contract, **in this order**: `title`, `initialHeight`, `placeholderMessages`, `css`, `html`, `jsFunctions`, `jsExpressions`.
68
224
 
69
225
  - **Streaming:** placeholder lines show until `css` is complete. Then a preview frame opens and the cleaned partial `html` is morphed in ([idiomorph](https://github.com/bigskysoftware/idiomorph)). When the arguments are complete, the **same frame is finalized in place**: the final HTML is morphed and the code runs. There is no rebuild, blank flash or height jump.
70
226
  - **Runtime helpers** (global `UI`):
71
- - `UI.three(el, { camera, target, autoRotate })` returns a renderer, scene, lights and OrbitControls (drag rotates, wheel/pinch zooms), renders on demand, and handles resize.
72
- - `UI.canvas(el, draw)` gives a HiDPI 2D canvas that is redrawn on resize and theme change.
73
- - `UI.state` / `UI.setState()` persist state. Inputs with an `id` are persisted and restored automatically.
74
- - `UI.color('accent')` returns an opaque theme color for three.js/canvas, and `UI.css()` returns a raw value.
75
- - `UI.onTheme(cb)`, `UI.scheme` and `UI.reducedMotion` are also available.
76
- - Helper classes: `.ui-row`, `.ui-control`, `.ui-card`, `.ui-stage`.
227
+
228
+ | Helper | What it does |
229
+ |---|---|
230
+ | `UI.three(el, { camera, target, autoRotate })` | Renderer, scene, lights and OrbitControls (drag rotates, wheel/pinch zooms); renders on demand and handles resize |
231
+ | `UI.canvas(el, draw)` | HiDPI 2D canvas, redrawn on resize and theme change |
232
+ | `UI.state` / `UI.setState()` | Persisted state; inputs with an `id` are persisted and restored automatically |
233
+ | `UI.color('accent')` / `UI.css()` | Opaque theme color for three.js/canvas / raw CSS value |
234
+ | `UI.onTheme(cb)`, `UI.scheme`, `UI.reducedMotion` | React to theme and motion preferences |
235
+ | `.ui-row`, `.ui-control`, `.ui-card`, `.ui-stage` | Helper classes |
236
+
77
237
  - **Bridge:** `Bridge.sendPrompt({ text })` disables the clicked control until the host answers, so double clicks cannot queue duplicates. `Bridge.openLink({ url })` opens links, and `Websandbox.connection.remote.*` is kept for OpenIntelligentUI compatibility.
78
238
  - **Libraries:** an importmap maps `three`, `three/addons/…`, `d3`, `chart.js/auto`, `gsap` and `canvas-confetti` to esm.sh.
79
239
  - **Recovery:** script errors show a count with **Reload** and **Ask to fix** (the latter sends the errors to the agent).
@@ -82,61 +242,25 @@ Fields follow the OpenIntelligentUI streaming contract, **in this order**: `titl
82
242
 
83
243
  - While a turn runs, the UI renders inline inside the tool call. One component instance spans preparing → running → result, so interaction state carries over.
84
244
  - When the turn ends, DSH folds tool calls into the "process" section. The plugin re-presents each UI **beneath the reply** (`conversation.chat.turnTail`), and the inline copy shrinks to a pointer.
85
- - **Interaction state persists per tool call:** the open tab, table sort, form values, the submitted lock, Plot sliders, widget controls and `UI.state`, and frame heights. It lives in memory and in `localStorage` (bounded), so moving beneath the reply or reloading the page restores it. The UIs themselves are rebuilt from the durable session log.
245
+ - **Interaction state persists per UI** (per tool call; patched versions share their `ui_render` call's state): the open tab, table sort, form values, the submitted lock, Plot sliders, widget controls and `UI.state`, and frame heights. It lives in memory and in `localStorage` (bounded), so moving beneath the reply or reloading the page restores it. The UIs themselves are rebuilt from the durable session log.
86
246
  - **Stopped generation:**
87
247
  - If the call already reached DSH, the view keeps the finished part (a static widget preview or the completed components) with a notice and a **Regenerate** button.
88
248
  - If the user stops while the arguments are still streaming, DSH drops the call entirely. The plugin stashes what was streamed and shows it beneath the stopped turn the same way.
89
249
 
90
- ## Security model
250
+ ## 🔒 Security model
251
+
252
+ <img src="https://raw.githubusercontent.com/dreamfarer-space/deepseek-harness-intelligent-ui/main/docs/assets/readme/security.svg" alt="Widgets run in an opaque-origin sandboxed iframe under a strict CSP; access to the host page is blocked and only token-checked postMessage calls reach the rate-limited bridge" width="100%">
91
253
 
92
254
  - **Widget isolation:** widgets run in `<iframe sandbox="allow-scripts allow-forms">` with `srcdoc` and **no** `allow-same-origin`. They cannot reach the DSH page, its cookies or storage, or its API. The E2E checks this in the browser: `parent.document`, `localStorage` and `document.cookie` all throw `SecurityError`, `fetch` is blocked, and `window.origin === "null"`.
93
- - **CSP:**
94
- - `default-src 'none'`.
95
- - Scripts, styles, fonts and images load only from `cdnAllowlist`.
96
- - `connect-src 'none'` unless `allowNetwork` is on.
97
- - `form-action 'none'` and `base-uri 'none'`.
255
+ - **CSP:** `default-src 'none'`; scripts, styles, fonts and images load only from `cdnAllowlist`; `connect-src 'none'` unless `allowNetwork` is on; `form-action 'none'` and `base-uri 'none'`.
98
256
  - **Messages from the frame:** they are accepted only from that frame's `contentWindow`, must carry a per-frame random token, and are shape-checked. Persisted widget state is size-limited JSON. The E2E checks that forged messages from the page or the frame are dropped.
99
- - **`sendPrompt` limits:**
100
- - It is refused during the first second after mount.
101
- - At most one send every 2 seconds.
102
- - Text is capped at `sendPromptMaxChars`.
103
- - The message is queued as a normal user message.
257
+ - **`sendPrompt` limits:** refused during the first second after mount; at most one send every 2 seconds; text capped at `sendPromptMaxChars`; queued as a normal user message.
104
258
  - **`openLink`:** https only, optionally restricted to `openLinkAllowedOrigins`.
105
259
  - **Catalog:** text renders as React elements, never raw HTML. `Plot` expressions are compiled by a tokenizer/parser over a fixed math function table, with no `eval`, `Function`, or property access.
106
260
 
107
- ## Install
108
-
109
- You need [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) **0.1.7 – 0.2.x** (`>=0.1.7-rc.1 <0.3.0`) and Node.js ≥ 22.13, on Windows, Linux or macOS. Nothing is compiled during install. The package ships its prebuilt browser bundle and declares no install scripts.
110
-
111
- ```sh
112
- dsh plugin --profile web add deepseek-harness-intelligent-ui
113
- ```
114
-
115
- To have your environment checked first, use the guided installer instead. It verifies Node, the `dsh` version, pnpm and the profile, then runs the same `dsh plugin add`:
116
-
117
- ```sh
118
- npx deepseek-harness-intelligent-ui install # --profile <name>, --dsh <path>, --dry-run
119
- npx deepseek-harness-intelligent-ui doctor # diagnose only
120
- ```
121
-
122
- | Task | Command |
123
- |---|---|
124
- | Upgrade | `dsh plugin --profile web add deepseek-harness-intelligent-ui@latest` |
125
- | Try a prerelease (only when the `next` dist-tag exists) | `dsh plugin --profile web add deepseek-harness-intelligent-ui@next` |
126
- | Pin a version | `dsh plugin --profile web add deepseek-harness-intelligent-ui@0.2.2` |
127
- | Uninstall | `dsh plugin --profile web remove deepseek-harness-intelligent-ui` (or `npx deepseek-harness-intelligent-ui uninstall`) |
128
- | From a checkout | `dsh plugin --profile web add file:/path/to/deepseek-harness-intelligent-ui` |
129
-
130
- Supported DSH versions and the CI matrix are in [docs/compatibility.md](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/v0.2.2/docs/compatibility.md).
131
-
132
- **Troubleshooting**
133
-
134
- - *`pnpm was not found`*: DSH installs plugins with pnpm. Run `corepack enable` (it ships with Node) or `npm i -g pnpm`.
135
- - *`… is incompatible with dsh X`*: your DSH is outside the supported range. Upgrade DSH (`npm i -g @deepseek-ai/dsh@latest`) or check [compatibility](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/v0.2.2/docs/compatibility.md) for a plugin release that supports it.
136
- - *Upgrading from 0.1.x (`dsh-intelligent-ui` via `file:`)*: run `dsh plugin --profile web remove dsh-intelligent-ui` first. Both packages insert the same `intelligent-ui` row, and `doctor` reports the conflict.
137
- - *The UI does not appear*: restart DSH Web after installing, and make sure you started the profile you installed into (`dsh web`).
261
+ ## ⚙️ Configuration
138
262
 
139
- Restart DSH Web afterwards. The bundle patch inserts the plugin row automatically. To change settings, add to your profile's `cordis.patch.yml`:
263
+ The bundle patch inserts the plugin row automatically. To change settings, add to your profile's `cordis.patch.yml`:
140
264
 
141
265
  ```yaml
142
266
  - id: intelligent-ui
@@ -147,7 +271,7 @@ Restart DSH Web afterwards. The bundle patch inserts the plugin row automaticall
147
271
 
148
272
  | Option | Default | Meaning |
149
273
  |---|---|---|
150
- | `enableCatalog` | `true` | Register `ui_render` |
274
+ | `enableCatalog` | `true` | Register `ui_render` and `ui_patch` |
151
275
  | `enableWidget` | `true` | Register `ui_render_widget` |
152
276
  | `injectGuidance` | `true` | Add the "when to use UI" system-prompt section |
153
277
  | `cdnAllowlist` | esm.sh, jsdelivr, unpkg | Origins widgets (and Math/Mermaid) may load resources from |
@@ -157,22 +281,55 @@ Restart DSH Web afterwards. The bundle patch inserts the plugin row automaticall
157
281
  | `sendPromptMaxChars` | `4000` | Longest message a widget, button or form may send |
158
282
  | `openLinkAllowedOrigins` | `[]` | If non-empty, the only origins `openLink` may open |
159
283
 
160
- ## Develop and verify
284
+ ## 🛠 Develop and verify
161
285
 
162
286
  ```sh
163
287
  npm install
164
288
  npm run build # client-src/ → client/client.js (DSH __ModuleLoader__ factory; React and Cordis external)
165
289
  npm test # vitest (catalog, expressions, tools, plugin mount, streaming parser, frame/CSP/bridge, state, theme, views, interruption, turn tail, DSH compatibility gate, installer CLI)
166
290
  npm run typecheck
167
- npm run e2e:all # build → install into a temp DSH home → mock model + real DSH Web → 24 browser checks
291
+ npm run e2e:all # build → install into a temp DSH home → mock model + real DSH Web → 30 browser checks
168
292
  npm run e2e:all -- --record # also re-records docs/assets/demo.mp4 / demo.gif (needs ffmpeg)
169
293
  npm run e2e:all -- --spec ./deepseek-harness-intelligent-ui-<v>.tgz # E2E the packed tarball instead of the checkout
170
294
  npm run smoke -- --spec ./deepseek-harness-intelligent-ui-<v>.tgz # install smoke test (no browser): version gate, bundle row, DSH Web boot
295
+ npm run docs:art # regenerate the animated README illustrations in docs/assets/readme/
296
+ npm run eval:real # real-model quality baseline (needs DEEPSEEK_API_KEY): see below
171
297
  ```
172
298
 
173
- The E2E runs on Windows, Linux and macOS. It finds Chrome per platform (set `CHROME_PATH` to override) and uses `DSH_BIN` to test another `dsh` binary. CI runs everything on all three OSes against every supported DSH line ([compatibility](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/v0.2.2/docs/compatibility.md)).
299
+ The E2E runs on Windows, Linux and macOS. It finds Chrome per platform (set `CHROME_PATH` to override) and uses `DSH_BIN` to test another `dsh` binary. CI runs everything on all three OSes against every supported DSH line ([compatibility](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/compatibility.md)).
174
300
 
175
- **Releasing** (maintainers):
301
+ - **Layout:** `src/` is the Host half (plain ESM, no build). `client-src/` is the browser half (TSX, bundled with esbuild). `client/client.js` is committed and published so npm, `file:` and git installs need no build step. CI fails if it is stale. Never add a `prepare`/`postinstall` script, because DSH's pnpm blocks plugin build scripts.
302
+ - Requires Chrome (`CHROME_PATH`), `dsh` on PATH and, for `--record`, `ffmpeg`. Headless Chrome uses SwiftShader for WebGL.
303
+
304
+ <details>
305
+ <summary><b>✅ What the browser E2E checks</b> (<code>scripts/e2e/browser.mjs</code>)</summary>
306
+ <br>
307
+
308
+ Report: [`docs/e2e-report.json`](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/v0.2.1/docs/e2e-report.json).
309
+
310
+ - Plain Q&A stays text.
311
+ - The dashboard streams progressively.
312
+ - Chart hover, table sort and tabs are local.
313
+ - A form round-trip returns a new UI.
314
+ - A second-turn `ui_patch` turns the dashboard chart from bar to line: an invalid first attempt is refused, the old copy collapses, and sort, tab and form state are kept, including after a reload.
315
+ - The preview iframe is finalized in place.
316
+ - The Three.js scene renders, and the R slider, drag-rotate and wheel-zoom work.
317
+ - The `Bridge.sendPrompt` round trip works.
318
+ - KaTeX and Mermaid render.
319
+ - Plot sliders work.
320
+ - Earlier UIs keep their state.
321
+ - Stop → static preview → Regenerate works.
322
+ - The sandbox is isolated and forged messages are ignored.
323
+ - Dark theme reaches cards and widgets.
324
+ - Phone width has no overflow.
325
+ - A reload restores the UIs and their state.
326
+ - No plugin console errors appear.
327
+
328
+ </details>
329
+
330
+ <details>
331
+ <summary><b>📦 Releasing</b> (maintainers)</summary>
332
+ <br>
176
333
 
177
334
  See [release security and OIDC setup](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/releasing.md).
178
335
 
@@ -185,47 +342,103 @@ See [release security and OIDC setup](https://github.com/dreamfarer-space/deepse
185
342
  - creates the GitHub Release with the changelog section and the tarball.
186
343
  4. Run `npm run release:check` to perform the same preflight locally.
187
344
 
188
- - **Layout:** `src/` is the Host half (plain ESM, no build). `client-src/` is the browser half (TSX, bundled with esbuild). `client/client.js` is committed and published so npm, `file:` and git installs need no build step. CI fails if it is stale. Never add a `prepare`/`postinstall` script, because DSH's pnpm blocks plugin build scripts.
189
- - **What the E2E checks** (`scripts/e2e/browser.mjs`, report in [`docs/e2e-report.json`](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/v0.2.1/docs/e2e-report.json)):
190
- - Plain Q&A stays text.
191
- - The dashboard streams progressively.
192
- - Chart hover, table sort and tabs are local.
193
- - A form round-trip returns a new UI.
194
- - The preview iframe is finalized in place.
195
- - The Three.js scene renders, and the R slider, drag-rotate and wheel-zoom work.
196
- - The `Bridge.sendPrompt` round trip works.
197
- - KaTeX and Mermaid render.
198
- - Plot sliders work.
199
- - Earlier UIs keep their state.
200
- - Stop → static preview → Regenerate works.
201
- - The sandbox is isolated and forged messages are ignored.
202
- - Dark theme reaches cards and widgets.
203
- - Phone width has no overflow.
204
- - A reload restores the UIs and their state.
205
- - No plugin console errors appear.
206
- - Requires Chrome (`CHROME_PATH`), `dsh` on PATH and, for `--record`, `ffmpeg`. Headless Chrome uses SwiftShader for WebGL.
345
+ </details>
346
+
347
+ ## 📊 Real-model quality baseline
348
+
349
+ A tool call whose arguments parse is not the same as a UI the user can use. `npm run eval:real` measures three layers **separately**, and a pass in one is not evidence for another:
350
+
351
+ | Layer | What it proves | Latest · 2026-10-16 · `deepseek-flash` |
352
+ | --- | --- | :---: |
353
+ | **Mock E2E** | Rendering, streaming, interaction, persistence and isolation, with **scripted** model answers | **30 / 30** |
354
+ | **Real model · API** | The model picks the right tool, emits valid arguments on the **first** try, and the rendered UI contains the requested components and data. The model sees the plugin's real tools and guidance; the tools' own `execute()` runs, including `ui_patch` | **30 / 32** (94%) |
355
+ | **Real model · browser** | What the model generated actually works in real DSH Web + Chrome: it renders, responds to interaction, survives stop/resume and bad networks, and logs no console errors | **9 / 11** automated<br>**8 / 11** after manual review |
207
356
 
208
- ## Real-model Evaluation
357
+ | Timing (real model) | p50 | p95 |
358
+ | --- | ---: | ---: |
359
+ | First visible UI preview (browser) | 7.6 s | 70.2 s |
360
+ | UI generation complete (browser) | 11.8 s | 73.4 s |
361
+ | Browser-side execution after the stream (widget code, three.js from the CDN) | 26 ms | 2.9 s |
362
+ | One model API call | 2.4 s | 75.7 s |
209
363
 
210
- The API suite checks model routing, tool-argument schemas, and selected requested components or data across 15 prompts. It does **not** execute model-generated widgets in a browser or verify factual data, visual quality, physics, or interaction behavior. The separate browser E2E uses scripted model responses.
364
+ The long tail comes from large Three.js widgets: the model writes 10–20 KB of code before the preview starts.
211
365
 
212
- The checked-in [historical report](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/v0.2.1/docs/real-model-eval-report.json) was produced by the earlier evaluator. Its 15/15 result is not a current pass rate: B3 was incorrectly accepted despite containing no Chart. Other cases need a new run under the stricter assertions; the original report is retained with an audit note.
366
+ **What the run found.** These are real model and agent behaviours, not harness noise; each one is a failed or flagged case in the [full report](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/real-model-eval.md):
213
367
 
214
- The current suite requires rendered components (not orphan nodes), a sortable table for B2, a four-quarter multi-series bar chart for B3, range/checkbox form controls for B4, and parameter sliders for a native C2 Plot. Multi-turn cases validate all tool calls, acknowledge each call, require the submitted APAC/4/Optimistic parameters, and check all three requested screen sizes while retaining existing table values. These are automated assertions, not a complete quality assessment.
368
+ - **A tool call cut off mid-stream (R-chart):** the chart rendered correctly, but the turn ended while more components were still streaming. The plugin correctly downgraded the card to "generation interrupted, showing the completed part" with a Regenerate button. The automated content checks passed, but the manual review counts it as a failure, and a new check now fails any case that leaves an interrupted card.
369
+ - **Agent research instead of a UI (R-table):** DSH's agent spent the whole 5-minute budget researching car specs on the web (20 steps, about 1.3M tokens) and never rendered the table.
370
+ - **Three.js scope slips:** two recordings, two variants of the same bug. `view is not defined` in this run and `THREE is not defined` in the previous one: the widget code used a variable outside its scope. The card flags "1 个脚本错误", and the user's **"让模型修复"** button produced a working scene both times.
371
+ - **Invalid first attempts (API):** a missing closing brace in nested `props` (D2, both turns) and duplicate component ids (B1). The plugin refuses these and the model fixes them on retry, but the user briefly sees a refused card, so these count as failures.
372
+ - **Agent behaviour around the UI:** DSH's agent may first search the workspace, ask a clarifying question, or send sub-agents to research facts. The browser stage answers clarifying questions like a user would (picking the recommended option) and waits for the UI itself, not just for the turn to go idle.
215
373
 
216
- Set `DEEPSEEK_API_KEY` in the environment before running; no credentials file is read:
374
+ <details>
375
+ <summary><b>▶️ Run it</b></summary>
376
+ <br>
377
+
378
+ Put the key in the environment. No credentials file is read, and the run refuses to write a report that still contains the key.
217
379
 
218
380
  ```sh
219
- export DEEPSEEK_API_KEY='<your-key>'
220
- node scripts/run-real-model-suite.mjs all # A, B, C, D or all
381
+ export DEEPSEEK_API_KEY='<your-key>' # PowerShell: $env:DEEPSEEK_API_KEY = '<your-key>'
382
+ npm run eval:real # mock E2E + API + browser; exits 1 on any failure
383
+ npm run eval:real -- --repeat 3 # 3 runs per API case → per-case success rates, p50/p95
384
+ npm run eval:real -- --record && npm run eval:demo # also record and cut the demo video
385
+ npm run eval:gallery # README screenshots on one uniform 1200×750 canvas
221
386
  ```
222
387
 
223
- ```powershell
224
- $env:DEEPSEEK_API_KEY = '<your-key>'
225
- node scripts/run-real-model-suite.mjs all
226
- ```
388
+ | Option | Effect |
389
+ | --- | --- |
390
+ | `--stage mock,api,browser` | Run only some layers (`all` by default). `A`/`B`/`C`/`D` selects API categories, as before. |
391
+ | `--only B3,R-chart` | Run only these cases. |
392
+ | `--keep-other-layers` | Keep the layers (or, with `--only`, the cases) this run did not execute. Each keeps its own date. |
393
+ | `--append` | With `--only`: add the rerun as extra samples. A failure is never replaced by a lucky retry. |
394
+ | `--rescore` | Re-judge the last API run from its saved transcripts with the current assertions, with no model calls. |
395
+ | `DEEPSEEK_EVAL_MODEL` | Model id (default `deepseek-flash`). |
396
+
397
+ The **Real-model eval** GitHub workflow runs the same command on demand with the `DEEPSEEK_API_KEY` secret and uploads the report and screenshots. A full run costs about ¥1.5 with `deepseek-flash`.
398
+
399
+ </details>
400
+
401
+ <details>
402
+ <summary><b>🔬 What each case checks</b></summary>
403
+ <br>
404
+
405
+ **API stage** (`scripts/eval/api-stage.mjs`, 16 cases, assertions v3). Every criterion is its own check, so the report says *which* one failed:
406
+
407
+ - **Plain text (A1–A4):** no UI tool, a substantive answer.
408
+ - **Charts/tables (B1–B3, B5):**
409
+ - KPI tiles named by meaning (revenue, active users incl. 月活/MAU, conversion, order value);
410
+ - a sortable table with all five requested columns;
411
+ - a **bar chart with Q1–Q4 and both an online and an offline series** (the 2026-10-10 B3 answer, a form without a chart, is kept as a test fixture and must fail);
412
+ - a chronology with 4+ milestones (a `Timeline`, or a Mermaid diagram with 4+ ordered years).
413
+ - **Form (B4, D1):** range, checkbox and select fields. D1 submits the form exactly as the browser would and requires the follow-up to use the submitted region, quarter count and scenario.
414
+ - **Math Plot (C2):** three sliders, *every slider used in the expression*, and an `exp(…)·sin/cos(…)` shape.
415
+ - **Three.js and widgets (C1, C3, C4):** `UI.three()`/`import("three")`, animation, lights, start/pause and parameter controls.
416
+ - **Multi-turn edits:** D2 adds a column with every per-model value correct and every old value kept. D3 makes two successive edits (bar → line, then a third series with exact values, original series unchanged). `ui_patch` in place is recorded.
227
417
 
228
- Each invocation writes only that run's cases to `docs/real-model-eval-report.json`. A partial run does not merge older cases into a new timestamp. Failed assertions/API calls exit with code 1; an invalid category or missing key fails before changing the report. Keep keys out of git.
418
+ Each case records tool choice, first-attempt schema validity versus recovery, API time per call, the model id the API reports and its `system_fingerprint`, and the prompt hash.
419
+
420
+ **Browser stage** (`scripts/eval/browser-stage.mjs`, 11 cases, each in a new conversation):
421
+
422
+ - **Plain text:** no card is created.
423
+ - **Chart:** legend, Q1–Q4, 8+ bars, local hover tooltip.
424
+ - **Table:** clicking a header re-sorts rows with no model call.
425
+ - **Form:** filled and submitted with the real mouse and keyboard; the follow-up must mention the submitted values; the form locks.
426
+ - **Plot:** dragging a slider redraws the curve.
427
+ - **Three.js:** the canvas is **drawn, not blank** (pixel statistics), dragging changes the image, and there are no widget script errors. On failure, the "Ask to fix" path is tried and reported.
428
+ - **Stop/resume:** stop mid-stream → static preview, notice and Regenerate → a complete scene.
429
+ - **Two in-place edits:** one live card, version 3, line chart, third series.
430
+ - **Weak network:** Fast 3G with the cache off.
431
+ - **No network:** the CDN is blocked; the failure must be shown on the card, the DSH page must have no uncaught errors, and the chat must keep working.
432
+ - **Phone width (390 px):** no overflow; the KPI grid uses at most 2 columns.
433
+
434
+ Every case also records console errors, with the host page separated from widget frames. DSH run failures such as quota errors are reported as *infrastructure* errors, outside the model's success rate.
435
+
436
+ Automated checks cannot judge visual quality, so the report also carries a **sampled manual review** of every screenshot.
437
+
438
+ </details>
439
+
440
+ > [!IMPORTANT]
441
+ > The [2026-10-10 report](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/history/2026-10-10-real-model-eval-report.json) (15/15) is a **historical snapshot**, not a pass rate. It came from an API-only evaluator that passed B3 although no chart was rendered, and it did not keep the arguments needed to rescore it. The current numbers are in [`docs/real-model-eval.md`](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/real-model-eval.md) (human-readable) and [`docs/real-model-eval-report.json`](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/blob/main/docs/real-model-eval-report.json).
229
442
 
230
443
  ## Limitations
231
444
 
@@ -236,3 +449,5 @@ Each invocation writes only that run's cases to `docs/real-model-eval-report.jso
236
449
  ## Credits
237
450
 
238
451
  Concepts, the streaming widget contract, and parts of the prompt guidance come from [CopilotKit/OpenIntelligentUI](https://github.com/CopilotKit/OpenIntelligentUI) (MIT). See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). Licensed under MIT.
452
+
453
+ **Whale girl in the demo video.** The DeepSeek whale girl (鲸鱼娘) who narrates the demo, in 26 poses, is by [Sutera-Diffusus/dsh-whale-musume](https://github.com/Sutera-Diffusus/dsh-whale-musume) (MIT © Sutera-Diffusus; illustrations by [@SuteraWu](https://github.com/SuteraWu)). The unmodified files and their licence are in [`scripts/eval/demo/assets/whale/`](https://github.com/dreamfarer-space/deepseek-harness-intelligent-ui/tree/main/scripts/eval/demo/assets/whale). They are not part of the npm package.