Releases
Version history for both Specs packages. Each release follows Semantic Versioning.
[0.34.0] - 2026-09-23
A component’s disallowed prop combinations now live with its API. invalidPropCombinations renames invalidVariantCombinations and travels in the api concern document, so the constraint sits beside the props it constrains rather than with variant data. The schema also now states plainly that an element carrying positioning offsets always declares position: ABSOLUTE, so a spec’s placement reads on its own, without consulting the parent’s layout mode.
Changed
- An element carrying positioning offsets always declares
position: ABSOLUTE, so its placement is readable without consulting the parent’s layout mode. Documentation-only: the presence contract is stated onpositionand the six offset properties in the types and schema; no shape changes (ADR-070) Component.invalidPropCombinationsrenamesinvalidVariantCombinations, carried by the api concern document so disallowed combinations live beside the props they constrain (ADR-092)
Migration
Component.invalidVariantCombinations→Component.invalidPropCombinations: read the renamed key, and fromapi.yamlrather thanvariants.yamlin split-concern output; regenerate specs to migrate
[0.33.0] - 2026-09-20
A generation run states its facts once, in a document of its own. The author, timestamp, generator, schema, conventions and settings were identical in every file a run produced; RunMetadata holds them now, and Metadata keeps only source (ADR-089).
A spec can be written as three documents, one per concern. SpecApiDocument, SpecVariantsDocument and SpecExamplesDocument each require what their concern carries and permit nothing else, so a file states which of the three it is and validates as that alone (ADR-091).
A component records the image it was authored with. ImageProp.examples gives a component that paints its own image somewhere to say so, which a binding site cannot do for it (ADR-088).
Breaking: six Metadata keys become optional. Read them from the run’s RunMetadata document instead — see Migration below.
Breaking
Metadatarequires onlysource.author,lastUpdated,generator,schema,conventionsandsettingsbecome optional, because the run’s document states them once for every file it produced (ADR-089)
Added
RunMetadatastates a generation run’s facts as a document of its own, validated by the newschema/metadataentry point and reachable fromroot.schema.json(ADR-089)SpecApiDocument,SpecVariantsDocumentandSpecExamplesDocumenttype the files asplitConcernsrun writes, each requiring what its concern carries and permitting nothing else, all three reachable fromroot.schema.json(ADR-091)SpecApiSubcomponent,SpecVariantsSubcomponentandSpecExamplesSubcomponentcarry a nested subcomponent’s slice of each concern (ADR-091)SpecConcernDocumentunions the three concern documents, narrowed on a key (ADR-091)Metadata.concernnames which concern a document carries, typed asConcern—'api' | 'variants' | 'examples'(ADR-091)- An image prop records the image its component was authored with in
ImageProp.examples, the sameImageValueshape asImageBinding.examplesand distinct fromdefault(ADR-088) - A promoted container records its hoisted fill on
FigmaElementExtension.children, typedSlotContentRef, so restoring the frame needs no table lookup (ADR-090)
Changed
- A container’s
PrimitiveRule.sourcehonours every closed-value layout member (ADR-090)
Migration
Metadata.author,Metadata.lastUpdated,Metadata.generator,Metadata.schema,Metadata.conventions,Metadata.settings: guard each read — when absent, read the value from the run’sRunMetadatadocument instead.
ADRs
Accepted
- ADR-088 — Authoring-default images on
ImageProp - ADR-089 — Run Metadata Factored Out of the Component Spec
- ADR-090 — Container Promotion Sources Opened to Closed-Value Layout Properties
- ADR-091 — Each concern document is its own type
[0.32.0] - 2026-09-12
Pipeline is retired, and the config split is two parts rather than three. A transformer pipeline stopped being a thing to configure once each target emitted everything it needs — nothing left to order, and css had no output location of its own to name. The other half, analyses, never had a reader: specs analyze has always taken its analyzers as arguments. Pipeline, ResolvedPipeline, TransformEntry, AnalysisEntry, DEFAULT_PIPELINE and the ./schema/pipeline entry point are gone from the package’s exports; the retirement itself leaves Conventions and Settings untouched (both change elsewhere in this release). See the amendment on ADR-071.
Breaking: a published type and one of the schema entry points are removed. Nothing outside the CLI consumed them.
Conventions are keyed by platform, and the spec declares its own. conventions.figma becomes one entry in a platforms map whose keys name implementations, and the conventions that describe the spec rather than any platform — the states classification and the prop conventions — move to a specs sibling.
Composed example content can map to preferred components by matching a primitive’s style values with designated component props. Since designers build examples with raw layers, that layer can now become an instance in specs for what component it stood for. Opt-in.
Breaking
These reshape types that 0.31.0 published. This branch was cut before 0.31.0 shipped, so what began as a free relocation inside an unreleased type became a break in a published shape once that release went out ahead of it.
Conventions— thefigmanamespace becomes a platform-keyedplatformsmap;figmais one key amongreact,web-components,swiftui, and an absent key means that platform declares nothing (ADR-073)states— moves from thefigmaconventions toConventions.specs.states; it names a prop and an enum value the spec declares, so a transform reading only the spec can apply it (ADR-073)DEFAULT_CONVENTIONS— now{}; every default it carried belongs inside a declared platform entry (ADR-073)- Conventions are authored as one file per platform in
config/conventions/, the filename carrying the platform id; there is no single-file form (ADR-078) Metadata.conventions— typed asMetadataConventions; carries exactly the platform that produced the spec (ADR-079)
Added
PlatformConventions— one shape per platform, carrying encoding and vocabulary members alike, and the root of a singleconfig/conventions/<id>.yaml(ADR-073, ADR-078)Conventions.specs— conventions about the spec itself, a sibling ofplatformsandprimitives: thestatesclassification, the prop supplying an accessible name (accessibility.label), and the props describing a control’s value (value.prop, with an optionalindeterminateprop). Authored asconfig/conventions/specs.yaml(ADR-073)PropReferenceandValueConvention— prop conventions are objects rather than bare names, so one can grow fields without a break (ADR-073)Settings.spec.roles— the role feature’s on-switch: read Figma Dev Mode annotations and emitanatomy.<element>.role/.actions;falseby default, so nothing is read or emitted unless a workspace opts in (ADR-067, amendment)Settings.spec.roleValidation— severity for unmet required role obligations,'warn'(default) or'error'; sits besiderolesbecause it tunes the same feature (ADR-067)AnatomyElement.roleandRoleConceptName— a behavior concept (or several) an element carries, read fromrole:annotation lines (ADR-067)AnatomyElement.actions,ActionEntryandActionConceptName— behavior actions read fromaction:annotation lines, deduped in annotation order (ADR-087)PrimitiveKind—'text' | 'glyph' | 'container', the subset ofElementTypethat can be promoted to a design system component (ADR-074)Conventions.primitives— component-keyedPrimitiveEntryvalues, each anelementTypeand amapofPrimitiveRule; platform-neutral output, authored asconfig/conventions/figma.primitives.yamlbecause its sources and token names describe the design tool (ADR-075, ADR-073)PrimitiveRule— asourceread from a captured layer, sent to apropas-is or through a literalvalueslookup writing one or more props (ADR-075)PlatformConventions.stylesProp— prop receiving styling no promotion mapped, one per platform and the only styling-prop level; there is no per-primitive override, because a promotion target is named by the spec rather than by a platform binding (ADR-076)PlatformConventions.images.component— the image component’s name on a code platform, beside thematchnaming it in Figma (ADR-077)MetadataConventions— a spec records the one platform entry that produced it, not every platform the workspace configures (ADR-079)PlatformConventions.defaultFillWidth— container width for a root that resizes to fill its parent; fixed and hugging roots unaffected (ADR-081)Element.$extensions—com.figma.promotedPrimitive,com.figma.multipleMatches,com.figma.contentandcom.figma.styles, recording that a layer was promoted, whether more than one entry resolved, and the content and styles it consumed (ADR-084)Settings.spec.promotePrimitives— primitive layers in composed example content promote to design system component instances; opt-in, defaults tofalse(ADR-085)
ADRs
Accepted
Every ADR this release implements is accepted — fourteen written on this branch, plus ADR-072, drafted in 0.31.0 and accepted here.
- ADR-067 — Element Behavior Roles via
anatomy.role(amended:settings.spec.rolesis the on-switch) - ADR-068 — Form Control and Field Plumbing Role Concepts
- ADR-072 — Numeric Enum on NumberProp
- ADR-073 —
conventions.platforms, with Figma as One Platform Among Them - ADR-074 — Primitives Promote to Component Instances During Capture, in Composed Content
- ADR-075 —
conventions.primitives— a Declared Table from Styles to a Component’s Props - ADR-076 — Promoting a Container, and a Platform-Level
stylesProp - ADR-077 — The Image Component’s Code Name, and the Encoding / Vocabulary Boundary
- ADR-078 — One Conventions File per Platform, in
config/conventions/ - ADR-079 —
metadata.conventionsCarries Only the Producing Platform - ADR-081 —
defaultFillWidth— the Width a Fill-Width Root Fills - ADR-084 —
Element.$extensions— Figma Provenance for a Promoted Element - ADR-085 —
promotePrimitives— the Switch for Capture-Time Promotion - ADR-086 — Interactive Root and Announcement Role Concepts
- ADR-087 — Behavior Actions via
anatomy.action
Fixed
- A test verifies every exports-map entry resolves to a file this package ships, so a retired subpath cannot outlive what it pointed at
[0.31.0] - 2026-09-04
Configuration now separates what is true about a Figma library from what a run chooses to do with it. A convention — a naming pattern, a state classification, where subcomponents live — is a fact every consumer of that library must share, and getting one wrong produces incorrect output. A setting is a free choice that produces different output. They were peers in one Config; they are now two published types, each addressable, each validated on its own, with the work a workspace runs declared separately again.
Added
PropConfigurationValue— anullarm; a configuration states a nullable prop is unset, and an absent key inherits whilenulloverrides (ADR-080)InstanceExample.propConfigurations— acceptsnull, so an example can leave a prop unset; still refusesPropBindingNumberProp.enum— the closed set of values a numeric prop accepts, so a Figma VARIANT whose options are all numbers carries its numeric type and its authored option order at once (ADR-072)Conventions— facts about the Figma library, namespaced by source underfigma; blocks are optional and their absence means the library declares no such conventionSettings— run choices grouped by concern (data,spec,assets), each carrying its owndirectoryPipeline—transformersandanalysesa workspace runs, withAnalysisEntryalongsideTransformEntrySourceEntry— a source’s Figma filekeyand the artifacts tofetchDEFAULT_SETTINGS,DEFAULT_PIPELINEandDEFAULT_CONVENTIONS— one defaults constant per configuration artifact.DEFAULT_CONVENTIONScarries only the three members that have a meaningful default; convention blocks are absent by design, because absence is the statement that a library declares noneconventions.schema.json,settings.schema.json,pipeline.schema.json— one schema per authored artifact
Changed
-
Settings.spec.collapsePrimitiveWrapper— also collapses a root wrapping one slot, keeping the slot’s value on shared keys (ADR-083) -
Settings.spec.splitComponents,splitConcernsanduseSubfoldersnow default totrueand are required onResolvedSettings. The split layout — one folder per component, one file per concern — is whattransform,analyzeandrenderread, so the shape of generated output is not a per-consumer choice. They previously carried no default, leaving each consumer to pick its own; both consumers pickedfalse, which is the layout nothing downstream can use. Recorded specmetadata.settings.specnow carries all three on every spec. -
Metadata.config→Metadata.conventionsandMetadata.settings— each half comparable across specs on its own -
Config.format.figmaKeys→Conventions.figma.naming— no longer shares the wordkeyswith the emitted casing -
Config.processing.glyphNamePattern→Conventions.figma.glyphs.match;codeOnlyPropsPattern→codeOnlyProps.match;images.imageComponent→images.match -
Config.processing.states,subcomponents,instanceExamples,images,slotConstraints,inferNumberProps→Conventions.figma, whole, includingscopeandbackgroundImage -
Config.format.output→Settings.spec.format; the rest offormatand all ofincludefold intoSettings.spec -
Workspace-level
sources,dataDirectory,outputDirectory, and theoutputblock absorbed intoSettings
Removed
Config,ResolvedConfig, andDEFAULT_CONFIG— replaced by the types aboveworkspace.schema.json— one schema per artifact replaces itConfig.transformers— work to run now lives inPipeline
Migration
Config→Conventions+Settings: read library facts fromconventions, run choices fromsettings. A member describes the library if a different team pointing at the same Figma file would have to keep your value.DEFAULT_CONFIG→DEFAULT_SETTINGS: conventions have no defaults to supply, so nothing replaces the conventions half.Metadata.config→Metadata.conventions/Metadata.settings: read whichever half you compared before, and preferconventionswhen checking that two consumers agree about a library.
ADRs
Accepted
- ADR-071 — Separate Library Conventions from Tooling Settings
- ADR-080 —
nullas a Prop Configuration Value - ADR-083 — Collapsing a Slot-Only Wrapper
New drafts
- ADR-072 — Numeric Enum on NumberProp
[0.30.0] - 2026-08-17
A spec can now declare the naming convention its Figma file follows, so a formatted property key can be turned back into the name a designer sees on the canvas. When a key can’t reconstruct that name on its own — because the original contained characters formatting doesn’t survive — the spec records the Figma name alongside it, on every property type. Together these make round-tripping between a formatted spec and its source file possible without a side-channel lookup of the file’s own naming.
Added
- A spec can state the naming convention its Figma file uses —
Config.format.figmaKeys(NONE|SENTENCE|TITLE, defaultNONE) names the convention to reverseformat.keysback into.NONEdeclares no convention, so the safe key grammar, the divergence extension, and reversal are all opt-in (ADR-066) - Properties whose key can’t reconstruct their Figma name now carry that name —
FigmaPropExtension.nameis emitted only when the key alone is insufficient (ADR-066) - The key grammar that survives every formatting mode is now defined —
SafeKeySentenceandSafeKeyTitledescribe keys that round-trip under anyformat.keysvalue (ADR-066) - Number properties can now carry platform extensions —
NumberProp.$extensionswas the one prop type with nowhere to record its Figma name; the JSON schema already permitted$-prefixed keys, the TypeScript type did not
Changed
- The Figma layer-name extension now covers key divergence, not just wrapper collapse —
FigmaAnatomyElementExtension.name, renamed fromoriginalName(ADR-066) - The clip flag now reads as a verb, matching the rest of the style vocabulary —
Styles.clipsContent, renamed fromclipContent; type and token-bindability unchanged (ADR-069)
Migration
Styles.clipContent → Styles.clipsContent: read clipsContent instead; the value shape is unchanged. No stored spec carries the old key — it never matched emitted output.
FigmaAnatomyElementExtension.originalName → FigmaAnatomyElementExtension.name: read $extensions['com.figma'].name instead; the value and its collapse-provenance meaning are unchanged. Consumers that read the extension through an inline structural type will not see a compile error — update those call sites explicitly.
[0.29.0] - 2026-08-07
Numeric properties can now be marked nullable, matching the flexibility already available on strings, slots, and images. Horizontal text alignment now uses logical inline-axis direction (START/END) instead of physical LEFT/RIGHT, so specs read correctly regardless of writing direction.
Added
- Numeric properties can now be optional —
NumberProp.nullable, previously unexpressible, lets a numeric prop be absent (ADR-065)
Changed
- Nullable behavior is now documented for every prop type — absence defaults to
trueforStringProp,NumberProp,SlotProp,ImageProp;falseforEnumProp(ADR-065) - Horizontal text alignment now reads as logical direction, not physical side —
Styles.textAlignHorizontalnarrows fromStyletoTextAlignHorizontal('START' | 'CENTER' | 'END' | 'JUSTIFY') ornull(ADR-064)
Migration
Styles.textAlignHorizontalvaluesLEFT/RIGHT/JUSTIFIED→START/END/JUSTIFY: read the logical enum; resolveSTART/ENDby writing direction and regenerate any stored specs
[0.28.0] - 2026-07-16
Specs can now describe images. A container’s image fill is captured as a typed background image, image-source properties become a first-class prop type, and every image a component references is collected in a single registry — resolved to a data URI, URL, or asset path when available. Text truncation is also now expressible: specs record how overflowing text is handled and the line limit at which truncation begins.
Added
- Text overflow handling is now captured —
Styles.textOverflow, typed asTextOverflow('CLIP' | 'ELLIPSIS') ornull, records how overflowing text is handled (TEXT only, not token-bindable). Named after CSStext-overflow/ ComposeTextOverflow - Line limits are now captured —
Styles.maxLinesrecords the maximum line count before'ELLIPSIS'text overflow applies; null for no limit (TEXT only) - Container image fills are first-class styles —
Styles.backgroundImage, typed asImageValue({ $image, objectFit?: 'COVER' | 'CONTAIN' }), aTokenReferencewith$type: 'image'(an applied Figma fill style whose paint is an image routes its style reference here, not tobackgroundColor;imagejoins the$typeset as a Specs extension), ornull(ADR-063) - Every image a component references is collected in one registry —
Component.imagesmaps ids toImageData: resolved data (data:URI, URL, asset path) or afigma:<imageRef>placeholder - Image-source properties are a first-class prop type —
ImageProp(type: 'image') joinsAnyPropfor properties that supply an image source - Images can be forwarded into nested instances —
ImageBinding({ $binding, examples? }) joinsPropConfigurationValue, routing an image prop into a subcomponent instance - Image processing is opt-in via config —
Config.processing.images(ADR-063); presence is the on-switch, likesubcomponents. Independent triggers:backgroundImage(detect image fills; the fallback when a component is designated),imageComponent(designated component name; requiressourceProps), andsourceProps(raw Figma code-only prop names that re-type toImageProp; the first entry is the designated component’s own source prop)
Fixed
- Color token references no longer fail validation on their resolved raw value — the
rawValueprovenance field on a token reference now accepts a DTCG Color object (the shape the generator emits for color tokens) alongside string, number, and boolean, clearing false schema violations on token-bound fills and gradient stops. - Subcomponent source identity no longer fails validation — subcomponent entries carrying the ADR-060
sourcefield (pageId/nodeId/nodeType) were rejected because the closed Component definition composed via allOf could not admit it; the property is now declared where draft-07 requires it. - Collapsed-root anatomy provenance no longer fails validation — anatomy elements now accept
$extensions['com.figma'].originalName(written by primitive-wrapper collapse, ADR-058) in both the JSON schema and theAnatomyElementtype. - Nested-path configurations now enforce a non-empty path — the schema declared the constraint with a misspelled keyword (
minChildreninstead ofminItems), so validators silently skipped it; an emptypathon a$nestedentry is now rejected as ADR-052 intends.
[0.27.0] - 2026-07-01
Dashed strokes are now first-class, typed data instead of falling through as an unrecognized style, and subcomponents carry enough Figma node identity for reverse-direction tools to resolve them back to a canvas location without a side-channel lookup.
Added
- Dashed strokes are typed and preserved in output —
Styles.strokeDashPatternis typed asStrokeDashPattern({ dash: number; gap: number }) ornull; presence signals a dashed stroke, null or absent signals solid. Maps from FigmastrokeDashes[0](dash) andstrokeDashes[1](gap). - Subcomponents carry their Figma source identity —
Subcomponent.sourceis an optionalSubcomponentSource({ pageId, nodeId, nodeType }) enabling reverse-direction tools to resolveSubcomponentRefentries back to Figma nodes without side-channels.
[0.26.0] - 2026-06-22
Specs can now skip redundant wrapper frames around a lone text or glyph child. When Config.processing.collapsePrimitiveWrapper is enabled, a plain container holding a single text or icon leaf is collapsed away, promoting the leaf directly to spec root. This produces cleaner specs for simple label or icon components that Figma wraps in an auto-layout frame purely for sizing.
Added
- Single-child container frames can be collapsed out of specs —
Config.processing.collapsePrimitiveWrapper(boolean, defaultfalse) strips a plain wrapper frame around a lone text or glyph child and promotes the leaf to spec root (ADR-058). Useful for label-only or icon-only components where the Figma auto-layout frame adds structural noise without semantic meaning.
[0.25.0] - 2026-06-15
SlotProp.minItems/maxItems are renamed to minChildren/maxChildren, aligning the schema vocabulary with Figma’s native slotSettings API and with the universal “children” term across React, SwiftUI, and Jetpack Compose. Libraries using Figma’s native slot settings can now read constraint values without a translation layer. This release also corrects Metadata.generator.version from number to string, fixing a type mismatch for TypeScript consumers.
Changed
- Slot child constraints use framework-native naming —
SlotProp.minItemsandmaxItemsrenamed tominChildrenandmaxChildren(ADR-056). Aligns with Figma’sslotSettings.minChildren/maxChildrenand the “children” vocabulary used by React, SwiftUI, and Jetpack Compose. specs-from-figma can now read nativeslotSettingsvalues directly without field translation. - Generator version is now correctly typed as a string —
Metadata.generator.versioncorrected fromnumbertostring; the field holds a semver string (e.g."1.10.0")
Migration
SlotProp.minItems→minChildren,SlotProp.maxItems→maxChildren: rename field access in any code reading these fields; schema validation will catch mismatches at generate time. Libraries using code-only props must also rename{slot} minItems/{slot} maxItemsto{slot} minChildren/{slot} maxChildrenin Figma.Metadata.generator.version: update any code that treated this field as a number; read it as a string instead
[0.24.0] - 2026-06-05
Introduces the transformer pipeline’s schema foundation. Config.transformers (a flat TransformEntry[]) replaces the old two-level config.transform.transformers wrapper, and a new workspace.schema.json provides IDE validation for specs.config.yaml. Config.processing.states adds a concept-keyed map that classifies Figma variant props as browser-driven or consumer-controlled states, enabling the css transformer to emit semantic CSS selectors and the contract transformer to omit browser-driven props from Props interfaces.
Added
-
TransformEntrytype (ADR-053) —{ name: string; [option: string]: unknown }. Describes a single transformer entry inconfig.transformers. Transformer-specific options sit inline alongsidename. -
workspace.schema.json(ADR-054) — New top-level JSON schema file for the workspace config file (specs.config.yaml). Defines the full workspace shape includingdataDirectory,outputDirectory,sources,output,author, and theconfigblock (which embeds the componentConfigschema).Config.transformersis defined here rather than in the component schema, keeping transform configuration CLI-only and out of the per-component spec. -
Config.transformers—TransformEntry[]directly onConfig— Replaces the two-levelconfig.transform.transformersshape. The wrapper object is gone;config.transformersis the array. Absence means CLI defaults apply. -
VariantStateEntrytype (ADR-055) — classifies a Figma variant prop as a semantic state concept; fields:prop(string, required),value(string, optional — the Figma enum value activating the concept; defaults to"true"for boolean props),contract('omit'|'keep', optional — overrides the concept’s canonical default) -
Config.processing.states—Record<string, VariantStateEntry>— concept-keyed map (key = concept name, e.g.hover,disabled); classifies variant props for deterministic use by CSS and contract transformers; absence means all props emit asdata-*attribute selectors and all props are retained in contracts
Changed
Config.transformremoved; replaced byConfig.transformers— The intermediateTransformConfigwrapper object is eliminated.transform.transformerscollapses totransformersdirectly onConfig/ResolvedConfig.
Removed
[0.23.0] - never published (shipped in 0.24.0)
Added
Changed
Removed
[0.22.0] - 2026-05-23
Introduces the composition and slot-content model across ADRs 042, 046–052. Composition is a named registry entry with a top-level anatomy + elements + layout triplet and an optional slotContent map of bundled SlotContent fills. SlotContentRef ({ $slotContent }) is the universal pointer for all slot fills — component-scoped examples, intra-composition bundled fills, and cross-composition references all resolve to anatomy + elements + layout. PropConfigurations widens to accept PropBinding and SlotContentRef; InstanceExample.propConfigurations widens to accept SlotContentRef (not PropBinding). Children widens to SlotBinding for slot-bound containers. Config gains the examples-detection settings and output gates (ADR-050).
Added
SlotContent—{ anatomy, elements, layout }; the anonymous structural triplet used as a named slot fill. Carries no metadata; identity lives at the key where it is stored (ADR-046)Composition—{ title?, description?, anatomy, elements, layout, slotContent?: Record<string, SlotContent> }; a named registry entry whose top-level triplet is the primary content. OptionalslotContentbundles named fills alongside it for authoring convenience (ADR-042, ADR-046)Compositions—Record<string, Composition>; registry alias for external composition filesSlotContentRef—{ $slotContent: string }; universal JSON Pointer reference for slot fills. Resolves to aCompositionentry (top-level triplet), aComposition.slotContententry, or aComponent.slotContentExamplesentry — all yieldanatomy + elements + layout(ADR-046)Component.slotContentExamples—Record<string, SlotContent>; named slot-content examples per component, referenceable bySlotContentReffromSlotBinding.examplesand fromElement.propConfigurationsslot-prop entries.specs-from-figmade-duplicates entries by structural equality across variants and slots (ADR-047)SlotBinding— extendsPropBindingwith optionalexamples?: SlotContentRef[]; used inElement.childrenfor slot-bound containers. Eachexamples[i]points to a slot-content entry; emitters currently write a single entry at index 0 — Figma’s authoring default for the slot layer. Non-contractual reference material; code consumers handle missing slots through component logic and ignore it (ADR-047)Component.instanceExamples— typed asInstanceExamples(Record<string, InstanceExample>); named documented usages per component (ADR-048)InstanceExample—{ title?, propConfigurations?: Record<string, string | number | boolean | SlotContentRef> }; scalar props set directly, slot props filled viaSlotContentRef.PropBindingnot accepted — documented configurations are not live bindings (ADR-048)Config.processing.instanceExamples—{ scope?: 'PAGE' | 'FILE', match?: string[], exclude?: string[], parentNames?: string[] }; instance-example detection settings, mirroringprocessing.subcomponents.scopeisPAGE | FILE(NESTEDis inapplicable);matchis an optional secondary name filter (the primary relevance test is structural identity — the candidate is an instance of the component being generated — so absence accepts every in-scope instance, subject toexclude/parentNames);parentNamesfilters candidates by immediate-parent frame/section name. Absence of the block is the off-switch — its presence enables both detection and output (no separate include flag), likeprocessing.subcomponents. Pro-gated. Added toResolvedConfig(withscoperequired,matchoptional) (ADR-050)Config.include.defaultSlotContent—booleanoutput gate, defaultfalse; added toResolvedConfig(required) andDEFAULT_CONFIG. Pro-gated — ignored on the free tier (ADR-050)Config.format.tokens— addsFIGMA_SYNTAX_WEB,FIGMA_SYNTAX_IOS,FIGMA_SYNTAX_ANDROIDprofiles emitting per-platform Figma code syntax, falling back toTOKEN(ADR-051)
Changed
Children— widened fromstring[] | PropBindingtostring[] | SlotBinding; existing{ $binding }values still validate becauseSlotBindingis a structural superset ofPropBinding(ADR-047)PropConfigurations— value union widened fromstring | number | booleantostring | number | boolean | PropBinding | SlotContentRef; enables scalar prop pass-through (PropBinding) and slot-prop fills (SlotContentRef).InstanceExample.propConfigurationswidens separately and does not acceptPropBinding(ADR-049)PropConfigurations.$nested— added reserved key typed asNestedPropConfiguration[]({ path: string[] }plus the prop-config payload, keyed valuePropConfigurationValue); path-addressed configurations of nested descendant instances, wherepathis a list ofinstanceOfelement keys to the configured descendant and the terminal instance owns the filled slot/prop. Deep slot fills reuseSlotContentRef— no new payload, noElementchange.InstanceExample.propConfigurationsdoes not gain$nested(ADR-052)
Removed
[0.21.0] - 2026-05-22
Extends Config.format.tokens with per-platform Figma code syntax profiles. The new FIGMA_SYNTAX_WEB, FIGMA_SYNTAX_IOS, and FIGMA_SYNTAX_ANDROID options emit the platform-specific code syntax authored on Figma variables, falling back to the resolved TOKEN reference when no syntax is defined.
Added
Config.format.tokens— addsFIGMA_SYNTAX_WEB,FIGMA_SYNTAX_IOS,FIGMA_SYNTAX_ANDROIDprofiles emitting per-platform Figma code syntax, falling back toTOKEN
[0.20.0] - 2026-05-06
Adds configurable color output format (Config.format.color) supporting nine format options from hex strings to structured DTCG Color objects. Renames ColorValue to ColorObject for specificity and widens ColorStyle, Shadow.color, and GradientStop.color to accept formatted color strings alongside structured objects and token references.
Added
Config.format.color— typed asColorFormat('HEX' | 'HEXA' | 'RGB' | 'RGBA' | 'HSLA' | 'HSB' | 'OKLCH' | 'OKLAB' | 'OBJECT'); controls color value output format; defaults toHEX
Changed
ColorObject— renamed fromColorValuefor specificity; the type represents a DTCG Color §4.1 structured object, not a generic “color value”ColorStyle— widened tostring | ColorObject | TokenReference | GradientValue | null; thestringarm supports formatted color strings whenConfig.format.coloris non-OBJECTShadow.color— widened tostring | ColorObject | TokenReference; samestringarm rationale asColorStyleGradientStop.color— widened tostring | ColorObject | TokenReference; samestringarm rationale asColorStyle
[0.19.0] - 2026-04-28
Replaces Figma-raw x, y, and layoutPositioning with constraint-based positioning properties. position replaces layoutPositioning as a strict Position enum. Directional offsets (top, bottom, start, end, centerHorizontalOffset, centerVerticalOffset) replace x/y with anchor semantics derived from Figma constraints. See the Layout Positioning guide for constraint mapping rules and platform usage examples.
Added
Styles.position— typed asPosition('AUTO' | 'ABSOLUTE') ornull; structural property, not token-bindable (replaceslayoutPositioning)Styles.top— typed asPositionOffset(number | string | null); block-start offset from vertical MIN, STRETCH, or SCALE constraintStyles.bottom— typed asPositionOffset; block-end offset from vertical MAX or STRETCH constraintStyles.start— typed asPositionOffset; inline-start offset from horizontal MIN, STRETCH, or SCALE constraintStyles.end— typed asPositionOffset; inline-end offset from horizontal MAX or STRETCH constraintStyles.centerHorizontalOffset— typed asPositionOffset; horizontal offset from center (CENTER constraint)Styles.centerVerticalOffset— typed asPositionOffset; vertical offset from center (CENTER constraint)
Removed
Styles.x— replaced byStyles.start,Styles.end, orStyles.centerHorizontalOffsetdepending on constraintStyles.y— replaced byStyles.top,Styles.bottom, orStyles.centerVerticalOffsetdepending on constraintStyles.layoutPositioning— replaced byStyles.position
Migration
Styles.x→Styles.start/Styles.end/Styles.centerHorizontalOffset: determine which property to read based on the node’s horizontal constraint; SCALE values are percentage strings (e.g."25%")Styles.y→Styles.top/Styles.bottom/Styles.centerVerticalOffset: same constraint-based mapping for the vertical axisStyles.layoutPositioning→Styles.position: rename and narrow type — replace genericStylehandling with exhaustive match on'AUTO' | 'ABSOLUTE'
[0.18.0] - 2026-04-24
Replaces Figma-native layout property names with semantic equivalents for auto-layout alignment and wrapping. Introduces a bi-axial itemSpacing model that consolidates counterAxisSpacing into a single property, and narrows layoutMode from a generic Style to a strict enum. Five renamed alignment/wrap fields use plain-language names (mainAxisAlignment, crossAxisAlignment, wrapAlignment, wrap) instead of Figma axis terminology. The documentation site was considerably rearchitected with updated content and navigation.
Added
Styles.itemSpacing— widened fromStyletoStyle | ItemSpacing(scalar when uniform,ItemSpacingobject withhorizontal/verticalwhen axes differ)Styles.wrap— boolean toggle for auto-layout wrapping (replaceslayoutWrap)Styles.wrapAlignment— typed asWrapAlignment('START' | 'SPACE_BETWEEN') ornull; structural property, not token-bindable (replacescounterAxisAlignContent)Styles.mainAxisAlignment— typed asMainAxisAlignment('START' | 'END' | 'CENTER' | 'SPACE_BETWEEN') ornull; structural property, not token-bindable (replacesprimaryAxisAlignItems)Styles.crossAxisAlignment— typed asCrossAxisAlignment('START' | 'END' | 'CENTER' | 'STRETCH' | 'BASELINE') ornull; structural property, not token-bindable (replacescounterAxisAlignItems)
Changed
Styles.layoutMode— narrowed fromStyletoLayoutMode('NONE' | 'HORIZONTAL' | 'VERTICAL') ornull; enum constraint, no TokenReference
Removed
Styles.counterAxisSpacing— consolidated intoStyles.itemSpacingbi-axial modelStyles.layoutWrap— replaced byStyles.wrapStyles.counterAxisAlignContent— replaced byStyles.wrapAlignmentStyles.primaryAxisAlignItems— replaced byStyles.mainAxisAlignmentStyles.counterAxisAlignItems— replaced byStyles.crossAxisAlignment
Migration
Styles.counterAxisSpacing→Styles.itemSpacing.vertical: read theverticalsub-field of theItemSpacingobject when axes differ; scalaritemSpacingapplies uniformlyStyles.layoutMode→LayoutMode | null: replace genericStylehandling with exhaustive match on'NONE' | 'HORIZONTAL' | 'VERTICAL'Styles.layoutWrap→Styles.wrap: rename only; same boolean semanticsStyles.counterAxisAlignContent→Styles.wrapAlignment: rename and remap values —'AUTO'→'START','SPACE_BETWEEN'→'SPACE_BETWEEN'Styles.primaryAxisAlignItems→Styles.mainAxisAlignment: rename and narrow type — replace genericStylehandling with exhaustive match on'START' | 'END' | 'CENTER' | 'SPACE_BETWEEN'; remap Figma values'MIN'→'START','MAX'→'END'Styles.counterAxisAlignItems→Styles.crossAxisAlignment: rename and narrow type — replace genericStylehandling with exhaustive match on'START' | 'END' | 'CENTER' | 'STRETCH' | 'BASELINE'; remap Figma values'MIN'→'START','MAX'→'END'
[0.17.0] - 2026-04-13
Introduces ResolvedConfig as the fully-resolved counterpart to Config, where every property with a default is guaranteed present. Config properties with defaults are now optional, making input configs more tolerant of omissions. Removes the unused Variant.name and Variant.baseline fields.
Added
ResolvedConfig— fully-resolved config type where every property with a default is required; only true feature toggles (subcomponents,glyphNamePattern,codeOnlyPropsPattern) remain optional
Changed
Config— all properties with defaults are now optional (input type tolerant of omissions):processing.variantDepth— optional; defaults to 9999processing.details— optional; defaults to LAYEREDformat.output— optional; defaults to JSONformat.keys— optional; defaults to SAFEformat.layout— optional; defaults to LAYOUT
ResolvedConfig— all properties with defaults are required (post-merge guarantee):processing.slotConstraints— required (defaultfalse)processing.inferNumberProps— required (defaultfalse)format.tokens— required (defaultTOKEN)include.invalidVariants— required (defaultfalse)include.invalidCombinations— required (defaulttrue)include.emptyVariants— required (defaultfalse)subcomponents.scope— required when subcomponents is present (defaultNESTED)
DEFAULT_CONFIG— typed asResolvedConfig; now explicitly includesslotConstraints: false,inferNumberProps: false; removedsubcomponents(feature toggle — off by default, matching the Figma plugin)
Removed
Variant.name— unused optional label; variant identity is fully described byconfigurationVariant.baseline— never populated in output; baseline determination is an internal processing concern
Migration
Variant.name→ removed: delete any references; useVariant.configurationto identify variantsVariant.baseline→ removed: delete any references; no replacement needed (field was never populated)
[0.16.0] - 2026-04-05
Adds Config.include.emptyVariants for controlling inclusion of layered variants with no elements. Makes invalidVariants and invalidCombinations optional with sensible defaults, and removes the unused variantNames field.
Added
Config.include.emptyVariants— optional boolean to control inclusion of layered variants that contain no elements; defaults to false
Changed
Config.include.invalidVariants— now optional; defaults to false when absentConfig.include.invalidCombinations— now optional; defaults to true when absent
Removed
Config.include.variantNames— unused field removed from the API
Migration
Config.include.variantNames→ removed: delete any references to this field from config construction code; regenerate cached specs to remove the field from serialized output
[0.15.0] - 2026-03-25
Adds structured subcomponent discovery via a new Config.processing.subcomponents object with scope, match, and exclude settings, replacing the flat subcomponentNamePattern string. Introduces SubcomponentRef for referencing subcomponent definitions and widens instanceOf on both AnatomyElement and Element to accept it. Corrects Typography field types for leadingTrim, fontFamily, and fontStyle to match actual Figma API values.
Added
SubcomponentRef— reference to a subcomponent definition via{ $ref: "#/subcomponents/{key}" }AnatomyElement.instanceOf— widened to acceptstring | SubcomponentRefElement.instanceOf— widened to acceptstring | PropBinding | SubcomponentRefConfig.processing.subcomponents— optional nested object grouping subcomponent discovery settings:scope,match, andexclude. Absence means no subcomponent detectionConfig.processing.subcomponents.scope— optional enum (NESTED|PAGE) controlling where the transformer searches for subcomponentsConfig.processing.subcomponents.match— required array of{C}/{S}template patterns defining which assets are subcomponentsConfig.processing.subcomponents.exclude— optional array of{C}/{S}template patterns defining which matched assets to exclude
Changed
Typography.leadingTrim— corrected fromnumber | 'mixed' | TokenReferenceto'NONE' | 'CAP_HEIGHT' | 'mixed'per Figma APITypography.fontFamily— corrected fromstring | number | 'mixed'tostring | 'mixed' | TokenReference; removed impossiblenumber, addedTokenReferencefor variable-bound fontsTypography.fontStyle— corrected fromstring | number | 'mixed'tostring | 'mixed' | TokenReference; removed impossiblenumber, addedTokenReferencefor variable-bound fonts
Removed
Config.processing.subcomponentNamePattern— replaced byConfig.processing.subcomponents.matchConfig.include.subcomponents— subcomponent inclusion is now implied by the presence ofmatchpatterns
Migration
Config.processing.subcomponentNamePattern→Config.processing.subcomponents.match: wrap the single pattern string in a one-element arrayConfig.include.subcomponents→ removed: remove the field; subcomponents are included whenprocessing.subcomponentsis defined withmatchpatterns. Omitprocessing.subcomponentsentirely to disable detection
[0.14.0] - 2026-03-18
Introduces code-only props support with FigmaCodeOnlySource provenance metadata and a configurable container-layer naming pattern, plus a new NumberProp type for numeric property values. Adds DTCG-aligned $extensions for platform-specific metadata across all prop types, replacing the prior x-platform convention on BooleanProp. Expands SlotProp with minItems, maxItems, and anyOf constraints consolidatable via the new slotConstraints processing flag.
Added
FigmaCodeOnlySource— provenance metadata for props extracted from a Figma code-only container layer (kind,layer,instanceOf?)FigmaPropExtension.source— optionalFigmaCodeOnlySourceon the Figma extension, present only for code-only propsConfig.processing.codeOnlyPropsPattern— optional naming pattern for detecting the code-only props container layerConfig.processing.inferNumberProps— opt-in flag to inferNumberPropfrom TEXT code-only propsNumberProp— numeric property type for number-valued component propsAnyProp—NumberPropadded as a fifth union member (type: 'number')FigmaPropExtension— Figma-specific metadata for prop definitions (native prop type)PropExtensions— DTCG §5.2.3 platform-specific extensions container, keyed by reverse-domain notationBooleanProp.$extensions— optional platform metadata on boolean propsStringProp.$extensions— optional platform metadata on string propsEnumProp.$extensions— optional platform metadata on enum propsSlotProp.$extensions— optional platform metadata on slot propsSlotProp.minItems— minimum number of items the slot acceptsSlotProp.maxItems— maximum number of items the slot acceptsSlotProp.anyOf— component type names permitted in the slotConfig.processing.slotConstraints— opt-in flag to consolidate slot constraint code-only props into slot properties
Changed
BooleanProp—x-platformreplaced by$extensionsto align with DTCG convention used onTokenReference
[0.13.0] - 2026-03-13
Added
Metadata.schema.latest— optional stable URL pointing to the latest schema on main for discoveryMetadata.generator.license— optional resolved license state:statusandlevelnested inside generatorStyles.fillColor— glyph fill color for GLYPH element typeStringProp.examples— sample values demonstrating typical content for string propsElement.content— unified content for text strings and glyph namesSlotProp.nullable— optional flag indicating the slot prop accepts nullConditional— conditional binding withif/condition/then/elsefor derived valuesConditionExpression— declarative condition pairing anoperation(string) withargsConditionArgs— condition arguments:value(PropBinding) and optionalcompareTo
Changed
SlotProp.default— now optional; omitted when no meaningful default existsColorStyleValue— accepts bare hex strings (e.g.#666E74) matching the existingColorStyletypeBooleanProp,StringProp,EnumProp,SlotProp— allow$-prefixed metadata fields viapatternPropertiesMetadata.schema.url— clarified as versioned schema URL pinned to a git tagElementType—'icon'renamed to'glyph'to distinguish raw visual assets from composed Icon componentsConfig.processing.iconNamePattern→Config.processing.glyphNamePattern— glyph detection patternStringProp.default— now optional; useexamplesfor demo contentStringProp.default— widened tostring | nullfor nullable propsSlotProp.default— widened tostring | nullfor nullable slot propsBindingKey—'text'replaced by'content'
Removed
TextProp— merged intoStringPropIconProp— merged intoStringPropElement.text— useElement.contentinstead
Migration
TextProp/IconProp→StringProp: replace all type imports and references withStringProp; the shape is identicalElement.text→Element.content: read element content fromcontentinstead oftext; applies to both text strings and glyph namesElementType'icon'→'glyph': update all references to the'icon'literal in element type checksConfig.processing.iconNamePattern→Config.processing.glyphNamePattern: update config objects and any code referencing this field
[0.12.0] - 2026-03-05
Added
ElementTypeRef— reference to an external element type definition via$refURIConfig.processing.iconNamePattern— optional naming pattern for detecting icon content assetsSides— per-side composite object with logical directions:top,end,bottom,startCorners— per-corner composite object with logical directions:topStart,topEnd,bottomEnd,bottomStartStyles.padding— scalar when uniform,Sidesobject when per-side values differ
Changed
AnatomyElement.type— widened fromstringtostring | ElementTypeRefStyles.strokeWeight— acceptsStyle | Sidesinstead ofStyle; scalar when uniform,Sidesobject when per-side values differStyles.cornerRadius— acceptsStyle | Cornersinstead ofStyle; scalar when uniform,Cornersobject when per-corner values differ
Removed
Styles.paddingLeft— useStyles.paddingwithSides.startinsteadStyles.paddingRight— useStyles.paddingwithSides.endinsteadStyles.paddingTop— useStyles.paddingwithSides.topinsteadStyles.paddingBottom— useStyles.paddingwithSides.bottominsteadStyles.strokeTopWeight— useStyles.strokeWeightwithSides.topinsteadStyles.strokeBottomWeight— useStyles.strokeWeightwithSides.bottominsteadStyles.strokeLeftWeight— useStyles.strokeWeightwithSides.startinsteadStyles.strokeRightWeight— useStyles.strokeWeightwithSides.endinsteadStyles.topLeftRadius— useStyles.cornerRadiuswithCorners.topStartinsteadStyles.topRightRadius— useStyles.cornerRadiuswithCorners.topEndinsteadStyles.bottomLeftRadius— useStyles.cornerRadiuswithCorners.bottomStartinsteadStyles.bottomRightRadius— useStyles.cornerRadiuswithCorners.bottomEndinstead
Migration
Styles.paddingLeft/paddingRight/paddingTop/paddingBottom→Styles.padding: when all sides are equal, readpaddingas a number; when sides differ, readpaddingas aSidesobject withtop,end,bottom,startusing logical directions (start= left in LTR)Styles.strokeTopWeight/strokeBottomWeight/strokeLeftWeight/strokeRightWeight→Styles.strokeWeight: same scalar-or-Sidespattern; individual per-side stroke weights now live underSides.top,Sides.end,Sides.bottom,Sides.startStyles.topLeftRadius/topRightRadius/bottomLeftRadius/bottomRightRadius→Styles.cornerRadius: same scalar-or-Cornerspattern; per-corner values useCorners.topStart,Corners.topEnd,Corners.bottomEnd,Corners.bottomStart
[0.11.0] - 2026-03-04
Added
PropBinding— component-prop binding type; discriminated by$bindingkey; replacesReferenceValueTokenReference— unified DTCG-aligned token reference; replacesVariableStyleandFigmaStyle;$tokenand$typeare the complete platform-facing APITokenReference.$token— DTCG dot-separated token path, usable directly as a DTCG aliasTokenReference.$type— DTCG token type discriminator:color,dimension,string,number,boolean,shadow,gradient,typography,effectsTokenReference.$extensions["com.figma"]— optional Figma extraction metadata:id,name,collectionName,rawValueStyles.typography— composite typography; value isTokenReference,Typography, or omittedTypography— 13 optional inline text properties:fontSize,fontFamily,fontStyle,lineHeight,letterSpacing,textCase,textDecoration,paragraphIndent,paragraphSpacing,leadingTrim,listSpacing,hangingPunctuation,hangingListStyles.aspectRatio— aspect ratio constraint;AspectRatioValuepair ornullwhen unconstrainedAspectRatioValue— requiredx(numerator) andy(denominator) number fieldsAspectRatioStyle— type aliasAspectRatioValue | nullConfig.format.tokens— token reference serialization profile; five options:TOKEN(default,$token+$type),TOKEN_NAME(dot-delimited path string only),TOKEN_FIGMA_EXTENSIONS($token+$type+$extensions["com.figma"]),FIGMA_NAME(slash-delimited path string only),CUSTOM(same asTOKEN, signals custom post-processing)Metadata.license?— optional{ status: string; description: string }fieldstyles.textColor— style key for text colourstyles.cornerSmoothing— style key for corner smoothing (Figma squircle factor)styles.effects— style key replacingeffectStyleId;TokenReferenceor inlineEffectsShadow— fields:visible,inset?,offsetX,offsetY,blur,spread,colorBlur— fields:visible,radiusEffects— fields:shadows?,layerBlur?,backgroundBlur?GradientStop— fields:position(0–1),colorGradientCenter— fields:x,y(0–1)LinearGradient— discriminated bytype: 'LINEAR'; fields:angle,stopsRadialGradient— discriminated bytype: 'RADIAL'; fields:center,stopsAngularGradient— discriminated bytype: 'ANGULAR'; fields:center,stopsGradientValue— discriminated unionLinearGradient | RadialGradient | AngularGradient
Changed
Element.instanceOf— acceptsstring | PropBinding; bound form is{ $binding: "#/props/..." }Element.text— acceptsstring | PropBinding; bound form is{ $binding: "#/props/..." }Style—PropBindingreplacesReferenceValue;TokenReferencereplacesVariableStyleandFigmaStyleConfig.format—variables,simplifyVariables, andsimplifyStylesreplaced by singletokensfield; token output shape is now controlled entirely by thetokensprofileStyles.backgroundColor— narrowed toColorStyle; inline gradients representableStyles.textColor— narrowed toColorStyle; inline gradients representableStyles.strokes— narrowed toColorStyle; inline gradients representablestyles.fillsrenamed tostyles.backgroundColor
Removed
Config.format.variables— removed; useConfig.format.tokensinsteadConfig.format.simplifyVariables— removed; useConfig.format.tokensprofile insteadConfig.format.simplifyStyles— removed; useConfig.format.tokensprofile insteadReferenceValue— removed; usePropBindinginsteadisReferenceValue— removed; no replacement; prop-binding guards are upstream consumer responsibilityVariableStyle— removed; useTokenReferenceinsteadFigmaStyle— removed; useTokenReferenceinsteadStyles.fontSize— removed; usetypography.fontSizeinsteadStyles.fontFamily— removed; usetypography.fontFamilyinsteadStyles.fontStyle— removed; usetypography.fontStyleinsteadStyles.lineHeight— removed; usetypography.lineHeightinsteadStyles.letterSpacing— removed; usetypography.letterSpacinginsteadStyles.textCase— removed; usetypography.textCaseinsteadStyles.textDecoration— removed; usetypography.textDecorationinsteadStyles.paragraphIndent— removed; usetypography.paragraphIndentinsteadStyles.paragraphSpacing— removed; usetypography.paragraphSpacinginsteadStyles.leadingTrim— removed; usetypography.leadingTriminsteadStyles.listSpacing— removed; usetypography.listSpacinginsteadStyles.hangingPunctuation— removed; usetypography.hangingPunctuationinsteadStyles.hangingList— removed; usetypography.hangingListinsteadStyles.textStyleId— removed; usetypographywith aTokenReferenceinsteadstyles.effectStyleId— removed with no deprecation period; consumers must migrate tostyles.effects
Migration
config.format.variables/simplifyVariables/simplifyStyles→config.format.tokens: settokensto a profile —TOKEN(default) for$token+$type;TOKEN_NAMEfor dot-delimited string;TOKEN_FIGMA_EXTENSIONSfor full object with Figma metadata;FIGMA_NAMEfor slash-delimited string;CUSTOMfor custom post-processingElement.instanceOf→Element.instanceOf: replace{ $ref: "#/props/..." }with{ $binding: "#/props/..." }; update anyisReferenceValueguards to narrow against$bindingdirectlyElement.text→Element.text: same key rename from$refto$bindingStyles.visible→Styles.visible: same key rename from$refto$bindingVariableStyle→TokenReference: replace{ id, variableName, collectionName }with{ $token: "<Collection>.<name>", $type: "<type>", $extensions: { "com.figma": { id, name, collectionName } } }; read token path from$tokenand token category from$typeFigmaStyle→TokenReference: replace{ id, name }with{ $token: "<dot.path>", $type: "typography" | "effects" | ... }and move the Figma UUID to$extensions["com.figma"].idStyles.<typographyProperty>→Styles.typography.<property>: all 14 flat typography properties replaced with single compositetypographyfield; whentypographyis aTokenReference, read$tokenfor the style path; whentypographyis aTypographyobject, read individual fieldsfills→backgroundColor: any consumer readingcomponent.styles.fillsmust update tocomponent.styles.backgroundColoreffectStyleId→effects: any consumer readingstyles.effectStyleIdmust update tostyles.effects; wheneffectsis aTokenReference, read$tokenand$type; wheneffectsis anEffects, read fromshadows,layerBlur, orbackgroundBlurby role
[0.9.0] - 2026-02-12
Added
- DEFAULT_CONFIG - Default configuration constant for transformer setup
- Provides sensible defaults for all Config fields
- Ensures consistent behavior across CLI, MCP, and Plugin environments
- Co-located with Config type definition for easy maintenance
[0.10.0] - 2026-02-16
Added
- Runtime JS build - Compile TypeScript types to
dist/index.jsfor ESM consumers- Enables ESM runtime imports without loading
.tsfiles - Keeps type definitions in
types/for TypeScript tooling
- Enables ESM runtime imports without loading
[0.8.0] - 2026-02-10
Added
- TypeScript type definitions (
types/package): Complete type definitions for all schema entities- Core types: Component, Anatomy, Props, Variant, Metadata, Config, Styles, Element, Layout
- Property types with proper discriminators: BooleanProp, TextProp, IconProp, EnumProp, SlotProp
- Style types supporting all 59 properties with specific value types
- Reference value types for prop bindings and style references
Changed
- Component.metadata - Now optional to support components without full metadata
- Variant.layout - Changed from single LayoutNode to array of LayoutNode for proper hierarchical representation
- Props type values - TextProp, IconProp, and EnumProp now use
type: 'string'with discriminators (matching schema) - Style properties - Changed from generic
Record<string, Style>to specific Partial interface with all 59 style properties - VariableStyle - Now includes all properties from schema: id (required), rawValue, name, variableName, collectionName, collectionId (all optional)
- FigmaStyle - Simplified to match schema: id (required), name (optional)
Fixed
- SlotProp.default - Now accepts both
nullandstringtypes - TextProp/IconProp nullable - Made nullable field optional (was incorrectly required)
- Metadata.source - Added missing nodeType field with enum: COMPONENT | COMPONENT_SET | FRAME
- Metadata.config - Added complete Config definition with processing, format, and include options
- StyleKey type - Expanded from incomplete list to all 59 valid style properties matching schema
[0.7.0]
Changed
- Added distinct
anatomytypes for line, ellipse, star, polygon and rectange, all which were previously vector - Removed autodetecion of icon assets based on
isAssetplugin since this function is unavailable in the REST API
[0.6.0] - 2026-01-27
Added
-
Styles schema: New
styles.schema.jsonproviding complete validation for style properties- Defines 60+ style properties (fills, opacity, cornerRadius, fontSize, etc.)
- Type-specific validation for each property based on Style processor classes
- Supports all style value types: primitives, variables, Figma styles, and prop bindings
- Documents which value shapes depend on config.format settings (simplifyVariables, simplifyStyles)
-
Style value types: Precise type definitions for style property values
NumberStyleValue: number | VariableStyle | nullBooleanStyleValue: boolean | VariableStyle | nullBooleanBindableStyleValue: boolean | VariableStyle | ReferenceValue | null (forvisibleonly)ColorStyleValue: string | VariableStyle | FigmaStyle | nullStringStyleValue: string | VariableStyle | nullMixedNumberStyleValue: number | “mixed” | VariableStyle | nullMixedStringStyleValue: string | VariableStyle | nullStrokeStyleValue: number | “mixed” | VariableStyle | nullCornerStyleValue: number | “mixed” | VariableStyle | nullFontStyleValue: string | number | “mixed” | nullLineHeightStyleValue: string | number | VariableStyle | nullStyleIdValue: string | FigmaStyle | null
-
Styles reference documentation: New
reference/styles.yamlproviding human-readable documentation- Maps style properties to their applicable element types (COMPONENT, FRAME, TEXT, etc.)
- Documents style processing categories from RAW_STYLES_LOOKUP
- Lists all 60+ style properties with descriptions and value types
- Generated from StyleKeys.ts and RawStyles.ts constants
Changed
-
VariableStyle definition: Removed internal-only
rawValueproperty- Only includes properties emitted by Style.data(): id, name, variableName, collectionName, collectionId
rawValueis used internally for rendering but never serialized
-
FigmaStyle definition: Improved descriptions
- Documents simplified vs full object output based on config.format.simplifyStyles
-
Styles property: Now references
styles.schema.json#/definitions/Styles- Replaces generic
additionalProperties: Stylewith precise property-level validation - Each style property validates against its specific value type union
- Replaces generic
[0.5.0] - 2025-12-29
Added
-
Instance of attribute: Added optional
instanceOfproperty to element definitions- Indicates the component or component set name that an instance element references
- Only present for instance elements (Figma INSTANCE nodes)
- Shows ComponentSet name for variant instances, or Component name for standalone instances
- Also added to anatomy element definitions
-
Text property: Added optional
textproperty to element definitions- Contains the text content for text elements
- Can be a string value or a
ReferenceValuewhen bound to a text prop
-
Unified property bindings: Property bindings now appear as
$refvalues on their natural propertieschildrencan now be aReferenceValue(for slot content bindings)instanceOfcan now be aReferenceValue(for instance swap bindings)visiblestyle can now be aReferenceValue(for boolean prop bindings)textcan be aReferenceValue(for text prop bindings)- Added
ReferenceValuedefinition with$refJSON pointer to props
Removed
- propBindings section: Removed separate
propBindingsproperty from elements- Bindings are now represented directly on their target properties using
$ref
- Bindings are now represented directly on their target properties using
[0.4.0] - 2025-12-27
Added
- Layout structure: Added optional
layoutproperty to variant definitions that provides hierarchical element structure as a nested tree- Layout represents element nesting relationships in a recursive format
- Leaf nodes are strings (element names)
- Parent nodes are objects with element name as key and children array as value
- Example:
{ "parentElement": ["childA", { "childB": ["grandchild"] }] }
Changed
- Schema now supports both structure representations:
- New:
layoutat variant level (hierarchical tree) - Existing:
parentandchildrenproperties in each element (maintained for backward compatibility)
- New:
Deprecated
includedproperty has been removed from element definitions- Element inclusion is now determined by presence in the
layouttree - Elements not present in
layoutare considered excluded from that variant
- Element inclusion is now determined by presence in the
[0.3.0] - Previous release
[0.31.0] - 2026-09-23
The spec workspace now versions itself. specs version grades every change, keeps full history under versions/, and generates the release report and changelog — all in the free tier.
Breaking
- A component’s unsupported prop combinations are written to
api.yamlasinvalidPropCombinations, rather than tovariants.yamlasinvalidVariantCombinations. Regenerate your specs; anything reading the old key or the old file finds nothing (ADR-092).
Added
specs version cutgives the spec workspace a semantic version, grading every change since the last version:api.yamlchanges drive MAJOR or MINOR, whilevariants.yamland examples never exceed PATCH. A first cut starts the workspace at 0.1.0; each version is stored in full underversions/<version>/, withversions/latest/mirroring the newest.specs version diff,history, andrestoreanswer what changed, how it happened, and what a component looked like at any version, reading the historycutmaintains.restorenever overwrites the live spec.specs version figmapremergereports the impact of a Figma branch before you merge it, from just the branch URL — it derives the main file, generates specs from both sides, and writes the graded comparison underversions/diffs/.specs version reportrenders the release report and itemized changelog for everything versioned since the last release; the same two files land in each version’s folder when it is cut.- A recorded rename reports as one change with a
from → tomigration line, never a removal plus an addition. Record component, prop, and enum value renames inversions/renames.yaml. --force-major,--force-minor, and--force-patchoverride the computed grade with a required reason, recorded permanently in the history.specs version tagcreates the annotated library git tag for any cut version, at cut time or later, from the recorded history — never pushed.specs skills installemits the premerge and release orchestration skills into.claude/skills/, overwriting on refresh so the skills always match the CLI that ships them.
Dependency updates
@directededges/specs-schema^0.34.0 — specs name disallowed prop combinationsinvalidPropCombinations, carried inapi.yaml, and an element carrying positioning offsets always declaresposition: ABSOLUTE, so placement reads without consulting the parent’s layout mode.@directededges/specs-from-figma^0.33.0 — generated specs emit the renamedinvalidPropCombinationskey; entries and gating are unchanged.@directededges/react-from-specs^0.3.0 and@directededges/webcomponents-from-specs^0.3.0 — generated components no longer reference parts that were never drawn: ids referenced byhtmlFororaria-describedbyalways exist, collapsed controls take their name from the nominated accessibility prop, form controls emit the accessible name they resolved, and repeated indicators each render on the side the layout drew them. A rate-limited license check no longer locks a watch session into failure until restart.
[0.30.0] - 2026-09-20
A catalogue run states its metadata once. The author, generator, schema, conventions and settings were identical in every file a run produced, and repeated again for every concern document and subcomponent inside it; they now live in one latest.metadata.yaml beside the specs.
A watch keeps Storybook current without re-running a command. specs react --watch and specs webcomponents --watch re-emit on every spec or config change, and a full run now prunes emitted directories no spec accounts for.
Added
- A manifest run writes its metadata once, to
latest.metadata.yamlbeside the specs, leaving every spec withmetadata.sourcealone (ADR-089). specs react --watchandspecs webcomponents --watchre-emit on every spec or config change, watchingconfig/alongside the specs directory. Each change re-emits the whole set and re-runsfinalize; a failed component is logged and the watch continues.- A full
specs reactorspecs webcomponentsrun removes emitted component directories no spec accounts for, naming them in one warning line, so Storybook stops indexing a renamed component under its old name. A--componentsrun never prunes. specs rendermarks a component ready for dev in Figma, using the statusspecs scanrecorded for it. A component whose manifest row readsNONE, or has no row at all, renders unmarked as before.specs fetch --only variables --from-bridgedownloads a library’s variables through the connected plugin, so a workspace on any Figma plan gets variables without the Enterprise-only REST endpoint or aFIGMA_TOKEN. Needs the bridge running and the plugin connected in the library file.
Changed
- Every rule in a generated stylesheet is labelled with the spec statement that produced it, so a variant block names its configuration and the cursor rules name the state that declared them.
- A web components stylesheet selects on the attribute the element reflects,
:host([appearance="filled"])rather than a data attribute. A prop named after a global HTML attribute such ashiddenortitlekeeps the data form. React stylesheets are unchanged. - A subcomponent’s CSS class is namespaced by its parent, so
deCard’sreviewsstyles under.de-card-reviewsrather than.reviews. Regenerate every platform tree together. - Emitted files no longer repeat the component’s name, so
DeFavoriteButton/holdsscaffold.tsx,contract.ts,styles.cssandstories.tsx. Regenerate each platform tree from scratch: a run overwrites the new files but leaves the old prefixed ones, and Storybook would load both. - A per-concern spec file records its write time as
lastUpdated, the key the schema already defines for that fact, soapi.yaml,variants.yamlandexamples.yamlvalidate and an editor stops reporting an error on every spec file. specs renderreads the run’s metadata document back, so a spec carrying onlymetadata.sourcestill renders under the conventions and settings it was generated with (ADR-089).
Fixed
- An unreachable license server reports differently from a failed check: the first points at your network, any proxy, and TLS inspection; the second says to retry. Both say the key was not validated and no licensed output was written.
analyze propsreports a prop with no declarednullableas accepting null where its value set is open — a string, number, slot or image prop — and as not accepting null for an enum or boolean (ADR-065).- A classified state prop carrying a value no concept names now warns, naming the prop, the value and the component, instead of dropping the styling declared for it from the stylesheet in silence.
analyze dependenciescounts a slot relationship toward a component’s totals, so the roots and leaves lists no longer overlap and a component connected only through a slot is no longer reported as unconnected.- Focus styling follows the platform’s keyboard-focus heuristic —
:focus-visibleon a root that can hold focus,:has(:focus-visible)on a wrapper around its control — so a clicked button no longer keeps its focus ring after the click. A declaredfocus-withinconcept is untouched. - An image fills and centres itself in the element that displays it, whatever its aspect ratio, instead of drawing at its natural size cropped to the top-left corner when a component takes its image from a source prop.
specs scanleaves a subcomponent unchecked when its parent is checked, since a subcomponent is already specced as part of its parent. A separate component that a checked component instances is still selected.- A composed instance fills a slot that states its size through opposing insets. A slot pinned to all four edges names no width yet is as wide as its container; the child used to paint at its own master’s size.
- A child filling its parent’s cross axis keeps the parent’s alignment. Cross-axis FILL emits a dimension rather than
align-self: stretch, so a child stopped from filling by amin-widthormax-widthis centred under a centring parent. - A variant styled on a state the component’s own control announces keeps its rule, selecting on the prop’s data attribute rather than being dropped with a warning that no concept names the value.
- The
not-allowedcursor reaches a component whose disabled state lives on a control inside it, selecting what the root actually carries instead of an attribute only the nested control has. - A text field keeps the font and colour the spec gave its value and placeholder, carried onto the emitted input and its
::placeholderrather than left on elements the control replaced.
Dependency updates
- specs-schema 0.33.0 — a run’s shared facts (author, timestamp, generator, schema, conventions, settings) now have a document type of their own, so specs carry only their
sourceand the run states the rest once. The three per-concern files a split run writes are each typed as their own document, and a component can record the image it was authored with. - specs-from-figma 0.32.0 — generated specs keep more of what designers composed: a plain frame inside an example can become a placed instance with its children as slot content, and an image reaches the spec through any instance carrying a configured source prop, not only a direct instance of the designated image component. License checking no longer degrades silently — a key that could not be checked fails the run and says so, rate-limited checks retry with backoff, and an unreachable server reports differently from a failed check.
- react-from-specs 0.2.0 / webcomponents-from-specs 0.2.0 — an emitted platform tree is a self-contained package (its own
package.jsonandtsconfig.json), generated files drop the component-name prefix, a subcomponent’s exports are namespaced by its parent, and a long list of contract and render fixes makes emitted trees typecheck clean.
[0.29.0] - 2026-09-12
specs react and specs webcomponents replace specs transform, each emitting one target whole — component, contract, stylesheet and stories. Annotated elements emit real controls: with settings.spec.roles on, a checkbox is a native input you can check and submit, a disclosure announces and flips its own state, and the accessibility wiring between a control and its parts is generated. The emitted code is substantially better — effects and directly-declared gradients are emitted for the first time, and strokes, gradient geometry, truncation and background images now paint what the design draws. Configuration is reshaped: conventions become one file per platform in config/conventions/, config/pipeline.yaml is retired, and a stale layout stops the run rather than silently generating with defaults — specs migrate config moves a workspace over.
Breaking
Conventions are one file per platform. A single config/conventions.yaml is no longer read —
conventions now live in config/conventions/, the filename carrying the platform id
(figma.yaml, react.yaml, web-components.yaml), with each platform’s block de-indented to the
root of its own file (ADR-073, ADR-078). The conventions that describe the spec rather than any
platform — the states classification, accessibility.label, value — move out of the Figma
block into config/conventions/specs.yaml, and the primitive promotion table is authored as
config/conventions/figma.primitives.yaml (ADR-075).
A workspace still holding config/conventions.yaml stops with instructions rather than being
silently ignored — in 0.28.0 it was not recognized as configured at all, so generation ran with
defaults and produced specs missing everything the file declared, without ever failing. The same
refusal covers the old primitives.yaml basename.
specs migrate config performs the whole move, including from the pre-0.28.0
specs.config.yaml, and seeds the code-platform files.
specs transform is removed, as is config.pipeline on the loaded config. specs react and
specs webcomponents replace it.
Added
specs reactandspecs webcomponentseach emit one target whole — component, contract, stylesheet and stories — rather than asking for the pieces to be named in the right order.--no-storiesomits the stories. Output lands in that target’s own tree (react/,webcomponents/).- Roles emit native controls and their accessibility wiring (ADR-067, ADR-068, ADR-086), for
both targets.
settings.spec.rolesis the on-switch — off by default, so nothing changes until a workspace opts in. Controls (button,togglebutton,link,disclosure,checkbox,switch,textbox), announcements (alert,status,progressbar) and parts (label,description,errormessage,indicator,panel) are implemented;valueandplaceholderare concepts atextboxreads while collapsing, not roles that emit on their own. Any other role in the vocabulary stays inert rather than breaking. Roles that own a state — pressed, expanded, checked, value — generate real state management from the prop yourstatesconvention classifies, and degrade to a stub with a warning when no prop carries it. A part provided by a composed component (a field’s label or error message) is wired across that boundary. - A Web Components target. Lit custom elements with shadow DOM and native slots, driven by the
same spec and the same CSS as React. Form controls participate in the page’s
<form>and take focus into the shadow root. Experimental; output shape may change without a breaking-change note. specs fetch --source <url>fetches a Figma branch that is not in your config, naming it after the branch and fetching the same data kinds as the file it branches from. Payloads land beside the library’s as<parent>-<branch>.file.json, so a branch you compare a few times and throw away never needs a config edit.- CSS custom properties resolve from fetched library JSON rather than from any one component.
- Effects and gradients set on an element are emitted — shadows, blurs and gradient fills declared directly rather than through a token, dropped until now.
settings.spec.promotePrimitivesswaps raw layers in example content for the design system components they stand in for. Off by default, and sent over--from-bridgeso the plugin uses your table.- A primitive promotion table names which component each raw layer becomes — matching a layer’s
style values against designated component props, so a text or container layer a designer drew by
hand records as an instance of the component it stood for. Authored as
config/conventions/figma.primitives.yaml(ADR-075).
Changed
css,cssvars,contract,storiesandwebcomponents-storiesare no longer names you invoke. Each is part of what a target emits, andcssin particular had no output location of its own once output moved into the platform trees.specs scanretains the components a checked component composes. A dependency is kept in the manifest even when its own dev status would not have selected it, so generating a checked component no longer produces a spec whose composed children are missing. The run reports how many were retained;--include-allbypasses the filtering entirely.specs scan --source <alias>accepts a source fetched with--source, resolving it from the payload on disk when config has no entry for it.specs generate --get-imagespulls images from the file the specs came from, rather than always from the configured source — so specs generated from a branch get the branch’s images.- The React and Web Components transformers now live in their own packages —
@directededges/react-from-specsand@directededges/webcomponents-from-specs, which the CLI consumes as dependencies. Both install with the CLI; there is nothing to add to a workspace. - Emitted stylesheets and cssvars name the command that regenerates them, per target
Removed
specs transform, andconfig.pipelineon the loaded config.specs reactandspecs webcomponentsreplace it;--no-storiesomits the stories.config/pipeline.yaml.specs initno longer seeds it andspecs migrate configno longer writes one, droppingconfig.transformersrather than carrying it forward. A leftoverpipeline.yamlproduces a warning naming the commands that replaced it, andspecs migrate configrenames it out of discovery.- Emit-time primitive binding resolution —
PlatformConventions.primitivesand its per-kind binding types. Which component a layer becomes is decided at capture (ADR-074)
Fixed
-
A background image resolves from the stylesheet that references it. Image assets moved to
assets/images/at the workspace root when output split into platform trees, but the stylesheets kept emittingurl('../../_images/…')— a path into the React or Web Components tree, where nothing has been written since. The depth is now derived from where each stylesheet lands, so a component and a subcomponent cannot drift apart -
An angular gradient starts where the design starts it. It was written with a 90-degree offset, on the premise that Figma’s angular sweep begins at 3 o’clock and CSS’s conic at 12. An angular gradient carries no angle (ADR-003) — the rotation it appears at is the node’s, recorded as
rotationand applied as a transform — so the offset turned the arc a second time and every gradient-stroked element painted its arc a quarter turn out -
A gradient stroke paints the ring the design draws. It was emitted as
border-color, which cannot hold a gradient, so the surface painted flat — andborder-image, the only gradient-capable border property, ignoresborder-radiusand turns a circular element into a square frame. The ring is now a::beforethat covers the element, inherits its radius, and masks out its own middle: the element keeps its own background and declares no border, so the stroke costs no layout, the same as a solid one. A gradient arriving as a token reference is recognised by its$type, which previously saidcolorfor every styled paint -
A stroke no longer changes an element’s size. Figma draws a stroke without costing the frame any space, at any alignment; a CSS border only stays inside when the element has an explicit size, so a component hugging its content came out as much as 2px larger than the design and its content sat a pixel in. Every stroke is an outline now, with an inside stroke pulled back over its own edge
-
A text layer that truncates in the design truncates in the CSS.
maxLinesandtextOverflowwere carried in the spec and emitted nowhere: one line becomestext-overflow: ellipsisheld on a single line, more than one becomes the line-clamp box -
Stylesheets no longer reference a variable Figma could not resolve. The engine writes a sentinel name for an unreadable variable, and that name derived into an ordinary custom property that nothing defines. A single property now writes
unsetinstead — stating that the variant overrode it with something unreadable, so a base rule’s value does not win — and a warning at the end of the run names the sentinel and its occurrence count -
Text components sized in the design now render at the height their type styles ask for, rather than a line short of it
-
scankeeps the components a selected component is built from, instead of leaving them unchecked because they carry no dev status of their own -
scanleaves out the Examples sets and code-only-props sets a library keeps as authoring aids, including those filed in a hidden folder -
Loading a workspace with no
config/no longer throws while resolving conventions
[0.28.0] - 2026-09-04
Configuration is now three files that say different kinds of things — facts about your Figma library, choices your runs make, and the work your workspace runs — and specs migrate config moves an existing workspace over in one command. The split folder-per-component layout is now what specs generate writes without flags, since it is the shape every downstream command reads. And scan now records the dev status Figma actually set, so a component marked complete no longer looks identical to one nobody has touched.
Breaking
specs.config.yaml → a config/ directory for conventions, settings and pipeline, and
specs migrate config switches an existing workspace over.
The single file mixed two kinds of statement: facts about your Figma library that every consumer of it must agree on, and choices this particular run makes. A wrong fact produces incorrect output, while a different choice just produces different output — so they are now three files that can be read, validated and reviewed apart. Every command that loads configuration is affected, and a workspace that has not migrated gets an error naming the file and the command that converts it rather than a silent fall back to defaults, because the shape of your output depends on this file and ignoring it quietly would change what you emit without saying so.
Added
specs migrate <subject>— versioned workspace migrations.specs migrate configreadsspecs.config.yaml(or.json) and writesconfig/conventions.yaml,config/settings.yamlandconfig/pipeline.yaml, then renames the source to.migratedso discovery stops finding it.conventions.yamlandpipeline.yamlare written only if the source configured them;settings.yamlis always written, because the layout flags have to be carried across explicitly (see below).--dry-runreports what would change without writing.--source <path>converts a file discovery would not find — a custom name, or one you pass to other commands via--config.--from <version>pins the source version;--listshows what is registered.- The command is a registry, not a synonym for this one conversion: later migrations
register alongside
config v1 → v2rather than replacing it. - It writes to your workspace, which is why it is a command you run rather than something the loader does for you. Configuration is loaded by read-only commands and in CI, and a read path must not mutate a checkout.
Changed
- Configuration is discovered in
./config/. A directory is used when it holds at least one ofconventions,settingsorpipeline(.yamlor.json).--confignow accepts that directory as well as a file path. specs initscaffolds the three files and refuses to run in a workspace that still hasspecs.config.yaml, pointing atspecs migrate config. Scaffolding fresh defaults over a configured workspace would replace what it declares, andmigratewould then refuse becauseconfig/exists.- The vocabulary changed with the split —
format.figmaKeysis nowconventions.figma.naming,processing.glyphNamePatternisconventions.figma.glyphs.match,format.outputissettings.spec.format, and the rootsources,dataDirectory,outputDirectoryandoutputblocks fold intosettings.specs migrate configapplies all of it; the schema changelog lists the mapping in full. - Spec metadata records conventions and settings separately, so either half can be
compared across specs on its own. Regenerating rewrites the
metadatablock of every spec file. - The split layout is now the default.
specs generatewrites one folder per component holdingapi,variantsand (when there are examples)examplesfiles, without any flags. Everything downstream —transform,analyze,render— reads that layout, so it was the shape almost every workspace had to opt into by hand. - Layout flags are named for what they do.
--split-components,--split-concernsand--use-subfoldersare replaced by--combine-as-library,--combine-concernsand--no-subfolders. Each turns a split off; an absent flag defers to the configured value rather than overriding it. specs migrate configpreserves the layout a workspace emits today. The three flags defaulted tofalseinspecs.config.yamland default totruenow, so migration writes all three out explicitly — including from a source that configured nooutputblock. Deleting those three lines fromconfig/settings.yamlis how a migrated workspace adopts the new default.specs initno longer writes the three flags. The generatedconfig/settings.yamlshows them commented out at their defaults.transform’s prerequisite tip no longer names flags that no longer exist; runningspecs generateis now sufficient.
Removed
specs.config.yamlandspecs.config.jsonare no longer read. They are still detected, but only to refuse with the command that converts them.~/.specs/config.yaml— user-level configuration — has no equivalent. Conventions describe a specific Figma library and settings describe a specific workspace; neither is a personal preference that should follow you between projects. Move what it declares into the workspace’sconfig/directory and delete it.--split-components,--split-concernsand--use-subfoldersonspecs generate, replaced by the negative flags above.
Fixed
scanrecords the dev status Figma actually set, includingCOMPLETED— Every status on a node is carried through to the manifest’s Dev Status column verbatim instead of being collapsed toREADY_FOR_DEVorNONE, so a component marked complete is no longer indistinguishable from one nobody has touched. Selection is unchanged:scanstill checks onlyREADY_FOR_DEVrows by default, andgeneratestill builds only what is checked.- A manifest row with an unfamiliar dev status is kept and left unselected, not dropped — The parser previously accepted only two status values and silently discarded any row it could not match, so the component vanished from the run while the summary line still read as healthy. Such a row is now parsed, reported with a warning, and left unchecked.
scanandgeneratewarn about manifest rows they cannot read — Any line that looks like a component row but fails to parse now prints a warning naming the row instead of disappearing.
Dependency updates
- specs-schema 0.31.0 — the single
Configtype splits intoConventions,SettingsandPipeline, each authored and validated on its own; specmetadatarecords conventions and settings separately. A prop configuration can statenullto leave a nullable prop unset, and a numeric variant prop now carries its closed set of values in authored order. - specs-from-figma 0.30.0 — a root that only wraps a slot now collapses when
collapsePrimitiveWrapperis on; an instance example that hides a slot records it as unset rather than bound-and-hidden; and a code-only prop layer carrying both a content and a visibility binding is named from the binding it was extracted from, so a phantom prop no longer displaces the real one.
[0.27.0] - 2026-08-17
Specs can now go back into Figma. specs render takes a spec you already have and builds
the component it describes in a connected Figma file — the reverse of specs generate, and
the other half of a round trip you can run and re-run as a spec changes.
Added
specs render— build a component in Figma from a spec. Point it at a spec file, a component folder, or a directory of them; with no argument it renders everything in your configuredoutputDirectory. Variants, styles, props, slots, subcomponents, and icons are all reconstructed, with tokens bound to the file’s own variables and styles.--watchre-renders on every save, so you can edit a spec and watch the component change.--overwritereplaces a component of the same name. Without it, a name collision is an error rather than a silent deletion.--page <id>renders onto a specific page instead of whichever one is open — worth using in any script, since the open page can move underneath you.--strictfails the run when an element cannot be resolved, instead of producing a component with content missing.--timingreports where the time went, phase by phase.
specs bridge— the connection render works over.start,stop, andstatusfor a local server the Figma plugin connects to. Figma cannot be reached from outside, so the plugin opens the connection and the CLI talks through it. Enable the CLI Bridge in the plugin, andspecs bridge statuswill name the file it is connected to. Several Figma files can be connected at once;--file <fileKey>picks one, and in an interactive terminal you are offered a numbered list instead of an error.specs cache— the lookup tables render needs. Every component, icon, style, and variable name in your fetched data, resolved to what Figma needs to place it. Built fromspecs fetchoutput, refreshed with--force, or rebuilt in place withspecs render --refresh-cache. A stale cache fails the render rather than rendering something subtly wrong.specs generate --from-bridge— read a spec from what’s selected in Figma. A third source forgeneratealongside a fetched file and a manifest, with no REST fetch and no Figma token: select a component in a connected file and generate its spec directly. Your config governs the result, exactly as it does for a fetched generate, and the run leaves the Figma file untouched. Output follows the same--output/--split-components/--split-concernsresolution as every other source.specs fetch --only <name>now narrows by data kind as well as by source.--only iconsre-downloads just the icon SVGs, deriving them from the file payload already on disk rather than pulling the whole file again;--only variables,stylesskips the file entirely. A source alias still works as before, and the two combine (--only library,icons). A name that matches neither is an error listing both the configured aliases and the available kinds, rather than being quietly ignored.format.figmaKeysin the generatedspecs.config.yamltemplate — commented out at itsNONEdefault, documenting the opt-in that enables the safe key grammar and Figma name preservation (ADR-066)specs analyze keys— reports Figma layer and property names a formatted key cannot reconstruct, written to_analysis/keys.yaml. OrganizedbyComponentas a designer’s checklist — each component splitting intopropsandanatomy, with an empty surface omitted — thenbyCausefor systemic problems andbyNamefor a name repeated across the library. Requiresformat.figmaKeysto declare a convention; empty under theNONEdefault (ADR-066)specs fetchicons — a fourth data kind alongsidefile,variables, andstyles: fetch derives the library’s icon glyphs from the downloaded file payload (noscanrequired) and downloads their SVG assets with stable kebab-case slugs.
Changed
- CSS
overflowoutput — keyed on the renamedStyles.clipsContent, sooverflow: hidden/overflow: visibleis emitted for the first time (ADR-069)
Dependency updates
@directededges/specs-schema^0.30.0 — specs can now record the name a designer sees in Figma alongside a formatted key, so a key that cannot reconstruct its original name no longer loses it. Number properties gained the same platform-extension capability every other property type already had, and the style that records whether an element clips its content is now spelledclipsContent.@directededges/specs-from-figma^0.29.0 — generated specs carry Figma names wherever a key diverges, and a name already well-formed in your key convention is kept as authored instead of being reformatted. Several sources of phantom variants are gone: slot content no longer records measured sizes or withdrawn host bindings as design intent, an invalid variant combination now names the combination that is actually missing, and elements that clip their content report it for the first time — expect regenerated specs to gainclipsContent: trueon containers that clip, which is common rather than rare.
[0.26.0] - 2026-08-07
Two new specs analyze reports help you understand a design system at scale. specs analyze dependencies maps how components compose one another — instances, slot constraints, and example compositions — so you can answer “what’s the blast radius of changing this component?” and “which props does anyone actually use?” specs analyze styling now also flags tokens nobody references, surfacing dead variables and styles you can safely retire.
Added
specs analyze dependencies— component dependency graph — A new analyzer that builds a directed graph of how components compose one another frominstanceOfreferences, answering “what is the blast radius of changing this component?” and “which of its props do consumers actually use?”. Edges carry a kind —instance(placed instances),slot(anyOfconstraints),example(slotContentExamplescompositions) — and the blast-radius closure runs overinstanceedges only, with slot/example coupling reported separately as contract relations. Unmatched references become nodes flaggedexternal; subcomponents collapse into their parent; split-concerns layouts (variants.yaml,examples.yaml) and$nesteddeep configurations are supported. Two aggregates land in_analysis/(extension followsformat.output):dependencies.graph.{json|yaml}— nodes with degrees, typed edges with element/slot/example labels, plus roots, leaves, cycles, and external counts.dependencies.byComponent.{json|yaml}— per component: direct and transitive dependents/dependencies with min depth, contract relations, andpropUsage— per prop: total configuration sites, per-consumer breakdown by section (default/variants/examples) with per-value site counts, and a value → consumers roll-up. Props no consumer configures appear withconfiguredBy: 0.
specs analyze stylingnow reports unused tokens — A third aggregate report,_analysis/styling.unused.{json|yaml}, lists every token in the fetched foundations data (variables, color styles, text styles, effect styles) that no analyzed spec references, with a per-categorysummaryof total/used/unused counts. Variables are matched by their collection-prefixed name (Collection name/Variable name), styles by name; grid styles are excluded. The report requires{alias}.variables.json/{alias}.styles.jsonin the data directory and is skipped when absent.
Dependency updates
@directededges/specs-schema^0.29.0 — Numeric properties can now be marked nullable (NumberProp.nullable), matching strings, slots, and images. Horizontal text alignment now uses logical inline-axis direction (START/END/CENTER/JUSTIFY) instead of physicalLEFT/RIGHT/JUSTIFIED, so generated specs read correctly regardless of writing direction.@directededges/specs-from-figma^0.28.0 — Horizontal text alignment extraction now emits the new logical directions. Code-only text props whose default is only whitespace are recognized as empty placeholders instead of literal example content. Several extraction bugs are fixed: slot resolution for unpublished local components, gradient center coordinates, number-named layers, and anatomy/element ordering.
[0.25.0] - 2026-07-16
Generated specs can now include the images your components actually use. Enable processing.images in config and image fills emit as typed backgroundImage values backed by a per-component images registry; then run specs generate --get-images to download each referenced image once into an _images/ folder and point every registry entry at a real file. Text truncation (textOverflow, maxLines) is now captured on text elements, rotated elements keep their sub-degree precision, and specs generate output is quieter — per-file overwrite warnings collapse into a single summary line.
Added
- Image config surface (ADR-063) —
processing.imagesis validated when present (presence is the on-switch):backgroundImagecoerces to a literal boolean,sourcePropsmust be a non-empty array of strings (trimmed), andimageComponentrequires a non-emptysourceProps(its forwarding target issourceProps[0]) — invalid members are dropped with warnings. The images registry joins the examples concern under--split-concerns(emitted inexamples.yaml, counted toward whether that file is written). specs generate --get-images(ADR-063 resolution phase) — Resolves unresolved registry entries (Figma identity in$extensions['com.figma'].imageHash, nosrc) into real files. Calls Figma’s Get Image Fills endpoint (requiresFIGMA_TOKENand a configured source key), downloads each distinct image once, writes_images/<imageHash>.<ext>inside the output directory (format detected from magic bytes: png/jpg/gif/webp), and addssrcto each entry — a path relative to the referencing spec file (_images/..., or../_images/...in per-component-folder layouts). The Figma identity survives for reverse-direction tooling;$imagepointers are untouched; temporary S3 URLs are never persisted; unresolvable hashes stay unresolved with a warning.
Changed
specs generatewrite output simplified — Per-file “Overwriting existing file” warnings now collapse into a single summary line instead of one per file, and the redundant ”✓ Saved to”/”✓ Generated N files” success echo has been removed.
Dependency updates
@directededges/specs-schema^0.28.0 — Adds the image vocabulary generated specs now use: thebackgroundImagestyle, the per-componentimagesregistry, image-typed props, image bindings for forwarding into nested instances, and theConfig.processing.imagesblock. Also addstextOverflow/maxLinesfor text elements, and fixes several false validation failures — color token references with resolved color values, subcomponentsourceidentity, and collapsed-rootoriginalNameprovenance now validate cleanly, while$nestedconfigurations with an empty path are correctly rejected.@directededges/specs-from-figma^0.27.0 — Implements image detection: image fills on containers emitbackgroundImageentries into theimagesregistry instead of being silently dropped, image-source props re-type to image props, and a designated image component forwards images into the instances that render them. Text elements now carrytextOverflowandmaxLines, and rotated elements no longer lose sub-degree precision in CLI output (restoring parity with the plugin).
[0.24.0] - 2026-07-04
specs transform gains two new transformers — react and stories — that scaffold a working React component and a matching Storybook page directly from the spec, plus a --components filter to scope a run to specific components. The contract and css transformers pick up complementary additions (slot visibility rules, structural CSS fixes) to support the new component scaffolding. Generated filenames are now prefixed with the component name for clarity outside the folder tree — a breaking change for any tooling that hardcodes the old unprefixed filenames.
Added
reacttransformer — Emits a working React component (generated/react/{Component}.scaffold.tsx) with BEM markup from the merged layout tree,data-*variant attributes, ARIA state attributes, and slot/element rendering gated on the visibility rules fromcontract. Also seeds a one-time authored copy intosrc/react/{Component}.tsxplus empty{Component}.extensions.cssand{Component}.proposed.cssfiles — created once, never overwritten on subsequent runs. Subcomponents get their own scaffold and seeded authored files under their own subfolder. Instance-typed elements render as a placeholder comment (not yet implemented). Requiresvariants.yaml; components without it are skipped with a warning.storiestransformer — Emits a Storybook CSF page (generated/react/{Component}.stories.tsx) with aDefaultstory plus one story per variant configuration expressible through props. Imports the authored component seeded byreact, not the regenerated scaffold, so Storybook reflects implementation changes. Meta args combine the contract’sDefaultswith example values for string/slot props. Variants driven purely by browser-fired states (:hover,:active) are skipped, since they have no corresponding prop. Also requiresvariants.yaml. Subcomponents get their own stories file, importing their own authored component.contracttransformer emitsSlotsandSlotRules— Whenvariants.yamlis present, the contract now also emits a{Component}Slotsinterface and a{Component}SlotRulesconst describing which anatomy elements are content-injection points and when each renders (always,whenTrue,whenNotNull,whenValue). Consumed by thereacttransformer to gate element rendering. Slots come from anatomyslotelements (typedunknown) and boundtextelements (typedstring); a slot is required only when its rule isalways.transform --components <keys...>— Scopes aspecs transformrun to specific component folders instead of every component discovered in the output directory. Unknown keys log a warning and are skipped.
Changed
csstransformer now handles structurally-absent elements and stacking/containing-block fixes — Elements present in some variant layouts but absent from the default layout are now hidden at the base (display: none) and un-hidden only under the variant selectors that include them. Elements withposition: ABSOLUTEnow get their layout parent promoted toposition: relative(establishing a containing block), and that parent’s non-absolute siblings also getposition: relativeso DOM paint order matches Figma’s layer order instead of the absolute element covering static siblings. Both derived automatically from comparing default vs. variant layouts — no configuration needed.- Generated transform output filenames are now prefixed with the component name —
contract.ts→{Component}.contract.ts,styles.css→{Component}.styles.css, and the newreact/storiesoutput follow the same convention ({Component}.scaffold.tsx,{Component}.stories.tsx). Subcomponent output is prefixed with just the subcomponent’s own name (e.g.Group.contract.ts), not the parent’s, since the subfolder already disambiguates it. This is a breaking rename for anything that reads the previous unprefixed filenames directly. - Clearer
transformprerequisite guidance — The “no specs directory” and “no component directories” error messages no longer suggest--use-subfolders, which has no effect once--split-concernsis set. The correct prerequisite isspecs generate --split-components --split-concerns.
Dependency updates
@directededges/specs-from-figma^0.26.0 — Fixes a casing bug where variantconfigurationvalues, instancepropConfigurations, andinvalidVariantCombinationswere being reformatted (e.g.Error→error) while enum values and defaults were not, causing per-variant style overrides and invalid-combination checks to silently fail to match. Also fixes default slot content being pruned away when the slot itself is hidden by a conditional visibility prop — generated specs no longer lose default content for these slots.
[0.23.0] - 2026-07-01
Specs generated by the CLI now capture dashed stroke styling and record where each subcomponent lives back in the source Figma file — both come from upstream engine and schema updates, with no CLI-side changes required to pick them up.
Changed
- Generated specs now include dashed stroke data — Any element with a dashed (not solid) stroke now emits
strokeDashPatternin its styles, typed as{ dash, gap }. Previously dashed strokes fell through as unrecognized styling and were dropped from output. - Subcomponent entries now include their Figma source location — Each entry under
subcomponentsin generated specs now carries asourcefield (pageId,nodeId,nodeType) pointing back to the originating Figma node.
Dependency updates
@directededges/specs-schemabumped to^0.27.0— Adds theStrokeDashPatterntype backing dashed stroke output andSubcomponentSourcebacking the new subcomponentsourcefield.@directededges/specs-from-figmabumped to^0.25.0— Implements dashed stroke extraction (ADR-059) and subcomponent source population (ADR-060). Also stops reformatting VARIANT enum values and defaults, so generated specs now match Figma’s raw variant option strings exactly.
[0.22.0] - 2026-06-22
Label and icon components can now produce cleaner specs when collapsePrimitiveWrapper is enabled — a plain frame wrapping a lone text or glyph element is stripped out and the primitive becomes the spec root. This release also fixes specs fetch crashing on large Figma files and adds clearer diagnostic output for 403 access errors.
Added
-
collapsePrimitiveWrappersupport wired into the CLI — The config loader defaultsConfig.processing.collapsePrimitiveWrappertofalsewhen absent, and theinittemplate includes it as a commented-out option with a description of its behavior. -
Improved 403 error guidance in
fetch— The access-denied error message now includes two additional diagnostic bullets: one for SAML/SSO-enforced orgs (personal access tokens are blocked; use OAuth or ask your admin to allow PATs — links tohttps://www.figma.com/developers/api#oauth2), and one clarifying that a 403 means the file exists but the account lacks access (e.g. file is in personal drafts or a restricted team). Runningspecs fetch --verbosenow also prints the resolved file key in the error output.
Fixed
fetchstreams large Figma files to disk instead of buffering in memory — Previously,specs fetchread the entire Figma API response into a JavaScript string before writing it. Files large enough to exceed Node’s ~512 MB string limit crashed withCannot create a string longer than 0x1fffffe8 characters. The response body is now streamed directly to a temporary file and renamed into place only on success, so arbitrarily large files are handled without memory pressure and a failed mid-download leaves no partial file behind.
Dependency updates
@directededges/specs-schemabumped to^0.26.0— AddsConfig.processing.collapsePrimitiveWrapper(boolean, defaultfalse), enabling the new primitive wrapper collapse feature for label and icon components.@directededges/specs-from-figmabumped to^0.24.0— Implements wrapper collapse: strips plain container frames around a single text or glyph child and promotes the leaf to spec root. All-or-nothing per component — every variant must qualify.
[0.21.0] - 2026-06-15
Two bugs in the css transformer and analyze command are fixed. The css transformer now correctly emits selectors for enum-valued state variants (previously silently skipped when no explicit value was set). The analyze command now respects format.output for the props and styling analyzers instead of hardcoding file formats. Upstream: specs-schema v0.25.0 renames SlotProp.minItems/maxItems to minChildren/maxChildren and specs-from-figma v0.23.0 reads slot constraints natively from Figma’s slotSettings API.
Fixed
-
CSS transform now emits styling for enum-valued state variants — When a state in
config.processing.statesmaps a prop without an explicitvalue(e.g.selected: { prop: selected }), variants whose enum value matches the concept name (case-insensitively, likeSelected=Selected) now produce CSS selectors. Previously these variants were silently skipped, causing selected-state styling to be missing fromstyles.css. -
analyzecommand respectsformat.outputfor props and styling (#151) —propsandstylinganalyzers now write.jsonor.yamloutput files (and use the matching serializer) based onconfig.format.output. Previously both were hardcoded:propsalways wrote YAML,stylingalways wrote JSON.
Dependency updates
@directededges/specs-schemabumped to^0.25.0—SlotProp.minItems/maxItemsrenamed tominChildren/maxChildren(aligns with Figma’s nativeslotSettingsAPI).Metadata.generator.versioncorrected fromnumbertostring.@directededges/specs-from-figmabumped to^0.23.0— Slot constraints (minChildren,maxChildren,anyOf) now read from Figma’s nativeslotSettingswhen present; libraries using Figma’s built-in slot configuration no longer need code-only props for constraints. Legacy{slot} minItems/maxItemsprop names still accepted.
[0.20.0] - 2026-06-07
Introduces the specs analyze command with two analyzers: props (cross-library prop governance aggregate) and styling (token usage dictionary, moved from transforms). Extends the css and contract transformers with subcomponent support, a configurable CSS rule pipeline, and the border-shift-inset-shadow rule. Refines css output: boolean props emit presence selectors and disabled guards wrap hover/active.
Added
-
specs analyzecommand — New command for on-demand analysis passes over component specs. Accepts one or more analyzer names as positional arguments (specs analyze props,specs analyze props styling). Output goes to_analysis/by default; override with--analysis <path>. No config key — analyzers are run on demand, not as part of the regular transform pipeline. -
propsanalyzer — Reads every component’sapi.yamland produces_analysis/props.yaml: a cross-library aggregate with six sections — summary counts, prop name frequency, enum discordance (same prop name, divergent value sets), boolean naming patterns, API surface per component, and a slot inventory. Designed as structured input for LLM-assisted API governance analysis. -
stylinganalyzer — Moved from the transform registry to the analyzer registry. Writes_analysis/styling.byComponent.jsonand_analysis/styling.byToken.json(previously written to_dictionary/). Invoke withspecs analyze styling. -
csstransformer: subcomponent support — For each entry invariants.yaml’ssubcomponentsmap, emits astyles.cssinside a dedicated subfolder named after the subcomponent key (e.g.dsActionList/group/styles.css). BEM selectors are scoped to the subcomponent’s own kebab-case class name (e.g..group,.group__text). Subcomponent subdirs are created automatically. -
contracttransformer: subcomponent support — For each entry inapi.yaml’ssubcomponentsmap, emits acontract.tsinside a dedicated subfolder named after the subcomponent key (e.g.dsActionList/group/contract.ts). Interface and enum type names are prefixed with both the component and subcomponent name (e.g.DsActionListGroupProps). Subcomponent subdirs are created automatically. -
csstransformer: configurable rule pipeline — Thecsstransformer now accepts aruleslist under its transformer options. Rules run as pre-passes on the structured variants data before CSS emission, enabling semantic rewrites that require cross-variant context. Configure per-component inspecs.config.yaml:transformers:- name: cssrules:- border-shift-inset-shadow -
csstransformer:border-shift-inset-shadowrule — Eliminates layout shift from border-width changes across variants. Detects elements whosestrokeWeightorstrokeschange in any variant (INSIDE strokes only), rewrites the default block to reserve space with a transparent border (border: Npx solid transparent), and rewrites variant stroke changes tobox-shadow: inset 0 0 0 Npx <color>. OUTSIDE and CENTER strokes are unaffected (they useoutline, which is already layout-safe).
Changed
-
csstransformer: presence selectors for boolean props — Data attribute selectors for variant props whose value is a JS booleantruenow emit[data-x](presence) instead of[data-x="true"](value). String-valued props (e.g.checked: "checked",checked: "indeterminate") continue to emit value selectors. Matches the TSX idiomdata-x={value || undefined}. -
csstransformer::not(:disabled)guard on hover/active — When thedisabledconcept is configured inprocessing.states,:hoverand:activeselectors (including compound variants like[data-checked="checked"]:hover) are automatically guarded with:not(:disabled):not([aria-disabled="true"])to prevent interaction styles from applying to disabled elements.
Removed
Dependency updates
- No upstream dependency changes since 0.19.0. Continues to reference
@directededges/specs-schema ^0.24.0and@directededges/specs-from-figma ^0.22.0.
[0.19.0] - 2026-06-05
Introduces the specs transform command and three transformers: contract (typed Props interface + Defaults), css (BEM stylesheet with token vars and concept-driven state selectors via processing.states), and styling (token usage dictionary). Specs generated by the CLI now correctly identify themselves in metadata.generator instead of reporting the Figma plugin. Upstream: specs-from-figma v0.22.0 adds RestFoundations.generator for caller identity and fixes INSTANCE width/height variable bindings; specs-schema v0.24.0 adds Config.transformers, workspace.schema.json, and Config.processing.states.
Added
-
specs transformcommand — Discovers component subfolders under the output directory (each must containapi.yaml) and runs one or more named transformers against every component. Transformer names resolve in priority order: positional arguments →config.transformers→ CLI default (contract). Accepts-o/--output,--config, and--verboseoptions. Closes DirectedEdges/specs#137 -
contracttransformer — Emitscontract.tsper component: a typedPropsinterface (enum union types, nullable types, slot props excluded) and aDefaultsconst (satisfies Props) for every prop with a declared default. Default transformer when none are specified. Closes DirectedEdges/specs#137 -
csstransformer — Emitsstyles.cssper component fromvariants.yaml. Default element styles become root/BEM-child selectors; variant configurations become[data-propName="value"]attribute selectors (camelCase prop names kebabized); multi-prop configurations produce compound selectors. Token references resolve tovar(--)based onconfig.format.tokens: path-derived kebab forTOKEN/TOKEN_NAME/FIGMA_NAME; verbatim forFIGMA_SYNTAX_WEBvars that already start with--;$cssVarfield or path derivation forCUSTOM. Closes DirectedEdges/specs#139 -
stylingtransformer — Emitsstyling.jsonper component: token/style usage grouped by category (variables,colorStyles,textStyles,effectStyles), each row recordingname,appliedAs, andappliedTo(anatomy element → occurrence count). After all components run,finalize()writes two aggregate files to_dictionary/:styling.byComponent.json(all components combined) andstyling.byToken.json(token-first index: token name → components/elements using it). Subcomponents appear as dot-separated keys. Closes DirectedEdges/specs#138 -
config.transformersblock ininitconfig template —specs initnow generates a commented-outtransformers:block showing all three transformers, ready to uncomment. -
processing.statessupport incssandcontracttransformers — Both transformers now readconfig.processing.states, a concept-keyed map that classifies Figma variant props as semantic states. Thecsstransformer emits real CSS pseudo-classes (:hover,:active,:focus-within) and ARIA attribute selectors ([aria-disabled="true"],[aria-invalid="true"]) for classified props instead ofdata-*attribute selectors. Thecontracttransformer omits browser-driven props from generated Props interfaces automatically — no additional config flag required.propandvaluebridge any naming gap between library conventions (e.g.isDisabled,pressed) and canonical concept names. Absence ofprocessing.statesis backward-compatible: all props continue to emit asdata-*selectors and appear in contracts unchanged.
Fixed
metadata.generatornow correctly identifies the CLI — Previously, specs generated by the CLI reported the Figma plugin name, version, and URL inmetadata.generator. The CLI now passes{ name: '@directededges/specs-cli', version, url }viaRestFoundations.generator, and the version is stamped from__SPECS_CLI_VERSION__injected at build time. Closes DirectedEdges/specs#133
Changed
Removed
Dependency updates
@directededges/specs-schema→^0.24.0— addsConfig.transformers(flat array, replacesconfig.transform.transformers),workspace.schema.jsonfor IDE validation ofspecs.config.yaml, andConfig.processing.states(Record<string, VariantStateEntry>) for concept-keyed variant state classification.@directededges/specs-from-figma→^0.22.0—RestFoundations.generatorlets the CLI supply its own identity tometadata.generator; fixesINSTANCEwidth/height variable bindings; colorcomponentsvalues rounded to 4dp.
[0.18.0] - 2026-05-29
Hardens license-key validation to hard-fail on transient errors and improves actionable error messages for stale or inaccessible Figma file keys.
Fixed
generateno longer silently produces free-tier output when a license key can’t be validated (#119) — if a key was supplied (via-l,SPECS_LICENSE_KEY, orANOVA_LICENSE_KEY) but the license check could not be completed — a transient proxy/network failure or a rate-limit (e.g. the upstream validator returning 429) — the run previously fell back to FREE and wrote free-tier specs under a valid paid key, with only an easily-missed status line.generatenow hard-fails on these transient states (network-error,error, and a futurerate-limited) with an actionable message (“retry in a few seconds, or remove the key for free-tier output”) and a retryable exit code (NETWORK_ERROR, orRATE_LIMITonce the validator distinguishes it). Definitive key rejections (invalid/removed/expired) still fall back to free-tier output as before.- Actionable
fetch/generateerror messages for stale or inaccessible Figma file keys (#125) — afetchthat hit a 404 previously surfaced onlyHTTP 404 while fetching <alias>.<kind>, with no hint that the cause was the file key pinned in config. The 404 case now explains that the configured key is likely stale or out of reach (file moved/deleted/recreated, or the token can’t open it) and points atsources.<alias>.keyin the config file. Auth failures are split: 401 reports a missing/invalid/expiredFIGMA_TOKENand where to set it (.env), while 403 reports valid-but-no-access and points at the same config key. Ingenerate, a missing source.file.jsonnow tips the user to runspecs fetchor checksources.<alias>.key.
Dependency updates
@directededges/specs-from-figma^0.20.0 → ^0.21.0 — SLOT elements now evaluate the same container-surface styles as frames: auto-layout config, strokes, padding, and corner-smoothing. Slot styling in generated specs is more complete.@directededges/specs-schema^0.22.0 — no version change.
[0.17.0] - 2026-05-23
Surfaces the new composition/examples model to CLI users: config loading now accepts include.defaultSlotContent and the processing.instanceExamples block, so generated specs can include slot-content examples and pre-configured instance examples (both Pro-gated). --split-concerns gains a third examples.yaml output for these examples, and a fix ensures they’re no longer silently dropped when splitting concerns.
Added
- Examples config (ADR-050) —
ConfigLoadernow accepts the newinclude.defaultSlotContentflag (added to the include allowlist) and validatesprocessing.instanceExamples(scope∈PAGE/FILEwithPAGEfallback;matchrequired;exclude/parentNamesarray-checked), passing them through to the engine.instanceExamplesoutput is driven by the presence ofprocessing.instanceExamples(noinclude.instanceExamplesflag), mirroringsubcomponents. Both example features are Pro-gated — silently omitted on the free tier. SurfacesslotContentExamples/instanceExamplesgeneration to CLI users.
Changed
--split-concernsnow emits a thirdexamples.yaml— slot-content and instance examples are written toexamples.yaml(per-concern mode) or<component>/examples.yaml(combined mode), alongsideapi.yamlandvariants.yaml. The file is emitted only when at least one component has examples, and components without examples are omitted from it.
Fixed
--split-concernsno longer drops examples —slotContentExamplesandinstanceExamples(component- and subcomponent-level) were assigned to neither theapinorvariantsconcern, so--split-concernssilently discarded them, leaving the$slotContentreferences indefault/variantsdangling. They are now routed intoexamples.yaml.
Removed
Dependency updates
@directededges/specs-schema^0.21.0 → ^0.22.0 — adds the composition and slot-content model (ADR-042, 046–052):Composition,SlotContent, and the universalSlotContentRef({ $slotContent }) pointer, plusComponent.slotContentExamplesandComponent.instanceExamples. Specs can now carry named slot-content examples and documented instance examples, andConfiggainsprocessing.instanceExamples(example detection) andinclude.defaultSlotContent(output gate).@directededges/specs-from-figma^0.19.0 → ^0.20.0 — detects and emits slot-content examples (de-duplicated across variants and slots) and instance examples (pre-configured usages of a component), both Pro-gated. REST page/file-scoped discovery now correctly finds candidates underCANVASnodes, and a plugin-only hashing bug that collapsed all slot fills into a single example is fixed.
[0.16.0] - 2026-05-22
Adds the platform code-syntax token profiles (FIGMA_SYNTAX_WEB/IOS/ANDROID) to config loading and templates, so specs can emit each Figma variable’s per-platform code syntax. Picks up upstream transformer improvements: conditional-visibility boolean prop exposure, detection of subcomponents nested inside sections/frames, and a fix for SLOT properties that previously leaked raw GUID objects into output.
Added
- Platform code-syntax token profiles (ADR-051, DirectedEdges/specs#103) —
format.tokensnow acceptsFIGMA_SYNTAX_WEB,FIGMA_SYNTAX_IOS, andFIGMA_SYNTAX_ANDROIDin config loading and templates, surfacing the platform code-syntax profiles to CLI users. The transformer (specs-from-figma) emits each variable’s FigmacodeSyntaxfor the selected platform, falling back to the standard token output when a platform has no code syntax defined.
Dependency updates
@directededges/specs-schema^0.20.0 → ^0.21.0 — adds theFIGMA_SYNTAX_WEB/IOS/ANDROIDformat.tokensprofiles.@directededges/specs-from-figma^0.18.0 → ^0.19.0 — serializes the new platform code-syntax profiles from FigmacodeSyntax; specs now expose a boolean-prop reference for conditional visibility bindings; subcomponents nested inside sections/frames are now detected undersubcomponents.scope: PAGE; SLOT properties no longer emit raw GUID objects into output.
[0.15.1] - 2026-05-20
Patch release fixing the scan → generate round-trip. generate now accepts the v2 (markdown-table) manifests that scan has emitted since 0.15.0, restoring the documented workflow.
Fixed
generatenow accepts v2 (table) manifests —generate’s source auto-detection only recognized the legacy v1 checkbox-list format (- [), so any manifest produced byspecs scanin 0.15.0 failed withError: Unrecognized source format, breaking the documentedscan && generateround-trip.generatenow detects v2 manifests via the**Scan format version:**header and dispatches toManifestParserV2, while continuing to support v1 manifests and raw JSON files. (#101)- Escaped pipes in component names round-trip —
scanescapes|as\|in manifest table cells;ManifestParserV2now unescapes them so names likeToggle | On/Offparse back to their literal form.
Dependency updates
- No upstream dependency changes since 0.15.0. Continues to reference
@directededges/specs-schema ^0.20.0and@directededges/specs-from-figma ^0.18.0.
[0.15.0] - 2026-05-15
scan now drives curation from Figma’s Ready for Dev signal and merges intelligently with prior manifests, preserving manual edits except where Figma’s devStatus has changed. Introduces a new v2 manifest format with automatic migration from v1.
Added
- Ready-for-Dev curation in
scan— Components withdevStatus: READY_FOR_DEVare now checked by default. Libraries with no devStatus signal anywhere fall back to the legacy heuristic. - Manifest merge on rescan — Re-running
scanpreserves manual checkbox edits unlessdevStatuschanged for a row (Figma wins on flips).--keep-checkslocks manual edits regardless of devStatus changes;--reset-checksignores the prior manifest entirely. - Glyphs section in scan manifest — When
config.processing.glyphNamePatternis set, top-level components matching the pattern are routed to a read-only## Glyphssection after## Components. Glyph rows have no checkboxes and are excluded fromspecs generate. The section is omitted when no matches are found. Pattern matching reuses the same{i}-placeholder semantics as the engine’s glyph detection. - v2 manifest format — New markdown-table format with a
Dev Statuscolumn,**Scan format version:**header, and**File last modified:**metadata. Legacy v1 (checkbox-list) manifests are detected and migrated automatically.
Changed
- Docs updated —
cli/commands/scan.md,cli/getting-started.md, andcli/workflows.mdreflect the new curation flow and manifest format.
Dependency updates
- No upstream dependency changes since 0.14.0. Continues to reference
@directededges/specs-schema ^0.20.0and@directededges/specs-from-figma ^0.18.0.
[0.14.0] - 2026-05-15
Dependency-only release that picks up specs-from-figma 0.18.0. No CLI source changes.
Dependency updates
- @directededges/specs-from-figma 0.18.0 — Restores ~170 invalid variants previously dropped by the empty-variant filter, accounting for the bulk of test-round 0009 parity diffs. Fixes a
figma.mixedSymbol crash on text nodes with mixed text styles.TEXTelements now emit explicit size styles (width,height,minWidth,minHeight,maxWidth,maxHeight);GLYPHelements now emit shadow/blur effects and aspect-ratio constraints alongside their fill color.
[0.13.1] - 2026-05-08
Patch fix for --split-concerns output shape.
Fixed
propsdefaults to{}instead of[]when a component has no props (#84) —splitComponentByConcernandextractApiFromSubcomponentswere filling missingpropswith an empty array, producing output that violated the schema (Propsis an object). Components with no props (e.g. a divider) now emitprops: {}. TheComponentApiDataandSubcomponentApiDatainterfaces are corrected to typepropsasRecord<string, any>.
[0.13.0] - 2026-05-06
Adds configurable color output format (config.format.color) with nine options from hex strings to structured DTCG Color objects. Fixes EISDIR crash when outputDirectory targets a directory, and corrects config template URLs.
Added
config.format.color— validates and normalizes the newColorFormatoption (HEX,HEXA,RGB,RGBA,HSLA,HSB,OKLCH,OKLAB,OBJECT); defaults toHEX. Config template updated with inline docs. (ADR 043)
Fixed
- EISDIR when outputDirectory is a directory in single-file mode (#34) — When
outputDirectorypointed to an existing directory (e.g. from a prior--split-componentsrun),specs generatewithout split flags crashed with EISDIR. Now appendslibrary.{format}as the default filename. - Config template URLs now point to the doc site (
directededges.github.io/specs/config/...) instead of 404ing GitHub raw paths (#43)
Dependency updates
- @directededges/specs-schema 0.20.0 — New
Config.format.coloroption typed asColorFormat.ColorValuerenamed toColorObject.ColorStyle,Shadow.color, andGradientStop.colorwidened to accept formatted color strings alongside structured objects and token references. - @directededges/specs-from-figma 0.17.0 — Ten bug fixes: INSTANCE_SWAP prop resolution now correctly resolves names, strips glyph patterns, and formats keys;
visible: trueno longer leaks into default variant output; auto-layout fallback elements emit correct width/height; glyph fill colors on nested instances now resolve to token references instead of raw hex. RAW colors emit structuredColorValueobjects per ADR-009.
[0.12.2] - 2026-04-28
Dependency update: picks up constraint-based layout positioning from specs-schema 0.19.0 and specs-from-figma 0.16.0. Also fixes glyph instanceOf leaking into variant output.
Dependency updates
- @directededges/specs-schema 0.19.0 — Positioning properties
x,y, andlayoutPositioningare replaced by constraint-based equivalents:position('AUTO' | 'ABSOLUTE'),top,bottom,start,end,centerHorizontalOffset, andcenterVerticalOffset. Offset values are pixel numbers for MIN/MAX/CENTER/STRETCH constraints and percentage strings (e.g."25%") for SCALE constraints. - @directededges/specs-from-figma 0.16.0 — Specs now emit constraint-based positioning instead of raw pixel coordinates. A new
postEvaluatepipeline step ensures positioning values are computed before variant differencing, so variant diffs correctly capture position changes. Fixes glyph elements incorrectly emittinginstanceOfin variant output, and SCALE constraints now emit both start and end percentages.
[0.12.1] - 2026-04-24
Dependency patch: picks up specs-from-figma 0.15.1 subcomponent indexer propagation fix.
Dependency updates
- @directededges/specs-from-figma 0.15.1 — Fixed subcomponent indexer propagation:
getMainComponentAsync()now correctly sets the indexer on parent COMPONENT_SET nodes, eliminating “[RestInstanceNode] No indexer set” warnings during subcomponent discovery.
[0.12.0] - 2026-04-24
Dependency update: picks up specs-schema 0.18.0 layout property renames and specs-from-figma 0.15.0 bi-axial spacing model.
Dependency updates
- @directededges/specs-schema 0.18.0 — Layout alignment properties renamed from Figma axis terminology to platform-neutral names:
primaryAxisAlignItems→mainAxisAlignment,counterAxisAlignItems→crossAxisAlignment,counterAxisAlignContent→wrapAlignment,layoutWrap→wrap.counterAxisSpacingconsolidated into a bi-axialitemSpacingmodel.layoutModenarrowed from generic style to strict'NONE' | 'HORIZONTAL' | 'VERTICAL'enum. - @directededges/specs-from-figma 0.15.0 — Wrap-enabled auto-layout frames now emit bi-axial
itemSpacingwithhorizontal/verticalfields instead of separateitemSpacing/counterAxisSpacingkeys. Alignment values remapped from Figma terminology (MIN→START,MAX→END).wrapAlignmentis only emitted whenwrap: true, stripped as dead otherwise.
[0.11.0] - 2026-04-16
Fix: config file split options (splitComponents, splitConcerns, useSubfolders) were ignored because Commander’s explicit false default shadowed the config values. Also makes generate and scan runnable with zero arguments using a default file path derived from config.
Added
generatenow runs without arguments. When no source argument is provided,generateresolves the manifest path to{dataDirectory}/{alias}.manifest.mdusing the source alias fromspecs.config.yaml(preferslibrary, otherwise the first source alias whosedataincludesfile). Runningspecs generatewith no flags now uses this default manifest plusoutputDirectoryfrom config — matching the zero-arg ergonomics ofscanandfetch. If no source alias hasdata: [file], an error suggests runningspecs scanfirst.scannow runs without arguments. The<file>positional argument is now optional. With one source configured inspecs.config.yaml(whosedataincludesfile),specs scanauto-resolves{dataDirectory}/{alias}.file.json. An explicit file path still works and takes precedence.scan --source <alias>flag. When two or more sources inspecs.config.yamlhavedata: [file, ...],scanfails loudly with the list of available aliases and requires--source <alias>to disambiguate. Passing both[file]and--sourceis rejected as a conflict. This stricter convention (vs.generate’s preference-based fallback) prevents silent behavior changes when a second source is added to config.
Fixed
- Config file split options now take effect. Commander boolean flags (
--split-components,--split-concerns,--use-subfolders) previously defaulted tofalse, which caused the nullish coalescing chain (options.flag ?? config.value ?? false) to short-circuit before consultingspecs.config.yaml. Removed the Commander defaults so the flags areundefinedwhen absent, allowing config values to flow through.
Docs
- Updated
docs/cli/(index, getting-started, claude-onboarding, examples, commands/generate, commands/scan, commands/index, commands/apply-custom-tokens) to document and demonstrate the zero-argspecs generateworkflow. Existingspecs generate components.mdexamples are preserved where they document explicit-argument option behavior.
Dependency updates
- No upstream package changes. Continues to target
@directededges/specs-schema ^0.17.0and@directededges/specs-from-figma ^0.14.2.
[0.10.2] - 2026-04-14
Dependency patch: picks up the DTCG-compliant token path separator fix from specs-from-figma.
Changed
- Bump
@directededges/specs-from-figmato ^0.14.2 — Picks up the fix for$tokenpath separators violating DTCG character restrictions.
Dependency updates
- @directededges/specs-from-figma 0.14.2 — Token references (
$tokenvalues) in TOKEN, TOKEN_NAME, TOKEN_FIGMA_EXTENSIONS, and CUSTOM profiles now use/as the path separator instead of.(period). The DTCG spec (§5.1.1) prohibits.in token and group names, and this aligns all profiles with the existing FIGMA_NAME behavior.
[0.10.1] - 2026-04-14
Dependency patch: picks up a fix from specs-from-figma for empty variant filtering in LAYERED mode.
Changed
- Bump
@directededges/specs-from-figmato ^0.14.1 — Picks up the fix for empty variant filtering in LAYERED mode, where variants with configuration but no element or layout differences were not being excluded whenemptyVariants: false.
Dependency updates
- @directededges/specs-from-figma 0.14.1 — Fixes the
emptyVariants: falsefilter in LAYERED mode. Previously, variants that had a configuration object but noelementsarray (e.g., variants with only layout differences removed) were incorrectly retained in output. The filter now correctly excludes variants that lack both element and layout differences from their layered baseline.
[0.10.0] - 2026-04-13
Renames audit → scan, sourceDirectory → dataDirectory, and the config file from .specs.config.yaml to specs.config.yaml. Adds a --data-dir flag and makes config format values case-insensitive. Config types now use ResolvedConfig for full default guarantees.
Added
--data-dirflag onfetch,scan, andgenerate— Override the configdataDirectoryper run from the command line. Onfetch,--outDiris retained as a deprecated alias.
Changed
- Config file renamed from
.specs.config.yamltospecs.config.yaml— The config file is no longer a hidden dotfile. Specs config is actively edited (Figma file keys, output directories, format options), so it belongs visible in the filesystem — consistent withvite.config.ts,tailwind.config.js, and other tools where config is a first-class part of the workflow. The JSON variant is also renamed from.specs.config.jsontospecs.config.json. The global config path (~/.specs/config.yaml) is unchanged since home-directory configs are conventionally hidden. auditcommand renamed toscan— Better describes the command’s purpose: scan a file and produce a manifest for component curation.specs auditstill works as a deprecated alias with a stderr warning.sourceDirectoryconfig field renamed todataDirectory— Aligns the field name with its default value (./data) and removes confusion with thesourcesconfig block.sourceDirectorystill works as a deprecated alias with a stderr warning.generatemanifest mode falls back toconfig.outputDirectory—--outputis no longer required whenoutputDirectoryis set in the config file, fixing the main flag/config inconsistency reported by users.- Config types now distinguish partial input from resolved output —
CLIConfig.configandConfigLoader.mergeConfiguseResolvedConfig(all defaults guaranteed) instead ofConfig(which now permits omitted fields) inittemplate annotates license-gated configs — The YAML config template generated byspecs initnow notes thatformat.tokensandinclude.invalidCombinationsrequire a license key to produce output.
Fixed
- Config format values are now case-insensitive — Lowercase values like
yaml,camel, orlayoutin config files were silently rejected by validation and reset to defaults (e.g.,output: yamlalways produced JSON). Allformat.*values are now normalized to uppercase before validation, matching how the CLI flag already works. - YAML options type annotation includes
CreateNodeOptions—FileWriter.YAML_OPTIONSwas typed withoutParseOptions & CreateNodeOptions, causing a compile error foraliasDuplicateObjects - Config merge test references legacy
subcomponentNamePattern— Updated to usesubcomponents(the current property shape) withtoEqualinstead oftoBe - Vitest mock type mismatch in
GenerateCommandtests — ReplacedReturnType<typeof vi.spyOn>withMockInstancefor vitest 3.x compatibility
Deprecated
sourceDirectoryconfig field — UsedataDirectoryinstead. Will be removed in a future release.specs auditcommand — Usespecs scaninstead. Will be removed in a future release.fetch --outDirflag — Use--data-dirinstead. Will be removed in a future release.
Dependency updates
- @directededges/specs-schema v0.17.0 — Introduces
ResolvedConfig(fully-resolved config with all defaults guaranteed) alongside the now-more-permissiveConfig(optional fields with defaults). Removes unusedVariant.nameandVariant.baselinefields from output. - @directededges/specs-from-figma v0.14.0 — Fixes
format.keysnot applying when config values are lowercase and not formattinginvalidVariantCombinationsdimension names. Internal types updated toResolvedConfig.
[0.9.0] - 2026-04-09
Adds complete config template generation, better Figma rate-limit error messages, and fixes for YAML-only-comments config crashes, output format not being respected, and stale command references.
Changed
initconfig template includes all settings — The YAML template generated byspecs initnow includes everyConfigfield andoutputsection option:codeOnlyPropsPattern,slotConstraints,inferNumberProps,emptyVariants,splitComponents,splitConcerns, anduseSubfolders. All new entries are commented out with their defaults. (#16)fetchrate-limit errors surface Figma response headers — When Figma returns HTTP 429, the error message now includes theRetry-Afterduration (formatted as seconds, minutes, hours, or days), seat tier (Viewer/CollaborDev/Full), plan tier, and a link to Figma’s rate limit documentation.
Fixed
- Config validation crashes on YAML sections with only comments — When
specs.config.yamlcontains sections likeinclude:with only commented-out fields, the YAML parser producesnullinstead of an empty object. The config validator’sdeepMergeandvalidateAndCorrectConfigmethods now guard against null values, preventingTypeError: Cannot convert undefined or null to objectcrashes. Null values from YAML parsing no longer overwrite defaults. (spec-demo) - File output ignores
config.format.output— Thegeneratecommand always wrote YAML files regardless of theformat.outputconfig setting. TheOutputFormattype,FileManifestextensions, and all writers now respect the configured format (JSON or YAML). TheDEFAULT_OUTPUT_CONFIG.defaultFormatis aligned with specs-schema’sDEFAULT_CONFIG.format.output(JSON). (#14) auditandinitterminal output references defunctbatchcommand — Post-run help text inauditandinittold users to runspecs batch …. Thebatchcommand was consolidated intogenerate; all references now point tospecs generate.audit --variablesflag not writing to manifest header — The-v/--variablesflag was parsed but never passed to the manifest generator. The**Variables:**metadata line is now written when the flag is provided, andManifestParserextracts it back into metadata. (#18)
Dependency updates
- @directededges/specs-from-figma v0.13.0 — Slot
anyOfvalues now respect the configured key format instead of using raw Figma component names. Width/height fallback warnings for rotated nodes are more informative when geometry data is absent.
[0.8.0] - 2026-04-07
Adds fetch UX improvements (animated spinner, elapsed time, --no-geometry), renames the config key from model to config, and fixes style reconciliation and large-file fetch crashes.
Added
fetchanimated spinner with elapsed time — Thefetchcommand now displays an animated braille spinner with a live elapsed-time counter while downloading each payload. The final success line includes the total duration per request (e.g.,✓ Downloaded: kds file (14s)). Non-TTY environments fall back to a static log line.fetch --no-geometryflag — Omits?geometry=pathsfrom Figma file requests, reducing payload size by roughly half. Vector path data (fillGeometry,strokeGeometry,size,relativeTransform) is excluded; width/height fall back toabsoluteBoundingBoxduring processing.auditdefault output path —-ois now optional. When omitted, the manifest writes to{sourceDirectory}/{alias}.manifest.mdusing the config’ssourceDirectoryand the input filename. Added--configflag for config file resolution.
Changed
- Config key rename:
model→config— The YAML keymodel:and internal propertyCLIConfig.modelare renamed toconfig:/CLIConfig.configto align with the upstreamConfigtype from @directededges/specs-schema. Updates source, tests, and all documentation.
Refactored
- Remove dead styles payload shapes from
loadFoundations— Removed two unused code paths: theall_stylessimplified format and thestylesobject-map format. Only the Figma REST API format (meta.styles) is retained. Updated JSDoc to describe the two actual data sources (file seed + styles endpoint). - Comment out false-defaulting include fields in config template —
invalidVariantsandinvalidCombinationsnow have schema-level defaults and no longer need explicit values in the generated config template.
Fixed
- Style
$customreconciliation inloadFoundations— Published Figma styles referenced by components use file-local IDs (e.g.,19108:2530) that differ from the styles endpoint’snode_id(e.g.,5115:6703). The sharedkeyhash now bridges these two sources, ensuring$customtoken objects from the styles endpoint are available when resolving style references during spec generation. generate -cuses component ID as output key — In file mode (generate <file> -c <id>), the component’s Figma node ID was used as the output key instead of its name. Now resolves the display name from the file’scomponentSetsorcomponentsmetadata.fetchwrites raw response directly to disk — Previously, fetch parsed the Figma response into JSON and re-serialized it with pretty-printing before writing. This causedInvalid string lengthcrashes on large files (400MB+). Now writes the raw response body straight to disk, eliminating unnecessary memory overhead.
Dependency updates
- @directededges/specs-from-figma v0.12.0 — When the REST API response lacks geometry data (e.g., when using
--no-geometry), width and height fall back toabsoluteBoundingBox. A console warning now surfaces when this fallback is used on a rotated node, where bounding box dimensions may be inflated.
[0.7.0] - 2026-04-05
Rebrands from anova-cli to specs-cli and publishes to npmjs.org. Updates all dependencies to published npm packages. Removes the deprecated variantNames config field per specs-schema v0.16.0.
Changed
- Package rename —
@directededges/anova-cli→@directededges/specs-cli - Config file names —
.anova.config.yaml→specs.config.yaml(and.jsonvariant) - Config search path —
~/.anova/config.yaml→~/.specs/config.yaml - Dependencies — switched from local
file:references to published npm packages:@directededges/specs-schema@^0.16.0,@directededges/specs-from-figma@^0.11.0 - Publishing target — npm registry (was GitHub Packages)
Removed
Config.include.variantNames— removed from config template, validation, and documentation per specs-schema v0.16.0 (ADR 034). AddedemptyVariantsto valid config include keys.
Dependency updates
- @directededges/specs-schema v0.16.0 — removes
variantNames, adds optionalemptyVariants, makesinvalidVariantsandinvalidCombinationsoptional with defaults - @directededges/specs-from-figma v0.11.0 — renames from
anova-transformer, adds pageId resolution, empty variant filtering, stroke align fix, license metadata in output
[0.6.0] - 2026-03-25
Adds a new applyCustomTokens CLI command for injecting custom token objects into fetched foundation data, enabling the CUSTOM token profile in the transformer. Implements anova v0.15.0 schema with restructured subcomponent configuration and corrected typography types, and anova-transformer v0.10.0 with expanded subcomponent discovery and custom token support.
Added
- License key support —
-l, --license <key>flag andANOVA_LICENSE_KEYenvironment variable for Pro features (token references, variable bindings, visibility bindings) - License status display — shows
License: PRO (active)or fallback reason (e.g.,License: FREE (invalid — key not recognized)) when a key is provided - Manifest mode for
generate—generatenow accepts markdown manifests directly, replacing the separatebatchcommand; processes all[x]marked components in a single pass applyCustomTokenscommand — New CLI command that injects$customtoken objects from a JSON mapping file into fetched variables and styles JSON files. Config-aware with-v/-soverride flags. Supports idempotent re-application, status summary, and validation of mapping entries.$custompassthrough inloadFoundations— Variables and styles map entries now preserve$customproperties when loading from JSON files, enabling the transformer to read custom token objects.
Changed
generatecommand unified — accepts both JSON files (with-cfor single component) and markdown manifests (for multiple components); all split flags (--split-components,--split-concerns,--use-subfolders) work in both modes- ManifestParser — fixed regex to correctly parse component entries from manifest markdown
- Documentation — updated all docs to reflect unified
generatecommand, removedbatchreferences, added license setup guide and Free vs. Pro feature comparison
Dependency updates
- @directededges/anova v0.15.0 — Subcomponent configuration moves from a flat
subcomponentNamePatternstring to a structuredsubcomponentsobject supporting multiple match patterns, an exclude list, and a page-level search scope. Subcomponent references in anatomy and element output now use$refpointers. Typography fieldsleadingTrim,fontFamily, andfontStyleare corrected to match actual Figma API values. - @directededges/anova-transformer v0.10.0 — Specs now support page-level subcomponent discovery (scanning beyond the component tree), multiple and excludable match patterns for subcomponent detection, and alphabetically ordered subcomponent output. Subcomponent references in
instanceOffields emit as$refpointers. The new CUSTOM token profile uses$customobjects from variables and styles as style property values. Fixes detection of subcomponents nested as instances and inconsistent slash spacing in Figma component names.
Removed
batchcommand — consolidated intogenerate; useanova generate manifest.mdinstead ofanova batch manifest.md
[0.5.0] - 2026-03-18
Supports code-only props extraction: the transformer now detects a configurable container layer, excludes it from anatomy, and surfaces its children as props with $extensions provenance metadata. Slot constraint code-only props are promoted to SlotProp.minItems, maxItems, and anyOf fields. Also introduces NumberProp inference from text-based code-only props when enabled.
Added
ConfigLoadervalidation for new processing config fields:codeOnlyPropsPattern(string),slotConstraints(boolean), andinferNumberProps(boolean)
Changed
- Compatible with
@directededges/anovav0.14.0 and@directededges/anova-transformerv0.9.0
Fixed
ConfigLoaderwas validatingiconNamePatterninstead ofglyphNamePattern, the field name used inConfig.processingsince v0.3.0. The validator now correctly referencesglyphNamePatternBatchCommandmanifest parser regex now handles component names containing parentheses
[0.4.0] - 2026-03-13
Changed
- Compatible with
@directededges/anovav0.13.0 and@directededges/anova-transformerv0.8.0
[0.3.0] - 2026-03-08
Added
iconNamePatternconfig support for detecting icon content assets
Changed
- Compatible with
@directededges/anovav0.12.0 and@directededges/anova-transformerv0.7.1
[0.2.0] - 2026-03-04
Changed
- Updated for
@directededges/anovav0.11.0 compatibility - Config defect fix
[0.1.0] - 2026-02-10
Added
- Initial CLI and MCP server for design system operations
Component.fromRestApiintegration with anova-transformer