Commit Graph
1608 Commits
Author SHA1 Message Date
Trevor BrownandClaude Opus 5.5 794fe03690 Merge upstream/main into fix/crosshair-glyph-2198
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:39:39 -05:00
Trevor BrownandClaude Opus 5.5 e829590fdf Merge upstream/main into fix/escape-deselects-measured
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:39:38 -05:00
Trevor BrownandClaude Opus 5.5 043cce4996 Merge upstream/main into fix/boolean-corner-radius-2222
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:39:33 -05:00
Trevor BrownandClaude Opus 5.5 c6b030ae02 Merge upstream/main into feat/vector-edit-point-xy-mixed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:39:31 -05:00
Trevor BrownandClaude Opus 5.5 986f6d0a4c Merge upstream/main into fix/header-icon-size-2227
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:39:30 -05:00
Trevor BrownandClaude Opus 5.5 a85bd83bc7 Merge upstream/main into fix/submenu-opens-without-delay
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:39:25 -05:00
Trevor BrownandClaude Sonnet 5.5 b8f05d1948 M4: a typed number and full stop start a numbered list at that number, as measured in Figma
Typing `5. ` at the start of a plain paragraph starts a numbered list whose first item reads 5 (Figma has no start-number field); the next item goes on with 6. Digits only, 1 to 9999; `0. `, `12a. ` and `2)` stay text (not measured), `1)` works as before. The measured restart rows (plain paragraph, bullet item) are no longer marked silent.

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-11 09:58:57 -05:00
Trevor BrownandClaude Opus 5.5 92753a8f6d M2.1: auto layout parity doc: a Fill child's minimum still sizes its fr track
The minmax(0, 1fr) rule holds for fixed-size and hug-size children; a
Fill child's own minimum keeps its track at least that big, as before
(not measured).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 09:39:43 -05:00
Trevor BrownandClaude Sonnet 5.5 2dd1b5a53a Menus: the main menu and its submenus stay inside the window and scroll when taller (#2217) (#2220)
Submenus of the main menu were not constrained to the window (constrain: false, so they sit
beside their row), so the Object submenu ran past the bottom edge with its lowest commands out of
reach. Menus and submenus are now held within the window's height (moved up, never above the top,
8 pt margins, still beside their row so menu-aim is unchanged), and a panel taller than that
scrolls with the wheel; the keyboard's focus scrolls into view.


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

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 15:32:21 +01:00
Trevor BrownandClaude Opus 5.5 daa6b5b219 M2.1: auto layout parity doc: the behaviour suite's case count
The suite has 68 cases with this branch's three (the intro still said
64, and the revision row named totals nobody had counted).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 08:27:14 -05:00
Trevor BrownandClaude Sonnet 5.5 a28c03d9f1 M2.1: grid Fill tracks share the space and let a fixed child overflow, as measured in Figma
On a fixed-size axis a Fill (1fr) track now splits the free space by its fraction, after gaps and padding, and a fixed-size or hug-size child alone in it asks nothing of the track, so a child bigger than its share overflows into the next track. Measured in Figma web: frame 300 x 200, rows 1fr / 1fr, children 100 x 150 and 100 x 20 give rows 100 / 100 (second child at y 100); with a row gap of 10, rows 95 / 95 (y 105); with the frame height on Hug the rows fit their content, 150 / 20 (frame 170). This replaces the 2026-10-11 rule from the documentation (a track never gets less than its content). Unchanged: a Fill child's own minimum, spanning children, Hug and Fixed tracks, the Hug-axis path.

Three behaviour cases flipped to the measured rule and three added with the measured numbers; the two parity docs are updated.

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-11 08:26:17 -05:00
Trevor BrownandClaude Sonnet 5.5 641cc41594 M4: Flatten bakes a vector's point corner radius into its path (#2195)
Flatten used the vector's network as stored, so a rounded point kept its radius and the box kept the cut-off sharp vertex. It now takes the network as the layer draws it (VectorNetwork::rounded_smoothed through geometry::drawn_network): each rounded point becomes the arc's end points joined by curves, every radius reads 0, and the layer's box is re-fitted to the baked geometry. Shapes' own radii were already baked; a rounded point beside a curved segment keeps today's behaviour.

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-11 04:54:20 -05:00
Trevor BrownandClaude Sonnet 5.5 a75d8bf04f M4: Delete closes a region's loop through the segment that joins its ends (#1951)
A Pen bridge between two degree-2 points leaves the region alone, and Delete on the point between them then rotated the loop open with the bridge in no loop, so even an unrounded fill ran straight across a curved bridge. reopen_loops now pushes the one segment in no loop that joins an open chain's ends onto the loop's end (winding kept, loop by loop), as connect does. This also covers removing segments and the eraser.

