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 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 folder of components, each file describing itself, all sharing one `styles.css`):
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 skill
136
+ ### Install the agent skills
120
137
 
121
- HtmlBoard ships an [Agent Skill](https://agentskills.io) that teaches your agent how to use it: where to put artboards, how to size, name and group them (files describe themselves; `designs.json` is optional) and how to start the canvas. Install it in your project:
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/htmlboard/` (Claude Code) and `.agents/skills/htmlboard/` (Codex and other agents that read `.agents/skills`). Commit them so everyone working on the project gets the skill. Then start a new agent session and just ask for a design.
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 it (accepts the same --global / --agent)
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 skill; a copy you edited is kept unless you pass `--force`.
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 skill, 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.
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 `build` aside):
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 that query string, then its label. As many as you like; they follow the file. |
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, components) 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 read it, use it and grow it. Editing it reloads every design that links it.
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 components
304
- │ ├── 01-screens/ # Desktop dashboard, mobile feed
305
- │ └── 02-components/
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
- │ └── htmlboard/ # Agent skill (SKILL.md) copied by `htmlboard skills install`
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 skill to this project's agents
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 skill into your agents' skill folders
149
- htmlboard skills uninstall [options] Remove it
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 skill, then ask it for a design.`);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmlboard",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Infinite canvas and design explorer for HTML artboards — designed for humans and LLMs",
5
5
  "type": "module",
6
6
  "bin": {