Commit Graph
5641 Commits
Author SHA1 Message Date
dotCooCoo c9cc2ef076 Image Trace: Document Library keeps neutral tones off saturated library colors
With the Document Library palette, each pixel took the library color
nearest in plain Oklab distance. A 50% gray lies nearer in that distance
to a saturated blue of the same lightness than to black or a light
cream, and a muted brown is also nearer to the blue than to either. With
a black, cream and blue library, both traced as the blue.

This changes how Document Library matches pixels: each pixel now takes
the library color nearest with differences in hue and chroma (Oklab a
and b) counted three times as much as lightness. The same measure picks
which library colors are used. Neutral and muted tones take a neutral
library color, and the other palettes are unchanged.
2026-10-26 09:09:45 +07:00
dotCooCoo 0629d4e9a5 Image Trace: Overlapping layers reach only under areas that hold their own color
With Method Overlapping, each color layer covered every pixel of the colors
above it in the stack. A small dot or square on a background was then traced
once in its own color and again, at the same bounds, in every color between
the background and it, and with Shapes on each copy became its own live shape.
Those layers also ended on edges they never touched and showed through the
anti-aliased rim there.

Each layer now leaves out every 4-connected part of its stacked area that holds
no pixel of its own color. This changes the result of many Overlapping traces,
the photo presets included: fewer paths (High Fidelity Photo 121 to 69.6 on its
own quality inputs, Low Fidelity Photo 37.1 to 26.1) and no rim, around a
shape, of a color that sits apart from it. One existing test that counted the
extra copies now expects one layer per area, and another that compared layer
pixel counts now checks that the bottom layer covers the whole image.
2026-10-26 09:09:45 +07:00
dotCooCoo 2bbbdafc63 Image Trace: the eye button's hold ends on release, and the panel uses any selected trace or image
Squashed from #4076 onto main.
2026-10-26 09:09:45 +07:00
dotCooCoo 6c9ac54b22 Shape Builder: a merge across an overlap thinner than the precision gives one path
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.
2026-10-26 09:09:45 +07:00
dotCooCoo 529a2296a7 Shape Builder: erasing near the end of a thin region with split sides picks the region
When both sides of a 1.5 pt bar have a point level with a click 0.1 pt
from its end, the point of each side nearest the click is the end of a
segment, so the sides were not tried and an Alt-click there touched the
end edge.

A segment end now also counts when the segment is square there to the
line to the point, so splitting a side at the click gives the same
answer as an unsplit side.
2026-10-26 09:09:44 +07:00
dotCooCoo 62e562058b Shape Builder: erasing near the end of a thin region picks the region
A point 0.1 pt from the end of a 1.5 pt bar is nearest the end of the
bar, and the line from there through the point runs along the bar, so
the region did not count as narrow and an Alt-click there touched the
end edge.

A region now counts as narrow at a point when it is no more than twice
the tolerance across from any outline segment within the tolerance whose
point square to the line to the point lies inside the segment, as well
as from the nearest point of the outline. Segment ends other than the
nearest point are not tried, so a point added on an edge does not change
the result near a corner.
2026-10-26 09:09:44 +07:00
dotCooCoo b92f394c0b Shape Builder: erasing picks a region narrower than twice the tolerance
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.
2026-10-26 09:09:44 +07:00
Davit MatchakhelidzeandClaude Opus 5.5 5e68d5891c i18n(ka): shorter wording for labels that were cut off
Decided in the native-speaker review of the screens (all-t01).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-26 09:09:44 +07:00
dotCooCoo c527082ff1 Shape Builder: a click merges over the map the tool built for the hover
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.
2026-10-26 09:09:44 +07:00
Angelo BernardiniandClaude Fable 5.1 a5806c8caa G11: the grid budget counts an item's distinct cells; parity row and evidence
CodeRabbit on #4080: `needed` summed footprint pieces before insertion
deduplicated them, so on a degenerate grid (ten collinear lines, 4,096 pieces
each in ten cells) every item exceeded the budget and went to the scanned list,
making the per-cell assertion vacuous. The cells are now collected in piece
order and consecutive repeats dropped before the budget check and the filing;
the horizontal fixture asserts `rest` is empty and that the lines are filed.
docs/target-app-parity.md: the Path operations area row, the Shape Builder
row and the evidence list record the bounded index (scores unchanged).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014gCpjL3yUhaH3jRp7ksufh
2026-10-26 09:09:44 +07:00
Angelo BernardiniandClaude Fable 5.1 6f8ac34f38 G11: Shape Builder's edge-source grid holds a bounded number of references
Follow-up to #4021. The grid filed each piece of a segment under its cells
without checking, so a degenerate grid (collinear horizontal lines: cells a
hundred points wide and a hair tall) stored one segment ~400 times per cell,
and coincident vertical segments (one column, a row per segment) filed every
segment under every row: a quadratic number of references, about a GiB for
16,384 segments within the Shape Builder's limit.

