Skip to content

Feature: React and Web Components Transformers - #358

Open
nathanacurtis wants to merge 7 commits into
release/schema-0.31.0+cli-0.28.0from
feat/react-from-specs
Open

Feature: React and Web Components Transformers#358
nathanacurtis wants to merge 7 commits into
release/schema-0.31.0+cli-0.28.0from
feat/react-from-specs

Conversation

@nathanacurtis

@nathanacurtis nathanacurtis commented Aug 27, 2026

Copy link
Copy Markdown
Member

Rebuilds the transform work onto the current release branch. The previous
feat/react-from-specs branched from the 0.28.0/0.25.0 release and missed three
cycles, so its schema-side changes had become regressions — it still carried
clipContent, originalName, and a resurrected Config.ts. Those are discarded;
only the transform payload is carried forward. The webcomponents work is
cherry-picked for the same reason rather than merged.

Added

  • cssvars transformer — emits library-level CSS custom properties, resolved
    from the fetched library JSON in the workspace data directory, so generated
    stylesheets stand on their own.
  • Inline effects and gradients in the css transformer — shadows, blurs, and
    gradient fills declared directly on an element now become real box-shadow,
    filter, and background-image declarations. They were previously dropped.
  • webcomponents and webcomponents-stories transformers — experimental.
    A Lit element and a web-components Storybook page, the same shape react and
    stories produce for React. The output shape may change without a
    breaking-change note.

Changed

  • react and stories are consumed from @directededges/react-from-specs;
    webcomponents and webcomponents-stories from
    @directededges/webcomponents-from-specs. Names, options, and output unchanged.

Adaptations this port required

  • ADR-069styleToCSS.ts and Css.mapping.md used clipContent, which the
    engine no longer emits. Renamed to clipsContent so the overflow rule fires.
  • ADR-071 — the transformer context read config.dataDirectory; now
    config.settings.data?.directory. The imported webcomponents.md referred to
    config.processing.states, now the figma.states convention.
  • outputFormat is now required on TransformerContext, so every ported test
    context failed to compile. Supplied it, and replaced two as any casts in
    Cssvars.test.ts with real contexts rather than letting them hide the gap.
  • Css.mapping.md carried a client library namespace in three places; replaced
    with the neutral ds forms used on the release branch.
  • cssvars had a docs page but no nav entry or index row — it was unreachable.

Verification

  • tsc --noEmit: zero new errors. Two remain (GenerateCommand.ts:589, an rfc/
    storybook import) — both confirmed pre-existing on the release branch.
  • vitest packages/cli/tests: 688 passed, 2 skipped.
  • Runtime smoke test: the built CLI resolves all seven transformer names with no
    "unknown transformer" warning; a control run with a bogus name does warn,
    confirming the external packages load at runtime.

Dependencies

Neither @directededges/react-from-specs nor
@directededges/webcomponents-from-specs is published yet; both resolve through
workspace symlinks for development. Both need a first publish before a release
carrying this can ship.

The matching engine work is on feat/react-from-specs in specs-from-figma, which
carries the ADR-071 engine refactor this CLI calls against.

🤖 Generated with Claude Code

nathanacurtis and others added 2 commits August 27, 2026 14:55
…to their own package

Rebuilds the transform work onto the current release. Three adaptations the
original branch predates: clipsContent (ADR-069), configuration read from
settings.data.directory (ADR-071), and outputFormat now required on
TransformerContext.

- cssvars emits library-level CSS custom properties from the fetched library JSON
- css expresses inline shadows, blurs and gradient fills that were dropped before
- react and stories are consumed from @directededges/react-from-specs

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…vars docs

Cherry-picks the webcomponents wiring onto the current release rather than
merging feat/webcomponents-from-specs, whose base predates ADR-069 and ADR-071
and would have reinstated clipContent and the removed Config type.

- webcomponents and webcomponents-stories come from
  @directededges/webcomponents-from-specs, consumed like react-from-specs
- both are marked experimental in the docs and changelog; the output shape is
  not yet stable
- the imported webcomponents page referred to config.processing.states, which
  ADR-071 replaced with the figma.states convention
- cssvars had a docs page but no nav entry or index row, so it was unreachable

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nathanacurtis nathanacurtis changed the title feat(cli): cssvars transformer, inline effects in css, react/stories to their own package feat(cli): cssvars + inline effects, react/stories and experimental webcomponents transformers Aug 28, 2026
nathanacurtis and others added 4 commits August 28, 2026 14:29
`--components` narrows the per-component pass, but cssvars produces all of its
output in finalize() over the whole data directory, so a single-component run
still paid for the full library stylesheet.

TransformerContext now carries `scoped`, set when --components is given, and
the cssvars transform returns early rather than rebuilding output a scoped run
cannot have invalidated. Re-running it unscoped stays the way to pick up token
changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Figma frame dimensions include padding; the CSS default excludes it, so every
element carrying both a fixed dimension and padding rendered larger than the
spec by exactly its padding — a 24px frame with 4px padding measured 32px.
Generated stylesheets now set border-box on the block and its descendants.

False variant values emitted `[data-x="false"]`, but scaffolds write booleans as
presence: the attribute is set to "" when true and omitted when false, never
written as "false". Those rules matched nothing, so every false-valued variant's
styling was dead. They now emit `:not([data-x])`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…versals

Cursor is an affordance Figma has no concept of. It is now inferred from what
the stylesheet actually styles: a component that styles a pressed state is a
press target and gets `pointer`; one that styles a disabled state gets
`not-allowed`. Keying off emitted rules rather than declared states matters —
a spec can declare a state whose variant produces no styling at all, which is
not evidence of interactivity, and that gave text inputs a pointer.

An opacity VARIABLE is authored on a percentage scale (36) while an unbound
opacity arrives as the ratio Figma stores (0.36). Emitting the variable bare
produced `opacity: 36`, which clamps to 1 and silently discarded the state.
Token references are now multiplied into a CSS percentage; raw values are
untouched, so the two authoring paths both round-trip.

A classified boolean's FALSE value has no concept of its own — it is the
negation of the true concept. Those variants were dropped entirely as base/rest
state, so an unselected variant and every hover/pressed pairing with it emitted
no rule. They now emit `:not([aria-selected="true"])`, with multi-part concepts
negated as an AND of nots.

A variant layout that exactly reverses a flex parent's children now emits
`flex-direction: row-reverse`/`column-reverse` under that variant's selector.
This is a visual swap, so DOM order — and reading and tab order — stays as
authored. Partial reorders are not expressible this way and are relocated by
the emitters instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A negative itemSpacing means children OVERLAP, which `gap` cannot express. The
branch that recognised it returned early with a note that child margins were
"deferred" — so overlap was never emitted, and every layout declaration after
that point was silently dropped for the element too, including FILL
translation. Stylesheets now emit a negative margin on each child after the
first, along the parent's main axis, and the negative case only skips `gap`.

`--get-images` picked the file to query by a hard-coded preference for the
`library` alias, so generating from a different source asked the wrong file for
its image URLs. A hash present only in the source file came back missing and
was reported as Figma failing to return it. The alias now comes from the
manifest being generated, which already names its source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nathanacurtis nathanacurtis changed the title feat(cli): cssvars + inline effects, react/stories and experimental webcomponents transformers Feature: React and Web Components Transformers Aug 31, 2026
Brings in ADR-080: PropConfigurationValue and InstanceExample.propConfigurations
admit null, so a configuration can state that a nullable prop is unset.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant