Commit Graph
120 Commits
Author SHA1 Message Date
BFlat 6ef899cdbb M7: download a missing free font from the Missing fonts window (#754) (#1510)
A missing family the suite's font store has (Google Fonts, Noto) gets a
Download button. The first one asks the user to allow downloading fonts,
then craft-libs' craft-font-store installs pinned, checksum-verified files
into the Craft apps' shared font folder on a thread of its own. The font
scan reads that folder; once installed the text fits the real font.

font.download / font.downloads drive the store as its agent: refused while
downloading is off, never able to turn it on. Desktop only.
2026-10-29 16:41:31 +07:00
BFlat fe7b4bac63 Merge pull request #1143 from Guillergood/feat/penpot-import-export
M6: import and export Penpot 2.x files offline
2026-10-29 14:20:14 +07:00
bflatastic 1e22268602 Merge main into #1143 2026-10-29 12:44:42 +07:00
bflatastic d8a4de1bd5 Merge main into #1360 2026-10-29 11:28:49 +07:00
BFlat 4a0bd79797 Merge pull request #1488 from ChristopherLinnett/issue/1086-image-validation
fix(collab): image entries must match their hash and sane bounds (#1086)
2026-10-29 09:49:20 +07:00
Christopher Linnett f1c2ee81e4 fix(collab): image entries must match their hash and sane bounds (#1086)
Image entries applied bytes to any key with no checks (audit COL-9): a
collaborator could poison the shared content-addressed image store, claim
absurd dimensions, balloon memory, or name an image `../../x`, which a
saved file then wrote as the archive member `images/../../x`.

- Every key must be 1–128 ASCII letters, digits, `-` or `_`:
  `figcraft_doc::ImageData::is_valid_key`, promoted from the format
  crate's private `is_valid_hash` so the file reader and collab share one
  rule. The reader now also takes `-` and `_`, so the REST importer's
  `rest-image-missing` placeholder survives a save and load.
- The REST importer's side of the same attack: an unresolved `imageRef`
  that can't legitimately be an archive member name (path text, an
  over-long ref) shares the `rest-image-missing` placeholder instead of
  being keyed by its raw name
  (`image_tests::a_path_like_rest_image_ref_never_names_an_archive_member`).
- A key with the shape of a content hash (64 hex chars) must be exactly
  the lowercase sha256 of the bytes, the form `image_hash` writes; an
  uppercase look-alike is refused. Other keys (svg FNV-128, REST
  placeholders) claim no content address and only meet the rules above.
- A side is at most 65 535 px; decoded bytes at most 32 MiB, with the
  base64 text capped before decoding. 0×0 stays valid: the format crate
  stores it for sizes it can't probe.
- Refusals are Error::Invalid and reject their batch, so every replica's
  store stays clean; catch-up snapshots apply through the same check.
- Docs: collab-protocol.md, collaboration-parity.md and
  file-format-parity.md (rows, status lines, histories).
- Tests: collab image_tests (8), format image_tests: round-trip and the
  REST placeholder gate.
2026-10-29 08:11:13 +08:00
Christopher Linnett 73398cedf1 fix(app): ask before ⌘Q quits with unsaved changes (#857)
winit's default macOS app menu ends the process through AppKit's
`terminate:` at once, so ⌘Q and Quit from the menu bar never asked about
unsaved changes (the recovery copies were all that survived).

- Install craft-libs' app menu (craft-menu, same rev as the other
  craft-libs pins; macOS only). Its Quit sends the main window a close
  request, the same path as the window's close button, so
  windows::on_close_request cancels the close and the unsaved-changes
  question asks first. Failure to install keeps winit's menu and logs it.
- windows_tests::a_quit_with_unsaved_changes_waits_for_the_answer pins
  the contract the menu relies on: a close request with unsaved changes
  sends CancelClose and asks; Don't Save then closes.
- Like the close button it closes the main window only: with several
  windows open, each quit closes one (documented).
- docs/hardware-parity.md macOS row, status line and revision history.
- Cargo.lock: craft-menu, craft-quit, craft-keymap, muda and their deps;
  cargo's own minimal resolution also moves a few windows-sys/getrandom
  edges to versions already in the lock.
2026-10-29 08:11:13 +08:00
Brandon ThomasandClaude Opus 5.5 ef9721c5b9 Release 0.12.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:37:05 -04:00
Brandon ThomasandClaude Opus 5.5 08204fd7c2 Release 0.12.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:18:40 -04:00
Guillermo Bueno 7ea161f33f M6: merge main and isolate offline Penpot import/export 2026-10-27 18:59:49 +01:00
aiongg bdb7e84606 fix(ui): dropdown menus grow back after a search with no matches (#1429)
* feat(ui-egui): font family menu keeps its search field and grows back after a filter clears

Repin craft-libs to the shared dropdown (craft-ui), move the font family menu to
craft_ui::combo::Menu with the search in a fixed header, and use craft_ui::ComboBox
for the credits sort. clippy bans egui::ComboBox.

* chore(deps): craft-libs 60d60d48 (dropdown fixes)

* chore(deps): craft-libs 4d7b82f7 (dropdown fixes)

* chore(deps): craft-libs 92d392cf (#387 merged)

* fix(ui-egui): craft-ui on every target (wasm uses the shared ComboBox too)
2026-10-27 10:28:10 -04:00
Denis Gomes a497b99078 M7: place images at their own size, in points for 2x screenshots
A placed image was always fitted into 1000 px on its longer side, so a
screenshot of a 1440 px wide page came in at 1000 px and stopped lining
up with the design it was taken from. Figma places an image at its pixel
size and only scales down a side past 4096 px.

Placing now does the same, and reads the screen density a PNG records:
macOS screenshots carry 144 dpi (pHYs), so an 800 x 600 px capture of a
400 x 300 pt area comes in at 400 x 300, as in Figma. Only whole 2x-4x
multiples of 72 dpi count as a screen density; 300 dpi print scans and
other values keep their pixel size. The image data stays at full
resolution, so exports at 2x and up stay sharp.

On macOS, pasting reads the clipboard's public.png as is instead of the
decoded pixels arboard returns, which drop the density (and the colour
profile). Other platforms keep the decoded image.

Files dropped, Place image, paste and image.place all go through the
same path; an explicit width/height still wins.
2026-10-27 01:38:19 -04:00
bflatastic c16fd68242 Merge main into #840 2026-10-27 00:38:47 +07:00
BFlat b6fc2fd1ab Merge pull request #1152 from ricardoavilesfoto-collab/pr/fig-export-for-figma
.fig export: files Figma imports, with component set properties
2026-10-27 00:35:31 +07:00
bflatastic 2aae395310 Merge main into #840 2026-10-27 00:31:32 +07:00
BFlat 7cb369db8c Merge pull request #1372 from lileeei/1235-transliterate-class-names
Transliterate non-ASCII layer names into code-export class names
2026-10-27 00:23:36 +07:00
bflatastic b3016f7089 Merge main into #1152 2026-10-27 00:14:07 +07:00
bflatastic e469cc8135 Merge main into #840
# Conflicts:
#	apps/figcraft-cli/src/files.rs
#	apps/figcraft-cli/src/main.rs
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/lib.rs
#	crates/ui-egui/locales/en.toml
#	crates/ui-egui/locales/es.toml
#	crates/ui-egui/src/export_panel.rs
#	crates/ui-egui/src/export_preview_tests.rs
#	docs/file-format-parity.md
#	docs/mcp.md
#	docs/roadmap.md
#	docs/ui-parity.md
2026-10-26 23:23:07 +07:00
bflatastic 153250fedf Merge main into #1152 2026-10-26 23:00:18 +07:00
bflatastic d5e3e68158 Merge main into #1372 2026-10-26 22:57:22 +07:00
bflatastic b9fc8f5113 Merge main into #824 2026-10-26 22:57:20 +07:00
lileeei 1138b13f17 Merge remote-tracking branch 'origin/main' into 1235-transliterate-class-names
# Conflicts:
#	ROADMAP.md
#	crates/engine/Cargo.toml
2026-10-26 17:56:57 +08:00
Denis Gomes 5e32057bdd M7: font manager scan: drain Core Text's autoreleased objects; core-text 22
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>
(cherry picked from commit 7f6a3a4e46)
2026-10-26 00:36:38 -04:00
Denis Gomes f1b41ffeaa M7: find fonts that macOS font managers activate outside the font folders
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>
(cherry picked from commit 5699e7abe6)
2026-10-26 00:36:21 -04:00
lileeei aba296c6b8 M6: transliterate non-ASCII layer names into code-export class names (#1235)
class_name runs layer names through deunicode (new dependency: pure Rust
tables, zero deps, BSD-3-Clause, the slug crate's base) before its ASCII
filter, so Korean, Cyrillic, Latin-diacritic and CJK names become
readable ASCII classes in HTML/CSS/React/Leptos output, component roots
and data-* variant attributes: 로그인 화면 → rogeuin-hwamyeon, кнопка →
knopka, über → uber, 按钮 → an-niu (before, every one came out layer-,
layer--2, ...). Symbols-only names take the node's kind (.text, .frame)
instead of layer-, the 40-character cut lands on a word boundary, and a
leading digit keeps the layer- prefix. SwiftUI/Compose type names are
unchanged (their own sanitizer); ASCII-only names are byte-identical.
html_codegen_tests updated and extended.
2026-10-26 12:23:06 +08:00
bflatastic 27d1089847 Merge main into #1021
# Conflicts:
#	ROADMAP.md
#	docs/target-app-parity.md
2026-10-26 10:43:34 +07:00
BFlat bcde334043 Merge pull request #1314 from denisg0mes/keep-images-past-budget
M7: don't save over a file whose images past the budget weren't loaded
2026-10-26 10:41:03 +07:00
bflatastic 6c4bb00c9e Merge main into #1021
# Conflicts:
#	crates/text/src/fontdb_tests.rs
2026-10-26 10:20:43 +07:00
bflatastic 42ac4a50d2 Merge main into #623 2026-10-26 09:20:06 +07:00
bflatastic 07a654959a Merge main into #1021
# Conflicts:
#	docs/file-format-parity.md
2026-10-26 09:12:39 +07:00
bflatastic 1ec74401e6 Merge main into #1314 2026-10-26 09:11:52 +07:00
BFlat 3c9fee8dbd build: one craft-libs rev for every craft-libs crate (495e597) (#1354) 2026-10-26 08:59:52 +07:00
aiongg 8ae2e206fb chore(deps): safe dependency refresh (#1352) 2026-10-25 20:48:41 -04:00
Denis Gomes 03f4e6e070 M7: images-past-budget guard: hard links, and script output over its input
Two gaps CodeRabbit found in the guard:

- `same_file` compared paths, plain and resolved. A second hard link to
  the file is a different path to the same data, so `convert`,
  `run --out` and Save As to the link passed the guard and truncated
  the file. It now asks the file system (same-file: device and inode on
  Unix, volume and file index on Windows). same-file was already in the
  lock through walkdir.

- `run --in X --out X` checked the tab the script left active. A script
  that ends on `file.duplicate` or `file.new` leaves a tab with no count
  of images left in X, so X was written over. The count is now taken
  when X is opened and checked against `--out` whatever tab is active;
  the active tab's own file is still checked as before.

The script check is a separate function so it can be tested without a
file past the 4 GiB budget.
2026-10-25 18:41:58 -04:00
Dylan M. TaylorandCodebuff 0fb1f143cc build: pin craft-update to craft-libs main (495e597) (#1347)
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>
2026-10-25 17:37:10 -04:00
Jindřich ŠaršonandClaude Opus 5.5 6258ad2786 M6: Sketch import: merge main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 22:23:40 +01:00
Dylan M. Taylor 106ef69351 feat: startup update prompt modal and direct self-updating (#1328)
* 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
2026-10-25 16:23:00 -04:00
236f3185d6 Migrate FigCraft to egui 0.37 (#1312)
* 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>
2026-10-25 15:04:39 -04:00
Guillermo Bueno Vargas ff365be661 Merge branch 'main' into feat/penpot-import-export 2026-10-25 17:42:30 +01:00
Brandon ThomasandClaude Opus 5.5 d4a303d470 Release 0.11.2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:44:44 -04:00
Guillermo Bueno 1cd9c0d875 M6: add bounded offline Penpot import and export 2026-10-25 14:36:23 +01:00
Brandon ThomasandClaude Opus 5.5 22c15e3206 Release 0.11.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:14:22 -04:00
Brandon ThomasandClaude Opus 5.5 32b836fbc5 Release 0.11.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:04:17 -04:00
Jindřich ŠaršonandClaude Opus 5.5 87db9522b4 M6: Sketch import: merge main (Boards M12 and community fixes)
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>
2026-10-24 19:13:03 +02:00
bflatastic e596272d5a Merge main and the community batch into #623
# Conflicts:
#	docs/target-app-parity.md
2026-10-24 09:54:39 +07:00
aiongg c668e21759 PDF: write with craft-libs' krilla, which bounds decoded PNGs (#1240)
* PDF: write with craft-libs' krilla, which bounds decoded PNGs

* PDF: pin craft-libs' krilla at 9f75d3dc
2026-10-23 22:46:21 -04:00
Brandon ThomasandClaude Opus 5.5 80f916326f Release 0.10.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:39:04 -04:00
RicardoandClaude Opus 5.5 70a080af5b .fig export: files Figma imports, with component set properties
Checked by importing synthetic files into the incumbent (Drafts > Import,
black-box) and against a "Save local copy" of a synthetic document.

- Images are named and referenced by the SHA-1 of their bytes. With
  SHA-256 names, any file with an image failed to import ("Internal error
  during import").
- Component sets hold their property definitions, variant properties
  included (stateGroupPropertyValueOrders); each variant has its own copy
  of each definition (parentPropDefId) and its variantPropSpecs, and its
  layers and instances name that copy. Definitions and values also travel
  as varValue; text values carry their lines; layers get componentPropRefs
  next to the parameter consumption map. Before, sets came in without
  variant properties and with their other properties unbound.
- An axis without a maximum size is written as infinite: 0 collapsed
  auto layout frames with only a maximum width.
- Compound weights are spelt the way font families name their faces
  ("SemiBold"; "Semi Bold" in Inter): text in Libre Franklin Semi Bold was
  a missing font there and wasn't drawn.
- The kiwi encoder writes infinite floats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:56:07 -06:00
Dylan M. Taylor 0e3876e6d6 Use chrono for UTC/calendar conversion instead of hand-rolled copies
Replaces the two hand-rolled Howard Hinnant days-from-civil copies with chrono
as a workspace dependency:
- crates/collab/src/message.rs: utc_date (YYYY-MM-DD HH:MM UTC)
- crates/ui-egui/src/home_recents.rs: date (Mon D, YYYY, UTC; keeps the
  translated month names and trf formatting)
Output is unchanged. Neither function had a test, so each gains one pinning the
exact strings the old code produced (leap days included). chrono uses only std,
so wasm stays safe. AGENTS.md/CLAUDE.md now say time goes through chrono.

Follow-up to storytold/cadcraft#1785
2026-10-22 07:12:29 -04:00
Jindřich ŠaršonandClaude Opus 5.5 3e92904cba M6: Sketch import: review fixes
Texts an auto layout places keep the layout's position (the next layout
pass would take the baseline move back). A faded layer's group takes the
layer's id, so stacks, links and overrides still find it; faded masks
stay masks. figcraft-format no longer depends on figcraft-layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:27:20 +02:00