Queued sync messages wait in a list the session loop drains, so Leave, a lost
connection, presence and pings are never held up behind them. A text frame from
the host stays final. A session that ended stays on its tab after a Save as:
ending it there could turn a tab into a plain local file with no word about it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joining again after a session couldn't go on opened your offline changes in a
copy beside the file. They now go on top as one undoable edit, through the
three-way merge branches use. The rejoin asks to resume as the same client, so
layers made offline are yours to send. What can't go in (the same thing changed
both ways, or layers a new client can't make) leaves the server's version, and
your whole version opens beside it so nothing is lost. A server that no longer
knows you merges the same way instead of discarding unconfirmed work.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A session you joined could end for reasons that pass, or after about 85 s of
retries, and its tab then looked like an unsaved local file. Work done there
reached no one, with only a 3 s toast to say so.
A live connection now reconnects until you leave, pausing a minute after each
12 attempts. Only refusals that are for good stop it, so "too many messages",
"busy" and the like retry. Messages queued while offline go out paced so a long
queue can't trip the server's rate limit. A session that ended stays on its tab:
the tab shows a red mark and a strip across the canvas offers Retry now or Try
again, Save a copy and Dismiss. Joining again uses the same tab, and what changed
offline opens beside it as an unsaved copy. A session that gets out of step
joins again by itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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.