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 +120 -92
- package/bin/htmlboard.js +93 -59
- package/package.json +2 -2
- package/skills/htmlboard/SKILL.md +27 -33
- package/src/client/explorer.css +302 -598
- package/src/client/explorer.js +983 -601
- package/src/client/favicon.svg +1 -0
- package/src/client/fonts/OFL-bricolage-grotesque.txt +93 -0
- package/src/client/fonts/OFL-inter.txt +93 -0
- package/src/client/fonts/bricolage-grotesque.woff2 +0 -0
- package/src/client/fonts/inter.woff2 +0 -0
- package/src/client/index.html +87 -175
- package/src/client/logo.svg +1 -0
- package/src/client/mark.svg +1 -0
- package/src/designs.js +398 -0
- package/src/index.js +8 -2
- package/src/migrate.js +193 -0
- package/src/server.js +93 -212
- 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
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
|
|
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
|
-
[](https://www.npmjs.com/package/htmlboard)
|
|
11
|
+
[](LICENSE)
|
|
7
12
|
[](https://nodejs.org)
|
|
8
13
|
[](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**:
|
|
28
|
-
- ⚡ **Zero Dependencies**: Pure native Node.js ES modules. Runs instantly with `npx htmlboard
|
|
29
|
-
- 🔄 **State-Preserving Live Reload**:
|
|
30
|
-
-
|
|
31
|
-
- 📐 **
|
|
32
|
-
- 📱 **
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
|
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 `
|
|
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
|
-
##
|
|
155
|
+
## 📂 How Designs Are Found
|
|
143
156
|
|
|
144
|
-
|
|
157
|
+
Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist` and `build` aside):
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
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> +
|
|
221
|
-
| <kbd>
|
|
222
|
-
| <kbd
|
|
223
|
-
| <kbd>
|
|
224
|
-
| <kbd>
|
|
225
|
-
| <kbd
|
|
226
|
-
| <kbd>
|
|
227
|
-
| <kbd
|
|
228
|
-
| <kbd
|
|
229
|
-
| <kbd>
|
|
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
|
|
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
|
|
273
|
-
│ │ ├── index.html # Explorer shell
|
|
274
|
-
│ │ ├── explorer.css # Dark
|
|
275
|
-
│ │
|
|
276
|
-
│
|
|
277
|
-
│
|
|
278
|
-
│
|
|
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
|
-
|
|
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
|
|
85
|
-
-v, --version Display
|
|
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 #
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
Ctrl/Cmd +
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
279
|
-
\x1b[90mInfinite
|
|
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.
|
|
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
|
|
48
|
+
"node": ">=18"
|
|
49
49
|
}
|
|
50
50
|
}
|