htmlboard 0.5.0 โ†’ 0.7.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
@@ -39,6 +39,8 @@ Modern AI agents and developers generate dozens of HTML mockups, layout variatio
39
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.
40
40
  - ๐Ÿ“ฑ **Device Filters**: **Mobile**, **Tablet** and **Desktop** (and **Components**, or your own categories) lay out just those designs, side by side, to compare them.
41
41
  - ๐Ÿ”Ž **Search**: Find designs by name, file, tags or section (case and accents don't matter); the others fade out in place.
42
+ - ๐Ÿ‘๏ธ **Sections You Can Fold and Hide**: Fold a section in the list, or hide it from the canvas: its designs unload, so a canvas of hundreds of designs stays light. <kbd>Alt</kbd> + click its eye to see only that one; the list remembers what you folded and hid.
43
+ - ๐Ÿ“ธ **Copy or Save as an Image**: Hover a design for two buttons: copy it to the clipboard as a PNG of its full size (twice that on a Retina screen), or save it in a folder you choose once. <kbd>C</kbd> and <kbd>S</kbd> do the same for the selected design.
42
44
  - ๐ŸŽฏ **Focus & Zoom-to-Fit**: Click a design in the list or double-click it on the canvas to fit it, <kbd>Shift</kbd> + <kbd>1</kbd> fits everything, <kbd>โ†</kbd> <kbd>โ†’</kbd> step through the designs.
43
45
  - ๐Ÿ”— **Deep Links**: The URL follows the selected design (`http://localhost:3000/#checkout-b`), so you can share or reload a view of one design.
44
46
  - โŒจ๏ธ **Keyboard-First Workflow**: Figma-like shortcuts; <kbd>?</kbd> lists them.
@@ -93,6 +95,8 @@ Options:
93
95
  -h, --help Display help message
94
96
  ```
95
97
 
98
+ 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`.
99
+
96
100
  ### Examples
97
101
 
98
102
  ```bash
@@ -131,31 +135,39 @@ htmlboard migrate design --write
131
135
 
132
136
  HtmlBoard is built from the ground up to pair with AI coding agents (Claude, Gemini, Codex, ChatGPT, Antigravity, Cursor).
133
137
 
134
- ### Install the agent skill
138
+ ### Install the agent skills
139
+
140
+ 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:
135
141
 
136
- 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:
142
+ | Skill | What it teaches | Read |
143
+ | :--- | :--- | :--- |
144
+ | `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 |
145
+ | `htmlboard-components` | The component file: attributes, slots, scoped CSS, a script, its first-line example | Before writing or changing a component |
146
+ | `htmlboard-designs-json` | Curating the canvas with `designs.json`, and `htmlboard migrate` | When the designs folder has a `designs.json` |
147
+
148
+ Install them in your project:
137
149
 
138
150
  ```bash
139
151
  npx htmlboard skills install
140
152
  ```
141
153
 
142
- 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.
154
+ 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.
143
155
 
144
156
  ```bash
145
157
  npx htmlboard skills install --global # for your user (~/.claude/skills, ~/.agents/skills), all projects
146
158
  npx htmlboard skills install --agent claude # only one agent: claude | agents | all (default)
147
159
  npx htmlboard skills install --force # update a copy you edited locally
148
- npx htmlboard skills uninstall # remove it (accepts the same --global / --agent)
160
+ npx htmlboard skills uninstall # remove them (accepts the same --global / --agent)
149
161
  ```
150
162
 
151
- Running `install` again after upgrading HtmlBoard updates the skill; a copy you edited is kept unless you pass `--force`.
163
+ Running `install` again after upgrading HtmlBoard updates the skills; a copy you edited is kept unless you pass `--force`.
152
164
 
153
165
  ### The workflow
154
166
 
155
167
  1. **Ask your LLM to generate artboards:**
156
168
  > *"Create 3 design explorations for our checkout flow in `designs/03-checkout/`."*
157
169
 
158
- 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.
170
+ 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.
159
171
 
160
172
  2. **Run HtmlBoard in the background:**
161
173
  ```bash
@@ -230,7 +242,8 @@ Checked 6 designs in designs (the canvas at http://localhost:3000): 2 with probl
230
242
 
231
243
  Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist`, `build` and the [components](#-components) in `components/` aside):
232
244
 
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.
245
+ - **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 ("Top level" in the list).
246
+ - **In the list, a section folds** under its title, and **its eye hides it from the canvas**: its designs unload and the rows below move up, which keeps a canvas of many designs light. <kbd>Alt</kbd> + click an eye shows only that section (again: all of them); the buttons above the sections fold, unfold, show or hide them all. A hidden section's designs stay in the list, faded, and the search still finds them: clicking one (or a link to it) shows its section. The list remembers what you folded and hid, for each project.
234
247
  - **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.
235
248
  - **A file describes itself** in its `<head>`:
236
249
 
@@ -240,6 +253,7 @@ Every `.html` file under the folder is a design (hidden folders, `node_modules`,
240
253
  <meta name="htmlboard" content="width=390, height=844"> <!-- its size (and category=โ€ฆ) -->
241
254
  <meta name="htmlboard:label" content="light"> <!-- added to its name: "Checkout ยท light" -->
242
255
  <meta name="htmlboard:variant" content="?theme=dark ยท dark"> <!-- one more frame: "Checkout ยท dark" -->
256
+ <meta name="htmlboard:variant" content="width=768, height=1024 ยท tablet"> <!-- and at a tablet's size -->
243
257
  ```
244
258
 
245
259
  | Tag | What it says |
@@ -248,7 +262,7 @@ Every `.html` file under the folder is a design (hidden folders, `node_modules`,
248
262
  | `<meta name="description">` | Its description, shown in the list and found by the search. |
249
263
  | `<meta name="htmlboard" content="โ€ฆ">` | `width=โ€ฆ`, `height=โ€ฆ`, `category=โ€ฆ` (`mobile`, `tablet`, `desktop`, `components` or your own word), or `skip` to leave the file out (partials, templates). |
250
264
  | `<meta name="htmlboard:label">` | Text added to the name of the file's own frame, when it has variants. |
251
- | `<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. |
265
+ | `<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. |
252
266
 
253
267
  **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.
254
268
 
@@ -355,6 +369,7 @@ It writes each file's size, category, label, variants and description as tags af
355
369
  | <kbd>Shift</kbd> + <kbd>2</kbd>, double-click | Fit the design |
356
370
  | <kbd>โ†</kbd> / <kbd>โ†’</kbd> | Previous / next design |
357
371
  | <kbd>Enter</kbd> | Open the selected design on its own |
372
+ | <kbd>C</kbd> / <kbd>S</kbd> | Copy / save the selected design as an image (<kbd>Shift</kbd> + <kbd>S</kbd>: choose the folder) |
358
373
  | <kbd>/</kbd> | Search (<kbd>Enter</kbd> fits the first match, <kbd>Esc</kbd> clears) |
359
374
  | <kbd>1</kbd> โ€“ <kbd>4</kbd> | Show all, mobile, tablet, desktop (<kbd>5</kbd> on: the other categories) |
360
375
  | <kbd>R</kbd> | Reload the designs |
@@ -364,6 +379,8 @@ It writes each file's size, category, label, variants and description as tags af
364
379
 
365
380
  On the canvas the designs are pictures: the canvas takes every click and scroll, so <kbd>Ctrl</kbd> + scroll never zooms the page itself. Use **Open** (or <kbd>Enter</kbd>) to try a design's links and hover states on its own.
366
381
 
382
+ **Images of a design.** Hovering a design shows two buttons over its top right corner. **Copy** puts it in the clipboard as a PNG, to paste anywhere; **Save** writes it in a folder you pick the first time (the canvas remembers it for the project, <kbd>Shift</kbd> + click picks another), as `<design id>.png`, then `-2`, `-3`โ€ฆ so it never replaces an image. Browsers without folder access (Firefox, Safari) download it instead. Your browser draws the image from the page as its frame shows it, with its stylesheets, fonts and images, at the frame's size and your screen's pixel density. A few things can't be in it: images from other sites that don't allow it, pages inside the page (iframes), and shadow DOM.
383
+
367
384
  ---
368
385
 
369
386
  ## ๐Ÿ”Œ Programmatic API
@@ -456,6 +473,7 @@ htmlboard/
456
473
  โ”‚ โ”‚ โ”œโ”€โ”€ explorer.css # Dark chrome, dot grid
457
474
  โ”‚ โ”‚ โ”œโ”€โ”€ explorer.js # Layout, pan/zoom, search, live reload
458
475
  โ”‚ โ”‚ โ”œโ”€โ”€ check.js # Checks the designs in frames out of sight, for `htmlboard check`
476
+ โ”‚ โ”‚ โ”œโ”€โ”€ picture.js # A design as an image, drawn by the browser (copy, save)
459
477
  โ”‚ โ”‚ โ”œโ”€โ”€ probe.js # Notes script errors and failed loads in the frames the check loads
460
478
  โ”‚ โ”‚ โ”œโ”€โ”€ favicon.svg, logo.svg, mark.svg
461
479
  โ”‚ โ”‚ โ””โ”€โ”€ fonts/ # Inter and Bricolage Grotesque (OFL)
@@ -466,7 +484,9 @@ htmlboard/
466
484
  โ”‚ โ”œโ”€โ”€ 01-screens/ # Dashboard, customers, mobile feed
467
485
  โ”‚ โ””โ”€โ”€ 02-ui-kit/
468
486
  โ”œโ”€โ”€ skills/
469
- โ”‚ โ””โ”€โ”€ htmlboard/ # Agent skill (SKILL.md) copied by `htmlboard skills install`
487
+ โ”‚ โ”œโ”€โ”€ htmlboard/ # Agent skills (SKILL.md each) copied by `htmlboard skills install`
488
+ โ”‚ โ”œโ”€โ”€ htmlboard-components/
489
+ โ”‚ โ””โ”€โ”€ htmlboard-designs-json/
470
490
  โ”œโ”€โ”€ brand/ # The logo's files (from design/tools/logo.py)
471
491
  โ”œโ”€โ”€ design/ # htmlboard's own designs, on its own canvas: `npx htmlboard design --root .`
472
492
  โ”œโ”€โ”€ package.json
package/bin/htmlboard.js CHANGED
@@ -10,6 +10,7 @@ import { planMigration, checkMigration, applyMigration } from '../src/migrate.js
10
10
  import { installComponents, COMPONENTS_FOLDER, COMPONENTS_SCRIPT } from '../src/components.js';
11
11
  import { buildCatalog, formatCatalog } from '../src/catalog.js';
12
12
  import { runCheck, formatReport, hasProblems, CheckError } from '../src/check.js';
13
+ import { PORTS, findCanvases } from '../src/canvases.js';
13
14
 
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = path.dirname(__filename);
@@ -99,7 +100,7 @@ Examples:
99
100
  htmlboard ./designs -p 8080 # Serve ./designs on port 8080
100
101
  htmlboard design --root . # Artboards in ./design that link to ../css/โ€ฆ
101
102
  htmlboard --init # Add sample designs to the current folder
102
- 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
103
104
  htmlboard components designs # Let ./designs use components (components/tx-item.html is <tx-item>)
104
105
  htmlboard catalog designs # What ./designs share, in a few lines (for your AI agent)
105
106
  htmlboard check designs # Script errors, missing files, cut-off text in ./designs
@@ -154,8 +155,8 @@ function scaffoldProject(targetDir) {
154
155
  function showSkillsHelp() {
155
156
  console.log(`
156
157
  Usage:
157
- htmlboard skills install [options] Copy the HtmlBoard skill into your agents' skill folders
158
- 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
159
160
 
160
161
  Options:
161
162
  -g, --global Install for your user (~/.claude/skills, ~/.agents/skills) instead of this project
@@ -211,7 +212,7 @@ function runSkills(args) {
211
212
  console.log(` ${labels[status]} ${path.relative(process.cwd(), dest) || dest}`);
212
213
  }
213
214
  if (action === 'install' && results.some((r) => r.status === 'installed' || r.status === 'updated')) {
214
- 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.`);
215
216
  }
216
217
  }
217
218
 
@@ -453,6 +454,25 @@ async function main() {
453
454
  scaffoldProject(targetDir);
454
455
  }
455
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
+
456
476
  try {
457
477
  const { url, rootDir } = await startServer({
458
478
  dir: targetDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmlboard",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Infinite canvas and design explorer for HTML artboards โ€” designed for humans and LLMs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  name: htmlboard
3
- description: Design UI in plain HTML files and show them side by side on an infinite, live-reloading canvas with the htmlboard CLI. Use when the user asks for mockups, screens, design variants or explorations, components, flyers or any visual proposal, or mentions htmlboard, artboards or designs.json.
3
+ description: Design UI in plain HTML files and show them side by side on an infinite, live-reloading canvas with the htmlboard CLI. Use when the user asks for mockups, screens, design variants or explorations, flyers or any visual proposal, or mentions htmlboard or artboards.
4
4
  ---
5
5
 
6
6
  # HtmlBoard
7
7
 
8
8
  HtmlBoard serves a folder of HTML files as artboards on a zoomable canvas, like a Figma page. Every design is one ordinary HTML file that says its own name and size, so you design by writing HTML and CSS, and the user sees the result update live while you work.
9
9
 
10
+ Two more skills cover the rest when you need it: **htmlboard-components** to write or change a component, **htmlboard-designs-json** when the designs folder has a `designs.json`.
11
+
10
12
  ## Start the canvas
11
13
 
12
14
  Run it in the background, pointed at the designs folder (it is created if missing):
@@ -19,7 +21,7 @@ npx htmlboard designs --open
19
21
  - `-p <port>` picks the port. `--init` adds sample designs; skip it unless the user wants a starter.
20
22
  - If the artboards link to files above the designs folder (`../css/theme.css`), serve from the project root: `npx htmlboard design --root .`
21
23
  - An empty folder gets the samples automatically, so write your first artboard before starting the server.
22
- - If a server is already running for this folder, reuse it. `GET /api/health` answers `{"status":"ok","dir":โ€ฆ}`.
24
+ - Running it again for a folder that already has a canvas doesn't start another: it prints that canvas's URL.
23
25
 
24
26
  ## Write an artboard
25
27
 
@@ -48,87 +50,46 @@ One design per file, with its facts in the `<head>`, linking the shared styleshe
48
50
  - Always give the `width`: it is the frame's viewport, and the page's CSS needs it. Give the `height` for fixed sizes (a phone screen, a flyer: the frame cuts at it); leave it out for pages that should grow with their content.
49
51
  - Design at the real size of the target: phone 390ร—844, tablet 768ร—1024, desktop 1280ร—800 (or 1440ร—900), and the exact size for print or social formats (e.g. 1080ร—1350).
50
52
  - Use real content in the user's language, not lorem ipsum.
51
- - Relative links to CSS, images, scripts and fonts work (`assets/logo.svg`; `../css/theme.css` with `--root`).
52
- - No build step: plain HTML, CSS and browser JavaScript (ES modules and CDN scripts work).
53
+ - Relative links to CSS, images, scripts and fonts work (`assets/logo.svg`; `../css/theme.css` with `--root`). No build step: plain HTML, CSS and browser JavaScript (ES modules and CDN scripts work).
54
+
55
+ More tags, all optional:
56
+
57
+ - `<meta name="htmlboard:variant" content="?theme=dark ยท dark">` shows the same file again with that query string, named "<title> ยท dark": for states the page builds from its URL (themes, empty or error states, data). A variant can also change the size: `content="width=390, height=844 ยท mobile"` shows a responsive page at a phone's size too, from the same file (`?theme=dark width=390 ยท dark mobile` does both). One tag per variant; they follow the file.
58
+ - `<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.
59
+ - `category=โ€ฆ` in the `htmlboard` tag sets the explorer's filter (`mobile`, `components`, your own word) when the path and size don't say it.
60
+ - `<meta name="htmlboard" content="skip">` leaves a file out (partials, templates, a page that isn't a design).
53
61
 
54
62
  ## Shared styles
55
63
 
56
64
  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.
57
65
 
58
66
  - 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.
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.
67
+ - 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 classes you'll repeat.
60
68
  - Link it with a relative path: `styles.css` from the top of the folder, `../styles.css` from a section folder.
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`.
62
- - Split it when it grows (`forms.css`, `charts.css`) and link the extra files where they are used.
69
+ - 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`. Split it when it grows (`forms.css`, `charts.css`) and link the extra files where they are used.
63
70
  - 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.
64
71
 
65
- More tags, all optional:
66
-
67
- - `category=โ€ฆ` in the `htmlboard` tag files the design under the explorer's filters: `mobile`, `tablet`, `desktop`, `components` or your own word. Usually not needed: it comes from words in the path, then from the size when it is a screen's.
68
- - `<meta name="htmlboard:variant" content="?theme=dark ยท dark">` shows the same file again with that query string, named "<title> ยท dark". Use it for states the page builds from its URL (themes, empty or error states, data). Add one tag per variant; they follow the file.
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.
70
- - `<meta name="htmlboard" content="skip">` leaves a file out (partials, templates, a page that isn't a design).
71
-
72
72
  ## Components
73
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
- ```
74
+ Markup that designs repeat (a sidebar, a tab bar, a list row, a card with content) goes in a component, a file in `components/` that designs use as one tag instead of the block: `<tx-item icon="โ˜•" name="Blue Bottle" amount="-$6.75"></tx-item>`.
94
75
 
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`.
76
+ - The catalog lists the project's components with their attributes, an example and the designs that use them. Use them; don't copy their markup into designs.
77
+ - 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, nor in one-off explorations. To write or change one, use the htmlboard-components skill.
103
78
 
104
79
  ## Group and order
105
80
 
106
81
  - **A folder is a section** (a row on the canvas), named after the folder. Put related designs in a folder: `designs/03-checkout/`.
107
82
  - **Number prefixes set the order** of files and folders: `01-one-page.html`, `02-steps.html`. They don't show in names or links, so you can renumber freely. Files at the top of the designs folder form a first row without a title.
108
83
  - New files and folders show up on the canvas by themselves; you don't register them anywhere.
109
-
110
- **`designs.json`** is optional. When a project has one (in the designs folder), it curates: sections with titles (accents, capitals, an order across folders), and values that win over the files'. Designs it doesn't list still show after the ones it lists, in their folder's section, unless it says `"unlisted": "hide"`. If the project's designs.json groups designs by hand, add a new design to the right section there:
111
-
112
- ```json
113
- {
114
- "sections": [
115
- { "title": "Checkout ยท mobile", "designs": ["03-checkout/01-one-page.html", "03-checkout/02-steps.html"] },
116
- { "title": "Launch flyers", "folder": "04-flyers" }
117
- ]
118
- }
119
- ```
120
-
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).
84
+ - A `designs.json` in the designs folder, when there is one, curates the canvas (section titles, an order across folders, designs it hides): use the htmlboard-designs-json skill before adding, moving or renaming designs there.
122
85
 
123
86
  ## Live reload
124
87
 
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.
126
- - Saving a file artboards use (a stylesheet, a component, a script, an image, data) reloads the artboards that loaded it.
127
- - Adding, removing or moving artboards, or saving `designs.json`, rebuilds the canvas.
88
+ Saving a design reloads its frame, saving a file designs use (a stylesheet, a component, an image) reloads the designs that loaded it, and new or removed designs show up by themselves. The user's view stays put: there is nothing to refresh.
128
89
 
129
90
  ## Working with the user
130
91
 
131
92
  - 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.
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`).
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.
93
+ - Say which files you created or changed and what to look at; the user is watching the canvas, not the code. Link straight to a design with its file after the `#`: `http://localhost:3000/#03-checkout/02-steps.html`.
94
+ - 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 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). Fix it and run it again. A note about content below a fixed-height frame is fine for a screen that scrolls, not for a flyer. If it says the canvas isn't open, ask the user to open its URL.
134
95
  - 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>`).
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: htmlboard-components
3
+ description: Write or change an htmlboard component, a file in components/ that designs use as a custom element (components/tx-item.html is <tx-item>). Use when a block repeats across htmlboard designs, or before creating or editing a file in components/.
4
+ ---
5
+
6
+ # HtmlBoard components
7
+
8
+ A component is markup that designs repeat (a sidebar, a tab bar, a list row, a card with content), written once and used as one tag: `<tx-item icon="โ˜•" name="Blue Bottle" amount="-$6.75"></tx-item>`. The htmlboard skill covers the designs themselves.
9
+
10
+ - `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.
11
+ - Designs load `components.js` once, after their stylesheet: `<script type="module" src="../components.js"></script>`. If the designs folder has no `components.js`, run `npx htmlboard components <designs folder>`.
12
+ - See what exists first: `npx htmlboard catalog <designs folder>` lists the components with their attributes, an example and the designs that use them.
13
+
14
+ A component file is a piece of HTML (no `<!DOCTYPE>`, `<html>`, `<head>` or `<body>`):
15
+
16
+ ```html
17
+ <!-- <tx-item icon="๐Ÿ’ผ" name="Invoice #1042" meta="Yesterday" amount="+$3,450.00" positive></tx-item> -->
18
+ <div class="icon">{{icon}}</div>
19
+ <div class="what"><b>{{name}}</b> <span class="muted">{{meta}}</span></div>
20
+ <div class="amount">{{amount}}</div>
21
+
22
+ <style>
23
+ & { display: flex; align-items: center; gap: 14px; } /* & is the element itself */
24
+ .icon { width: 40px; height: 40px; border-radius: 12px; background: var(--surface-2); }
25
+ &[positive] .amount { color: var(--emerald); }
26
+ </style>
27
+ ```
28
+
29
+ - 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.
30
+ - `{{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.
31
+ - No logic in the markup: variants are attributes styled in CSS (`&[positive]`), and a list is several elements.
32
+ - 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.
33
+ - `<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.
34
+ - Relative links in a component start from its file (`../01-screens/02-customers.html`).
35
+ - 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.
36
+ - Editing a component changes every design that uses it, like `styles.css`. Afterwards, `npx htmlboard check <designs folder>` lists components that weren't built and errors in their scripts.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: htmlboard-designs-json
3
+ description: Curate an htmlboard canvas with designs.json (section titles and order, a design's name or size, hiding unlisted designs, showing components). Use when the designs folder has a designs.json, or the user wants sections named or ordered beyond what folders give.
4
+ ---
5
+
6
+ # HtmlBoard designs.json
7
+
8
+ Without one, folders are the canvas's sections and number prefixes set the order (the htmlboard skill covers the designs themselves). A `designs.json` in the designs folder curates on top of that: sections with titles (accents, capitals, an order across folders), and values that win over the files'. The files still say the rest: their name, size and variants in their `<head>`.
9
+
10
+ ```json
11
+ {
12
+ "title": "Wallet app",
13
+ "sections": [
14
+ { "title": "Checkout ยท mobile", "designs": ["03-checkout/01-one-page.html", "03-checkout/02-steps.html"] },
15
+ { "title": "Launch flyers", "folder": "04-flyers" }
16
+ ]
17
+ }
18
+ ```
19
+
20
+ - Sections come in their order, designs in theirs. An entry is a file name (its variants follow it; with a query string, it places one variant), or `{ "file", "title", "width", "height", "category", "description" }` to override the file's values.
21
+ - `"folder"` brings a whole folder, in prefix order.
22
+ - Designs it doesn't list still show after the ones it lists, in the section that holds their folder (or a section of their own), unless it says `"unlisted": "hide"`. When it groups designs by hand, add a new design to the right section.
23
+ - Components aren't on the canvas unless it 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=โ€ฆ">`.
24
+ - Saving it rebuilds the canvas.
25
+ - `npx htmlboard migrate <designs folder>` moves an old designs.json's sizes, names and variants into the files and keeps only what they can't say (`--write` to apply; without it, it only shows the plan).
@@ -0,0 +1,38 @@
1
+ import http from 'node:http';
2
+
3
+ // The htmlboard canvases running on this machine, found by asking ports for /api/health: for
4
+ // `htmlboard check`, and so that starting htmlboard again for a folder points at the running one.
5
+
6
+ /** Where htmlboard listens unless told otherwise: 3000, or the next free port */
7
+ export const PORTS = Array.from({ length: 21 }, (_, i) => 3000 + i);
8
+
9
+ /** GET a URL: its status and body */
10
+ export function get(url, { timeout = 0 } = {}) {
11
+ return new Promise((resolve, reject) => {
12
+ const req = http.get(url, (res) => {
13
+ const chunks = [];
14
+ res.on('data', (chunk) => chunks.push(chunk));
15
+ res.on('end', () => resolve({ status: res.statusCode, body: Buffer.concat(chunks).toString('utf8') }));
16
+ res.on('error', reject);
17
+ });
18
+ req.on('error', reject);
19
+ if (timeout) req.setTimeout(timeout, () => req.destroy(new Error('timeout')));
20
+ });
21
+ }
22
+
23
+ /**
24
+ * The htmlboard servers listening on these ports: each its health (dir, root, version) with its
25
+ * port and url.
26
+ */
27
+ export async function findCanvases(ports = PORTS) {
28
+ const found = await Promise.all([...new Set(ports)].map(async (port) => {
29
+ try {
30
+ const { status, body } = await get(`http://127.0.0.1:${port}/api/health`, { timeout: 1500 });
31
+ const health = JSON.parse(body);
32
+ return status === 200 && health.status === 'ok' && health.dir ? { ...health, port, url: `http://localhost:${port}` } : null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }));
37
+ return found.filter(Boolean);
38
+ }
package/src/check.js CHANGED
@@ -1,48 +1,18 @@
1
- import http from 'node:http';
2
1
  import path from 'node:path';
2
+ import { PORTS, get, findCanvases } from './canvases.js';
3
3
 
4
4
  // `htmlboard check`: the canvas running for a folder, open in a browser, loads its designs and says
5
5
  // what went wrong in each (see client/check.js); this finds that canvas, asks it, and puts the
6
6
  // answer in a few lines of text.
7
7
 
8
- /** Where htmlboard listens unless told otherwise: 3000, or the next free port */
9
- const PORTS = Array.from({ length: 21 }, (_, i) => 3000 + i);
10
-
11
8
  /** A problem the check couldn't get past (no canvas running, none open in a browser): exit code 2 */
12
9
  export class CheckError extends Error {}
13
10
 
14
- function get(url, { timeout = 0 } = {}) {
15
- return new Promise((resolve, reject) => {
16
- const req = http.get(url, (res) => {
17
- const chunks = [];
18
- res.on('data', (chunk) => chunks.push(chunk));
19
- res.on('end', () => resolve({ status: res.statusCode, body: Buffer.concat(chunks).toString('utf8') }));
20
- res.on('error', reject);
21
- });
22
- req.on('error', reject);
23
- if (timeout) req.setTimeout(timeout, () => req.destroy(new Error('timeout')));
24
- });
25
- }
26
-
27
11
  const isInside = (base, target) => {
28
12
  const rel = path.relative(base, target);
29
13
  return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
30
14
  };
31
15
 
32
- /** The htmlboard servers listening on these ports, each with its designs folder (dir) and root */
33
- async function findCanvases(ports) {
34
- const found = await Promise.all(ports.map(async (port) => {
35
- try {
36
- const { status, body } = await get(`http://127.0.0.1:${port}/api/health`, { timeout: 1500 });
37
- const health = JSON.parse(body);
38
- return status === 200 && health.status === 'ok' && health.dir ? { ...health, port, url: `http://localhost:${port}` } : null;
39
- } catch {
40
- return null;
41
- }
42
- }));
43
- return found.filter(Boolean);
44
- }
45
-
46
16
  /**
47
17
  * Check designs with the canvas that shows them: a designs folder, a folder in it, or a design's
48
18
  * file (default: all the designs of the canvas found). The canvas must be open in a browser, where