htmlboard 0.2.0 → 0.4.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
@@ -32,8 +32,9 @@ Modern AI agents and developers generate dozens of HTML mockups, layout variatio
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
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.
35
- - 🔍 **Auto-Discovery**: Point HtmlBoard at any folder. It finds every HTML file, names it after its `<title>`, sizes it from words in its path (mobile, tablet, desktop…) and lays the folders out as rows.
36
- - 📐 **Declarative Manifest (`designs.json`)**: Optional file to choose the sections, the order, the sizes and the names.
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
+ - 📐 **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
+ - 📱 **Device Filters**: **Mobile**, **Tablet** and **Desktop** (and **Components**, or your own categories) lay out just those designs, side by side, to compare them.
37
38
  - 🔎 **Search**: Find designs by name, file, tags or section (case and accents don't matter); the others fade out in place.
38
39
  - 🎯 **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.
39
40
  - 🔗 **Deep Links**: The URL follows the selected design (`http://localhost:3000/#checkout-b`), so you can share or reload a view of one design.
@@ -58,7 +59,7 @@ htmlboard
58
59
 
59
60
  ### Scaffold a Starter Project
60
61
 
61
- To create a new workspace with starter templates (mobile screen, desktop dashboard, and component library):
62
+ To start with sample designs (a folder of screens and a folder of components, each file describing itself, all sharing one `styles.css`):
62
63
 
63
64
  ```bash
64
65
  npx htmlboard --init
@@ -73,6 +74,7 @@ Usage:
73
74
  htmlboard [directory] [options]
74
75
  npx htmlboard [directory] [options]
75
76
  htmlboard skills <install|uninstall> [options] Teach your AI agent to use HtmlBoard
77
+ htmlboard migrate [directory] [--write] Move designs.json's sizes, titles and variants into the files
76
78
 
77
79
  Options:
78
80
  -p, --port <number> Port to listen on (default: 3000)
@@ -80,7 +82,7 @@ Options:
80
82
  -r, --root <path> Serve files from this parent folder, so artboards can link to
81
83
  shared files outside the designs folder (default: the designs folder)
82
84
  -o, --open Open the explorer automatically in default browser
83
- --init Scaffold starter designs.json and sample artboards
85
+ --init Add sample designs (two folders of artboards)
84
86
  -v, --version Display HtmlBoard version
85
87
  -h, --help Display help message
86
88
  ```
@@ -100,8 +102,12 @@ htmlboard ./src/designs -p 8080
100
102
  # Artboards in ./design that link to the site's own ../css and ../js
101
103
  htmlboard design --root .
102
104
 
103
- # Initialize sample templates in a new folder
105
+ # Start a new folder with sample designs
104
106
  htmlboard ./my-new-ui --init --open
107
+
108
+ # See what moving ./design/designs.json into its files would do, then do it
109
+ htmlboard migrate design
110
+ htmlboard migrate design --write
105
111
  ```
106
112
 
107
113
  ---
@@ -112,7 +118,7 @@ HtmlBoard is built from the ground up to pair with AI coding agents (Claude, Gem
112
118
 
113
119
  ### Install the agent skill
114
120
 
115
- HtmlBoard ships an [Agent Skill](https://agentskills.io) that teaches your agent how to use it: where to put artboards, how to size and name them, how `designs.json` works and how to start the canvas. Install it in your project:
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:
116
122
 
117
123
  ```bash
118
124
  npx htmlboard skills install
@@ -132,7 +138,9 @@ Running `install` again after upgrading HtmlBoard updates the skill; a copy you
132
138
  ### The workflow
133
139
 
134
140
  1. **Ask your LLM to generate artboards:**
135
- > *"Create 3 design explorations for our checkout flow in `artboards/checkout-v1.html`, `artboards/checkout-v2.html`, and `artboards/checkout-v3.html`."*
141
+ > *"Create 3 design explorations for our checkout flow in `designs/03-checkout/`."*
142
+
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.
136
144
 
137
145
  2. **Run HtmlBoard in the background:**
138
146
  ```bash
@@ -144,78 +152,71 @@ Running `install` again after upgrading HtmlBoard updates the skill; a copy you
144
152
 
145
153
  ---
146
154
 
147
- ## 📋 The `designs.json` Manifest
155
+ ## 📂 How Designs Are Found
148
156
 
149
- While HtmlBoard automatically discovers HTML files, you can add a `designs.json` file in your designs folder for custom layout and organization. Only `sections[].designs[].file` is required; the short form is enough:
157
+ Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist` and `build` aside):
150
158
 
