A request line can carry timeoutMs beside id and method, capped at 600,000 ms; without it the wait stays 60 s and older apps ignore the field. figcraft-cli mcp --attach PORT --timeout SECONDS sends it with every request and reads the answers that much longer. A timeout's message says how long the request waited.
* M6: figcraft-cli connect publish publishes code connect mappings to a design file
figcraft-cli connect publish <file.figcraft> [--mappings figcraft.connect.json] [--dry-run] links each entry's component or set by name through codeConnect.import, warns about and skips links that don't fit, saves the file in place through the engine's file.save, and exits 1 when nothing links. Gap 16.
* M6: the generated man pages include connect and connect publish
clap derive types replace the hand-written parser: typed values, usage
errors (exit 2) before any work, help from the same definitions.
Syntax changes: a repeated option is a usage error (was: last wins; the
automation roots still repeat); a value starting with `-` needs `=`; a
positional starting with `-` needs `--`; `--o` and the `version`
subcommand are gone (`--version` stays); `<sub> --help` prints that
subcommand's help; usage errors are clap's text; `--format` is checked
before the file is opened.
`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>
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.
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 Files paragraph of the CLI usage text now names .sketch (Sketch 48 or
newer, best effort), as docs/cli.md already does (PR review).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
* Dev profile: line tables for workspace crates, no debug info for dependencies
* Tests: one integration-test binary per crate with several test files
* xtask ci: nextest, doctests, tests without examples, --keep-going; add xtask test
Review: `mcp --bogus --help` stopped at --bogus with exit 2. The parser
now keeps the first bad option and reports it only when no --help or -h
follows.
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.
`figcraft-cli mcp --help` failed with "unknown option --help" and
exit code 2; only a leading --help worked. The argument parser now
returns CliError::Help for --help or -h among a subcommand's options
(not as an option's value), and main prints the usage on stdout with
exit code 0. docs/cli.md says so.
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>
A batch step can name itself with "as" and later steps refer to it with
{"$ref": "name"}, alongside positional {"$step": n}. "field" now takes a
slash path into the result as well as a key, so {"$ref": "copy",
"field": "ids/0"} reaches the first id edit.duplicate returned (the
convention variable.bind uses for fills/0/color; a leading "/" as in
JSON Pointer also works, and a key holding "/" still matches first).
Resolution moves to crates/engine/src/batch.rs (BatchStep, BatchScope),
shared by command.batch, the CLI script runner, the control channel and
the MCP batch tool, which now passes "as" through. Duplicate names fail
before any step runs; unknown, same-step or later names, bad paths and
out-of-range indexes fail the batch with the step, name and full path.
$ref is reserved like $step; nested batches keep their own names.
Fixes#994.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CLI help and docs/mcp.md described the roots as confining whatever the run
touches, which reads as if they also applied to `mcp --attach`. They don't: an
attached server runs the commands inside the app, and the app confines them
from its own flags. VectorCraft documents the same split, and the missing
sentence is what made the difference confusing, as #835 reports.
Co-authored-by: xiaou61 <113765024+xiaou61@users.noreply.github.com>