The paste handler read the system clipboard synchronously (2 s timeout) even
though the paste event already carries that text, and discarded the read's
result. On the desktop, the clipboard bridge delivers `event_text` that it read
while the window thread was free; re-reading from the paste handler blocks that
same window thread - and when the app itself owns the clipboard (a self-paste),
its own data source can only be served by that very thread, so every self-paste
froze the window for the full 2 s read timeout on Linux/Wayland.
Prefer the event's text and only fall back to the synchronous read when the
platform handed over none (chords, image-only clipboard, context-menu paste).
A regression test pins that a paste event carrying text never triggers the
synchronous read.
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>
* **No blank canvas** when switching back to a document tab or to a page in a file: the image it had when last shown is drawn from the first frame while it renders again
* Images of up to four recently shown files and pages are kept; closing a tab frees its image right away
Refs: #2074
pans_and_edits_get_no_preview_and_build_on_the_kept_image failed in full lib runs with "a pan after a previewed zoom renders strips only: Full". The render kept from the zoom is stale once the global font generation changes (retained.rs prepare: last.fonts == layout_generation()), and the once-per-process system font scan that many other tests start bumps it (fontdb.rs scan_system_fonts -> clear_layout_cache). When that scan landed between the test's zoom and its pan, the pan redrew in full instead of patching. Unrelated to the clock or the threshold. document() now runs scan_system_fonts() first (a concurrent caller waits for the scan), so the generation is settled before any render. Test-only change; the shipped staleness check is intended behaviour.
Stress, full lib binary with 16 test threads: 3 of 6 runs failed before, 0 of 25 after.
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>
* fix(collab-net): never send the server key over plain ws:// to another machine
Publishing a plugin, uploading or sharing with the server's key accepted a
ws:// address for a remote host, so the reusable administrative key went
out in clear text. The key now goes only over wss://, or over ws:// to
this machine (loopback or localhost); browsing without a key is unchanged.
* address review: connect only to loopback addresses for a plain ws://localhost endpoint
A row's fill copied through the control channel, then a stroke copied by
a command, left the fill off the engine clipboard; a programmatic paste
then fell through to the layers copied before the fill. It now does
nothing, as a user's paste already did.
Tests: the programmatic case above, an effect paste replacing the
highlighted effect above the bottom one (index 1), and a property copied
in one window pasting in another while the highlight stays in its window.
* fix(ui): access preferences from home screen via shortcut and sidebar (#956)
* fix(ui): handle preferences shortcut before popup guard
* docs: add docstrings for modified functions to satisfy coverage check
---------
Co-authored-by: bflatastic <bombayapirat@gmail.com>
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.
Fill, stroke and effect rows in the Design panel couldn't be selected.
A click on a row's reorder grip (the strip left of its fields) or on its
background between its controls now highlights it in the selected-row
colour, one row at a time, as the help center's "Copy and paste
properties between layers" describes ("Click to highlight the property
on the right sidebar").
- The row is a click target under its controls (`row_target`), and the
grip of these lists also takes a click (`ListDrag::row_clicked`).
Neither is focusable, so Tab still goes from control to control.
- The highlight belongs to the selection, file and Design tab it was made
on. Escape lets it go before it deselects; so does a press on the
canvas or elsewhere in the Design panel, or a frame its row isn't
drawn in (an undo took its paint away). It follows its paint or effect
when rows are reordered or removed.
Tests: property_rows_tests (7).
* 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.
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.
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.
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.
The scaled-instance round-trip test now asserts the scaled width and height instead of printing them. text_path read the layer's raw size, so a text on a path inside a scaled instance kept the main's path length; it now uses the scaled or fitted size.
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
- A nested instance's override and derived rows (and its text property values) are written in the enclosing main's space: OverrideRow carries the factor of the layer that supplied it and View::with takes (row, scale) pairs. Test: a_nested_instances_own_rows_are_at_its_enclosing_mains_scale.
- Progressive blur startOffset and endOffset are unit-box fractions, not lengths: only offset and noiseSize scale. Test: a_progressive_blurs_positions_are_not_lengths.
- An effect or grid style takes the factor of the layer that links it (1 when an instance row swaps it), not the main's. Test: an_effect_style_takes_the_scale_of_the_layer_that_links_it.
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 high
An instance scaled with the Scale tool in Figma stores `symbolData.uniformScaleFactor`; the importer never read it, so the layers of a 128 px instance of a 256 px component came in at 256 px with the main's radii, strokes and spacing. Layers of an instance now take the main's lengths times the factor (composed through nested instances), the same values the Scale tool scales, unless an override or derived row gives the value, which is already at the instance's scale. Eight tests: the reproducer, the round trip, a derived size row not scaled twice, override rows, auto layout gap/padding/text, nested composition, factor 1 or none, and junk factors ignored.
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 high