Commit Graph
111 Commits
Author SHA1 Message Date
dotCooCoo 7c813e7171 Document: document.compare measures how far an artboard's render is from an image
Squashed from #3995 onto main.
2026-11-08 23:50:25 +07:00
dotCooCoo c6cb5d9de0 Commands: an id that is not an object id is refused by commands that otherwise use the selection
Commands that read `id` and otherwise act on the selection, the highlighted
layer row or a new object read it with a lenient helper, so `{"id": "abc"}`,
`true` or `{}` acted on the selection instead. They now read it with
`object_param`, which refuses any value that is not a non-negative integer
with "`id` must be a whole number" under the command's name and changes
nothing; an omitted or null `id` keeps the default.

Commands: object.isolate, layer.locate, layer.releaseToLayers,
graphicStyle.new and redefine, charStyle and paraStyle new, apply and
redefine, image.info, links.info, links.goTo, links.unembed, shaper.release,
shaper.expand (whose errors now carry their own name),
transparency.editOpacityMask, transparency.viewOpacityMask, symbol.edit, the
object.blend.spine commands, path.curvature and the type commands that read
type ranges.
2026-11-08 13:17:30 +07:00
dotCooCoo 864e606505 Commands: an id that is not an object id is refused instead of acting on the selection
targets() read `id` as an object only when it was a number. Any other
value ("abc", "5", true, {}, [1]) fell through to the selection, so a
command such as css.export, whose `id` the shared check does not read,
acted on the selected objects instead of failing. targets() now reads
`ids` and `id` through named_ids(): a value that is not a non-negative
integer is an error, and an omitted or null `id` still means the
selection. A numeric string is refused, as it already is in `ids`.

effect.apply and appearance.addEffect accept the effect as `id` when
`effect` is left out; they now read their targets with that `id` set to
null, so the effect still applies to the selection as before.
2026-11-08 13:17:30 +07:00
dotCooCoo 0ce3147aab History: the New Document button makes a document from the selected state
The History panel gets a New Document from State button at its bottom,
which runs the new edit.newDocumentFromState command. The command makes
a new document holding the art of a History panel row ({index?}, the
current state when omitted) and makes it the active document. An index
that names no state is an error and changes nothing.

The new document takes the next Untitled-N name, is marked unsaved, has
nothing selected, no steps to undo or redo and no modified date, and
leaves symbol, opacity-mask and pattern editing; the art drawn in a
pattern's tile is stored in the pattern, as Done stores it. Its links point at the files the
source finds, including files found in the source's folder away from the
links' paths. Those files are looked for only within the folders the
asking caller may read; for a caller without limits, within the folders
the caller that opened the source may read. The source
document, its steps and its saved state stay as they are; typing or a
drag in progress is first kept as a step, as Undo keeps it. While an IME
composes, the command waits, as Undo does.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-08 12:20:59 +07:00
FractalEncrypt cd88b15912 Shape tools: review follow-ups (Shift, handle boundary, cursor order, feedback)
- The Selection tool takes a Shift-press on a selected object's centre point
  too: a Shift-drag moves the selection held to 45° steps, and a Shift-click
  there takes the object out of the selection, as a Shift-press on the object
  does.
- One rotate zone for the Selection tool and the shape tools
  (`select::rotate_zone`): a press on a bounding-box handle is not a turn, so
  the shape tools' zone matches the Selection tool's classification exactly.
- The shape tools' hover cursor follows the press order (corner and pie widgets,
  then the centre point, then the rotate zone), and a move of the selection
  shows the Selection tool's feedback: SelectionCopy with Alt, ArrowHollow while
  it snaps.
- Engine-level tests for the whole gesture (`crates/engine/src/tests/shapetools.rs`):
  repeated previews and one undo step, Alt copy, Escape then release, a turn
  about the centre, the corner widget still rounding, and an unfilled shape
  moved by the Selection tool.
- docs: evidence in target-app-parity.md's Shapes bullet and the M2 milestone,
  and the Move cursor's entry names the copy and snap cursors.
