* fix(ui): Rotation gets its own icon, not Corner radius's (#256)
The Rotation field showed the corner radius icon, so the two fields
looked alike. Rotation now shows an angle: two rays from a vertex with
the arc between them, drawn in code like the rest of the icon set.
Refs #256 ("The icon for rotating objects is the same as for rounding
corners").
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(ui): the angle icon is drawn unhinted and centred; its test sits with the icons (#256)
From review: the rays and the arc are both unhinted, so the arc meets
the rays at the 14 px field size; the glyph moves up half a unit to
centre it like the other leads; the test moves from a11y_tests.rs to
the icon tests.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(ui): SVG and PDF export rows offer no size (#256)
The size picker (1x, 2x, 512w…) showed on every export row, but SVG
and PDF are vector and their export ignores it (export_bytes_with). It
now shows for PNG, JPG and WebP only, and the suffix field takes its
width.
Refs #256 ("exporting to SVG is offered in different resolutions,
although this should not be the case").
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(ui): export size field as an Option; test WebP too (#256)
From review: the size field's width is an Option instead of a negative
width, and the test checks WebP as well as JPG.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Unrelated to the security changes in this branch; `main` already failed
`clippy --workspace --all-targets -- -D warnings` without them. Eight
clippy::nonminimal_bool sites across seven files, each taking clippy's own
suggested rewrite.
Every rewrite is pure boolean algebra with the negation kept outside, so the
float comparisons and their NaN behaviour are untouched: !A && !B becomes
!(A || B), and !opt.is_some_and(f) becomes opt.is_none_or(!f). Two sites needed
explicit parentheses the suggestion omitted, where the rewritten term is the left
operand of an && and || binds looser.
designcraft cleared the same lint in bc840130; this is figcraft's equivalent.
clippy::needless_borrow; `url` is already a reference. Missed on review because a
workspace clippy run aborts at the first failing crate, which was figcraft-format.
A document carries things the app acts on for whoever opens it. Three of them were
taken as given.
Prototype Open link and Dev Mode dev resources both reached the platform's opener
with any scheme. In the web build the opener is window.open, so a saved document
could run script in the app's own origin; on the desktop any scheme went to the
system's handler. Both now pass through the new engine module `doc_urls`, which
allows http, https and mailto and nothing else. It is an allow-list on purpose: a
list of known-bad schemes is outgrown by every handler a platform adds.
A figcraft:// invite link joined the session at once. The system hands the app
whatever such link was followed, which may come from a page or a message, so the
Join dialog now opens with the link filled in and the person confirms.
A library link stored in a document was loaded from wherever it pointed. Only a
path on this computer is followed now, so opening someone else's file cannot make
the app contact their server (which on Windows would also send credentials).
Tests: each change has one that fails without it, verified by reverting. The
prototype test fires through an afterDelay trigger, since no click is needed, and
keeps a positive case so a refusal cannot pass vacuously.
- Text editing on the canvas now tells the platform it wants the IME on **every** frame of the
edit. Before, it only did so on frames that brought input events.
- The candidate window opens at the caret. Before, it opened at the canvas corner: the desktop
integration places it by `IMEOutput.rect`, which was the whole canvas.
- Keys that reach the editor while a composition is in progress belong to the IME. Enter, Escape,
Backspace, arrows and shortcuts don't act on the text then, so Enter picking a candidate doesn't
add a newline.
- New test `crates/ui-egui/src/ime_tests.rs`.
- Docs: the IME rows in `docs/ui-parity.md` and `ROADMAP.md` now say it hasn't been checked on a Mac.
Fixes#747.
Escape or Enter while the pen draws ends the path and keeps its vector in
edit mode with the pen, so the next path joins the same layer; another
Escape leaves edit mode. Entering edit mode on a layer that already is a
vector no longer records an empty undo step.
Current macOS keeps PingFang and other on-demand fonts in its asset
store, which the font scan never walked, so Chinese text showed as
boxes. The scan now also walks the com_apple_MobileAsset_Font* folders
of /System/Library/AssetsV2 (and the older /System/Library/Assets) with
the same depth, size and file checks, and Hiragino Sans GB and Heiti SC
(preinstalled) join the Chinese UI faces and the text fallbacks.
Tested with a folder laid out the same way; not run on a Mac.
gridColumnsSizing and gridRowsSizing are CSS grid-template strings: px
tracks are fixed, fr tracks fill, auto/min-content/max-content hug,
minmax(_, Nfr) fills and repeat(n, ...) expands; other tracks stay equal
(1fr). Tracks past the axis's own count are left out. Files from .fig
keep reading the tracks from their source.
The format's SPACE_EVENLY is its older name for equal gaps without edge
space, so files keep reading it as space between; true space evenly is
SPACE_EVENLY_CSS, which the export now writes.
- The import report translates an importer note when the catalog has it, but 30 notes added by the
`.fig` import series (#626–#639) had no catalog entries, so they showed in English: connector
labels and snapshots, grid track sizing, image filters, slots and their content, vector segment
styles, and text on a path.
- All 30 are now in `en.toml` and in every full catalog: es, de, fr, pt-BR, ru, ja, ko, zh-Hans, cs,
id, sv and tr (30 each). `es-419` overrides only the 3 notes about settings (configuración rather
than Spain's ajustes), as the overlay does elsewhere. The wording follows each catalog's existing
terms for connector, layer, frame, stroke, grid, slot and text on path. The translations are drafts,
like the catalogs, and wait for native speakers' review.
- `every_key_in_the_code_is_in_the_english_catalog_and_no_other` now counts each importer note (the
`warn("…")` literals in `crates/format/src/fig` and `crates/format/src/rest`) as a required key, not
only as an allowed one. A new note that isn't in `en.toml` fails the check, and because Spanish is
in `COMPLETE` it has to be translated there too, so the report can't fall behind again.
`docs/translating.md` says so.
- The importers' texts are unchanged. Notes built with `format!` (layer counts, type names, field
names) still show in English, since the report matches whole notes.
Brings the progress docs up to date with what landed on 2026-10-17: community batches #727, #728, #733, #735, #736 and #753, plus #729, #745, #746 and #751. Docs only.
- A minimised or covered window now answers every control request that acts on the app's state
(`engine.execute`, `document.inspect`, `node.get`, `ui.inspect`, `ui.pointer`, `ui.key`, `ui.type`,
`ui.set`, `ui.menu.invoke`, `ui.render`, `window.list`, `window.focus`, `app.quit`), in that window's
file, from the app's `logic` (no frame drawn). Before, only six methods were answered there.
- What only a drawn frame can answer (`ui.screenshot`, `ui.input`, `window.new`) is an error at once
for a hidden window: `window 0 is minimised or hidden: `ui.screenshot` needs it on screen
(`ui.render` draws the document without it)`. Before, these waited up to 60 s.
- `crates/ui-egui/src/control.rs`: the `NO_FRAME` allow-list becomes a short `NEEDS_FRAME` list;
`drain_hidden` answers the rest and fails the frame-only ones at once.
- Docs: `docs/control-protocol.md` (hidden windows, `ui.screenshot`) and `docs/mcp.md` (attached
mode while the window is minimised). ROADMAP progress log and revision history.
Part of #720.