justskill-design 0.0.1 → 3.2.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.en.md +235 -0
- package/README.md +184 -11
- package/SKILL.md +435 -0
- package/canvas/app.mjs +2870 -0
- package/canvas/groups.mjs +204 -0
- package/canvas/index.html +141 -0
- package/canvas/styles.css +1032 -0
- package/canvas/tree.mjs +249 -0
- package/cordis.patch.yml +8 -0
- package/design.mjs +172 -0
- package/docs/dsh-workbench.png +0 -0
- package/dsh/client.js +864 -0
- package/dsh/index.js +354 -0
- package/dsh/scan.js +139 -0
- package/dsh/skill.js +41 -0
- package/lib/base-css.mjs +92 -0
- package/lib/detect.mjs +123 -0
- package/lib/diagnostics.mjs +382 -0
- package/lib/export-html.mjs +309 -0
- package/lib/export-svg.mjs +111 -0
- package/lib/fragment.mjs +702 -0
- package/lib/geometry.mjs +827 -0
- package/lib/icons.mjs +106 -0
- package/lib/levenshtein.mjs +133 -0
- package/lib/node/cli.mjs +387 -0
- package/lib/node/export-svg.mjs +75 -0
- package/lib/node/fs.mjs +309 -0
- package/lib/node/op-json.mjs +75 -0
- package/lib/node/raster-run.mjs +115 -0
- package/lib/node/server.mjs +1264 -0
- package/lib/node/static.mjs +206 -0
- package/lib/node/verbs/add.mjs +220 -0
- package/lib/node/verbs/export.mjs +161 -0
- package/lib/node/verbs/format.mjs +106 -0
- package/lib/node/verbs/import.mjs +126 -0
- package/lib/node/verbs/ls.mjs +98 -0
- package/lib/node/verbs/migrate.mjs +236 -0
- package/lib/node/verbs/move.mjs +76 -0
- package/lib/node/verbs/new.mjs +284 -0
- package/lib/node/verbs/remove.mjs +41 -0
- package/lib/node/verbs/rename.mjs +87 -0
- package/lib/node/verbs/serve.mjs +164 -0
- package/lib/node/verbs/set.mjs +150 -0
- package/lib/node/verbs/show.mjs +122 -0
- package/lib/node/verbs/tokens.mjs +167 -0
- package/lib/node/verbs/validate.mjs +130 -0
- package/lib/node/write-verbs.mjs +215 -0
- package/lib/normalize.mjs +288 -0
- package/lib/ops.mjs +2063 -0
- package/lib/parse.mjs +997 -0
- package/lib/raster.mjs +186 -0
- package/lib/readers/pen.mjs +1684 -0
- package/lib/receipt.mjs +203 -0
- package/lib/render.mjs +503 -0
- package/lib/serialize.mjs +441 -0
- package/lib/spans.mjs +177 -0
- package/lib/svg-path.mjs +483 -0
- package/lib/table.mjs +1047 -0
- package/lib/tokens.mjs +295 -0
- package/lib/validate.mjs +929 -0
- package/package.json +68 -6
- package/references/cookbook.md +156 -0
- package/references/images.md +171 -0
- package/references/product-flow.md +408 -0
- package/references/schema.md +90 -0
- package/references/styling.md +181 -0
- package/scripts/install.mjs +95 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: justskill-design
|
|
3
|
+
description: >-
|
|
4
|
+
Take a product from idea to approved screens to code, and design the pages on
|
|
5
|
+
the way. Use this skill whenever the user describes a product idea — an app,
|
|
6
|
+
site, tool or mini program — or asks to add, change or remove a user-facing
|
|
7
|
+
product feature, even without saying "design": it guides a soft, skippable
|
|
8
|
+
route through feature breakdown and confirmation, then page design on a canvas
|
|
9
|
+
the user watches and adjusts, then implementation. Use it whenever the user
|
|
10
|
+
wants to design, mock up, wireframe, redesign or restyle a web page, app screen
|
|
11
|
+
or mobile screen — landing page, dashboard, settings, pricing, onboarding — or
|
|
12
|
+
wants to change spacing, colour, typography or layout, turn a design into
|
|
13
|
+
deliverables, or import a `.pen` file. Also use it for a page editor, a design
|
|
14
|
+
file, editing design tokens, or reviewing someone else's design. Prefer it over
|
|
15
|
+
hand-written markup: the file is validated and the user can adjust it on a
|
|
16
|
+
canvas without code. For a concrete edit in an existing design, skip the route.
|
|
17
|
+
compatibility: Node 18 or newer. No dependencies, no install step, no build.
|
|
18
|
+
allowed-tools: Bash(node:*), Read, Write, Edit
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# justSkill
|
|
22
|
+
|
|
23
|
+
You write one HTML file with inline CSS. It is the design. This skill checks it,
|
|
24
|
+
lets you edit it one change at a time, and gives the user a canvas to drag.
|
|
25
|
+
|
|
26
|
+
You are also the product designer. When the user brings an idea, or a feature to
|
|
27
|
+
add or change, you take it from a short feature breakdown to screens they
|
|
28
|
+
approve on the canvas, and only then to code. That route is the default, not a
|
|
29
|
+
gate: it moves in small steps the user can skip at any point.
|
|
30
|
+
|
|
31
|
+
Nothing installs, nothing logs in, nothing runs between sessions. Every command
|
|
32
|
+
is `node design.mjs <verb>`.
|
|
33
|
+
|
|
34
|
+
## From an idea to a product
|
|
35
|
+
|
|
36
|
+
The user often arrives not with a page but with a product: "an app that splits
|
|
37
|
+
group expenses", "add favourites to the recipe list", "make checkout one
|
|
38
|
+
screen". Designing a page before the product is agreed gives you a beautiful
|
|
39
|
+
answer to the wrong question, and writing the code before the user has seen a
|
|
40
|
+
screen is work done twice. So when the request is product-shaped, lead this
|
|
41
|
+
route. A new product idea starts at stage 1; a feature to add or change starts
|
|
42
|
+
at stage 2, because the product question is smaller but still a product
|
|
43
|
+
question. A bug fix, a code task, or a question with no screens in it is not
|
|
44
|
+
this route: do the work, and do not start the route around it.
|
|
45
|
+
|
|
46
|
+
| # | Stage | What you do | Done when |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| 1 | Idea | Restate the product in one sentence, and who it is for. Ask at most the two or three questions that change the screens. | The user recognises the idea in your sentence. |
|
|
49
|
+
| 2 | Feature breakdown | Propose the smallest first version: the core loop, one to five screens with a job each, the empty, loading and error states they need, the data each shows, and what is deliberately out. | The breakdown is short enough to read in one message. |
|
|
50
|
+
| 3 | Feature confirmation | Ask for a clear yes, and what is missing or must-have. Fold the answer back into the breakdown. | The user confirms the scope, or tells you to decide. |
|
|
51
|
+
| 4 | Design takeover | Say you are taking over the screens, and propose the page list and one visual direction. Create the design file and its tokens — or open the existing file when the product already has one. | The page list and the direction are stated. |
|
|
52
|
+
| 5 | Design first | State the order once — in the same message as the feature confirmation is fine: screens first, your approval, then code. | The user agrees, or has already given you the order. |
|
|
53
|
+
| 6 | Canvas | Open the canvas: if a `justskill_canvas` tool is available, call it with the design file and never start a server yourself. Otherwise start `serve` in the background and give the user the URL. Then design page by page; every `add` and `set` appears in the open canvas as you go. | The first screen is on the canvas. |
|
|
54
|
+
| 7 | UI adjustment | Teach the handshake once: select an element, press `⌘C`, paste the copied pointer back with the change. Apply it with `set`, `move`, `add`, `remove` or `tokens`. | The user stops asking for changes. |
|
|
55
|
+
| 8 | Page sign-off | Run `validate`, walk the checklist — every planned screen, its states, the copy, the images, the tokens — and ask for approval. | The user approves the screens. |
|
|
56
|
+
| 9 | Code | Treat the validated file as the spec, then implement the screens in the user's stack. | The running product matches the approved screens. |
|
|
57
|
+
|
|
58
|
+
This route is soft. It is the default because rebuilding a project twice costs
|
|
59
|
+
more than asking twice, not because the user must walk it:
|
|
60
|
+
|
|
61
|
+
- **Begin, do not ask to begin.** Do not open with "shall we do product design
|
|
62
|
+
first?". Do stages 1 and 2, then end with one confirmation, so a user who
|
|
63
|
+
wanted something else can redirect in one message.
|
|
64
|
+
- **Every stage is skippable.** "Just do it", "I'll tell you what I want",
|
|
65
|
+
"write the code now": do that instead, and do not re-argue the route later.
|
|
66
|
+
- **Ask about the product, decide the design.** Who the user is, what "done"
|
|
67
|
+
means, and what must be in the first version are the user's answers. Colour,
|
|
68
|
+
spacing and layout are yours; one direction is enough, and a direction the
|
|
69
|
+
user already stated is not offered back as a question.
|
|
70
|
+
- **One short message of questions.** A form of eight questions is not a
|
|
71
|
+
conversation; two or three at stage 1 is the ceiling, and a confirmation
|
|
72
|
+
already given is not asked again.
|
|
73
|
+
- **Scale the ritual to the risk.** A typo needs no breakdown; a new state or
|
|
74
|
+
flow does. A request that already names the screen and its content ("add a
|
|
75
|
+
pricing row to this page") is an edit: make it directly, with no route.
|
|
76
|
+
|
|
77
|
+
The per-stage playbook — the questions worth asking, the breakdown template,
|
|
78
|
+
the canvas handshake, the handoff to code, and how to stay soft under pressure
|
|
79
|
+
— is in `references/product-flow.md`. Read it when this route is in play.
|
|
80
|
+
|
|
81
|
+
## The one rule
|
|
82
|
+
|
|
83
|
+
**The design file is the truth. Write it, do not rewrite it.**
|
|
84
|
+
|
|
85
|
+
- Write the file with `Write`. Give every element a `data-id` and a `data-type`.
|
|
86
|
+
- `format` rewrites a file into canonical form. Use it when a file came from
|
|
87
|
+
somewhere else; do not use it to change a design.
|
|
88
|
+
- Change it with `set`, `add`, `remove`, `move`, `rename`. Never rewrite the whole
|
|
89
|
+
file to change one value: an op is one undo step, and a rewrite is an afternoon
|
|
90
|
+
of lost work for the user who has the canvas open.
|
|
91
|
+
- Run `validate` before you hand anything back.
|
|
92
|
+
|
|
93
|
+
If the user has the canvas open, you are not the only writer. `set` and friends
|
|
94
|
+
re-read the file first, so they never clobber the user's drag. A full-file
|
|
95
|
+
`Write` does.
|
|
96
|
+
|
|
97
|
+
## The file
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<!doctype html>
|
|
101
|
+
<html lang="en">
|
|
102
|
+
<head>
|
|
103
|
+
<meta charset="utf-8">
|
|
104
|
+
<title>Landing</title>
|
|
105
|
+
<meta name="design-schema" content="3.0">
|
|
106
|
+
<style>:root{ --color-ink:#111827; --space-md:16px }</style>
|
|
107
|
+
<style>section[data-type="page"]{ margin:0 0 24px }</style>
|
|
108
|
+
</head>
|
|
109
|
+
<body>
|
|
110
|
+
<section data-id="home" data-type="page" data-name="Home" data-canvas="0,0" style="width:390px;height:844px">
|
|
111
|
+
<div data-id="home__root" data-type="frame" style="display:flex;flex-direction:column;gap:16px;padding:24px">
|
|
112
|
+
<p data-id="home__title" data-type="text" style="font-size:32px;font-weight:700;color:var(--color-ink)">你好</p>
|
|
113
|
+
</div>
|
|
114
|
+
</section>
|
|
115
|
+
</body>
|
|
116
|
+
</html>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Three things to notice, because they are the ones that get refused:
|
|
120
|
+
|
|
121
|
+
1. **Every element carries `data-id` and `data-type`.** `data-id` is unique in
|
|
122
|
+
the whole file. `data-type` must match the tag.
|
|
123
|
+
2. **Exactly two `<style>` blocks, both in `<head>`:** the token block (`:root`)
|
|
124
|
+
and the page-frame block. Everything else is inline in a `style` attribute.
|
|
125
|
+
3. **One file holds many pages.** A page is `<section data-type="page">`.
|
|
126
|
+
|
|
127
|
+
Run `node design.mjs new design.html --name home` for a starting file that is
|
|
128
|
+
already correct, and `node design.mjs show design.html home__title` to read one
|
|
129
|
+
element back.
|
|
130
|
+
|
|
131
|
+
## The command loop
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
# 1. Start from a valid file, or read what is there.
|
|
135
|
+
node design.mjs new design.html --name home --title "Landing"
|
|
136
|
+
node design.mjs ls design.html --pages
|
|
137
|
+
node design.mjs show design.html home__root
|
|
138
|
+
|
|
139
|
+
# 2. Add content. The fragment is plain HTML: the model's own language.
|
|
140
|
+
node design.mjs add design.html --html-file /tmp/card.html --in home__root
|
|
141
|
+
|
|
142
|
+
# 3. Change one thing at a time.
|
|
143
|
+
node design.mjs set design.html home__title --style 'font-size:40px;color:#111827'
|
|
144
|
+
node design.mjs set design.html home__title --text "Ship faster"
|
|
145
|
+
node design.mjs move design.html home__card --after home__title
|
|
146
|
+
node design.mjs remove design.html home__notice
|
|
147
|
+
node design.mjs tokens design.html ls
|
|
148
|
+
node design.mjs tokens design.html set color.accent '#7c3aed'
|
|
149
|
+
node design.mjs tokens design.html rm space.xxl
|
|
150
|
+
|
|
151
|
+
# 4. Check. Always.
|
|
152
|
+
node design.mjs validate design.html --json
|
|
153
|
+
node design.mjs format design.html --check # is the file in canonical form?
|
|
154
|
+
node design.mjs migrate design.html # only if validate asks for it
|
|
155
|
+
|
|
156
|
+
# 5. Hand over deliverables, or let the user drag.
|
|
157
|
+
node design.mjs export design.html --all -o dist # HTML, one file per page
|
|
158
|
+
node design.mjs export design.html --format svg -o page.svg
|
|
159
|
+
node design.mjs export design.html --format png --scale 2 -o page.png
|
|
160
|
+
node design.mjs serve design.html
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`add` reads a fragment from `--html-file` or `--html -` (stdin). Prefer a file
|
|
164
|
+
or stdin: a fragment inside shell single quotes breaks on the first apostrophe.
|
|
165
|
+
|
|
166
|
+
### Images
|
|
167
|
+
|
|
168
|
+
When a page has a place for a photograph — a hero, a card, a gallery, an avatar —
|
|
169
|
+
and the user has not supplied an image, fill it from `references/images.md`:
|
|
170
|
+
verified Unsplash URLs, grouped by what the photo shows. Choose by subject — a
|
|
171
|
+
hero wants a wide landscape, an avatar a face — and write the URL as it appears
|
|
172
|
+
there. `alt` says what a person would see; the credit goes in `data-credit`,
|
|
173
|
+
which never renders.
|
|
174
|
+
|
|
175
|
+
The user's own image always wins: a URL, a file, or an image inside the `.pen` is
|
|
176
|
+
never replaced. If no catalogue photo fits, a solid colour or a gradient scrim is
|
|
177
|
+
the honest placeholder — never a made-up URL, never a local path that does not
|
|
178
|
+
exist. The catalogue is hotlinked, so a design renders its photos only with a
|
|
179
|
+
connection.
|
|
180
|
+
|
|
181
|
+
### Tokens
|
|
182
|
+
|
|
183
|
+
A design token is the one thing in the file that is not an element, so no other
|
|
184
|
+
verb can reach it. Changing a brand color is **one op**:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
node design.mjs tokens design.html set color.accent '#7c3aed'
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
- The name is `<group>.<name>`, or the file's own `--group-name` after a `--`:
|
|
191
|
+
`tokens design.html set -- --color-accent '#7c3aed'`.
|
|
192
|
+
- The seven groups are `color`, `font`, `space`, `radius`, `size`, `shadow`,
|
|
193
|
+
`border`. There is no eighth: `surface.warm` is refused, and so is a token
|
|
194
|
+
file that already says `--surface-warm`.
|
|
195
|
+
- `space`, `radius` and `size` carry a unit. `set space.md 16` writes `16px` and
|
|
196
|
+
the receipt says so; a value that is not a length is refused.
|
|
197
|
+
- A color is normalized to hex, so `red` becomes `#ff0000` in the file.
|
|
198
|
+
- A token may reference a token (`var(--color-ink)`). A cycle is refused.
|
|
199
|
+
- Never rewrite the file to change a token. That is the one thing this workflow
|
|
200
|
+
exists to prevent, and the user may have the canvas open.
|
|
201
|
+
|
|
202
|
+
## Reading a receipt
|
|
203
|
+
|
|
204
|
+
Every write prints what it did. You cannot see the file, so read this:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
added: home__cta, home__cta-label (2 elements)
|
|
208
|
+
normalized: #fff → #ffffff
|
|
209
|
+
normalized: 12px 12px 12px 12px → 12px
|
|
210
|
+
dropped: cursor:pointer — The format describes appearance, not interaction. A cursor is behavior.
|
|
211
|
+
applied 1 op, revision a161db2c (0 errors, 0 warnings)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- `normalized` is not an error. It is the canonical form doing its job, and the
|
|
215
|
+
file now holds the value on the right of the arrow.
|
|
216
|
+
- `dropped` means the file does **not** hold that declaration. If it mattered,
|
|
217
|
+
write it again the way the message says.
|
|
218
|
+
- Nothing is ever dropped in silence. If a declaration is not in the file, it is
|
|
219
|
+
in this receipt.
|
|
220
|
+
|
|
221
|
+
## Reading a refusal
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
REFUSED display:grid — grid is not a value of display.
|
|
225
|
+
Accepted: flex | block | none
|
|
226
|
+
design.html was not changed.
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A refusal means the file is byte-identical to what it was. Read the hint: it
|
|
230
|
+
always names what to write instead. The common ones:
|
|
231
|
+
|
|
232
|
+
| Refusal | What to write instead |
|
|
233
|
+
|---|---|
|
|
234
|
+
| a one-sided `margin`, such as `margin-top` | the shorthand: `margin: 0 8px 0 0` |
|
|
235
|
+
| `display:grid` | `display:flex` with `flex:1` on the children |
|
|
236
|
+
| `position:fixed` | `position:absolute` with `left` and `top` |
|
|
237
|
+
| a missing `alt` | `alt=""` for a decorative image. Missing is not the same as empty |
|
|
238
|
+
| a duplicate `data-id` | the hint names a free id, such as `home__a-2` |
|
|
239
|
+
| `transform: translateX(-50%)` | `transform: rotate(<deg>)` only; centre with `left` |
|
|
240
|
+
|
|
241
|
+
Not refused, but reported: `cursor` and `animation` are outside the format's
|
|
242
|
+
property table, so the write goes through and the receipt lists them under
|
|
243
|
+
`dropped`. A `transform` is in the table, but only as `rotate(<deg>)` — a
|
|
244
|
+
`translate()` or `scale()` is a refusal, not a drop. And `#fff` or `rgba(...)` is not a refusal either — the file
|
|
245
|
+
takes it and writes the canonical hex, and the receipt calls that `normalized`
|
|
246
|
+
(see the receipt above).
|
|
247
|
+
|
|
248
|
+
## What the format is, and is not
|
|
249
|
+
|
|
250
|
+
Supported: flexbox layout, absolute positioning, a fixed set of 47 CSS
|
|
251
|
+
properties, images, inline SVG icons, design tokens, and text in one run per text
|
|
252
|
+
element.
|
|
253
|
+
|
|
254
|
+
Not supported, and refused by name rather than ignored: grid, `position:fixed`,
|
|
255
|
+
transform functions other than `rotate()`, rotation about a point other than the
|
|
256
|
+
element's centre, animation, responsive breakpoints, rich text inside one text
|
|
257
|
+
element, and any property outside the table.
|
|
258
|
+
|
|
259
|
+
The full property list, with the accepted values for each, is in
|
|
260
|
+
`references/styling.md`. The element contract is in `references/schema.md`.
|
|
261
|
+
Read one of them before writing a big fragment; they are short, and they prevent
|
|
262
|
+
most refusals.
|
|
263
|
+
|
|
264
|
+
## The canvas
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
node design.mjs serve design.html
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
In DeepSeek Harness with the JustSkill Design plugin, the canvas is the session's
|
|
271
|
+
right-sidebar **Canvas** tab. Call the `justskill_canvas` tool with the design file
|
|
272
|
+
instead of running `serve`; the plugin opens or focuses the tab.
|
|
273
|
+
|
|
274
|
+
Prints a URL and opens it. The service stays in the foreground for as long as the
|
|
275
|
+
canvas is open, so start it as a background process when you want to keep
|
|
276
|
+
designing with the canvas up, and read the URL it prints. The canvas follows the
|
|
277
|
+
file: every `add` and `set` appears in the user's tab as you go, which is what
|
|
278
|
+
makes a design appear gradually instead of arriving finished. The user drags;
|
|
279
|
+
changes go through the same funnel as your commands, so a drag cannot write
|
|
280
|
+
something `validate` would refuse. If a drag is refused, the canvas shows what it
|
|
281
|
+
would have introduced. Clicking the empty canvas clears the selection; dragging it
|
|
282
|
+
pans.
|
|
283
|
+
|
|
284
|
+
Picking is by rungs, not by depth: hovering outlines the container the canvas is
|
|
285
|
+
standing on (solid) and that container's direct children (dashed). A click
|
|
286
|
+
selects that container — never the deepest element under the pointer — so a
|
|
287
|
+
person selects a card without landing on the word in it, and a drag that follows
|
|
288
|
+
moves the card. A double-click steps into the dashed box under the pointer, one
|
|
289
|
+
rung deeper. Escape walks back up a rung.
|
|
290
|
+
|
|
291
|
+
Picking one element and pressing `⌘C` copies a pointer to it, not the design:
|
|
292
|
+
`Use justSkill tools. Current file <uri>. Page id: <page>. Selected node ids:
|
|
293
|
+
<id>`, confirmed by a toast. (`Page id:` appears only when the selection is not
|
|
294
|
+
itself a page.) A user who pastes that into the chat is pointing at one node in
|
|
295
|
+
this file — read it with `show`, then change it. This is the normal way a user
|
|
296
|
+
asks for a small adjustment, so tell them the shortcut when the canvas opens.
|
|
297
|
+
|
|
298
|
+
The canvas has three columns: a layer tree (every page, its elements nested,
|
|
299
|
+
opening fully collapsed, with expand-all and collapse-all in the header — picking
|
|
300
|
+
an element on the canvas opens the tree down to it and selects that row), the
|
|
301
|
+
design, and the selected element's properties in sections. The right panel only ever shows properties the element already has —
|
|
302
|
+
it cannot add a declaration the file does not carry, so use `set` for that. Either
|
|
303
|
+
panel folds away with `⌘\` / `⇧⌘\` or the button on the canvas edge, and either
|
|
304
|
+
edge drags to resize; that state lives in the browser, never in the file.
|
|
305
|
+
|
|
306
|
+
Close the tab and the service exits. The user can also `set` from the command
|
|
307
|
+
line while the canvas is open: both writers re-read the file first.
|
|
308
|
+
|
|
309
|
+
## Rules that save you a round trip
|
|
310
|
+
|
|
311
|
+
1. **Run `validate` when you are unsure.** It is faster than guessing, and it
|
|
312
|
+
tells you the exact offset and the exact fix.
|
|
313
|
+
2. **Never edit `data-id` with `set`.** It is refused, because an id is an
|
|
314
|
+
address rather than a property. Use `node design.mjs rename design.html card
|
|
315
|
+
product`, which is the one verb that changes what an element is called.
|
|
316
|
+
3. **Do not add a `<style>` block** for a one-off value. Only the two allowed
|
|
317
|
+
blocks are read; a third is an error.
|
|
318
|
+
4. **Do not write a one-sided `margin`.** Spacing is `gap` and `padding`.
|
|
319
|
+
`margin` itself is allowed, but only the shorthand: a one-sided `margin-top`
|
|
320
|
+
and friends are refused, because they are a second spelling of the same
|
|
321
|
+
intent. Offset an element with `margin: 0 8px 0 0`, never `margin-right`.
|
|
322
|
+
5. **Prefer tokens.** If `:root` has `--color-ink`, use `var(--color-ink)`
|
|
323
|
+
instead of a literal. The user can then restyle the whole design in one edit.
|
|
324
|
+
6. **Small edits, in order.** Three `set` commands beat one rewrite, and each
|
|
325
|
+
one is a step the user can undo.
|
|
326
|
+
|
|
327
|
+
## Deliverables, and which need a browser
|
|
328
|
+
|
|
329
|
+
| `--format` | Needs | Notes |
|
|
330
|
+
|---|---|---|
|
|
331
|
+
| `html` (default) | nothing | One page, or `--all` for a site. Self-contained. |
|
|
332
|
+
| `svg` | nothing | Wraps the page in `<foreignObject>`. A browser draws it; Figma, Illustrator and Sketch show an empty rectangle. |
|
|
333
|
+
| `png` `jpeg` `webp` | a Chrome-family browser, or the canvas | `--scale 1..3`, `--size WxH` |
|
|
334
|
+
|
|
335
|
+
The image formats find a browser already on the machine — no download, no
|
|
336
|
+
bundled Chromium. When there is none the export **fails** and names three ways
|
|
337
|
+
forward: install Chrome, Chromium or Edge, leave the canvas open, or use
|
|
338
|
+
`--format svg`. It never quietly writes a different format, because a different
|
|
339
|
+
format is a different deliverable.
|
|
340
|
+
|
|
341
|
+
Before any image export, make sure the fonts are declared. `export` adds the
|
|
342
|
+
stylesheet links itself from the families the page uses, and the browser waits
|
|
343
|
+
for them; a screenshot taken before a web font arrives is captured in the
|
|
344
|
+
fallback family, where the text wraps at different words and everything below it
|
|
345
|
+
moves.
|
|
346
|
+
|
|
347
|
+
The HTML deliverable takes the page's own `background-color` onto `<body>`. A
|
|
348
|
+
page covers its own box and nothing more, so a viewport taller than the page
|
|
349
|
+
shows what is behind it: left white, that is a colour the design never chose. A
|
|
350
|
+
page that states no colour, or states `transparent`, keeps a plain document.
|
|
351
|
+
|
|
352
|
+
## Working with a `.pen` file
|
|
353
|
+
|
|
354
|
+
```sh
|
|
355
|
+
node design.mjs import pen design.pen -o design.html
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
One direction only: pen in, design file out. Every run prints a ledger:
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
26 pages · 2197 elements · 239 references inlined · 140 with overrides · lost: perCornerRadius×1
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`lost` names what this format has no spelling for. Read it and tell the user,
|
|
365
|
+
rather than letting them find out on the review screen. `--report r.json` writes
|
|
366
|
+
the whole ledger.
|
|
367
|
+
|
|
368
|
+
Four things the reader translates rather than copies, because the format has no
|
|
369
|
+
spelling for pen's version of them:
|
|
370
|
+
|
|
371
|
+
- **A glyph is measured.** A `path` node's outline is drawn on its own grid, and
|
|
372
|
+
that grid becomes the icon's `viewBox` — pen's own export writes
|
|
373
|
+
`viewBox="0 0 14 14"` on a 366×366 arrow. An outline whose box cannot be
|
|
374
|
+
measured keeps the node's box, which is what every glyph in the reference file
|
|
375
|
+
happens to have anyway.
|
|
376
|
+
- **A stroked outline is drawn as a stroke**, on the `<path>`, with
|
|
377
|
+
`vector-effect: non-scaling-stroke` so the width stays the width pen drew.
|
|
378
|
+
It is not a `border` on the `<svg>` — that is a ring around a box, not a
|
|
379
|
+
drawing.
|
|
380
|
+
- **A rotation is baked into the outline** for a `path` node (the ledger counts
|
|
381
|
+
`bakedRotation`): a path is an opaque drawing with no box to turn, and pen turns
|
|
382
|
+
an element about its top-left corner while this format turns it about its
|
|
383
|
+
centre. Any other rotated node is drawn upright and named in `lost.rotation`,
|
|
384
|
+
because reproducing pen's origin would need a `transform-origin` the format does
|
|
385
|
+
not have.
|
|
386
|
+
- **A line break the author placed survives** as `white-space: pre-line`. Written
|
|
387
|
+
as a literal newline it is a space, and the line then wraps wherever the font
|
|
388
|
+
puts it.
|
|
389
|
+
|
|
390
|
+
The ledger also counts what it rewrote: `themedVariables` (a themeable variable
|
|
391
|
+
takes the document's default theme, which is the entry with no `theme` of its
|
|
392
|
+
own), `hardBreaks`, `pathAsGeometry`, `bakedRotation`. A variable that cannot be
|
|
393
|
+
reduced to one value is left out of the token block and counted as
|
|
394
|
+
`lost.unresolvedVariable` — never written as the string `[object Object]`, which
|
|
395
|
+
is a value that validates and means nothing.
|
|
396
|
+
|
|
397
|
+
**A reference is inlined, because this format has no reference.** pen stores a
|
|
398
|
+
`ref` node and the master it points at; the reader copies the master's subtree in
|
|
399
|
+
where the reference stood. The copy's root keeps the reference's own id and
|
|
400
|
+
everything under it is renumbered as `<reference-id>__<its own id>`, which is why
|
|
401
|
+
an imported file has 239 more subtrees in it than the `.pen` had nodes. Four
|
|
402
|
+
things follow, and they are the ones to tell the user:
|
|
403
|
+
|
|
404
|
+
- Editing a copy edits that copy alone. The master is a separate element now, so
|
|
405
|
+
a change meant for every card has to be made on each one — or on the `.pen`,
|
|
406
|
+
which is where the sharing lives.
|
|
407
|
+
- `descendants` patches are applied during the import: the text, the size, the
|
|
408
|
+
swapped glyph are all in the file, and nothing applies them later.
|
|
409
|
+
- A reference that names nothing is refused (`pen/unresolved-ref`), as is one
|
|
410
|
+
inside a loop (`pen/ref-cycle`) or nested more than 16 deep (`pen/ref-depth`).
|
|
411
|
+
- Children on a reference node are not part of the master's subtree, so they are
|
|
412
|
+
counted in `lost.refChildren` and not written.
|
|
413
|
+
|
|
414
|
+
## What the skill needs at run time
|
|
415
|
+
|
|
416
|
+
The skill runs from `SKILL.md`, `references/`, `design.mjs`, `lib/` and
|
|
417
|
+
`canvas/` — about 1 MB. The repository also carries media that no command reads:
|
|
418
|
+
|
|
419
|
+
| Media only | What it is | Size |
|
|
420
|
+
|---|---|---|
|
|
421
|
+
| `docs/justskill-demo.gif`, `docs/justskill-demo.mp4` | the README demo (English, 30s) | ~4.6 MB |
|
|
422
|
+
| `docs/justskill-demo-zh.gif`, `docs/justskill-demo-zh.mp4` | the same demo in Chinese, for the Chinese README | ~4.6 MB |
|
|
423
|
+
| `docs/*.png` | the screenshots, including the case wall | ~1 MB |
|
|
424
|
+
|
|
425
|
+
If the skill was copied into the skills directory (not symlinked to a checkout),
|
|
426
|
+
the user can remove those and free about 10 MB; nothing in `validate`, `set`,
|
|
427
|
+
`serve` or `export` changes, and every other file is text:
|
|
428
|
+
|
|
429
|
+
```sh
|
|
430
|
+
rm -rf docs/*.gif docs/*.mp4 docs/*.png
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Ask before removing anything, and never prune a symlinked checkout — that is
|
|
434
|
+
the user's repository. Everything else stays: the docs, examples, tests and
|
|
435
|
+
READMEs are text, small, and worth reading. Keep `LICENSE` and `package.json`.
|