feat(import-design): complete Figma Auto Layout lowering in all three producers#6338
Merged
Conversation
… 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"
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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.
counterAxisAlignContent SPACE_BETWEEN -> alignContent; wrap-only.
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.
flexible — on a fully fixed node the ratio would re-derive the cross
axis and fight Figma's solved size over rounding.
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
.figdecoder, 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
flexGrow,alignSelf, andaspectRatiofor flowing children of flex parents; ABSOLUTE children take the coordinates/constraints path.rowGap/columnGapandalignContent(wrap-only).gridTemplateColumns/Rowsand per-childgridColumn/gridRowlines; consumers read templates when v0/TSX attributes are absent.minWidth/Height,maxWidth/Height) with safe guards..figlanes.Migration
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.
🔎 Provenance
claudem5~/Code/pulp-alfd0e374f-d5dc-4e2a-bb33-6fa8b66124a0Resume
Jump to this tab
Relaunch (any agent)
Restore URL — https://claude.ai/code/session_014e8kbMn8Sf4mF8ozsy1Jot
stamped 2026-07-19 09:41 UTC