Done ran text.spelling.removeWord and text.spelling.addWord one word at a
time, each saving the file, so a save that failed after a delete was saved
left that word deleted even after Cancel. text.spelling.editDictionary
{remove, add} makes every change, saves once and takes all of them back when
the save fails; Done runs it once.
Exports RGB, CMYK and grey JPGs at qualities 100, 80 and 50, at 1x and 2x,
and decodes each one. It also checks that only the CMYK files leave out the
JFIF segment (#3140). Covers #2096 too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Direct Selection showed and dragged the anchors of type on a path's path
(#3980) but not their direction handles: handle_anchors read only a
path's own data, and the canvas draws only a path's handles.
- text.setPathHandle moves a handle of type on a path's path, in
document space, as path.setHandle moves a path's; the brackets keep
their fractions and the text reflows. One undo step.
- Direct Selection draws the handles of selected type on a path and
drags them with it (Alt: one side, Shift: 45 degree steps).
The custom dictionary's words in a list, with an Entry field and Add, Delete
and Change, applied on Done, as the public Check and correct spelling page
describes it. The menu item moves from the Spelling submenu to the Edit menu,
where the page puts it.
Each command a script runs keeps a savepoint of the state it may need
to put back if it fails. The savepoint cloned every open document's
whole undo history, so a script that made N edits did O(N^2) work.
- The savepoint now clones each document's state without its history
and keeps a mark: the newest undo and redo steps and the serials of
the steps under them. Until the command ends, the history trims no
step, and a new step sets the redo steps aside instead of dropping
them. A failed command truncates both stacks back to the mark.
- file.close, file.closeAll, document.close and file.revert still keep
a whole copy, since they remove the history or the document.
- When a failed command removed or changed a step under the newest
one, the history cannot be put back exactly, and the run stops with
an error; its rollback restores the documents as they were before
the run.
- run_guarded no longer copies the histories during a script's run,
and a committed run moves the histories from before it back instead
of copying them.
- Symbol editing mode shares its saved redo steps between copies of a
document's state.
- A script cannot run edit.clearHistory. A run keeps each document's
undo steps from before it, so the command reported a clear that the
commit then undid.
Escape reset a rotated view only as the second of two presses less than
a second apart, and a click or another key between them started the
count again. With the Rotate View tool one press reset it.
Now an Escape that has nothing else to end resets the rotation. An
Escape that ends something else (isolation mode, a path being drawn, a
full screen mode, a dialog and the others listed in
docs/target-app-parity.md) ends only that, and the next Escape resets
the view, however long after it comes. Tools report a path or pick that
Escape ends with `Tool::ends_on_escape`.
The rotate view tests that needed two presses now check one press, and
the screen mode test that named the old pair is renamed.
Docs: docs/target-app-parity.md.
A document a script opens now counts the art in its graph designs and in
the original of the pattern being edited toward the growth allowance.
`Document::art_roots` and `art_roots_mut` list every tree of a document's
art; `any_node`, `edit_nodes_where` and the script's object count use them.
`script.run` checks the depth of `args` without recursion before it copies
or writes them out, so `args` nested 10,000 levels deep is refused with the
place it went past 64 levels instead of overflowing the stack.
New tests: graph-design art and the growth allowance, `args` nested 10,000
levels deep from a script (a RangeError) and from a caller, `run --cmd
script.run` on the command line with a report and with a thrown error, and
a property test that sends arbitrary source through `script.run`.
With Measure Type by Glyph Bounds on, point type is measured by its
glyph outlines and its underline and strikethrough bars, as the canvas
draws them. A typed W or H gives what is drawn that size, and underlined
or struck-through spaces are measured by their bars instead of the em
box.
The texts now say that each point type object selected as an object
(not inside a selected group) is measured this way, that moved type
snaps by its ink box, and which Arrange Tools commands follow the
option: Swap Objects, Replace with Top Object, Set Size of Each and
Sort Objects. Arrange in Grid and Group Overlapping Objects keep
measuring visual bounds.
engine.apiReference {} -> {markdown} writes a reference from the built-in
commands' parameter schemas each time it runs: a section per command that
has a schema, in registry order, with its label, menu path, API levels,
what it writes, cost, summary and return, and a table of its parameters
(type, range with included or excluded ends, unit, JSON or worked-out
default, required, levels and description; object members as dotted
rows). Commands without a schema, plug-in commands included, are left
out. Schema text is escaped so it stays plain text in its cell, control
characters become spaces, and a reference past 1 MiB is an error rather
than a cut-short text. The command needs no document.
MCP serves the same text as the fixed resource
vectorcraft://commands/reference (text/markdown; charset=utf-8); the
other resources still read as JSON.
The command's own schema raises the schema API level to 2. Its label and
its size error have rows in every catalog that needs them.
Docs: docs/mcp.md, docs/target-app-parity.md.
Export Selection, Export for Screens by selection or by layer, Asset
Export and CSS Properties' rasterized pictures put the objects on a new
plain layer. An object inside a clip group, or on a clipping layer, was
written without its clip, in SVG, PNG and the other formats, and the
opacity masks, effects, opacity and blend modes of the groups and
layers that held it were dropped too.
These exports now keep each object in the layers and groups that hold
it, and crop the file to the bounds the renderer measures for that art
(a clip group by its clip). Those layers are drawn even when they are
hidden, template or non-printing layers, as the objects were before. A
PSD of the export names its layers after the document's layers instead
of one layer named Selection or after the asset.
document.export with selectedOnly uses the same code: an object of a
blend, envelope, repeat or intertwine is now written on its own instead
of inside a blend of one object, and a Shaper group keeps its source
art. A text export of the selection still holds only the selected type,
not that of a type clipping path around it.
Measure Type by Glyph Bounds (`typeGlyphBounds`, off by default) is in
Preferences > Type > Glyph Bounds and the Transform panel menu. With it
on, the bounding box, the Transform, Control bar and Properties X, Y, W
and H, `object.setBounds`, the transform tools' reference point and the
Arrange commands measure point type selected on its own by the ink of
its glyphs instead of its em box. A typed W or H scales the live type
until its ink has that size.
- The engine caches each type object's ink in text space with its
layout bounds; the cache is not saved.
- Use Preview Bounds adds the characters' strokes, and type with
geometry effects is measured as drawn. Skewed type is measured by its
outlines.
- Type that draws nothing, area type, type on a path and type inside a
selected group are measured as before. Align and Distribute keep
following Align to Glyph Bounds.
Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md,
object.setBounds params text.
view.capsLock joins the state a run puts back when it fails or is
stopped. A finished run keeps the state it set, and keeps the state the
app reported during the run when the script did not change it. The
pointer over the canvas is refreshed when a run's end changes Caps Lock.
effect.setParams checks a Revolve before the edit, so turning a hidden Revolve back on measured
its profile after every geometry effect on the object, those after the Revolve included, and could
refuse a profile that draws. The check now measures the profile after only the geometry effects
before the Revolve, hidden or shown, as drawing does. An object holds at most one Revolve, hidden
ones included, so the check finds the same one that draws.
engine.commands, command_list and list_commands now list an inputSchema
next to schema for each command with a parameter schema. It is the same
JSON Schema 2020-12 schema with the root type "object", the form an MCP
tool's inputSchema takes
(https://modelcontextprotocol.io/specification/2026-07-28/schema);
schema keeps the root type ["object", "null"] when no parameter is
required. A command without a schema lists neither field. tools/list is
unchanged, and the commands are still run with command_run or
run_command.
brief now leaves out schema and inputSchema as well as the params text;
a listing filtered to the command's id gives them.
Docs: docs/mcp.md, docs/target-app-parity.md.
A document that document.open or file.new adds during a run counts whole
toward the growth allowance: every object in its layers, symbols and
patterns, opacity masks included, and its document data.