save_file's note for a key in `options` that the written format doesn't
read named every .ai file a PDF-compatible .ai, also when the save wrote
a version 7 file (`aiVersion: "7"`) or one without PDF content
(`pdfCompatible: false`).
The save itself now writes that note, beside its note on a top-level
`compress`, `version`, `preview` or `precision`, and names the file it
writes, including when it reuses the options the document was last saved
with. save_file returns the save's reply unchanged, and
`run_command document.save` and the other save commands give the same
notes.
A `backquote` given as a string or a number, on the request or on an
event, was read as absent, so `"true"` moved the object's geometry
instead of its pattern. `pointer_gesture` and `ui.pointer`, headless
and desktop, now answer with an error before any event runs.
File > Open and File > Place read BMP files named .dib or .rle (run-length
encoded bitmaps included), Photoshop files named .pdd and PNG files named
.pns, in any letter case. The Open and Place dialogs list them in "All
readable files", "All placeable files" and their format's own filter, and
Glass's Load Texture lists them too. Linked files with these names update
and relink, the Links panel shows their format, and Edit Original opens
them.
The extensions are read-only: exports still write .bmp, .psd and .png, and
an export to a path with one of the new extensions is refused as before.
The desktop packaging does not associate them with the app.
Holding backquote while dragging with the Selection tool (move, bounding-box
scale or rotate) or the Rotate, Reflect, Scale or Shear tool applies the drag
to the selected objects' pattern fills and strokes and leaves their geometry
in place. The key state at release decides the result: the key may be pressed
after the mouse button goes down, and releasing it before the button comes up
commits an ordinary transform. Objects without pattern paint do not move.
PointerEvent gains a `backquote` flag (false when absent). The desktop canvas
reads it from the held key on every pointer event, and `ui.pointer` and the
`pointer_gesture` tool accept it per event or as a default for the gesture.
remote_call_reports_nothing_sent_when_reconnect_is_refused set up the
closed connection once, so only its first run went through the
stale-connection probe. Each run now sets up its own closed connection
before the timed call.
The vectorcraft-mcp tests of the remote backend in tests.rs gave a fake
app 2 or 5 s to accept a connection or to read a request, and waited
2 s for a connection to end or a line to arrive. When a busy machine
held the client's thread for longer, the fake app gave up and the test
failed. These waits now use WAIT (20 s).
remote_connect_fails_fast asserted that a refused connect returned
within 5 s, and remote_call_reports_nothing_sent_when_reconnect_is_refused
asserted that the call returned within 2 s. On Windows a refused local connect
ends at the backend's own 800 ms connect timeout. A connect without
that timeout takes about 2 s. The 5 s bound did not tell the two apart,
and the 2 s bound left 1.2 s for any pause of the thread. Both tests
now bound the connect by FAILED_CONNECT (1.5 s) and run it up to five
times, until one run is under the bound. The second test checks the
error of every run.
The reference app switches an arc between open and closed with C,
flips it with F and changes its angle with Up/Down while it is dragged
out. VectorCraft's Arc tool ignored these keys during a drag.
While an arc is dragged out, C now switches it between an open and a
closed path, F switches its base between the X and Y axes in the same
box, and Up/Down raise and lower its slope by 10 within -100 to 100.
Each press switches once. The arc drawn on release uses the new values,
and they stay as the Arc tool's options. With nothing dragged, C and F
are shortcuts as before.
Docs: docs/mcp.md, docs/target-app-parity.md.
Found while testing crates/mcp/src/server.rs:180-205 (read_line_capped)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/mcp/src/resources.rs:169 (decode)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
After imageTrace.setView, screenshot and render_preview still drew the
tracing result, because ui.render renders with the export options and only
the canvas turns on trace views. Only the app window showed the view.
ui.render now takes traceViews. With traceViews: true, Image Trace objects
draw in the view set with imageTrace.setView, as on the canvas, both
headless and in the running app. screenshot and render_preview take
traceViews and pass it on; without it they draw the tracing result, as
exports do. The trace-and-style prompt names the argument.
A supersampled render drew Outline view's lines, type outlines and dotted
guides as little as a quarter of an output pixel wide, so they came out
pale gray. The renderer now scales those widths and the guides' dashes by
the supersampling factor in the larger frame, so the lines stay one output
pixel wide.
Docs: docs/mcp.md (render_preview, screenshot), docs/control-protocol.md
(ui.render).
draw_path, draw_shape and object.setProps take opacity in percent (0-100),
but the document.inspect / document.node summary behind doc_inspect,
inspect_document and vectorcraft://object/{id} reported the stored 0-1
fraction under the same name. An agent that read 0.55 and wrote it back
got a nearly invisible object.
The summary now adds opacityPct (0-100, rounded to 1/100 of a percent)
beside the unchanged opacity, and the tool, resource and command
descriptions say which scale each field uses. Round-trip test in the mcp
crate: draw at 55 %, read 55 back from doc_inspect, inspect_document and
the object resource, write it back unchanged.
Since #3971, open_file, save_file, export and screenshot made every path
absolute against the server's working directory, headless as well as
with --connect. Headless, the engine runs in the server's process and
resolves a relative path against the same directory, so the conversion
is only needed for a connected app. It also made a relative path fail
headless when the absolute path is not UTF-8.
The server now makes these paths absolute only for a connected app, as
docs/mcp.md describes. Headless, it sends them as given.
Native files store every anchor and handle as a full double, such as
3.4876474632797176, so a file of traced art is larger than it needs
to be.
document.save, file.saveAs, file.saveCopy and file.saveAsTemplate take
precision (0 to 9) for native files. The save rounds the anchors and
handles of path objects, Live Corners' uncut outlines, blend spines,
envelope outlines, Intertwine areas and brush art to that many decimal
places, in the layers, symbols, graph designs, patterns and opacity
masks, and in the art that placed documents become in files for older
versions. Colors, transforms, opacity and type are written exactly.
Without precision the file is written as before, and Save does not
remember the option. A .ai file, saved or exported, ignores precision.
The VectorCraft Options dialog shows no row for it.
A save given precision beside the path for a format that does not read
it (SVG, PDF, .ai and .ait) says so in its warnings, as it does for
compress, version and preview. MCP save_file passes precision on to the
save as it passes the other native options, and its schema, description
and docs/mcp.md list it.
With nothing selected and no ids, set_paint changes only the paint for
new art, but its reply was the same as when it painted objects, so a
caller could not tell that no object changed.
paint.setFill, paint.setStroke, paint.none, paint.lastColor,
paint.lastGradient and stroke.set now reply {objects}: how many objects
took the paint, as the commands resolve their targets (a selected group
counts the objects inside it). set_paint adds the largest of those
counts to its reply as `objects`, and when it is 0 a `note` says that
only the paint for new art changed. The tool description and the
set_paint row in docs/mcp.md describe the reply.
Holding Alt over a selection the Shape Builder had already mapped built
the planar map a second time, to add the outline edges that erasing
hits. A merge, and a Live Paint Bucket fill of a plain selection, also
dropped the map, so a gesture that left the document unchanged was
followed by another full build.
The Shape Builder now builds its map with the outline edges the first
time, and both tools keep the map until the document, its revision or
the selection changes.
When the inputs have more than 128 segments, a hover with the Shape
Builder or the Live Paint Bucket now builds the map on another thread
and highlights no region until it arrives, instead of holding up the
frame; the canvas checks for it each frame. A press waits for the map,
and the headless MCP host waits for it after a move. The web build has
no threads and builds the map at once. The Live Paint Selection tool's
hover no longer builds a map of the selection, which it did not use.
With Use Artboards and two or more artboards, a document with no visible art gets only the artboard files. The useArtboards and range descriptions, the document.export and document.exportEps params text, the control protocol and the parity line now say so.
The server lists two names for several calls (command_list and
list_commands, command_run and run_command, doc_inspect and
inspect_document, ui_inspect and inspect_ui, ui_screenshot and
screenshot with window:true), but no description said so, and the two
listing and running pairs take different argument names. Each
description now names its pair and the argument that differs, and undo
and redo name each other.
ui_screenshot took no arguments and returned the window at its full
size, which can be several megapixels on a large display.
ui_screenshot and screenshot {window: true} now take an optional
max_side (1..4096) that scales the returned image down to that many
pixels on its longer side. The reply's text gives the returned size as
width/height and the capture's as capturedWidth/capturedHeight; a file
written to path keeps the full capture. max_side on an artboard render
is an error that points to scale. render_preview reads its max_side
through the same check.
docs/mcp.md describes the pairs and the new argument.
A document opened or created between edit.undoGroup.begin and
edit.undoGroup.end opens an undo group of its own with the begun
group's name, so an atomic command_batch that starts with file.new or
document.open is one undo step in the new document. The group opens
after the command that opens the document: when that command also
edits it (a command.batch), those edits stay the command's own undo
step, and a canceled group keeps them, so the journal still replays.
Both commands also run with no document open, and a command.batch that
fails rolls back a begun group. Switching to a document that was
already open does not open a group in it.
When an agent followed the trace-and-style prompt step by step, it
failed at the second step: open_file leaves the image unselected, so
imageTrace.make returned "nothing selected". The text did not say that
the Default preset traces in black and white. No step expanded the
trace, so the source image stayed in the document. The set_paint step
gave every traced shape the same paint. Step 2 also said that
screenshots show the Image Trace view, while render_preview and
screenshot without window draw the tracing result. Step 1 said that
the image opens at its own pixel size, while an image that declares a
resolution opens at its size at that resolution.
The steps now select the image with select.all (or name its id), say
which presets keep colors, expand the trace with imageTrace.expand or
imageTrace.makeAndExpand, recolor with recolor.colors and recolor.apply,
and check the result with render_preview or screenshot. The prompt's
name, arguments and defaults are unchanged.
With Use Artboards and two or more artboards, EPS export now writes a
central file under the path's own name before the file of each artboard.
The central file contains the whole document, and its bounding box is
the visible art, as in an export without Use Artboards. An export of a
single chosen artboard still writes one file at the path's name, and
Export for Screens still writes one EPS per artboard. When no art shows,
only the artboard files are written.
Docs: the useArtboards params text, the MCP export tool's range text,
docs/mcp.md, docs/control-protocol.md and docs/target-app-parity.md.
With --connect, open_file, save_file, export and screenshot {path} sent a
relative path to the app unchanged, and the app resolved it against its
own working directory (/ when started from the Finder). A relative path
then named a different file than it does headless, where the server
resolves it against its own working directory.
The server now makes these paths absolute against its working directory
before it sends them, including export's options.path and the path a
screenshot reply reports. Paths inside run_command and command_batch
params go to the app as given. docs/mcp.md describes the rule.
ui.render, which render_preview and screenshot call in the headless
session and in the app, rendered the artboard with the canvas options:
8-bit compositing and edges smoothed shape by shape. A PNG export
composites in floating point and supersamples edges, so the preview
showed edge colors the exported file does not have, most visibly where
shapes meet edge to edge.
ui.render now renders with the options of a PNG export on white, so
its pixels equal the export's at the same scale. render_preview also
no longer enlarges an artboard smaller than max_side: with the default
max_side of 1024, a 612 x 792 page came back as an interpolated
791 x 1024 image, and it now comes back at 612 x 792. docs/mcp.md and
docs/control-protocol.md describe this.
The MCP drawing tools ran the creating command (path.create, shape.*)
and only then paint.setFill, paint.setStroke, stroke.set,
object.setProps and effect.apply, each as a command of its own.
check_draw_options checked dash, cap, join, opacity, name and effects up
front, but not the paints: a fill or stroke the engine can't read
(stroke: "currentColor"), or a swatch the document lacks, failed after
the object was made, so the call returned an error and left the path,
its undo step and its SVG node behind. Each styling command was also an
undo step of its own.
The tools now send the creating command and the styling commands as one
command.batch (crates/mcp/src/tools.rs, draw): the engine rolls the
whole batch back when a step fails, so a refused paint leaves no object,
no undo step and nothing in the SVG, and a good call is one undo step
named as the creating command names its own (Pen, Rectangle, …). The
styling steps act on the new object, which the creating command selects,
with item: null (its whole appearance), as the ids did before. The
reply gives the failed step's own error, without the batch's prefix.
docs/mcp.md says so.
Test: tests_drawopts::a_bad_paint_draws_nothing (invalid fill, stroke,
a missing swatch and a non-numeric strokeWidth on draw_path and
draw_shape: error, node count, undo history and SVG export unchanged;
a good call adds one undo step named Pen / Rectangle). It fails without
the change (draw_path with stroke "currentColor" drew a path) and passes
with it; the other draw option and command_batch tests still pass.
The reference app adds a new fill with Ctrl+/ on Windows and Command+/
on macOS, and it adds a new stroke with Ctrl+Alt+/ on Windows and
Option+Command+/ on macOS. VectorCraft had Add New Fill and Add New
Stroke on the Appearance panel, with no default key for either.
appearance.addFill now has the default Cmd+/ and appearance.addStroke
has Cmd+Alt+/, which are Ctrl+/ and Ctrl+Alt+/ on Windows and Linux.
Like the panel's buttons and menu items, each key runs its command with
no params and adds the item to every selected object. Keyboard Shortcuts
lists both under Window › Appearance with their keys, and press_key
runs them for agents.
Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.