151
- ```json
152
- {
153
- "sections": [
154
- {
155
- "title": "Home",
156
- "designs": [
157
- { "file": "home-mobile.html", "title": "Home · phone", "width": 390, "height": 844 },
158
- { "file": "home-mobile.html?theme=dark", "title": "Home · phone · dark", "width": 390, "height": 844 },
159
- { "file": "home-desktop.html", "title": "Home · desktop", "width": 1440 }
160
- ]
161
- }
162
- ]
163
- }
159
+ - **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
+ - **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.
161
+ - **A file describes itself** in its `<head>`:
162
+
163
+ ```html
164
+ <title>Checkout</title> <!-- its name -->
165
+ <meta name="description" content="One page, address first"> <!-- its description -->
166
+ <meta name="htmlboard" content="width=390, height=844"> <!-- its size (and category=…) -->
167
+ <meta name="htmlboard:label" content="light"> <!-- added to its name: "Checkout · light" -->
168
+ <meta name="htmlboard:variant" content="?theme=dark · dark"> <!-- one more frame: "Checkout · dark" -->
164
169
  ```
165
170
 
166
- Sections are rows (top to bottom) and designs run left to right. A `file` may carry a query string, so one HTML file can show several states. Leave `height` out to let the frame grow with its page. `id` and `category` are derived from the file when left out.
171
+ | Tag | What it says |
172
+ | :--- | :--- |
173
+ | `<title>` | The design's name (without one: the file's name, prefix and extension off). |
174
+ | `<meta name="description">` | Its description, shown in the list and found by the search. |
175
+ | `<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
+ | `<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. |
178
+
179
+ **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.
167
180
 
168
- The full form adds ids, descriptions (shown in the list), categories and tags (searchable); `name`/`artboards` are synonyms of `title`/`designs`:
181
+ **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
+
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.
184
+
185
+ ---
186
+
187
+ ## 📋 `designs.json` (Optional)
188
+
189
+ A `designs.json` in the folder curates the canvas when folders aren't enough: section titles with accents and capitals, an order across folders, a name or size the file doesn't give. The files still say the rest:
169
190
 
170
191
  ```json
171
192
  {
172
- "title": "Fintech Mobile & Web App",
173
- "description": "Exploration of screen flows, responsive layouts, and UI components",
193
+ "title": "Wallet app",
174
194
  "sections": [
195
+ { "title": "Onboarding", "designs": ["01-onboarding/01-welcome.html", "01-onboarding/02-sign-up.html"] },
196
+ { "title": "Checkout · round 2", "folder": "03-checkout" },
175
197
  {
176
- "id": "screens",
177
- "name": "Screen Layouts",
178
- "description": "Full page layouts across mobile and desktop viewports",
179
- "artboards": [
180
- {
181
- "id": "desktop-dashboard",
182
- "title": "Desktop Dashboard",
183
- "file": "artboards/desktop.html",
184
- "width": 1280,
185
- "height": 820,
186
- "category": "desktop",
187
- "tags": ["desktop", "dashboard", "analytics"]
188
- },
189
- {
190
- "id": "mobile-app",
191
- "title": "Mobile App Feed",
192
- "file": "artboards/mobile.html",
193
- "width": 390,
194
- "height": 844,
195
- "category": "mobile",
196
- "tags": ["mobile", "ios", "finance"]
197
- }
198
- ]
199
- },
200
- {
201
- "id": "components",
202
- "name": "Component Library",
203
- "artboards": [
204
- {
205
- "id": "ui-primitives",
206
- "title": "UI Kit Elements",
207
- "file": "artboards/components.html",
208
- "width": 780,
209
- "height": 720,
210
- "category": "components",
211
- "tags": ["design-system", "buttons", "inputs"]
212
- }
213
- ]
198
+ "title": "Print",
199
+ "description": "Flyers for the launch",
200
+ "designs": [{ "file": "flyer.html", "title": "Launch flyer", "height": 1350 }]
214
201
  }
215
202
  ]
216
203
  }