Now a segment is filed once per cell (its pieces come in order, so the cell's
last entry is enough to check), and the grid holds a budget of references
(64 per item plus four rows' or columns' worth, `reference_budget`); the
items past it go to the scanned list, so the index stays O(n·sqrt(n)) in
memory and every lookup still finds every segment through its point.

Regression: `rect_index_holds_a_bounded_number_of_references` (fails without
the change: 4,100 references per cell for ten collinear lines).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014gCpjL3yUhaH3jRp7ksufh
2026-10-26 09:09:44 +07:00
dotCooCoo 3567e5be7d Live Paint: a plain group named "Live Paint..." is not a Live Paint group
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.
2026-10-26 09:09:44 +07:00
acosimati 03ab7bb805 MCP: doc_inspect reports opacityPct in the percent scale the drawing tools take
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.
2026-10-26 09:09:44 +07:00
bflatastic aadf73becd CLI help: one space before --editing-data, as before #4147 2026-10-26 09:09:44 +07:00
nrq6993-a11y 1579b65b07 convert: attachment note from the export result's warnings, not a file scan 2026-10-26 09:09:44 +07:00
Cursor Agentandnrq6993-a11y 1217313e8a Fix #3627: convert to PDF omits editing data by default
## Summary
`vectorcraft-cli convert` to PDF now passes `preserveEditing: false`, matching
a command-line converter expectation while Save As PDF in the app still uses
the VectorCraft Default preset. `--preserve-editing` opts in and adds a JSON
warning when the native document attachment is written.

## Tests
Integration test in `vectorcraft-cli` checks default PDF output has no editing
attachment and `--preserve-editing` embeds it with a warning.

Closes storytold/vectorcraft#3627

Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
2026-10-26 09:09:44 +07:00
Cursor Agentandnrq6993-a11y 4e0c422447 Fix duplicate test attribute in fileio test
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
2026-10-26 09:09:44 +07:00
Cursor Agentandnrq6993-a11y 7855d8ede4 Fix #3627: convert to PDF omits editing data by default
## Summary
`vectorcraft-cli convert` to PDF now passes `preserveEditing: false`, matching
a command-line converter expectation while Save As PDF in the app still uses
the VectorCraft Default preset. `--preserve-editing` opts in and adds a JSON
warning when the native document attachment is written.

## Tests
Integration test in `vectorcraft-cli` checks default PDF output has no editing
attachment and `--preserve-editing` embeds it with a warning.

Closes storytold/vectorcraft#3627

Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
2026-10-26 09:09:44 +07:00
nrq6993-a11y 94832657d1 Star label: R2 from the preview's ratio, not the held radius 2026-10-26 09:09:44 +07:00
Cursor Agentandnrq6993-a11y f3205e7344 G1: Star tool shows inner and outer radii in measurement labels
## Summary
While dragging with the Star tool, the measurement label lists the outer
radius (R1) and inner radius (R2) from the centre to the pointer, matching
the radii used for the live preview (including Ctrl/Cmd holding the inner
radius and Alt straightening the points).

## Tests
- `star_drag_measure_label_shows_outer_and_inner_radii` in
  `crates/tools/src/shape.rs`.

## Sources
- https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-live-shapes/change-inner-and-outer-radius-of-stars.html — R1 is the outer radius and R2 the inner radius of a star.
- https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html — while dragging a star, Command/Ctrl keeps the inner radius constant as the outer radius follows the pointer.

