A negative width or height was clamped to zero by the scale helper, producing a layer smaller than asked for; vector.create now says so. Regression test covers width -10 with height 8.
Review follow-up. A file written by this version carries editorOutlines, so a stroke someone chose is kept even when it matches the old decoration; only files without the marker get the one-time legacy cleanup. vector.create now errors when a requested side cannot scale the geometry or when both axes would collapse, instead of creating an invisible layer.
A path's intrinsic box replaced the width and height asked for, so an icon imported at 24 stayed on its source grid. Scale the geometry to the requested size; with one side the other keeps its proportion. A path or point set that draws nothing is an error now, not an invisible 0x0 layer.
Node::new gave every component set a purple dashed stroke, so export and screenshot drew the editor frame into the image. Sets now start with no paint; the canvas draws the dashed frame itself, and files written by earlier versions lose exactly that stored decoration when opened. A stroke someone chose is kept.
- **Quit (macOS):** `on_exit` now calls `services::shutdown` and then, on macOS only,
`std::process::exit(0)`. This happens before eframe tears the windows down, which is where the
crash in #781 is thrown (same approach as the sibling apps, tracked in craft-libs#3).
- **`services::shutdown(app, dir, recovery)`** replaces `save_state`. It runs on every platform and
everything in it is finished before it returns:
1. the preferences (`state.json`) are written atomically, as before;
2. this run's recovery copies are settled by the new `Recovery::quit(session, asked)`:
- if the unsaved-changes question was asked (`ui.quit`: last window closed through the
question, or `app.quit`), or nothing is unsaved, it works as before: `finish()` removes
them;
- otherwise the unsaved documents are copied (waiting for any copy still being written)
and the copies stay for the next start, as after a crash. This is the macOS Quit menu
(Cmd+Q): winit's menu calls `terminate:`, which reaches `on_exit` without asking. Until
now that path removed the recovery copies of files that had unsaved changes;
3. live sessions end explicitly: a session this app hosts tells everyone and stops; a joined
session is left. Before, this only happened in the app's thread-local destructor at
process exit, if at all.
- If the app can't be reached in `on_exit` (it's busy), the recovery copies are left on disk for
the next start rather than removed.
Quit paths checked:
- **Cmd+Q / the menu bar's Quit (macOS):** `terminate:` → winit `applicationWillTerminate` →
eframe `exiting` → `on_exit`. Covered.
- **Last window closed** (close button, title bar Close, Dock/Finder quit Apple event → `Close`):
`on_close_request` → `begin` sets `ui.quit` → root `Close` → `on_exit`. Covered.
- **`app.quit` (control channel / MCP):** sets `ui.quit` and closes the root window → `on_exit`.
Covered. Its reply goes to the connection thread before the window closes. The process now ends
sooner after that, so a client that reads very slowly could in theory see EOF instead of the
reply (not seen).
- **The frame guard (panic):** isn't a quit. It writes recovery copies and the app keeps running,
so it doesn't need this. A panic outside the guard aborts as before.
- **eframe error mid-run:** eframe also calls `on_exit` before `run_native` returns `Err`. On macOS
that error now only reaches the log (eframe logs it), not the "window couldn't open" box. Startup
failures still show the box: `on_exit` only runs once the app exists.
Part of #781.
From review:
- The check ran after any file.open/openRecent/import result, so a
pasted or dropped image, reopening a file that was already open, or
Clear recent brought the window back. It now runs only for results
with a `summary` (a file opened, or pages imported), and never in the
browser.
- Text > Missing fonts... before the startup scan finished listed
installed fonts as missing. It now says it is checking the installed
fonts and lists them once that is done
(`figcraft_text::installed_fonts_known`, also true when system fonts
are opted out, where checks used to wait forever).
- ROADMAP progress log and file-format-parity status line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Text in a family that isn't installed was drawn in a fallback font
without a word. Now:
- `text.missingFonts` lists the families a document names that no
installed or built-in font provides: text layers and their styled
ranges, text styles, and string variables bound to a font family
(every mode, through aliases), with the number of text layers.
- The desktop app shows a Missing fonts window after the user opens a
file that has any: once installed fonts are indexed (before that every
one of them would count as missing) and after any import report is
closed. Files named on the command line count. Programmatic opens and
the browser build never open it by themselves. Text > Missing fonts...
shows it for the file in front; Escape closes it.
- `figcraft-cli info` prints `missing font` lines (`missingFonts` with
--json).
- file-format-parity: the row claimed REST import warnings that no code
produced.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 91 UI strings, 12 command labels and 2 menu wordings the strings added since the French catalog was drafted left in English are translated in the catalog's conventions (NBSP before : ! and inside guillemets, one/other plurals, 0 singular): starter templates and the Templates tab, the home screen's recents table with its sorting and dates in the French way, dropping files to open or import, the Getting started tips with sharing by code, the Playground's bound-variable chip, smart-selection spacing, outline mode's options, the converting and panel-width commands. Appended to each section; still requires native review.
* 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>