htmlboard 0.4.0 → 0.6.0
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 +190 -15
- package/bin/htmlboard.js +176 -4
- package/package.json +1 -1
- package/skills/htmlboard/SKILL.md +28 -33
- package/skills/htmlboard-components/SKILL.md +36 -0
- package/skills/htmlboard-designs-json/SKILL.md +25 -0
- package/src/canvases.js +38 -0
- package/src/catalog.js +449 -0
- package/src/check.js +143 -0
- package/src/client/check.js +349 -0
- package/src/client/explorer.js +68 -2
- package/src/client/probe.js +41 -0
- package/src/components.js +95 -0
- package/src/designs.js +107 -23
- package/src/index.js +18 -0
- package/src/migrate.js +2 -2
- package/src/server.js +167 -11
- package/src/skills.js +2 -1
- package/src/templates/01-screens/01-dashboard.html +112 -0
- package/src/templates/01-screens/02-customers.html +77 -0
- package/src/templates/01-screens/{02-mobile.html → 03-mobile.html} +4 -54
- package/src/templates/{02-components/01-ui-kit.html → 02-ui-kit/01-overview.html} +16 -4
- package/src/templates/components/app-sidebar.html +67 -0
- package/src/templates/components/app-topbar.html +19 -0
- package/src/templates/components/stat-card.html +14 -0
- package/src/templates/components/tx-item.html +33 -0
- package/src/templates/components.js +180 -0
- package/src/templates/styles.css +24 -2
- package/src/templates/01-screens/01-desktop.html +0 -281
package/README.md
CHANGED
|
@@ -31,8 +31,11 @@ Modern AI agents and developers generate dozens of HTML mockups, layout variatio
|
|
|
31
31
|
|
|
32
32
|
- 🛸 **Infinite Canvas**: Drag or scroll to move, <kbd>Ctrl</kbd>/<kbd>⌘</kbd> + scroll or pinch to zoom (2% to 800%), with a mouse, a trackpad or a touch screen. Names and section titles stay sharp at any zoom, and the dot grid keeps its spacing.
|
|
33
33
|
- ⚡ **Zero Dependencies**: Pure native Node.js ES modules, fonts included. Runs instantly with `npx htmlboard`, offline too.
|
|
34
|
-
- 🔄 **State-Preserving Live Reload**: The server watches the project and pushes every save (Server-Sent Events). Saving a design reloads its frame; saving a file designs use (a stylesheet, a script, an image, data) reloads just the designs that loaded it; new, removed and renamed designs show up by themselves. Your view never moves.
|
|
34
|
+
- 🔄 **State-Preserving Live Reload**: The server watches the project and pushes every save (Server-Sent Events). Saving a design reloads its frame; saving a file designs use (a stylesheet, a component, a script, an image, data) reloads just the designs that loaded it; new, removed and renamed designs show up by themselves. Your view never moves.
|
|
35
35
|
- 📂 **Designs That Describe Themselves**: Every HTML file is a design, each folder a section, number prefixes the order. A file says its own size, category and variants in a `<meta>` tag or two, so adding a design means writing one file — nothing to keep in sync.
|
|
36
|
+
- 🧩 **Components**: Each file in `components/` is a custom element named after it (`tx-item.html` is `<tx-item>`), so the sidebar every screen shares is written once and used as one tag: attributes, slots and scoped CSS, built in the light DOM with your `styles.css`, no build step.
|
|
37
|
+
- 🗂️ **A Catalog for Agents**: `htmlboard catalog` reads the project and prints what the designs share in a few lines: each stylesheet's variables and classes, the scripts, every component with its attributes, an example and where it's used. An agent learns your styles and components without reading every file, and since it's read from the files, it is never out of date.
|
|
38
|
+
- 🩺 **Checks for Agents**: `htmlboard check` has the canvas open in your browser load each design again and report, as text, what went wrong: script errors, missing files and components, fonts that didn't load, content cut at the frame's edge, text cut off or spilling out of its box. An agent checks its work without screenshots.
|
|
36
39
|
- 📐 **Optional `designs.json`**: Curates when you want it (section titles, order, overrides); designs it doesn't list still show at the end, or don't (`"unlisted": "hide"`). `htmlboard migrate` moves an existing one into the files.
|
|
37
40
|
- 📱 **Device Filters**: **Mobile**, **Tablet** and **Desktop** (and **Components**, or your own categories) lay out just those designs, side by side, to compare them.
|
|
38
41
|
- 🔎 **Search**: Find designs by name, file, tags or section (case and accents don't matter); the others fade out in place.
|
|
@@ -59,7 +62,7 @@ htmlboard
|
|
|
59
62
|
|
|
60
63
|
### Scaffold a Starter Project
|
|
61
64
|
|
|
62
|
-
To start with sample designs (a folder of screens and a
|
|
65
|
+
To start with sample designs (a folder of screens and a UI kit, each file describing itself, all sharing one `styles.css` and four components):
|
|
63
66
|
|
|
64
67
|
```bash
|
|
65
68
|
npx htmlboard --init
|
|
@@ -75,6 +78,9 @@ Usage:
|
|
|
75
78
|
npx htmlboard [directory] [options]
|
|
76
79
|
htmlboard skills <install|uninstall> [options] Teach your AI agent to use HtmlBoard
|
|
77
80
|
htmlboard migrate [directory] [--write] Move designs.json's sizes, titles and variants into the files
|
|
81
|
+
htmlboard components [directory] [--force] Add components.js: files in components/ become custom elements
|
|
82
|
+
htmlboard catalog [directory] What the designs share: stylesheets, scripts, components
|
|
83
|
+
htmlboard check [directory or file] [-p <port>] What went wrong in the designs, from the canvas open in a browser
|
|
78
84
|
|
|
79
85
|
Options:
|
|
80
86
|
-p, --port <number> Port to listen on (default: 3000)
|
|
@@ -87,6 +93,8 @@ Options:
|
|
|
87
93
|
-h, --help Display help message
|
|
88
94
|
```
|
|
89
95
|
|
|
96
|
+
Running `htmlboard` for a folder it already shows (same folder and root, same version) doesn't start a second canvas on the next port: it prints the running one's URL, and opens it with `--open`.
|
|
97
|
+
|
|
90
98
|
### Examples
|
|
91
99
|
|
|
92
100
|
```bash
|
|
@@ -105,6 +113,15 @@ htmlboard design --root .
|
|
|
105
113
|
# Start a new folder with sample designs
|
|
106
114
|
htmlboard ./my-new-ui --init --open
|
|
107
115
|
|
|
116
|
+
# Let the designs in ./design use components
|
|
117
|
+
htmlboard components design
|
|
118
|
+
|
|
119
|
+
# What the designs in ./design share, in a few lines (what the agent skill reads)
|
|
120
|
+
htmlboard catalog design
|
|
121
|
+
|
|
122
|
+
# What went wrong in them: script errors, missing files, cut-off text (the canvas must be open)
|
|
123
|
+
htmlboard check design
|
|
124
|
+
|
|
108
125
|
# See what moving ./design/designs.json into its files would do, then do it
|
|
109
126
|
htmlboard migrate design
|
|
110
127
|
htmlboard migrate design --write
|
|
@@ -116,31 +133,39 @@ htmlboard migrate design --write
|
|
|
116
133
|
|
|
117
134
|
HtmlBoard is built from the ground up to pair with AI coding agents (Claude, Gemini, Codex, ChatGPT, Antigravity, Cursor).
|
|
118
135
|
|
|
119
|
-
### Install the agent
|
|
136
|
+
### Install the agent skills
|
|
120
137
|
|
|
121
|
-
HtmlBoard ships
|
|
138
|
+
HtmlBoard ships [Agent Skills](https://agentskills.io) that teach your agent how to use it. An agent keeps only each skill's one-line description in mind and reads a skill when a task calls for it, so the ones it rarely needs cost almost nothing until then:
|
|
139
|
+
|
|
140
|
+
| Skill | What it teaches | Read |
|
|
141
|
+
| :--- | :--- | :--- |
|
|
142
|
+
| `htmlboard` | Start the canvas; write, size, name and group designs; shared styles; size and state variants; the catalog and the check | For any design work |
|
|
143
|
+
| `htmlboard-components` | The component file: attributes, slots, scoped CSS, a script, its first-line example | Before writing or changing a component |
|
|
144
|
+
| `htmlboard-designs-json` | Curating the canvas with `designs.json`, and `htmlboard migrate` | When the designs folder has a `designs.json` |
|
|
145
|
+
|
|
146
|
+
Install them in your project:
|
|
122
147
|
|
|
123
148
|
```bash
|
|
124
149
|
npx htmlboard skills install
|
|
125
150
|
```
|
|
126
151
|
|
|
127
|
-
This writes `.claude/skills
|
|
152
|
+
This writes them in `.claude/skills/` (Claude Code) and `.agents/skills/` (Codex and other agents that read `.agents/skills`). Commit them so everyone working on the project gets them. Then start a new agent session and just ask for a design.
|
|
128
153
|
|
|
129
154
|
```bash
|
|
130
155
|
npx htmlboard skills install --global # for your user (~/.claude/skills, ~/.agents/skills), all projects
|
|
131
156
|
npx htmlboard skills install --agent claude # only one agent: claude | agents | all (default)
|
|
132
157
|
npx htmlboard skills install --force # update a copy you edited locally
|
|
133
|
-
npx htmlboard skills uninstall # remove
|
|
158
|
+
npx htmlboard skills uninstall # remove them (accepts the same --global / --agent)
|
|
134
159
|
```
|
|
135
160
|
|
|
136
|
-
Running `install` again after upgrading HtmlBoard updates the
|
|
161
|
+
Running `install` again after upgrading HtmlBoard updates the skills; a copy you edited is kept unless you pass `--force`.
|
|
137
162
|
|
|
138
163
|
### The workflow
|
|
139
164
|
|
|
140
165
|
1. **Ask your LLM to generate artboards:**
|
|
141
166
|
> *"Create 3 design explorations for our checkout flow in `designs/03-checkout/`."*
|
|
142
167
|
|
|
143
|
-
With the
|
|
168
|
+
With the skills, the agent writes `01-one-page.html`, `02-steps.html`, `03-express.html`, each with its size in a `<meta>` tag. They show up as a new "checkout" row, in that order.
|
|
144
169
|
|
|
145
170
|
2. **Run HtmlBoard in the background:**
|
|
146
171
|
```bash
|
|
@@ -150,11 +175,70 @@ Running `install` again after upgrading HtmlBoard updates the skill; a copy you
|
|
|
150
175
|
3. **Watch the canvas update live:**
|
|
151
176
|
As the LLM writes or refines each HTML file, HtmlBoard detects the file change and reloads the iframe on the canvas immediately. You never have to refresh your browser or hunt for file URLs.
|
|
152
177
|
|
|
178
|
+
### What the designs share: `htmlboard catalog`
|
|
179
|
+
|
|
180
|
+
Before an agent writes a new design, it needs to know what the others use: the stylesheet's variables and classes, the components and how to write them. Reading every file for that costs tokens in every session. `htmlboard catalog` reads them instead and prints a summary (the skill runs it). On the samples, shortened:
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
$ npx htmlboard catalog designs
|
|
184
|
+
designs: 4 designs. Paths below are from this folder.
|
|
185
|
+
|
|
186
|
+
Stylesheets
|
|
187
|
+
styles.css · all 4 designs
|
|
188
|
+
:root { --bg: #0b0d13; --surface: #141724; --surface-2: #1c2030; --border: #23283c;
|
|
189
|
+
--text: #f1f5f9; --text-muted: #94a3b8; --text-subtle: #64748b; --accent: #6366f1; … }
|
|
190
|
+
classes .muted .label .row .page-title .page-sub .app .app-main .app-content .card .btn
|
|
191
|
+
.btn-primary .btn-secondary .btn-outline .btn-danger .badge .badge-dot .badge-emerald …
|
|
192
|
+
|
|
193
|
+
Scripts
|
|
194
|
+
components.js · all 4 designs
|
|
195
|
+
|
|
196
|
+
Components (components/)
|
|
197
|
+
<stat-card label value trend [warn]> · 2 designs: 01-screens/01-dashboard.html, 02-ui-kit/01-overview.html
|
|
198
|
+
e.g. <stat-card label="Monthly Recurring Revenue" value="$184,290" trend="▲ +12.4% vs last mo"></stat-card> …
|
|
199
|
+
<tx-item icon name meta amount [positive]> · 2 designs: 01-screens/03-mobile.html, 02-ui-kit/01-overview.html
|
|
200
|
+
e.g. <tx-item icon="💼" name="Client Invoice #1042" meta="Yesterday • Wire Transfer" amount="+$3,450.00" positive></tx-item> …
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
It describes whatever the project has and asks for nothing:
|
|
204
|
+
- **Stylesheets** with any name, in any folder: the ones the designs link or `@import` (CDN ones by their URL), and the ones at the top of the folder that no design links yet.
|
|
205
|
+
- **Variables** under the selector that declares them, so a dark theme's values show too.
|
|
206
|
+
- **Components**: their attributes come from their markup (`{{name}}`), their script and their CSS (`&[warn]` is a flag, `&[size="lg"]` a value).
|
|
207
|
+
|
|
208
|
+
On the samples it is 2 KB instead of the 9 KB of `styles.css` and `components/`, and the gap grows with the project: a stylesheet's rules don't show, only what they define.
|
|
209
|
+
|
|
210
|
+
### Checking the designs: `htmlboard check`
|
|
211
|
+
|
|
212
|
+
An agent can't see the canvas, and a screenshot costs it many tokens, when it can take one at all. `htmlboard check` asks the canvas open in your browser to look instead: each design loads again, out of sight, in a frame of its size, and what went wrong comes back as text:
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
$ npx htmlboard check designs
|
|
216
|
+
Checked 6 designs in designs (the canvas at http://localhost:3000): 2 with problems, 1 with notes.
|
|
217
|
+
|
|
218
|
+
01-screens/02-customers.html · Customers
|
|
219
|
+
error TypeError: Cannot read properties of null (reading 'append') (01-screens/02-customers.html:88)
|
|
220
|
+
error <org-cel> isn't built: there is no components/org-cel.html
|
|
221
|
+
error missing file: img/acme.svg (<img>)
|
|
222
|
+
layout cut at the right edge (the frame is 1280 px wide): table.table reaches 1342
|
|
223
|
+
layout text cut off by p.plan (overflow: hidden): "Enterprise annual plan"
|
|
224
|
+
|
|
225
|
+
01-screens/03-mobile.html · Mobile
|
|
226
|
+
layout text spills out of .btn: "Confirm and continue"
|
|
227
|
+
|
|
228
|
+
02-print/01-flyer.html · Flyer
|
|
229
|
+
note content goes on below the frame, down to 1460 px (the frame is 1350 px tall)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
- **What it finds:** script errors and `console.error`; files that aren't there (a stylesheet's fonts and images, a fetch's data too); components that weren't built; fonts that didn't load; a page that shows nothing; content cut at the frame's right edge; text cut off by a box that hides its overflow, or spilling out of its box. A note says when content goes on below a frame of fixed height: fine for a screen that scrolls, not for a flyer.
|
|
233
|
+
- **What it leaves alone:** what a design means to do, such as text that ends in an ellipsis or is clamped, boxes that scroll, and content hidden or moved off the page on purpose. It doesn't judge looks (colors, spacing, alignment).
|
|
234
|
+
- **It runs in your browser**, so the canvas must be open there. Each check loads the files as they are on disk, so it can run right after a save. Pages shown in frames get a small script first in their `<head>` (`/__explorer/probe.js`) to hear what goes wrong from the start; it only listens in the frames the check loads, and a design opened on its own doesn't get it.
|
|
235
|
+
- A file or a folder checks just those designs: `htmlboard check designs/01-screens/02-customers.html`. It finds the canvas on ports 3000 to 3020 (`-p` for another) and exits with 1 when there are problems, 2 when it can't check. `--json` gives the report as data.
|
|
236
|
+
|
|
153
237
|
---
|
|
154
238
|
|
|
155
239
|
## 📂 How Designs Are Found
|
|
156
240
|
|
|
157
|
-
Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist` and `
|
|
241
|
+
Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist`, `build` and the [components](#-components) in `components/` aside):
|
|
158
242
|
|
|
159
243
|
- **Sections are folders.** Each folder is a row on the canvas, named after the folder; the files at the top of the folder make a first row without a title.
|
|
160
244
|
- **Number prefixes set the order** of files and folders: `01-…`, `02-…`, `10-…` (up to three digits; `2` comes before `10`). They don't show in names or in links, so reordering breaks nothing.
|
|
@@ -166,6 +250,7 @@ Every `.html` file under the folder is a design (hidden folders, `node_modules`,
|
|
|
166
250
|
<meta name="htmlboard" content="width=390, height=844"> <!-- its size (and category=…) -->
|
|
167
251
|
<meta name="htmlboard:label" content="light"> <!-- added to its name: "Checkout · light" -->
|
|
168
252
|
<meta name="htmlboard:variant" content="?theme=dark · dark"> <!-- one more frame: "Checkout · dark" -->
|
|
253
|
+
<meta name="htmlboard:variant" content="width=768, height=1024 · tablet"> <!-- and at a tablet's size -->
|
|
169
254
|
```
|
|
170
255
|
|
|
171
256
|
| Tag | What it says |
|
|
@@ -174,13 +259,61 @@ Every `.html` file under the folder is a design (hidden folders, `node_modules`,
|
|
|
174
259
|
| `<meta name="description">` | Its description, shown in the list and found by the search. |
|
|
175
260
|
| `<meta name="htmlboard" content="…">` | `width=…`, `height=…`, `category=…` (`mobile`, `tablet`, `desktop`, `components` or your own word), or `skip` to leave the file out (partials, templates). |
|
|
176
261
|
| `<meta name="htmlboard:label">` | Text added to the name of the file's own frame, when it has variants. |
|
|
177
|
-
| `<meta name="htmlboard:variant">` | One more frame of the same file with
|
|
262
|
+
| `<meta name="htmlboard:variant">` | One more frame of the same file: with a query string (`?theme=dark`), at another size (`width=390, height=844`, so a responsive page shows on a phone too), or both; then its label. As many as you like; they follow the file. |
|
|
178
263
|
|
|
179
264
|
**Sizes.** The width is the frame's viewport: the page's CSS needs it before it renders. Give the height for designs of a fixed size (a phone screen, a flyer: the frame is that tall and cuts the rest); leave it out and the frame grows with the page. Without a size, words in the path decide (`mobile` → 390 × 844, `tablet` → 768 × 1024, `desktop` → 1280 × 800, `component` → 720 × 600), else 1200 wide.
|
|
180
265
|
|
|
181
266
|
**Categories** drive the filters. Left out, they come from words in the path, then from the size when it is a screen's (a phone's width, a tablet's 3:4, a desktop's landscape); flyers, boards and other formats get none and show under **All**.
|
|
182
267
|
|
|
183
|
-
**Shared styles.** Designs link one stylesheet for what they share (tokens, base styles,
|
|
268
|
+
**Shared styles.** Designs link one stylesheet for what they share (tokens, base styles, classes like `.btn` and `.card`) and keep only their own CSS in their `<style>`, so they stay consistent and each new design is shorter to write. The samples use a `styles.css` at the top of the folder (`<link rel="stylesheet" href="../styles.css">`), and the agent skill tells LLMs to look it up (`htmlboard catalog`), use it and grow it. Editing it reloads every design that links it.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 🧩 Components
|
|
273
|
+
|
|
274
|
+
Designs repeat markup too: the same sidebar on every screen, a dozen rows of a list. Written as a component, it is one tag in each design, and one file to change. Every file in `components/` (at the top of the designs folder) is a custom element named after it, built by `components.js`, which designs load once next to their stylesheet:
|
|
275
|
+
|
|
276
|
+
```html
|
|
277
|
+
<link rel="stylesheet" href="../styles.css">
|
|
278
|
+
<script type="module" src="../components.js"></script>
|
|
279
|
+
…
|
|
280
|
+
<app-sidebar active="Customers"></app-sidebar>
|
|
281
|
+
<tx-item icon="☕" name="Blue Bottle Coffee" meta="Oct 26 • Food & Beverage" amount="-$6.75"></tx-item>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
A component is a piece of HTML, `components/tx-item.html`:
|
|
285
|
+
|
|
286
|
+
```html
|
|
287
|
+
<!-- <tx-item icon="💼" name="Client Invoice #1042" meta="Yesterday" amount="+$3,450.00" positive></tx-item> -->
|
|
288
|
+
<div class="icon">{{icon}}</div>
|
|
289
|
+
<div class="what">
|
|
290
|
+
<div class="name">{{name}}</div>
|
|
291
|
+
<div class="muted">{{meta}}</div>
|
|
292
|
+
</div>
|
|
293
|
+
<div class="amount">{{amount}}</div>
|
|
294
|
+
|
|
295
|
+
<style>
|
|
296
|
+
& { display: flex; align-items: center; gap: 14px; }
|
|
297
|
+
.icon { width: 40px; height: 40px; border-radius: 12px; background: var(--surface-2); }
|
|
298
|
+
&[positive] .amount { color: var(--emerald); }
|
|
299
|
+
</style>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
| In the file | What it does |
|
|
303
|
+
| :--- | :--- |
|
|
304
|
+
| A comment first | An example of use: the component's documentation, for people and agents, and its preview on the canvas. |
|
|
305
|
+
| `{{name}}` | The element's `name="…"` attribute, as text. |
|
|
306
|
+
| `<slot>`, `<slot name="x">` | The element's content; its children with `slot="x"`. What a slot holds shows when nothing fills it. |
|
|
307
|
+
| `<style>` | CSS for this element only (`&` is the element, a block box by default), added once to the pages that use it. |
|
|
308
|
+
| `<script>` | Runs for each element once it is built, with `this` as the element: for the little that markup and CSS can't do, like marking the current link. |
|
|
309
|
+
|
|
310
|
+
- **They work with your stylesheet.** Components are built in the light DOM, with no shadow root in the way, so `styles.css` reaches inside: they use the same classes as the designs (`card`, `btn`, `muted`). A component's CSS comes after `styles.css` and before the design's own `<style>`, so a design can still adjust one element.
|
|
311
|
+
- **Variants are attributes styled in CSS** (`&[positive]`). There is no template logic to learn; a list is several elements.
|
|
312
|
+
- **Relative links start from the component's file**, so they work from every design.
|
|
313
|
+
- **`components.js` is a file in your project**, so designs work on any static server. `--init` adds it; `htmlboard components [dir]` adds it to a designs folder. After upgrading htmlboard, `htmlboard components [dir] --force` replaces an older copy (and any edits you made to it).
|
|
314
|
+
- **Saving a component reloads the designs that use it.** Component files aren't designs; `designs.json` can show them on the canvas (below).
|
|
315
|
+
- **`htmlboard catalog` lists them** with their attributes, the example each one starts with and the designs that use them.
|
|
316
|
+
- HTML has no self-closing custom elements: write `<tx-item …></tx-item>`, not `<tx-item />`. And a component can't be a table's row or cell itself (the browser moves it out of the `<table>`), but it works inside a cell: `<td><org-cell …></org-cell></td>`.
|
|
184
317
|
|
|
185
318
|
---
|
|
186
319
|
|
|
@@ -206,6 +339,7 @@ A `designs.json` in the folder curates the canvas when folders aren't enough: se
|
|
|
206
339
|
- Sections come in their order, designs in theirs. An entry is a file name, or `{ "file", … }` with values that win over the file's (`title`, `description`, `width`, `height`, `category`, `tags`, `id`). A `file` may carry a query string to place a variant itself.
|
|
207
340
|
- A file's variants follow it unless designs.json lists them. `"folder"` brings a whole folder (in prefix order).
|
|
208
341
|
- **Designs it doesn't list still show**, after the ones it does: in the section that holds their folder, else in their folder's own section (the files at the top in "Unlisted"). `"unlisted": "hide"` shows only what it lists.
|
|
342
|
+
- **Components show on the canvas with `"components": "show"`**, each alone in its frame, from the example its file starts with, in a "components" section after the designs. They are 480 wide, unless the component's file says `<meta name="htmlboard" content="width=1040">`. A section can also list them (`"components/tx-item.html"`) or bring them all (`"folder": "components"`). A `designs.json` that only says `{ "components": "show" }` leaves the rest of the canvas as the files make it.
|
|
209
343
|
- `name` and `artboards` are synonyms of `title` and `designs`.
|
|
210
344
|
|
|
211
345
|
### Moving an existing `designs.json` into the files
|
|
@@ -279,6 +413,38 @@ installSkills({ dir: process.cwd(), agent: 'all' });
|
|
|
279
413
|
// → [{ skill: 'htmlboard', path: '…/.claude/skills/htmlboard', status: 'installed' }, …]
|
|
280
414
|
```
|
|
281
415
|
|
|
416
|
+
And so is the components installer:
|
|
417
|
+
|
|
418
|
+
```javascript
|
|
419
|
+
import { installComponents } from 'htmlboard';
|
|
420
|
+
|
|
421
|
+
installComponents({ dir: './designs' });
|
|
422
|
+
// → { path: '…/designs/components.js', status: 'installed' }
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
And the catalog, as data or as the text `htmlboard catalog` prints:
|
|
426
|
+
|
|
427
|
+
```javascript
|
|
428
|
+
import { buildCatalog, formatCatalog } from 'htmlboard';
|
|
429
|
+
|
|
430
|
+
const catalog = buildCatalog('./designs'); // { root } for links that start with "/", like --root
|
|
431
|
+
// → { designs: ['01-screens/01-dashboard.html', …],
|
|
432
|
+
// stylesheets: [{ path: 'styles.css', users: [{ design: '01-screens/01-dashboard.html' }, …], variables, classes, keyframes, fonts }, …],
|
|
433
|
+
// scripts: [{ path: 'components.js', users }],
|
|
434
|
+
// components: [{ tag: 'tx-item', attributes: [{ name: 'icon' }, …, { name: 'positive', flag: true }], slots, example, users }, …] }
|
|
435
|
+
console.log(formatCatalog(catalog));
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
And the check, which needs the canvas open in a browser (the server answers it at `GET /api/check`, `?only=<file or folder>` for some of the designs):
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
import { runCheck, formatReport } from 'htmlboard';
|
|
442
|
+
|
|
443
|
+
const { canvas, report } = await runCheck('./designs'); // { port } when it isn't on 3000 to 3020
|
|
444
|
+
// → report: { dir, root, designs: [{ file, title, width, height, problems: [{ level: 'error', kind: 'missing', path: '/img/logo.png', tag: 'img' }, …] }, …] }
|
|
445
|
+
console.log(formatReport(report, { url: canvas.url }));
|
|
446
|
+
```
|
|
447
|
+
|
|
282
448
|
---
|
|
283
449
|
|
|
284
450
|
## 📂 Project Structure
|
|
@@ -291,20 +457,29 @@ htmlboard/
|
|
|
291
457
|
│ ├── index.js # Library entry point & API exports
|
|
292
458
|
│ ├── server.js # HTTP server, SSE live reload
|
|
293
459
|
│ ├── designs.js # Which designs a folder holds: files, their tags, designs.json
|
|
460
|
+
│ ├── components.js # Components: their previews, `htmlboard components`
|
|
461
|
+
│ ├── catalog.js # What the designs share: `htmlboard catalog`
|
|
462
|
+
│ ├── check.js # `htmlboard check`: asks the canvas, prints its report
|
|
294
463
|
│ ├── migrate.js # `htmlboard migrate`
|
|
295
464
|
│ ├── skills.js # `htmlboard skills` installer
|
|
296
465
|
│ ├── client/ # The canvas
|
|
297
466
|
│ │ ├── index.html # Explorer shell: list, canvas, toolbar, shortcuts
|
|
298
467
|
│ │ ├── explorer.css # Dark chrome, dot grid
|
|
299
468
|
│ │ ├── explorer.js # Layout, pan/zoom, search, live reload
|
|
469
|
+
│ │ ├── check.js # Checks the designs in frames out of sight, for `htmlboard check`
|
|
470
|
+
│ │ ├── probe.js # Notes script errors and failed loads in the frames the check loads
|
|
300
471
|
│ │ ├── favicon.svg, logo.svg, mark.svg
|
|
301
472
|
│ │ └── fonts/ # Inter and Bricolage Grotesque (OFL)
|
|
302
473
|
│ └── templates/ # The sample designs `--init` adds
|
|
303
|
-
│ ├── styles.css # Their shared tokens and
|
|
304
|
-
│ ├──
|
|
305
|
-
│
|
|
474
|
+
│ ├── styles.css # Their shared tokens and classes
|
|
475
|
+
│ ├── components.js # Turns components/*.html into custom elements (copied into projects)
|
|
476
|
+
│ ├── components/ # Sidebar, top bar, stat card, transaction row
|
|
477
|
+
│ ├── 01-screens/ # Dashboard, customers, mobile feed
|
|
478
|
+
│ └── 02-ui-kit/
|
|
306
479
|
├── skills/
|
|
307
|
-
│
|
|
480
|
+
│ ├── htmlboard/ # Agent skills (SKILL.md each) copied by `htmlboard skills install`
|
|
481
|
+
│ ├── htmlboard-components/
|
|
482
|
+
│ └── htmlboard-designs-json/
|
|
308
483
|
├── brand/ # The logo's files (from design/tools/logo.py)
|
|
309
484
|
├── design/ # htmlboard's own designs, on its own canvas: `npx htmlboard design --root .`
|
|
310
485
|
├── package.json
|
package/bin/htmlboard.js
CHANGED
|
@@ -7,6 +7,10 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { startServer } from '../src/server.js';
|
|
8
8
|
import { installSkills, uninstallSkills } from '../src/skills.js';
|
|
9
9
|
import { planMigration, checkMigration, applyMigration } from '../src/migrate.js';
|
|
10
|
+
import { installComponents, COMPONENTS_FOLDER, COMPONENTS_SCRIPT } from '../src/components.js';
|
|
11
|
+
import { buildCatalog, formatCatalog } from '../src/catalog.js';
|
|
12
|
+
import { runCheck, formatReport, hasProblems, CheckError } from '../src/check.js';
|
|
13
|
+
import { PORTS, findCanvases } from '../src/canvases.js';
|
|
10
14
|
|
|
11
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
16
|
const __dirname = path.dirname(__filename);
|
|
@@ -76,6 +80,9 @@ Usage:
|
|
|
76
80
|
npx htmlboard [directory] [options]
|
|
77
81
|
htmlboard skills <install|uninstall> [options] Teach your AI agent to use HtmlBoard
|
|
78
82
|
htmlboard migrate [directory] [--write] Move designs.json's sizes, titles and variants into the files
|
|
83
|
+
htmlboard components [directory] [--force] Add components.js: files in components/ become custom elements
|
|
84
|
+
htmlboard catalog [directory] What the designs share: stylesheets, scripts, components
|
|
85
|
+
htmlboard check [directory or file] [-p <port>] What went wrong in the designs, from the canvas open in a browser
|
|
79
86
|
|
|
80
87
|
Options:
|
|
81
88
|
-p, --port <number> Port to listen on (default: 3000)
|
|
@@ -93,7 +100,10 @@ Examples:
|
|
|
93
100
|
htmlboard ./designs -p 8080 # Serve ./designs on port 8080
|
|
94
101
|
htmlboard design --root . # Artboards in ./design that link to ../css/…
|
|
95
102
|
htmlboard --init # Add sample designs to the current folder
|
|
96
|
-
htmlboard skills install # Add the HtmlBoard
|
|
103
|
+
htmlboard skills install # Add the HtmlBoard skills to this project's agents
|
|
104
|
+
htmlboard components designs # Let ./designs use components (components/tx-item.html is <tx-item>)
|
|
105
|
+
htmlboard catalog designs # What ./designs share, in a few lines (for your AI agent)
|
|
106
|
+
htmlboard check designs # Script errors, missing files, cut-off text in ./designs
|
|
97
107
|
|
|
98
108
|
On the canvas:
|
|
99
109
|
Drag, scroll Move around
|
|
@@ -145,8 +155,8 @@ function scaffoldProject(targetDir) {
|
|
|
145
155
|
function showSkillsHelp() {
|
|
146
156
|
console.log(`
|
|
147
157
|
Usage:
|
|
148
|
-
htmlboard skills install [options] Copy the HtmlBoard
|
|
149
|
-
htmlboard skills uninstall [options] Remove
|
|
158
|
+
htmlboard skills install [options] Copy the HtmlBoard skills into your agents' skill folders
|
|
159
|
+
htmlboard skills uninstall [options] Remove them
|
|
150
160
|
|
|
151
161
|
Options:
|
|
152
162
|
-g, --global Install for your user (~/.claude/skills, ~/.agents/skills) instead of this project
|
|
@@ -202,7 +212,135 @@ function runSkills(args) {
|
|
|
202
212
|
console.log(` ${labels[status]} ${path.relative(process.cwd(), dest) || dest}`);
|
|
203
213
|
}
|
|
204
214
|
if (action === 'install' && results.some((r) => r.status === 'installed' || r.status === 'updated')) {
|
|
205
|
-
console.log(`\n Restart your agent session to load the
|
|
215
|
+
console.log(`\n Restart your agent session to load the skills, then ask it for a design.`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function runComponents(args) {
|
|
220
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
221
|
+
console.log(`
|
|
222
|
+
Usage:
|
|
223
|
+
htmlboard components [directory] [--force]
|
|
224
|
+
|
|
225
|
+
Adds ${COMPONENTS_SCRIPT} and a ${COMPONENTS_FOLDER}/ folder to the designs folder (default: the current one).
|
|
226
|
+
Each file in ${COMPONENTS_FOLDER}/ is then a custom element named after it (${COMPONENTS_FOLDER}/tx-item.html is
|
|
227
|
+
<tx-item>) in the designs that load ${COMPONENTS_SCRIPT}, once, beside their stylesheet:
|
|
228
|
+
|
|
229
|
+
<script type="module" src="../${COMPONENTS_SCRIPT}"></script>
|
|
230
|
+
|
|
231
|
+
A ${COMPONENTS_SCRIPT} that differs from this version's (an older one, or one you edited) is kept;
|
|
232
|
+
--force replaces it.
|
|
233
|
+
`);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const unknown = args.find((arg) => arg.startsWith('-') && arg !== '--force' && arg !== '-f');
|
|
237
|
+
if (unknown) {
|
|
238
|
+
console.error(`[htmlboard] Unknown option: ${unknown} (see htmlboard components --help)`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
const dirArg = args.find((arg) => !arg.startsWith('-'));
|
|
242
|
+
const { path: dest, status } = installComponents({
|
|
243
|
+
dir: dirArg || process.cwd(),
|
|
244
|
+
force: args.includes('--force') || args.includes('-f')
|
|
245
|
+
});
|
|
246
|
+
const labels = {
|
|
247
|
+
installed: '\x1b[32m✓ added\x1b[0m',
|
|
248
|
+
updated: '\x1b[32m✓ updated\x1b[0m',
|
|
249
|
+
unchanged: '\x1b[90m= up to date\x1b[0m',
|
|
250
|
+
skipped: '\x1b[33m! differs from this version, kept (use --force to replace)\x1b[0m'
|
|
251
|
+
};
|
|
252
|
+
console.log(` ${labels[status]} ${path.relative(process.cwd(), dest) || dest}`);
|
|
253
|
+
if (status === 'installed') {
|
|
254
|
+
const folder = path.relative(process.cwd(), path.join(path.dirname(dest), COMPONENTS_FOLDER)) || COMPONENTS_FOLDER;
|
|
255
|
+
console.log(`\n Write components in ${folder}/ (tx-item.html is <tx-item>) and load ${COMPONENTS_SCRIPT} in the designs.`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// A folder as the user would type it: from the current folder, unless that means going up
|
|
260
|
+
function shownPath(dir) {
|
|
261
|
+
const rel = path.relative(process.cwd(), dir);
|
|
262
|
+
return !rel ? '.' : rel.startsWith('..') ? dir : rel;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function runCatalog(args) {
|
|
266
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
267
|
+
console.log(`
|
|
268
|
+
Usage:
|
|
269
|
+
htmlboard catalog [directory] [--root <path>]
|
|
270
|
+
|
|
271
|
+
Prints what the designs in the folder (default: the current one) share, read from the files: the
|
|
272
|
+
stylesheets and scripts they load, each local stylesheet's variables, classes, @keyframes and
|
|
273
|
+
fonts, and the components in ${COMPONENTS_FOLDER}/ with their attributes, slots, example and where
|
|
274
|
+
they are used. A few lines instead of every file, for the AI agent that writes the designs.
|
|
275
|
+
|
|
276
|
+
Options:
|
|
277
|
+
-r, --root <path> The folder the canvas serves (htmlboard … --root), for links that start
|
|
278
|
+
with "/" (default: the designs folder)
|
|
279
|
+
`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
let root;
|
|
283
|
+
const dirs = [];
|
|
284
|
+
for (let i = 0; i < args.length; i++) {
|
|
285
|
+
const arg = args[i];
|
|
286
|
+
if (arg === '--root' || arg === '-r') root = args[++i];
|
|
287
|
+
else if (arg.startsWith('--root=')) root = arg.slice('--root='.length);
|
|
288
|
+
else if (arg.startsWith('-')) {
|
|
289
|
+
console.error(`[htmlboard] Unknown option: ${arg} (see htmlboard catalog --help)`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
} else dirs.push(arg);
|
|
292
|
+
}
|
|
293
|
+
let catalog;
|
|
294
|
+
try {
|
|
295
|
+
catalog = buildCatalog(dirs[0] || process.cwd(), { root });
|
|
296
|
+
} catch (err) {
|
|
297
|
+
console.error(`[htmlboard] ${err.message}`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
process.stdout.write(formatCatalog(catalog, { name: shownPath(catalog.dir) }));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function runCheckCommand(args) {
|
|
304
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
305
|
+
console.log(`
|
|
306
|
+
Usage:
|
|
307
|
+
htmlboard check [directory or file] [-p <port>] [--json]
|
|
308
|
+
|
|
309
|
+
Has the canvas running for the designs (open in a browser: the check runs there) load each one
|
|
310
|
+
afresh and prints what went wrong, in a few lines of text: script errors and console.error, files
|
|
311
|
+
that aren't there, components that weren't built, fonts that didn't load, content cut at the
|
|
312
|
+
frame's edge, text cut off or spilling out of its box. A directory checks the designs in it, a
|
|
313
|
+
file just that design (default: the current directory).
|
|
314
|
+
|
|
315
|
+
Exits with 1 when it finds problems, 2 when it can't check (no canvas running, or none open).
|
|
316
|
+
|
|
317
|
+
Options:
|
|
318
|
+
-p, --port <number> The canvas's port (default: found on 3000 to 3020)
|
|
319
|
+
--json The report as JSON
|
|
320
|
+
`);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
let port;
|
|
324
|
+
let asJson = false;
|
|
325
|
+
const targets = [];
|
|
326
|
+
for (let i = 0; i < args.length; i++) {
|
|
327
|
+
const arg = args[i];
|
|
328
|
+
if (arg === '--port' || arg === '-p') port = parseInt(args[++i], 10);
|
|
329
|
+
else if (arg.startsWith('--port=')) port = parseInt(arg.slice('--port='.length), 10);
|
|
330
|
+
else if (arg === '--json') asJson = true;
|
|
331
|
+
else if (arg.startsWith('-')) {
|
|
332
|
+
console.error(`[htmlboard] Unknown option: ${arg} (see htmlboard check --help)`);
|
|
333
|
+
process.exit(2);
|
|
334
|
+
} else targets.push(arg);
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const { canvas, report } = await runCheck(targets[0], { port });
|
|
338
|
+
if (asJson) console.log(JSON.stringify(report, null, 2));
|
|
339
|
+
else process.stdout.write(formatReport(report, { name: shownPath(report.dir), url: canvas.url }));
|
|
340
|
+
process.exitCode = hasProblems(report) ? 1 : 0;
|
|
341
|
+
} catch (err) {
|
|
342
|
+
console.error(err instanceof CheckError ? err.message : `[htmlboard] ${err.message}`);
|
|
343
|
+
process.exitCode = 2;
|
|
206
344
|
}
|
|
207
345
|
}
|
|
208
346
|
|
|
@@ -266,11 +404,26 @@ async function main() {
|
|
|
266
404
|
return;
|
|
267
405
|
}
|
|
268
406
|
|
|
407
|
+
if (args[0] === 'components') {
|
|
408
|
+
runComponents(args.slice(1));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
269
412
|
if (args[0] === 'migrate') {
|
|
270
413
|
runMigrate(args.slice(1));
|
|
271
414
|
return;
|
|
272
415
|
}
|
|
273
416
|
|
|
417
|
+
if (args[0] === 'catalog') {
|
|
418
|
+
runCatalog(args.slice(1));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (args[0] === 'check') {
|
|
423
|
+
await runCheckCommand(args.slice(1));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
274
427
|
const options = parseArgs(args);
|
|
275
428
|
|
|
276
429
|
if (options.help) {
|
|
@@ -301,6 +454,25 @@ async function main() {
|
|
|
301
454
|
scaffoldProject(targetDir);
|
|
302
455
|
}
|
|
303
456
|
|
|
457
|
+
// A canvas already running for this folder (same root, same version): say where, rather than
|
|
458
|
+
// starting a second one on the next port
|
|
459
|
+
const root = options.root ? path.resolve(options.root) : targetDir;
|
|
460
|
+
const running = (await findCanvases([...PORTS, ...Array.from({ length: 21 }, (_, i) => options.port + i)]))
|
|
461
|
+
.filter((canvas) => canvas.dir === targetDir && canvas.root === root);
|
|
462
|
+
const same = running.find((canvas) => canvas.version === pkg.version);
|
|
463
|
+
if (same) {
|
|
464
|
+
console.log(`
|
|
465
|
+
\x1b[90mhtml\x1b[0m\x1b[1mboard\x1b[0m is already running for this folder:
|
|
466
|
+
|
|
467
|
+
\x1b[32m➜\x1b[0m \x1b[1mLocal:\x1b[0m \x1b[36m${same.url}\x1b[0m
|
|
468
|
+
`);
|
|
469
|
+
if (options.open) openBrowser(same.url);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
for (const canvas of running) {
|
|
473
|
+
console.log(`[htmlboard] v${canvas.version} is running for this folder at ${canvas.url}; starting v${pkg.version} too.`);
|
|
474
|
+
}
|
|
475
|
+
|
|
304
476
|
try {
|
|
305
477
|
const { url, rootDir } = await startServer({
|
|
306
478
|
dir: targetDir,
|