Commit Graph
1661 Commits
Author SHA1 Message Date
Trevor BrownandClaude Opus 5.5 32f5fd01c4 Merge upstream/main into feat/tidy-up-figma-rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 12:36:27 -05:00
bflatastic 87d5f993f7 Merge main into #2319 2026-11-14 00:04:47 +07:00
bflatastic 0bf58651bf Merge main into #2285 2026-11-14 00:02:36 +07:00
BFlat ec18cc67cb Merge pull request #2320 from TrevorBrown-dev/fix/native-code-every-fill-2271
M10: SwiftUI, Compose and Flutter code export writes every colour and gradient fill, in paint order (#2271)
2026-11-14 00:00:13 +07:00
bflatastic e352aa8d61 Merge main into #2319 2026-11-13 23:44:35 +07:00
bflatastic cae4a71c0c Merge main into #2308 2026-11-13 23:43:29 +07:00
bflatastic 4ad2c52b28 Merge main into #2285 2026-11-13 23:41:48 +07:00
bflatastic aef88e0197 Merge main into #2320 2026-11-13 23:39:40 +07:00
bflatastic 8932347ef8 Merge main into #2139 2026-11-13 23:23:44 +07:00
bflatastic e1647fba6a Merge main into #2139 2026-11-13 23:04:53 +07:00
bflatastic 953383fff8 Merge main into #2331 2026-11-13 22:54:24 +07:00
BFlat c56d5d22ad Merge pull request #2327 from TrevorBrown-dev/fix/mind-map-parent-default
M12: a mind map in a section or frame without a place starts at the view's centre
2026-11-13 22:51:44 +07:00
BFlat 1f7e6e0482 Merge pull request #2321 from TrevorBrown-dev/fix/flutter-space-distribution-spacing
M10: Flutter code writes no spacing beside a space distribution
2026-11-13 22:48:28 +07:00
bflatastic 71e063de9f Merge main into #2139 2026-11-13 22:47:03 +07:00
BFlat 1448a91a54 Merge pull request #2086 from TrevorBrown-dev/fix/snap-threshold-measured
Smart guides: objects snap within 4.5 screen pixels, as measured in Figma
2026-11-13 22:45:56 +07:00
BFlat ec524f6cd3 Merge pull request #2315 from TrevorBrown-dev/fix/css-layered-background-2271
M10: CSS code export writes several fills as one layered background, top first, as Figma does (#2271)
2026-11-13 22:44:32 +07:00
BFlat ce7de19097 Merge pull request #2294 from TrevorBrown-dev/fix/fig-list-start-2273
M6: .fig import: a numbered list keeps its start only where it starts counting (#2273)
2026-11-13 22:41:46 +07:00
BFlat ebc0260017 Merge pull request #2289 from TrevorBrown-dev/feat/sticky-size-menu
M12: Boards: a sticky's text-size menu (Small 16 to Huge 96), remembered for new stickies
2026-11-13 22:39:41 +07:00
Trevor BrownandClaude Sonnet 5.5 cdef36b543 Merge upstream/main into feat/polygon-count-handle
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
2026-11-13 10:39:31 -05:00
Trevor BrownandClaude Sonnet 5.5 74bc74e4f6 Merge upstream/main into feat/tidy-up-figma-rule
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
2026-11-13 10:38:49 -05:00
bflatastic ae2e4799d7 Merge main into #2285 2026-11-13 22:35:39 +07:00
bflatastic 83a0177310 Merge main into #2139 2026-11-13 22:29:54 +07:00
bflatastic 91d2caf9df Merge main into #2086 2026-11-13 22:29:18 +07:00
BFlat 6fb76233ec Merge pull request #2087 from TrevorBrown-dev/feat/variables-duplicate-group-2084
Variables: Duplicate group copies a group's variables as "<name> 2" in one undo step
2026-11-13 22:27:09 +07:00
Trevor BrownandClaude Opus 5.5 1a68ded581 M8: Tidy up keeps the second layer after a narrow first one when the gap is negative
raise_negative_gap raised a negative gap from the interior widths only, so a first layer narrower than the gap's size let the second start before it (widths 10, 100 x5 at X 0, 80, 200, 260, 400, 460: the modal -40 gave X 0, -30, 30, ...). The second must start after the first, g > -w(0), in whole pixels floor(-w(0)) + 1. Figma kept the order in every measured run; a narrow first layer was not measured, so this bound keeps the order rather than follow a reading. Test: tidy_tests::a_narrow_first_layer_still_keeps_the_second_after_it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-13 01:43:34 -05:00
Denis GomesandClaude Opus 5.5 c74bcd0b0e M1: Paste over selection puts the copies just above the selected layer
Shift+Cmd/Ctrl+V pasted into the selected layer's parent at its top-left,
but appended the copies, so they went to the top of the parent's layers
whatever was selected. In an auto layout frame that put them at the end of
the flow, away from the layer they were pasted over.

They now go just above the selected layer, in the copied order: in the
Layers panel the row above it, in an auto layout frame the item after it.
A section pasted over a layer in a frame still goes on the page, on top
of its layers, as before. Paste to replace already kept the replaced
layer's place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 01:41:48 -05:00
Trevor BrownandClaude Sonnet 5.5 e0e57dd42a M12: a mind map in a section or frame without a place is centred on the view
board.mindMap's default x and y came from the view's centre, which is on the page, but x and y are the root's place in its parent: under a section at (1000, 500) the map landed 1000 right and 500 down of the view's centre. The default now takes the centre through the parent's inverse world transform (a parent that can't be inverted gives a non-finite centre, which the coordinate check refuses). Explicit x and y stay parent-local. Tests: two in board_mindmap_tests (a section; a frame turned 30 degrees, with a given place carried through the frame).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
2026-11-13 00:45:11 -05:00
Trevor BrownandClaude Sonnet 5.5 a7a9e62721 M8: Tidy up takes a row whose layers overlap in a chain, and raises a negative gap so no layer reaches the one two before it, as in Figma
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
2026-11-13 00:00:02 -05:00
Trevor BrownandClaude Opus 5.5 7d94fa78e9 Merge upstream/main into fix/mirror-sharp-point-2110
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 22:06:21 -05:00
Trevor BrownandClaude Opus 5.5 1106b44ad3 Merge upstream/main into feat/scale-origin-centre
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 21:44:28 -05:00
Trevor Brown 0342b97574 Merge remote-tracking branch 'upstream/main' into fix/flutter-space-distribution-spacing 2026-11-12 21:28:29 -05:00
Trevor BrownandClaude Opus 5.5 38c68e6714 Merge upstream/main into fix/fig-list-start-2273
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 21:17:54 -05:00
Trevor Brown a16321603c Merge remote-tracking branch 'upstream/main' into fix/native-code-every-fill-2271 2026-11-12 21:12:28 -05:00
Trevor BrownandClaude Sonnet 5.5 a62435bb11 Merge remote-tracking branch 'upstream/main' into fix/css-layered-background-2271
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
2026-11-12 20:53:31 -05:00
Trevor BrownandClaude Sonnet 5.5 197af01629 Merge upstream/main into fix/snap-threshold-measured
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
2026-11-12 20:42:00 -05:00
Trevor BrownandClaude Sonnet 5.5 7057fbf3a5 Merge upstream/main into feat/variables-duplicate-group-2084
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
2026-11-12 20:41:57 -05:00
Trevor BrownandClaude Sonnet 5.5 e4ef6a62a7 Merge upstream/main into feat/zoom-selection-margin
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
2026-11-12 20:41:24 -05:00
Trevor BrownandClaude Opus 5.5 b90504a7dc Merge upstream/main into feat/polygon-count-handle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 20:40:53 -05:00
Trevor BrownandClaude Opus 5.5 a0a0570d64 Merge upstream/main into feat/tidy-up-figma-rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 20:36:50 -05:00
NetterSilverandClaude Opus 5.5 7b34dbab00 Keep library links working when the project folder is moved or cloned (#2132)
Fixes #2122

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 01:48:30 +01:00
Trevor BrownandClaude Sonnet 5.5 3d22c606a7 M10: Tailwind mobile merge keeps a max-width both frames share (#2272) (#2296)
inspect.code {language: "tailwind", mobile} dropped every mobile max-width and put the desktop one behind lg:, so a fixed-size layer that is the same size on both frames (a 48 x 48 badge) stretched to the full width below 1024 px. A mobile max-width that equals the desktop one is now kept as a plain class, with no lg: copy. A differing max-width still goes fluid on mobile with the desktop cap behind lg:.

Tests: the issue's two frames (a 1440 and a 390 page, each with a 48 x 48 badge) keep max-w-[48px] unprefixed, and a page-wide banner still gets its lg: cap only. The slashed-names test gave the CTAs frame the same 342 px on both frames, which the new rule rightly keeps, so its desktop frame is now 600 px wide. docs/mcp.md notes the rule.


Model-Effort: Opus 5.5 drove; Fable 5.2 queued; Opus 5.5 briefed; Sonnet 5.5 wrote at medium

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 01:44:17 +01:00
NetterSilverandClaude Haiku 5.5 04a6c76a9b Return an error for non-numeric miterLimit, cornerSmoothing and paragraph props (#2303)
Found while testing crates/engine/src/cmd/props.rs:660 (also :710, :984, :985)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-13 01:42:52 +01:00
Trevor BrownandClaude Opus 5.5 477f004400 Merge upstream/main into fix/mirror-sharp-point-2110
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 19:39:01 -05:00
Trevor BrownandClaude Opus 5.5 02199134b7 M10: Flutter code writes no spacing beside a space distribution
flutter_line skipped `spacing:` when the main-axis alignment string
contained "Space", but the strings are MainAxisAlignment.spaceBetween,
spaceAround and spaceEvenly (lower-case s), so it never matched and the
gap was written on top of the distribution. It now checks the MainAlign
value itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote; Fable 5.2 spotted it
2026-11-12 17:38:44 -05:00
Trevor BrownandClaude Sonnet 5.5 e276b1e4ff M10: SwiftUI, Compose and Flutter code export writes every colour and gradient fill, in paint order (#2271)
Part of #2271. The native targets took ONE colour from a layer (`first_fill`: the bottom visible solid, or a gradient's first stop) and dropped every other fill, so a frame with white, a gradient and 50% black came out as flat white.

Now SwiftUI, Compose and Flutter write every visible solid and gradient fill of a frame, group, rectangle or ellipse, in paint order, and a gradient as a gradient. A shared `visible_fills` gives each target the same list (document order, bottom first); each target only formats it.

- SwiftUI: chained `.background(...)`, top fill first, since each one goes behind the ones before it. A rectangle or ellipse fills with the bottom paint and lays a copy of the shape over it (`.overlay`) for each paint above, bottom first.
- Compose: chained `Modifier.background(...)`, bottom fill first, since each one draws over the ones before it. A linear gradient snaps to one of eight directions (a modifier doesn't know pixels); radial and angular ones sit at the box's centre.
- Flutter: a `BoxDecoration` paints one colour or gradient, so the Container keeps the top fill and each fill under it becomes a `DecoratedBox` around the widget, the bottom fill outermost. The drop shadow goes on the outermost box so it never lies over a lower fill. An instance's call is now made once, in `flutter_placed`.

A layer with one solid fill gives the same code as before, variable names included (only the paint at index 0, when solid, takes the `fill` variable's name). A lone gradient used to come out as its first stop's colour and is now a gradient.

Still not written: image and pattern fills in the native targets, ArkUI (one background and one gradient per component, so a stack needs extra nodes) and text with several fills. Figma's iOS and Android output was not measured: the order follows how each platform paints.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 briefed; Sonnet 5.5 wrote at medium
2026-11-12 17:04:23 -05:00
Trevor BrownandClaude Sonnet 5.5 d3f7ae9029 M8: Tidy up rounds the gap, tidies overlapping rows and grids along their longer axis, as measured
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
2026-11-12 16:32:48 -05:00
Trevor BrownandClaude Sonnet 5.5 cfd6bf5273 M8: Tidy up keeps the order and sizes and evens the gaps, as measured in Figma
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
2026-11-12 16:18:06 -05:00
Trevor BrownandClaude Sonnet 5.5 9467d2355b M10: CSS code export writes several fills as one layered background, top first, as Figma does (#2271)
inspect.code wrote one `background` per visible fill, so only the last survived. A non-text layer now gets one `background` with every visible fill as a layer, top fill first (a document's fills run bottom to top), joined with ", ". A solid above another fill is `linear-gradient(0deg, c, c)` since CSS allows a plain colour only as the last layer; the bottom solid stays a plain colour (and keeps its variable token); gradients, images and patterns keep their layers. A single fill is unchanged. Text keeps one `color` per fill. Tailwind emits a layered value as one `[background:...]` class. Native targets are unchanged.

Tests: several_fills_are_one_layered_background_top_first, one_solid_fill_stays_a_plain_colour, several_fills_are_one_layered_background_class.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 queued; Opus 5.5 briefed; Sonnet 5.5 measured and wrote at medium
2026-11-12 13:54:12 -05:00
Trevor BrownandClaude Opus 5.5 d773ab2985 M8: a pointer on the shape's centre gives the most points
atan2 of two zeros reads as 180 degrees, so the centre gave 3; it now gives 60,
and the test asserts that exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and fixed
2026-11-12 11:42:22 -05:00
Trevor BrownandClaude Sonnet 5.5 89d8b50e9d M8: polygons and stars get a Count handle on the canvas, as in Figma
A selected polygon or star shows a Count handle on its first point clockwise from the top (a polygon's vertex 1, a star's outer point 1, not inset). Dragging it sets the count to round(360 / the pointer's clockwise angle from the top point about the centre), measured in the shape's own frame and clamped to 3 to 60, written through node.set pointCount in one undo step. The readout and hover label read "Count N". Measured in Figma's web app on 2026-11-12; a polygon's maximum wasn't read, so it is clamped at 60 like the star's.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 queued; Opus 5.5 briefed; Sonnet 5.5 measured and wrote at medium
2026-11-12 09:56:45 -05:00