figura-cli 0.18.1 → 0.20.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.18.1",
3
+ "version": "0.20.0",
4
4
  "description": "Command-line client for the Figura visualization SaaS",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,6 +36,7 @@
36
36
  "@panofm/figura-core": "workspace:*",
37
37
  "@types/node": "^20.14.0",
38
38
  "axios": "^1.7.9",
39
+ "package-manager-detector": "^1.8.0",
39
40
  "postgres": "^3.4.9",
40
41
  "typescript": "^5.5.0"
41
42
  }
@@ -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_generate_fig, mcp__figura__figura_create_fig, mcp__figura__figura_revise_fig, mcp__figura__figura_publish_fig, mcp__figura__figura_commit_fig, 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_generate_fig, mcp__figura__figura_create_fig, mcp__figura__figura_revise_fig, mcp__figura__figura_publish_fig, mcp__figura__figura_commit_fig, Bash(figura *), Bash(bunx figura-cli *)'
6
6
  ---
7
7
 
8
8
  # /fig — figure it out before you build it
@@ -171,7 +171,11 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
171
171
  change). If you truly couldn't locate the source, say so in the brief and label
172
172
  the baseline an approximation, same as the hand-build fallback.
173
173
 
174
- 4. **Open the HOSTED fig for approval — never a local file.** Run
174
+ 4. **Verify, then open the HOSTED fig for approval — never a local file.** First
175
+ run **`figura fig verify <id>`**: the component CSS is stamped server-side, so
176
+ only the served bytes can tell a working fig from one that renders as empty
177
+ phones. It exits non-zero if a frame class shipped unsized — fix and re-land
178
+ before a human sees it. Then run
175
179
  `figura fig open <id>` (the id `figura_create_fig`/`figura_generate_fig` just
176
180
  returned). It opens **`viewUrl`** in the reviewer's browser — the actual
177
181
  Figura render (brand chrome, CSP, favicon, responsive frame), team-scoped so
@@ -227,7 +231,14 @@ truncation, and doesn't need a library):
227
231
  status chip and ONE condensed context line (`+ / –`). This view is for
228
232
  scanning and picking a direction, not for reading detail. The grid must
229
233
  wrap to one column before it can overflow — **a fig never scrolls
230
- horizontally**, at any viewport.
234
+ horizontally**, at any viewport. To shrink the framed phones, set the kit's
235
+ **`--fig-scale`** custom property (e.g. `style="--fig-scale:.42"` on each
236
+ `.fig-device`, or on the grid container — it inherits) — Figura shrinks the
237
+ whole framed phone and reserves the scaled layout box so the minis tile
238
+ without overlapping. **Do NOT** write your own rule on `.fig-device`
239
+ (a `width`/`height` or `transform: scale()`): `--fig-scale` is the one
240
+ sanctioned scaling knob, and hand-sizing the frame fights the injected
241
+ geometry.
231
242
  - **Focus — full frame.** One state at a time behind state pills (Today / A /
232
243
  B / …), each state framed identically so flipping states in place reads as
233
244
  spot-the-difference. This is where the context lives, pinned on the frame
@@ -242,20 +253,30 @@ truncation, and doesn't need a library):
242
253
  arguing the pick against the others. Keep the chrome around the frames
243
254
  minimal — the frames are the content; no legends (chips label themselves),
244
255
  no repeated context, fluid `max-width` layout throughout.
