htmlboard 0.4.0 → 0.5.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)
@@ -105,6 +111,15 @@ htmlboard design --root .
105
111
  # Start a new folder with sample designs
106
112
  htmlboard ./my-new-ui --init --open
107
113
 
114
+ # Let the designs in ./design use components
115
+ htmlboard components design
116
+
117
+ # What the designs in ./design share, in a few lines (what the agent skill reads)
118
+ htmlboard catalog design
119
+
120
+ # What went wrong in them: script errors, missing files, cut-off text (the canvas must be open)
121
+ htmlboard check design
122
+
108
123
  # See what moving ./design/designs.json into its files would do, then do it
109
124
  htmlboard migrate design
110
125
  htmlboard migrate design --write
@@ -150,11 +165,70 @@ Running `install` again after upgrading HtmlBoard updates the skill; a copy you
150
165
  3. **Watch the canvas update live:**
151
166
  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
167
 
168
+ ### What the designs share: `htmlboard catalog`
169
+
170
+ 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:
171
+
172
+ ```text
173
+ $ npx htmlboard catalog designs
174
+ designs: 4 designs. Paths below are from this folder.
175
+
176
+ Stylesheets
177
+ styles.css · all 4 designs
178
+ :root { --bg: #0b0d13; --surface: #141724; --surface-2: #1c2030; --border: #23283c;
179
+ --text: #f1f5f9; --text-muted: #94a3b8; --text-subtle: #64748b; --accent: #6366f1; … }
180
+ classes .muted .label .row .page-title .page-sub .app .app-main .app-content .card .btn
181
+ .btn-primary .btn-secondary .btn-outline .btn-danger .badge .badge-dot .badge-emerald …
182
+
183
+ Scripts
184
+ components.js · all 4 designs
185
+
186
+ Components (components/)
187
+ <stat-card label value trend [warn]> · 2 designs: 01-screens/01-dashboard.html, 02-ui-kit/01-overview.html
188
+ e.g. <stat-card label="Monthly Recurring Revenue" value="$184,290" trend="▲ +12.4% vs last mo"></stat-card> …
189
+ <tx-item icon name meta amount [positive]> · 2 designs: 01-screens/03-mobile.html, 02-ui-kit/01-overview.html
190
+ e.g. <tx-item icon="💼" name="Client Invoice #1042" meta="Yesterday • Wire Transfer" amount="+$3,450.00" positive></tx-item> …
191
+ ```
192
+
193
+ It describes whatever the project has and asks for nothing:
194
+ - **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.
195
+ - **Variables** under the selector that declares them, so a dark theme's values show too.
196
+ - **Components**: their attributes come from their markup (`{{name}}`), their script and their CSS (`&[warn]` is a flag, `&[size="lg"]` a value).
197
+
198
+ 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.
199
+
200
+ ### Checking the designs: `htmlboard check`
201
+
202
+ 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:
203
+
204
+ ```text
205
+ $ npx htmlboard check designs
206
+ Checked 6 designs in designs (the canvas at http://localhost:3000): 2 with problems, 1 with notes.
207
+
208
+ 01-screens/02-customers.html · Customers
209
+ error TypeError: Cannot read properties of null (reading 'append') (01-screens/02-customers.html:88)
210
+ error <org-cel> isn't built: there is no components/org-cel.html
211
+ error missing file: img/acme.svg (<img>)
212
+ layout cut at the right edge (the frame is 1280 px wide): table.table reaches 1342
213
+ layout text cut off by p.plan (overflow: hidden): "Enterprise annual plan"
214
+
215
+ 01-screens/03-mobile.html · Mobile
216
+ layout text spills out of .btn: "Confirm and continue"
217
+
218
+ 02-print/01-flyer.html · Flyer
219
+ note content goes on below the frame, down to 1460 px (the frame is 1350 px tall)
220
+ ```
221
+
222
+ - **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.
223
+ - **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).
224
+ - **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.
225
+ - 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.
226
+
153
227
  ---
154
228
 
155
229
  ## 📂 How Designs Are Found
156
230
 