217
204
  ```
218
205
 
206
+ - 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
+ - A file's variants follow it unless designs.json lists them. `"folder"` brings a whole folder (in prefix order).
208
+ - **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.
209
+ - `name` and `artboards` are synonyms of `title` and `designs`.
210
+
211
+ ### Moving an existing `designs.json` into the files
212
+
213
+ ```bash
214
+ npx htmlboard migrate design # shows what it would do, and checks the canvas stays the same
215
+ npx htmlboard migrate design --write # does it
216
+ ```
217
+
218
+ It writes each file's size, category, label, variants and description as tags after its `<title>`, and leaves designs.json with the sections, their titles and order, and the values the files can't say (a name that isn't the file's `<title>` plus a label, say).
219
+
219
220
  ---
220
221
 
221
222
  ## ⌨️ Keyboard Shortcuts
@@ -232,10 +233,11 @@ The full form adds ids, descriptions (shown in the list), categories and tags (s
232
233
  | <kbd>←</kbd> / <kbd>→</kbd> | Previous / next design |
233
234
  | <kbd>Enter</kbd> | Open the selected design on its own |
234
235
  | <kbd>/</kbd> | Search (<kbd>Enter</kbd> fits the first match, <kbd>Esc</kbd> clears) |
236
+ | <kbd>1</kbd> – <kbd>4</kbd> | Show all, mobile, tablet, desktop (<kbd>5</kbd> on: the other categories) |
235
237
  | <kbd>R</kbd> | Reload the designs |
236
238
  | <kbd>\</kbd> | Show or hide the list |
237
239
  | <kbd>?</kbd> | Show the shortcuts |
238
- | <kbd>Esc</kbd> | Deselect |
240
+ | <kbd>Esc</kbd> | Deselect, then clear the search, then the filter |
239
241
 
240
242
  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.
241
243
 
@@ -259,6 +261,15 @@ console.log(`HtmlBoard running at ${instance.url}`);
259
261
  // await instance.close();
260
262
  ```
261
263
 
264
+ The list of designs is there too, as the explorer gets it:
265
+
266
+ ```javascript
267
+ import { loadDesignsManifest } from 'htmlboard';
268
+
269
+ const { sections } = loadDesignsManifest('./designs');
270
+ // → [{ id: 'screens', name: 'screens', artboards: [{ id, title, file, width, height, category }, …] }, …]
271
+ ```
272
+
262
273
  The skill installer is exported too:
263
274
 
