figura-cli 0.25.0 → 0.26.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 +242 -227
- package/dist/mcp.js +53 -53
- package/package.json +1 -1
- package/skills/fig/SKILL.md +108 -5
- package/skills/figura-brand/SKILL.md +49 -4
- package/skills/figura-brand/reference/brand-format.md +32 -7
- package/skills/skills.json +5 -5
package/package.json
CHANGED
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_upload_asset, 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_merge_figs, 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
|
|
@@ -62,6 +62,20 @@ something in a vacuum — and end up rebuilding what already ships. So every tim
|
|
|
62
62
|
`figura_list_figs`). If a fig of this screen already exists, **iterate that
|
|
63
63
|
fig** (step 5), never a fresh duplicate.
|
|
64
64
|
|
|
65
|
+
★ **The chain is the unit — and Figura now enforces it.** A create that looks
|
|
66
|
+
like the next round of an open question is **refused** (409 `similar_chain`) and
|
|
67
|
+
the response names the chain to revise. That is not an obstacle to route around:
|
|
68
|
+
each round you land as its own fig is a card in the library nobody can walk, and
|
|
69
|
+
a reviewer who opens round 4 sees neither rounds 1–3 nor the discussion held on
|
|
70
|
+
them. Revise the named fig. `new: true` (CLI `--new`) exists for a genuinely
|
|
71
|
+
different question — not for "revising felt like more work".
|
|
72
|
+
|
|
73
|
+
**Already forked it?** `figura_merge_figs` (CLI `figura fig merge <id> --into
|
|
74
|
+
<id>`) folds the sibling back in: its versions append after the destination's
|
|
75
|
+
head, share links keep resolving, verdicts and decisions follow. Run it with
|
|
76
|
+
`dry_run` first to see the plan. Merging your own fork mid-session is normal
|
|
77
|
+
housekeeping, not an admission of anything.
|
|
78
|
+
|
|
65
79
|
If any prior design exists → build in **`mode: compare`** and render it as
|
|
66
80
|
**existing (as-is) → proposed**, grounded in the real current state. Even when the
|
|
67
81
|
proposal is better, it must be argued against what's there — that's the whole
|
|
@@ -178,7 +192,9 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
|
|
|
178
192
|
- **`sharedCss`** — ONE stylesheet every screen shares: put your brand tokens
|
|
179
193
|
(from the scaffold) and shared content classes here. Per-screen one-offs go
|
|
180
194
|
in that screen's `css`.
|
|
181
|
-
- Plus `area`, `title`, `orientation`, and a real `description
|
|
195
|
+
- Plus `area`, `title`, `orientation`, and a real `description` — the fig's
|
|
196
|
+
BRIEF, and the reviewer's only view of the thread. Contract: "The brief"
|
|
197
|
+
below.
|
|
182
198
|
|
|
183
199
|
**Small screens are the SERVER's job — with one exception that is yours.**
|
|
184
200
|
Below a catalog-derived breakpoint every multi-frame layout (`compare`,
|
|
@@ -280,7 +296,8 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
|
|
|
280
296
|
`html` for a composed fig is refused (422) — it would leave the references
|
|
281
297
|
behind. `figura_get_fig` returns the fig's `children` when you need to see the
|
|
282
298
|
current composition. Never hand-author the framed document to feed `revise`
|
|
283
|
-
(hard rule 1).
|
|
299
|
+
(hard rule 1). A revision takes its own `description` — restate the arc
|
|
300
|
+
there; whoever reads v4 did not read v1. Publishing is
|
|
284
301
|
NOT inherited automatically: a revision of an already-published fig still
|
|
285
302
|
lands unpublished by default (`viewUrl` is a team-scoped view, and the
|
|
286
303
|
existing public link keeps serving the OLD version) — pass `publish: true`
|
|
@@ -317,6 +334,8 @@ so you hand it the studied structure in the brief (see step 3, "Server render").
|
|
|
317
334
|
**`figura_request_feedback`** (CLI: `figura fig request-feedback <id> --from
|
|
318
335
|
<name|email> --note <text>`). See "Assign a reviewer" below — this step is
|
|
319
336
|
what stops a fig from being landed, committed, and never actually read.
|
|
337
|
+
The note names the decision; the fig's `description` carries the context —
|
|
338
|
+
check it against "The brief" below *before* you send the ask.
|
|
320
339
|
|
|
321
340
|
## Presenting a redesign — FOCUS first, overview second
|
|
322
341
|
|
|
@@ -339,6 +358,27 @@ using it for one shipped a 4-state fig as 134px phones with ~3px annotation
|
|
|
339
358
|
chips. A Focus-only fig is **complete on its own**; add Overview only when there
|
|
340
359
|
are enough states that scanning them at once genuinely helps.
|
|
341
360
|
|
|
361
|
+
**★ THE LAST PILL IS YOUR READ — a fig that shows options must pick one.**
|
|
362
|
+
Whenever a fig carries more than one *direction* (A / B / …, or any "which way
|
|
363
|
+
do we go" set), the FINAL state is a **verdict state**, not one more variation:
|
|
364
|
+
the options weighed against each other, and one of them named as the pick.
|
|
365
|
+
Without it a reviewer gets a gallery and no argument, and the decision lands back
|
|
366
|
+
on whoever opened the link — which is the exact thing a fig exists to prevent.
|
|
367
|
+
|
|
368
|
+
The verdict state is one framed screen like any other, labelled `My read`, and
|
|
369
|
+
it carries, tersely:
|
|
370
|
+
|
|
371
|
+
- **the pick, named in the first line** — "Go with A" — never "both have merit";
|
|
372
|
+
- **one line per option** of what it wins and what it costs (`+` / `–`) — the
|
|
373
|
+
same pairs the Overview chips carry, so the two views can't disagree;
|
|
374
|
+
- **the deciding reason**: the one constraint that broke the tie (a component
|
|
375
|
+
that already ships, a cap, a rule the others violate);
|
|
376
|
+
- **what would change your mind**, when the pick is close.
|
|
377
|
+
|
|
378
|
+
Two states of the SAME direction (a before/after, one layout at two
|
|
379
|
+
orientations) need no verdict — there is nothing to weigh. Three directions and
|
|
380
|
+
no verdict pill is an unfinished fig.
|
|
381
|
+
|
|
342
382
|
- **Focus — full frame (default).** One state at a time behind state pills
|
|
343
383
|
(Today / A / B / …), each state framed identically so flipping states in place
|
|
344
384
|
reads as spot-the-difference. This is where the context lives: what's broken
|
|
@@ -374,6 +414,21 @@ are enough states that scanning them at once genuinely helps.
|
|
|
374
414
|
same rule holds by hand: the rail is a **sibling of the frame**, never a
|
|
375
415
|
descendant of the clipped `.fig-screen`, and nothing but a pin sits on the
|
|
376
416
|
design.
|
|
417
|
+
- **Every direction gets the SAME treatment.** If one direction is shown in a
|
|
418
|
+
second orientation, a second state, or a second breakpoint, they ALL are.
|
|
419
|
+
A subset argues from unequal evidence and quietly favours whichever direction
|
|
420
|
+
got the extra look: one fig shipped `Today ↻` and `A ↻` pills while B and
|
|
421
|
+
Fresh had no landscape frame at all, so two directions were judged turned and
|
|
422
|
+
two were judged only upright. **Cover the orientations the product actually
|
|
423
|
+
ships** — on an app that is landscape-native, that means portrait AND
|
|
424
|
+
landscape for every direction, by default, not as an extra. Narrow to one
|
|
425
|
+
orientation only when the work itself is scoped to one: a portrait-only bug,
|
|
426
|
+
or a conversation already about the turn, doesn't need its opposite.
|
|
427
|
+
- **A pill label is plain TEXT.** `label` is a text field, not markup — write
|
|
428
|
+
the character (`·`, `↻`, `—`), never an HTML entity. The same call carries
|
|
429
|
+
each screen's `html`, where `·` is exactly right, so the habit crosses
|
|
430
|
+
over easily; the composer decodes an entity it finds in a label rather than
|
|
431
|
+
shipping it literally, but the label still means text.
|
|
377
432
|
- **Overview — side by side (alternative).** Every state at once: the honest
|
|
378
433
|
current state first, then each variation, as scaled-down frames in a grid.
|
|
379
434
|
Under each: a status chip and ONE condensed context line (`+ / –`). This view
|
|
@@ -387,8 +442,8 @@ are enough states that scanning them at once genuinely helps.
|
|
|
387
442
|
(`broken` / `improved` / `tradeoff` / `detail`) rather
|
|
388
443
|
than hand-picking colors — the rail and its pins take the team's status colors
|
|
389
444
|
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
|
|
391
|
-
|
|
445
|
+
friends re-points all of them at once; and mark the recommended direction in
|
|
446
|
+
both — the pick the verdict state argues for wears its chip here too. Keep the
|
|
392
447
|
chrome around the frames minimal — the frames are the content; no legends
|
|
393
448
|
(notes label themselves), no repeated context, fluid `max-width` throughout.
|
|
394
449
|
- **Verify the SERVED fig, not your local draft.** The component kit (device
|
|
@@ -479,6 +534,54 @@ Example — a GSAP-driven entrance for a mobile screen, pinned + allowlisted:
|
|
|
479
534
|
</script>
|
|
480
535
|
```
|
|
481
536
|
|
|
537
|
+
## The brief — what the reviewer reads before your fig
|
|
538
|
+
|
|
539
|
+
**`description` is the fig's BRIEF, not a caption.** Figura renders it as the
|
|
540
|
+
**Brief** panel beside the fig, above the discussion: it is the first thing the
|
|
541
|
+
person you assign in step 8 reads, and across a multi-round exercise it is the
|
|
542
|
+
only place the *thread* survives — each round lands as its own fig, and nothing
|
|
543
|
+
in the product links them. The fig argues THIS round. The brief carries what
|
|
544
|
+
happened before it.
|
|
545
|
+
|
|
546
|
+
★ **Four beats, in this order.**
|
|
547
|
+
|
|
548
|
+
1. **The objective, in the user's own words.** Quote the sentence that started
|
|
549
|
+
the exercise, verbatim — and re-quote it in every later round. It is the
|
|
550
|
+
standard the fig gets judged against, and the first thing to evaporate when a
|
|
551
|
+
round is written from memory.
|
|
552
|
+
2. **The arc — one line per prior round: what it tried, and what it settled or
|
|
553
|
+
killed.** Name each round so the reviewer can go and find it.
|
|
554
|
+
3. **What is still open** — the question this round does *not* close.
|
|
555
|
+
4. **The pick, and the decision you want back.**
|
|
556
|
+
|
|
557
|
+
★ **An abandoned direction stays in the brief, with the reason it was
|
|
558
|
+
abandoned.** "Withdrawn", "superseded", "we tried that" are deletions, not
|
|
559
|
+
summaries. A dead route is the most useful thing a reviewer can be handed: it
|
|
560
|
+
stops them re-proposing it, and it is usually where the unresolved tension is
|
|
561
|
+
still sitting. And when a round was killed by *evidence* — a real user rejected
|
|
562
|
+
it, a constraint bit, a measurement came back — that evidence outranks every
|
|
563
|
+
design argument in the brief, and has to survive into the next round's.
|
|
564
|
+
|
|
565
|
+
★ **Never write a later round's brief from session memory.** Before composing a
|
|
566
|
+
fig that continues, supersedes, or replaces an earlier one, read the earlier
|
|
567
|
+
briefs — `figura_list_revisions` for the chain you're revising, or
|
|
568
|
+
`figura_list_figs` (by `area`/`tag`) then `figura_get_fig` — and carry the
|
|
569
|
+
objective and the arc forward. A long session compresses; the brief must
|
|
570
|
+
not. The prior fig is on the server, so this costs one call and never depends on
|
|
571
|
+
what is still in your context.
|
|
572
|
+
|
|
573
|
+
**Which field is which.** `description` is the brief, on every write path
|
|
574
|
+
(`figura_compose_fig` / `figura_create_fig` / `figura_revise_fig` /
|
|
575
|
+
`figura_update_fig`), and it is carried per version — so **a revision restates
|
|
576
|
+
the arc** instead of assuming the reader saw v1. Cap: 2000 characters. `prompt`
|
|
577
|
+
is a different field: the raw brief/instruction you were handed, kept for
|
|
578
|
+
provenance. `intent` (the `figura:context` meta) is a one-line design intent for
|
|
579
|
+
search and is **truncated at 400 characters** — never park the objective there.
|
|
580
|
+
|
|
581
|
+
**The brief is not the fig.** Don't narrate what the frames already show. If the
|
|
582
|
+
brief and the fig disagree about the recommendation, one of them is stale — fix
|
|
583
|
+
it before you send the ask.
|
|
584
|
+
|
|
482
585
|
## Assign a reviewer — a proposal fig isn't done until someone's on it
|
|
483
586
|
|
|
484
587
|
**A link nobody was pointed at is a fig nobody saw.** Landing, publishing, and
|
|
@@ -71,10 +71,47 @@ Hunt across whatever stack this repo uses — do not assume one framework:
|
|
|
71
71
|
`src/routes/*`, `src/screens/*`). These become each platform's `surfaces[]` —
|
|
72
72
|
THIS product's real areas ("Feed", "Checkout"), never a generic placeholder
|
|
73
73
|
list and never another product's.
|
|
74
|
+
- **Locales** — which languages the product ships, their script and direction.
|
|
75
|
+
Run `figura brand detect-locales [path]`: an offline scan returning
|
|
76
|
+
`locales` (paste-ready `identity.locales`), `evidence` (why each locale got its
|
|
77
|
+
script/direction), `needsConfirmation`, and the `signals` it fired on. Put the
|
|
78
|
+
DEFAULT language first.
|
|
79
|
+
|
|
80
|
+
Three rules, each from a real miss on a real repo (#998):
|
|
81
|
+
|
|
82
|
+
- **NEVER list a vendor directory's languages.** `node_modules`, `Pods`,
|
|
83
|
+
`dist`, `build`, `.next`, `out`, `vendor`, `DerivedData`, `.git`, `.claude`
|
|
84
|
+
are not this product. A React Native monorepo's `node_modules` alone carries
|
|
85
|
+
40+ `*.lproj` languages; listing them makes the brand claim languages the
|
|
86
|
+
product does not ship. `detect-locales` drops them itself — if you scan by
|
|
87
|
+
hand instead, exclude every one of those path segments.
|
|
88
|
+
- **Script and direction come from the message TEXT, not the tag.** A repo can
|
|
89
|
+
tag Sorani Kurdish `ku`, which any tag table calls Latin/LTR — SHA7N's
|
|
90
|
+
`locale/ku/general.json` is 4848 Arabic codepoints. Read the locale's message
|
|
91
|
+
files and judge from the characters. Arabic script plus any of ڕ ڵ ێ ۆ is
|
|
92
|
+
Sorani → emit the tag **`ckb`** (that is the tag the per-script font
|
|
93
|
+
resolver's Sorani coverage check keys on; `ku` would let a font missing those
|
|
94
|
+
letters through), not `ar` and not `ku`.
|
|
95
|
+
- **Ambiguous tags are a question, not a guess.** `ku ks pa sd az uz tk ha ber
|
|
96
|
+
mn` are each written in more than one script depending on region or
|
|
97
|
+
orthography. With message content, classify from it. Without, they arrive in
|
|
98
|
+
`needsConfirmation` — ASK the user which script that language uses in this
|
|
99
|
+
product, and do not paste the locale until they answer.
|
|
100
|
+
|
|
101
|
+
Show the user the `evidence` line for each locale alongside the JSON, so a
|
|
102
|
+
wrong call is visible rather than silent.
|
|
103
|
+
|
|
104
|
+
Manual signals if the CLI is unavailable: i18n deps in `package.json`
|
|
105
|
+
(`next-intl`, `react-i18next`, `i18next`, `@lingui/*`, `vue-i18n`), an i18n
|
|
106
|
+
config's `locales: [...]` array, `locale/*` / `locales/*.json` /
|
|
107
|
+
`messages/*.json` dirs, `dir="rtl"` in markup, iOS `*.lproj/` dirs +
|
|
108
|
+
`Localizable.strings`, Android `values-ar/strings.xml`.
|
|
74
109
|
- **Which platform(s)** — detect the target platform(s) so you only emit the
|
|
75
110
|
surfaces that apply:
|
|
76
111
|
- `mobile` — an iOS/Android/React-Native/Expo app (`*.xcodeproj`, `Info.plist`,
|
|
77
112
|
`android/`, `app.json`/Expo, SF Symbols usage, safe-area/tab-bar tokens).
|
|
113
|
+
Note WHICH OS it targets — an `android/` + Gradle + Material repo is
|
|
114
|
+
`os: "android"`, an `*.xcodeproj`/SwiftUI repo is `os: "ios"`.
|
|
78
115
|
- `web` — a web app / site (`index.html`, a web router, CSS media queries,
|
|
79
116
|
hover states, a `tailwind.config`).
|
|
80
117
|
- `desktop` — an Electron/Tauri/native-desktop shell.
|
|
@@ -93,14 +130,22 @@ EXACTLY (strict keys, hex 3/4/6/8 digits, no blank array entries, only
|
|
|
93
130
|
|
|
94
131
|
- **name** — the product/brand name (still required on the write).
|
|
95
132
|
- **identity** — the SHARED brand: `colors` (real palette, each
|
|
96
|
-
`{ name, hex, role?, meaning? }`), `gradient` (if any), `
|
|
133
|
+
`{ name, hex, role?, meaning? }`), `gradient` (if any), `signature`
|
|
134
|
+
(`{ kind: solid|gradient|iridescent, stops?: hex[] }` — solid unless the repo's
|
|
135
|
+
tokens define a gradient identity; see `reference/brand-format.md`), `type`
|
|
97
136
|
(`{ families, ramp, rules }`), `spacing`, `radius`, `voice`
|
|
98
|
-
(`{ nouns, promise, lines, tone }`), `
|
|
99
|
-
`
|
|
137
|
+
(`{ nouns, promise, lines, tone }`), `locales` (each
|
|
138
|
+
`{ tag, script, direction, fonts? }` — BCP-47 tag, ISO 15924 script,
|
|
139
|
+
`ltr`/`rtl`; FIRST entry is the default; per-locale `fonts` override the
|
|
140
|
+
shared families for that script, e.g. an Arabic display face), `forbidden`
|
|
141
|
+
(hard guardrails as `[{ id?, rule }]`).
|
|
100
142
|
- **platforms** — a map with a key ONLY for each platform you detected in step 1.
|
|
101
143
|
Each surface: `components` (signature components as `[{ name, spec }]`), `icons`
|
|
102
144
|
(the platform's icon language), `layout` (its frame/shell law), `surfaces` (this
|
|
103
|
-
product's real areas), `bans` (platform-specific don'ts).
|
|
145
|
+
product's real areas), `bans` (platform-specific don'ts). `mobile` ALSO takes
|
|
146
|
+
`os` (`"ios"` | `"android"`) — set it when the repo names its OS; it drives the
|
|
147
|
+
fig's device frame, system-bar heights, back affordance, and corner radii.
|
|
148
|
+
Omit it for an iOS/cross-platform repo (iOS is the default). Use the per-platform
|
|
104
149
|
**`tokensBlock`** escape-hatch ONLY for guidance that genuinely doesn't fit a
|
|
105
150
|
structured field — prefer a structured field.
|
|
106
151
|
|
|
@@ -8,21 +8,41 @@ so it can't drift). This page is only orientation + one worked example.
|
|
|
8
8
|
|
|
9
9
|
Two halves:
|
|
10
10
|
- **`identity`** — the SHARED brand (same on every platform): `colors`
|
|
11
|
-
(`[{ name, hex, role?, meaning? }]`), `gradient?`,
|
|
12
|
-
(`{
|
|
13
|
-
`
|
|
14
|
-
(`
|
|
11
|
+
(`[{ name, hex, role?, meaning? }]`), `gradient?`,
|
|
12
|
+
`signature?` (`{ kind: "solid" | "gradient" | "iridescent", stops?: hex[] }`),
|
|
13
|
+
`type?` (`{ families?: { slot: fontName }, ramp?: number[], rules? }`), `spacing?`,
|
|
14
|
+
`radius?`, `voice?` (`{ nouns?, promise?, lines?, tone? }`), `locales?`
|
|
15
|
+
(`[{ tag, script, direction, fonts?: { display?, body?, mono? } }]` — the
|
|
16
|
+
languages the product ships: BCP-47 `tag` ("en", "ar", "ckb"), ISO 15924
|
|
17
|
+
`script` ("Latn", "Arab"), `direction` `ltr`|`rtl`; the FIRST entry is the
|
|
18
|
+
default; per-locale `fonts` override the shared type families for that
|
|
19
|
+
script), `forbidden?` (`[{ id?, rule }]`).
|
|
20
|
+
|
|
21
|
+
`signature` is the brand page's hero treatment. The rule: **solid unless the
|
|
22
|
+
repo's tokens define a gradient identity** — a signature gradient named in the
|
|
23
|
+
design system, not a stray mention of the word (a "avoid gradients" guardrail is
|
|
24
|
+
a solid brand). `gradient`/`iridescent` require 2–5 `stops` (the gradient's own
|
|
25
|
+
hexes, in order). Omit the field entirely for a plain solid-accent brand.
|
|
15
26
|
- **`platforms`** — a map keyed by `mobile` / `web` / `desktop`. Include ONLY the
|
|
16
27
|
platforms the product targets. Each surface: `components?` (`[{ name, spec }]`),
|
|
17
28
|
`icons?`, `layout?`, `surfaces?` (this product's real areas), `bans?`,
|
|
18
29
|
`tokensBlock?` (the prose ESCAPE HATCH for guidance that genuinely doesn't fit a
|
|
19
|
-
structured field — use sparingly).
|
|
30
|
+
structured field — use sparingly). `mobile` also takes `os?` (`"ios"` |
|
|
31
|
+
`"android"`), which drives the fig's device frame and OS chrome geometry;
|
|
32
|
+
unset means iOS. `os` on `web`/`desktop` is rejected.
|
|
20
33
|
|
|
21
34
|
Validation the schema enforces (a violation is a 400 at `PUT /brand`): **strict
|
|
22
35
|
keys** (an unknown/misspelled key is rejected); **hex** = `#` + exactly 3/4/6/8
|
|
23
36
|
digits; **no blank** array entries; platform keys exactly `mobile`/`web`/`desktop`;
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
`locales` when present needs >= 1 entry with unique tags; caps on array lengths +
|
|
38
|
+
string lengths (the fetched schema carries the exact numbers). Author against the
|
|
39
|
+
fetched schema, not these prose notes.
|
|
40
|
+
|
|
41
|
+
Locales drive fig generation: the scaffold states them, `fig create --locale ar`
|
|
42
|
+
(or the `locale` param on `figura_create_fig`/`figura_revise_fig`) stamps
|
|
43
|
+
`<html lang dir>` and loads a per-script font — a family that does not cover the
|
|
44
|
+
script (Inter has no Arabic) resolves to a recorded fallback instead of a silent
|
|
45
|
+
browser substitute.
|
|
26
46
|
|
|
27
47
|
## Worked example — a web/desktop brand ("Ledgerline")
|
|
28
48
|
|
|
@@ -36,6 +56,7 @@ numbers). Author against the fetched schema, not these prose notes.
|
|
|
36
56
|
{ "name": "Ink", "hex": "#101312", "role": "ground", "meaning": "near-black base background" }
|
|
37
57
|
],
|
|
38
58
|
"gradient": "linear-gradient(90deg, #1F6F54, #C9A227)",
|
|
59
|
+
"signature": { "kind": "gradient", "stops": ["#1F6F54", "#C9A227"] },
|
|
39
60
|
"type": {
|
|
40
61
|
"families": { "display": "Fraunces", "body": "Inter", "mono": "JetBrains Mono" },
|
|
41
62
|
"ramp": [12, 14, 16, 18, 20, 24, 30, 36, 48],
|
|
@@ -43,6 +64,10 @@ numbers). Author against the fetched schema, not these prose notes.
|
|
|
43
64
|
},
|
|
44
65
|
"spacing": [4, 8, 12, 16, 20, 24, 32, 40, 48, 64],
|
|
45
66
|
"radius": [4, 8, 12, 999],
|
|
67
|
+
"locales": [
|
|
68
|
+
{ "tag": "en", "script": "Latn", "direction": "ltr" },
|
|
69
|
+
{ "tag": "ar", "script": "Arab", "direction": "rtl", "fonts": { "body": "IBM Plex Sans Arabic" } }
|
|
70
|
+
],
|
|
46
71
|
"voice": {
|
|
47
72
|
"nouns": ["ledger", "balance", "month-end"],
|
|
48
73
|
"promise": "Books, balanced.",
|
package/skills/skills.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "Skill version manifest (#752)
|
|
2
|
+
"$comment": "Skill version manifest (#752) \u2014 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.27",
|
|
6
|
+
"releasedAt": "2026-08-27T00:00:00.000Z"
|
|
7
7
|
},
|
|
8
8
|
"figura-brand": {
|
|
9
|
-
"version": "2026.
|
|
10
|
-
"releasedAt": "2026-
|
|
9
|
+
"version": "2026.08.25.2",
|
|
10
|
+
"releasedAt": "2026-08-26T00:00:00.000Z"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
}
|