2026-11-07 05:15:03 +07:00
NetterSilverandClaude Haiku 5.5 0001a1c702 Disable Object > Path items when no path is selected
Fixes #5796

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 21:34:32 +07:00
ruchirguitarandClaude Opus 5.5 e0645341a4 Document tabs: drag a tab along the strip to reorder the open documents (#5663)
A document tab dragged sideways along the tab strip moves its document to the
place an insertion line marks; the current document stays current. Dragged
above or below the strip, the tab still tears off into a floating window. The
views Window > New Window opened move with their document, and each document
keeps its own zoom, centre and artboard.

The move is the engine command `document.move {from?, to}`, so the control
channel, MCP and recordings reach it, and a replay moves the document before
the steps recorded after it. `sync_views` keeps the views with their documents
by uid when the documents change order.

Behaviour: https://helpx.adobe.com/illustrator/using/customizing-workspace.html

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:02:46 +07:00
Don Omondi f3faa115c6 Fix #3226: copy and paste ruler guides
Edit > Copy and Cut put the selected ruler guides on the clipboard,
with the selected objects or on their own. Pasting puts them on the
layer new art goes to, selected, in the same undo step: with objects
they move as the objects do; alone they keep their places, while Paste
in Place / in Front / in Back put them on the active artboard as they
lay on theirs, and Paste on All Artboards on every artboard.
2026-11-04 20:11:59 +07:00
dotCooCoo 146d30502a Expand: selected symbol instances become their art, as Break Link places it
Object > Expand with Object on now turns each symbol instance in the
selection, including one in a selected group or in a placed document's
art, into its art, as Break Link to Symbol places it. It goes one level
deep: the instances in that art stay instances. The art keeps the
instance's id and place in the stacking order, and Expand stays one
undo step.

- Instances with effects stay instances, and Expand Appearance bakes
  them as before. Instances with an effect on a fill or stroke,
  instances in perspective, and those in blends, repeats and Intertwine
  groups stay instances.
- The art keeps the instance's opacity mask, as Break Link gives it.
- A hidden or locked instance's art stays hidden or locked.
- A placed document in an instance's art is expanded too.
- When the instances' art would make more than 1,000,000 objects, they
  stay instances, the limit Break Link uses.
- The Fill and Stroke options (object.expand.info, and the Expand
  dialog) count the fills, strokes and gradients in the art of those
  instances, with a dynamic instance's own changes applied. The info
  reports under symbolArt which of them only that art holds, and the
  dialog enables those options only while Object is on.
2026-11-04 15:14:02 +07:00
Don Omondi 4f60ee47e8 Fix #2920: an open path in a PDF-compatible .ai opens as drawn
Squashed from #4616 onto main.
2026-11-04 10:25:55 +07:00
dotCooCoo 83f2bfe9d6 Symbols: a scaled instance of a symbol with 9-slice guides keeps its corners and edges unstretched
Squashed from #5079 onto main.
2026-11-02 15:22:07 +07:00
Don Omondi 046e443944 Fix #5054: a double-click on a repeat isolates it to edit its source
A double-click on a Radial, Grid or Mirror Repeat with the Selection
tool enters isolation mode on it. There its source art is selected and
edited as usual (a double-click on type in it switches to the Type tool
on that text) and every copy follows, the repeat keeping its settings.
2026-11-02 14:36:14 +07:00
dotCooCoo 6be88a24b4 Fix #785: commands refuse an id that isn't an object id instead of using the selection
Commands whose `id` or `ids` the shared check in Session::execute does not
read still parsed them with id_param/ids_param, which drop anything that is
not a non-negative integer, and then acted on the selection or the current
layer. `object.blend.make {"ids": 3}` blended the selection,
`object.envelope.makeWithWarp {"ids": [2, "x"]}` warped the selection,
`graph.setData {"id": "2"}` changed the selected graph and
`layer.newSublayer {"parent": "2"}` added a sublayer to the current layer.

- cmd::named_ids and cmd::named_id read `ids`/`id` as targets does: a value
  that is not an object id, or an unresolved step reference, is an error.
- The symbol and brush, Live Paint, Shape Builder and Image Trace, blend,
  envelope and mesh, perspective, Liquify, Puppet Warp and graph helpers use
  them in place of the lenient readers.
- layer.clippingMask.toggle checks `id`, and layer.newSublayer checks
  `parent`, with checked_id before falling back to the highlighted row or the
  current layer.
2026-11-02 14:32:49 +07:00
dotCooCoo ff036557fb Symbols: Edit Symbol opens the symbol in its own editing mode, and every instance follows each edit
Squashed from #5064 onto main.
2026-11-01 11:09:03 +07:00
bflatastic f22caa6108 Fix #4938: Dynamic symbols keep per-instance appearance linked to the symbol 2026-11-01 04:53:38 +07:00
marshiyar 4861fb9d85 Engine guard: a recovered panic restores every document's undo history and the active document (#3599)
run_guarded put back only the active document's art, selection and interaction.
An undo step pushed before the panic stayed (Undo then stepped to a state equal
to the current one) and Redo was lost; other documents a command touched kept
their half-applied edits; the active document was restored by index.

It now snapshots every open document with its history and restores them, and
the active document by uid.

Fixes #3599
2026-10-31 19:43:17 +07:00
ashokram-si 7a2fc777f4 Gradient panel: presets to apply, save and delete (G13)
People reuse the same gradients across documents and rebuilt them by
hand. The Gradient panel now has a Presets menu: the presets of the
app's built-in gradient libraries, grouped by library (theme), then
the saved ones. Choosing one gives the gradient behind the active
proxy (or a solid paint) the preset's type, stops, method and dither,
keeping its placement, as one undo step, through paint.editGradient.
The panel menu saves the current gradient as a preset (named in a
dialog) and deletes saved ones; Hide/Show Presets toggles the row.

Saved presets are kept with the preferences, next to the other user
preset lists (width profiles, flattener, perspective). Stops linked to
a document swatch keep their colour, unlinked, since a preset is used
in other documents. Built-in presets can't be deleted; names are
unique.

Commands: gradient.presets.list, .apply, .save and .delete. The menu's
rows are drawn only while in view, as its headings are.

Tests: engine tests for list, apply (fill and stroke, placement kept,
one undo step), save (unique names, unlinked swatch stops) and delete;
panel tests for applying from the menu and saving, deleting and
hiding presets from the panel menu, including scrolling the menu to
the saved presets. Docs: gaps G13, target-app and UI parity, ROADMAP.
2026-10-30 19:50:25 +07:00
bflatastic 513b02233f Align: Align to Glyph Bounds for point and area type (#4231) 2026-10-30 02:01:58 +07:00
bflatastic 5165b6eb1d Pathfinder: live compound shapes (Make, Shape Mode, Release, Expand Compound Shape)
Squashed from #4621 onto main.
2026-10-30 00:15:58 +07:00
dotCooCoo 1538733954 Images: a fill color colorizes grayscale and 1-bit images
Squashed from #4431 onto main.
2026-10-30 00:15:57 +07:00
dotCooCoo 11d65eb8a8 Flare: the drag turns the rays, Shift keeps their orientation and Space moves the flare
The reference app's Flare tool rotates the rays as the center is dragged
out, Shift constrains them to their original orientation, and Space
held moves the shape being drawn. VectorCraft's Flare tool drew the
rays in one fixed orientation and ignored Shift and Space.

shape.flare takes rotation (degrees, clockwise), which turns the rays
about the center and leaves the halo, the center and the rings as they
are. While the center is dragged out, the rays turn with the pointer,
as a polygon's first corner does, and a drag straight up leaves them
unturned. Shift keeps them unturned, or turned by General › Constrain
Angle as a polygon drawn with Shift is. Space held during the center
drag or the drag that places the rings' end point moves the flare at
its current size, and the drag goes on from there when Space is let
go, as with the shape tools.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 20:47:47 +07:00
bflatastic efc1501ac9 Path: Object › Path › Smooth smooths the selected paths with a slider and Auto-Smooth (#4115) 2026-10-29 13:51:51 +07:00
dotCooCoo 02f2dd8c5a Object: Unlock All and Show All with Shift+Alt act only within the selected object's group
Squashed from #4439 onto main.
2026-10-29 11:18:38 +07:00
dotCooCoo c3af6605ef Type: Undo and Redo put the caret where the undone or redone edit was
While the Type tool edits type, Edit > Undo and Redo (the menu, the
keyboard, the History panel and MCP all run edit.undo and edit.redo)
left the caret at its byte offset, clamped to the text. Typing "abc"
before "XYZ" and pressing Undo left the caret after "XYZ".

The engine now tells the active tool about edit.undo and edit.redo
before it finishes the command. The Type tool keeps the edited text and
a list of the steps that changed it, and puts the caret, with nothing
selected, at the end of the span the step changed: where typed text
began after its Undo, after restored text after the Undo of a deletion,
and after retyped text after Redo. For the steps the tool made, it keeps
where the span ended, so text that repeats the characters next to it
puts the caret at the right end. A step that changes no characters,
such as a formatting change, leaves the caret and the selection as they
are. Typed lists keep their caret positions.
2026-10-29 10:10:58 +07:00
aiongg 83eec43b18 refactor(engine): move tests_*.rs to tests/<topic>.rs 2026-10-26 18:26:56 +00:00
Brandon Thomas dda90f0f4f Merge pull request #4060 from dotCooCoo/feat/unknown-command-suggestion
Commands: an unknown command id is answered with the nearest command id
2026-10-25 10:37:54 -04:00
dotCooCoo 6e6fbbac1d Commands: an unknown command id is answered with the nearest command id
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.
2026-10-24 11:20:27 -07:00
Marco Arnulfo 7a38c39a96 Properties and Transform: X, Y, W and H of a compound path's member set only that path (fixes #4046) 2026-10-24 19:53:46 +02:00
dotCooCoo dbc1b69f1c Find Objects: document.find filters by bounds, size and anchor count and can select the matches
document.find took only name, kind and text, so removing hairline slivers or
every object in one region needed the whole inspect tree and a filter
written outside the app. It now also takes within and intersects
([x, y, width, height] in document points), min and max width, height,
thickness (the smaller side) and area of the geometric bounds, and min and
max anchor count (a compound path counts its members together). Each given
filter must match, and limits are inclusive.

select: true also selects the matches, leaving out hidden and locked
objects, matches inside another match, and compound path members unless
the compound path is isolated; the reply adds selected. Select > Reselect
repeats the find, and an action being recorded records a find that
selects. The count that select.color, the Select > Same commands and the
Select > Object commands report is now the number of objects selected,
after Lock Guides leaves out guide paths.
2026-10-24 17:46:13 +07:00
dotCooCoo 2db1ada660 Open and Place: a file read in the background waits for a busy tool only during a drag
A file opened or placed off the UI thread waited while the active tool
reported busy. The Dimension tool also reports busy between its clicks,
so after one click a ready file stayed queued with its spinner until
the dimension was finished or canceled. Files also waited between the
clicks of the Blend tool.

The session now records whether the pointer button is held. A file
waits for a busy tool only while the button is held. A file that
opens between a tool's clicks resets the tool, as a switch of
documents does; a file placed in the active document leaves the
tool's clicks in place. Files still wait while a dialog is open, a
drag is under way or a preview is open, and a tool's preview between
its clicks counts as one (the Flare tool after its center drag, the
Dimension tool's line once the pointer moves after its second click).
2026-10-24 02:05:19 +07:00
bflatastic 1b7cfc8bee Automation: --automation-read-root and --automation-write-root may each be given more than once (#3546)
Squashed from #3631 onto main.
2026-10-22 21:36:15 +07:00
dotCooCoo bfc3f5251a Commands: path.create with path data refuses closed given as text, and the grids refuse a count
path.create read closed only when it drew from anchors. Given path
data, {"d": "M0 0 L10 0 L10 10", "closed": "true"} drew the path and
recorded an undo step, while the same closed with anchors fails with
"`closed` must be true or false". shape.rectangularGrid and
shape.polarGrid read rows, columns, concentric and radial given as text
as the count left out, so {"rows": "3"} drew the default 5 horizontal
dividers instead of 3.

path.create now checks closed before it reads d or anchors, and the
grid commands refuse such a count with "`rows` must be a number, not
"3"". Neither command changes the document or its undo history then.
With d, a closed of true, false or null still leaves the path as its
path data draws it.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-22 08:24:09 +07:00
dotCooCoo caa6962508 Commands: Transform Each, artboards and more refuse a boolean or number given as text
object.transformEach refused reflectX, reflectY, random and copy given
as text, but read strokes, corners, objects and patterns given as text
as the key left out. With Scale Strokes & Effects on,
{"scaleH": 200, "scaleV": 200, "strokes": "false"} scaled the stroke,
recorded an undo step and kept the params for Transform Again, which
repeated the call. artboard.rearrange, artboard.move and
artboard.duplicate read their patterns option the same way, and
artboard.setProps reads its patterns, strokes and corners options the
same way.

These commands now refuse such a value with "`<key>` must be true or
false" before they change anything. So do the other options and
numbers of artboard.setProps and artboard.move, the art of Duplicate,
Copy and Cut Artboard, the shape commands and path.create, path.close,
path.setHandle, path.moveAnchors, the Pencil, Curvature, Reshape
Segment and Blob Brush options, text.create, text.createInPath,
text.setRangeStyle, text.editRange, text.getRange, text.touchType,
text.areaOptions, type.step, the font size steps,
type.opticalMarginAlignment, type.systemLayout, type.fractionalWidths,
type.ruby, the symbol and swatch commands, the Appearance panel's
commands, the gradient and freeform gradient commands, Shape Builder,
Live Paint, Image Trace, the opacity of object.setProps, the radius,
sides and relativeRadii of object.setLiveShape, the reference point of
object.setBounds and the artboard of Align, Distribute and Distribute
Spacing. They refuse a number given as text with "`<key>` must be a
number, not <value>", and a gradient stop's tint with "stop <n>:
`tint` must be a number". A null still reads as the key left out.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-22 06:40:27 +07:00
bflatastic e7cdf154ca Commands: a boolean or number given as text is refused
Squashed from #3428 onto main.
2026-10-22 00:10:57 +07:00
bflatastic e5471be4d9 Commands: an index of 2^32 or more no longer wraps around on 32-bit builds
Squashed from #3292 onto main.
2026-10-21 23:38:03 +07:00
nrq6993-a11yandCursor ab0ddf0eae G1: Live Corners relative rounding (#1144)
Store corner radii as fractions of each corner's max so rounding scales with the shape; expose via Corners dialog and object.setLiveShape relativeRadii.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 23:38:03 +07:00
dotCooCoo 6a1c8eda46 Part of #1816: Snap to Grid lands a moved object by its nearest edge and no longer snaps its center
With View › Snap to Grid on, a whole object moved with the Selection,
Group Selection or Direct Selection tool landed on the grid by
whichever of its two edges or its center was nearest a gridline on
each axis. An object on the grid whose width is an odd number of grid
steps has its center half a step between gridlines, so a short drag
could pull it half a step off the grid: on a 20 pt grid, a 100 pt
square at (100, 100) dragged by (8, 8) moved by (10, 10).

A move now lands the nearer of the two edges on a gridline on each
axis, as the reference app's help describes, and the center no longer
snaps. The square above stays at (100, 100). This changes the rule #769
chose for #740. The plain move and the Shift-constrained one both use
grid_pull, so both change.

Docs: docs/target-app-parity.md (Drawing aids).
2026-10-21 16:55:17 +07:00
dotCooCoo bdf54007a0 Isolation mode: object.isolate refuses hidden or locked objects and locked guide paths
A click with the Selection tool does not select a hidden or locked
object, an object in a hidden or locked group or layer, or a guide path
while Lock Guides is on, but object.isolate isolated each of them. Once
a locked or hidden group, or a group on a locked or hidden layer, was
isolated, a Selection tool drag moved the member under the pointer. The
Layers panel's Enter Isolation Mode sent the same command for a locked
or hidden layer or group.

object.isolate now refuses a hidden or locked object, or one in a
hidden or locked group or layer, with "the object is hidden or locked"
(the text of layer.target), and a guide path while Lock Guides is on
with "guides are locked". Isolation mode stays off, and the selection
stays as it is.

Docs: docs/mcp.md.
2026-10-21 16:46:57 +07:00
dotCooCoo cc8944ad2a Transform Each, Rasterize, Mosaic, Trim Marks and Rearrange: a number given as text is refused
Transform Each, Rasterize, Create Object Mosaic, Create Trim Marks and
Rearrange All Artboards read their optional numbers with f64_or, which
falls back to a default for a value that is not a JSON number.
object.transformEach {"scaleH": "50"} left the selected objects at
their size and reported success. Rasterize did the same with ppi,
Create Object Mosaic did the same with width, height, spacingX and
spacingY, Create Trim Marks did the same with offset, length, bleed and
weight, and Rearrange All Artboards did the same with spacing. Crop
Image cropped to the artboard when rect was a string or another value
that is not an array.

These commands now fail with "`<key>` must be a number, not <value>"
for a value that is not a number or null, as the transform commands
do, and change nothing. Transform Each checks scaleH, scaleV, moveH,
moveV and rotate. Crop Image refuses a rect that is not an array of
four numbers with "rect must be [x, y, width, height]", the message it
already gave for an array of the wrong length; a null rect crops to the
artboard, as a missing one does.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-19 00:03:46 +07:00
bflatastic 4f526b4e3f Reshape: the lengths along the path are measured once per drag, and the default reach stays the path's size (#2900)
anchor_arc_distance measured every segment again for each anchor, so a reshape cost the square of the anchor count; path.reshape now measures the positions once, and the two helpers only it used are gone. The default radius stays half the subpath's bounding-box diagonal, now measured along the path: half its length reached the far corners of a square, moving art the grab is nowhere near (reshape_moves_the_grabbed_point_and_its_neighbourhood).
2026-10-18 20:12:47 +07:00
nrq6993-a11yandCursor e8e88c14f2 Fix #2723: Reshape falloff uses arc length along the path
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 20:12:47 +07:00
dotCooCoo 034ef916ad Selection: a Shift-constrained move that snaps stays on its line
A move that Shift kept level, upright or on a diagonal left that line
when it snapped. With View › Snap to Grid on, a level Shift-drag also
moved the selection up or down onto a gridline, and Snap to Pixel,
Smart Guides and Snap to Point moved it off the line in the same way.
Each axis snapped on its own after Shift had constrained the move.

With the Selection tool, and with Group Selection and Direct Selection
moving whole objects, a Shift-constrained move now slides along its
line until it snaps. With Snap to Grid or Snap to Pixel, the move
slides along the line by the shorter of the two distances that put it
on a gridline or a whole pixel on either axis. The bounds line up with
the other objects, or space evenly with them, only on the axes the line
runs along, and the grabbed point stops where the line passes an anchor
or crosses a ruler guide. A level or upright move snaps along its own
axis only. Moves without Shift snap as before.

Docs: docs/target-app-parity.md.
2026-10-18 12:40:10 +07:00
dotCooCoo 1caa957531 History: the panel menu clears the history
The History panel menu had no Clear History, and VectorCraft had no
command that removes the undo and redo steps. In the reference app,
Clear History in the History panel menu removes the states after the
selected state, and it removes the states before it while their
changes remain in the document.

edit.clearHistory (Clear History) removes every step to undo and to
redo in the active document and returns how many it removed. The
document and the selection stay as they are, and clearing is not an
undo step. Typing or a drag still in progress becomes a step first, as
Undo makes it one, so its change stays in the document. The command is
disabled when there is no step to clear, and it waits while an input
method composes, as Undo does.

The History panel menu has Clear History after Step Forward. It runs
the command and is disabled when there is no step to clear.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 12:39:07 +07:00
dotCooCoo 23dd9a1f2a Fix #2054: bounding-box resize handles snap to the grid, pixels, points and ruler guides
A bounding-box handle dragged with the Selection or Free Transform tool
snapped only with Smart Guides on, and then only to the other objects
and the artboards. View > Snap to Grid, Snap to Pixel, Snap to Point
with Smart Guides off and the ruler guides did not move it, while a
move snaps to all of them.

The corner or side a handle moves now snaps as a dragged point does.
With Snap to Grid on it lines up with the nearest gridline, and with
Snap to Pixel on it lines up with whole pixels. With Smart Guides off,
Snap to Point lands it on the other objects' anchors and on ruler
guides within its distance. With Smart Guides and Snap to Point both
on, it lines up with the ruler guides as well as with the other
objects. Shift still keeps the proportions. Without Alt the opposite
side stays put, and with Alt the box still scales about its center.

Docs: docs/target-app-parity.md.
2026-10-18 01:27:14 +07:00
bflatastic 546b8846b6 Part of #2441: Group Selection picks and moves one path of a compound path
A click with the Group Selection tool on a compound path selects the path
under the pointer (its outline within the pick tolerance, else the innermost
path whose area holds the click), so the outer shape and each hole can be
selected on their own; Shift-click adds another, and a click on a selected
path adds the compound path, as a click on a group member climbs to its group.

A path selected this way is an object of its own for Move, Transform, Rotate
and the other transforms, as it already was inside an isolated compound path:
the hole of a ring moves without the ring. Paint and the other commands still
go to the compound path, which holds the paint.

Docs: docs/target-app-parity.md, docs/ui-parity.md.

# Conflicts:
#	docs/target-app-parity.md
2026-10-17 17:13:03 +07:00
dotCooCoo d18135bfcd Object Mosaic: columns and rows that are not whole numbers are refused
object.createObjectMosaic read columns and rows with as_u64 and fell
back to 10 for anything else, so 2.5, -3 or "4" made a 10 x 10 mosaic
and reported success. Transform Each read reference and seed the same
way (a reference of 2.5 used the center point, and a seed of 7.5 used
the default seed), and Rearrange All Artboards cut 1.5 columns to 1
and read "1" as the default 2.

These five parameters now take a whole number, written 4 or 4.0 as the
Rearrange All Artboards dialog sends it. A fraction or a negative
number is refused with "`columns` must be a whole number", the message
object.slice.divide gives. A string or another value that is not a
number is refused with the check_numbers error. The command then
changes nothing. The existing ranges stay: 1 to 1000 mosaic tiles
across and down, a reference of at most 8, and 1 to the artboard
count of columns.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 16:38:42 +07:00
vangel76andClaude Opus 5.5 1eb9bea82a Inspect: scope document.inspect / doc_inspect by ids, kind, limit and offset (#2167)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:49:48 +07:00
dotCooCoo ebc2857549 Align: Distribute Objects uses the spacing value with a key object
With a key object, the Align panel's six Distribute Objects buttons
ignored the Distribute Spacing value. They kept the outermost objects
in place, spaced the others evenly between them and could move the
key object.

object.distribute now takes spacing. While the selection has a key
object, the key object stays in place, with the compound path or
selected group that contains it, and the objects' left edges, centers
or right edges (top edges, centers or bottom edges) end up spacing
points apart, outward from the key object's, in their order on that
line. With 0, the field's default, every object ends on the key
object's line. Two objects are enough, and to and artboard are not
used. The panel sends the value with the Distribute Objects buttons
while there is a key object, as it does with the Distribute Spacing
buttons. Without a key object, or with anchor points selected,
object.distribute does not use spacing and works as before. A spacing
that is not a number is an error.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 14:58:08 +07:00
Marco Arnulfo 80b7aee756 Isolation mode: a path of the isolated compound path moves on its own (fixes #2354) 2026-10-16 14:57:22 +07:00
Gert Pellin e6170de1a2 Transform commands: a string for an optional number is an error (#1952)
object.setBounds read x, y, width and height with f64_or, which falls back
to the current value for anything that isn't a JSON number, so
{"width": "60 mm"} returned ok and changed nothing. object.move,
object.nudge, object.rotate, object.scale (sy) and object.shear did the
same with their optional numbers. A value that is neither a number nor
null is now a BadParams error naming the key, as a missing required
number already is.
2026-10-16 02:56:35 +07:00