Part of gap G1

Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
2026-10-26 09:09:44 +07:00
Cursor Agent 142942e6c4 G1: Shear tool Shift locks width or height
Squashed from #4164 onto main.
2026-10-26 09:09:44 +07:00
Cursor Agent e399427937 G1: Slice Selection resize honors Transform Tools
Squashed from #4168 onto main.
2026-10-26 09:09:44 +07:00
Cursor Agent a54d143b11 G1: Artboard resize honors Transform Tools readouts
Squashed from #4160 onto main.
2026-10-26 09:09:44 +07:00
Cursor Agentandnrq6993-a11y 988c958e3d ci: allow unknown_lints for manual_isolate_lowest_one on Rust 1.95
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
2026-10-26 09:09:44 +07:00
Cursor Agentandnrq6993-a11y 011b0a41e1 G1: Reshape overlay readouts honor Measurement Labels pref
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
2026-10-26 09:09:44 +07:00
Dylan M. Taylor 46ece73197 lint: method references and elided const lifetimes (S1612, S8863) (#4204)
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.
2026-10-25 22:08:44 -04:00
BFlat 6b5ba89cd0 Merge pull request #4187 from Silvertree2010/fix/4163-join-tool-ctrl-j-not-joining-the
Join the selected end points of two paths with Ctrl+J
2026-10-26 08:41:53 +07:00
BFlat e5ef8c7b95 Merge pull request #4132 from Silvertree2010/fix/4109-stroked-paths-issue
Reset stroke weight to zero when the stroke colour is removed
2026-10-26 08:41:38 +07:00
BFlat 2055080568 Merge pull request #4177 from youwishgames/fix/3510-ai-editing-data-512-mib
.ai import: editing data up to 512 MiB opens on 64-bit builds, so a card file with embedded rasters keeps its layers and text (part of #3510)
2026-10-26 08:41:25 +07:00
BFlat 71c4069b51 Merge pull request #4179 from youwishgames/fix/ai-undefined-number-and-xc
.ai import: the -- placeholder in a gradient highlight and the XC operator no longer send the file to its PDF part (fixes #4176)
2026-10-26 08:41:09 +07:00
BFlat f87fd4f236 Merge pull request #4190 from KaboomFox/plugin-eager-compile
Plug-ins: compiling a plug-in's code takes none of a run's instructions
2026-10-26 08:40:55 +07:00
BFlat e3b20c0f0f Merge pull request #4184 from dotCooCoo/fix/character-direction-empty-caret
Character: Character Direction is off at a caret with no characters selected
2026-10-26 08:40:40 +07:00
BFlat 84d760fa71 Merge pull request #4185 from dotCooCoo/fix/stain-replaces-linked-type-paint
Symbol Stainer: stained type keeps no link to a global swatch
2026-10-26 08:40:28 +07:00
BFlat e92659283c Merge pull request #4193 from dotCooCoo/fix/appearance-add-keys-need-selection
Appearance: Add New Fill and Add New Stroke are off with nothing selected
2026-10-26 08:40:13 +07:00
BFlat 22e51ed9da Merge pull request #4197 from dotCooCoo/chore/paintbrush-closing-ring-test
Tests: the Paintbrush closing cursor follows the point a release keeps
2026-10-26 08:40:01 +07:00
BFlat 175be126ed Merge pull request #4194 from dotCooCoo/fix/control-test-slow-client
Tests: two control server line-deadline tests pass on a loaded machine
2026-10-26 08:39:50 +07:00
NetterSilverandClaude Haiku 5.5 b089b7e948 Join the selected end points of two paths with Ctrl+J
Fixes #4163

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 08:18:33 +07:00
NetterSilverandClaude Haiku 5.5 6259448c39 Reset stroke weight to zero when the stroke colour is removed
Fixes #4109

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 08:18:32 +07:00
YOU WISH! GAMES aa7f6bb31d .ai import: editing data up to 512 MiB opens on 64-bit builds, so a card file with embedded rasters keeps its layers and text (part of #3510)
Squashed from #4177 onto main.
2026-10-26 08:18:31 +07:00
YOU WISH! GAMESandClaude Sonnet 5.5 9af023b084 .ai import: the -- placeholder in a gradient highlight and the XC operator no longer send the file to its PDF part (fixes #4176)
An undefined number (--) in `-- -- angle length Bh` is read as a radial
gradient with no highlight, and XC (a color space name after a linked
image's file name) is ignored like its neighbours. Either one used to make
the whole editing data unreadable, so the file opened as its PDF part and
lost its layers and live text. A -- anywhere else is still an operator the
reader doesn't know.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 08:18:25 +07:00
Louise FoxandClaude Opus 5.5 0bd011bd18 Plug-ins: compiling a plug-in's code takes none of a run's instructions
wasmi compiles each function of a plug-in when it is first called and,
by default, charges the run that calls it 7 instructions for each byte
of the function. The host hands a run its budget in 20 M-instruction
slices and resumes it when a slice runs out. But when a slice runs out
while a function compiles, wasmi returns an error from the call, not an
out-of-fuel pause, and the host reports it as Limit("instruction
budget"). So a plug-in's first run could fail with most of its budget
left, depending on where the slices fell, and the next run, with its
functions compiled, succeeded.

StitchCraft's PES exporter hit it on a design of 98 × 148 mm. In
VectorCraft 0.17.0's host its first export failed with 10 G and with
275 G instructions alike, and its second succeeded, taking under 1 G.
Run on wasmi directly, with the host's slices, the first export failed
in its 36th slice ("required_fuel=174139"), and with compiling free it
finished in 49.

Compiling now costs nothing: wasmi's fuel costs with no cost for
translating or validating, and its default cost for copying. A run pays
for its own instructions only, as many on its first run as on the next.
Compiling every function when the plug-in loads (CompilationMode::Eager)
also fixes it, but loads a 1.4 MB module in 42 ms instead of 10.

The new test fails without the change: a run that takes 1,040
instructions once its functions are compiled failed within them on a
plug-in just loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-26 08:18:24 +07:00
dotCooCoo bb45038617 Character: Character Direction is off at a caret with no characters selected
With the Type tool at a caret and no characters selected, choosing an
item in the Character panel menu's Character Direction submenu set the
direction of every character in the type object. The three items are now
disabled until characters are selected.
2026-10-26 08:18:23 +07:00
dotCooCoo ab821a776b Symbol Stainer: stained type keeps no link to a global swatch
The Symbol Stainer mixed the color of type characters painted with a
global swatch but kept the swatch name and tint, so the characters'
paint named a swatch whose color it no longer had, and a later edit of
the swatch could put the unstained color back. Their fill and stroke now
become plain colors, as a path's paint does.
2026-10-26 08:18:22 +07:00
dotCooCoo 3088b6dc99 Appearance: Add New Fill and Add New Stroke are off with nothing selected
With nothing selected, Cmd+/ and Cmd+Alt+/ ran the commands, which put
"select objects or give ids" in the status bar, and the command palette
listed both as available. Without ids and without an Appearance panel
subject (a targeted layer, group or object, or a selected one), the two
commands are now off in menus and the palette, as the panel's buttons
are, and their keys run nothing. Called with ids, they still add to those
objects.
2026-10-26 08:18:22 +07:00
dotCooCoo ded22f432c Tests: the Paintbrush closing cursor follows the point a release keeps
The Paintbrush drops a sample less than a pixel from the last kept
point, and a release there keeps that point. The new test moves the
pointer inside Close Paths When Ends Are Within while the kept point
stays outside it, and checks that the cursor shows no closing ring and
that the release leaves the path open.
2026-10-26 08:18:21 +07:00
dotCooCoo 728ef19a42 Tests: two control server line-deadline tests pass on a loaded machine
buffered_partial_control_line_also_expires and
stalled_control_line_is_refused_with_the_reason used short_timeouts(),
which gives a connection 150 ms before its first byte and 150 ms to write
a reply. When a test thread pauses longer than that, the server closes
the connection without a reply, the client reads end of stream, and the
test fails parsing an empty line (CI run 40212833958 on #4152).

The two tests now use line_timeouts(): the same 120 ms line deadline,
with idle and write budgets of 10 s. The other short_timeouts() tests
are unchanged.
2026-10-26 08:18:20 +07:00
skylar taylor-barrick 4ddd0332cf chore(deps): refresh muda, webbrowser and weezl (#4189)
* chore(deps): bump muda to 0.21.2 and webbrowser to 1.2.5

* chore(deps): bump weezl to 0.2 in render and pdf
2026-10-25 19:31:19 -04:00
Dylan M. Taylor 3f8d7a0c5a build: pin craft-update to craft-libs main (495e597) (#4183)
Picks up the startup prompt without the glyphs a font may lack (storytold/craft-libs#343, #344): the close mark is painted and the version pill is ASCII, so apps that panic on a missing glyph no longer take the prompt down.

Part of tracking issue storytold/craft-libs#319
2026-10-25 17:55:25 -04:00
Dylan M. Taylor 8c45a8dd5b Merge pull request #3937 from dylanmtaylor/security/review-findings-pathops
Security: planar map budgets for booleans, Live Paint, Offset Path and 3D effects
2026-10-25 16:46:05 -04:00
Dylan M. Taylor caef923806 feat: startup update prompt modal and direct self-updating (#4138)
* feat: startup update prompt modal and direct self-updating

The update flow (check at start or on request, the startup prompt, the self-update worker) is craft-update's Updater; the app only supplies the suite, its close path and its status window. The check at start is opt-in for the desktop app, and the Windows update helper runs before argument parsing.

Ref: storytold/craft-libs#319

* refactor(updates): hold the install back while documents are unsaved

* fix(updates): drop the manual window's custom id; pin craft-update to craft-libs main (db19c37)

* fix(updates): address review feedback

Wake the UI when a manual check is queued from a menu callback; the update prompt owns the keyboard while it is open (LightCraft); Download opens through the desktop URL hook (FilmCraft); the Help menu dispatches through menus::invoke (FigCraft); print jobs and comment drafts hold an install back (WordCraft); Check for Updates is a registered command (GridCraft); an install holds back while documents are unsaved.

* fix(updates): localize the update status messages

* build: resolve the craft-update pin after rebasing
2026-10-25 16:22:52 -04:00