htmlboard 0.2.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 +80 -69
- package/bin/htmlboard.js +83 -46
- package/package.json +1 -1
- package/skills/htmlboard/SKILL.md +26 -32
- package/src/client/explorer.css +69 -34
- package/src/client/explorer.js +148 -25
- package/src/client/index.html +2 -0
- package/src/designs.js +398 -0
- package/src/index.js +7 -2
- package/src/migrate.js +193 -0
- package/src/server.js +1 -198
- package/src/templates/{artboards/desktop.html → 01-screens/01-desktop.html} +2 -0
- package/src/templates/{artboards/mobile.html → 01-screens/02-mobile.html} +2 -0
- package/src/templates/{artboards/components.html → 02-components/01-ui-kit.html} +2 -0
- package/src/templates/designs.json +0 -50
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
|
-
-
|
|
36
|
-
- 📐 **
|
|
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
|
|
62
|
+
To start with sample designs (a folder of screens and a folder of components, each file describing itself):
|
|
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
|
|
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
|
-
#
|
|
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
|
|
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 `
|
|
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,69 @@ Running `install` again after upgrading HtmlBoard updates the skill; a copy you
|
|
|
144
152
|
|
|
145
153
|
---
|
|
146
154
|
|
|
147
|
-
##
|
|
155
|
+
## 📂 How Designs Are Found
|
|
148
156
|
|
|
149
|
-
|
|
157
|
+
Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist` and `build` aside):
|
|
150
158
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
+
---
|
|
184
|
+
|
|
185
|
+
## 📋 `designs.json` (Optional)
|
|
167
186
|
|
|
168
|
-
|
|
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:
|
|
169
188
|
|
|
170
189
|
```json
|
|
171
190
|
{
|
|
172
|
-
"title": "
|
|
173
|
-
"description": "Exploration of screen flows, responsive layouts, and UI components",
|
|
191
|
+
"title": "Wallet app",
|
|
174
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" },
|
|
175
195
|
{
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
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
|
-
]
|
|
196
|
+
"title": "Print",
|
|
197
|
+
"description": "Flyers for the launch",
|
|
198
|
+
"designs": [{ "file": "flyer.html", "title": "Launch flyer", "height": 1350 }]
|
|
214
199
|
}
|
|
215
200
|
]
|
|
216
201
|
}
|
|
217
202
|
```
|
|
218
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
|
+
|
|
219
218
|
---
|
|
220
219
|
|
|
221
220
|
## ⌨️ Keyboard Shortcuts
|
|
@@ -232,10 +231,11 @@ The full form adds ids, descriptions (shown in the list), categories and tags (s
|
|
|
232
231
|
| <kbd>←</kbd> / <kbd>→</kbd> | Previous / next design |
|
|
233
232
|
| <kbd>Enter</kbd> | Open the selected design on its own |
|
|
234
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
235
|
| <kbd>R</kbd> | Reload the designs |
|
|
236
236
|
| <kbd>\</kbd> | Show or hide the list |
|
|
237
237
|
| <kbd>?</kbd> | Show the shortcuts |
|
|
238
|
-
| <kbd>Esc</kbd> | Deselect |
|
|
238
|
+
| <kbd>Esc</kbd> | Deselect, then clear the search, then the filter |
|
|
239
239
|
|
|
240
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.
|
|
241
241
|
|
|
@@ -259,6 +259,15 @@ console.log(`HtmlBoard running at ${instance.url}`);
|
|
|
259
259
|
// await instance.close();
|
|
260
260
|
```
|
|
261
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
|
+
|
|
262
271
|
The skill installer is exported too:
|
|
263
272
|
|
|
264
273
|
```javascript
|
|
@@ -278,7 +287,9 @@ htmlboard/
|
|
|
278
287
|
│ └── htmlboard.js # CLI executable
|
|
279
288
|
├── src/
|
|
280
289
|
│ ├── index.js # Library entry point & API exports
|
|
281
|
-
│ ├── server.js # HTTP server, SSE live reload
|
|
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`
|
|
282
293
|
│ ├── skills.js # `htmlboard skills` installer
|
|
283
294
|
│ ├── client/ # The canvas
|
|
284
295
|
│ │ ├── index.html # Explorer shell: list, canvas, toolbar, shortcuts
|
|
@@ -286,9 +297,9 @@ htmlboard/
|
|
|
286
297
|
│ │ ├── explorer.js # Layout, pan/zoom, search, live reload
|
|
287
298
|
│ │ ├── favicon.svg, logo.svg, mark.svg
|
|
288
299
|
│ │ └── fonts/ # Inter and Bricolage Grotesque (OFL)
|
|
289
|
-
│ └── templates/ #
|
|
290
|
-
│ ├──
|
|
291
|
-
│ └──
|
|
300
|
+
│ └── templates/ # The sample designs `--init` adds
|
|
301
|
+
│ ├── 01-screens/ # Desktop dashboard, mobile feed
|
|
302
|
+
│ └── 02-components/
|
|
292
303
|
├── skills/
|
|
293
304
|
│ └── htmlboard/ # Agent skill (SKILL.md) copied by `htmlboard skills install`
|
|
294
305
|
├── 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
|
|
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 #
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
@@ -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
|
|
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
|
|
26
|
+
One design per file, self-contained so it renders on its own, with its facts in the `<head>`:
|
|
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 ·
|
|
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 -->
|
|
34
36
|
<style>
|
|
35
37
|
body { margin: 0; }
|
|
36
|
-
.screen { width: 390px;
|
|
38
|
+
.screen { width: 390px; height: 844px; /* … */ }
|
|
37
39
|
</style>
|
|
38
40
|
</head>
|
|
39
41
|
<body>
|
|
@@ -42,54 +44,46 @@ 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
50
|
- 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.
|
|
48
51
|
- No build step: plain HTML, CSS and browser JavaScript (ES modules and CDN scripts work).
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
More tags, all optional:
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
- `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.
|
|
56
|
+
- `<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.
|
|
57
|
+
- `<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.
|
|
58
|
+
- `<meta name="htmlboard" content="skip">` leaves a file out (partials, templates, a page that isn't a design).
|
|
53
59
|
|
|
54
|
-
|
|
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.
|
|
60
|
+
## Group and order
|
|
57
61
|
|
|
58
|
-
**
|
|
62
|
+
- **A folder is a section** (a row on the canvas), named after the folder. Put related designs in a folder: `designs/03-checkout/`.
|
|
63
|
+
- **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.
|
|
64
|
+
- New files and folders show up on the canvas by themselves; you don't register them anywhere.
|
|
65
|
+
|
|
66
|
+
**`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
67
|
|
|
60
68
|
```json
|
|
61
69
|
{
|
|
62
|
-
"title": "Checkout redesign",
|
|
63
70
|
"sections": [
|
|
64
|
-
{
|
|
65
|
-
|
|
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
|
-
}
|
|
71
|
+
{ "title": "Checkout · mobile", "designs": ["03-checkout/01-one-page.html", "03-checkout/02-steps.html"] },
|
|
72
|
+
{ "title": "Launch flyers", "folder": "04-flyers" }
|
|
73
73
|
]
|
|
74
74
|
}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
|
|
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.
|
|
77
|
+
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
78
|
|
|
85
79
|
## Live reload
|
|
86
80
|
|
|
87
|
-
- Saving an artboard's HTML reloads just that frame; the user's view stays put.
|
|
81
|
+
- 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
82
|
- Saving a file artboards use (a stylesheet, a script, an image, data) reloads the artboards that loaded it.
|
|
89
|
-
-
|
|
83
|
+
- Adding, removing or moving artboards, or saving `designs.json`, rebuilds the canvas.
|
|
90
84
|
|
|
91
85
|
## Working with the user
|
|
92
86
|
|
|
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
|
|
87
|
+
- 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.
|
|
88
|
+
- 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
89
|
- 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.
|