245
- - **Verify before publishing.** Render the fig headless at a few viewport
246
- widths: `document.scrollWidth` must equal the viewport in every view and
247
- state (sideways scroll = broken beware CSS `zoom`, which leaks unzoomed
248
- width into scrollable overflow; scale minis with `transform` in
249
- fixed-aspect wrappers instead), and screenshot each state to check no
250
- annotation chip covers the content it annotates.
251
- When you write the fig's HTML out to render/screenshot it, save it to a
252
- **deterministic**, project-scoped path the repo's `.figura/figs/` (already
253
- gitignored), or `~/.figura/figs/` outside a bound repo — never `/tmp` or
254
- `mktemp`. A stable path means a rerun overwrites the same file instead of
255
- littering volatile temp locations, and matches where `figura fig open --local`
256
- writes (`<repo>/.figura/figs/figura-fig-<id>.html`) this is YOUR headless
257
- self-check copy, separate from the hosted `viewUrl` a human reviewer opens
258
- (step 4 above).
256
+ - **Verify the SERVED fig, not your local draft.** The component kit (device
257
+ frame, status bar, control bar) is stamped **server-side at save** — it does
258
+ **not** exist in the HTML you authored locally, so a headless render of your
259
+ own file shows *unstyled, empty* frames whether or not the fig is actually
260
+ correct. A local screenshot is structurally blind to the most common way a
261
+ framed fig breaks. So verify the fig that actually ships:
262
+ 1. **Land it first** (`figura_create_fig` / `figura_revise_fig`), then dump
263
+ the stamped bytes: `figura fig open <id> --local --print`. This is the
264
+ served document, kit and all.
265
+ 2. **Assert the kit stamped `figura fig verify <id>`.** One command, and it
266
+ **exits non-zero** if any frame class your markup uses shipped with no CSS
267
+ giving it dimensions. It checks every injected component (device frame,
268
+ status bar, control bar, browser chrome), cross-checks the manifest's
269
+ `components` array, and reports the fig's own `frameKit` / `complete` /
270
+ `doubleFramed` checks; `--json` for machine output. The engine won't
271
+ silently drop the kit anymore, and create/revise now warns loudly if it
272
+ ever did — this is the check that *proves* a frame renders.
273
+ 3. **Screenshot THAT** (the printed served HTML, saved to a deterministic
274
+ project-scoped path — the repo's `.figura/figs/` (gitignored), or
275
+ `~/.figura/figs/` outside a bound repo, never `/tmp`/`mktemp`), at a few
276
+ viewport widths: `document.scrollWidth` must equal the viewport in every
277
+ view and state (sideways scroll = broken), and no annotation chip may
278
+ cover the content it annotates. `figura fig open <id> --local` writes the
279
+ same served bytes to `<repo>/.figura/figs/figura-fig-<id>.html`.
259
280
 
260
281
  A **wipe-slider** stays the narrow tool for exactly two states of the same
261
282
  layout with pixel-level tweaks. Never ship a squeezed side-by-side as the
@@ -316,62 +337,26 @@ with two things you do **not** hand-write:
316
337
  search and the self-improving loop. Artifact-descriptive only — no team/brand
317
338
  ids (the block is served on the public link).
318
339
 
319
- So when you **hand-build** a fig (the default path), do your part of the contract:
320
-
321
- - **Bare mobile screen and ONLY then:** when the fig's HTML *is* one raw app
322
- screen (no masthead, no state pills, no hand-drawn `.fig-device` frames — the
323
- Figura viewer draws the iPhone shell around it), size the body to the exact
324
- logical screen and emit `<meta name="figura:artboard" content="WxH">` matching
325
- it (iPhone portrait `393x852` / landscape `852x393`). For OS-chrome insets
326
- prefer `var(--fig-safe-top, 59px)` / `.fig-safe-top` over hardcoded pixels
327
- the stamp fills the value, the fallback keeps you correct if the kit is absent.
328
- - **A document-style fig NEVER gets an artboard.** The artboard meta is the
329
- viewer's opt-in signal for "size me to exactly WxH and wrap me in a device
330
- shell" stamped on an overview focus redesign page (or any compare /
331
- variations document with its own masthead, pills, or hand-drawn device
332
- frames), it crams the whole document into a phone frame. A document fig
333
- renders full-page precisely because it carries NO artboard meta, even when
334
- every screen it depicts is a mobile app.
335
- - **Always:** emit `<meta name="figura:context" content="...">` (design intent).
336
- - **Optional but valued:** if the fig grounds a specific feature or flag, emit
337
- `<meta name="figura:feature" content="<flag-or-slug>">` so it links back to the
338
- feature it depicts.
339
-
340
- ### The two mobile presentations — pick ONE, never both (#717)
341
-
342
- A mobile fig is EITHER a frameless single OR a canvas composition. Emitting both
343
- signals — a whole-fig `figura:artboard` meta AND an in-fig `.fig-device` frame —
344
- makes the viewer wrap the *whole* composition in one phone (the key, masthead,
345
- and second screen all crammed inside the bezel). That contradiction is flagged
346
- (`double-framed`), so choose deliberately:
347
-
348
- - **Frameless single** — the whole fig IS one screen. Whole-fig `figura:artboard`
349
- meta, `<body>` sized to exactly W×H, body == the screen, no surrounding HTML.
350
- The viewer draws the phone around the entire fig. Best fidelity for "just the
351
- screen".
352
- - **Canvas composition** — a document that frames one or more devices **in-fig**,
353
- with author HTML around them (a design-language key, annotations, multiple
354
- screens, states with controls). **No whole-fig `figura:artboard` meta.** Frame
355
- each target device as a bounded `.fig-device` element and Figura stamps the
356
- bezel + Dynamic Island + safe-area insets for that device (do NOT author CSS
357
- for `.fig-device` / `.fig-screen` / `.fig-di` / `.fig-homebar`):
358
-
359
- ```html
360
- <div class="fig-device portrait" data-device="iphone-16">
361
- <div class="fig-screen fig-safe"> …screen content… </div>
362
- <div class="fig-di"></div> <!-- Dynamic Island / notch, drawn for you -->
363
- <div class="fig-homebar"></div> <!-- home indicator, drawn for you -->
364
- </div>
365
- ```
366
-
367
- Use `class="fig-device landscape"` for a landscape device (the island moves to
368
- the leading edge automatically). `data-device` is any catalog id
369
- (`figura fig scaffold` / `brand --json` list them); **different `data-device`
370
- values on one canvas frame different phones** — an `iphone-16` beside an
371
- `iphone-se-3`, each with its own bezel radius and cutout. Put your key,
372
- annotations, and any other screens as ordinary HTML AROUND the framed
373
- device(s). A composition renders full-page (no whole-fig meta) so nothing is
374
- phone-wrapped as a whole.
340
+ So when you **hand-build** a fig (the default path), you do your part of the
341
+ contract — but the exact rules are **fetched, not restated here**: the
342
+ presentation model (frameless single vs canvas composition), when to emit or
343
+ omit the whole-fig `<meta name="figura:artboard">`, the `.fig-device` /
344
+ `data-device` frame markup (Figura stamps the bezel, Dynamic Island, and
345
+ safe-area insets **before** your `<style>`, so you normally leave those classes
346
+ alone if you do style one, your rule wins by cascade and the frame still
347
+ works; to shrink a frame use the `--fig-scale` custom property, never a
348
+ hand-written `.fig-device` width/height/transform), the `figura:context` /
349
+ `figura:feature` metas, and the valid devices all live in the canonical scaffold.
350
+
351
+ - **`figura_fig_scaffold`** (CLI: `figura fig scaffold`) is authoritative for the
352
+ presentation model, the frame markup, and the output contract. Fetch it and
353
+ follow it verbatim (step 2 above) do NOT restate design/format rules from
354
+ memory. A bundled skill can pin stale; the scaffold is always current.
355
+ - **`figura_fig_devices`** (CLI: `figura fig devices`) is the full valid-
356
+ `data-device` catalog every framed-device id + its geometry (logical dims,
357
+ safe-area insets, cutout, corner radius). Pick a real id from it for a canvas
358
+ composition's `<div class="fig-device" data-device="">`; an unknown id
359
+ silently gets no frame.
375
360
 
376
361
  ## Notes
377
362