On Windows a socket's read timeout counts whole milliseconds, so a read
bounded by the hello deadline can time out a moment before it.
`next_message` then took the timeout for a closed connection: the client
got no "no hello in time" refusal, and the socket closed only after the
2 s linger. `Duplex::read` now reads again until the deadline has passed.
Tests:
- `connections_without_a_hello_are_told_why_they_end` (new): twelve
connections that never say hello each get the refusal. Failed 5 of 10
runs without the fix.
- `pre_hello_lifetime` notes the end of the stream on a thread of its own
instead of reading one message per 300 ms poke, where pongs queued
ahead of the close delayed it.
- `quiet_guests_stay_and_silent_connections_are_dropped`: the guest takes
only the host's keepalive. Its 600 ms idle timeout also bounded the wait
for the welcome.
- `two_files_are_hosted_and_joined_independently`: the two joins race, so
the file whose welcome arrived last is in front. The test checks that,
then brings B to the front.
- `a_local_share_is_joined_by_its_code_and_edits_sync`: once the share
stops, another program's host can take the freed port and refuse the
code.
* security: linear vector import, stroking and rounding, validation and a cycle check for dev snapshots
Closing a subpath, rounding corners and chaining stroke runs each rescanned the whole
network per step, and Document::validate searched a parent's children per child. They now
update only the current subpath's last segment, rebuild each region loop in one pass, walk
per-vertex adjacency lists, and check edges against a once-built index. A dev snapshot whose
layers contain a cycle under the root is refused instead of reconstructed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(doc): address review findings for snapshot children and 2-segment rounded loops
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
The export setting's advanced options name the contentsOnly option
"Ignore overlapping layers" and offer Include bounding box only when
every selected layer is a text layer. A setting that already has Include
bounding box on still shows the option, so it can be turned off, and the
export still applies it. The saved keys and the contentsOnly and
includeBoundingBox params of export.add, export.update and file.export
are unchanged.
Every catalog's "Contents only" row becomes an "Ignore overlapping
layers" row.
A slice exported every layer of its page that showed in its area, whatever
its Contents only setting said. With Contents only on (the default), a slice
inside a frame or group now exports only that frame or group and the layers
in it. With it off, or for a slice on the page or in a section, the slice
exports everything in its area as before. The Export section's preview of a
slice follows its first export setting in the same way.
The player opened a prototype's Open link at once, also from an After
delay trigger with no click. The help center's Prototype actions page
says that the viewer is told they are leaving before an external
website opens, with a checkbox that skips this page on the current
device.
An Open link to an http or https address now shows a notice with the
link, Open link, Cancel and "Don't show this again". Open link opens
it; Cancel, the close button or Escape drops it. While the notice
shows, the prototype takes no keys, and the presenter's C, Shift+C and
F keys do nothing. When Open link is pressed with the box ticked, the
desktop app saves the choice with its preferences; the web build keeps
it until the page reloads. proto.linkNotice {show} turns the notice on
or off. A mail link opens at once, as before, also while the notice
shows. A second link to a website that fires while the notice shows is
dropped.
The bindings that move with the path's fills and effects are now found with list_binding, which reads `fills` as the first fill's color, as style_sets in cmd/styles.rs does.
The hover search now recurses into each child first and looks up its mask only when the child added a candidate, as layers_at in crates/doc/src/geometry.rs does.
A path linked to an effect style with no effects gave the text an empty effect list and the style link. The text now takes the path's effects only when there are any.
text.onPath, and the Design panel's Put text on path button that runs
it, kept the text layer's own fill and effects when the text went onto
a path. The help center's Guide to text says that the vector object's
fill and effects are transferred to the text layer.
The text now takes the path layer's fills, with their fill style and
variable bindings, when one of them shows, and the path layer's effects,
with their effect style and bindings, when it has any. The text's own
bindings on those fields are dropped. When the path has no visible fill
or no effects, the text keeps its own.
A server with a key rejects publishes without one, but Preferences' server
address never holds a key. Add a "Server key" field to the Share popover's
Advanced section and pass it to collab.share so a keyed server can be used
from the popover without the Publish dialog.
Co-Authored-By: Claude <noreply@anthropic.com>
Publishing uploads a whole file, and a large project on a slow link takes
the server several minutes to receive and answer. The one-shot request
connections (hosting, the publish upload, a library, a plugin directory)
waited the session idle timeout (45s), so a large publish timed out with
"no answer from the server" before the server finished. Add a dedicated
request_reply_timeout (5m) for these connections, keeping the session idle
timeout unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
Found while testing crates/pathops/src/lib.rs:352 (path_end, used by arrowhead)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Text went onto a path only with the Design panel's Put text on path
button (a text layer and a path selected together) or with text.onPath.
The Text on a path tool, in the Text flyout of the toolbar
(tool.textOnPath), shows the I-beam over a shape's or vector's path, and
a click there puts new, empty text on the path, starting at the click,
and starts typing. The text takes the path layer's place, transform,
box, constraints and effects, and its fills when one of them shows; the
path layer is removed. Ending the edit with nothing typed puts the path
back with no undo step.
The tool measures the distance on the page, so a layer scaled more one
way than the other, or shown smaller, is reached where it shows. Shapes
in front whose outline is away from the click do not hide a path behind
them, and paths that a mask or a clipping frame hides at the click are
skipped, as are locked layers and layers inside instances.
A double-click into a text layer that was just made no longer makes it
count as an existing layer, so leaving it empty records no undo step,
with this tool and with the Text tool.
Found while testing crates/engine/src/codegen.rs:112 (num)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The status line of docs/file-format-parity.md repeated eight topics,
from SVG/PDF export depth refusal (#208) to "last major". Each topic is
now listed once, and the boards entry closes its parenthesis.
The Files paragraph of the CLI usage text now names .sketch (Sketch 48 or
newer, best effort), as docs/cli.md already does (PR review).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/doc/src/text.rs:592 (TextData::replace_range, with normalize at :573)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Conflicts resolved keeping both sides: `.sketch` stays in `is_document`
(now via `OsStr::to_str`); ROADMAP and the parity, roadmap and
file-format docs keep main's headers and rows with the `.sketch` notes
and history rows added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release builds embed some families with only a few styles (Poppins
Regular, Italic and Bold), and the first font scan dropped every
installed face of such a family, so an installed Poppins SemiBold or
Medium was never offered. The first scan now keeps the embedded faces,
which still win for the styles they cover, and offers the installed
styles they lack beside them, as a rescan already did.
Closes#1246
Three parity rows still listed work that has landed on main. The
prototyping Triggers row listed delays on mouse enter, leave, down and
up as missing; they landed on 2026-10-13. Canvas & navigation's
remaining work in target-app-parity listed outline mode's Include
hidden layers and Include object bounds, which View > Outlines has.
The file-format Fonts row said missing fonts give warnings only; the
desktop app lists them in the Missing fonts window, and replacing them
is still missing.
Each file gets its status line and a revision-history row. The
percentages and hours are unchanged.
Copy as CSS, SVG, PNG and Copy link to selection move into a Copy as
submenu, placed under Paste to replace, instead of four flat rows near
the bottom of the menu.
Edit > Copy as > Copy link to selection (edit.copyLink) was only in the main
menu. Add it to the right-click menu on a layer, after Copy as PNG, labelled
with the command's own translated label.
The command now says "Link copied" when it copies, and copies nothing when
no layer is selected (it used to put figcraft://node/0 on the clipboard).
Page rows and layer rows take drags in Dev Mode again. A drag on either leaves the order as it was and shows the Dev Mode note, as a double click or the row menu already does.
samples() reversed the whole script list for a Korean UI, which put the
Bengali and Thai entries ahead of Hangul. Swap Hangul and Kana instead,
so the order is Hangul, Han, Kana, then Bengali and Thai. A test covers
the mixed case.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cs5R9irCMt9rwfhpkwxvSn
The Corner smoothing row showed "iOS: 60%" as plain text. The help
page on corner radius and smoothing says to click iOS to set corner
smoothing to 60%. The row now has an iOS button that sets
cornerSmoothing to 0.6 on the selection in one undo step, with the
hover text "Set corner smoothing to 60%".
Layer, page and frame names in Thai drew as tofu squares in the panels and
on the canvas labels: the UI loaded system faces for CJK and Bengali only.
- cjk.rs: Thai is a fifth script (U+0E00..U+0E7F). When a name, a typed text
or the UI language needs it, the desktop app adds an installed Thai face
(Noto Sans Thai, Leelawadee UI, Tahoma, Sukhumvit Set, Thonburi, Ayuthaya,
Sarabun, Loma, Garuda, Waree). English users still load nothing; the web
build leaves Thai out, like Bengali.
- locales/th.toml: machine-drafted Thai catalog (every UI string, 589
command labels, the menu wordings), marked as a draft needing native
review. i18n.rs: Thai has no plural forms.
- docs/translating.md and docs/localization-parity.md updated.
cargo test -p figcraft-ui-egui passes (662); cargo xtask i18n-report,
assets and layers are ok. cargo xtask wasm and clippy were not run
(no wasm32 target here; clippy -D warnings already fails on other files
with Rust 1.95).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cs5R9irCMt9rwfhpkwxvSn
Polygon and star outlines did not use their corner smoothing: the value
was stored, imported and exported, but `shape_path` drew their corners
as plain circular arcs, and the Design panel showed the Corner
smoothing field only for rectangles and frames. Rounded polygon and
star corners now take smoothing the way rectangle corners do. Each
corner keeps a circular arc over the unsmoothed part of its turn and
eases into both edges, and it starts (1 + smoothing) times the plain
corner's length from the vertex. When a smoothed corner does not fit
in half of its shorter edge, smoothing is reduced first, then the
radius, as for rectangles. The Individual corners button opens the
panel for polygons and stars too, with the Corner smoothing row and no
field per corner.
The Design panel's font family and font style menus offered no way to
bind a string variable; these bindings could be made only by commands
such as variable.bind. Each menu now starts with Apply variable, which
opens the variable picker on string variables for the selected text
layers. A bound family or style shows its variable's chip in place of
the menu. A click on the chip picks another variable, and its detach
button unbinds it. While a text is being edited (a caret or selected
characters), the menus have no Apply variable item and show the plain
dropdown. A pick applies to the selected characters, or to the whole
layer when no characters are selected, as before.
The variable picker opened from a style's field listed every variable of
the field's type, and the Fill and Stroke style picker's Color variables
listed every color variable, whatever their scopes. Both now offer only
variables scoped to the property, as the Design panel's pickers do. A
paint style's colors take variables scoped to a fill or to strokes, an
effect style's fields take effect variables, and a text style's fields
take the variables of their font property. Layout grid fields still take
any number variable. style.bind still binds a variable outside its
scopes, as variable.bind does.
Found while testing crates/engine/src/cmd/arrange.rs:210 (distribute, also used by arrange.packH/packV)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The style editor bound variables to paint colors, shadow colors, blur
radii and grid sizes, but a text style's fields took variables only
through style.bind. The font and font style menus now start with Apply
variable, which opens the picker on string variables, and the size, line
height and letter spacing fields show a variable button on hover for
number variables, as the Design panel's number fields do. A bound field
shows its variable's chip in place of the value, as in the Design
panel. A click on the chip picks another variable, and its detach
button unbinds it.
kurbo restarts the dash pattern at every MoveTo, so many short subpaths
with a pattern of tiny dashes before a long gap made ~1M dashes (5M path
elements) while the length-based estimate allowed them. stroke_center
now adds one pattern's length per subpath to the estimate: an upper
bound on the dashes kurbo makes.
Test: each_subpath_restarting_the_dash_pattern_counts_toward_the_limit
(5M elements before).
Reported by CodeRabbit on #1278.