An assignment and the layers that read it can name different copies of
one definition (the set's own id on one side, a variant's copy on the
other). The values were kept under the id the assignment names and read
under the id the layer names, so such a value never reached its layer.
Both sides now go through prop_root, the definition the copies were
made from (CodeRabbit on #847). In Untitled UI, 143 assignments name a
copy that no layer reads while another copy of it is read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A component property assignment carries its value in `value`, and in
current files also in the variable form `varValue`. Many assignments
leave `value` empty and say it only in `varValue` (booleans as
boolValue, instance swaps as symbolIdValue, text as textDataValue), and
those were ignored: a hidden layer showed and a swapped icon stayed the
default. In Untitled UI that is 1,643 booleans and 185 swaps, so inputs
showed their hint text, help icon and leading icon on top of the
neighbouring layers. Both forms are read now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a path can't be resolved (a link that leads nowhere, `..` below a
missing folder), the reason is given only for paths inside a root.
That check compared the path as typed with the roots resolved, so a
root reached through a link (`/tmp/work` on macOS is
`/private/tmp/work`) called its own dangling links "outside the folders
that may be written: /private/tmp/work", and a link out of a root still
had its reason told. The existing part of the path is now resolved
before the comparison, as the roots were.
This is also why `links_that_lead_out_of_a_root_are_outside` and
`outside_paths_are_refused_alike_whatever_exists_there` failed on
macOS, where the temp folder is under `/var` -> `/private/var`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each variant of a component set keeps a copy of the set's property
definitions (an id and a parentPropDefId, no name or type), and the
variant's layers and its instances' assignments use the copy's id. Those
copies were skipped, so imported variants lost every boolean, text and
instance swap binding, and instances lost their property values. In
Untitled UI (8,074 copies) an input's "Hint text" no longer followed its
property. A copy now names the definition it was made from.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mistyped or unsupported flag (e.g. --automation-write-roots) used to be
dropped silently, so the app started unconfined. It now exits 2 with
'unknown option', like a bad automation root. A bare -- ends the options.
Adds a desktop control-channel test that file.save / file.saveAs outside the
write root are refused and create nothing, while saves inside succeed, and
documents that the root flags arrived in 0.7.0 (0.6.0 ignores them).
Routing the CLI's writes through `file_access` changed what happens when the
output path is a symbolic link. `file_access::write` stages the bytes and
renames them into place, and a rename replaces the link with a regular file,
where `std::fs::write` followed it and wrote to its target. A render to a link
reported success while the target kept its old contents.
Unconfined writes go back to `std::fs::write`. Confined ones keep the staged
write, which is right there: `Roots::check` resolves the path first and has
already refused a link leading out of the root, so the write lands on the
resolved target inside it.
`figcraft-cli mcp` takes `--automation-read-root` / `--automation-write-root`
to confine the files an agent's commands touch, but the file subcommands took
neither: `figcraft-cli run script.json --out /anywhere/x.figcraft` read and
wrote as the user who started it. The README invites agent use, and a script
or agent driving `run`, `export` or `convert` had no way to say which folders
it may reach (#833).
`info`, `export`, `convert`, `render` and `run` take the same two flags, with
the same semantics: repeatable, the folders must exist, reads inside a read or
write root, writes only inside a write root, everything else refused -- so
giving no write root denies writing altogether. Without the flags they behave
as before.
The flags would have been decorative on their own: `figcraft_engine` enforces
roots through `file_access`, which the engine's own loads already go through,
but the CLI wrote its output with `std::fs` and read its script the same way.
Those three calls now go through `file_access` too, so the confinement reaches
the files the CLI itself touches.
File ▸ Open, Import and Place image did nothing in the browser (the pickers
return a path, which a browser never has) and dropped files were ignored
(eframe hands their contents over asynchronously).
- Services::pick_open_contents: a platform can pick a file's name and
contents; the web build uses an <input type="file">. Open and Import pass
them to file.open / file.import as data + name.
- A dropped file without a path waits behind the opening card until its
contents are read (bytes_async on wasm), or says it couldn't be read.
- Canvas drops still import, as on the desktop.
Closes#759.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: drop references to competing commercial software
* docs: clarify parity checklist coverage in status callout
* #786: say interface design once in the tagline
---------
Co-authored-by: bflatastic <bombayapirat@gmail.com>
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.