figura-cli 0.23.2 → 0.25.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/dist/index.js +253 -209
- package/dist/mcp.js +41 -41
- package/package.json +2 -1
- package/skills/fig/SKILL.md +151 -94
- package/skills/skills.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "figura-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Command-line client for the Figura visualization SaaS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@types/node": "^20.14.0",
|
|
41
41
|
"@types/ws": "^8.5.12",
|
|
42
42
|
"axios": "^1.7.9",
|
|
43
|
+
"execa": "^9",
|
|
43
44
|
"nostr-tools": "^2.10.4",
|
|
44
45
|
"package-manager-detector": "^1.8.0",
|
|
45
46
|
"postgres": "^3.4.9",
|
package/skills/fig/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: fig
|
|
3
3
|
description: "Mock up a screen as an on-brand, shareable HTML fig via your Figura team — render a UI state in seconds before writing the real code. Use when asked to visualize a screen, mock a UI, or 'make a fig'. iPhone-framed by default; browser frame for web/desktop."
|
|
4
4
|
argument-hint: '<what to visualize>'
|
|
5
|
-
allowed-tools: 'mcp__figura__figura_whoami, mcp__figura__figura_fig_scaffold, mcp__figura__figura_fig_devices, mcp__figura__figura_list_figs, mcp__figura__figura_generate_fig, mcp__figura__figura_compose_fig, mcp__figura__figura_create_fig, mcp__figura__figura_revise_fig, mcp__figura__figura_publish_fig, mcp__figura__figura_commit_fig, mcp__figura__figura_request_feedback, Bash(figura *), Bash(bunx figura-cli *)'
|
|
5
|
+
allowed-tools: 'mcp__figura__figura_whoami, mcp__figura__figura_fig_scaffold, mcp__figura__figura_fig_devices, mcp__figura__figura_list_figs, mcp__figura__figura_generate_fig, mcp__figura__figura_compose_fig, mcp__figura__figura_create_fig, mcp__figura__figura_revise_fig, mcp__figura__figura_upload_asset, mcp__figura__figura_publish_fig, mcp__figura__figura_commit_fig, mcp__figura__figura_request_feedback, Bash(figura *), Bash(bunx figura-cli *)'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# /fig — figure it out before you build it
|
|
@@ -19,6 +19,28 @@ place (your team's Figura brand profile) and is injected server-side. That is th
|
|
|
19
19
|
whole point: a fig is on-brand *by construction*, and this skill can never drift
|
|
20
20
|
from your brand. To change the brand, use the `figura-brand` skill, not this one.
|
|
21
21
|
|
|
22
|
+
## Hard rules — read before anything else
|
|
23
|
+
|
|
24
|
+
1. **When you author the markup, a phone fig ⇒ `figura_compose_fig`. Always.
|
|
25
|
+
No exceptions.** The phone is the only frame the composer builds, and every
|
|
26
|
+
framed phone screen goes through it. **Web/desktop (browser frame) ⇒
|
|
27
|
+
`figura_create_fig`**, following the scaffold's `--platform web` frame
|
|
28
|
+
contract verbatim — markup only, the scaffold owns the CSS.
|
|
29
|
+
**Tablet has no authoring path** (#869): say so and stop.
|
|
30
|
+
(`figura_generate_fig` authors nothing locally — step 3 governs it.)
|
|
31
|
+
2. **The frame is Figura's, never yours.** Bezel, notch/Dynamic Island, status
|
|
32
|
+
bar, home bar, safe insets, artboard geometry and scale are server-owned.
|
|
33
|
+
You author screen CONTENT only.
|
|
34
|
+
3. **`figura_create_fig` is for web/`document`/exotic figs only.** Never for a
|
|
35
|
+
phone fig — this is a rule, not a preference. No `compose` on this engine? Say
|
|
36
|
+
so and stop; the caller upgrades. A hand-framed phone is not the fallback.
|
|
37
|
+
4. **Self-check before you land a phone fig.** Search your own markup for these;
|
|
38
|
+
any hit means you drew the frame — discard it and rebuild via
|
|
39
|
+
`figura_compose_fig`:
|
|
40
|
+
`.fig-device` wrapper · `--fig-safe-*` · notch / Dynamic Island / bezel /
|
|
41
|
+
home-bar elements · a status-bar row · a phone-shaped `border-radius`
|
|
42
|
+
container · a hand-written device width/height.
|
|
43
|
+
|
|
22
44
|
## When to use
|
|
23
45
|
|
|
24
46
|
- Asked to mock a screen, visualize a UI state, or "make a fig".
|
|
@@ -37,8 +59,8 @@ something in a vacuum — and end up rebuilding what already ships. So every tim
|
|
|
37
59
|
matching route). If a real implementation exists, **Read it** and treat it as the
|
|
38
60
|
baseline you must show.
|
|
39
61
|
- **Check the team's figs** — `figura fig list --area <area>` (or
|
|
40
|
-
`figura_list_figs`). If a fig of this screen already exists,
|
|
41
|
-
(
|
|
62
|
+
`figura_list_figs`). If a fig of this screen already exists, **iterate that
|
|
63
|
+
fig** (step 5), never a fresh duplicate.
|
|
42
64
|
|
|
43
65
|
If any prior design exists → build in **`mode: compare`** and render it as
|
|
44
66
|
**existing (as-is) → proposed**, grounded in the real current state. Even when the
|
|
@@ -105,7 +127,7 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
|
|
|
105
127
|
to the user verbatim (a one-line "figura-mcp is behind / below min — run …"),
|
|
106
128
|
then proceed. It's a passive nudge, shown once per session — never a blocker.
|
|
107
129
|
|
|
108
|
-
2. **Default — write the CONTENT, let Figura own the STRUCTURE.** Build
|
|
130
|
+
2. **Default — write the CONTENT, let Figura own the STRUCTURE.** Build phone
|
|
109
131
|
figs with **`figura_compose_fig`**: you author only each **screen's CONTENT**
|
|
110
132
|
(the markup *inside* the phone); Figura owns the parts that used to drift — the
|
|
111
133
|
iPhone **frame** (bezel + Dynamic Island/notch + home bar), the **safe zone**,
|
|
@@ -130,14 +152,29 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
|
|
|
130
152
|
detail in; never for a redesign argument), or `document` (NO device
|
|
131
153
|
framing — brand/overview figs).
|
|
132
154
|
- **`screens`** — an ARRAY. Each screen's **`html` is CONTENT ONLY**: the
|
|
133
|
-
markup that goes *inside* the phone
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
intent Figura needs — **`device`** (a real catalog id from
|
|
155
|
+
markup that goes *inside* the phone (hard rule 2). For every device layout
|
|
156
|
+
each screen also carries the typed intent Figura needs — **`device`** (a
|
|
157
|
+
real catalog id from
|
|
137
158
|
`figura_fig_devices`, e.g. `iphone-16`), **`orientation`**
|
|
138
159
|
(`portrait`/`landscape`), and **`safeZone`** (`respect` = Figura pads your
|
|
139
160
|
content to the device's real safe insets; `ignore` = edge-to-edge for a
|
|
140
161
|
splash/hero). (`document` screens need none of these.)
|
|
162
|
+
- **`childFig`** (instead of that screen's `html`) — compose the frame BY
|
|
163
|
+
REFERENCE: the id of an existing fig composed with `kind: 'child'`, which
|
|
164
|
+
Figura embeds in its own iframe. `single` and `compare` only. Reach for it
|
|
165
|
+
when two screens fight over the same CSS (each child's styles are sealed in
|
|
166
|
+
its own document and cannot reach the frame or the other screen), or when a
|
|
167
|
+
screen deserves its own versions, reactions and comments. Compose each child
|
|
168
|
+
first, then pass their ids, and give the screen the SAME `device` and
|
|
169
|
+
`orientation` the child was composed with — the frame draws that phone's
|
|
170
|
+
chrome around the child's own safe-area guides, so a mismatch is refused.
|
|
171
|
+
The reference is PINNED: a later revision of a child does not change a
|
|
172
|
+
composition that already exists. A composed-by-reference parent is revised
|
|
173
|
+
by **typed screens, never by HTML** — see step 5. Publishing the parent is
|
|
174
|
+
what makes its children reachable at the public link; a child fig has no
|
|
175
|
+
share link of its own. A referenced screen still takes `notes` on `compare`
|
|
176
|
+
— the rail and its pins are the parent's markup, so they never reach into
|
|
177
|
+
the child.
|
|
141
178
|
- **`sharedCss`** — ONE stylesheet every screen shares: put your brand tokens
|
|
142
179
|
(from the scaffold) and shared content classes here. Per-screen one-offs go
|
|
143
180
|
in that screen's `css`.
|
|
@@ -162,11 +199,11 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
|
|
|
162
199
|
brand with `bunx figura-cli brand --json` and author your content styles
|
|
163
200
|
against that.
|
|
164
201
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
**The other path — `figura_create_fig`** (hard rule 3): a full `html` document
|
|
203
|
+
you hand-author, for a web/`document`/exotic fig. Fetch the scaffold for the
|
|
204
|
+
platform (`--platform web` for a browser frame) and follow its frame contract
|
|
205
|
+
verbatim; the chrome CSS is stamped at save, so author the markup and none of
|
|
206
|
+
its styles.
|
|
170
207
|
|
|
171
208
|
**Landing IS hosting — leave `publish` and `commit` unset.** Composing a fig
|
|
172
209
|
always persists it to Figura (there is no separate "save it for real" step);
|
|
@@ -220,19 +257,30 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
|
|
|
220
257
|
only the served bytes can tell a working fig from one that renders as empty
|
|
221
258
|
phones. It exits non-zero if a frame class shipped unsized — fix and re-land
|
|
222
259
|
before a human sees it. Then run
|
|
223
|
-
`figura fig open <id>` (the id
|
|
224
|
-
|
|
260
|
+
`figura fig open <id>` (the id the landing call just returned). It opens
|
|
261
|
+
**`viewUrl`** in the reviewer's browser — the actual
|
|
225
262
|
Figura render (brand chrome, CSP, favicon, responsive frame), team-scoped so
|
|
226
263
|
any signed-in teammate can open it even though nothing was published. That's
|
|
227
264
|
what gets approved, and it's the exact page that ships once published — not a
|
|
228
265
|
`file://` reconstruction that can drift from it. No display reachable (a
|
|
229
266
|
headless/CI session)? Paste the printed `viewUrl` into the conversation
|
|
230
|
-
instead
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
5. **Iterate against the SAME fig, not a fresh one.**
|
|
234
|
-
through **`figura_revise_fig`** (`id`, the updated `html`) — it
|
|
235
|
-
existing chain as a new version and returns a fresh `viewUrl`.
|
|
267
|
+
instead — there is no local-preview fallback; a fig is only viewed on
|
|
268
|
+
figura.so.
|
|
269
|
+
|
|
270
|
+
5. **Iterate against the SAME fig, not a fresh one.** On the create path, changes
|
|
271
|
+
from review go through **`figura_revise_fig`** (`id`, the updated `html`) — it
|
|
272
|
+
joins the existing chain as a new version and returns a fresh `viewUrl`.
|
|
273
|
+
**A COMPOSED fig revises by typed screens**: call `figura_revise_fig` with
|
|
274
|
+
`layout` + `screens` (the same shape compose takes) instead of `html`, and it
|
|
275
|
+
re-composes into the same chain. Every child reference is carried forward with
|
|
276
|
+
its pin, label, notes and position; an empty screen `{}` keeps a REFERENCED
|
|
277
|
+
slot exactly as it was, so changing one side of a by-reference diptych is
|
|
278
|
+
`screens: [{}, {…}]`. A slot that held INLINE `html` must have its html sent
|
|
279
|
+
again — Figura never reads content back out of a composed document. Passing
|
|
280
|
+
`html` for a composed fig is refused (422) — it would leave the references
|
|
281
|
+
behind. `figura_get_fig` returns the fig's `children` when you need to see the
|
|
282
|
+
current composition. Never hand-author the framed document to feed `revise`
|
|
283
|
+
(hard rule 1). Publishing is
|
|
236
284
|
NOT inherited automatically: a revision of an already-published fig still
|
|
237
285
|
lands unpublished by default (`viewUrl` is a team-scoped view, and the
|
|
238
286
|
existing public link keeps serving the OLD version) — pass `publish: true`
|
|
@@ -255,9 +303,9 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
|
|
|
255
303
|
the team (digest-batched) — so at the end of the session, commit exactly
|
|
256
304
|
the fig the user asked for / the final agreed version, and nothing else.
|
|
257
305
|
Explorations, rejected variants, and intermediate iterations STAY drafts.
|
|
258
|
-
If the deliverable is the fig you're about to
|
|
259
|
-
`commit: true` on that final `
|
|
260
|
-
call
|
|
306
|
+
If the deliverable is the fig you're about to land anyway, prefer
|
|
307
|
+
`commit: true` on that final landing call (CLI: `--commit`) over a separate
|
|
308
|
+
call; otherwise commit it afterwards
|
|
261
309
|
with **`figura_commit_fig`** (CLI: `figura fig commit <id>`). Committed the
|
|
262
310
|
wrong one? `figura_commit_fig` with `uncommit: true` (CLI:
|
|
263
311
|
`figura fig uncommit <id>`) returns it to a draft — the share link is
|
|
@@ -280,8 +328,8 @@ per-screen content. You supply each state's CONTENT + a `label`; the composition
|
|
|
280
328
|
the focus pills, the scaled overview grid, and the no-horizontal-scroll guarantee
|
|
281
329
|
are server-owned. The design intent below still guides WHAT each state's content
|
|
282
330
|
says and which is recommended — you just no longer hand-write the toggle markup
|
|
283
|
-
or the frame scaling. (
|
|
284
|
-
|
|
331
|
+
or the frame scaling. (On the `create` path — a web fig — the same pattern is
|
|
332
|
+
hand-authored: plain CSS-only radio tabs, binary, no library.)
|
|
285
333
|
|
|
286
334
|
**★ FOCUS IS THE DEFAULT VIEW.** The fig opens on ONE full-size frame with state
|
|
287
335
|
pills — on desktop AND mobile. Overview is the *alternative*, behind the second
|
|
@@ -293,39 +341,64 @@ are enough states that scanning them at once genuinely helps.
|
|
|
293
341
|
|
|
294
342
|
- **Focus — full frame (default).** One state at a time behind state pills
|
|
295
343
|
(Today / A / B / …), each state framed identically so flipping states in place
|
|
296
|
-
reads as spot-the-difference. This is where the context lives
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
344
|
+
reads as spot-the-difference. This is where the context lives: what's broken
|
|
345
|
+
today, what each variation improves, what it trades away. Open on the first
|
|
346
|
+
pill = the honest current state.
|
|
347
|
+
- **★ Annotation NEVER goes on the design — pass `notes`, don't draw chips.**
|
|
348
|
+
A note written into a screen's `html` lands inside the phone screen, on top of
|
|
349
|
+
the layout the fig exists to let someone judge. Measured on a shipped fig
|
|
350
|
+
(`fig_wjgcksh38pix`): 3–4 absolutely-positioned chips per state, every one 100%
|
|
351
|
+
inside the screen box, together covering **12–19% of the artboard** — sitting
|
|
352
|
+
on the masonry grid and hero row that were the whole question — while the page
|
|
353
|
+
around the frame was empty black. So annotation is **data, not markup**: give
|
|
354
|
+
each screen a `notes` array on `figura_compose_fig` and Figura renders a rail
|
|
355
|
+
**beside** the frame, with only a ~20px numbered pin on the artboard (~0.3%
|
|
356
|
+
coverage). Nothing to toggle: the layout and the argument are both fully
|
|
357
|
+
visible at every viewport, and below the restructure breakpoint the rail
|
|
358
|
+
stacks under the frame automatically.
|
|
359
|
+
|
|
360
|
+
```jsonc
|
|
361
|
+
"notes": [
|
|
362
|
+
// `at` is percent on the SCREEN → a numbered pin there, note N in the rail.
|
|
363
|
+
{ "kind": "broken", "text": "Hero is score-picked, so it lands mid-row and manufactures a second full-width row (#2755).", "at": { "x": 34, "y": 52 } },
|
|
364
|
+
{ "kind": "improved", "text": "A run IS a collection — a name is always attached.", "at": { "x": 91, "y": 14 } },
|
|
365
|
+
// No `at` → a point about the state as a whole. Numbered, no pin.
|
|
366
|
+
{ "kind": "tradeoff", "text": "Costs the shipped vertical-advance gesture." }
|
|
367
|
+
]
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`kind` is `broken` | `improved` | `tradeoff` | `detail` (default `detail`), max 8
|
|
371
|
+
notes a screen, and `compose` **rejects** notes on `single`/`variations`/
|
|
372
|
+
`document` — those layouts have no rail, and silently dropping a note drops
|
|
373
|
+
the argument. If you're hand-authoring via `figura_create_fig` instead, the
|
|
374
|
+
same rule holds by hand: the rail is a **sibling of the frame**, never a
|
|
375
|
+
descendant of the clipped `.fig-screen`, and nothing but a pin sits on the
|
|
376
|
+
design.
|
|
303
377
|
- **Overview — side by side (alternative).** Every state at once: the honest
|
|
304
378
|
current state first, then each variation, as scaled-down frames in a grid.
|
|
305
379
|
Under each: a status chip and ONE condensed context line (`+ / –`). This view
|
|
306
380
|
is for scanning and picking a direction, not for reading detail. The grid must
|
|
307
381
|
wrap to one column before it can overflow — **a fig never scrolls
|
|
308
|
-
horizontally**, at any viewport.
|
|
309
|
-
kit's **`.fig-mini`** wrapper (see "Scaling a framed device" below), never a
|
|
310
|
-
rule on `.fig-device` and never CSS `zoom` (zoom leaks an unzoomed
|
|
311
|
-
`scrollWidth` into horizontal overflow — the exact thing this view forbids).
|
|
382
|
+
horizontally**, at any viewport. Frame scaling is the server's (hard rule 2).
|
|
312
383
|
- **In both views:** render the current state honestly from the shipped
|
|
313
384
|
implementation — **reconstructed from source** per "Reconstruct the baseline
|
|
314
385
|
from SOURCE" (no screenshot ⇒ study the component file(s) and rebuild the real
|
|
315
|
-
layout, don't approximate);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
386
|
+
layout, don't approximate); classify every note by function via its `kind`
|
|
387
|
+
(`broken` / `improved` / `tradeoff` / `detail`) rather
|
|
388
|
+
than hand-picking colors — the rail and its pins take the team's status colors
|
|
389
|
+
from the design system, and a `sharedCss` override of `--fig-note-improved` and
|
|
390
|
+
friends re-points all of them at once; mark the recommended variation and
|
|
391
|
+
close with a short "my read" arguing the pick against the others. Keep the
|
|
392
|
+
chrome around the frames minimal — the frames are the content; no legends
|
|
393
|
+
(notes label themselves), no repeated context, fluid `max-width` throughout.
|
|
321
394
|
- **Verify the SERVED fig, not your local draft.** The component kit (device
|
|
322
395
|
frame, status bar, control bar) is stamped **server-side at save** — it does
|
|
323
396
|
**not** exist in the HTML you authored locally, so a headless render of your
|
|
324
397
|
own file shows *unstyled, empty* frames whether or not the fig is actually
|
|
325
398
|
correct. A local screenshot is structurally blind to the most common way a
|
|
326
399
|
framed fig breaks. So verify the fig that actually ships:
|
|
327
|
-
1. **Land it first
|
|
328
|
-
the stamped bytes: `figura fig open <id> --
|
|
400
|
+
1. **Land it first**, then dump
|
|
401
|
+
the stamped bytes: `figura fig open <id> --print`. This is the
|
|
329
402
|
served document, kit and all.
|
|
330
403
|
2. **Assert the kit stamped — `figura fig verify <id>`.** One command, and it
|
|
331
404
|
**exits non-zero** if any frame class your markup uses shipped with no CSS
|
|
@@ -341,15 +414,26 @@ are enough states that scanning them at once genuinely helps.
|
|
|
341
414
|
repo's `.figura/figs/` (gitignored), or `~/.figura/figs/` outside a bound
|
|
342
415
|
repo, never `/tmp`/`mktemp`), and render it at a few viewport widths.
|
|
343
416
|
`document.scrollWidth` must equal the viewport in every view and state
|
|
344
|
-
(sideways scroll = broken),
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
the
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
417
|
+
(sideways scroll = broken), and **measure how much of the artboard the
|
|
418
|
+
annotation covers**: sum the intersection of every annotation element with
|
|
419
|
+
the `.fig-screen` box and divide by its area. With `notes` that number is
|
|
420
|
+
the pins alone (~0.3%); anything in the double digits means chips got drawn
|
|
421
|
+
into the design again. Also assert nothing is cut by a clipping ancestor:
|
|
422
|
+
walk each element's ancestors up to `<html>` and, wherever computed
|
|
423
|
+
`overflow` is `hidden`/`clip`, assert its rect stays inside that ancestor's
|
|
424
|
+
box — the viewport box for `<body>`/`<html>`, whose overflow propagates
|
|
425
|
+
there. Comparing to the frame's rect alone misses the vertical escape, and
|
|
426
|
+
no rect test sees a rounded clip, which is why keeping the rail a sibling
|
|
427
|
+
of the frame is the real guarantee. `figura fig open <id> --print` is where
|
|
428
|
+
those served bytes come from.
|
|
429
|
+
|
|
430
|
+
**A fig composed BY REFERENCE has no printable bytes.** Its screens are served
|
|
431
|
+
per request, so `--print` refuses (non-zero) and names the hosted URL: the
|
|
432
|
+
stored document is references, not a render. Verify it by opening that URL —
|
|
433
|
+
`figura fig open <id>` — and screenshotting the hosted page. Steps 1 and 3
|
|
434
|
+
above assume printable bytes and do not apply; `figura fig verify <id>` still
|
|
435
|
+
reports on the parent's own markup. Its `notes` rail and pins are the
|
|
436
|
+
PARENT's markup, so they render and measure the same way.
|
|
353
437
|
|
|
354
438
|
A **wipe-slider** stays the narrow tool for exactly two states of the same
|
|
355
439
|
layout with pixel-level tweaks. Never ship a squeezed side-by-side as the
|
|
@@ -422,7 +506,7 @@ private.
|
|
|
422
506
|
|
|
423
507
|
## Metadata & geometry — stamped for you
|
|
424
508
|
|
|
425
|
-
Every landed fig is **auto-stamped server-side** (
|
|
509
|
+
Every landed fig is **auto-stamped server-side** (`compose`, `create`, `generate`)
|
|
426
510
|
with two things you do **not** hand-write:
|
|
427
511
|
|
|
428
512
|
- A **device kit** `<style>` — the target's hard geometry as CSS custom
|
|
@@ -435,53 +519,26 @@ with two things you do **not** hand-write:
|
|
|
435
519
|
search and the self-improving loop. Artifact-descriptive only — no team/brand
|
|
436
520
|
ids (the block is served on the public link).
|
|
437
521
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
presentation model (frameless single vs canvas composition), when to emit or
|
|
444
|
-
omit the whole-fig `<meta name="figura:artboard">`, the `.fig-device` /
|
|
445
|
-
`data-device` frame markup (Figura stamps the bezel, Dynamic Island, and
|
|
446
|
-
safe-area insets **before** your `<style>`, so you normally leave those classes
|
|
447
|
-
alone — if you do style one, your rule wins by cascade and the frame still
|
|
448
|
-
works; to shrink a frame use the `.fig-mini` wrapper, never a hand-written
|
|
449
|
-
`.fig-device` width/height/transform), the `figura:context` / `figura:feature`
|
|
450
|
-
metas, and the valid devices all live in the canonical scaffold.
|
|
451
|
-
|
|
452
|
-
**Scaling a framed device — use `.fig-mini`, never a rule on `.fig-device`.**
|
|
453
|
-
An overview grid needs shrunken frames, and the obvious way to write that is a
|
|
454
|
-
CSS rule on the injected class. Don't: your rule wins over the kit, so the frame
|
|
455
|
-
stops matching real device geometry — and `zoom` is worse, it leaks the unzoomed
|
|
456
|
-
width into scrollable overflow. The kit ships the wrapper instead:
|
|
457
|
-
|
|
458
|
-
```html
|
|
459
|
-
<div class="fig-mini" style="--fig-scale:.34">
|
|
460
|
-
<div class="fig-device portrait" data-device="iphone-16">
|
|
461
|
-
<div class="fig-screen">…</div><div class="fig-di"></div><div class="fig-homebar"></div>
|
|
462
|
-
</div>
|
|
463
|
-
</div>
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
`.fig-mini` scales its device with `transform` and takes the **scaled** layout
|
|
467
|
-
box, so a row of minis lays out at its visual size instead of reserving
|
|
468
|
-
full-size phones. Add `landscape` to the device (or the wrapper) and the box
|
|
469
|
-
flips with it. `--fig-scale` defaults to `.34`; set it per element.
|
|
522
|
+
The rest of this section applies ONLY to the **`figura_create_fig`** path (hard
|
|
523
|
+
rule 3), where you hand-author the whole document: the exact rules are
|
|
524
|
+
**fetched, not restated here** — the presentation model, the browser frame
|
|
525
|
+
contract, the `figura:artboard` / `figura:context` / `figura:feature` metas all
|
|
526
|
+
live in the canonical scaffold.
|
|
470
527
|
|
|
471
528
|
- **`figura_fig_scaffold`** (CLI: `figura fig scaffold`) is authoritative for the
|
|
472
529
|
presentation model, the frame markup, and the output contract. Fetch it and
|
|
473
530
|
follow it verbatim (step 2 above) — do NOT restate design/format rules from
|
|
474
531
|
memory. A bundled skill can pin stale; the scaffold is always current.
|
|
475
|
-
- **`figura_fig_devices`** (CLI: `figura fig devices`) is the
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
composition's `<div class="fig-device" data-device="…">`; an unknown id
|
|
479
|
-
silently gets no frame.
|
|
532
|
+
- **`figura_fig_devices`** (CLI: `figura fig devices`) is the catalog `compose`
|
|
533
|
+
validates against — pick each screen's **`device`** id from it (an unknown id
|
|
534
|
+
is a 400).
|
|
480
535
|
|
|
481
536
|
## Notes
|
|
482
537
|
|
|
483
|
-
- **MCP unavailable** (headless / cron, or no interactive auth):
|
|
484
|
-
|
|
538
|
+
- **MCP unavailable** (headless / cron, or no interactive auth): the CLI has no
|
|
539
|
+
`compose`, so a phone fig has no CLI path — say so and stop (hard rule 3). For
|
|
540
|
+
a web/`document` fig fall back to
|
|
541
|
+
`bunx figura-cli fig create` / `fig publish`, with `fig revise` /
|
|
485
542
|
`fig revisions` for versions, `fig commit` / `fig uncommit` (or `--commit` on
|
|
486
543
|
the final create/revise) for the team library, `bunx figura-cli fig scaffold`
|
|
487
544
|
for the authoring scaffold, and `bunx figura-cli brand --json` for the raw
|
package/skills/skills.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$comment": "Skill version manifest (#752) — the source of truth for each Figura skill's OWN version line, DECOUPLED from the figura-cli package version. A release bumps the version here when a skill's content changes, then regenerates the engine seed (bun packages/core/scripts/build-skill-seed.mjs) and re-seeds. Version is a date tag (YYYY.MM.DD[.n]); releasedAt is ISO-8601. The CLI/MCP stamp this version into .figura-version at install; the engine seeds the registry from it.",
|
|
3
3
|
"skills": {
|
|
4
4
|
"fig": {
|
|
5
|
-
"version": "2026.08.
|
|
6
|
-
"releasedAt": "2026-08-
|
|
5
|
+
"version": "2026.08.11.1",
|
|
6
|
+
"releasedAt": "2026-08-11T17:35:23.364Z"
|
|
7
7
|
},
|
|
8
8
|
"figura-brand": {
|
|
9
9
|
"version": "2026.07.26",
|