264
275
  ```javascript
@@ -278,7 +289,9 @@ htmlboard/
278
289
  │ └── htmlboard.js # CLI executable
279
290
  ├── src/
280
291
  │ ├── index.js # Library entry point & API exports
281
- │ ├── server.js # HTTP server, SSE live reload, auto-discovery
292
+ │ ├── server.js # HTTP server, SSE live reload
293
+ │ ├── designs.js # Which designs a folder holds: files, their tags, designs.json
294
+ │ ├── migrate.js # `htmlboard migrate`
282
295
  │ ├── skills.js # `htmlboard skills` installer
283
296
  │ ├── client/ # The canvas
284
297
  │ │ ├── index.html # Explorer shell: list, canvas, toolbar, shortcuts
@@ -286,9 +299,10 @@ htmlboard/
286
299
  │ │ ├── explorer.js # Layout, pan/zoom, search, live reload
287
300
  │ │ ├── favicon.svg, logo.svg, mark.svg
288
301
  │ │ └── fonts/ # Inter and Bricolage Grotesque (OFL)
289
- │ └── templates/ # Starter scaffold templates
290
- │ ├── designs.json # Starter manifest
291
- │ └── artboards/ # Sample mobile, desktop, & component screens
302
+ │ └── templates/ # The sample designs `--init` adds
303
+ │ ├── styles.css # Their shared tokens and components
304
+ │ ├── 01-screens/ # Desktop dashboard, mobile feed
305
+ │ └── 02-components/
292
306
  ├── skills/
293
307
  │ └── htmlboard/ # Agent skill (SKILL.md) copied by `htmlboard skills install`
294
308
  ├── brand/ # The logo's files (from design/tools/logo.py)
package/bin/htmlboard.js CHANGED
@@ -6,6 +6,7 @@ import { exec } from 'node:child_process';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { startServer } from '../src/server.js';
8
8
  import { installSkills, uninstallSkills } from '../src/skills.js';
9
+ import { planMigration, checkMigration, applyMigration } from '../src/migrate.js';
9
10
 
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
@@ -74,6 +75,7 @@ Usage:
74
75
  htmlboard [directory] [options]
75
76
  npx htmlboard [directory] [options]
76
77
  htmlboard skills <install|uninstall> [options] Teach your AI agent to use HtmlBoard
78
+ htmlboard migrate [directory] [--write] Move designs.json's sizes, titles and variants into the files
77
79
 
78
80
  Options:
79
81
  -p, --port <number> Port to listen on (default: 3000)
@@ -81,7 +83,7 @@ Options:
81
83
  -r, --root <path> Serve files from this parent folder, so artboards can link to
82
84
  shared files outside the designs folder (default: the designs folder)
83
85
  -o, --open Open the explorer automatically in your default browser
84
- --init Scaffold starter designs.json and sample artboards in target directory
86
+ --init Add sample designs (two folders of artboards) to the directory
85
87
  -v, --version Display the version
86
88
  -h, --help Display this help message
87
89
 
@@ -90,7 +92,7 @@ Examples:
90
92
  htmlboard --open # Run and launch browser automatically
91
93
  htmlboard ./designs -p 8080 # Serve ./designs on port 8080
92
94
  htmlboard design --root . # Artboards in ./design that link to ../css/…
93
- htmlboard --init # Initialize starter templates in current folder
95
+ htmlboard --init # Add sample designs to the current folder
94
96
  htmlboard skills install # Add the HtmlBoard skill to this project's agents
95
97
 
96
98
  On the canvas:
@@ -98,6 +100,7 @@ On the canvas:
98
100
  Ctrl/Cmd + scroll Zoom (or pinch)
99
101
  Double-click Fit a design (Shift + 1 fits everything)
100
102
  / Search
103
+ 1 – 4 Show all, mobile, tablet, desktop
101
104
  ? All the shortcuts
102
105
  `);
103
106
  }
