* CI: line tables only for workspace crates, no debug info for dependencies
* CI: one integration-test binary per crate
Each crate's tests/*.rs files become modules of tests/it/main.rs. Files that change
process-wide state (Color Settings, the font database, installed plug-ins, the working
directory), the fuzz sweeps and the test targets the Affinity corpus workflow names stay
their own binaries.
* Docs: name the integration tests' new paths
* CI: xtask test with nextest and doctests, one wasm check, clippy --keep-going
cargo xtask ci runs the tests with cargo nextest (cargo test when it is not installed),
builds them with --lib --bins --tests, runs the doctests as their own step, and checks
the wasm crates in one cargo check --keep-going call.
Review follow-up (CodeRabbit): a line that wasn't valid JSON was reported
by its line only, while every other --commands error names the run's step.
It now says `step N (line L)`, N counted with the --cmd steps before it.
run_refuses_malformed_commands_naming_the_step expects `step 2 (line 3)`;
it fails without the change.
`vectorcraft-cli run/convert --scale` and the MCP export tool's `scale`
are raster pixels per point, but they were passed to every export, and
DXF reads `scale` as drawing units per unit: a 2x PNG next to a DXF
doubled the drawing and left it unitless ($INSUNITS 0). They now go
only to raster formats. DXF's own scale stays reachable through the
format options.
document.export, document.serialize, document.exportSelection and
Export for Screens rows answered "PDF-compatible .ait template files
can be opened but not written" for a .ait, although
file.saveAsTemplate writes them. The CLI's convert and run --export
call document.export, so vectorcraft-cli convert in.pdf out.ait
failed with that error.
The error now reads "PDF-compatible .ait template files are written
by file.saveAsTemplate", with a row in the 17 catalogs that translate
status messages and in bg, da, id and nl. convert and run --export
write a .ait with file.saveAsTemplate, which writes every artboard
with the .ai options. convert passes --replace-lossy and its .ai flags
on, and refuses --artboard and --range for a .ait.
Docs: docs/mcp.md, docs/target-app-parity.md.
PDF presets are kept one file each in a PDF Presets folder, ICC profiles as copies in a Color Profiles folder, next to the preferences; the desktop app and vectorcraft-cli use them. Adds color.removeProfile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tmp() helpers in apps/vectorcraft-cli/tests/cli.rs and fileio.rs
name their folder after the process id (vectorcraft-cli-test-<pid>,
vectorcraft-cli-fileio-<pid> in the temp folder) and never empty or
remove it. When a later run gets the same process id, it finds the
earlier run's files there. mcp_automation_roots then fails on Unix with
EEXIST (Os { code: 17, kind: AlreadyExists, message: "File exists" }):
its first symlink() call finds the link in/escape.svg that the earlier
run made. A stale output file can also let a fileio test read an
earlier run's export.
The first tmp() call in each test process now removes the folder and
creates it again empty. The roots test's body moves into
automation_roots(), and mcp_automation_roots_in_a_folder_an_earlier_run_left
runs it twice in one folder, emptying the folder in between as tmp()
does. Without the removal the second run fails with the same error.
perf --deep printed the memory at eight points of its run, and no row
showed memory that the run took and freed between two readings. With
--paths 2000, a release build on Windows printed 430 MB as its largest
row, and the process's peak working set was 612 MB. On Windows the
memory row after rendering the photos also printed its value 3 columns
right of the others: its name is 55 characters, and the name column is
52 characters wide. The tracked row for the photos printed its value 2
columns right of the other tracked rows: its name is 54 characters, and
their name column is 52 characters wide. In the budget table the handle
and segment drag rows printed their values 1 and 2 columns right of the
other rows: their names are 41 and 42 characters, and the name column
is 40 characters wide.
Each memory row now also gives the peak so far: the peak working set
(PeakWorkingSetSize, from the same GetProcessMemoryInfo call) on
Windows, and VmHWM from /proc/self/status on Linux. On macOS the rows
give no peak. A new last row gives the memory after the SVG export and
the save → open → save at the end of the run. The name columns of the
memory rows, the tracked rows and the budget table are 56, 54 and 42
characters wide. The help says on which systems the rows give the peak.
Docs: docs/target-app-parity.md.
document.open (and file.newFromTemplate) with a path relative to the
working directory took the SVG's folder from Path::parent(), which is
"" for a bare file name and was then dropped, so relatively linked
images were "not found" (and, linked by their bare name, not listed in
missingLinks either). A relative path is now made absolute first: its
folder is the working directory, as for the absolute path.
vectorcraft-cli convert printed only the export's warnings; what reading
the input left out now comes as importWarnings.
Fixes#1604
On Windows, perf printed "load average ?": load_average runs sysctl
or reads /proc/loadavg, and Windows has neither. The warning for a
busy machine could not print, so a run over budget on a busy machine
failed, where macOS and Linux warn and exit 0. perf --deep printed no
memory rows, because rss_mb runs ps. --help did not list --deep.
On Windows, perf now reads GetSystemTimes twice, one second apart,
and its first line gives the cores busy on average over that second
where macOS and Linux give the load average. It warns above three
quarters of the cores, as on macOS and Linux: with all 32 logical
processors of a test machine kept busy, the line read "cores busy
32.0" and a run with three budgets over exited 0. The --deep memory
rows give the working set (GetProcessMemoryInfo). Both calls use
winsafe 0.0.29, which Cargo.lock already lists for the desktop app;
the CLI depends on it on Windows with the kernel and psapi features.
The help lists --deep and says that a run over budget on a busy
machine exits 0. perf's report on macOS and Linux does not change.
`run` reported "Broken pipe" (on Windows "The pipe is being closed") and exited 1 when the
reader of its stdout went away, unlike `commands` and the other subcommands, which end quietly.
Its step printer now treats a closed pipe as the end of output, not an error: the remaining
steps still run (an export is still written), unprinted, and the run exits 0. Other write
errors and failing steps still fail.
The test runs document.json, a rectangle and an SVG export with stdout closed: status 0, empty
stderr, the SVG written; `run --cmd not-a-command` still fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
vectorcraft-cli mcp and vectorcraft --control take the flags PhotoCraft
takes. While roots are in force, every path a command reads (open, place,
relink, SVG image links, library and profile loads, plug-ins, preset
imports, font searches) must lie inside the read root and every path it
writes (save, export, package, Export for Screens and for Web, print to
file, captures, library saves) inside the write root. Paths are made
absolute, links are followed (a missing file's nearest folder's), and the
result is compared by whole components, ignoring case on Windows; `..`
below a missing folder, dangling links and device names are refused. A
root left out grants none of its access; without the flags nothing
changes.
The checks live in one place, vectorcraft_engine::file_access, consulted
by the engine's file helpers and the app's file services. The headless
server confines its whole process; the app confines control requests,
the frames carrying input they inject and the background saves they
start, leaving the person at the keyboard and the app's own folders
alone. The screenshot tool now has the backend write its file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Discord feedback: "EPS files are always rasterized: they open as bitmaps, in
other programs as vectors". Every EPS saved by Illustrator 2026 fell back to
its TIFF preview (`gcheck` was unknown), and cairo/Inkscape files stopped at
their first image (`status`), dropped masked images (ImageType 3) and failed
on mesh gradients; the warning didn't say where the program failed.
Fallback never silent:
- The warning names the PostScript error, the operator that raised it and
the named procedures it ran in, innermost first ("a PostScript error
(typecheck in `add`), in `inner` in `outer`"); errors caught by `stopped`
aren't reported. It reaches the status bar, document.open, app.open and
file.open (which now return document.open's result, so MCP's open_file over
the control channel gets it), and `vectorcraft-cli info` ("warnings").
- A file whose PostScript can't be read opens as its TIFF preview, else its
Windows metafile preview (as vectors, scaled to the bounding box), else its
EPSI bitmap. es @msg rows for the new messages.
Interpreter:
- Operators programs probe or use: gcheck/scheck, rootfont,
currentcolorrendering, currenthsbcolor, status, token, read,
bytesavailable, fileposition/setfileposition, resetfile, write*, pdfmark
(marks dropped, as Ghostscript does), execform, nulldevice, strokepath,
pathforall, user paths (ufill/ueofill/ustroke/uappend/upath/setbbox, the
encoded form too), setcachedevice2, findcolorrendering, cache, device and
VM queries, revision; (%stdout) (w) file gives a sink.
- `//name` is replaced by its value when read, inside procedures too.
- Tiling patterns become pattern swatches (their PaintProc drawn in pattern
space, one cell; uncoloured ones in the colour under them; makepattern
copies share one swatch); nested patterns stop at 8 levels.
- Type 3 fonts draw their glyphs (BuildGlyph/BuildChar, widths from
setcachedevice/setcharwidth) as outlines grouped per string, spaced by
ashow/widthshow/awidthshow/xshow/yshow/xyshow/kshow; glyphshow and
stringwidth/cshow use them.
- Shadings of types 1 and 4-7 become gradient meshes (data from arrays,
strings or files, packed or not, flags sharing edges), as fills through a
shading pattern too; sampled functions (type 0) are evaluated;
GradientMesh::coons/triangle are shared with PDF import.
- ImageType 3 masked images, interleaved by sample, by row or in separate
sources; ReusableStreamDecode reads its data when made; Flate/LZW PNG and
TIFF predictors.
- Bounded: DeviceN at 32 inks, sampled functions at 2^24 samples and 32
outputs, 4096 patches a shading, strokepath ignores dashes finer than 1e5
pieces, Type 3 glyphs nest 8 deep (a glyph that shows itself overflowed the
stack before the limit), 64-bit index maths for 32-bit targets.
Tests: a synthetic program per generator (cairo/Inkscape, its Type 3 fonts,
matplotlib, CorelDRAW, Affinity, LibreOffice, Ghostscript eps2write,
Illustrator procsets), meshes and function shadings, masked images, fault
messages, probing operators, metafile/EPSI previews, `//name`, user paths,
hostile patterns/glyphs/shadings (run against main, 12 of the 14 generator
and feature tests fail; CorelDRAW- and LibreOffice-style programs already
read there). The import fuzz vocabulary covers the new operators. Checked
against real files made here with Inkscape 1.4 (cairo) and Illustrator 2026
(its own format and the CS-to-CC compatibility formats; all fell back or
stopped early on main and open as vectors now; its Illustrator 8 format
needs the next commit); those files aren't committed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.inspect takes the same {depth?, childLimit?} slicing as
document.node (a depth-0 read is the layer skeleton), and document.find
{name?|kind?|text?, limit?} locates nodes across the tree with their
ancestor paths and a total. docs/mcp.md documents the drill-down recipe;
the CLI stdio sweep covers both.
`depth` / `childLimit` without `summary: true` used to return the whole
full-JSON subtree silently; it is now an invalid-params error. docs/mcp.md
describes the slice parameters and childCount, the engine test covers
fractional values, unsliced reads and limits past the tree, and the CLI
stdio sweep reads a sliced group through run_command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`vectorcraft-cli commands | head -1` panicked and exited with status 101:
thread 'main' panicked at library/std/src/io/stdio.rs:1168:9:
failed printing to stdout: Broken pipe (os error 32)
println! and print! panic when stdout is closed. Output now goes through
outln! and out!, which end the program with status 0 on a broken pipe (the
reader has what it wanted) and report any other write error on stderr with
status 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As the branch does for older tests: `panic!("…")` instead of
`unreachable!()` in test code (clippy.toml only allows unwrap, expect and
panic there), and the integration tests main added (CLI file I/O, render
gradient text and stroke geometry, SVG export suites) opt out of the
unwrap/expect/panic lints. No test logic changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shipped code no longer unwraps, expects or panics: every one of the 124
unwrap()/expect()/panic!/unreachable! sites outside tests now handles the
failure (Result/Option, ?, let-else, defaults) and reports errors to the user.
- Lints: workspace clippy denies unwrap_used, expect_used, panic, todo,
unimplemented and unreachable; clippy.toml allows them in tests, and
integration tests, examples and testkit opt out explicitly.
- Safety net (vectorcraft_engine::guard): a panic in a top-level command rolls
the document back and returns EngineError::Internal; a panicking tool is
reset; the MCP server answers with an internal error and keeps serving; the
UI frame and each control request are guarded.
- APIs that hid failures now return them: Rendered::to_png/to_jpeg/to_webp
return Result (JPEG/WebP errors used to write empty files),
FontDb::face returns Option, Region::top returns Option.
- Fuzzing: engine/tests/import_fuzz.rs feeds garbage, hostile and mutated SVG
and PDF through import → render → export (30k cases locally, no panics).
- Docs: AGENTS.md/CLAUDE.md rule, docs/development.md "Robustness" section,
README pointer and a PR template checklist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>