Review on #1309 pointed out that core-text 20.1's get_descriptors retains
the array it gets under the create rule a second time, so every scan kept
one array of font descriptors alive. Checking that turned up a larger leak:
Core Text autoreleases objects while it builds the collection, and the scan
runs where no autorelease pool is active (the app's background scan
thread, the CLI, the MCP server), so nothing ever drained them.
Measured with `figcraft-cli run` of N `text.rescanFonts` steps on macOS 15
(peak memory footprint):
| | 10 rescans | 200 rescans |
|---|---|---|
| main, without this PR | 221 MB | 220 MB |
| this PR before the fix | 228 MB | 796 MB |
| with the fix | 226 MB | 225 MB |
A standalone Swift loop over the same Core Text calls grows from 9 MB to
759 MB over 200 scans without a pool and stays at 20 MB inside one, so the
pool is what matters; the core-text bump removes the over-retain on top.
- The query runs inside objc2::rc::autoreleasepool. objc2 0.6 is already
in the lockfile (winit's), and the function is safe, so the workspace's
unsafe_code = "forbid" still holds.
- core-text goes to =22.0.0, the first release that takes ownership of the
array correctly. It brings core-foundation 0.10, core-graphics 0.25 and
core-graphics-types 0.2 beside the older versions other crates use. 22.0
moved framework linking behind a `link` feature that is on by default,
so with default features off it is named explicitly.
A rescan takes about 0.2 s instead of 0.08 s on that Mac (266 more font
files are indexed); it only runs when asked for.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Font managers such as RightFont, FontBase and Typeface keep fonts in their
own library folders and activate them through Core Text, without copying
them into /Library/Fonts or ~/Library/Fonts. Every other Mac app lists
these fonts; FigCraft walks only the font folders, so it didn't, and a
file using one of them drew in a fallback font.
On macOS the scan now also asks Core Text for the file of every available
font and indexes the files that lie outside the walked folders. All three
scans (the first, the on-demand one and text.rescanFonts) share this, so a
font activated while the app runs is found by a rescan like a newly
installed one.
Core Text needs unsafe FFI, which the workspace forbids, so the query goes
through the safe wrappers of the core-text crate, pinned at =20.1.0. That
version builds on the core-foundation 0.9, core-graphics 0.23 and
foreign-types 0.5 the lockfile already has: core-text is the only new
package. The call sits inside catch_unwind, so anything unexpected from
Core Text leaves the scan with the font folders alone. Other platforms are
unchanged.
On a Mac with RightFont, 15 families from its library are now listed
(Geist, JetBrains Mono, Clash Display and others) that weren't before. A
full font listing went from 0.06 s to 0.20 s on that machine.
Tests: the filter that keeps files outside the font folders once, indexing
a font file found outside them, and (macOS only) that Core Text lists the
system's font files. cargo test --workspace, cargo clippy -p figcraft-text
--all-targets -D warnings and cargo fmt --check pass on macOS 15. CI
builds on Linux only, so the macOS path is checked locally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picks up the startup prompt without the glyphs a font may lack (storytold/craft-libs#343, #344): the close mark is painted and the version pill is ASCII, so apps that panic on a missing glyph no longer take the prompt down.
Part of tracking issue storytold/craft-libs#319
Picks up the update prompt fix that draws its close mark and version pill
without glyphs a font may lack, so the prompt no longer takes the app down
when a bundled face has no ✕ or →.
Part of tracking issue storytold/craft-libs#319🤖 Generated with Codebuff
Co-authored-by: Codebuff <noreply@codebuff.com>
* feat: startup update prompt modal and direct self-updating
The update flow (check at start or on request, the startup prompt, the self-update worker) is craft-update's Updater; the app only supplies the suite, its close path and its status window. The check at start is opt-in for the desktop app, an install holds back while documents are unsaved, and the Windows update helper runs before argument parsing.
Ref: storytold/craft-libs#319
* fix(updates): re-add the update hooks to the 0.37 frame loop
* fix(updates): address review feedback
Wake the UI when a manual check is queued from a menu callback; the update prompt owns the keyboard while it is open (LightCraft); Download opens through the desktop URL hook (FilmCraft); the Help menu dispatches through menus::invoke (FigCraft); print jobs and comment drafts hold an install back (WordCraft); Check for Updates is a registered command (GridCraft); an install holds back while documents are unsaved.
* fix(updates): show the prompt on Home and presenter frames and close the application, not one window
* fix(updates): add the update strings to the English catalog
* E21: require release font coverage and stabilize capture deadline tests
* E21: separate capture, IME and field-save regression phases
* M0: correct the migration qualification reference
* M-egui037: document input lifecycle and UI regression contracts
* M0: correct egui 0.37 status docs and stabilize join-code regression
---------
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
The 0.11.1 release's Web job failed: the wasm was 25,248,590 bytes, over packaging/web/package.sh's 25,165,824-byte limit. opt-level z brings it to 23,306,645 bytes (measured locally with the release's craft-fonts ref, wasm-bindgen 0.2.129 and wasm-opt -Oz).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
import-cert.sh deleted its whole private temp directory on exit, and the keychain lives there, so codesign failed with "The specified item could not be found in the keychain" in the release build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/ui-egui/src/field_math.rs:105 (Parser::factor, unit loop; end() at :109)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/component_codegen.rs:~226 (RESERVED list used by ident at :346)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/svg/src/lib.rs:97 (num, used by matrix at :129 for the image pattern's patternTransform at :243-254)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Prototype player: no clicks reach the prototype behind the link notice
* Selection colors: scopes filter the variable menu; the command binds as variable.bind does
* Style picker: color variables scoped to every selected layer's fill or stroke
* Corner button: named Corner smoothing on polygons and stars
* docs/gaps.md: known gaps from the #1311 review
* cargo fmt
* Style picker: clippy