* 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
* Build: craft-libs at 2869b8cc
Pin craft-pdf, craft-print, craft-docpath, craft-ui, craft-keymap,
craft-window and the patched hayro/krilla backends to craft-libs
2869b8cc1077530bd07a7e192fb2b0e1304a1354, with the pdf-deps gate,
NOTICE, ASSETS.md and docs/pdf-backend.md at the same revision.
PDF: no modification date beyond the creation date, DeviceN images
are not JPEG-encoded, and clipping text draws nothing (its outlines
arrive as a clip path).
* Docking: compile against craft-ui at 2869b8cc
Floating windows are trees of groups: window_panels and window_active
read one as a flat group, and windows rebuilt from the legacy groups
keep their ids. Unknown dock actions are rejected, the new ones are
checked like the old, and every panel a close action removes keeps its
return location.
* Docking: panels and canvas on craft-ui's docking area
* Docking: tests for the dock area, flyouts, floating panels and the Tools panel
* Docking: catalogs without the old dock's strings
* Build: craft-libs at 8a503cfe
* Docking: floating panels in OS windows of their own on the desktop
* Docking: modal dialogs block panel windows; their keys and dropped files reach the main window
* Docking: art dragged onto a panel in its own window drops there
* Docking: module doc names the OS windows
* Build: craft-libs at a042cf26
* Docking: onto, above, below, collapsed, icon and column places on Float and Dock Panel; ui.docking
* Docking: command descriptions of Float, Dock, Move and Collapse Panel for the craft-ui layout
* Docking: control protocol describes the craft-ui layout, its commands and ui.docking
* Docking: rustfmt
* Docking: layouts saved before the canvas joined convert on load
Saved ui.json files and workspaces from before the shared layout held the
canvas (a dock-only tree plus floating_panels, docking_icons,
docking_collapsed, dock_collapsed and dock_width) convert into one layout:
the dock tree's groups in the main column, the icon column with the panels
dropped on it, floating groups and sets, sets docked as columns, collapsed
groups, the dock width and the places of closed panels. The legacy fields
are read, never written. A ui.json read only in part is copied to
ui.unreadable.json before anything saves over it.
* Docking: catalogs without the old collapse message
* Docking: converted floating sets start at most 720 points tall
* Docking: closed icon panels convert into the icon column; old floating rects, set heights and a copy of the file before conversion
* Build: craft-libs at 27e60765
* Docking: dock column places and icon count from the icon column; a docked column moves to a later place
* Build: craft-libs at d24e4bcc
* Build: craft-libs at 5bbe9f62
* Docs: floating panels in OS windows of their own close the other-monitor gap
* Docking: keys forwarded from a floating panel's OS window run shortcuts while the main window is unfocused
* 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.
The Image Trace panel had no way to compare a trace with its source
image except changing View and changing it back.
The panel now has an eye button to the right of View, enabled with an
Image Trace object selected. While it is held down, the canvas draws the
selected Image Trace objects with their source image, whatever their
View, and draws their View again on release. The document, its View and
the undo history are unchanged. The renderer takes the objects in
RenderOptions::trace_source, which is also part of the canvas frame key
and the raster-effect cache key.
On Linux under X11, and under XWayland, the X server lists a pen's
eraser end as a pointer device of its own, and winit 0.30 hands it to
the window as a mouse. Turning the pen over and dragging the eraser
end drew with the active tool, for example a Paintbrush stroke.
The tablet reader now marks a pen device whose name or XInput type
contains "eraser", and Services::stylus carries the pen's end with
its pressure. Once a frame while no press is held and the pointer is
in the window, the canvas selects the Eraser when the eraser end
moves, and it selects the tool used before when the tip moves again.
The return goes through Session::switch_tool, so a perspective grid
hidden meanwhile stays hidden. A tool chosen while the eraser end
stands in stays when the tip comes back. The feed starts afresh when
the pen turns over, so a press of one end does not take the pressure
the other end last touched with. Native Wayland is unchanged.
The switch follows PhotoCraft's sync_eraser_tool
(storytold/photocraft#255, and storytold/photocraft#5043 for the
cleared tool).
Docs: docs/development.md, docs/gaps.md, docs/hardware-parity.md,
docs/target-app-parity.md.
On Linux under X11, and under XWayland, winit 0.30 hands a drawing
tablet's pen to the window as a mouse without its pressure. Every pen
stroke reached the tools at full pressure: a Calligraphic brush with
Size on Pressure drew one width, and the Liquify tools' Use Pressure
Pen applied full intensity.
When the window is on X11, the desktop app now reads XInput2 raw
events on a second X connection (x11rb's RustConnection, without
unsafe). A device with an "Abs Pressure" valuator is a pen, unless
its XInput type is TOUCHPAD or TOUCHSCREEN. Its pressure, scaled from
the valuator's range to 0..1, goes into Services::stylus, and another
pointer moving clears it. The canvas uses that pressure when a frame
has no touch force. A press takes the highest pressure of the pen's
latest touch since the canvas last read the feed, and waits up to
5 ms for the touch of a hovering pen. The moves and the release of a
held press keep the pressure the pen last touched with when its lift
has already arrived. Native Wayland is unchanged.
The valuator mapping and the reader come from PhotoCraft's tablet
crate (storytold/photocraft#255), with the pressure axis only.
Docs: README.md, docs/development.md, docs/hardware-parity.md,
docs/target-app-parity.md.
A filter plug-in gets 50 M instructions and a minute for each run.
Filters that order a plotter's paths, pack shapes or run a simulation
need minutes, and the app shouldn't freeze while they compute.
A filter's manifest may now ask for "budget": "heavy" (Budget): each run
gets HEAVY_FUEL (1 G instructions) and HEAVY_WALL_TIME_MS (300 s). Only a
filter may ask. plugin.install refuses such a filter without allowHeavy:
true, and plugin.inspect returns the entry a module would have without
installing it. Install Plug-in… and File › Open ask the user and install
on OK; the plug-in store keeps the answer, and folder loads skip heavy
filters with a note, since they ask nobody.
The app runs a heavy filter on a thread of its own (plugin_runs), not in
Background Save's queue, with the status bar and a progress window
(Cancel, Continue in Background) that it shares with Background Export
(export_progress::window). FilterRun splits plugin.run: prepared on the
session, run on any thread, its output handed in, and the next
plugin.run with the same params applies that output instead of running
the plug-in again, so the edit stays one guarded, journaled command. A
result whose document or plug-in changed meanwhile is dropped, and the
status bar says to run it again. A cancelled plug-in run is now
EngineError::Cancelled.
What a filter keeps with the document (#3720) goes with the run: it is
given _context.documentData when the run is prepared, and the
documentData of its output is applied with its objects, in the same undo
step, when the run is handed in (FilterRun::finish).
Tests: plugins/tests/abi.rs (100 M loop steps out of a light budget,
within a heavy one; folders skip heavy filters), manifest.rs, engine
tests_plugins.rs (consent, inspect, the store, hand-in, a handed-in
run's document data, cancel) and
ui-egui tests_plugin_runs.rs (installing asks, no Preview, a background
run as one undo step, a stale result kept out, the progress window).
CRAFT_UI_HEAVY_FILTER_WASM=<folder> writes a test filter to try in the
app. Its strings and messages have rows in each complete catalog
(COMPLETE_MESSAGES).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
With the Artboard tool, File › Revert kept the active artboard, but a
command.batch with a file.revert step made artboard 1 the active one.
A batch that switched to another document and back, and the Actions
panel menu's Batch… over a folder whose files it closes or over data
sets with a Revert step, did the same. Each of them starts the tool
afresh and ends in the document it started in, and the app moved the
view to the fresh tool's artboard 1. Only a plain file.revert was
handled, by its command id.
The engine now counts the times switching or closing documents, or
replacing the active one, starts the tool afresh (Session::tool_resets),
and records whether the tool's options were set after that
(Session::tool_set_since_reset). When the count changes during a
command and nothing set the tool's options after it, the app starts
the tool on the active artboard, as it does when a command chooses
another document. When a later step chose the tool's artboards (a
Paste or Cut after the revert), the artboard the tool then has active
becomes the active one.
Docs: docs/mcp.md, docs/target-app-parity.md.
With the Artboard tool, Paste and Paste in Place select the pasted
artboards, Cut gives the tool the artboard before the first cut one
(the new first artboard when artboard 1 is cut), and tool.setOption
sets its selected or active artboard. The active artboard, the view's,
stayed where it was, so the navigator showed another artboard than the
tool and the Artboards panel. document.activate and File › Revert
started the tool on artboard 1, while choosing a document's tab starts
it on that document's active artboard.
After a command that changes the artboards the Artboard tool has
selected, the app now makes the tool's active artboard the active one,
as a click with the tool does. After a command that chooses another
document, and after Revert, the tool starts on the document's active
artboard.
Docs: docs/mcp.md, docs/target-app-parity.md.
With the pointer resting over art while the view moves (trackpad or wheel
scrolling, pinch zoom, a Hand/space pan), the document point under the
pointer changes every frame, and the canvas hit-tested the art there on
each one, up to three times: Object Highlighting (canvas.rs
hover_highlight -> selection_hit_at) and the Selection tool's cursor
(select.rs cursor: hit_test, and with Snap to Point the nearest-anchor
search). Over a detailed engraving in a clip group (the reporter's
PhotoCraft icon: one path of tens of thousands of segments) each of those
costs roughly 3-6 ms, so every frame of the move took 12-15 ms of UI time;
off the art they return at once.
- canvas.rs: the canvas notes when its view (rect, zoom, centre,
rotation) last changed. Until it has been still for HOVER_STILL_SECS
(0.15 s) it draws no hover highlight and keeps the cursor the tool last
gave for the same tool and modifier keys instead of asking again; it
asks for a repaint then, so both come back without the pointer moving.
Pointer moves still reach the tool as before.
- test: a_moving_view_leaves_the_art_under_the_pointer_unhighlighted_until_it_stops
(wheel frames draw no hover outline and keep the cursor; once still,
the outline returns and the cursor follows the art). It fails on main
at the first wheel frame.
Measured on an Apple M4 (release build, 1440x900 window, the PhotoCraft
app icon SVG fitted at 138%, Selection tool, Smart Guides and Snap to
Point on), median UI frame time over 60 wheel-pan frames with the pointer
resting:
before: over the art 12.3-15.0 ms, off the art 1.0 ms
after: over the art 0.4-1.0 ms, off the art 0.6 ms
The render readout is the art's own raster time on the render worker,
which this change doesn't touch.
Activate visible tabs after a delayed Selection drag and copy artwork through a document-scoped command without changing the clipboard or source. Cancel stale transfers, preserve pattern alignment and transparency, and insert into the editable destination context. Cover pointer gestures, resource rendering, input bounds and locked layers; translate the new labels and messages.
Switch to the Touch built-in workspace when the preference is on and a touch
screen is present; restore the prior workspace when it is turned off.
Co-authored-by: Cursor <cursoragent@cursor.com>
With Use Preview Bounds on, the bounding box, the Transform panel,
Align, Distribute, Distribute Spacing and object.setBounds measured
each object without its geometry effects. A square with Offset Path
10 measured as the square, and type with Outline Object measured by
its layout box instead of its glyphs.
The new vectorcraft_effects::preview module measures objects as they
are drawn. A path is measured by its effected shape, with the
geometry effects of each fill and stroke that paints. Type, images,
live objects and symbol instances are measured by their reshaped art.
A group or layer with geometry or Pathfinder effects, or with a fill
or stroke of its own that has geometry effects or paints members with
geometry effects, is measured by its evaluated art. A blend, envelope
or repeat whose objects have geometry effects is measured by its
evaluated art, with those objects measured after their effects. Raster
effects are not measured. In one object, the art of at most 4,096 symbol
instances is measured with its geometry effects, and the other
instances are measured without them. With Use Preview Bounds off, the
measures are unchanged.
object.setBounds sizes objects with geometry effects by measuring
them after each scale it tries, so type with Outline Object set to
100 pt wide gets a glyph box 100 pt wide.
Selection::anchors_box is split out of Selection::anchor_bounds. The
doc comment of VectorcraftApp::selection_bounds said that it includes
effects; it now says that it leaves them out.
The web build has no render worker, so every frame of a pan, zoom or
rotation of the view rendered the whole canvas on the browser's main
thread before it was shown. In Chrome, each of the 20 steps of a
Space-drag over a document of 50,000 paths took a frame of 533 to
900 ms.
In the web build the canvas now waits while only the view changes on
a document whose last render took more than 8 ms. It draws the last
frame moved, scaled or turned with the view, as the desktop app does
while its worker renders, and it renders once the view has been still
for 0.2 s. The same Space-drag now keeps every frame within 17 ms and
renders once, in 667 to 767 ms, after the view stops. After an edit, a
preview or an option change the canvas renders at once. The desktop
app renders as before, with or without its worker.
Docs: docs/target-app-parity.md.
- ui-egui: the font-install poll (#2576) only runs on native builds, but its
timestamp field, its initializer and FONT_INSTALL_POLL were compiled for
wasm too, where nothing read them. They're gated like the code that reads
them.
- engine: `Session::store_profile` is only called by the native Load
Profile (cmd/colormgmt.rs); gated the same way.
- app (macOS): `App::logic_frame` checked `!file_dialog_open()` twice, one
`if` nested in an identical one, with the comment repeated. One check.
`cargo xtask wasm` runs `cargo check` without `-D warnings`, so these
warnings didn't fail CI; after this change it prints none.
Using a library's character or paragraph style, and scrubbing a
numeric field, closed their undo step with Session::end_undo_group,
which closes the open undo group of every document. An undo group open
in another document, such as one begun with edit.undoGroup.begin for
an MCP command_batch with atomic, was closed too, and that document's
next edits became undo steps of their own. When the style failed to
apply, or Escape ended the scrub, the other document's group was
canceled as well and the edits made in it were undone. Inside an undo
group already open in the same document, a scrub closed that group,
Escape undid the edits made in it before the drag, and a style that
failed to apply left the style it had added.
Session::begin_undo_scope and end_undo_scope make the edits in between
one undo step of the active document: the scope opens an undo group
there, or joins the one already open. Ending a scope closes only the
group it opened, in its own document, even when another document
became active in the meantime, and a canceled scope undoes only its
own edits, also inside a group that was already open. library.use and
the scrub use the scope, so Escape inside an open group also takes
back a scrub of the Transform panel's Shear field. edit.undoGroup.end
still closes the open group of every document.
The Selection tool ended its Move interaction as soon as the pointer left
the canvas, so the art jumped back to where it started while the pointer
was over a toolbar or panel. As #2826 describes, the art should wait where
it last moved and the move should continue when the pointer returns.
Keep the interaction live while the pointer is off the canvas: Drag::Art
holds the art where it last moved, and coming back clears the panel-drop
payload and sends a Drag so the move goes on with the pointer. A release
over a panel ends the move before the panels draw (release_over_panel,
called from ui_frame), so a drop that takes art still gets it as it was
and no interaction is left open.
Tests: art_moved_off_the_canvas_waits_and_drops_on_the_panel,
art_moved_off_the_canvas_resumes_when_the_pointer_returns and
a_move_released_over_a_panel_ends_before_the_panels_draw cover the canvas
states; a_move_dragged_onto_a_panel_reads_the_menus checks the full app
waits over a panel and ends on release. cargo xtask ci: all 10 steps pass.
After "nothing in data set A was applied: no object is bound to a
variable it has a value for" showed in the status bar, choosing a data
set that did apply, or stepping to one, left that message in place.
`run_now` set the status from a command's `warning` and cleared it only
for `file.new`, and the Variables panel test cleared the status by hand
before its last check.
`dataset.select`, `dataset.next` and `dataset.prev` now clear the status
bar when they succeed, and the warning shows when no object is bound to
a variable the new data set has a value for. The test no longer clears
the status by hand and checks all three commands.
The reference app's View menu has Show Live Paint Gaps, which
highlights the gaps in the selected Live Paint group based on the gap
options set for that group. VectorCraft had no such item.
View > Show Live Paint Gaps (view.livePaintGaps) turns the highlight
on or off, and the item reads Hide Live Paint Gaps while it is on.
While it is on and edges show, the canvas draws the gaps of each
selected Live Paint group up to its Paint Stops At size, the gaps that
livePaint.info lists. It draws them as the Gap Options preview does,
as lines in the group's Gap Preview Color. The setting works as View >
Hide Text Threads does: one setting covers every document, it is saved
with the interface state, and Hide Edges hides the gaps. It is off by
default.
Docs: docs/mcp.md, docs/target-app-parity.md.
The readout was a moving average of 1/dt over any two consecutive frames.
An idle window wakes now and then (a timer, a control request, a repaint
after input) and each wake-up draws two frames about 4 ms apart, the second
one not waiting for vsync because the swapchain has a free image. Every
wake-up pushed the average up by 0.1 x 240 and the idle gap pulled it down
by only 0.1 x 2, so at rest it climbed past the display's refresh rate
(105 fps on a 30 Hz monitor that was drawing 4 frames a second). As soon as
the pointer moved and frames became vsync-paced, it fell back to the real
rate, which looked like a slowdown.
- frame_rate: frames counted over the last second of continuous drawing.
A gap more than 100 ms longer than the previous frame's own work means
the window was idle and starts a new count. The readout changes only
after half a second of drawing, so a wake-up's frames keep the last
rate. Slow frames still read slow, because their gap is their own work.
- chrome: "– fps" until the window has drawn continuously, instead of 0.
Measured on an Apple M4 driving a 30 Hz 4K display: drawing continuously
reads 30; at rest with a wake-up every 0.5 s it stays 30 (before: 45 -> 105).
New warp effect dialogs seed bend, distortion and orientation from the
previous OK instead of catalog defaults; editing an applied warp is unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire the View menu toggle and add glyph baselines and bounds to drawing snap targets so point type placement lines up with existing text when Smart Guides are on.
Co-authored-by: Cursor <cursoragent@cursor.com>
An effect raster too large to keep whole (a wide blur, a shadow or glow,
or any blur zoomed in) is cropped to what can show in the view, or made
at a lower resolution and resampled to the view's pixels, and kept while
the view stays the same. Dragging other art over the object then draws
its effects from the still frame instead of previewing them at the
drag's lower resolution on every frame. An interaction's frames stay on
the render worker once it drew the frame shown, since only its renderer
holds those rasters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Font services (NexusFont, Adobe Fonts) register faces with the OS without
touching font-folder mtimes; only re-asking the platform lister sees them.
Check every two seconds as well as on window focus, and test that path churn
is detected on the system font scan.
Co-authored-by: Cursor <cursoragent@cursor.com>
While an export runs in the background (Background Export), a dialog shows
what is being exported and how far it has got: artboard by artboard for a
raster export of several artboards, busy otherwise. Cancel (also Esc) stops
it and writes no file; Continue in Background leaves it to the status bar.
It opens after 0.4 s and never over another dialog.
Built on the shared vectorcraft_doc::cancel (#2546): fileio::progress::Progress
wraps its CancelToken and a progress sink, so a cancelled export stops
drawing and keeps no half-drawn pattern tile or effect raster.
- ui: background jobs carry their Progress; export_progress draws the
dialog; UI commands file.cancelExport and file.exportInBackground.
- i18n: the strings in every language with complete menus, Danish, Dutch
and Indonesian included; "{n} more exports waiting" is a plural (trn).
- docs: ui-parity, gaps (G12) and ROADMAP rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dt49z1ubK9x1bPq51APgz3
When zoomed in, an object with a texture, a blur or a shadow was slow
to drag. Filtered content whose on-screen area passes the 8.4 million
pixel raster budget keeps no raster, and a shadow or glow larger than
4 million pixels keeps none either, so every frame of the drag ran the
effects again over up to 8.4 million pixels. A drag that scales or
rotates an object runs them again at any zoom.
The canvas now renders a tool's drag with RenderOptions::dragging. In
such a frame the renderer draws filtered content within 1,000,000
pixels, at a lower resolution where it would need more, unless it can
draw the content from an earlier frame's raster. On the multithreaded
pipeline it does the same for drop shadows, outer glows and inner
glows. No raster made that way is kept. They count as moved rasters,
so a raster made over them is made again after the drag, and the
desktop canvas renders the frame after the drag on the render worker.
That frame renders them as a still frame does. A dialog's preview,
exports and printing render as before.
zoomed_drag_perf times a drag's frames at 100% to 6400% with and
without dragging.
Docs: docs/target-app-parity.md.
The task bar offers more next steps for each kind of selection:
- an open path: Join (closes it) and Edit Path (the Direct Selection
tool); a closed path: Edit Path;
- a compound path: Edit Path and Release Compound Path;
- a clipping group: Edit Clipping Path and Release Clipping Mask
instead of Ungroup and Isolate;
- an image selected with vector art: Make Clipping Mask instead of Unite;
- point or area type: Convert To Area Type / Convert To Point Type
beside Create Outlines.
Buttons may now carry command parameters. "Edit Path" is translated in
every catalog; the other labels reuse the menu's. cargo xtask ci passes
all 9 steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The public documentation says Image Trace warns about images larger
than 1500 pixels on a side but traces them, only more slowly. VectorCraft
traced them silently, and refused any trace of more than 2,000,000
anchors, which a 4000 x 3000 photo at High Fidelity Photo nearly reaches
(1.9 million on a code-drawn photo) and a noisier one passes.
- imageTrace.make and makeAndExpand return `warnings`: one for an image
over 1500 px on a side (traced, but more slowly; a lower-fidelity
preset or a smaller copy is quicker), one for an image over 64 MP
that was traced from a scaled-down copy. The app shows the first in
the status bar.
- MAX_TRACE_ANCHORS is 8,000,000 natively (2,000,000 on wasm, where a
page has at most 4 GB). Measured through imageTrace.make on
code-drawn photos: about 90 bytes an anchor kept in the document;
7.9 million anchors kept 685 MB with an 876 MB peak (4000 x 3000,
Full Tone at Colors 100, Paths 100, Noise 1, 37 s).
- The refusal keeps its translated advice: raise Noise, lower Paths or
Colors, or pick a lower-fidelity preset.
Tests: a_large_image_traces_with_a_warning (1600 x 40 and 1500 x 8
code-drawn images, make and make-and-expand),
trace_warnings_name_a_large_or_scaled_down_image, a compile-time check
of the limit's range, and a UI test that the status bar shows the note.
Source:
https://helpx.adobe.com/illustrator/using/image-trace-results-optimization.html
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
Each swatch or graphic style library now opens in a panel of its own,
`library:<kind>:<library id>`, in the shared docking system: several can be open
at once, and they float, group as tabs, dock and close like any panel.
Opening a library that is open brings its panel forward; Previous / Next
Library show the neighbouring library in the same panel.
The library panel menu gains Persistent. The panels' view, Show Find Field
and Persistent are kept in the UI preferences (`library_panels`); at startup
the persistent panels reopen where the saved layout had them, a library
loaded from a file read again from its path (a missing file is skipped with
a status-bar note). `ui.libraryPanel {panel, library?, view?, showFind?,
persistent?}` reaches all of it; docs/mcp.md documents the panels.
Object > Rasterize, Crop Image, Flatten Transparency, Expand
Appearance of raster effects and Vector Halftone draw the art with the
renderer and replace it with the result. When the art held an image
that could not be decoded, the renderer drew nothing for it, and the
result lacked the image with no message. A raster export of the same
art warns that "an image couldn't be decoded and is missing from the
picture".
These five commands now compare decode_failures before and after they
draw, as raster exports do, and their replies carry that text as
`warning`. They still make their result in one undo step. The app
shows a command's `warning` in the status bar: after a menu command,
after OK in a dialog with a preview, and after a tool runs a command
(Crop Image's Apply).
Docs: docs/mcp.md (Flatten Transparency, Expand Appearance, Document
Raster Effects Settings, Vector halftone).
View › Rulers › Change to Global Rulers (Cmd+Alt+R) was a disabled
stub: the rulers always counted from one document zero point. The
reference app's public help describes two modes: artboard rulers, the
default, count from the active artboard's top-left corner, and each
artboard can have its own zero point; global rulers keep one zero point
for the whole document whichever artboard is active.
https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/about-rulers.htmlhttps://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html
`view.globalRulers {global?}` switches the mode (toggle by default),
undoable and saved with the document (`rulers`). The menu item reads
Change to Artboard Rulers while global rulers are on. A new document has
artboard rulers; a file saved before the mode existed has no `rulers`
key and opens with global rulers, so it measures as it did.
With artboard rulers, dragging the ruler corner sets the active
artboard's zero point, kept as an offset from its corner
(`artboard_ruler_origins`, by artboard id) so it moves with the
artboard, and double-clicking the corner resets it. The global zero
point (`ruler_origin`) stays separate. `document.rulerOrigin`,
`document.setRulerOrigin` and `document.resetRulerOrigin` take
`artboard`; `Document::ruler_origin_for` is the zero point with an
artboard active, falling back to the corner or the global one when a
stored value is out of range.
The rulers, the Transform, Properties and Control bar X/Y, the Info
panel's X/Y (which ignored the zero point before), ruler-guide
readouts and Shift ruler-tick snapping all count from that zero point:
the view's active artboard reaches the tools through `ViewInfo` and
`ToolContext::ruler_origin`. Command coordinates stay absolute.
Tests: engine/tests/ruler_origin.rs (default mode, per-artboard origin
following its artboard, global origin apart, toggle and undo, save and
legacy files, out-of-range values), ui-egui/src/tests_ruler_origin.rs
(position fields and corner drag on the active artboard, menu label and
shortcut), tools/src/rulerguide.rs (guide readouts and tick snapping).
Tests written for the single global zero point now turn global rulers
on first. "Change to Artboard Rulers" is translated in every catalog.
Docs: docs/ui-parity.md, docs/gaps.md (G7), docs/target-app-parity.md,
docs/mcp.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2JbUtM4MkzWLm9FgYiEsq
After the desktop save panel closes, the app adds the file type's
extension to a name typed without it (or with one the save does not
write). The panel asked only about the name as typed, so on Linux
typing `card` with `card.vectorcraft` in the folder wrote over
`card.vectorcraft` without a question. On Linux the save panel is the
XDG desktop portal, and its GTK and GNOME file choosers return the
name as typed. Windows and macOS add the extension themselves only
when the panel lists file types, and the panels of Document Info's
Save, the presets dialogs' Export…, the CSS Properties export, the
swatch and graphic style library saves and the Libraries panel's
export list none.
When the added extension names a file that exists, Save, Save As, Save
a Copy, Save as Template, the exports and the other writes through a
save panel now ask "“card.vectorcraft” already exists. Replace it?"
first (dialog `replaceFile`). OK runs the write again with the same
answer and replaces the file. Cancel writes nothing and shows the
dialog it was asked over again. The host reports whether a file exists
through a new `Services::exists`.
Docs: docs/control-protocol.md, docs/target-app-parity.md.
Shift+Enter in a panel or Control bar field ended its editing, as Enter
does. The reference app's default keyboard shortcuts list "Apply value
and keep text box active" under Use panels and "Apply a value and keep
focus in edit field" under Transform panel, both Shift+Enter on Windows
and Shift+Return on macOS.
- A focused single-line field of the shared widgets (num_field,
mixed_field, plain_field, range_field, spin_field, spin_field_auto,
spin_plain, text_field, exact_text_field, opt_field and hex_field)
takes Shift+Enter before its text box reads it, applies the text as
Enter does and keeps the focus. In the next frame it shows the value
as it now is, all selected, so typing replaces it.
- A text box of several lines keeps Shift+Enter as a line break.
- While a dialog is open, Shift+Enter applies the field's value and
confirms the dialog, as before. The app sets widgets::set_dialog_open
at the start of each frame.
Docs: docs/target-app-parity.md.
attributes.openUrl refuses other URL schemes with a message. It and
links.editOriginal hand a URL or file to another app, so they answer a
control or MCP request (and synthetic input) with an error; a placed
document still opens in a tab.
With View › Hide Edges on, the canvas does not draw the bounding box,
the type widget beside it or the Live Corners widgets, but the
selection tools still answered the pointer on them. A press on a hidden
corner widget rounded the corners, a drag from a hidden handle scaled
the selection, a double-click on the hidden type widget converted the
type, and the cursor changed over each of them.
While edges are hidden, the app now tells the tools that the bounding
box and the corner widgets are off. A press at their places reaches the
art or the canvas under them.
Docs: docs/target-app-parity.md.