Design Prototype
One HTML file, double-click to open, fully interactive, rendered by the actual
design system (@lobehub/ui 5.x + antd 6 + antd-style tokens, exact versions from
this repo's node_modules) — while the source stays production-style React
(import { Block } from '@lobehub/ui', createStyles(({ css, token }) => …), hooks,
memo). Promotion to production is mostly "split into files", not "rewrite".
Prototype code may be quick-and-dirty (one file, no i18n, inline data) — but the
interaction must be complete: states, transitions, and affordances are the point
of a prototype, not its code style.
How it works
one-time (~2s, cached): scripts/build-runtime.sh → lobe-prototype-runtime.js (IIFE global)
+ vendored babel.min.js
per prototype (no build): single HTML = <script runtime> + babel-standalone
+ <script type="text/babel"> with production-style React
- The runtime bundles
react, react-dom/client, @lobehub/ui, @lobehub/ui/base-ui,
antd (curated subset), antd-style, lucide-react from the repo's own
node_modules — so tokens/components/versions match production, and react/emotion/
theme-context are singletons by construction.
- A 3-line
window.require shim + babel's transform-modules-commonjs lets the
prototype keep real import statements.
ThemeProvider themeMode="auto" → light/dark follow the OS.
⚠️ Don't re-attempt the pure-CDN route (esm.sh import maps). It was tried and
rejected: esm.sh's CJS named-export analysis fails serially across antd's dep chain
(@ant-design/fast-color → @ant-design/colors → @rc-component/qrcode → …), and
?bundle leaks an unversioned react-is 404. Whack-a-mole, not a foundation.
Quickstart
Build the runtime into the prototype's directory (any dir works; /tmp is fine):
bash .agents/skills/design-prototype/scripts/build-runtime.sh /tmp/my-proto
Skip if lobe-prototype-runtime.js + babel.min.js are already there and the
design-system versions haven't bumped.
Copy references/template.html into the same dir,
rename, and replace the sample App with the real surface. Keep the runtime shim
block untouched.
Open it (open /tmp/my-proto/xxx.html) — no server, no build.
Verify before delivering
Headless-check it renders with zero console errors (the repo's e2e Playwright works):
// node /tmp/check.mjs — adjust paths
import { chromium } from '<repo>/e2e/node_modules/playwright/index.mjs';
const b = await chromium.launch();
const p = await b.newPage();
const errs = [];
p.on('pageerror', (e) => errs.push(String(e)));
await p.goto('file:///tmp/my-proto/xxx.html');
await p.waitForSelector('#root *', { timeout: 30000 });
await p.screenshot({ path: '/tmp/proto.png' }); // Read the screenshot yourself
console.log('errors:', errs);
await b.close();
Then Read the screenshot — a prototype is a visual deliverable; don't ship it
sight-unseen. Check light + dark (emulate prefers-color-scheme) and a narrow
viewport if the surface has a mobile story.
What's in the runtime (extend freely)
See assets/entry.mjs — the single source of truth. Currently:
@lobehub/ui: ActionIcon, Alert, Avatar, Block, Button, Center, Collapse,
DraggablePanel, Drawer, DropdownMenu, Empty, Flexbox, Highlighter, Hotkey, Icon,
Image, Input, InputNumber, Markdown, Modal, NeuralNetworkLoading, Popover,
ScrollShadow, SearchBar, Segmented, Select, Skeleton, SortableList, Tabs, Tag,
Text, TextArea, ThemeProvider, Tooltip
@lobehub/ui/base-ui: full namespace (Select, Modal, DropdownMenu, Switch, Toast,
FloatingSheet, …)
antd: App, Badge, Checkbox, Divider, Dropdown, Progress, Radio, Slider, Space,
Steps, Table
antd-style, lucide-react, react, react-dom/client: full namespaces
Missing a component → add to entry.mjs, rerun the build script (~2s). Sizes:
curated ≈ 16MB (fine from disk); a full export * from '@lobehub/ui' works too
(≈ 27MB) if you'd rather never curate.
Pitfalls
Switch lives in @lobehub/ui/base-ui, not the root package (as do the other
base-ui primitives). Importing it from the root fails the runtime build.
- Use
createStyles (runtime), not createStaticStyles — static extraction
needs a build step. This is the one sanctioned deviation from production style;
note it when handing the prototype to an implementer.
- babel must stay the raw UMD file (unpkg/jsdelivr). esm.sh rewrites it to ESM
and a classic
<script src> chokes on export.
- The require shim throws with the module name when an import isn't in the runtime —
that's the "add to entry.mjs and rebuild" signal, not a template bug.
Text type accepts only secondary|success|warning|danger|info; Tag color has
no primary (DESIGN.md "Applying tokens in components").
Design bar (same as production surfaces)
A prototype is a surface — the ux checklists apply to what it
demonstrates:
- Show at least one non-happy-path state (empty / loading / error / in-progress),
ideally behind a state-toggle strip like the template's. A happy-path-only
prototype under-specifies the design and silently blesses missing states.
- Reuse the app's surface contracts: side panels are
DraggablePanel
(collapse + drag-resize come free), loading is skeleton/NeuralNetworkLoading
(never antd Spin), modals via createModal-style flows.
- Don't paint affordances you don't wire (
cursor: zoom-in with no zoom, keycap
chips with no keys) — in an interactive prototype a dead affordance is a spec bug.
- Walk the ux Quick review against the prototype before delivering; annotate
anything deliberately out of scope in an HTML comment so the implementer knows
it's a cut, not a decision.
1---2name: design-prototype3description: Use for interactive HTML prototypes (交互原型) with the real LobeHub UI stack. Excludes production UI implementation.4---5
6# Design Prototype
7
8One HTML file, double-click to open, fully interactive, rendered by the **actual
9design system** (`@lobehub/ui` 5.x + antd 6 + antd-style tokens, exact versions from
10this repo's `node_modules`) — while the source stays **production-style React**
11(`import { Block } from '@lobehub/ui'`, `createStyles(({ css, token }) => …)`, hooks,
12`memo`). Promotion to production is mostly "split into files", not "rewrite".
13
14Prototype code may be quick-and-dirty (one file, no i18n, inline data) — but the
15**interaction must be complete**: states, transitions, and affordances are the point
16of a prototype, not its code style.
17
18## How it works
19
20```
21one-time (~2s, cached): scripts/build-runtime.sh → lobe-prototype-runtime.js (IIFE global)
22 + vendored babel.min.js
23per prototype (no build): single HTML = <script runtime> + babel-standalone
24 + <script type="text/babel"> with production-style React
25```
26
27- The runtime bundles `react`, `react-dom/client`, `@lobehub/ui`, `@lobehub/ui/base-ui`,
28 `antd` (curated subset), `antd-style`, `lucide-react` from the repo's own
29 `node_modules` — so tokens/components/versions match production, and react/emotion/
30 theme-context are singletons by construction.
31- A 3-line `window.require` shim + babel's `transform-modules-commonjs` lets the
32 prototype keep real `import` statements.
33- `ThemeProvider themeMode="auto"` → light/dark follow the OS.
34
35> ⚠️ Don't re-attempt the pure-CDN route (esm.sh import maps). It was tried and
36> rejected: esm.sh's CJS named-export analysis fails serially across antd's dep chain
37> (`@ant-design/fast-color` → `@ant-design/colors` → `@rc-component/qrcode` → …), and
38> `?bundle` leaks an unversioned `react-is` 404. Whack-a-mole, not a foundation.
39
40## Quickstart
41
421. **Build the runtime into the prototype's directory** (any dir works; `/tmp` is fine):
43
44 ```bash
45 bash .agents/skills/design-prototype/scripts/build-runtime.sh /tmp/my-proto
46 ```
47
48 Skip if `lobe-prototype-runtime.js` + `babel.min.js` are already there and the
49 design-system versions haven't bumped.
50
512. **Copy [`references/template.html`](references/template.html)** into the same dir,
52 rename, and replace the sample `App` with the real surface. Keep the runtime shim
53 block untouched.
54
553. **Open it** (`open /tmp/my-proto/xxx.html`) — no server, no build.
56
57## Verify before delivering
58
59Headless-check it renders with zero console errors (the repo's e2e Playwright works):
60
61```js
62// node /tmp/check.mjs — adjust paths
63import { chromium } from '<repo>/e2e/node_modules/playwright/index.mjs';
64const b = await chromium.launch();
65const p = await b.newPage();
66const errs = [];
67p.on('pageerror', (e) => errs.push(String(e)));
68await p.goto('file:///tmp/my-proto/xxx.html');
69await p.waitForSelector('#root *', { timeout: 30000 });
70await p.screenshot({ path: '/tmp/proto.png' }); // Read the screenshot yourself
71console.log('errors:', errs);
72await b.close();
73```
74
75Then **Read the screenshot** — a prototype is a visual deliverable; don't ship it
76sight-unseen. Check light + dark (emulate `prefers-color-scheme`) and a narrow
77viewport if the surface has a mobile story.
78
79## What's in the runtime (extend freely)
80
81See [`assets/entry.mjs`](assets/entry.mjs) — the single source of truth. Currently:
82
83- `@lobehub/ui`: ActionIcon, Alert, Avatar, Block, Button, Center, Collapse,
84 DraggablePanel, Drawer, DropdownMenu, Empty, Flexbox, Highlighter, Hotkey, Icon,
85 Image, Input, InputNumber, Markdown, Modal, NeuralNetworkLoading, Popover,
86 ScrollShadow, SearchBar, Segmented, Select, Skeleton, SortableList, Tabs, Tag,
87 Text, TextArea, ThemeProvider, Tooltip
88- `@lobehub/ui/base-ui`: full namespace (Select, Modal, DropdownMenu, Switch, Toast,
89 FloatingSheet, …)
90- `antd`: App, Badge, Checkbox, Divider, Dropdown, Progress, Radio, Slider, Space,
91 Steps, Table
92- `antd-style`, `lucide-react`, `react`, `react-dom/client`: full namespaces
93
94Missing a component → add to `entry.mjs`, rerun the build script (\~2s). Sizes:
95curated ≈ 16MB (fine from disk); a full `export * from '@lobehub/ui'` works too
96(≈ 27MB) if you'd rather never curate.
97
98## Pitfalls
99
100- **`Switch` lives in `@lobehub/ui/base-ui`**, not the root package (as do the other
101 base-ui primitives). Importing it from the root fails the runtime build.
102- **Use `createStyles` (runtime), not `createStaticStyles`** — static extraction
103 needs a build step. This is the one sanctioned deviation from production style;
104 note it when handing the prototype to an implementer.
105- **babel must stay the raw UMD file** (unpkg/jsdelivr). esm.sh rewrites it to ESM
106 and a classic `<script src>` chokes on `export`.
107- The require shim throws with the module name when an import isn't in the runtime —
108 that's the "add to entry.mjs and rebuild" signal, not a template bug.
109- `Text type` accepts only `secondary|success|warning|danger|info`; `Tag color` has
110 no `primary` (DESIGN.md "Applying tokens in components").
111
112## Design bar (same as production surfaces)
113
114A prototype is a surface — the [ux](../ux/SKILL.md) checklists apply to what it
115_demonstrates_:
116
117- Show **at least one non-happy-path state** (empty / loading / error / in-progress),
118 ideally behind a state-toggle strip like the template's. A happy-path-only
119 prototype under-specifies the design and silently blesses missing states.
120- Reuse the app's **surface contracts**: side panels are `DraggablePanel`
121 (collapse + drag-resize come free), loading is skeleton/`NeuralNetworkLoading`
122 (never antd `Spin`), modals via `createModal`-style flows.
123- Don't paint affordances you don't wire (`cursor: zoom-in` with no zoom, keycap
124 chips with no keys) — in an _interactive_ prototype a dead affordance is a spec bug.
125- Walk the ux Quick review against the prototype before delivering; annotate
126 anything deliberately out of scope in an HTML comment so the implementer knows
127 it's a cut, not a decision.