* M3: glass bends with the reference tool's thick-lens profile and splits its colours over 8 steps (#1885)
The backdrop bends most a 1-unit bevel in from the edge, falls off as the
3.5th power of the way out of the curve to nothing `depth` further in, and
moves a fifth as much on the bevel itself (it fell off as the square of the
way in from the very edge). Dispersion samples each channel at 8 steps along
the bend with a triangular window, red moving furthest and blue least (it was
three samples, blue furthest), as far apart as the reference tool's: at 100%
red and blue move 0.16 of the bend apart (`Glass::DISPERSION_SPREAD`).
`Glass::max_bend` bounds red's furthest step, so the backdrop read around a
pane covers every sample, and the field margin covers the bevel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's bend stays within `max_bend`, checked on a single bright pixel too (#1885)
Review follow-up: the bend reaches `depth` × `refraction` a bevel in from
the edge, so `Glass::max_bend` drops its 0.999 and bounds it, as a test now
checks for several refractions. A single white pixel on black pins the
dispersion's triangular window, which a ramp and a flat backdrop can't tell
from one centred sample. The parity assessment records the bend, the
dispersion and how they were measured against the reference tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's light is a line on its bevel, and splay turns the bend towards the centre (#1885)
The light is a line on the 1-unit bevel added to the backdrop, as the
reference tool's: fully where the edge faces the light, none below 0.35 of
facing it and 0.6 as much on the opposite side, with a faint gradient across
the curve, 5% darker facing the light and lighter opposite. It was a glow
mixed towards white, as deep as `splay` made it, so `light_reach` goes.
`splay` turns the bend from along the edge's normal towards the way away
from the layer's centre, and no longer widens the light: files with a splay
above 0 look different. The direction to the centre is worked out in the
shape's own frame (`own_frame`, shared with the field cache), so cut renders
match the whole one byte for byte.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass bends towards its core, and rounded rectangles work it out in closed form (#1885)
The backdrop bends towards the glass's core, the shape shrunk by `depth`
and the bevel, instead of along the smoothed edge normal: round a sharp
corner the bend turns smoothly instead of meeting along the diagonal, and a
part thinner than twice that (a capsule, a letter's stroke) bends across
itself towards its own middle, pixels about as deep as any within 2 px. The
fields keep a bend direction beside the normal (an exact feature transform
from the core, box-blurred once), their key includes the core's depth, and
their margin covers the core.
Rectangles, frames, components, instances and sections whose corners aren't
smoothed, under a rotation, flip and even scale, work their glass out in
closed form (`AnalyticRect`): signed distance, the light's normal (corners
rounded to at least the bevel) and the bend direction (corners rounded to
the core) per pixel, in the layer's own frame, with nothing to rasterise,
transform or keep. A zoom frame draws in 40 ms instead of 54 and a resize
frame in 46 instead of 67 (`glass_render_timings`, release, 2×).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: cut renders of glass match with splay turning a dispersed bend (#1885)
Review follow-up: the cut-versus-whole test had splay only on looks without
refraction, so no turned bend reached the dispersion. A look with splay 1 and
the default bend and dispersion now renders the same however it is cut. The
parity assessment records the light on the bevel and splay's new meaning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M8: a glass too big to keep bends along its normals, and the core's fields take less memory (#1885)
Review follow-up: a shape that doesn't use the closed form and whose whole
window at the zoom, with the core's margin, would pass `MAX_SHAPE_PX`
(a large vector, text or smoothed rectangle, or a deep glass far zoomed in)
bends along its edge normals instead of working out a core, with the plain
margin. The choice depends on the shape and the zoom alone, so every band and
patch of a render agrees, and no render works out a core over more pixels
than a kept shape holds. The core's feature transform keeps `u32` indices
(16 bytes a pixel) and drops its maxima and core before making the
directions.
The edge normals are box-blurred twice again and the bend directions once,
as their docs say: the two were swapped.
The parity assessment records the bend towards the core and the closed form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's flat inside skips the bend's curve, and full dispersion stays within `max_bend` (#1885)
Review follow-up: `bend_at` returns 0 straight away past `depth` + the
bevel, where the curve and the bevel's easing are both 0, instead of
working out a power of 0 for every pixel of a pane's flat inside. A test
moves one white pixel along a row under every bend of the profile at full
dispersion and checks that no channel ever reads it further out than
`Glass::max_bend`, which sizes the backdrop read around a pane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's light shows over a transparent backdrop, and its flat inside skips the light (#1885)
Review follow-up: the light was added to the backdrop's colour and then
clamped to the backdrop's alpha, so with nothing under the glass (an
export of the pane alone) the line on the bevel vanished, where the old
glow raised the alpha and showed. Now the alpha rises as far as the lit
colour needs (`lit`), and stays the backdrop's wherever the light fits
under it, so renders over an opaque backdrop don't change.
Past `depth` + the bevel the rim light, the gradient and the bend are all
0, so those pixels take one sample of the backdrop and skip the light's
direction, facing and splay work, as the old glow's `reach` early-out did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M8: a kept glass shape works out only its bend again when the depth changes, and a core gets a wider margin (#1885)
Review follow-up. The field cache was keyed on the core's exact depth, so
every step of the Depth slider missed it and worked the whole shape out
again over a window twice as wide: coverage, distances, normals, the
feature transform and the blur, every frame, for every shape that isn't a
plain rounded rectangle. Only the bend directions depend on the depth. The
kept fields (`EdgeFields`: coverage, distances, normals) are now keyed
without it, and hold the bend directions for the last two depths asked
for beside them, so a new depth works out only those (`EdgeFields::bends`).
Two glass layers of one shape at different depths each keep theirs.
The margin a core takes was `reach` + 2 px past the plain one, but the
nearest core pixel can be √2 × `reach` away round a sharp corner, past
what that covers once `reach` is over about 91 px. It is now
1.5 × `reach` + 2 px past the plain margin. The cut-through-the-pane test
gets a 60-unit-deep look (a core 122 px in at 2×).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Glass: splay's doc says splay 0 bends towards the core
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Oleksii Sukholita <oleksii.sukholita@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* M3: glass bends with the reference tool's thick-lens profile and splits its colours over 8 steps (#1885)
The backdrop bends most a 1-unit bevel in from the edge, falls off as the
3.5th power of the way out of the curve to nothing `depth` further in, and
moves a fifth as much on the bevel itself (it fell off as the square of the
way in from the very edge). Dispersion samples each channel at 8 steps along
the bend with a triangular window, red moving furthest and blue least (it was
three samples, blue furthest), as far apart as the reference tool's: at 100%
red and blue move 0.16 of the bend apart (`Glass::DISPERSION_SPREAD`).
`Glass::max_bend` bounds red's furthest step, so the backdrop read around a
pane covers every sample, and the field margin covers the bevel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's bend stays within `max_bend`, checked on a single bright pixel too (#1885)
Review follow-up: the bend reaches `depth` × `refraction` a bevel in from
the edge, so `Glass::max_bend` drops its 0.999 and bounds it, as a test now
checks for several refractions. A single white pixel on black pins the
dispersion's triangular window, which a ramp and a flat backdrop can't tell
from one centred sample. The parity assessment records the bend, the
dispersion and how they were measured against the reference tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's light is a line on its bevel, and splay turns the bend towards the centre (#1885)
The light is a line on the 1-unit bevel added to the backdrop, as the
reference tool's: fully where the edge faces the light, none below 0.35 of
facing it and 0.6 as much on the opposite side, with a faint gradient across
the curve, 5% darker facing the light and lighter opposite. It was a glow
mixed towards white, as deep as `splay` made it, so `light_reach` goes.
`splay` turns the bend from along the edge's normal towards the way away
from the layer's centre, and no longer widens the light: files with a splay
above 0 look different. The direction to the centre is worked out in the
shape's own frame (`own_frame`, shared with the field cache), so cut renders
match the whole one byte for byte.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: cut renders of glass match with splay turning a dispersed bend (#1885)
Review follow-up: the cut-versus-whole test had splay only on looks without
refraction, so no turned bend reached the dispersion. A look with splay 1 and
the default bend and dispersion now renders the same however it is cut. The
parity assessment records the light on the bevel and splay's new meaning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's flat inside skips the bend's curve, and full dispersion stays within `max_bend` (#1885)
Review follow-up: `bend_at` returns 0 straight away past `depth` + the
bevel, where the curve and the bevel's easing are both 0, instead of
working out a power of 0 for every pixel of a pane's flat inside. A test
moves one white pixel along a row under every bend of the profile at full
dispersion and checks that no channel ever reads it further out than
`Glass::max_bend`, which sizes the backdrop read around a pane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's light shows over a transparent backdrop, and its flat inside skips the light (#1885)
Review follow-up: the light was added to the backdrop's colour and then
clamped to the backdrop's alpha, so with nothing under the glass (an
export of the pane alone) the line on the bevel vanished, where the old
glow raised the alpha and showed. Now the alpha rises as far as the lit
colour needs (`lit`), and stays the backdrop's wherever the light fits
under it, so renders over an opaque backdrop don't change.
Past `depth` + the bevel the rim light, the gradient and the bend are all
0, so those pixels take one sample of the backdrop and skip the light's
direction, facing and splay work, as the old glow's `reach` early-out did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Oleksii Sukholita <oleksii.sukholita@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* M3: glass bends with the reference tool's thick-lens profile and splits its colours over 8 steps (#1885)
The backdrop bends most a 1-unit bevel in from the edge, falls off as the
3.5th power of the way out of the curve to nothing `depth` further in, and
moves a fifth as much on the bevel itself (it fell off as the square of the
way in from the very edge). Dispersion samples each channel at 8 steps along
the bend with a triangular window, red moving furthest and blue least (it was
three samples, blue furthest), as far apart as the reference tool's: at 100%
red and blue move 0.16 of the bend apart (`Glass::DISPERSION_SPREAD`).
`Glass::max_bend` bounds red's furthest step, so the backdrop read around a
pane covers every sample, and the field margin covers the bevel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's bend stays within `max_bend`, checked on a single bright pixel too (#1885)
Review follow-up: the bend reaches `depth` × `refraction` a bevel in from
the edge, so `Glass::max_bend` drops its 0.999 and bounds it, as a test now
checks for several refractions. A single white pixel on black pins the
dispersion's triangular window, which a ramp and a flat backdrop can't tell
from one centred sample. The parity assessment records the bend, the
dispersion and how they were measured against the reference tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: glass's flat inside skips the bend's curve, and full dispersion stays within `max_bend` (#1885)
Review follow-up: `bend_at` returns 0 straight away past `depth` + the
bevel, where the curve and the bevel's easing are both 0, instead of
working out a power of 0 for every pixel of a pane's flat inside. A test
moves one white pixel along a row under every bend of the profile at full
dispersion and checks that no channel ever reads it further out than
`Glass::max_bend`, which sizes the backdrop read around a pane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Oleksii Sukholita <oleksii.sukholita@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* M3: glass effects take variables, and their frost stays within 0–100 (#1885)
Glass's light angle, intensity, refraction, depth, dispersion, frost and
splay bind to number variables, on a layer (`variable.bind`,
`effects/<i>/<field>`, `radius` being the frost) and in an effect style
(`style.bind`), and the Design panel's glass fields take a variable like
the shadow and blur fields. A variable reads as the field shows it:
intensity, refraction, dispersion and splay in percent.
A glass's frost stays within 0–100, the reference tool's range: a larger
frost from an older file draws at 100, and the field, a bound variable and
scaling stop there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: a frost from an older file scales and shows as it draws (#1885)
Review follow-up: a frost saved past 100 draws at 100, so scaling a layer
starts from that (150 halved draws at 50, not 75) and the Design panel's
Frost field shows it. The revision row's binding path escapes its pipes, so
the table keeps three columns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M3: Dev Mode and .fig export show a frost from an older file as it draws (#1885)
A frost saved before the 0–100 range draws at 100, and the canvas, CSS and
scaling already read it that way, but Dev Mode's Glass row and the .fig
export still read the stored number, so they said 150 where the canvas
shows 100. Both now read the sanitized frost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Oleksii Sukholita <oleksii.sukholita@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* M1: dragging a gradient handle on the canvas keeps the colour picker open (#1852)
A press on one of the open gradient's handles or stops closed the picker: the canvas took the press
and began the drag, then the picker, drawn later in the same frame, saw a press outside its card and
closed, committing the drag's interaction at once. The handles vanished with the picker and the drag
went on without an undo step of its own. The handles are the picker's controls: while a gradient
handle or stop drags (`handles::gradient_dragging`, the press included) the picker doesn't close on
an outside press, as it already doesn't while the eyedropper samples. A press elsewhere on the
canvas still closes it. Docs: ui-parity.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M1: a handle drag released off the canvas ends there (#1852)
Review: a gradient handle drag whose button went up over a panel, over the colour picker or outside
the window stayed stashed, its interaction uncommitted, until the pointer was back on the canvas,
and the next press there moved the handle to wherever it landed; with the picker now held open while
a gradient drag is stashed, it would also have stayed open meanwhile. `handles::end_released_drag`
commits and clears a stashed drag when the button is up, called from the canvas input's off-canvas
branch and when the pointer is over something drawn above the canvas. Test:
`a_gradient_drag_released_off_the_canvas_ends_there` (released over the sidebar and over the picker).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M1: a handle drag ends on a release over a ruler, and when Escape or Undo closes its gesture (#1852)
The release check ran only where the pointer was not on the canvas, after the
comment pins and ruler guides had their turn; both return early, so a gradient
handle released over a ruler stayed dragged, uncommitted, until the next press.
It now runs first, wherever the pointer is, and the frame it ends a drag on is
the handle's, as the release on the handle itself was.
Escape (the picker closes and commits) or Undo can also close the gesture while
the button is still down. The stashed drag then kept setting the fill every
frame with no gesture open, one undo step a frame; with no gesture open the
drag is now over.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* M4: wrap style on a text layer: Auto, Balance and Pretty decide where its lines break (#1827)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Docs: no table pipe inside the wrap-style row's code span
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Wrap styles: no new emergency breaks, hanging punctuation in Pretty's width, wrap style as an override
Balance and Pretty reject a re-break with more mid-word breaks than the greedy lines; Pretty narrows a line by its text less its hanging marks; wrapStyle joins the textStyle override group; docs wording. Tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M8: a click with the Bend tool curves a sharp point, its handles along the line between its neighbours (#1875)
A release without movement on a sharp point between two segments runs ops::auto_smooth instead of cancelling; a curved point still sharpens, a drag still sets the handles by hand, an endpoint is left alone. Tests; docs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Vector edit hit test: the nearer of a handle's end and a point wins
A short path's handles end within the point's hit tolerance; a click on the point is on the point, one on the handle's end still takes the handle. Test short_handles_leave_their_point_clickable.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Vector edit hit test: the nearer of a handle's end and a point is judged on the page
Candidates stay within the tolerance as before; ranking by page distance keeps a non-uniform scale from favouring the wrong one. Test a_handle_and_a_point_are_compared_on_the_page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vector edit hit test: a handle wins a tie with a point, as before
A handle snapped onto another point stays selectable there. Test a_handle_on_another_point_is_still_the_handles (fails with the strict comparison).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M7: Minimize collapses the navigation bar and both sidebars, the right one back while something is selected (#1835)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M7: a narrow window ignores Minimize, and a rename started while minimized waits without repaints
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M2: a child resized to the full available space of its auto layout frame becomes Fill container
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Auto layout: a child becomes Fill for the full space only across the flow or alone along it
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Auto layout: the full available space is what layout leaves (padding and an included inside stroke), judged after a new path's size
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M2: a rotated child resized to the frame's room stays Fixed (review)
CodeRabbit on #1859: a rotated layer's local size isn't the room layout
gives it, so Fill would squash its geometry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M2: a double-click on an auto layout edge sets Hug contents, with Alt Fill container (#1838)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Auto layout edges: a text child fills with Alt too, and still enters on a plain double-click
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M4: superscript and subscript are synthesised where the font has no glyphs for them (#1828)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Text: a zero OS/2 script offset counts as unset, and the native-glyph check sees the case-mapped characters
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M4: max lines on auto-width text keep it auto width, hugging that many lines
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M4: an auto-width text with max lines hugs the ellipsis on its last line, and the editing test expects auto width
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M4: Backspace or Delete at a list item's start keeps its indentation level
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Lists: the level is kept by Backspace and Delete alone, and the tests read the text's own glyph
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M2: fill container children share the free space by their content area, border-box fashion (#1824)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Layout: rustfmt's line for the frozen-share closure
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M2: one doc comment on distribute (review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M4: bulleted lists keep the same bullet at every level of indentation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* list_markers: the comment says bullets are the same at every level
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M4: typing 1) and a space starts a numbered list, as 1. does
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Parity: the Text area row names the list creation characters
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Parity: the list creation characters without spaces inside the code spans
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Shortcuts: Cmd/Ctrl+Shift+Alt+= / - / 0 make the interface larger, smaller and default-sized
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Interface size keys: live while editing text too, and the new size draws on the next frame
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* A preference set during a frame repaints this window too, so the interface size applies on the next frame
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Control channel: a preference changed by a hidden window's command repaints every window
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Shortcuts: Cmd/Ctrl+Alt+arrows rotate a degree at a time, 15 with Shift
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* The rotate gesture's label in the English catalog
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Rotate keys: about the selection's rotation origin, not while editing points, Dev Mode refusal on the control path too
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M3: Delete on a layer inside an instance hides it instead of being refused
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* The components structure test: Delete inside an instance hides
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* M4: a link on text is underlined by default, and Underline takes it off
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* text.link: only a new link takes the default underline; Underline reads the texts inside a target
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Shortcuts: the Layers panel opens with Ctrl+1 as well as Alt+1 on Windows and Linux
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Parity note: in the browser Ctrl+1 is the browser's tab key
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* M8: .fig import: a routed connector takes its line style from the generated record (#1807)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M8: .fig import: a routed connector keeps its own empty paints and dash pattern
CodeRabbit review on #1813: an explicit empty strokePaints (no stroke) or
dashPattern (solid) on the connector no longer takes the generated record's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M3: library analytics list how each style and variable is used
A file reports how many of its layers use each of a team library's styles and
variables, and Show usage lists them by a Type choice with their layers and
files. Layers in instances count for their component. Also ignores plugins/target.
* M3: the style analytics test waits for the instance report too
A widget description can have a line, 100 long and black and 1 thick unless told
otherwise, and an SVG that comes in as locked layers, scaled to fit a size and
within the widget's 256 layers. The Counter example uses both.
Both reports were checked by running them and are already fixed on main. Pins a rounded rectangle or frame stroking only the side asked for in every alignment, and SVG font families and ids leaving out characters XML forbids.
The desktop shell swallows a plain Cmd/Ctrl+V, press and release, and sends an
image paste event; FigCraft ignored it and waited for a release that no longer
comes. The browser's paste event gives eframe only text, so the page now reads
the image from it, under the same limits.
Bound variables with an iOS or Android code syntax are named in generated SwiftUI and Compose in place of their value: paints, stroke weight, sizes, corner radius, gaps, padding, opacity and text. A variable with none for the platform keeps the value.
Choosing Image shows a checkered placeholder and Upload from computer instead of a dialog at once; an image paint shows its asset preview. A file dropped on the open picker replaces the image, keeping the fill mode, crop, position and adjustments (image.replace, one undo step; paths capped as image.place's). The browser's file picker reaches the tab too. Also fixes a race in library_detach_tests.
Gap 10: a file counts the library instances it places, creates, pastes or duplicates and reports them like its detaches. The server counts what a file's totals grow past their highest in the current week and keeps a year of weeks (actions.json). library.usage {days} and Show usage give insertions and detachments over the past 30 days, 90 days or year. New variants are appended; an older server refuses them and Show usage says so.