closing_segment is public, derives its own start point, walks strictly (a loop out of walking order is left alone) and gives up when two segments qualify. The render fix stays as the fallback for files already saved in that state.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 reviewed; Sonnet 5.5 wrote at medium
2026-11-11 02:16:29 -05:00
Trevor BrownandClaude Sonnet 5.5 8e6c342f13 M4: a rounded vector's fill and outline follow every corner after a Pen bridge (#1951)
The reporter's file has a corner cut off by a segment drawn first, with the corner point then deleted with Delete. The region's loop is left as the open chain of the other four segments, and the cutting segment, which joins the chain's two ends, is in no loop. The stroke follows the network, so it rounds both ends of that segment; the fill and the selection outline follow the loop.

Cause: VectorNetwork::rounded_smoothed rebuilds each loop by inserting a corner's arcs where the loop arrives at the point, so the point the loop starts at is never cut (in a closed loop it is arrived at last), and the open loop's closing edge ran straight to it. closing_segment now finds the segment joining an open loop's two ends when no loop holds it, and rounding walks it as the loop's last segment, so every corner of the fill and outline matches the stroke. Closed loops and the cases #1991 fixed are untouched.

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-11 02:01:32 -05:00
Trevor BrownandClaude Opus 5.5 985231858c M10: Design panel: Swap orientation and the quick actions use the shared icon size (#2227)
The frame's Swap orientation button and the selection's quick-action buttons (24 pt) still
painted their glyphs at the old 14 pt inset beside Fit to contents' 16 pt; they use
`icon_glyph_rect` now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-11 00:38:03 -05:00
Trevor BrownandClaude Opus 5.5 8e1a2ff262 M10: Docs: the hardware-parity row keeps its three cells (#1343)
A bare pipe inside the code span `WS_CAPTION | WS_BORDER` split the table row; the two style
names are separate code spans now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-11 00:35:51 -05:00
Trevor BrownandClaude Opus 5.5 19c8e01811 M10: Design panel: 24 pt icon buttons draw 16 pt glyphs, as a fill row does (#2227)
A live check showed the header's eye still smaller than a fill row's: `icons::button`, which
draws the fill rows, paints the icons' native 16 px glyph in a 24 pt button, while the shared
inset gave 14. The inset is now 4, so section headers, `icon_button_at` and the panels' Close and
Swap buttons draw 16 pt glyphs like the rest. The test compares the header's eye with both
`icon_button_at` and `icons::button`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-11 00:08:06 -05:00
Denis GomesandClaude Opus 5.5 503823f958 M8: hardware-parity names the copy an image draws from
The canvas takes the smallest copy that holds the size an image shows in
device pixels, whatever the canvas zoom, as #1483 did for images over
4096 px. The canvas row said the copy was no larger than the image shows,
and both changelog rows said views at 100% and closer draw from the image
itself; a 3000 px image fitted into a 600 px layer draws from a 1024 px
copy at 100%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:56:35 -05:00
Trevor BrownandClaude Sonnet 5.5 97ceeca35e M10: Windows: the window no longer draws below a hidden caption (#1343)
FigCraft draws its own title bar on Windows and opens undecorated windows, but winit 0.30 still gives them WS_CAPTION | WS_BORDER. On some systems the desktop compositor reserves that caption, so the content is drawn about 30 px low (a black band above it) while clicks keep the client coordinates and land above what is drawn. Fixes #1343; the same report as #1422 and #2233.

A per-frame step (apps/figcraft/src/window_frame.rs, run from raw_input_hook) clears WS_CAPTION and WS_BORDER from every winit window of the process (the main window and the other windows alike), keeps WS_THICKFRAME and the window buttons so edge resizing and snap work, and calls SetWindowPos(SWP_FRAMECHANGED) only when a style changed. It uses safe winsafe calls (no unsafe), Windows only; other platforms and the browser are unchanged. The maximize-to-work-area trim of DesignCraft's fix is left for a later change.

Ports the fix of DesignCraft #2139 and PhotoCraft #2401. Tests: the style arithmetic (caption and border cleared, thick frame and buttons kept, a clean style unchanged) and the per-frame hook (runs every frame, a no-op when unset).

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-10 20:55:23 -05:00
Trevor BrownandClaude Sonnet 5.5 ebcaa58c4f M10: Design panel: section header icons draw at the panel's icon size (#2227)
Section header buttons painted their glyph in a 12 pt box inside the 24 pt button while icon_button_at used 14 pt. Both now use icon_glyph_rect (ICON_BUTTON_INSET 5 pt), as do the 24 pt Close and Swap buttons in the assets and prototype panels. Test compares the painted Eye bounds of a header and an icon button.

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-10 15:55:41 -05:00
Trevor BrownandClaude Sonnet 5.5 529134b850 M4: a boolean group's corner radius rounds its result (#2222)
A boolean group takes a corner radius (and corner smoothing): the Design panel shows the field for it, `node.set cornerRadius` writes it, and the renderer rounds every sharp corner of the group's result after the fold, convex and concave alike. Layers inside keep their own radius and do not stack with it, a radius larger than the edges allow shrinks to fit while the field keeps the typed value, Flatten bakes the arcs, and a mixed selection reads Mixed. Measured in Figma's web app on 2026-11-10.

The rounded result is cached apart from the fold (keyed by the folded path, radius and smoothing), so scrubbing the radius never redoes the boolean steps. The .fig and REST importers and the .fig exporter now carry the radius and smoothing of a boolean group, and paste properties and radius variables reach it. Docs: ui-parity, target-app-parity, gaps, file-format-parity, ROADMAP, mcp.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 designed; Sonnet 5.5 wrote at medium
2026-11-10 13:36:10 -05:00
Trevor BrownandClaude Sonnet 5.5 281918e2f2 M7: Text styles: a face name bound after a weight clears the axis; review fixes (#2174)
A style's fontStyle binding now goes through set_font_style, so it drops the stale wght axis a weight binding left behind. Also: a test and doc line for the token_roles weight role, the apply-order comment, the weight chip opening the weight picker, junk weight values pinned, fixture assertions fixed, and the ROADMAP and parity status lines.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 reviewed; Sonnet 5.5 wrote at medium
2026-11-10 13:09:35 -05:00
Trevor BrownandClaude Sonnet 5.5 ccb33e0bbc M7: Text styles: bind a number variable to the weight (#2174)
A text style takes a Number variable on `fontWeight` (`style.bind`): the style gets the face nearest the number and sets the `wght` axis, so static and variable fonts both follow. A bound weight and a bound face name replace each other, and the weight wins in a file carrying both. The style editor's weight menu offers number variables beside string ones, and a variable bound to a weight is Unitless in the CSS export. A number bound to a line height stays pixels, as the reference tool applies it; a test pins that.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 designed; Sonnet 5.5 wrote at medium
2026-11-10 12:43:24 -05:00
Trevor BrownandClaude Sonnet 5.5 020388cf1f M10: Export: a background blur or glass exported on its own keeps the content behind it (#2213)
An export of a layer draws only that layer, so a background blur or glass in it read an empty
backdrop. The export now renders the page's layers below the first exported layer, with the margin
the effects reach, as the backdrop (render_area_over), clipped to the export area; only the exported
layers are drawn on top. Layers without those effects, and every other export path, are unchanged.

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-10 10:31:10 -05:00
Trevor BrownandClaude Sonnet 5.5 4201fed27d M9: .jam import: stickies come in as native board stickies (#1808)
On a FigJam whiteboard the mapper builds a sticky with complete generated records as a native board sticky: the saved colour, size, place, opacity and shadows, the note and the author line as text layers, no background vector. It writes a board field on the REST JSON, which the REST importer reads (junk values are ignored with a warning). Damaged stickies and stickies in a .fig keep the generated layers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 briefed; Sonnet 5.5 wrote at medium
2026-11-10 09:17:19 -05:00
Carbocode 67c232eab2 M1: strengthen page transparency and sidebar review tests 2026-11-10 12:50:16 +01:00
Trevor BrownandClaude Sonnet 5.5 6d128d3377 Cursors: the crosshair is drawn dark with a white edge, so it shows on a white frame (#2198)
Cursor::Crosshair was the system's crosshair, whose colour follows the pointer style and
was white on Windows, lost on the default white frame. It is now Glyph::Crosshair, painted
from lines like the pen: a plus with 2 px open at the centre, a 3 px white edge under 1 px
dark lines. Prototype connections still set the system crosshair directly.

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-10 02:48:54 -05:00
Denis GomesandClaude Opus 5.5 c82986b26c M8: zoomed-out images draw from small copies that stay decoded
The canvas drew an image shown smaller than its pixels from a 4096 or 8192 px
copy, and an image of 4096 px or less from itself, whatever its size on screen.
A page of a few hundred photos zoomed out then asks the decoded-image cache
(512 MB) for several GB: every render decoded them again, and a zoom out took
seconds with the canvas empty around the old frame.

The copies now also come at 256, 512, 1024 and 2048 px, the smallest that holds
the size shown. Measured on a file with 237 images (median 5.8 MP), canvas
options, 2352 x 2090 view, M4 Max:

| zoom | before | after, first | after, again |
|---|---|---|---|
| 10% | 0.25 s | 0.12 s | 6 ms |
| 4%  | 0.88 s | 0.46 s | 7 ms |
| 1%  | 3.4 s  | 0.9 s  | 4 ms |

Peak memory over the run went from 3.9 GB to 2.3 GB. Exports and views at
100% and closer still draw from the image itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:39:52 -05:00
Trevor BrownandClaude Opus 5.5 30767bd2b2 M3: the eyedropper picks a magnified image's pixel under the pointer (#2189)
The eyedropper rendered its 1x1 sample with default render options, without the canvas's
magnified-image pixels, so on an image shown past 2x it took a bicubic blend of a 1 px line and
its neighbours: the reporter's #3D444D border beside a darker background came out darker. The
sample also began at the pointer instead of being centred on it, half a screen pixel off. It now
renders as the canvas does (magnified_image_pixels) and centres the pixel on the pointer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 found the cause from the reporter's values and wrote; Sonnet 5.5 investigated the flat cases at medium
2026-11-09 18:53:03 -05:00
Trevor BrownandClaude Opus 5.5 21d0594614 M3: the Selection colors rows agree on library styles and qualify the one-colour case (#2191 review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 checked the findings and wrote
2026-11-09 18:38:13 -05:00
Trevor BrownandClaude Sonnet 5.5 dadd4dfc69 M3: Selection colors shows for a layer with children from one colour (#2188)
Measured in Figma web app 2026-11-09: a selection holding a layer with children (frame, group, component, instance) shows Selection colors from one distinct colour; layers without children need two. The section now shows when there are two colours, or one colour and a selected layer with children. Tests in selection_colors_tests; ui-parity updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 measured in Figma and wrote at medium
2026-11-09 16:50:45 -05:00
ljkx 33ac1d14fc M1: Cmd/Ctrl+C and Cmd/Ctrl+V copy and paste the highlighted fill, stroke or effect (#1494)
With a fill, stroke or effect row highlighted, Cmd/Ctrl+C (or Edit ▸
Copy) copies that paint or effect instead of the layers, through the
same `fill.copy`, `stroke.copy` and `effect.copy` the row's menu runs.
Cmd/Ctrl+V then pastes it onto every selected layer with `fill.paste`,
`stroke.paste` or `effect.paste`, one undo step: on top of each layer's
list, or in place of the highlighted row when it is of the same kind.

- A copy keeps its kind. A stroke goes to the strokes and an effect to
  the effects whatever row is highlighted, and the last row copied is
  the one pasted.
- A solid colour goes to the system clipboard as its hex code, anything
  else as a marker. A paste takes the property only while the system
  clipboard still holds that copy and no layers were copied since, so
  text or an image from another app, or a later layer copy, pastes as
  before. The row menus' Copy writes the same, so Cmd/Ctrl+V pastes it.
- A programmatic copy never writes the system clipboard, and forgets a
  property mirror it replaced. A mirror whose engine copy is gone pastes
  nothing rather than the layers copied before it.
- The copy is shared by every window, as the layer copy is.

Tests: property_rows_tests (15 more, 22 in all); the replace index and
the Escape key were each removed once to check the tests fail.
2026-11-09 20:34:32 +00:00
Trevor BrownandClaude Sonnet 5.5 e415a3a0fb M2: pin the leading-edge drag reorder at its boundaries, zero delta and several layers (#2142 review)
Tests for the -129 leftward boundary, the vertical boundaries both ways, a drag with no move along the flow, and several moving layers using their combined box. Doc comments say whose edge is "leading" and what the caller passes; the parity row names the unchanged cases and the web-app measurement source.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 triaged and briefed; Sonnet 5.5 verified and wrote at medium
2026-11-09 15:16:36 -05:00
Trevor BrownandClaude Opus 5.5 499717d6a0 M7: the variant connection docs name the grouped-set case and target_at's None cases (#2161 review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 checked and wrote
2026-11-09 14:32:49 -05:00
Trevor BrownandClaude Sonnet 5.5 bb6d1c35ee M12: a board table's height change goes into its rows (#2172)
Resizing a board table by its bottom edge, a corner or a height command left it at a fixed height, so its rows stayed at 40 with blank space below them (or were clipped when shrunk) and typing in a cell no longer grew the table. Measured in FigJam's web app (2026-11-09): a table has no lock-proportions control and never scales proportionally; a bottom edge grows only the last row, a corner drag shares the change equally over every row and column, text keeps its size, and a cell has a minimum size.

board_table::settle_resize runs first in the normalize pass (so a drag, node.set, transform.resize, MCP and live-session peers all agree): a table left at a fixed height gets the change in its rows' minimum height (the last row, or every row when both axes changed), is kept at least columns x 40 wide, and hugs its rows again. New cells get a minimum width of 40. FigJam's floor is about 52 and FigCraft's is 40; there are no per-column widths yet.

Docs: boards-parity Tables row, revision history and status line; the board_table module doc.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Fable 5.1 (work seat) scoped; Sonnet 5.5 measured in FigJam and wrote at medium
2026-11-09 14:02:07 -05:00
skylar taylor-barrick a2e87433d8 Naming: Developer Mode follow-ups (#2165)
* Naming: record Developer Mode in the feature-names doc and the stage paragraph

docs/feature-names.md gains the entry for the rename: the old label, the new one, Figma's
US registration for DEV MODE (7574348) as the reason with its TSDR record, the identifiers
that keep their names, and the translations that moved from a generic "dev mode" to the
developer mode. The stage paragraph in ROADMAP.md listed the mode as Dev Mode.

* Actions palette: put each search doc comment back on its function

search_text had grown a second doc line under matches's, so the sentence about the query sat
above a function that takes none. search_text now says what it reads and matches keeps its own.
2026-11-09 15:44:02 +01:00
Trevor BrownandClaude Opus 5.5 ff3e4e6010 M12: sticky growth checked with a measured font; the normalize note drops sticky size (#2164 review)
The growth test scans the system fonts, as the other text-measuring engine tests do (#333), and
asserts the note grows taller unconditionally instead of only when text happened to measure.
docs/boards-parity.md no longer lists a sticky's text size among what the normalize pass derives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 checked the findings and wrote
2026-11-09 09:38:56 -05:00
Trevor BrownandClaude Sonnet 5.5 1ee3087bc9 M12: a sticky's text keeps its size and the note grows taller (#2157)
FigJam's sticky text is 18 (Small) and never changes as the note fills; the note keeps its width and grows taller (measured in the web app, 2026-11-09). A new sticky's text is STICKY_TEXT_SIZE (18) and the normalize pass no longer resets a sticky's text size by its length, so saved stickies keep the size they have. No size menu yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 measured in Figma and wrote at medium
2026-11-09 08:46:12 -05:00
Trevor BrownandClaude Sonnet 5.5 5c50025270 M7: a connection dropped on a sibling variant creates Change to (#2158)
A connection dragged from a variant (or a layer in it) onto a sibling variant of its set now creates Change to that variant (On click, Instant) and outlines the sibling while dragging, as measured in Figma. The set background and the variant itself add nothing; a frame outside the set still gets Navigate to.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 measured in Figma and wrote at medium
2026-11-09 08:37:11 -05:00
Ragulsundaram 50a76d0914 i18n: Tamil (ta) interface catalog (#2159)
Adds ta.toml with every UI string, command label and menu wording, marked draft.
Desktop draws Tamil with an installed face (#2018); the web picker leaves Tamil out
until craft-fonts has Noto Sans Tamil, like Georgian and Arabic, with a test.
docs/translating.md gains Tamil's entry, typography notes and a Tamil draft section.
2026-11-09 18:45:27 +05:30
Carbocode 481b6831cc M1: composite translucent pages over a themed checkerboard 2026-11-09 11:21:38 +01:00
Carbocode a45d932a34 M1: reuse the sidebar paint field for page backgrounds 2026-11-09 11:21:38 +01:00
Trevor BrownandClaude Opus 5.5 6856437479 M8: the Scale panel keeps FigCraft's origin; the centre default is a follow-up (#2146)
The panel set the origin to the centre once per app. The origin is the Scale tool's drag pivot
too (scale_factor), so that also changed handle drags, unmeasured against Figma, while the
engine and its tests keep top-left. This PR keeps to the focus and the Enter and Escape
endings the issue asks for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-09 03:03:26 -05:00
Trevor BrownandClaude Sonnet 5.5 92145e6f8d M8: K focuses the Scale field, so K, 2, Enter scales by two (#2146)
The Scale tool now opens its factor field focused with its text selected (once per activation), the origin starts at the centre, and Enter or Escape in the field returns the tool to Move with the selection kept. Measured in Figma web, 2026-11-09. Docs: ui-parity Scale tool row and revision.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 measured and wrote at medium
2026-11-09 02:59:53 -05:00
Trevor BrownandClaude Sonnet 5.5 6fce958ae6 M8: Preferences > Invert zoom direction reverses wheel zoom (#2145)
Adds the invert_zoom preference and the pref.invertZoom command, a checkbox in the Preferences window after Scroll wheel zoom. With it on, Cmd/Ctrl+wheel and a plain mouse wheel (with Scroll wheel zoom on) zoom the other way by the same factor; a pinch and panning are unchanged. Documented in docs/ui-parity.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 measured and wrote at medium
2026-11-09 02:53:44 -05:00
Trevor BrownandClaude Opus 5.5 7e2752d577 M4: a text style made from a whole layer counts a trailing empty paragraph's wrap style (#2094 review)
`paragraph_style` compared the paragraphs `para_span` gives for the whole text, which leaves
out an empty paragraph after a final newline, so Balance paragraphs over an Auto trailing one
made a Balance style. It now compares every paragraph by index, as the list style does. The
parity row says the wrap styles are lost through a .fig round trip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-09 01:34:38 -05:00
Trevor BrownandClaude Sonnet 5.5 2839497d62 M8: Shift+T picks the Annotation tool, which opens a layer's note in the Inspect tab (#1840)
Adds ToolId::Annotation and tool.annotation {id?}. With a layer selected (or an id) it asks the UI for that layer's note at once; in the tool a press on a layer selects it and asks, a press on empty canvas does nothing, and the engine never writes the note. annotation.new shows the Inspect tab (not a toggle) and focuses the layer's new-note field once. The tool passes Developer Mode's gates and stays selected until V or Escape (inferred; Figma's placement, cursor and stay-in-tool behaviour are unmeasured). Docs, shortcut counts (137 documented, 136 bound) and tests updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.1 briefed; Sonnet 5.5 wrote at medium
2026-11-09 01:21:43 -05:00
Trevor BrownandClaude Sonnet 5.5 cf567c3f63 M2: a child dragged in auto layout passes a sibling when its leading edge reaches the sibling's middle
Within its own row or column, a dragged child's slot follows the box it would fill under the pointer (start bounds plus the unsnapped drag), by the edge facing the drag, not the pointer. Measured in Figma's web app, 2026-11-09. Dragging in from outside, wrapping layouts and grids keep their rules. Several moving layers use their combined box.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 measured and wrote at medium
2026-11-09 00:51:03 -05:00
Trevor BrownandClaude Opus 5.5 046238d392 Merge main into feat/wrap-style-paragraph-and-style-1827 (#2135 Developer Mode)
docs/ui-parity.md conflicted on the wrap style and text direction rows: kept this branch's text with #2135's Developer Mode naming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-09 00:35:59 -05:00
skylar taylor-barrick 3839fe4a0c Naming: use Developer Mode instead of Figma's Dev Mode (#2135)
Figma holds a US registration for DEV MODE (US 7574348). Display text only: the view.devMode label, Inspect tab, shortcut row, refusal messages, CLI help, docs and all locale catalogs now say Developer Mode. Command ids, preference keys and saved fields are unchanged; "Dev Mode" stays a search alias in the Actions palette. Adds a naming guard test.
2026-11-09 06:24:44 +01:00