A command.batch that edited a document with an undo group and then a
document without one was dropped from the journal as a whole when the
group was canceled, so a replay lost the second document's edit, which
the session kept. Each batch step is now noted in the undo groups it
belongs to, by the same rule as a top-level command, and a cancel takes
only those steps out of the batch's journal entry. A step that changed
the active document becomes that change. Undo groups that stay open
follow the entry and step numbers that a cancel changes.
Two shapes that overlap by less than the region precision (0.01 pt) leave
their overlap out of the Shape Builder's regions as a sliver. Merging the
regions on both sides of it united only the picked faces, so the result
was a compound path with a hairline gap where the overlap had been.
A merge now takes in each dropped sliver next to a merged face when only
the merged faces' shapes fill it, both from the arrangement and from the
planar map used when open paths are selected. A thin cutout that no shape
fills stays a hole. What is left of a shape after the merge takes in a
sliver only where the shape covers it and the merged shape does not take
it in, so the two do not overlap. Pathfinder operations and Live Paint do
not use the merger and are unchanged; Live Paint's planar map does not
test which shapes cover a sliver.
In erase mode the tool tests each point for a line piece or an edge
within the tolerance before the region under it. Every point of a region
narrower than twice the tolerance is that close to its edges, so an
Alt-click, the Alt hover and an Alt-drag over a thin overlap touched its
edges and not the overlap, and the overlap was left behind.
A point in a region now touches the region when the region is no more
than twice the tolerance across there, measured from the nearest point
of its outline through the point to the outline on the other side. This
covers a drag at any angle, a drag that starts or ends in the region,
and a thin shape inside another or on its own. Near an edge of a wider
region, the edge is touched as before.
shapeBuilder.merge built the planar map of the selection again, on the
thread that runs the command, although the Shape Builder tool had just
built the same map for its hover. The tool now returns its map from a
new Tool::shape_builder_map, and the command uses that map when its
inputs are the merge's inputs and its gap is the merge's gap; otherwise
it builds its own as before. The tool's map has the outline edges, so
erasing and splitting a stroke use it too.
A group counted as a Live Paint group when its name started with "Live
Paint". A plain group named, for example, "Live Painting" was then read
as one. With it and another shape selected, the Shape Builder found no
regions in it, and a merge on the other shape deleted the group and its
paths. Make Live Paint took the group as a Live Paint group to merge
into, and its paths did not become sources.
A Live Paint group now also needs its sources group, the group named
"Live Paint Sources" that Make Live Paint puts in it. A plain group with
such a name is traversed like any other group.
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.
Use function paths where a closure only forwarded its argument
(PoisonError::into_inner, OsStr::to_str, FindRange::is_empty), and drop
redundant 'static on associated and module consts. No behaviour change.
Boxes that touch at -0.0/+0.0 now count as overlapping, so the pair cap cannot be skipped. try_boolean_n, try_unite_all and try_area report a refused input, and the Pathfinder shape-mode unite and the Blob Brush use them to show an error. When the planar map is over its budget, the fallback leaves out paths that fill nothing so a flood of cutting lines cannot take the filled shapes down with it.
Found while testing crates/engine/src/cmd/fileio/screens.rs:168-177 (safe_file_name)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/fileio/screens.rs:49 (ScreenSize::parse)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
useArtboards: false writes one file of the visible art even when artboards are named. With useArtboards absent, naming artboards writes the same files as useArtboards: true, including the no-art exception for the central file. The option description and the document.exportEps params text now say so, and the EPS test covers both cases.
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.
* Shortcuts: the keymap, overrides and dispatch come from craft-keymap
The editor's catalogue (commands, UI commands, panels, tools; one tool
group per tool; show/hide commands marked as toggles) builds a
craft-keymap Keymap. UiState::shortcut_overrides is a craft_keymap
Overrides stored through keymap::stored, which writes the same map of
entry key -> one string as before, with panel: keys and VectorCraft's
spellings, so earlier versions read the file; a value that is not text
drops that entry instead of every preference.
Key presses run through craft_keymap's Dispatcher with accept filters:
keys that type text go to the one-key text path, and while the Type
tool edits only command-key chords run. Held keys repeat. Conflicts
and assignment use the crate's conflicts and set_bindings. Extra Shift
no longer falls back to the chord without it.
* Shortcuts: .kys import matches and applies through craft-keymap
The review proposes targets with craft_keymap's namematch, and Apply
hands the ready rows to craft_keymap::plan as one set, each row's
target passed as the remembered answer for its entry. The later row
takes a chord, and commands outside the set lose the chords the set
gives. Rows with one name in one block keep their own targets.
The engine's own namematch module is removed.
* Shortcuts: Cmd+, beside other hits, web Mac platform, cached dialog view
- Pin craft-keymap at 362eb59f.
- Cmd+, opens Settings on a Mac after the frame's other shortcuts run, with the
Mac read from egui's OS; drop settings_chord, which dispatch no longer used.
- The web build follows the browser's OS, so Ctrl stays Ctrl on a Mac.
- The Keyboard Shortcuts dialog keeps its keymap, row texts and conflict count
between frames while the working set is unchanged.
- Every default shortcut text becomes its entry's binding (test).
- craft-keymap counts as a UI crate for the layer check.
- Translate "too many changed shortcuts"; the parse error reuses the existing
"can't parse shortcut `{c}`" message.
* Shortcuts: the chords the operating system takes can't be assigned
The keymap reserves craft_keymap::reserved::system for its platform:
window switching and closing, and the virtual consoles off the Mac.
The Keyboard Shortcuts dialog and shortcuts.set refuse them with
"<chord> is reserved by the system", and a saved override on one keeps
the entry's default. No default shortcut uses a reserved chord.
* Shortcuts: craft-keymap at craft-libs 2eca9b3a
craft-keymap comes from storytold/craft-libs (craft-libs #164 and #186), at the rev craft-pdf
already uses. The web build keeps the browser's full platform, so Windows browsers get Windows'
reserved chords, and each platform caches its own default keymap.
A misspelled parameter key got a did-you-mean suggestion, but a
misspelled command id only got "unknown command `select.al`", through
the CLI, MCP and the control channel alike. Session::execute now names
the nearest command id, using the same matching as parameter keys
(ignoring case), and action.batch does the same for an unknown step. An
id with no near match keeps the plain error.
The matching skips candidates whose length differs by more than the
allowed number of edits, so a very long unknown command id or parameter
key is answered at once instead of being measured against every command
id or accepted key.
The new message has a row in every catalog, and docs/mcp.md's
Parameter keys section shows it.
New Symbol and Redefine Symbol replaced a frame of threaded text by a
symbol instance whose art holds a copy of the frame. The thread's
re-flow then added the frame's text to the story, as it does for a
frame that was deleted, so the other frames held that text a second
time while the instance drew it.
A frame that New Symbol or Redefine Symbol puts in the symbol now
leaves its thread with the text it shows, and the other frames keep
the text they hold, as with Create Outlines, Expand and Flatten
Transparency. When the thread's last frame goes into the symbol, the
text it has no room for goes after the text of the last frame that
stays, and the copy in the symbol's art holds only the text the frame
shows. When every frame of a thread goes into the symbol, the copy of
the last frame keeps all its text. Undo restores the thread.
Docs: docs/mcp.md, docs/target-app-parity.md.
File > Data Recovery read the copy without marking the threaded frames
whose first paragraph continues from the frame before, so those frames
drew the paragraph's first-line indent and list marker again until the
next edit re-flowed the thread. The restored copy now gets the same
marks as an opened file.
In a file name pattern, <filename> is documented as the document's name
without its extension, but it expanded to the File Info title whenever the
document had one, so a saved my-logo.vectorcraft titled "Logo: final
version" exported as Logo--final-version__Artboard 1.png. The Full
Document file and the .zip took the same title.
Export for Screens and the dialog's Example line now take <filename> from
the document's file, as Save names it (save.rs), and fall back to the
title only for a document that has not been saved.
Object › Rasterize replaced a frame of threaded text by an image of
it. The thread's re-flow then added the frame's text to the story, as
it does for a frame that was deleted, so the other frames held that
text a second time while the image drew it.
A frame that Rasterize turns into an image now leaves its thread with
the text it shows, and the other frames keep the text they hold, as
with Create Outlines and Expand. When the thread's last frame is
rasterized, the text it has no room for goes after the text of the
last frame that stays. A thread left with one frame is no longer a
thread, and undo restores the thread.
Docs: docs/mcp.md, docs/target-app-parity.md.
Found while testing crates/engine/src/steps.rs:74 (parse)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With a Live Paint group in the selection, the Shape Builder took every
visible path in the group as an input. Unfilled faces became regions of
their own and came back from a merge as invisible paths. An edge that
closes on itself, such as the outline of a single rectangle, became a
second shape over its face: a click on the face gave the merged shape
the edge's look, a stroke with no fill.
The Shape Builder now takes the group's visible filled faces as shapes
and its visible stroked edges as open lines; a closed edge is opened at
its first anchor. A merge replaces the group with the merged shape, the
filled faces it did not use and the edges, except the edges between
merged faces, as it does for other selected paths and lines. An area
that the edges enclose around an unfilled face is still a region. The
Live Paint commands still collect every face and edge.
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.
Object › Flatten Transparency replaced a frame of threaded text by its
outlines with Convert All Text to Outlines, and by flat regions or an
image where the frame was transparent. The thread's re-flow then added
the frame's text to the story, as it does for a frame that was deleted,
so the other frames held that text a second time while the outlines,
regions or image drew it.
A frame that Flatten Transparency outlines or flattens now leaves its
thread with the text it shows, and the other frames keep the text they
hold, as with Create Outlines and Expand. When the thread's last frame
is flattened, the text it has no room for goes after the text of the
last frame that stays. A thread left with one frame is no longer a
thread, and undo restores the thread.
Docs: docs/mcp.md, docs/target-app-parity.md.
A merge removed every piece of an open line that had a merged region on
both sides. The end of a line that stops inside a shape has the same
region on both sides, so a click or a drag on the shape, or deleting it
in erase mode, cut the line back to where it enters the shape.
A piece is now removed only when it runs between two different merged
or deleted regions. A line that crosses a shape still splits it, and
merging the two halves still removes the piece between them.
A drag tested points spaced by a step that grew with the selection (at
least 0.25 pt), and the tool's highlight tested points 2 screen pixels
apart. A region narrower than the step, such as a thin overlap, fell
between samples: it kept its piece of each source, the merge left strips
behind, or the drag failed with "no region under the pointer" while the
highlight showed the region. Zoomed out, the highlight could miss a
region the merge then took.
The drag now finds where each segment crosses a region outline and tests
the middle of every stretch between crossings, so every region it
crosses is picked. The tool's highlight and shapeBuilder.merge use the
same picking, and Shaper's scribble picks the faces it crosses the same
way. In erase mode the line pieces and edges are still found within the
tolerance, and a segment that crosses one also tests the crossing point.
Shaper's scribble found the strokes to erase at points at least 0.5 pt
apart, so with a tolerance below 1 pt it could pass over a stroke
without erasing it. It now also tests where the scribble crosses each
stroke and points tolerance / 2 apart.
A paragraph split between two threaded frames took its first-line
indent again at the top of the next frame, and a list paragraph drew
its bullet or number there again. Each frame is laid out on its own,
and its first paragraph kept every paragraph attribute there.
A text object now marks a frame whose first paragraph continues from
the frame before (first_para_continues, not saved). The thread re-flow
sets the mark where each frame's share starts inside a paragraph and
measures each share with it, and a restored document reads the marks
from its saved text. Area type and type on a path set the first line of
such a paragraph as a wrapped line: no first-line indent and no bullet
or number. The paragraph after it takes the next number. Release
Selection, Remove Threading, Convert To Point Type and copies of a
frame clear the mark, so the first paragraph there has its indent and
bullet or number again. When Create Outlines, Expand or Expand
Appearance takes frames out of a thread, a frame that stays keeps the
mark only while it is threaded and the frames before it end inside a
paragraph. A frame whose mark changes lays out its first line again,
and when it is still threaded, its thread re-flows. When the outlined
frame is the last one, the text it has no room for is measured as the
frame lays it out: without the first-line indent when it continues a
paragraph, and with its numbers counted on from the frames before it.
The frame before it now takes exactly the text the last frame has no
room for. Duplicates, copies dragged in the Layers panel, the copies
that Release to Layers (Build), Release Opacity Mask and Edit Opacity
Mask make, and Draw Inside's clipping copy then have their cached
bounds computed again, as paste already does for pasted type.
Docs: docs/mcp.md, docs/target-app-parity.md.
pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters counted
every line containing Tj or TJ. The file ID in the trailer and the XMP is
base64 derived from the timestamp, so about one run in a hundred it spells
one of them and the test saw two extra lines (main CI, 5c1185dd). Only
lines that begin a text object count now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
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.
Review follow-up. A function-based shading is read once per import (kept
per shading pattern and inks), and all of them draw on one budget of
200,000 function evaluations; past it a shading comes in as its average
colour, then flat grey, each with a warning that names the cause. A
gradient read leaves enough of the budget to average another shading.
A colour that changes in step with the sampling grid is checked across
the middle of the domain before an axis is chosen, so it isn't read
along the wrong one. The grey fallback warning for other shadings names
only them.
Tests: the same shading on 2,000 paths is read once; 40 distinct
shadings stay within the budget; a hostile file (5,000 fills, 20
shadings with 500-step functions) opens in 0.8 s, 253 s before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Flat Logo traces a copy of the image at most 1024 pixels on a side,
averaged down by a whole factor, so a 1025-pixel image is traced at
513 pixels. The trace's warnings did not say so, because they compared
the image only with the decoded raster. For an image over 1500 pixels
they said instead that it is traced more slowly, which Flat Logo does
not do.
The trace result now carries Flat Logo's working size when it averaged
the image down. imageTrace.make and imageTrace.makeAndExpand then name
both sizes in their warnings, and the status bar shows that note, in
place of the slower-trace and megapixel notes. Other modes keep their
warnings. The message has a row in each catalog that translates the
other Image Trace warnings.
Live Paint builds its faces and edges from path outlines alone. Make,
Merge and the Live Paint Bucket put every selected object among the
group's hidden sources, so type, an image, a symbol, a blend or a group
holding one of them vanished from the canvas. They now refuse such a
selection before changing anything ("Live Paint takes paths and compound
paths: convert type, images and other objects to paths first"), as type
and raster images can't go into a Live Paint group directly
(https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/create-live-paint-groups.html).
One predicate, vectorcraft_tools::builder::live_paint_takes (Live Paint
groups, paths, compound paths and groups of them), decides it for Make,
Merge and the Properties panel's Merge Live Paint, which no longer shows
for a selection Merge refuses. The message has an @msg row in every
catalog.
Tests: tests_build::live_paint_refuses_type_and_images_and_changes_nothing
(Merge, Make and the Bucket with type, an image or a group holding type:
refused, document, selection and undo history unchanged; a group of
paths and a path still merge) and
panels::properties::tests::merge_live_paint_is_a_quick_action_for_a_live_paint_group_and_more
(hidden with type selected). Both fail without the change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>