Skip to content

feat(import-design): complete Figma Auto Layout lowering in all three producers#6338

Merged
shipyard-local[bot] merged 1 commit into
mainfrom
feat/figma-auto-layout
Jul 19, 2026
Merged

feat(import-design): complete Figma Auto Layout lowering in all three producers#6338
shipyard-local[bot] merged 1 commit into
mainfrom
feat/figma-auto-layout

Conversation

@shipyard-local

@shipyard-local shipyard-local Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Audit checklist #3 (Auto Layout row): the consumers already parsed and
lowered child flex, wrap gaps, aspect ratio, and CSS grid — the Figma
producers simply never emitted them. Close the gap in the .fig decoder
(scene.mjs), the plugin extractor (extract-pure.ts), and the REST
exporter (figma_rest_export.py), all writing the consumer's camelCase
layout keys (parse_ir_layout's exact members) with CSS-kebab values:

  • Child grow/align: kiwi stackChildPrimaryGrow/stackChildAlignSelf,
    plugin+REST layoutGrow/layoutAlign -> flexGrow/alignSelf, gated to
    FLOWING children of FLEX auto-layout parents (INHERIT omits — that IS
    the flex default). An ABSOLUTE stack child now takes the
    absolute-position + constraints path in the plugin/REST lanes instead
    of getting neither flex nor coordinates.
  • Wrap extras: counterAxisSpacing -> rowGap (row) / columnGap (column),
    counterAxisAlignContent SPACE_BETWEEN -> alignContent; wrap-only.
  • Figma GRID auto-layout -> the existing IR grid contract. Plugin/REST:
    repeat(N, 1fr) templates from grid counts, 0-based child anchors ->
    CSS 1-based lines. .fig: GUID-keyed track lists sorted by
    fractional-index position, FLEX->fr / FIXED->px sizing, anchor GUIDs
    (field spellings verified against a decoded GRID NodeChange). Grid
    children flow: GRID joins flex in the absolute-position, constraints,
    and mask gates in all lanes.
  • targetAspectRatio -> aspectRatio, emitted only when an axis is
    flexible — on a fully fixed node the ratio would re-derive the cross
    axis and fight Figma's solved size over rounding.
  • min/max sizing -> style min_/max_ width/height clamps (per-axis > 0
    guard; the kiwi OptionalVector rides unset axes as 0 and a zero max
    would collapse the node). Consumed end-to-end: parse_ir_style ->
    FlexStyle clamps (native) and style.minWidth (web).

Consumer completions shipped alongside (the JS lane lagged the native
one): design_codegen now lowers explicit flex_grow values, align_self,
aspect_ratio, row/column gaps, flex_wrap, and align_content, with the
constraint fallbacks deduplicated behind grow_done/stretch_done; the
native materializer and cpp codegen read grid templates from the IR
layout fields when the v0/TSX contract attributes are absent
(previously a Figma GRID entered grid mode with an empty track list and
dropped every child) and both gained per-child gridColumn/gridRow line
parsing. Plugin schema layout keys now use the consumed camelCase
spellings; the previously declared snake_case row_gap/flex_grow/... that
no producer emitted and no consumer read are gone.

Validation: end-to-end Catch2 cases ([view][import][autolayout]) drive
both wire dialects through parse_design_ir_json AND
parse_figma_plugin_json to generate_pulp_js; producer unit tests in
fig.test.mjs, figma-plugin/test/layout.test.ts, and
test_figma_rest_export.py::AutoLayoutTest cover each lane's source
spelling including the child-of-auto-layout gates. Real-file layout
parity on the 1299-node reference frame: layout_parity.py reports are
byte-identical before/after; the only solved-rect deltas are the 16
GRID cells (<= 0.5px: y-placement now exact where rounding was 0.5 off,
heights 33 -> 33.5 because the grid engine stretches children to their
cell — a documented engine limitation, not a producer one).

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_014e8kbMn8Sf4mF8ozsy1Jot
Hotspot-Grow: core/view/src/design_codegen.cpp reason="lower explicit flex_grow/align_self/aspect_ratio + row/column gap + flex_wrap/align_content for Figma auto-layout"
Hotspot-Grow: core/view/src/design_cpp_codegen.cpp reason="read grid templates + per-child grid placement so a Figma GRID no longer drops its children in native codegen"
Hotspot-Grow: core/view/src/design_import_native_common.cpp reason="read grid templates from layout for live-native auto-layout GRID materialization"
Hotspot-Grow: test/test_design_import_fidelity.cpp reason="end-to-end auto-layout codegen tests: grow/align-self/grid-span/counter-axis/aspect across both wire dialects"


Summary by cubic

Completes Figma Auto Layout lowering across the .fig decoder, plugin extractor, and REST exporter so producers emit the full layout contract with camelCase keys and CSS values. Fixes GRID imports that dropped children and brings plugin/REST/.fig outputs in line with the consumer’s IR.

  • New Features

    • Emit child flexGrow, alignSelf, and aspectRatio for flowing children of flex parents; ABSOLUTE children take the coordinates/constraints path.
    • Add wrap extras: rowGap/columnGap and alignContent (wrap-only).
    • Map Figma GRID auto-layout to IR: gridTemplateColumns/Rows and per-child gridColumn/gridRow lines; consumers read templates when v0/TSX attributes are absent.
    • Output style min/max clamps (minWidth/Height, maxWidth/Height) with safe guards.
    • JS/native codegen now lower explicit flex props and grid templates; end-to-end tests cover plugin, REST, and .fig lanes.
  • Migration

    • Plugin export schema now uses camelCase layout keys: rowGap, columnGap, flexGrow, alignSelf, gridTemplateColumns/Rows. Removed snake_case keys (row_gap, flex_grow, etc.). Parser already expects camelCase, so downstream consumers should be unaffected.

Written for commit 75b4d7b. Summary will update on new commits.

Review in cubic


🔎 Provenance

Agent claude
Machine m5
Directory ~/Code/pulp-al
Session fd0e374f-d5dc-4e2a-bb33-6fa8b66124a0

Resume

claude --resume fd0e374f-d5dc-4e2a-bb33-6fa8b66124a0

Jump to this tab

cmux surface focus B1B434D3-85F6-4937-8806-96D59886F574

Relaunch (any agent)

cmux surface resume get --surface B1B434D3-85F6-4937-8806-96D59886F574

Restore URLhttps://claude.ai/code/session_014e8kbMn8Sf4mF8ozsy1Jot

stamped 2026-07-19 09:41 UTC

… producers

Audit checklist #3 (Auto Layout row): the consumers already parsed and
lowered child flex, wrap gaps, aspect ratio, and CSS grid — the Figma
producers simply never emitted them. Close the gap in the .fig decoder
(scene.mjs), the plugin extractor (extract-pure.ts), and the REST
exporter (figma_rest_export.py), all writing the consumer's camelCase
layout keys (parse_ir_layout's exact members) with CSS-kebab values:

- Child grow/align: kiwi stackChildPrimaryGrow/stackChildAlignSelf,
  plugin+REST layoutGrow/layoutAlign -> flexGrow/alignSelf, gated to
  FLOWING children of FLEX auto-layout parents (INHERIT omits — that IS
  the flex default). An ABSOLUTE stack child now takes the
  absolute-position + constraints path in the plugin/REST lanes instead
  of getting neither flex nor coordinates.
- Wrap extras: counterAxisSpacing -> rowGap (row) / columnGap (column),
  counterAxisAlignContent SPACE_BETWEEN -> alignContent; wrap-only.
- Figma GRID auto-layout -> the existing IR grid contract. Plugin/REST:
  repeat(N, 1fr) templates from grid counts, 0-based child anchors ->
  CSS 1-based lines. .fig: GUID-keyed track lists sorted by
  fractional-index position, FLEX->fr / FIXED->px sizing, anchor GUIDs
  (field spellings verified against a decoded GRID NodeChange). Grid
  children flow: GRID joins flex in the absolute-position, constraints,
  and mask gates in all lanes.
- targetAspectRatio -> aspectRatio, emitted only when an axis is
  flexible — on a fully fixed node the ratio would re-derive the cross
  axis and fight Figma's solved size over rounding.
- min/max sizing -> style min_/max_ width/height clamps (per-axis > 0
  guard; the kiwi OptionalVector rides unset axes as 0 and a zero max
  would collapse the node). Consumed end-to-end: parse_ir_style ->
  FlexStyle clamps (native) and style.minWidth (web).

Consumer completions shipped alongside (the JS lane lagged the native
one): design_codegen now lowers explicit flex_grow values, align_self,
aspect_ratio, row/column gaps, flex_wrap, and align_content, with the
constraint fallbacks deduplicated behind grow_done/stretch_done; the
native materializer and cpp codegen read grid templates from the IR
layout fields when the v0/TSX contract attributes are absent
(previously a Figma GRID entered grid mode with an empty track list and
dropped every child) and both gained per-child gridColumn/gridRow line
parsing. Plugin schema layout keys now use the consumed camelCase
spellings; the previously declared snake_case row_gap/flex_grow/... that
no producer emitted and no consumer read are gone.

Validation: end-to-end Catch2 cases ([view][import][autolayout]) drive
both wire dialects through parse_design_ir_json AND
parse_figma_plugin_json to generate_pulp_js; producer unit tests in
fig.test.mjs, figma-plugin/test/layout.test.ts, and
test_figma_rest_export.py::AutoLayoutTest cover each lane's source
spelling including the child-of-auto-layout gates. Real-file layout
parity on the 1299-node reference frame: layout_parity.py reports are
byte-identical before/after; the only solved-rect deltas are the 16
GRID cells (<= 0.5px: y-placement now exact where rounding was 0.5 off,
heights 33 -> 33.5 because the grid engine stretches children to their
cell — a documented engine limitation, not a producer one).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e8kbMn8Sf4mF8ozsy1Jot
Hotspot-Grow: core/view/src/design_codegen.cpp reason="lower explicit flex_grow/align_self/aspect_ratio + row/column gap + flex_wrap/align_content for Figma auto-layout"
Hotspot-Grow: core/view/src/design_cpp_codegen.cpp reason="read grid templates + per-child grid placement so a Figma GRID no longer drops its children in native codegen"
Hotspot-Grow: core/view/src/design_import_native_common.cpp reason="read grid templates from layout for live-native auto-layout GRID materialization"
Hotspot-Grow: test/test_design_import_fidelity.cpp reason="end-to-end auto-layout codegen tests: grow/align-self/grid-span/counter-axis/aspect across both wire dialects"
@shipyard-local
shipyard-local Bot merged commit c241fa6 into main Jul 19, 2026
29 of 54 checks passed
@danielraffel
danielraffel deleted the feat/figma-auto-layout branch July 19, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant