Measured in Figma's web app on 2026-11-13. Co-Authored-By below.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 measured and wrote at medium
Third Figma run (web app, 2026-11-12): the mean gap rounds half up (X 0, 60, 135, 215 gives 0, 72, 144, 216; 0, 60, 135 gives 0, 68, 136); a tie between repeated gaps takes the smaller; a row is one row group of layers and may overlap along X, a negative gap counting in the mean (X 0, 60, 105, 170 gives 0, 57, 114, 171); a grid is packed along the axis with more groups (2 x 3 by centred columns, 3 x 2 by centred rows); a diagonal, an irregular layout and layers that all overlap are unavailable. The earlier cell rule is removed.
Choices, not measured: a 2 x 2 grid as columns, auto layout flow children unavailable, the 2,000 layer cap, rotated layers by their box, spacing with two layers, no rounding with Snap to pixel grid off. Grid positions along a stack can differ by a pixel from Figma's (it read 52 and 51 for the same mean 51.67).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 measured and wrote at medium
Measured in Figma's web app on 2026-11-12 (top-level rectangles): a row, a column and a 2 x 3 grid of unequal sizes, eight runs on the gap, and the cases where Tidy up is greyed out (fewer than three layers, overlapping layers). Figma keeps the order, the sizes and the first layer, evens one gap per axis (the most common existing gap, else the mean taken down to a whole number) and tidies a grid by columns.
Choices, not measured: the smaller of two equally common gaps, a mean ending in .5 going down, gaps a pixel apart differing, a diagonal being a row, a grid with an empty place, a grid always read by columns, one overlapping pair making it unavailable, auto layout flow children making it unavailable, the 2,000 layer cap, rotated layers by their box, spacing with two layers, and no rounding with Snap to pixel grid off. A selection that is none of a row, a column or a grid keeps the earlier rule unchanged.
The Design panel's Tidy up row is greyed out with a tip when unavailable; the command then moves nothing and adds no undo step.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 measured and wrote at medium
* perf(engine): replace linear scans that made document-sized inputs quadratic
enum_of re-classified the whole string for every character; widget names,
comment mentions and devcompare's common siblings each searched a growing
Vec; data.fill's export names and codegen's class names retried every
suffix from 2 against a list, cubic in the number of copies of one name;
and a batch rename template had no length cap although it is expanded
again for every match.
* address review: report the pattern in its own length error
selection.createColorStyle gives the new style the variable bindings of the fill or stroke it is made from, the color and the gradient stops, so linked layers keep following the variables. Targets bound to different variables are refused, and the Styles menu leaves Create style out for them.
selection.applyColorStyle chose the library path only when `library` read as a string, so an
object or array fell through and linked a local style of the same name. Any non-null `library`
now goes to the library lookup, which refuses a non-string before any change.
A created style holds the row's one paint, and linking it replaces the target's whole fill or
stroke list, so a layer with other fills or strokes lost them. selection.createColorStyle now
refuses such targets before any change, and the Styles menu leaves Create style out for those
rows.
A row's Styles menu lists this file's color styles (library copies are no
longer listed as local), then each enabled library's published color
styles under the library's name. Choosing a library style copies it in,
linked and read-only, and applies it to every fill and stroke the row
stands for in one undo step (selection.applyColorStyle with `library`).
A row linked to a style or variable now has the Styles button too, so it
can switch to another style.
Create style on an ordinary row opens a window with the next free
"Color n" name. Create makes a style of the row's whole paint (gradient
stops, opacity and blend mode included) and links it to every target in
one undo step (selection.createColorStyle {targets, name, description?}).
A name another style has, an empty name, or a row whose slots no longer
share one paint is refused, and the window stays open. Text ranges with
fills of their own take the style's value without a link.
node.create and node.set take {"$var": variable id or name} for fill and stroke: an exact id
first, then an unambiguous name or Collection/name. The binding goes through the same code as
variable.bind, is applied after literal values and style links, replaces other bindings of that
paint's colour and clears its style link, all in one undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>