htmlboard 0.1.0 → 0.3.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
@@ -1,9 +1,14 @@
1
- # ✦ HtmlBoard
1
+ <h1>
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/luizcarlos1405/htmlboard/main/brand/logo-dark.svg">
4
+ <img alt="htmlboard" src="https://raw.githubusercontent.com/luizcarlos1405/htmlboard/main/brand/logo-light.svg" height="56">
5
+ </picture>
6
+ </h1>
2
7
 
3
8
  > **Infinite canvas and design explorer for HTML artboards — designed for humans and LLMs.**
4
9
 
5
- [![npm version](https://img.shields.io/npm/v/htmlboard.svg?style=flat-square&color=6366f1)](https://www.npmjs.com/package/htmlboard)
6
- [![license](https://img.shields.io/npm/l/htmlboard.svg?style=flat-square&color=10b981)](LICENSE)
10
+ [![npm version](https://img.shields.io/npm/v/htmlboard.svg?style=flat-square&color=3D9DFF)](https://www.npmjs.com/package/htmlboard)
11
+ [![license](https://img.shields.io/npm/l/htmlboard.svg?style=flat-square&color=48484D)](LICENSE)
7
12
  [![node version](https://img.shields.io/node/v/htmlboard.svg?style=flat-square)](https://nodejs.org)
8
13
  [![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg?style=flat-square)](package.json)
9
14
 
@@ -24,15 +29,16 @@ Modern AI agents and developers generate dozens of HTML mockups, layout variatio
24
29
 
25
30
  ## ✨ Features
26
31
 
27
- - 🛸 **Infinite Canvas**: Smoothly pan, zoom (from 5% to 300%), and fly across dozens of artboards with trackpad or mouse.
28
- - ⚡ **Zero Dependencies**: Pure native Node.js ES modules. Runs instantly with `npx htmlboard` without installing heavy packages.
29
- - 🔄 **State-Preserving Live Reload**: Automatically watches files via Server-Sent Events (SSE). When you or an LLM edit an HTML file, the corresponding artboard updates instantly without resetting your canvas pan/zoom position; editing a shared CSS, script or image reloads them all.
30
- - 🔍 **Auto-Discovery**: Point HtmlBoard at any folder. It automatically discovers all HTML files, extracts their titles, classifies them by device type, and renders them onto the canvas.
31
- - 📐 **Declarative Manifest (`designs.json`)**: Optional structured schema to organize artboards into sections, define custom viewports (iPhone, iPad, MacBook, 4K), and attach metadata.
32
- - 📱 **Multi-Device Categories**: One-click filters for Mobile (390×844), Desktop (1280×820), Tablet (768×1024), and Component kits.
33
- - 🎯 **Focus & Zoom-to-Fit**: Instantly center any artboard with a click in the sidebar, or hit <kbd>F</kbd> to fit all designs on screen.
34
- - 🔗 **Deep Links**: The URL follows the focused artboard (`http://localhost:3000/#checkout-b`), so you can share or reload a view of one design.
35
- - ⌨️ **Keyboard-First Workflow**: Full suite of intuitive shortcuts matching design industry standards.
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
+ - ⚡ **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.
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.
38
+ - 🔎 **Search**: Find designs by name, file, tags or section (case and accents don't matter); the others fade out in place.
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.
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.
41
+ - ⌨️ **Keyboard-First Workflow**: Figma-like shortcuts; <kbd>?</kbd> lists them.
36
42
 
37
43
  ---
38
44
 
@@ -53,7 +59,7 @@ htmlboard
53
59
 
54
60
  ### Scaffold a Starter Project
55
61
 
56
- 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):
57
63
 
58
64
  ```bash
59
65
  npx htmlboard --init
@@ -68,6 +74,7 @@ Usage:
68
74
  htmlboard [directory] [options]
69
75
  npx htmlboard [directory] [options]
70
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
71
78
 
72
79
  Options:
73
80
  -p, --port <number> Port to listen on (default: 3000)
@@ -75,7 +82,7 @@ Options:
75
82
  -r, --root <path> Serve files from this parent folder, so artboards can link to
76
83
  shared files outside the designs folder (default: the designs folder)
77
84
  -o, --open Open the explorer automatically in default browser
78
- --init Scaffold starter designs.json and sample artboards
85
+ --init Add sample designs (two folders of artboards)
79
86
  -v, --version Display HtmlBoard version
80
87
  -h, --help Display help message
81
88
  ```
@@ -95,8 +102,12 @@ htmlboard ./src/designs -p 8080
95
102
  # Artboards in ./design that link to the site's own ../css and ../js
96
103
  htmlboard design --root .
97
104
 
98
- # Initialize sample templates in a new folder
105
+ # Start a new folder with sample designs
99
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
100
111
  ```
101
112
 
102
113
  ---
@@ -107,7 +118,7 @@ HtmlBoard is built from the ground up to pair with AI coding agents (Claude, Gem
107
118
 
108
119
  ### Install the agent skill
109
120
 
110
- 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:
111
122
 
112
123
  ```bash
113
124
  npx htmlboard skills install
@@ -127,7 +138,9 @@ Running `install` again after upgrading HtmlBoard updates the skill; a copy you
127
138
  ### The workflow
128
139
 
129
140
  1. **Ask your LLM to generate artboards:**
130
- > *"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.
131
144
 
132
145
  2. **Run HtmlBoard in the background:**
133
146
  ```bash
@@ -139,94 +152,92 @@ Running `install` again after upgrading HtmlBoard updates the skill; a copy you
139
152
 
140
153
  ---
141
154
 
142
- ## 📋 The `designs.json` Manifest
155
+ ## 📂 How Designs Are Found
143
156
 
144
- 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):
145
158
 
146
- ```json
147
- {
148
- "sections": [
149
- {
150
- "title": "Home",
151
- "designs": [
152
- { "file": "home-mobile.html", "title": "Home · phone", "width": 390, "height": 844 },
153
- { "file": "home-mobile.html?theme=dark", "title": "Home · phone · dark", "width": 390, "height": 844 },
154
- { "file": "home-desktop.html", "title": "Home · desktop", "width": 1440 }
155
- ]
156
- }
157
- ]
158
- }
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" -->
159
169
  ```
160
170
 
161
- 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.
180
+
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
+ ---
162
184
 
163
- The full form adds ids, descriptions, categories (for the toolbar filters) and searchable tags (`name`/`artboards` are synonyms of `title`/`designs`):
185
+ ## 📋 `designs.json` (Optional)
186
+
187
+ 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:
164
188
 
165
189
  ```json
166
190
  {
167
- "title": "Fintech Mobile & Web App",
168
- "description": "Exploration of screen flows, responsive layouts, and UI components",
191
+ "title": "Wallet app",
169
192
  "sections": [
193
+ { "title": "Onboarding", "designs": ["01-onboarding/01-welcome.html", "01-onboarding/02-sign-up.html"] },
194
+ { "title": "Checkout · round 2", "folder": "03-checkout" },
170
195
  {
171
- "id": "screens",
172
- "name": "Screen Layouts",
173
- "description": "Full page layouts across mobile and desktop viewports",
174
- "artboards": [
175
- {
176
- "id": "desktop-dashboard",
177
- "title": "Desktop Dashboard",
178
- "file": "artboards/desktop.html",
179
- "width": 1280,
180
- "height": 820,
181
- "category": "desktop",
182
- "tags": ["desktop", "dashboard", "analytics"]
183
- },
184
- {
185
- "id": "mobile-app",
186
- "title": "Mobile App Feed",
187
- "file": "artboards/mobile.html",
188
- "width": 390,
189
- "height": 844,
190
- "category": "mobile",
191
- "tags": ["mobile", "ios", "finance"]
192
- }
193
- ]
194
- },
195
- {
196
- "id": "components",
197
- "name": "Component Library",
198
- "artboards": [
199
- {
200
- "id": "ui-primitives",
201
- "title": "UI Kit Elements",
202
- "file": "artboards/components.html",
203
- "width": 780,
204
- "height": 720,
205
- "category": "components",
206
- "tags": ["design-system", "buttons", "inputs"]
207
- }
208
- ]
196
+ "title": "Print",
197
+ "description": "Flyers for the launch",
198
+ "designs": [{ "file": "flyer.html", "title": "Launch flyer", "height": 1350 }]
209
199
  }
210
200
  ]
211
201
  }
212
202
  ```
213
203
 
204
+ - 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.
205
+ - A file's variants follow it unless designs.json lists them. `"folder"` brings a whole folder (in prefix order).
206
+ - **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.
207
+ - `name` and `artboards` are synonyms of `title` and `designs`.
208
+
209
+ ### Moving an existing `designs.json` into the files
210
+
211
+ ```bash
212
+ npx htmlboard migrate design # shows what it would do, and checks the canvas stays the same
213
+ npx htmlboard migrate design --write # does it
214
+ ```
215
+
216
+ 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).
217
+
214
218
  ---
215
219
 
216
220
  ## ⌨️ Keyboard Shortcuts
217
221
 
218
222
  | Shortcut | Action |
219
223
  | :--- | :--- |
220
- | <kbd>Space</kbd> + <kbd>Drag</kbd> | Pan the canvas freely |
221
- | <kbd>Ctrl</kbd> / <kbd>⌘</kbd> + <kbd>Wheel</kbd> | Zoom in/out anchored to cursor |
222
- | <kbd>+</kbd> / <kbd>-</kbd> | Zoom in / Zoom out |
223
- | <kbd>0</kbd> | Reset zoom to 100% |
224
- | <kbd>F</kbd> or <kbd>Shift</kbd> + <kbd>1</kbd> | Fit all artboards in viewport |
225
- | <kbd>/</kbd> or <kbd>⌘</kbd> + <kbd>K</kbd> | Search artboards by title, tag, or path |
226
- | <kbd>[</kbd> or <kbd>]</kbd> | Toggle navigation sidebar |
227
- | <kbd>1</kbd> – <kbd>5</kbd> | Quick filter by device category |
228
- | <kbd>?</kbd> | Show keyboard shortcuts modal |
229
- | <kbd>Esc</kbd> | Close modals or blur search |
224
+ | Drag, <kbd>Space</kbd> + drag | Move around |
225
+ | Scroll (<kbd>Shift</kbd>: sideways) | Move around |
226
+ | <kbd>Ctrl</kbd>/<kbd>⌘</kbd> + scroll, pinch | Zoom, anchored to the pointer |
227
+ | <kbd>+</kbd> / <kbd>−</kbd> | Zoom in / out |
228
+ | <kbd>Shift</kbd> + <kbd>0</kbd> | Zoom to 100% |
229
+ | <kbd>Shift</kbd> + <kbd>1</kbd> | Fit everything (what the search keeps) |
230
+ | <kbd>Shift</kbd> + <kbd>2</kbd>, double-click | Fit the design |
231
+ | <kbd>←</kbd> / <kbd>→</kbd> | Previous / next design |
232
+ | <kbd>Enter</kbd> | Open the selected design on its own |
233
+ | <kbd>/</kbd> | Search (<kbd>Enter</kbd> fits the first match, <kbd>Esc</kbd> clears) |
234
+ | <kbd>1</kbd> – <kbd>4</kbd> | Show all, mobile, tablet, desktop (<kbd>5</kbd> on: the other categories) |
235
+ | <kbd>R</kbd> | Reload the designs |
236
+ | <kbd>\</kbd> | Show or hide the list |
237
+ | <kbd>?</kbd> | Show the shortcuts |
238
+ | <kbd>Esc</kbd> | Deselect, then clear the search, then the filter |
239
+
240
+ 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.
230
241
 
231
242
  ---
232
243
 
@@ -248,6 +259,15 @@ console.log(`HtmlBoard running at ${instance.url}`);
248
259
  // await instance.close();
249
260
  ```
250
261
 
262
+ The list of designs is there too, as the explorer gets it:
263
+
264
+ ```javascript
265
+ import { loadDesignsManifest } from 'htmlboard';
266
+
267
+ const { sections } = loadDesignsManifest('./designs');
268
+ // → [{ id: 'screens', name: 'screens', artboards: [{ id, title, file, width, height, category }, …] }, …]
269
+ ```
270
+
251
271
  The skill installer is exported too:
252
272
 
253
273
  ```javascript
@@ -267,17 +287,23 @@ htmlboard/
267
287
  │ └── htmlboard.js # CLI executable
268
288
  ├── src/
269
289
  │ ├── index.js # Library entry point & API exports
270
- │ ├── server.js # HTTP server, SSE live reload, auto-discovery
290
+ │ ├── server.js # HTTP server, SSE live reload
291
+ │ ├── designs.js # Which designs a folder holds: files, their tags, designs.json
292
+ │ ├── migrate.js # `htmlboard migrate`
271
293
  │ ├── skills.js # `htmlboard skills` installer
272
- │ ├── client/ # The infinite canvas frontend
273
- │ │ ├── index.html # Explorer shell & canvas DOM
274
- │ │ ├── explorer.css # Dark theme styles & canvas grid
275
- │ │ └── explorer.js # Pan/zoom engine, SSE listener, auto-measure
276
- │ └── templates/ # Starter scaffold templates
277
- │ ├── designs.json # Starter manifest
278
- │ └── artboards/ # Sample mobile, desktop, & component screens
294
+ │ ├── client/ # The canvas
295
+ │ │ ├── index.html # Explorer shell: list, canvas, toolbar, shortcuts
296
+ │ │ ├── explorer.css # Dark chrome, dot grid
297
+ │ │ ├── explorer.js # Layout, pan/zoom, search, live reload
298
+ │ │ ├── favicon.svg, logo.svg, mark.svg
299
+ │ │ └── fonts/ # Inter and Bricolage Grotesque (OFL)
300
+ │ └── templates/ # The sample designs `--init` adds
301
+ │ ├── 01-screens/ # Desktop dashboard, mobile feed
302
+ │ └── 02-components/
279
303
  ├── skills/
280
304
  │ └── htmlboard/ # Agent skill (SKILL.md) copied by `htmlboard skills install`
305
+ ├── brand/ # The logo's files (from design/tools/logo.py)
306
+ ├── design/ # htmlboard's own designs, on its own canvas: `npx htmlboard design --root .`
281
307
  ├── package.json
282
308
  ├── README.md
283
309
  └── LICENSE
@@ -288,3 +314,5 @@ htmlboard/
288
314
  ## 📄 License
289
315
 
290
316
  MIT © Luiz Carlos & Contributors
317
+
318
+ The explorer ships the Inter and Bricolage Grotesque fonts under the SIL Open Font License 1.1 (see `src/client/fonts/`).
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);
@@ -67,13 +68,14 @@ function parseArgs(args) {
67
68
 
68
69
  function showHelp() {
69
70
  console.log(`
70
- HtmlBoard v${pkg.version}
71
+ htmlboard v${pkg.version}
71
72
  Infinite canvas and design explorer for HTML artboards — designed for humans and LLMs
72
73
 
73
74
  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,8 +83,8 @@ 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
85
- -v, --version Display HtmlBoard version
86
+ --init Add sample designs (two folders of artboards) to the directory
87
+ -v, --version Display the version
86
88
  -h, --help Display this help message
87
89
 
88
90
  Examples:
@@ -90,18 +92,16 @@ 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
- Keyboard Shortcuts in Canvas:
97
- Space + Drag Pan canvas
98
- Ctrl/Cmd + Wheel Zoom into focal point
99
- + / - Zoom in / Zoom out
100
- 0 Reset zoom to 100%
101
- F Zoom to fit all artboards
102
- / or Cmd+K Search artboards
103
- [ or ] Toggle navigation sidebar
104
- ? Show shortcuts help dialog
98
+ On the canvas:
99
+ Drag, scroll Move around
100
+ Ctrl/Cmd + scroll Zoom (or pinch)
101
+ Double-click Fit a design (Shift + 1 fits everything)
102
+ / Search
103
+ 1 – 4 Show all, mobile, tablet, desktop
104
+ ? All the shortcuts
105
105
  `);
106
106
  }
107
107
 
@@ -121,42 +121,25 @@ function openBrowser(url) {
121
121
 
122
122
  function scaffoldProject(targetDir) {
123
123
  const templatesDir = path.join(__dirname, '..', 'src', 'templates');
124
- const targetConfig = path.join(targetDir, 'designs.json');
125
- const targetArtboardsDir = path.join(targetDir, 'artboards');
126
-
127
- console.log(`[htmlboard] Scaffolding starter project in ${targetDir}...`);
128
-
129
- if (!fs.existsSync(targetDir)) {
130
- fs.mkdirSync(targetDir, { recursive: true });
131
- }
132
-
133
- if (!fs.existsSync(targetArtboardsDir)) {
134
- fs.mkdirSync(targetArtboardsDir, { recursive: true });
135
- }
136
-
137
- // Copy designs.json if not present
138
- if (!fs.existsSync(targetConfig)) {
139
- const srcConfig = path.join(templatesDir, 'designs.json');
140
- if (fs.existsSync(srcConfig)) {
141
- fs.copyFileSync(srcConfig, targetConfig);
142
- console.log(` ✓ Created designs.json`);
143
- }
144
- } else {
145
- console.log(` ℹ designs.json already exists (skipped)`);
146
- }
147
-
148
- // Copy starter artboards
149
- const starterFiles = ['mobile.html', 'desktop.html', 'components.html'];
150
- for (const file of starterFiles) {
151
- const srcFile = path.join(templatesDir, 'artboards', file);
152
- const destFile = path.join(targetArtboardsDir, file);
153
- if (!fs.existsSync(destFile) && fs.existsSync(srcFile)) {
154
- fs.copyFileSync(srcFile, destFile);
155
- 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
+ }
156
139
  }
157
- }
158
-
159
- console.log(`\n🎉 Starter templates created successfully!`);
140
+ };
141
+ fs.mkdirSync(targetDir, { recursive: true });
142
+ copy(templatesDir, '');
160
143
  }
161
144
 
162
145
  function showSkillsHelp() {
@@ -223,6 +206,58 @@ function runSkills(args) {
223
206
  }
224
207
  }
225
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
+
226
261
  async function main() {
227
262
  const args = process.argv.slice(2);
228
263
 
@@ -231,6 +266,11 @@ async function main() {
231
266
  return;
232
267
  }
233
268
 
269
+ if (args[0] === 'migrate') {
270
+ runMigrate(args.slice(1));
271
+ return;
272
+ }
273
+
234
274
  const options = parseArgs(args);
235
275
 
236
276
  if (options.help) {
@@ -249,21 +289,15 @@ async function main() {
249
289
  scaffoldProject(targetDir);
250
290
  }
251
291
 
252
- // If directory is empty, auto-prompt or auto-scaffold
292
+ // An empty folder (or a new one) gets the sample designs
253
293
  let entries = [];
254
294
  try {
255
295
  entries = fs.readdirSync(targetDir);
256
296
  } catch (_) {
257
297
  fs.mkdirSync(targetDir, { recursive: true });
258
298
  }
259
-
260
- const hasHtml = entries.some((e) => e.endsWith('.html')) ||
261
- (fs.existsSync(path.join(targetDir, 'artboards')) &&
262
- fs.readdirSync(path.join(targetDir, 'artboards')).some((e) => e.endsWith('.html')));
263
- const hasDesignsJson = fs.existsSync(path.join(targetDir, 'designs.json'));
264
-
265
- if (!hasHtml && !hasDesignsJson && entries.filter((e) => !e.startsWith('.')).length === 0) {
266
- 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...`);
267
301
  scaffoldProject(targetDir);
268
302
  }
269
303
 
@@ -275,8 +309,8 @@ async function main() {
275
309
  });
276
310
 
277
311
  console.log(`
278
- ✦ \x1b[1m\x1b[36mHtmlBoard\x1b[0m \x1b[90mv${pkg.version}\x1b[0m
279
- \x1b[90mInfinite Canvas & Design Explorer for HTML Artboards\x1b[0m
312
+ \x1b[90mhtml\x1b[0m\x1b[1mboard\x1b[0m \x1b[90mv${pkg.version}\x1b[0m
313
+ \x1b[90mInfinite canvas for HTML designs\x1b[0m
280
314
 
281
315
  \x1b[32m➜\x1b[0m \x1b[1mLocal:\x1b[0m \x1b[36m${url}\x1b[0m
282
316
  \x1b[32m➜\x1b[0m \x1b[1mTarget:\x1b[0m \x1b[90m${targetDir}\x1b[0m${rootDir !== targetDir ? `\n \x1b[32m➜\x1b[0m \x1b[1mRoot:\x1b[0m \x1b[90m${rootDir}\x1b[0m` : ''}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmlboard",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Infinite canvas and design explorer for HTML artboards — designed for humans and LLMs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,6 +45,6 @@
45
45
  "url": "https://github.com/luizcarlos1405/htmlboard/issues"
46
46
  },
47
47
  "engines": {
48
- "node": ">=18.0.0"
48
+ "node": ">=18"
49
49
  }
50
50
  }