Repository navigation
@directededges/specs-cli v0.32.0
A workspace is now a documented, tested library, and screens are first-class in it. Three themes:
- The CLI specs compositions — the frames your library marks ready for dev — so a screen or a pattern becomes a spec and working code the same way a component does.
specs storybookscaffolds and publishes a workspace Storybook, with generated pages for your foundations, components, analyses and visual test results.specs testing visualmeasures your emitted code against your designs, and against the renders you last accepted.
Breaking
-
specs/now names what each folder holds:components/,compositions/andanalysis/. A directory written before this release is read as legacy and says so once per run; re-runspecs generateto write the current layout. Nothing migrates out of_analysis/(ADR-096). -
--combine-as-libraryand--combine-concernscollapse a catalogue per kind, writingcomponents/andcompositions/each their own set of documents, so a composition and a component of the same name can no longer overwrite each other. -
-onames a directory, and a path ending in.yaml,.ymlor.jsonis refused unless both splits are off. A split run writes a folder per component, so a filename was never what you meant — it used to be honoured literally. -
specs scanmatches subcomponent patterns exactly, every character outside{C}and{S}included, spaces and slashes among them. A library spelling a separator two ways now declares a pattern for each (ADR-094). -
specs fetchwrites each file payload as a page-split<alias>.file/directory —root.jsonplus one JSON per Figma page — so a file of any size flows through the pipeline. Existing single-file payloads still read; only scripts opening<alias>.file.jsondirectly need changing.
Added
Compositions
-
specs scanlists compositions in a third manifest section, curated exactly as components are. A frame earns a row by being marked ready for dev in Figma, and inside another marked frame the outermost wins, so one arrangement is never specced twice (ADR-095). -
specs generatewrites checked composition specs tospecs/compositions/<key>/, carryingmetadata.source.nodeType: FRAMEas the marker for what the spec describes.-cnarrows to one by name, id or key, as it does for a component. -
specs reactandspecs webcomponentsemit a composition as one story in aCompositionsnav group, at<tree>/src/compositions/<Name>/, so screens browse separately from the component list. Compositions are Pro; the free tier skips them with a note (ADR-097). -
--compositionsand--no-compositionsscope a run to one kind, onspecs generate,specs reactandspecs webcomponents, so iterating on component output no longer pays for the slowest specs in the catalogue. A scoped run never prunes the kind it excluded. -
Compositions reach
specs analyzeandspecs version.dependenciesandstylingread them, so which components a screen is built from shows in the blast radius;propsandkeysstay component-only. Every change to a composition is a patch, because it declares no contract.
Storybook
-
specs storybookscaffolds and publishes a workspace Storybook.initwrites the host once and prints the one install you run yourself; a barespecs storybookrewrites the generated content per concern —overview,foundations,components,analysis,versions. The CLI ships no browser. -
specs storybook devis the one process a designer runs. It publishes, re-emits each platform as specs change, serves Storybook, republishes when config or assets move, and recovers an index poisoned by a half-saved story file. One Ctrl-C stops it all. -
Generated pages cover your foundations, components, analyses and visual test results. Colour and typography read your library's own variables and styles, each component gets one entry with React · Web Components · Specs tabs, and a section appears only once its data exists.
-
Your pages win. Copy any generated page to the same path under
storybook/content-overrides/and it is used instead, live and in both directions. No command ever writes there, and publish reports which overrides are in effect. -
A workspace can declare the port its Storybook serves on, as
host.portinconventions/storybook.yaml, so each workspace keeps a localhost you can bookmark. A--portflag outranks it, then the scaffolded npm script, then6006(ADR-098). -
conventions/storybook.yamldeclares how a page presents.modes.collectionsturns each variable collection into a toolbar dropdown,color.collectionsnarrows and orders which collections render, andcolor.rowGroupandcolor.groupLeavesdecide what shares a row. An unknown concern or wrong value shape warns by name (ADR-098).
Visual testing
-
specs testing visualmeasures your emitted Storybook against two baselines. Fidelity mode diffs each variant against a Figma export of the same node; regression mode diffs against the renders you last accepted. A composition is diffed as one full-page pair and reported as advisory, never deciding the run's exit status. -
Each stage is its own subcommand, and each platform is measured separately.
manifestandstatusneed neither browser nor network,baselinecaptures Figma exports and only ever runs when named,shootscreenshots,diffre-scores in seconds, andacceptpromotes reviewed renders.--target <react|webcomponents>selects the platform and keys every artifact. -
specs testing visual initscaffolds the browser dependencies you install, and seedsvisual-ignore.yaml. Playwright, pixelmatch and pngjs are declared intesting/visual/package.jsonfor you to install. The ignore file is seeded only when absent, and--forcenever touches it, because every line in it is a decision. -
specs testing visual baselinescopes itself to what changed. Each capture records a fingerprint of the Figma node it photographed, sostatusreports each baseline as current, changed or missing, and a recapture after a refetch covers only the designs that moved.
The workspace chain
-
specs buildandspecs runperform your whole workspace in order — data, manifest, specs, each platform's code, then Storybook.buildruns the chain once and exits non-zero at the first failure;runruns it once and then watches, redoing only what a change affects. Experimental (ADR-101). -
--renderadds rendering back into Figma as the last step of either command. Off by default, because it is the only step that writes somewhere other than disk and it needs a running bridge.specs run --renderoverwrites the existing same-titled page;specs build --rendertreats a title collision as an error. -
Having nowhere to render to is reported and skipped, not treated as a failure. No bridge running, no Figma file connected, or several with nothing naming one — each says so in a line and the chain carries on. Both commands take
--file <fileKey>rather than prompting.
Conventions and settings
-
conventions/specs.yamlcan name your library's default slot, and a nested hierarchy then reads as one tree. Declareslots.default.matchand content you place in a nested instance's default slot is captured as that instance's children, in the spec's own anatomy, instead of a$slotContentreference. A non-default slot is unchanged (ADR-099). -
Glyphs are detected by structure, not only by name. The
glyphsconvention gains astructureform — child node shapes plus an optional shared fill — so a library whose icons are plainly named gets glyph detection without renames. A property-bearing component is never structurally claimed (ADR-103). -
settings.curationdecides which components a scan selects, withdefaultSelection,preserveManualSelectionsfor whether your manifest edits outrank a changed Figma dev status, andincludeDependencies.--include-alland--keep-checksoverride the first two for one run; every default matches previous behaviour (ADR-093). -
matchinconventions/figma.primitives.yamlnames the layers that promote to a component, for a layout family whose members share one set of styles and so cannot be told apart by them. An entry declaring none is chosen by its styles, as before (ADR-100). -
settings.yamlrejects what the schema does not declare, out loud. An unknown top-level key warns by name and is ignored; an invalid enum value warns with the valid list and uses the default. Nothing is silently transformed on a governed surface.
Analysis
-
specs analyzehas a command reference, and runs every analyzer when you name none. The reference covers its arguments, options and exit codes, including an unknown analyzer name as the warning that skips rather than a failure. -
The props analysis flags a name that means two things.
typeDiscordancenames which components hold which type for a prop they disagree on, andenumDiscordancenow also reports a name some components close with anenumwhile others leave open, markedopenAndClosed.
Roles
-
specs analyze rolesreports which anatomy elements declare a role and which are bare, with the concepts your library uses, the ones it has never reached for, and the concepts in use that nothing emits for yet. An unrecognized value is reported rather than refused, because a role is an open string and a near-miss is almost always a typo. -
specs skills installships two skills that carry role adoption across a library. One proposes a role for every unannotated element as a table you mark up; the other adds the approved ones to your Figma file a page at a time, then regenerates so you can see what landed. Nothing reaches Figma before you have checked the table. -
The bridge reads a component's structure as well as its spec.
POST /inspectdescribes a live component's anatomy elements keyed the way your specs key them, andPOST /annotateadds role annotations to one page — appending, so notes andaction:lines already on a layer survive, and an element that already carries a role is left alone.
Generate and render
-
A full
specs generaterun names spec folders it found but did not write, so a deselected component or a reverted convention leaves nothing for you to discover later. Nothing is deleted, and the note says so. -
SPECS_RENDER_WARNINGS=1prints the render warnings a run withholds. They stay hidden by default, because they are dominated by known defects and by degradations you cannot act on. -
Regenerating an unchanged design produces unchanged files: each spec's
metadata.lastUpdatedcarries the Figma file'slastModifiedinstead of the run's wall clock, so spec diffs show real changes only.
Fetch and cache
-
No ingest failure is silent. The cache reports what every source contributed and fails loudly when a payload cannot be read;
fetchvalidates config before downloading, carries on past one source's failure, and exits non-zero on partial failure. -
A transport-level fetch failure names the source, the data kind, the underlying cause chain and the exact
--onlyretry command, where it used to be a bare "fetch failed".
Changed
-
The style mapping reference is published, and corrected. Every spec style key and the declaration it produces is now an appendix page on the docs site, linked from Styles. It had drifted badly: gradient strokes, logical padding sides, cross-axis fill and variant selectors were all documented wrongly.
-
specs fetchsays "glyphs" wherever it used to say "icons", matchingscanand theglyphs.matchconvention. Theiconsfetch kind and theassets/icons/directory are unchanged. -
specs initscaffolds a settings file you can run, with a Figma source to fill your file key into, YAML output, camelCase keys, HEXA colors and an assets directory. Every commented line is the opposite of its default, so uncommenting one always changes something. -
specs fetchreports preparing and downloading as two stages, timed as one, and estimates the preparing wait as a range from your previous fetch of that source. Figma builds the whole payload before sending any of it, so a long silence is now accounted for. -
specs fetchgives up on a source after waiting 4 minutes for Figma to start sending. The deadline covers that wait only; a download already underway is never cut off, however slow it runs. -
A source's
keyaccepts a Figma URL pasted from the browser, not only a bare file key, which is what--sourcehas always accepted. A branch URL resolves to the branch.
Fixed
-
Figma node ids in a written spec are quoted, so a YAML 1.1 parser reads
42:2as an id rather than the integer 2522. Colon-separated digits are base-60 numbers in that version of the format, and a consumer on it received a plausible wrong value with no error. Nothing on the 1.2 spec, this CLI included, was affected. -
A component swapped onto a nested instance renders. A swap set two instances deep — an icon on a button inside a dialog's action row — was left out of what the plugin was told to look for, so the component arrived without it and nothing reported the omission.
-
specs renderworks withSPECS_DIRandDATA_DIRset and no workspace around the spec. That pairing is the documented override for a layout that keeps specs and data apart, and it failed with a type error about a path argument instead of rendering. -
Glyph slugs agree everywhere a digit follows a word.
specs fetchnamed a glyph from its raw Figma name (brightness-1.svg) while specs and emitted code carry the camel key (brightness1), so the scaffold requested a file fetch never wrote. Existing workspaces rename affected files on their next fetch. -
A spec whose Figma name has no letters or digits is refused rather than written to a folder named
component, where the next such spec would overwrite it. The check that rejects the name and the writer that names the folder now share one derivation. -
A concern a run stops generating is renamed to
*.yaml.stalerather than left to contradict the files beside it. Nothing is deleted, the run names every file it renamed, and a later run that generates the concern again clears it. -
specs analyze keysfinds divergent names again, reading the declared Figma naming convention from the run document and the workspace config rather than only from per-spec metadata. A run that finds no declared convention now warns instead of reporting a clean result. -
A style referencing a variable your fetch did not reach falls back to the value Figma last read for it, instead of rendering as though the property were never declared. Requires the
TOKEN_FIGMA_EXTENSIONSorCUSTOMtokens format, which is what carries that value. -
specs fetchno longer reports success while a configured data kind is absent from disk. A requested kind that did not land fails the run, a kind excluded by--onlyis called out, and cache counts are labelled as cache contents so they cannot read as this run's downloads. -
A file payload that arrives incomplete fails the fetch instead of being saved as though it were whole. Figma sends no length to check against, so a body that stopped early was written and reported as downloaded, surfacing only when another command could not read it.
-
A source with no key fails immediately, naming the source and the field, instead of asking Figma for a file called
undefinedand blaming a key for being stale. Every unusable key in your settings is reported in one run. -
A failed source's retry hint names every kind still missing, not only the one that failed, so following it recovers the whole source in one run. A source that failed to download is no longer called "skipped" in the cache report.
-
A run whose license key cannot be checked stops at the first component instead of failing every one into the same rate limit, and a rate-limited check says to wait the window out rather than to check your network.
-
The documentation links
specs initand config errors print now reach a live page. The domain they named stopped answering. -
A scaffolded workspace fetches on the first try, because
specs initno longer asks for icons in the source it writes, which madespecs fetchrefuse to start until a glyphs convention was declared.
Dependency updates
-
@directededges/specs-schema→^0.35.0. A spec can now say which slot a library's nested layouts fill through, and records the conventions its run used, so a reader holding only a spec can resolve a nested instance's default slot. Specs also carry which components of a library a run selected, and glyph membership can be declared by child structure rather than by layer name. -
@directededges/specs-from-figma→^0.34.0. Content you place in a nested instance's default slot is captured as that instance's children instead of a separate registry entry, so a screen of nested layout components reads as one tree — one real card component drops from 56 slot-content entries to 22. Composition frames can be specced from fetched library data, and the license check now happens once per run rather than once per component, which stops a long catalogue from silently producing free-tier output when the license service throttles. -
@directededges/react-from-specs→^0.4.0and@directededges/webcomponents-from-specs→^0.4.0. Nested content emits inside the component that holds it rather than beside an empty element, compositions emit as a single story in their own nav group, and every generated file now names the command that rewrites it. A token reference in an emitted inline style carries the spec's captured value as avar()fallback.