The settings folder comes from craft-settings' rules: FIGCRAFT_CONFIG_DIR,
a `portable` file next to the executable (config/ beside it), then the
platform folder every earlier version used. The Windows portable zip ships
the marker, and a portable folder without preferences starts with a copy
of the platform folder's.
Direct3D 12 compiles with FXC and no device compiles wgpu's indirect-call
validation shaders, the two causes of "Parent device is lost" at start.
When the graphics still fail before the first frame, the app starts again
with the next of a fixed list of attempts (Windows: Vulkan, OpenGL; Linux
and FreeBSD: OpenGL; then lower requirements on the power-saving GPU), and
the last failure's message names what was tried.
* M9: one name for comments and live sessions, shown in the Join dialog, kept in the browser, renamed live (#1380)
* M9: a reconnecting session's reset is its own helper (#1380)
* M9: a per-address budget for server admin requests, and join codes found by hash (#1075)
* M9: the server tests share one snapshot and address helper (#1075)
The batches a session keeps for apps that reconnect were bounded only by count on a desktop host
and a private figcraft-server (`max_log_bytes` defaulted to usize::MAX). It now defaults to
64 MiB, and figcraft-server takes it from a new `max_log_mb` setting (default 64; open to the
public, also at most a quarter of `max_file_mb` as before). An app behind the trimmed log catches
up with a snapshot, as it already did past the count limits. The protocol is unchanged.
Nine 8 MiB renames with default limits: all 75.5 MB kept before, 58.7 MB after.
Fixes#1069
The browser build's wasm was 25,982,540 bytes, past the gate raised for 0.12.1. It is now
23,915,213 bytes (22.8 MiB), 1.2 MiB under the restored 24 MiB gate:
- Catalog rows keep their plural forms behind a reference: Text is two words, not room for
four strings, across ~52,000 baked-in rows (-454 KB).
- The browser build compiles with wasm SIMD (apps/figcraft-web/.cargo/config.toml, wasm-opt
--enable-simd), so egui's glyph rasterizer leaves out its scalar SIMD fallback (-1.6 MB).
The plugin workspace is built from the repository root and stays without SIMD.
- Revert the gate raise (cb227c97); the hosting guide names the WebAssembly SIMD requirement.
`ui.input` over `window.figcraft` (the page's control channel, `?control`) never did anything in
the browser: the batch was queued for the next frame as on the desktop, but the browser shell
has no `raw_input_hook`, the eframe callback that hands a queued batch to the frame about to
draw (`windows::take_input`). The events stayed in their queue and the request's promise never
settled, so an agent driving the page could run commands and take screenshots but not point,
click or type.
The browser shell now has the hook the desktop app has: the batch goes into the frame's raw
input, is applied by it and is acknowledged once the frame is presented, as on the desktop.
Checked in Chrome on the release web build: `ui.input` with a key (O, R, F: the ellipse,
rectangle and frame tools) and with a pointer gesture (a drag with the rectangle tool: a
rectangle drawn) resolves and takes effect, where on `main` the same requests time out and
change nothing.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
The pixel cap reads only the PNG's header, so a small image followed by a
large tail of data passed it and was copied and base64-encoded whole.
MAX_CLIPBOARD_PNG_BYTES (256 MB, the same cap as design data read from the
clipboard) is checked on the NSData length before the copy on macOS, and
again in the paste path so every Services implementation is held to it.
A PNG past the cap is treated like an unreadable one: paste falls back to
the decoded image.
Comments become the task queue for an attached or headless MCP client: comment.prompt returns a bounded Markdown brief, the Comments panel gains Copy for AI, and the MCP server exposes fix_comments, watch_comments, figcraft://comments and wait_for_changes. Mutating comment.* commands append to a bounded in-memory event log; the control channel's events.poll long-polls it without the UI thread, and a successful batch commits its events only if the whole batch lands. No model, network dependency, asset or file-format change.
`run_command {"command": "inspect.code", "args": {...}}` ran the command
without its params, on the selection. MCP tool arguments are now checked
against each tool's input schema (and the objects it describes inside,
such as a batch's steps; open objects like `params` take anything), and
a step key other than `command`, `params` and `as` is refused in
command.batch, figcraft-cli scripts and the MCP batch tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A density now counts only within 0.005 of a whole 2x-4x multiple of
72 dpi. That covers the rounding of pixels per metre (144 dpi is stored
as 5669 or 5670) and rejects nearby resolutions: 141, 143 and 145 dpi
were read as 2x under the old 0.05.
Paste falls back to the decoded clipboard image when the clipboard's PNG
can't be read, instead of pasting nothing.
Also the line on what the objc2 crates are for, and the ROADMAP row for
the new placing default.
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.