"(as file.export)" read as all of them, but the PDF and XML options are
refused there, so it now says they are file.export's SVG options.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.placeImage runs image.place's code and file.exportSvg file.export's,
so their docs now list the names those read (width, height, target, mode;
scale or size and the export options). MCP's create_node passes points,
closed and select to node.create, and pointer and key read ctrl, so their
schemas list them too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`file.open` on a path already open in a tab switched to it and returned
its index, even when another process had changed the file since, so the
caller kept looking at the old copy with nothing saying so (#997). The
tab's disk stamp from #1330 (`DocState::on_disk`) now tells:
- the result says `stale: true` when the file on disk differs from what
the tab read or last saved;
- `reload: true` reads the file again into the tab, as Revert to saved
does (refused while the tab has unsaved changes or is in a live
session), and the result says `reloaded: true`.
MCP's `open_file` takes `reload` and passes it on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before commands can refuse names their docs don't list (#1413), the docs
have to list what the code reads. These are the top-level names the run
code (and every helper it hands the params to) reads that its doc left
out, all checked against the code; nothing changes behaviour:
- creation: node.create (characters, points, closed), frame.create,
shape.*, text.create and vector.create (index, name, select),
image.place (name)
- aliases the code already took: comment.* (id | link), annotation.add
and annotation.edit (label | text), text.set (characters | text),
text.listStyle (style | list), fill.add and stroke.add (paint | color),
proto.interaction.add/set (durationMs | duration_ms, delayMs | delay_ms)
- options: file.export and export.update (contentsOnly,
includeBoundingBox, svgOutlineText, svgSimplifyStroke), comment.next
(frame, resolved), layout.suggest (direction), variable.create
(description), vector.edit.widthPoint (side), collab.share (key),
collab.manageFile (access, label, link, name), fill.pattern (visible,
blend)
- docs whose shape hid a name: proto.interaction.add (a stray `}` closed
the object before delayMs; dest was only in a trailer),
proto.interaction.set (patch fields in a second object), collab.join
(a top-level comma inside the link examples), pref.uiScale (commas in
the value's description)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open and Revert to saved recorded the file's size and modification time after reading it: a change written in between would be stamped as already loaded and a later save would replace it. Taking the stamp first makes such a change refuse the next save instead (CodeRabbit on #1374).
With a file open in the desktop app, figcraft-cli could change it on disk;
the next Save (or auto-save) wrote the app's copy over it and said
"Saved", losing the CLI's changes.
Each tab now remembers its file's size and modification time when it
opens, reverts or saves it (the Stamp library files already use), and
Save and auto-save refuse while the file on disk differs, pointing to
Revert to saved or Save as. Scripts pass `file.save {overwrite: true}`.
Save as and saves to an explicit path are unchanged.
Existing text keeps the named instance's optical size, so documents render as before; the
Sketch import stores the size as the text's opsz value instead.
A native file whose images together pass IMAGE_BUDGET (4 GiB, or 20,000
images) opens without the images past it. They are intact in the file, but
saving it again wrote the archive from the images in memory, so every
save, autosave or `convert` onto itself deleted them for good. A 7.6 GiB
`.fig` converted to a 7.3 GiB `.figcraft` reopened with 1,312 of its 2,337
images: one save would have dropped the other 1,025.
Loading now tells images left out for the budget apart from damaged ones
(`Loaded::images_past_budget`). A tab opened or reverted from such a file
remembers the file, and saving over it is refused with a message that says
how many images would be lost and to save under another name; saving a copy
elsewhere works, and the original keeps its images. Auto-save skips the
tab instead of retrying and failing every minute. `figcraft-cli convert`
and `run --in X --out X` refuse to write over their input the same way.
The same file is matched by its resolved path, so `./a.figcraft` and
`a.figcraft` count as one (`file_access::same_file`).
The note for each image left out for the budget now says it stays in the
file, which can't be saved over without losing it. Damaged and missing
images keep the old wording: they can't be kept, and saving drops them as
before. The doc comment on report_lost_images said saving always drops
the images it reports, which is no longer true for budget ones, so it went.
This stops the data loss; it doesn't make such files editable in place.
Loading images on demand would, and is discussed in the issue this links.
Tests: the count and the note for both budgets; saving over the file
refused with the file unchanged, Save As then working, and saving back over
the original refused again; a snapshot encoded in the background refused
too; a tab without images left out saving as before. Checked by hand on the
7.3 GiB file with file.save, file.saveAs, convert and run --out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The command parameter docs (CLI `commands`, MCP list_commands, the Actions
palette) and the MCP open_file tool description listed .fig but not
.sketch; docs/mcp.md and docs/cli.md already did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts were only in the Change notes of docs/file-format-parity.md and
docs/target-app-parity.md: main's notes kept, the `.sketch` note added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A slice exported every layer of its page that showed in its area, whatever
its Contents only setting said. With Contents only on (the default), a slice
inside a frame or group now exports only that frame or group and the layers
in it. With it off, or for a slice on the page or in a section, the slice
exports everything in its area as before. The Export section's preview of a
slice follows its first export setting in the same way.
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>
* chore: resolve SonarCloud maintainability issues
- Define SHA256_FMT constant in packaging script to remove duplicated string literal (shelldre:S1192)
- Remove redundant 'static lifetime annotations from const items (rust:S8863)
- Replace closures with method and function pointers (rust:S1612)
- Extract helper functions in synthetic document generation, bezier polyline flattening, and component restoration to reduce cognitive complexity (rust:S3776)
* fix(packaging): use verify_sha256 helper function to avoid shellcheck SC2059
* fix(engine): remove duplicate closing brace in large_document
* fix(engine): take &mut Document in restore_id_map; keep 'static on associated consts
restore_id_map allocates ids, so it needs a mutable document. Elided
lifetimes in associated consts trip elided_lifetimes_in_associated_constant,
so those annotations are restored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assign positional parameters to locals in sha256 verification
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
Board files (File > New board) with sticky notes, shapes with text, connectors that follow what they join (straight or elbow, arrowheads, labels), marker, highlighter and eraser, and stamps; a board toolbar and a contextual bar; the Design panel hidden on boards, Prototype and Dev Mode refused there. Boards are ordinary FigCraft files built from ordinary layers, so saving, undo, export, MCP and live sessions work unchanged. File tabs, recent files and Open files show a pen nib for design files and a note for boards (original icons drawn in code).
Import lines for connected components inside generated layer trees; Compare changes against any saved version; a Dev Mode behaviour suite with an inspect-only canvas (menus, shortcuts, paste and Revert to saved refuse design edits; a drag under way is dropped on entering), Changed in the ready list, statuses only on top-level frames and sections; read-only plugins' panels from the Inspect tab.
Review: " . .name. . " kept inner dots at the edges; trim spaces and dots
in one pass. docs/cli.md and safe_file_name's comment give the 100 and
200 character limits.
export into a folder replaced every character outside letters, digits,
'-', '_', space and '.' with '_', so a frame called
"Homepage - desktop (live + verbeteringen)" became
"Homepage - desktop _live _ verbeteringen_.png". The app's
file.exportLayers keeps those names and only replaces what file systems
refuse. The CLI now uses the engine's safe_file_name, and docs/cli.md
says which characters are replaced.
Also adds my own entry to contributors/people.toml.
- --no-system-fonts holds for .sketch imports too: the CLI opts the
process out, so the import's font matching doesn't scan.
- Shared text styles get the installed family and platform tracking
their linked layers get.
- Each text run writes its letter spacing, line height and font axes,
neutral when it has none, instead of taking the first run's.
- The unsupported-drop hint and its catalogs list .sketch.
- A test sends damaged .sketch data through file.open and file.import.
- Stack items are matched to their source layers through a map.
- Docs: .sketch is a convert input; the Carbon kit's Sketch 47 edition
is refused (the 68 and 83 ones open).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The font database knows each face's PostScript name; the Sketch import
finds the installed face a file's font names (CourierNewPSMT is Courier
New Regular) and reads the family from the name only when it isn't
installed. Text without a fixed line height takes the line Sketch draws:
ascent and descent rounded to whole pixels, the line gap left out, a
fifth more for classic Mac fonts, the baseline where Sketch puts it
(measured on sketchtool renders of 80 fonts).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>