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 +83 -69
- package/bin/htmlboard.js +83 -46
- package/package.json +1 -1
- package/skills/htmlboard/SKILL.md +39 -34
- 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} +51 -167
- package/src/templates/{artboards/mobile.html → 01-screens/02-mobile.html} +48 -143
- package/src/templates/02-components/01-ui-kit.html +77 -0
- package/src/templates/styles.css +133 -0
- package/src/templates/artboards/components.html +0 -199
- 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, 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
|
|
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,71 @@ 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.
|
|
167
180
|
|
|
168
|
-
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
]
|
|
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
|
|
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/ #
|
|
290
|
-
│ ├──
|
|
291
|
-
│
|
|
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
|
|
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, 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 ·
|
|
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
|
-
|
|
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
|
|
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
|
-
##
|
|
53
|
+
## Shared styles
|
|
51
54
|
|
|
52
|
-
|
|
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
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
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.
|