@@ -118,42 +121,25 @@ function openBrowser(url) {
118
121
 
119
122
  function scaffoldProject(targetDir) {
120
123
  const templatesDir = path.join(__dirname, '..', 'src', 'templates');
121
- const targetConfig = path.join(targetDir, 'designs.json');
122
- const targetArtboardsDir = path.join(targetDir, 'artboards');
123
-
124
- console.log(`[htmlboard] Scaffolding starter project in ${targetDir}...`);
125
-
126
- if (!fs.existsSync(targetDir)) {
127
- fs.mkdirSync(targetDir, { recursive: true });
128
- }
129
-
130
- if (!fs.existsSync(targetArtboardsDir)) {
131
- fs.mkdirSync(targetArtboardsDir, { recursive: true });
132
- }
133
-
134
- // Copy designs.json if not present
135
- if (!fs.existsSync(targetConfig)) {
136
- const srcConfig = path.join(templatesDir, 'designs.json');
137
- if (fs.existsSync(srcConfig)) {
138
- fs.copyFileSync(srcConfig, targetConfig);
139
- console.log(` ✓ Created designs.json`);
140
- }
141
- } else {
142
- console.log(` ℹ designs.json already exists (skipped)`);
143
- }
144
-
145
- // Copy starter artboards
146
- const starterFiles = ['mobile.html', 'desktop.html', 'components.html'];
147
- for (const file of starterFiles) {
148
- const srcFile = path.join(templatesDir, 'artboards', file);
149
- const destFile = path.join(targetArtboardsDir, file);
150
- if (!fs.existsSync(destFile) && fs.existsSync(srcFile)) {
151
- fs.copyFileSync(srcFile, destFile);
152
- console.log(` ✓ Created artboards/${file}`);
124
+ console.log(`[htmlboard] Adding sample designs to ${targetDir}...`);
125
+ // Folders are sections and number prefixes the order; each file says its own size
126
+ const copy = (from, rel) => {
127
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
128
+ const relPath = path.join(rel, entry.name);
129
+ const dest = path.join(targetDir, relPath);
130
+ if (entry.isDirectory()) {
131
+ fs.mkdirSync(dest, { recursive: true });
132
+ copy(path.join(from, entry.name), relPath);
133
+ } else if (fs.existsSync(dest)) {
134
+ console.log(` ℹ ${relPath} already exists (skipped)`);
135
+ } else {
136
+ fs.copyFileSync(path.join(from, entry.name), dest);
137
+ console.log(` ✓ Created ${relPath}`);
138
+ }
153
139
  }
154
- }
155
-
156
- console.log(`\n🎉 Starter templates created successfully!`);
140
+ };
141
+ fs.mkdirSync(targetDir, { recursive: true });
142
+ copy(templatesDir, '');
157
143
  }
158
144
 
159
145
  function showSkillsHelp() {
@@ -220,6 +206,58 @@ function runSkills(args) {
220
206
  }
221
207
  }
222
208
 
209
+ function runMigrate(args) {
210
+ const write = args.includes('--write');
211
+ const dirArg = args.find((arg) => !arg.startsWith('-'));
212
+ if (args.includes('--help') || args.includes('-h')) {
213
+ console.log(`
214
+ Usage:
215
+ htmlboard migrate [directory] [--write]
216
+
217
+ Moves what designs.json says about each design into the design's own file (its size, category,
218
+ label, variants and description, as <meta> tags after its <title>) and leaves designs.json with
219
+ the sections, their titles and order, and the values that differ from the files'.
220
+
221
+ Without --write it only shows what it would do, and checks that the canvas stays the same.
222
+ `);
223
+ return;
224
+ }
225
+ const dir = path.resolve(dirArg || process.cwd());
226
+ let plan;
227
+ try {
228
+ plan = planMigration(dir);
229
+ } catch (err) {
230
+ console.error(`[htmlboard] Can't read ${path.join(dir, 'designs.json')}: ${err.message}`);
231
+ process.exit(1);
232
+ }
233
+ const differences = checkMigration(dir, plan);
234
+ const [before, after] = plan.entries;
235
+
236
+ console.log(`\n \x1b[1m${path.relative(process.cwd(), dir) || '.'}\x1b[0m`);
237
+ if (!plan.files.length) console.log(' \x1b[90mNo file to change.\x1b[0m');
238
+ for (const { file, lines } of plan.files) {
239
+ console.log(`\n ${file}`);
240
+ for (const line of lines) console.log(` \x1b[32m+\x1b[0m ${line}`);
241
+ }
242
+ console.log(`\n designs.json: ${before} entries → ${after}`);
243
+ if (differences.length) {
244
+ console.log(`\n \x1b[33mThe canvas would change:\x1b[0m`);
245
+ for (const { before: was, after: will } of differences.slice(0, 20)) {
246
+ if (was) console.log(` \x1b[31m-\x1b[0m ${was}`);
247
+ if (will) console.log(` \x1b[32m+\x1b[0m ${will}`);
248
+ }
249
+ } else {
250
+ console.log(` \x1b[32m✓\x1b[0m The canvas stays the same.`);
251
+ }
252
+
253
+ if (!write) {
254
+ console.log(`\n Nothing written. Run again with --write to apply.\n`);
255
+ return;
256
+ }
257
+ applyMigration(dir, plan);
258
+ console.log(`\n \x1b[32m✓ Written:\x1b[0m ${plan.files.length} file${plan.files.length === 1 ? '' : 's'} and designs.json.\n`);
259
+ }
260
+
223
261
  async function main() {
224
262
  const args = process.argv.slice(2);
225
263
 
@@ -228,6 +266,11 @@ async function main() {
228
266
  return;
229
267
  }
230
268
 
269
+ if (args[0] === 'migrate') {
270
+ runMigrate(args.slice(1));
271
+ return;
272
+ }
273
+
231
274
  const options = parseArgs(args);
232
275
 
233
276
  if (options.help) {
@@ -246,21 +289,15 @@ async function main() {
246
289
  scaffoldProject(targetDir);
247
290
  }
248
291
 
249
- // If directory is empty, auto-prompt or auto-scaffold
292
+ // An empty folder (or a new one) gets the sample designs
250
293
  let entries = [];
251
294
  try {
252
295
  entries = fs.readdirSync(targetDir);
253
296
  } catch (_) {
254
297
  fs.mkdirSync(targetDir, { recursive: true });
255
298
  }
256
-
257
- const hasHtml = entries.some((e) => e.endsWith('.html')) ||
258
- (fs.existsSync(path.join(targetDir, 'artboards')) &&
259
- fs.readdirSync(path.join(targetDir, 'artboards')).some((e) => e.endsWith('.html')));
260
- const hasDesignsJson = fs.existsSync(path.join(targetDir, 'designs.json'));
261
-
262
- if (!hasHtml && !hasDesignsJson && entries.filter((e) => !e.startsWith('.')).length === 0) {
263
- console.log(`[htmlboard] Directory is empty. Initializing starter artboards...`);
299
+ if (!options.init && !entries.some((e) => !e.startsWith('.'))) {
300
+ console.log(`[htmlboard] Directory is empty. Adding sample designs...`);
264
301
  scaffoldProject(targetDir);
265
302
  }
266
303
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmlboard",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Infinite canvas and design explorer for HTML artboards — designed for humans and LLMs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,7 +5,7 @@ description: Design UI in plain HTML files and show them side by side on an infi
5
5
 
6
6
  # HtmlBoard
7
7
 
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, so you design by writing HTML and CSS, and the user sees the result update live while you work.
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
10
  ## Start the canvas
11
11
 
@@ -16,24 +16,26 @@ npx htmlboard designs --open
16
16
  ```
17
17
 
18
18
  - It prints the URL (default `http://localhost:3000`; if the port is busy it takes the next free one). Give the URL to the user.
19
- - `-p <port>` picks the port. `--init` adds three sample artboards and a `designs.json`; skip it unless the user wants a starter.
19
+ - `-p <port>` picks the port. `--init` adds sample designs; skip it unless the user wants a starter.
20
20
  - If the artboards link to files above the designs folder (`../css/theme.css`), serve from the project root: `npx htmlboard design --root .`
21
21
  - An empty folder gets the samples automatically, so write your first artboard before starting the server.
22
22
  - If a server is already running for this folder, reuse it. `GET /api/health` answers `{"status":"ok","dir":…}`.
23
23
 
24
24
  ## Write an artboard
25
25
 
26
- One design per file. Keep each file self-contained so it renders on its own:
26
+ One design per file, with its facts in the `<head>`, linking the shared stylesheet (see below):
27
27
 
28
28
  ```html
29
29
  <!DOCTYPE html>
30
30
  <html lang="en">
31
31
  <head>
32
32
  <meta charset="UTF-8">
33
- <title>Checkout · variant B</title> <!-- the artboard's title on the canvas -->
33
+ <title>Checkout · one page</title> <!-- its name on the canvas -->
34
+ <meta name="description" content="Address and payment on one screen">
35
+ <meta name="htmlboard" content="width=390, height=844"> <!-- its size -->
36
+ <link rel="stylesheet" href="../styles.css"> <!-- shared tokens and components -->
34
37
  <style>
35
- body { margin: 0; }
36
- .screen { width: 390px; min-height: 844px; /* … */ }
38
+ .screen { height: 844px; /* only what this design needs */ }
37
39
  </style>
38
40
  </head>
39
41
  <body>
@@ -42,54 +44,57 @@ One design per file. Keep each file self-contained so it renders on its own:
42
44
  </html>
43
45
  ```
44
46
 
47
+ - 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.
45
48
  - 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).
46
49
  - Use real content in the user's language, not lorem ipsum.
47
- - Relative links to shared CSS, images, scripts and fonts work (`assets/logo.svg`; `../css/theme.css` with `--root`). Reuse the project's own stylesheet and tokens when there is one.
50
+ - Relative links to CSS, images, scripts and fonts work (`assets/logo.svg`; `../css/theme.css` with `--root`).
48
51
  - No build step: plain HTML, CSS and browser JavaScript (ES modules and CDN scripts work).
49
52
 
50
- ## How the canvas lays out files
53
+ ## Shared styles
51
54
 
52
- **Without `designs.json`** (zero config), every `.html` under the folder is an artboard:
55
+ 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.
53
56
 
54
- - Each subfolder is a section (row); files at the top level go to "Root Designs".
55
- - The size comes from words in the file's path: `mobile`, `phone`, `iphone` → 390×844; `tablet`, `ipad` → 768×1024; `component`, `ui`, `atomic` → 720×600; `desktop`, `laptop`, `screen` → 1280×800; anything else → 1200×800. Name files accordingly, e.g. `checkout/mobile-variant-b.html`.
56
- - Hidden folders, `node_modules`, `dist` and `build` are skipped.
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
+ - 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
+ - Link it with a relative path: `styles.css` from the top of the folder, `../styles.css` from a section folder.
60
+ - 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
+ - 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.
57
63
 
58
- **With `designs.json`** in the designs folder, the manifest replaces discovery completely: a file not listed in it doesn't show. Use it to choose order, sizes and grouping:
64
+ More tags, all optional:
65
+
66
+ - `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.
67
+ - `<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.
68
+ - `<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
+ - `<meta name="htmlboard" content="skip">` leaves a file out (partials, templates, a page that isn't a design).
70
+
71
+ ## Group and order
72
+
73
+ - **A folder is a section** (a row on the canvas), named after the folder. Put related designs in a folder: `designs/03-checkout/`.
74
+ - **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.
75
+ - New files and folders show up on the canvas by themselves; you don't register them anywhere.
76
+
77
+ **`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:
59
78
 
60
79
  ```json
61
80
  {
62
- "title": "Checkout redesign",
63
81
  "sections": [
64
- {
65
- "title": "Checkout · mobile",
66
- "description": "Three directions for the payment step",
67
- "designs": [
68
- { "file": "checkout/a.html", "title": "A · current", "width": 390, "height": 844 },
69
- { "file": "checkout/b.html", "title": "B · one page", "width": 390, "tags": ["proposal"] },
70
- { "file": "checkout/b.html?theme=dark", "title": "B · dark", "width": 390, "height": 844 }
71
- ]
72
- }
82
+ { "title": "Checkout · mobile", "designs": ["03-checkout/01-one-page.html", "03-checkout/02-steps.html"] },
83
+ { "title": "Launch flyers", "folder": "04-flyers" }
73
84
  ]
74
85
  }
75
86
  ```
76
87
 
77
- - Sections are rows, top to bottom; designs run left to right.
78
- - `file` is relative to the designs folder and may carry a query string, so one file can show several states.
79
- - Omit `height` to let the frame grow to the page's full height (long pages, boards, docs).
80
- - Optional: `description` (shown in the list), `tags` and `category` (searchable), `id` (derived from `file`).
81
- - `name` and `artboards` are accepted as synonyms of a section's `title` and `designs`.
82
-
83
- When the project already has a `designs.json`, add every new file to it.
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).
84
89
 
85
90
  ## Live reload
86
91
 
87
- - Saving an artboard's HTML reloads just that frame; the user's view stays put.
92
+ - 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.
88
93
  - Saving a file artboards use (a stylesheet, a script, an image, data) reloads the artboards that loaded it.
89
- - Saving `designs.json`, or adding or removing artboards, rebuilds the canvas.
94
+ - Adding, removing or moving artboards, or saving `designs.json`, rebuilds the canvas.
90
95
 
91
96
  ## Working with the user
92
97
 
93
- - Put variants in separate files next to each other (`hero-a.html`, `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.
94
- - 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 `.html`, slashes and other symbols turned into `-`: `checkout/b.html` → `checkout-b`).
98
+ - 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
+ - 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`).
95
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.