Commit Graph
4092 Commits
Author SHA1 Message Date
ljkx 8330ffb32a M1: Crop chosen in the colour picker enters crop mode (#2150)
Choosing Crop from the image fill's mode dropdown only set scale_mode: crop
mode never started, the first press on a handle closed the picker and went
to the tool, and the picture lost its place (no placement crop). For one
layer's image fill it now runs image.crop, as Object > Crop image and the
Fill row's button do; other targets only set the mode, as before.

Fixes #2150
2026-11-09 12:44:21 +01:00
ljkx 984270fbec M8: automation roots refuse a path on another computer before resolving it (#1094)
On Windows, Roots::check resolved every path (canonicalize opens it) before
comparing it with the roots, so `\server\share\x` contacted the server, and
could send it the user's credentials, before it was refused. Now the path as
given and made absolute is classified by its spelling with craft_docpath
first: another computer that no root's share is on, a device path, or a DOS
device name anywhere in it is refused, and so is a link on the way that
leads to one (followed by its target's spelling, up to 40 links).

Fixes #1094
2026-11-09 12:38:29 +01:00
Carbocode d9f52aab1a M1: remove page background notes from the roadmap 2026-11-09 11:43:49 +01:00
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 197a390a84 M8: the Scale field refocuses after another tab, and Enter elsewhere keeps the tool (#2148 review)
The focus request was reset only while the Design panel drew, so choosing the tool again on the
Prototype tab left the field without the keyboard; it now follows the frame the section last
showed in. Whether the factor field had the keyboard was read before the widgets ran, so an
Enter right after a click into W or H could end the tool; it is read after them now. A number
field's leftover edit from when it was last shown no longer swallows the focus request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-09 03:40:27 -05: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 Opus 5.5 8b15c00f84 M8: the MCP pointer tool lists the Annotation tool (#1840)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-09 01:22:52 -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
FredApps 72cfe7be30 fix(naming): use direct FigCraft feature descriptions (#2131)
Drops the "Figma-class" self-description from the MCP instructions and the "Accessibility at Figma" aside from pref.uiScaleUp help. Help text only; ids and preference keys unchanged. Adds a display-name guard over mcp/src and engine/src/cmd string literals.
2026-11-09 06:22:48 +01:00
ljkx 0aba3f7632 M4: a Pencil stroke stays where it was drawn, without spikes off it (#1741)
kurbo's simplify_bezpath, which smooths Pencil samples and runs Simplify
path, fitted two common shapes with a curve that ran far off the path: a
stroke that goes out and back to the very point it left (kurbo keeps a
reversal as a smooth join) and nearly collinear samples (kurbo measures
its error across the path, not along it).

figcraft_geom::simplify_path cuts the path where it turns back by more
than a right angle, simplifies each run with kurbo, and keeps a run as it
was when its fit strays more than twice the accuracy from it. The Pencil
and vector.simplify use it.
2026-11-09 06:15:33 +01:00
Dylan M. Taylor 970cdfe664 Merge pull request #2136 from dotCooCoo/chore/craft-update-1b34eba41
Updates: take craft-update from craft-libs main at 1b34eba41
2026-11-08 23:03:14 -05:00
dotCooCoo 67f5e24a3f Updates: take craft-update from craft-libs main at 1b34eba41 2026-11-08 19:45:14 -08:00
Dylan M. Taylor a4251ca89e Merge pull request #2130 from dotCooCoo/chore/craft-update-updater-fixes
Updates: the updater reports installing, ready, installed and failed, and installs at exit
2026-11-08 22:12:41 -05:00
Trevor BrownandClaude Opus 5.5 92cf984902 Selection: Escape tests for a top-level layer, several layers and the focused frame (#2107 review)
One Escape selects nothing for a top-level frame, a top-level layer, two layers in one group,
layers in different parents, and a nested layer with a top-level one. With the focused frame
itself selected, one Escape leaves the focus view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-08 17:38:13 -05:00
dotCooCoo 892cda8e34 Updates: the updater reports installing, ready, installed and failed, and installs at exit
craft-update moves to a craft-libs branch that merges storytold/craft-libs#687 and #696.

- The check result window shows for every status but idle and stays
  closed while the startup prompt is open, so the first start after an
  update reports that it installed or why it failed. Restart to Update
  calls Updater::restart_to_update.
- on_exit calls Updater::start_pending_update before the process ends,
  which starts the install of an update waiting for the app to close.
2026-11-08 13:32:01 -08:00
Davit Matchakhelidze 9c2c2cb74e i18n(ka): place the new rows next to their English keys (review) 2026-11-09 00:59:12 +04:00
Trevor BrownandClaude Opus 5.5 dc841f8548 M8: vector.edit.position moves points in one pass; refusal, empty-selection and scrub tests
The handler grouped equal offsets by a linear scan per point, quadratic when every point moves
by its own offset. Only a single move covering every point carries the regions' paint frames,
so it now checks in one pass whether all offsets are equal: if so the points move as one,
otherwise each moves by its own offset. The result is the same as before.

Tests: booleans, a valid x with a refused y, and the overflow case are refused with no undo
step; nothing selected is refused; a shared Y scrubs every point in one undo step and a Mixed
X doesn't scrub. selection_position's doc comment names its one user, vector.edit.bounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-08 14:44:47 -05:00
NetterSilverandClaude Haiku 5.5 0ff3a3682e Reject negative and fractional index in file.openRecent
Found while testing crates/engine/src/cmd/file/open.rs:33 (open_recent, `.nth(i.max(0.0) as usize)`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 19:22:04 +00:00
Trevor BrownandClaude Opus 5.5 8c24cbf691 Merge upstream/main into fix/loaded-font-wins-2092
Co-Authored-By: Claude Opus 5.5
2026-11-08 12:32:40 -05:00
impulsado a6d9b74df9 M3: resolve inherited slot properties by their defining component 2026-11-08 18:23:08 +01:00
Giovanni Almeida a6be897d31 M6: a vector point without a corner radius of its own takes the layer's on .fig import (#2114)
Figma rounds every point whose style doesn't set a corner radius with the
layer's cornerRadius. The import read only the point's style, so those
vectors came in sharp. Points now fall back to the layer's radius, at the
scale of the layer that carries it (#2091), and the network cache keys on
it. The export writes an explicit 0 for a sharp point on a rounded vector
so it stays sharp.
2026-11-08 13:55:56 -03:00
FredApps 8a7ebc2484 Merge pull request #2123 from FredApps/fix/scaled-vector-corners-upstream-2097
M6: scale per-point vector radii in scaled instances
2026-11-08 16:53:44 +01:00
FredApps 041225afdd Merge pull request #2100 from TrevorBrown-dev/fix/fig-uniform-scale-factor-2091
M6: .fig import draws a scaled instance's layers at its scale factor (#2091)
2026-11-08 16:30:44 +01:00
FredApps d54439be8b M6: scale imported vector point radii with their instance row (#2097) 2026-11-08 16:10:54 +01:00
FredApps a07a21b697 M6: bound exported instance factors to the supported range (#2097) 2026-11-08 16:08:34 +01:00
Trevor BrownandClaude Opus 5.5 62e7e3a41a M8: the parity docs scope the points' X and Y to the layer being edited
CodeRabbit on #2121: with points selected in several edited layers, the X and Y fields read and set only the active layer's points (`selected_parent_points` and `vector.edit.position` use the edited layer), so the status lines, the vector edit row and the revision entries now say so, and the row lists X and Y over several edited layers under Not yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-08 09:32:09 -05:00
FredApps 23ed778562 M6: preserve scaled instance export overrides (#2097) 2026-11-08 15:23:36 +01:00
Trevor BrownandClaude Opus 5.5 194a4a1329 M8: the parity row lists the points' X and Y as done, not under Not yet
CodeRabbit on #2121: the Vector edit mode row in ui-parity.md put the new Point-section X/Y behaviour after "Not yet:", contradicting the row's done status and the revision entry. It now sits with the implemented features; only areas of other layers edited at once stay under Not yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-08 08:31:06 -05:00
Trevor BrownandClaude Opus 5.5 8edcc02aee M8: points moved by one offset still carry the regions' paint frames
Setting each point's own coordinate moved the points one at a time, so when every point of a layer moved by the same offset (`{"x": {"add": 10}}` on all of them) `ops::move_vertices` no longer saw the whole network move and left the regions' paint frames behind. Points with the same offset now move together.

Verified: `moving_every_point_by_one_offset_carries_the_regions_paint_frame` failed before this and passes; vectoredit tests 92 passed, junk sweep 49 passed, clippy and fmt clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-08 07:57:54 -05:00
Trevor BrownandClaude Sonnet 5.5 767ebb67cc M8: store the new vector point tests with LF line endings
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove, measured and briefed; Sonnet 5.5 wrote at medium
2026-11-08 07:53:27 -05:00
Trevor BrownandClaude Sonnet 5.5 6ac20c7d53 M8: points' X and Y in vector edit mode show Mixed and set every selected point, as in Figma
Cause: with several points selected, the Point section's X and Y showed the selection's top-left and `vector.edit.position` moved the group so that its top-left landed on the typed value. Figma shows the value the selected points share (Mixed when they differ) and sets every selected point's own coordinate.

Fix: `vector.edit.position {x?, y?}` takes, per axis, a number (every selected point's own coordinate becomes it, in the parent's coordinates) or `{add}`, `{mul}`, `{div}` applied to each point's own coordinate; each point moves by its own delta with its handles, in one undo step; a zero divisor, a non-finite result, unknown keys and other junk are refused. A new `selected_parent_points` gives the panel the points in the parent's coordinates. The panel's X and Y are mixed-value fields: the shared value (compared within 1e-6) or Mixed, a typed value sets every point, and a relative edit typed into a Mixed field sends the object form. A scrub only starts from a shared value and sets it absolutely each frame inside one interaction, as the other point fields do.

Verified: engine tests for two points at different x, one point, add, mul and div, curve handles, one undo, the parent's coordinates and refused junk; panel tests for Mixed, typing -200 and a relative edit on a Mixed field, and a single point's value; the engine lib tests, the vector and field panel tests, clippy and fmt. Measured in Figma on 2026-11-08.

Not changed: the W and H row, other edited layers' points, and scrubbing semantics (the Mixed field cannot be scrubbed, as no start value exists).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove, measured and briefed; Sonnet 5.5 wrote at medium
2026-11-08 07:53:14 -05:00
NetterSilverandClaude Haiku 5.5 6222fce8d1 Reject signs and exponents in ExportSize::parse
Found while testing crates/doc/src/props.rs:1394 (ExportSize::parse, `num.parse::<f64>()`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 12:22:45 +00:00
NetterSilverandClaude Haiku 5.5 10d97e2716 Reject negative or fractional index in node.reparent
Found while testing crates/engine/src/cmd/edit/props.rs:248 (reparent, `index = num(p, "index").map(|i| i.max(0.0) as usize)`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 11:00:14 +00:00
Janiz-zzzandClaude Opus 5.5 b6739cd04f i18n(fi): translate the 50 labels Finnish lacks; Past year
- "Past year" was "Viimeinen vuosi" (the last calendar year); the filter means
  the last 12 months: "Viimeiset 12 kuukautta" (CodeRabbit on #1993).
- 40 UI strings (pick select, field scrubbing, live session reconnect, text
  direction, padding, variable scopes), 7 command labels, 3 menu wordings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:33:55 +02:00
Trevor BrownandClaude Sonnet 5.5 dda27030c8 Components: the component button shows its menu arrow, Create component set is offered only where it works; tests for every refusal (#2095 review)
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-08 01:47:50 -05:00
Trevor BrownandClaude Sonnet 5.5 87ce9612e6 Vector edit clipboard: Cut and Copy with only lone points are refused; one x/y refused; the system clipboard gets the partial copy; specs match (#2105 review)
Cut with nothing to copy (only points that touch no segment) is Disabled and changes nothing, as Copy now is (Copy used to fall through to the whole layer); paste borrows the clipboard instead of cloning it; a partial copy or cut mirrors its SVG to the system clipboard; x without y (or y without x) is refused; the junk test asserts refusals change nothing and accepted results are finite; the edit.copy, edit.cut and edit.paste specs state the behaviour and the mode-specific paste response.

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-08 01:44:23 -05:00
Trevor BrownandClaude Opus 5.5 ebd463fa5f Tests: the comment says which paste cases were measured and which pins FigCraft's own branch (#2108 review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-08 01:31:31 -05:00
NetterSilverandClaude Haiku 5.5 db1bec08c8 Reject invalid layer ids in file.export and file.exportLayers
Found while testing crates/engine/src/cmd/file/export.rs:89 (export; same pattern at :332 in export_layers)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 06:31:13 +00:00
Trevor BrownandClaude Sonnet 5.5 d036eb2f9b Tests: the UI arrow-pan test at the new step; the step at other zooms and on both axes (#2106 review)
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 low
2026-11-08 00:42:50 -05:00
Trevor BrownandClaude Sonnet 5.5 ad701088a2 Vector edit: Cut deletes exactly what it copies for a selected region (#2105 review)
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-08 00:38:49 -05:00
Trevor BrownandClaude Opus 5.5 1b7ce67966 Docs: a stored width that isn't a number falls back to 240; one date; no local notes path (#2103 review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-08 00:31:58 -05:00
Trevor BrownandClaude Opus 5.5 bceb5476e0 Docs: the new history row below the table's separator (#2107 review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
2026-11-08 00:31:58 -05:00
Trevor BrownandClaude Sonnet 5.5 476daeab8e Tests: paste lands in place, at the view's centre, or in a selected frame's centre, as measured in Figma
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 low
2026-11-08 00:24:29 -05:00