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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figura-cli",
3
- "version": "0.23.2",
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",
@@ -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, **`fig revise` it**
41
- (a new version in the same chain), never a fresh duplicate.
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 device
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. Do **NOT** draw a frame, bezel, status
134
- bar, or safe padding; do **NOT** set `--fig-safe-*`; do **NOT** wrap it in
135
- `.fig-device`. For every device layout each screen also carries the typed
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
- **`figura_create_fig` is the escape hatch** reach for it only for a
166
- `document`/exotic fig where you must hand-author the entire HTML (or when
167
- `compose` is unavailable on an older engine). It takes a full `html` document
168
- and you own the whole contract yourself. For any device fig, prefer
169
- `compose` — it can't drift.
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 `figura_create_fig`/`figura_generate_fig` just
224
- returned). It opens **`viewUrl`** in the reviewer's browser — the actual
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, or fall back to `figura fig open <id> --local` for a local sandboxed
231
- HTML preview.
232
-
233
- 5. **Iterate against the SAME fig, not a fresh one.** Changes from review go
234
- through **`figura_revise_fig`** (`id`, the updated `html`) — it joins the
235
- existing chain as a new version and returns a fresh `viewUrl`. Publishing is
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 create/revise anyway, prefer
259
- `commit: true` on that final `figura_create_fig` / `figura_revise_fig`
260
- call (CLI: `--commit`) over a separate call; otherwise commit it afterwards
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. (If you're on the `create` escape hatch instead, the same
284
- pattern must be hand-authored plain CSS-only radio tabs, binary, no library:)
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, pinned on the
297
- frame as annotation chips: what's broken today, what each variation improves,
298
- what it trades away. Open on the first pill = the honest current state.
299
- - **Never put annotation chips inside a clipping box.** `position:absolute`
300
- cannot escape an ancestor's `overflow:hidden`, so a chip that straddles a
301
- rounded-corner frame ships sliced. Hang chips off a positioned wrapper as
302
- siblings of the frame, anchored to grow away from its edge.
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. To shrink the framed phones, wrap each in the
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); color-code chips by function (broken / improved /
316
- trade-off / detail) using the team's status colors from the fetched design
317
- system; mark the recommended variation and close with a short "my read"
318
- arguing the pick against the others. Keep the chrome around the frames
319
- minimal the frames are the content; no legends (chips label themselves),
320
- no repeated context, fluid `max-width` layout throughout.
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** (`figura_create_fig` / `figura_revise_fig`), then dump
328
- the stamped bytes: `figura fig open <id> --local --print`. This is the
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), no annotation chip may cover the content it
345
- annotates, and no chip may be cut by a clipping ancestor: walk each chip's
346
- ancestors up to `<html>` and, wherever computed `overflow` is
347
- `hidden`/`clip`, assert the chip's rect stays inside that ancestor's box
348
- the viewport box for `<body>`/`<html>`, whose overflow propagates there.
349
- Comparing a chip to the frame's rect alone misses the vertical escape, and
350
- no rect test sees a rounded clip, which is why the authoring rule above is
351
- the real guarantee. `figura fig open <id> --local` writes the same served
352
- bytes to `<repo>/.figura/figs/figura-fig-<id>.html`.
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** (on `create` and `generate`)
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
- With **`figura_compose_fig`** you do NOT hand-write any of the frame/safe/device
439
- contract below Figura stamps it from your typed params. The rest of this
440
- section applies ONLY to the **`figura_create_fig` escape hatch**, where you
441
- hand-author the whole document: the exact rules are **fetched, not restated
442
- here** the
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 full valid-
476
- `data-device` catalogevery framed-device id + its geometry (logical dims,
477
- safe-area insets, cutout, corner radius). Pick a real id from it for a canvas
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 againstpick 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): fall back to the
484
- CLI `bunx figura-cli fig create` / `fig publish`, with `fig revise` /
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
@@ -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.04",
6
- "releasedAt": "2026-08-04T17:44:19.938Z"
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",