157
- Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist` and `build` aside):
231
+ Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist`, `build` and the [components](#-components) in `components/` aside):
158
232
 
159
233
  - **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
234
  - **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.
@@ -180,7 +254,55 @@ Every `.html` file under the folder is a design (hidden folders, `node_modules`,
180
254
 
181
255
  **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
256
 
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.
257
+ **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.
258
+
259
+ ---
260
+
261
+ ## 🧩 Components
262
+
263
+ 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:
264
+
265
+ ```html
266
+ <link rel="stylesheet" href="../styles.css">
267
+ <script type="module" src="../components.js"></script>
268
+ …
269
+ <app-sidebar active="Customers"></app-sidebar>
270
+ <tx-item icon="☕" name="Blue Bottle Coffee" meta="Oct 26 • Food & Beverage" amount="-$6.75"></tx-item>
271
+ ```
272
+
273
+ A component is a piece of HTML, `components/tx-item.html`:
274
+
275
+ ```html
276
+ <!-- <tx-item icon="💼" name="Client Invoice #1042" meta="Yesterday" amount="+$3,450.00" positive></tx-item> -->
277
+ <div class="icon">{{icon}}</div>
278
+ <div class="what">
279
+ <div class="name">{{name}}</div>
280
+ <div class="muted">{{meta}}</div>
281
+ </div>
282
+ <div class="amount">{{amount}}</div>
283
+
284
+ <style>
285
+ & { display: flex; align-items: center; gap: 14px; }
286
+ .icon { width: 40px; height: 40px; border-radius: 12px; background: var(--surface-2); }
287
+ &[positive] .amount { color: var(--emerald); }
288
+ </style>
289
+ ```
290
+
291
+ | In the file | What it does |
292
+ | :--- | :--- |
293
+ | A comment first | An example of use: the component's documentation, for people and agents, and its preview on the canvas. |
294
+ | `{{name}}` | The element's `name="…"` attribute, as text. |
295
+ | `<slot>`, `<slot name="x">` | The element's content; its children with `slot="x"`. What a slot holds shows when nothing fills it. |
296
+ | `<style>` | CSS for this element only (`&` is the element, a block box by default), added once to the pages that use it. |
297
+ | `<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. |
298
+
299
+ - **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.
300
+ - **Variants are attributes styled in CSS** (`&[positive]`). There is no template logic to learn; a list is several elements.
301
+ - **Relative links start from the component's file**, so they work from every design.
302
+ - **`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).
303
+ - **Saving a component reloads the designs that use it.** Component files aren't designs; `designs.json` can show them on the canvas (below).
304
+ - **`htmlboard catalog` lists them** with their attributes, the example each one starts with and the designs that use them.
305
+ - 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
306
 
185
307
  ---
186
308
 
@@ -206,6 +328,7 @@ A `designs.json` in the folder curates the canvas when folders aren't enough: se
206
328
  - 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
329
  - A file's variants follow it unless designs.json lists them. `"folder"` brings a whole folder (in prefix order).
208
330
  - **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.
331
+ - **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
332
  - `name` and `artboards` are synonyms of `title` and `designs`.
210
333
 
211
334
  ### Moving an existing `designs.json` into the files
@@ -279,6 +402,38 @@ installSkills({ dir: process.cwd(), agent: 'all' });
279
402
  // → [{ skill: 'htmlboard', path: '…/.claude/skills/htmlboard', status: 'installed' }, …]
280
403
  ```
281
404
 
405
+ And so is the components installer:
406
+
407
+ ```javascript
408
+ import { installComponents } from 'htmlboard';
409
+
410
+ installComponents({ dir: './designs' });
411
+ // → { path: '…/designs/components.js', status: 'installed' }
412
+ ```
413
+
414
+ And the catalog, as data or as the text `htmlboard catalog` prints:
415
+
416
+ ```javascript
417
+ import { buildCatalog, formatCatalog } from 'htmlboard';
418
+
419
+ const catalog = buildCatalog('./designs'); // { root } for links that start with "/", like --root
420
+ // → { designs: ['01-screens/01-dashboard.html', …],
421
+ // stylesheets: [{ path: 'styles.css', users: [{ design: '01-screens/01-dashboard.html' }, …], variables, classes, keyframes, fonts }, …],
422
+ // scripts: [{ path: 'components.js', users }],
423
+ // components: [{ tag: 'tx-item', attributes: [{ name: 'icon' }, …, { name: 'positive', flag: true }], slots, example, users }, …] }
424
+ console.log(formatCatalog(catalog));
425
+ ```
426
+
427
+ 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):
428
+
429
+ ```javascript
430
+ import { runCheck, formatReport } from 'htmlboard';
431
+
432
+ const { canvas, report } = await runCheck('./designs'); // { port } when it isn't on 3000 to 3020
433
+ // → report: { dir, root, designs: [{ file, title, width, height, problems: [{ level: 'error', kind: 'missing', path: '/img/logo.png', tag: 'img' }, …] }, …] }
434
+ console.log(formatReport(report, { url: canvas.url }));
435
+ ```
436
+
282
437
  ---
283
438
 
284
439
  ## 📂 Project Structure
@@ -291,18 +446,25 @@ htmlboard/
291
446
  │ ├── index.js # Library entry point & API exports
292
447
  │ ├── server.js # HTTP server, SSE live reload
293
448
  │ ├── designs.js # Which designs a folder holds: files, their tags, designs.json
449
+ │ ├── components.js # Components: their previews, `htmlboard components`
450
+ │ ├── catalog.js # What the designs share: `htmlboard catalog`
451
+ │ ├── check.js # `htmlboard check`: asks the canvas, prints its report
294
452
  │ ├── migrate.js # `htmlboard migrate`
295
453
  │ ├── skills.js # `htmlboard skills` installer
296
454
  │ ├── client/ # The canvas
297
455
  │ │ ├── index.html # Explorer shell: list, canvas, toolbar, shortcuts
298
456
  │ │ ├── explorer.css # Dark chrome, dot grid
299
457
  │ │ ├── explorer.js # Layout, pan/zoom, search, live reload
458
+ │ │ ├── check.js # Checks the designs in frames out of sight, for `htmlboard check`
459
+ │ │ ├── probe.js # Notes script errors and failed loads in the frames the check loads
300
460
  │ │ ├── favicon.svg, logo.svg, mark.svg
301
461
  │ │ └── fonts/ # Inter and Bricolage Grotesque (OFL)
302
462
  │ └── templates/ # The sample designs `--init` adds
303
- │ ├── styles.css # Their shared tokens and components
304
- │ ├── 01-screens/ # Desktop dashboard, mobile feed
305
- │ └── 02-components/
463
+ │ ├── styles.css # Their shared tokens and classes
464
+ │ ├── components.js # Turns components/*.html into custom elements (copied into projects)
465
+ │ ├── components/ # Sidebar, top bar, stat card, transaction row
466
+ │ ├── 01-screens/ # Dashboard, customers, mobile feed
467
+ │ └── 02-ui-kit/
306
468
  ├── skills/
307
469
  │ └── htmlboard/ # Agent skill (SKILL.md) copied by `htmlboard skills install`
308
470
  ├── brand/ # The logo's files (from design/tools/logo.py)
package/bin/htmlboard.js CHANGED
@@ -7,6 +7,9 @@ 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';
10
13
 
11
14
  const __filename = fileURLToPath(import.meta.url);
12
15
  const __dirname = path.dirname(__filename);
@@ -76,6 +79,9 @@ Usage:
76
79
  npx htmlboard [directory] [options]
77
80
  htmlboard skills <install|uninstall> [options] Teach your AI agent to use HtmlBoard
78
81
  htmlboard migrate [directory] [--write] Move designs.json's sizes, titles and variants into the files
82
+ htmlboard components [directory] [--force] Add components.js: files in components/ become custom elements
83
+ htmlboard catalog [directory] What the designs share: stylesheets, scripts, components
84
+ htmlboard check [directory or file] [-p <port>] What went wrong in the designs, from the canvas open in a browser
79
85
 
80
86
  Options:
81
87
  -p, --port <number> Port to listen on (default: 3000)
@@ -94,6 +100,9 @@ Examples:
94
100
  htmlboard design --root . # Artboards in ./design that link to ../css/…
95
101
  htmlboard --init # Add sample designs to the current folder
96
102
  htmlboard skills install # Add the HtmlBoard skill to this project's agents
103
+ htmlboard components designs # Let ./designs use components (components/tx-item.html is <tx-item>)
104
+ htmlboard catalog designs # What ./designs share, in a few lines (for your AI agent)
105
+ htmlboard check designs # Script errors, missing files, cut-off text in ./designs
97
106
 
98
107
  On the canvas:
99
108
  Drag, scroll Move around
@@ -206,6 +215,134 @@ function runSkills(args) {
206
215
  }
207
216
  }
208
217
 
218
+ function runComponents(args) {
219
+ if (args.includes('--help') || args.includes('-h')) {
220
+ console.log(`
221
+ Usage:
222
+ htmlboard components [directory] [--force]
223
+
224
+ Adds ${COMPONENTS_SCRIPT} and a ${COMPONENTS_FOLDER}/ folder to the designs folder (default: the current one).
225
+ Each file in ${COMPONENTS_FOLDER}/ is then a custom element named after it (${COMPONENTS_FOLDER}/tx-item.html is
226
+ <tx-item>) in the designs that load ${COMPONENTS_SCRIPT}, once, beside their stylesheet:
227
+
228
+ <script type="module" src="../${COMPONENTS_SCRIPT}"></script>
229
+
230
+ A ${COMPONENTS_SCRIPT} that differs from this version's (an older one, or one you edited) is kept;
231
+ --force replaces it.
232
+ `);
233
+ return;
234
+ }
235
+ const unknown = args.find((arg) => arg.startsWith('-') && arg !== '--force' && arg !== '-f');
236
+ if (unknown) {
237
+ console.error(`[htmlboard] Unknown option: ${unknown} (see htmlboard components --help)`);
238
+ process.exit(1);
239
+ }
240
+ const dirArg = args.find((arg) => !arg.startsWith('-'));
241
+ const { path: dest, status } = installComponents({
242
+ dir: dirArg || process.cwd(),
243
+ force: args.includes('--force') || args.includes('-f')
244
+ });
245
+ const labels = {
246
+ installed: '\x1b[32m✓ added\x1b[0m',
247
+ updated: '\x1b[32m✓ updated\x1b[0m',
248
+ unchanged: '\x1b[90m= up to date\x1b[0m',
249
+ skipped: '\x1b[33m! differs from this version, kept (use --force to replace)\x1b[0m'
250
+ };
251
+ console.log(` ${labels[status]} ${path.relative(process.cwd(), dest) || dest}`);
252
+ if (status === 'installed') {
253
+ const folder = path.relative(process.cwd(), path.join(path.dirname(dest), COMPONENTS_FOLDER)) || COMPONENTS_FOLDER;
254
+ console.log(`\n Write components in ${folder}/ (tx-item.html is <tx-item>) and load ${COMPONENTS_SCRIPT} in the designs.`);
255
+ }
256
+ }
257
+
258
+ // A folder as the user would type it: from the current folder, unless that means going up
259
+ function shownPath(dir) {
260
+ const rel = path.relative(process.cwd(), dir);
261
+ return !rel ? '.' : rel.startsWith('..') ? dir : rel;
262
+ }
263
+
264
+ function runCatalog(args) {
265
+ if (args.includes('--help') || args.includes('-h')) {
266
+ console.log(`
267
+ Usage:
268
+ htmlboard catalog [directory] [--root <path>]
269
+
270
+ Prints what the designs in the folder (default: the current one) share, read from the files: the
271
+ stylesheets and scripts they load, each local stylesheet's variables, classes, @keyframes and
272
+ fonts, and the components in ${COMPONENTS_FOLDER}/ with their attributes, slots, example and where
273
+ they are used. A few lines instead of every file, for the AI agent that writes the designs.
274
+
275
+ Options:
276
+ -r, --root <path> The folder the canvas serves (htmlboard … --root), for links that start
277
+ with "/" (default: the designs folder)
278
+ `);
279
+ return;
280
+ }
281
+ let root;
282
+ const dirs = [];
283
+ for (let i = 0; i < args.length; i++) {
284
+ const arg = args[i];
285
+ if (arg === '--root' || arg === '-r') root = args[++i];
286
+ else if (arg.startsWith('--root=')) root = arg.slice('--root='.length);
287
+ else if (arg.startsWith('-')) {
288
+ console.error(`[htmlboard] Unknown option: ${arg} (see htmlboard catalog --help)`);
289
+ process.exit(1);
290
+ } else dirs.push(arg);
291
+ }
292
+ let catalog;
293
+ try {
294
+ catalog = buildCatalog(dirs[0] || process.cwd(), { root });
295
+ } catch (err) {
296
+ console.error(`[htmlboard] ${err.message}`);
297
+ process.exit(1);
298
+ }
299
+ process.stdout.write(formatCatalog(catalog, { name: shownPath(catalog.dir) }));
300
+ }
301
+
302
+ async function runCheckCommand(args) {
303
+ if (args.includes('--help') || args.includes('-h')) {
304
+ console.log(`
305
+ Usage:
306
+ htmlboard check [directory or file] [-p <port>] [--json]
307
+
308
+ Has the canvas running for the designs (open in a browser: the check runs there) load each one
309
+ afresh and prints what went wrong, in a few lines of text: script errors and console.error, files
310
+ that aren't there, components that weren't built, fonts that didn't load, content cut at the
311
+ frame's edge, text cut off or spilling out of its box. A directory checks the designs in it, a
312
+ file just that design (default: the current directory).
313
+
314
+ Exits with 1 when it finds problems, 2 when it can't check (no canvas running, or none open).
315
+
316
+ Options:
317
+ -p, --port <number> The canvas's port (default: found on 3000 to 3020)
318
+ --json The report as JSON
319
+ `);
320
+ return;
321
+ }
322
+ let port;
323
+ let asJson = false;
324
+ const targets = [];
325
+ for (let i = 0; i < args.length; i++) {
326
+ const arg = args[i];
327
+ if (arg === '--port' || arg === '-p') port = parseInt(args[++i], 10);
328
+ else if (arg.startsWith('--port=')) port = parseInt(arg.slice('--port='.length), 10);
329
+ else if (arg === '--json') asJson = true;
330
+ else if (arg.startsWith('-')) {
331
+ console.error(`[htmlboard] Unknown option: ${arg} (see htmlboard check --help)`);
332
+ process.exit(2);
333
+ } else targets.push(arg);
334
+ }
335
+ try {
336
+ const { canvas, report } = await runCheck(targets[0], { port });
337
+ if (asJson) console.log(JSON.stringify(report, null, 2));
338
+ else process.stdout.write(formatReport(report, { name: shownPath(report.dir), url: canvas.url }));
339
+ process.exitCode = hasProblems(report) ? 1 : 0;
340
+ } catch (err) {
341
+ console.error(err instanceof CheckError ? err.message : `[htmlboard] ${err.message}`);
342
+ process.exitCode = 2;
343
+ }
344
+ }
345
+
209
346
  function runMigrate(args) {
210
347
  const write = args.includes('--write');
211
348
  const dirArg = args.find((arg) => !arg.startsWith('-'));
@@ -266,11 +403,26 @@ async function main() {
266
403
  return;
267
404
  }
268
405
 
406
+ if (args[0] === 'components') {
407
+ runComponents(args.slice(1));
408
+ return;
409
+ }
410
+
269
411
  if (args[0] === 'migrate') {
270
412
  runMigrate(args.slice(1));
271
413
  return;
272
414
  }
273
415
 
416
+ if (args[0] === 'catalog') {
417
+ runCatalog(args.slice(1));
418
+ return;
419
+ }
420
+
421
+ if (args[0] === 'check') {
422
+ await runCheckCommand(args.slice(1));
423
+ return;
424
+ }
425
+
274
426
  const options = parseArgs(args);
275
427
 
276
428
  if (options.help) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmlboard",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Infinite canvas and design explorer for HTML artboards — designed for humans and LLMs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@ npx htmlboard designs --open
23
23
 
24
24
  ## Write an artboard
25
25
 
26
- One design per file, with its facts in the `<head>`, linking the shared stylesheet (see below):
26
+ One design per file, with its facts in the `<head>`, linking the shared stylesheet and components (see below):
27
27
 
28
28
  ```html
29
29
  <!DOCTYPE html>
@@ -33,7 +33,8 @@ One design per file, with its facts in the `<head>`, linking the shared styleshe
33
33
  <title>Checkout · one page</title> <!-- its name on the canvas -->
34
34
  <meta name="description" content="Address and payment on one screen">
35
35
  <meta name="htmlboard" content="width=390, height=844"> <!-- its size -->
36
- <link rel="stylesheet" href="../styles.css"> <!-- shared tokens and components -->
36
+ <link rel="stylesheet" href="../styles.css"> <!-- shared tokens and classes -->
37
+ <script type="module" src="../components.js"></script> <!-- shared components -->
37
38
  <style>
38
39
  .screen { height: 844px; /* only what this design needs */ }
39
40
  </style>
@@ -54,11 +55,11 @@ One design per file, with its facts in the `<head>`, linking the shared styleshe
54
55
 
55
56
  Put what the designs share in one stylesheet instead of repeating it in every file: it keeps the designs consistent and spares writing the same CSS again.
56
57
 
57
- - The designs folder has a `styles.css` at its top (sample projects start with one). Read it before writing a design and use its tokens (`var(--accent)`) and classes (`.btn`, `.card`, …).
58
+ - The designs folder has a `styles.css` at its top (sample projects start with one). Before writing a design, see what the designs already share: `npx htmlboard catalog <designs folder>` prints it in a few lines, read from the files (each stylesheet's variables and classes, the scripts, the components with their attributes and an example). Use what fits (`var(--accent)`, `.btn`, `<tx-item>`) and add what's missing; open a file only for what the list doesn't say.
58
59
  - If there is none and the project already has a stylesheet or design tokens, link those. Otherwise create `styles.css` with your first design: colors, type and spacing as custom properties, the base styles, and the components you'll repeat.
59
60
  - Link it with a relative path: `styles.css` from the top of the folder, `../styles.css` from a section folder.
60
61
  - A design's own `<style>` holds only what is specific to it. When a second design needs the same thing, move it to `styles.css`.
61
- - Split it when it grows (`components/forms.css`, `charts.css`) and link the extra files where they are used.
62
+ - Split it when it grows (`forms.css`, `charts.css`) and link the extra files where they are used.
62
63
  - Editing `styles.css` restyles every design that links it, which is the point for a product's screens. For one-off explorations that should keep their look (a different style, logo options), style them in their own file instead.
63
64
 
64
65
  More tags, all optional:
@@ -68,6 +69,38 @@ More tags, all optional:
68
69
  - `<meta name="htmlboard:label" content="light">` adds a label to the name of the file's own frame ("Checkout · light"), to tell it from its variants.
69
70
  - `<meta name="htmlboard" content="skip">` leaves a file out (partials, templates, a page that isn't a design).
70
71
 
72
+ ## Components
73
+
74
+ Markup that designs repeat (a sidebar, a tab bar, a list row, a card with content) goes in a component, so a design writes one tag instead of the block: `<tx-item icon="☕" name="Blue Bottle" amount="-$6.75"></tx-item>`.
75
+
76
+ - `components/tx-item.html`, at the top of the designs folder, is `<tx-item>`: the file name is the tag, with a hyphen and no number prefix. Designs load `components.js` once (above). If the folder has no `components.js`, run `npx htmlboard components <designs folder>`.
77
+ - The catalog (above) lists them, with their attributes, an example and the designs that use them. Use them; don't copy their markup into designs.
78
+ - Make one when a second design repeats a block (or a design repeats it three times). Not for what a `styles.css` class already does (`<button class="btn btn-primary">` is short), nor in one-off explorations.
79
+
80
+ A component file is a piece of HTML (no `<!DOCTYPE>`, `<html>`, `<head>` or `<body>`):
81
+
82
+ ```html
83
+ <!-- <tx-item icon="💼" name="Invoice #1042" meta="Yesterday" amount="+$3,450.00" positive></tx-item> -->
84
+ <div class="icon">{{icon}}</div>
85
+ <div class="what"><b>{{name}}</b> <span class="muted">{{meta}}</span></div>
86
+ <div class="amount">{{amount}}</div>
87
+
88
+ <style>
89
+ & { display: flex; align-items: center; gap: 14px; } /* & is the element itself */
90
+ .icon { width: 40px; height: 40px; border-radius: 12px; background: var(--surface-2); }
91
+ &[positive] .amount { color: var(--emerald); }
92
+ </style>
93
+ ```
94
+
95
+ - First line: a comment with an example of use, with real values, every attribute and its variants side by side. The catalog shows it, and it is the component's preview on the canvas.
96
+ - `{{name}}` is the element's `name="…"` attribute, as text. Content goes in slots: `<slot></slot>` takes the element's children, `<slot name="actions"></slot>` its children with `slot="actions"`, and what a slot holds shows when nothing fills it.
97
+ - No logic in the markup: variants are attributes styled in CSS (`&[positive]`), and a list is several elements.
98
+ - Its `<style>` only reaches inside the element, which is a block box unless `&` says otherwise. `styles.css` applies inside too: build with its classes (`card`, `btn`, `muted`) and name the component's own parts with names it doesn't use. A design's own `<style>` comes after and can restyle one element, but its selectors reach inside components too, so keep them specific.
99
+ - `<script>` runs for each element once it is built, with `this` as the element. Use it sparingly, a few lines for what markup and CSS can't do, like marking the current link (`<app-sidebar active="Customers">`) or a toggle. No fetching, data or frameworks.
100
+ - Relative links in a component start from its file (`../01-screens/02-customers.html`).
101
+ - Close the tag: `<tx-item …></tx-item>`, never `<tx-item />`. 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>`. Where it sits (fixed, margins, the grid around it) belongs to the design.
102
+ - Editing a component changes every design that uses it, like `styles.css`.
103
+
71
104
  ## Group and order
72
105
 
73
106
  - **A folder is a section** (a row on the canvas), named after the folder. Put related designs in a folder: `designs/03-checkout/`.
@@ -85,16 +118,17 @@ More tags, all optional:
85
118
  }
86
119
  ```
87
120
 
88
- An entry is a file name (its variants follow it), or `{ "file", "title", "width", "height", "category", "description" }` to override the file. `"folder"` brings a whole folder. `npx htmlboard migrate <folder>` moves an old designs.json's sizes, names and variants into the files (`--write` to apply; without it, it only shows the plan).
121
+ An entry is a file name (its variants follow it), or `{ "file", "title", "width", "height", "category", "description" }` to override the file. `"folder"` brings a whole folder. Components aren't on the canvas unless designs.json says `"components": "show"` (or lists `components/<tag>.html`): each shows alone, from its first-line example, 480 wide unless its file has `<meta name="htmlboard" content="width=…">`. `npx htmlboard migrate <folder>` moves an old designs.json's sizes, names and variants into the files (`--write` to apply; without it, it only shows the plan).
89
122
 
90
123
  ## Live reload
91
124
 
92
125
  - Saving an artboard's HTML reloads just that frame; the user's view stays put. A new name or size in its `<head>` updates in place.
93
- - Saving a file artboards use (a stylesheet, a script, an image, data) reloads the artboards that loaded it.
126
+ - Saving a file artboards use (a stylesheet, a component, a script, an image, data) reloads the artboards that loaded it.
94
127
  - Adding, removing or moving artboards, or saving `designs.json`, rebuilds the canvas.
95
128
 
96
129
  ## Working with the user
97
130
 
98
131
  - Put variants in separate files next to each other (`01-hero-a.html`, `02-hero-b.html`) instead of overwriting the current design, so they can be compared on the canvas. Keep the current one as the reference until the user picks.
99
132
  - Say which files you created or changed and what to look at; the user is watching the canvas, not the code. Link straight to an artboard with `http://localhost:3000/#<id>`: the id is the file's path without prefixes and `.html`, other symbols turned into `-` (`03-checkout/02-steps.html` → `checkout-steps`; a variant adds its query: `checkout-steps-theme-dark`).
100
- - If you can browse or take screenshots, open the artboard directly (`http://localhost:3000/<file>`) to check it at its real size before reporting back. Fix overflow, clipped text and broken images first.
133
+ - Before reporting back, run `npx htmlboard check <designs folder>` (or a design's file). The canvas open in the user's browser loads each design again and lists, 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. Fix what it lists and run it again. A note that content goes on below a frame of fixed height is fine for a screen that scrolls, not for a flyer. The check runs in the browser: if it says the canvas isn't open, ask the user to open its URL.
134
+ - The check can't judge looks (colors, spacing, alignment). If you can take screenshots, also look at the design at its real size (`http://localhost:3000/<file>`).