Commit Graph
54 Commits
Author SHA1 Message Date
Dylan M. Taylor 55dddf6adf Extract UI event and rendering helpers 2026-10-28 16:44:17 +00:00
aiongg 0f5658d270 refactor(ui-egui): move control.rs tests to control/tests.rs 2026-10-26 18:18:18 +00:00
nrq6993-a11y c38185f79c ci: Rust 1.95 clippy and flaky Arabic PDF test after upstream merge
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
2026-10-26 18:40:47 +07:00
bflatastic a8cc7b8f72 Dialog field-list tests use fields the Move dialog reads (#4049 refuses the others) 2026-10-26 09:09:46 +07:00
dotCooCoo 9f358a1d6d MCP: answer_dialog sets the fields of the app's open dialog and confirms or cancels it
Squashed from #4057 onto main.
2026-10-26 09:09:46 +07:00
dotCooCoo eadf7674f1 MCP: screenshot and render_preview can draw the Image Trace view
After imageTrace.setView, screenshot and render_preview still drew the
tracing result, because ui.render renders with the export options and only
the canvas turns on trace views. Only the app window showed the view.

ui.render now takes traceViews. With traceViews: true, Image Trace objects
draw in the view set with imageTrace.setView, as on the canvas, both
headless and in the running app. screenshot and render_preview take
traceViews and pass it on; without it they draw the tracing result, as
exports do. The trace-and-style prompt names the argument.

A supersampled render drew Outline view's lines, type outlines and dotted
guides as little as a quarter of an output pixel wide, so they came out
pale gray. The renderer now scales those widths and the guides' dashes by
the supersampling factor in the larger frame, so the lines stay one output
pixel wide.

Docs: docs/mcp.md (render_preview, screenshot), docs/control-protocol.md
(ui.render).
2026-10-26 09:09:45 +07:00
dotCooCoo 0641412cce Dialogs: ui.dialog.set returns an error for a field the open dialog does not read
ui.dialog.set stored any field name it was given, so a misspelled field
(`discrad` for `discard`) was kept without effect and the dialog then
ran with its old value. It now answers such a field with an error that
lists the dialog's fields, and leaves the dialog unchanged.

A dialog accepts the fields it holds, `__` fields, `discard` with a
discard button, `preview` with a live preview, the fields it reads
before a control adds them (each spec's `inputs`, for example Copy and
the object options of the Transform dialogs, Move's `distance` and
`angle`, the reference point, `path` of the export options dialogs,
`searchFolder`, Brush Options' `tips`, the Justification and
Hyphenation values that open blank, the Corners `kind` and `radius` of
corners that differ, the Gradient Stop popover's `color`, `opacity`,
`location` and `spread`, and a new artboard's `color` and display
marks), and the fields it passes to the command it runs: every
parameter of the generic `command` dialog's command, and the
`textureImage` that Load Texture… sets in the Glass and Texturizer
effects. Edit Selection now opens holding `deleted<i>` for each of its
rows, so setting a row number it does not have returns an error, and
Transparency Flattener Presets opens holding the selected preset's name
and options instead of adding them on its first frame.
2026-10-26 09:09:45 +07:00
dotCooCoo 3eda1b128c Image Trace: changes that wait with Preview off stay with their object
With Preview off, changes made in the Image Trace panel for one Image
Trace object were lost when another object was selected: selecting the
first object again showed its stored settings. The panel now keeps the
waiting changes for each object and shows them again when that object
is selected, unless the object's own settings changed in the meantime.
When Preview was turned on while another object was selected, the
object is traced with its changes when it is selected again.

Preview is now the UI command imageTrace.preview ({on?}: on, off or
toggle), and both the panel's checkbox and ui.set's imageTracePreview
run it. The command label has a row in every catalog.
2026-10-26 09:09:45 +07:00
aiongg 714434527e Docking: panels on craft-ui's shared docking, floating panels in OS windows (#4135)
* 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
2026-10-25 15:25:35 -04:00
skylartaylor 340e3495d8 egui037: migrate VectorCraft UI and reconcile current main 2026-10-25 03:27:29 +01:00
dotCooCoo 8c3672fb31 MCP: save_file against the running app waits for the save and reports its result
Squashed from #3969 onto main.
2026-10-24 19:01:51 +07:00
dotCooCoo d29790b34e Image Trace: a Preview checkbox chooses whether setting changes trace at once
Every change in the Image Trace panel traced the selected Image Trace
object again at once, and there was no way to adjust several settings
first and trace once.

The panel now has a Preview checkbox before Trace. It is on by default
and kept with the UI preferences; Reset to Default leaves it. With
Preview off, setting changes wait in the panel until Trace applies them
as one undo step, or until Preview is turned on again. When Preview is
turned on with no Image Trace object selected, the object is traced
with the waiting changes when it is selected again. A change of view
keeps the waiting settings, and selecting another Image Trace object
shows that object's own settings. The control channel's ui.set takes
imageTracePreview.
2026-10-24 17:46:12 +07:00
dotCooCoo 58827952b7 MCP: screenshot and render_preview draw edges as a PNG export does
ui.render, which render_preview and screenshot call in the headless
session and in the app, rendered the artboard with the canvas options:
8-bit compositing and edges smoothed shape by shape. A PNG export
composites in floating point and supersamples edges, so the preview
showed edge colors the exported file does not have, most visibly where
shapes meet edge to edge.

ui.render now renders with the options of a PNG export on white, so
its pixels equal the export's at the same scale. render_preview also
no longer enlarges an artboard smaller than max_side: with the default
max_side of 1024, a 612 x 792 page came back as an interpolated
791 x 1024 image, and it now comes back at 612 x 792. docs/mcp.md and
docs/control-protocol.md describe this.
2026-10-24 17:29:33 +07:00
dotCooCoo 5fe4f3ead1 Place: files are read and decoded on a background thread
Squashed from #3740 onto main.
2026-10-23 09:07:55 +07:00
dotCooCoo 580aa8daa5 Clipboard: Copy and Cut make the system clipboard's formats on the background thread
Copy and Cut made the system clipboard's formats in the frame that ran
them. The formats include the SVG markup and a PNG of the selection at
1 px per pt, up to 4,096 px a side. In a release build on Windows, Copy
of 50,002 rectangles on a 1600 x 1200 pt page took 1.00 s in its frame.

The thread that writes background saves and exports now makes the
formats from a copy of the clipboard, and the app writes them in a
later frame. The same Copy takes 0.18 s in its frame, and the SVG and
PNG are the same bytes. While that thread has a save, an export, a print or
a recovery copy to finish, Copy and Cut make and write the formats at
once. A later Copy or Cut, or text copied in a field or with the Type
tool, drops formats that are not written yet. Until they are written,
Paste on the canvas takes the internal clipboard, and while a text
field or the Type tool has the keyboard the app waits for them.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 04:04:51 +07:00
dotCooCoo 25df070576 Color: Sample Color from Screen picks a color anywhere on the screen
The Color Picker gets an eyedropper button beside the New and Original
chips, and `ui.sampleScreenColor {stroke?, picker?}` shows the system's
color sampler. The command answers at once. When the person clicks a
color anywhere on the screen, the color becomes the fill or the stroke
through paint.setFill / paint.setStroke as one undo step, or with
`picker` it becomes the open Color Picker's color, which previews on the
selection as any pick does. Escape cancels the sampler. The sampled sRGB
color goes through Cms::srgb_to_rgb, so in a Display P3 working space a
color inside sRGB shows as it did on the screen. `ui.inspect` reports
`screenSampler` while the sampler is open.

- macOS uses NSColorSampler (apps/vectorcraft/src/mac_screen_color.rs).
- The web app uses the browser's EyeDropper API where the browser has
  one.
- Without a sampler (Windows, Linux, other browsers) the button is gray
  and the command returns an error. Keyboard Shortcuts lists the
  command, so it can be given a key.

Docs: docs/control-protocol.md, docs/target-app-parity.md.
2026-10-16 02:25:53 +07:00
bflatastic ea30e411e7 Fix #1725: a control request its caller timed out on is withdrawn, not run later
The worker answered "timeout" after 60 s and left the request on the queue, so
the app ran it whenever it next drained the queue: an edit nobody was waiting
for, and a second one if the caller had retried. A request now leaves the
queue once: taken by the app, or given up by its caller, whichever comes
first. The timeout reply says which ("will not run" / "still running").
2026-10-14 08:47:10 +07:00
DWAA1660 fdf6ba9c9d M1: bound synthetic control input expansion (#1709) 2026-10-14 06:01:10 +07:00
bflatastic 175ad4a1e3 Effects: effect.apply and effect.setParams refuse parameters the effect doesn't take (#1592) 2026-10-13 11:53:38 +07:00
bflatastic 3acc29f8e0 Fix #1442: an unknown parameter key is an error naming it and the nearest accepted key 2026-10-13 02:54:33 +07:00
arhammusheerandClaude Opus 5.5 023c8e3edd #1244: streaming open and save, per-section limits, recovery that keeps images apart, background open
Save serializes JSON straight into the compressor and writes images from their Arc into the
file; open reads each image into its own buffer, and NodeKind reads its tag first instead of
buffering every node. Already-compressed images are stored rather than deflated again in the
same gzip stream. Limits apply per section, so any file VectorCraft writes opens again. Data
Recovery writes each image once, content-addressed, instead of every interval. Native files open
on a background thread. Files stay readable by main both ways; JSON-only saves are identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
2026-10-12 11:02:33 +07:00
dotCooCoo 845aa6848f MCP: select.key checks its id, and agents can read the key object
Agents could set and clear the key object but not read it:
document.inspect, the control channel's ui.pointer reply and the
headless pointer_gesture reply left it out, and select.key returned
null. select.key with an id that was not selected, or with a value
that is not an id, such as "x", cleared the key without an error. A
compound-path member or an object inside a selected group could become
the key.

select.key now checks its id as select.set, select.add and
select.toggle do (#784). It returns an error for an id that is not a
selected top-level object and leaves the key as it was. Layers,
compound-path members and objects inside another selected object are
not top-level objects. select.key {id: null} used to clear the key and
is now an error; select.key {} still clears it. select.key returns
{key}, and document.inspect and both pointer replies include the key
object as "key".

In isolation mode a Selection-tool click can pick an object that
select.key does not accept as the key: a sublayer inside an isolated
layer, a member inside an isolated compound path, or an object in an
isolated group that is also selected. The tool no longer sends
select.key {id} for such an object. When the object is already the
key, the tool still sends select.key {}, which clears the key.

A recorded action whose select.key step names an object that is not a
selected top-level object when the action plays now fails and is
rolled back, as an action with a select.set step naming a deleted
object has failed since #784.

Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md.
2026-10-12 00:16:23 +07:00
EmlembowandClaude Opus 5.5 7ff0269a1d Fix #867: macOS file dialogs are sheets, and no dialog runs inside the event handler
The crash: rfd's blocking dialog ran -[NSSavePanel runModal] inside
winit's event handler; when a resize of the panel ends, AppKit runs the
closures winit queued meanwhile, and winit's re-entrancy panic fires
inside an Objective-C block, where it can't unwind, so the app aborts.
As PhotoCraft (#673) and PdfCraft (#145) ship it:

- macOS: picks::ask shows an rfd::AsyncFileDialog as a sheet on the
  window (beginSheetModalForWindow returns at once); a waiter thread
  sends the paths and wakes the window. A pick asked outside an entry is
  refused and logged instead of running a modal. Linux keeps its thread
  (#592) and now also wakes the window; Windows is unchanged.
- Nine paths that asked outside an entry now run as entries: Load
  Profile, Swatches and Graphic Styles Other Library, the shortcut
  editor's Import and Export, and the presets dialogs' Export.
- While a dialog is open: the macOS menu bar is inert except Hide, Hide
  Others and Minimize; Quit, Close, Close All and the close button are
  refused; Finder/Dock opens and the IME discard wait; another dialog
  is refused with a status message. The answer re-activates the
  document that asked (by uid) and Save Changes finds its document by
  uid; a frame guard clears a stale picks entry after a recovered panic.
- ui.inspect reports fileDialog.

Squashed onto current main by the maintainer (with main's automation
sandbox wrappers and font-name menus kept).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:14:17 +07:00
bflatastic 148ef1870a #1004: a connected app's screenshot renders the artboard asked for
The desktop app's ui.render (MCP `screenshot` when connected) always rendered the first
artboard and ignored `artboard`, which the headless backend honours.

It now renders the 0-based `artboard` (the first without one, an error for one that doesn't
exist), as headless does. control-protocol.md and the control.rs list show the parameter.

The test drives ui.render through the control channel on a 64 x 64 and a 96 x 48 artboard:
no index and 0 give 64 x 64, 1 gives 96 x 48 (48 x 24 at scale 0.5), 2 is an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	ROADMAP.md
2026-10-11 03:51:19 +07:00
bflatasticandClaude Opus 5.5 9f875050ef Fix #832: --automation-read-root and --automation-write-root confine the MCP server's files
vectorcraft-cli mcp and vectorcraft --control take the flags PhotoCraft
takes. While roots are in force, every path a command reads (open, place,
relink, SVG image links, library and profile loads, plug-ins, preset
imports, font searches) must lie inside the read root and every path it
writes (save, export, package, Export for Screens and for Web, print to
file, captures, library saves) inside the write root. Paths are made
absolute, links are followed (a missing file's nearest folder's), and the
result is compared by whole components, ignoring case on Windows; `..`
below a missing folder, dangling links and device names are refused. A
root left out grants none of its access; without the flags nothing
changes.

The checks live in one place, vectorcraft_engine::file_access, consulted
by the engine's file helpers and the app's file services. The headless
server confines its whole process; the app confines control requests,
the frames carrying input they inject and the background saves they
start, leaving the person at the keyboard and the app's own folders
alone. The screenshot tool now has the backend write its file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:27:24 +07:00
bflatasticandClaude Opus 5.5 db8be89052 Fix #830: the control channel's app.quit says when it asks first
Raw app.quit closed the window and answered null at once, so a client
couldn't tell that a save prompt was waiting. It now runs the menu's
Quit: {"pending": "saveChanges"} while a modified document is asked
about, as file.close and file.closeAll answer and the protocol
documentation says; otherwise null, and the window closes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:27:47 +07:00
bflatasticandClaude Opus 5.5 2497713779 Fix #598: the Free Transform widget
While the Free Transform tool (E) is active, a small box at the canvas's
top left holds Constrain over the tool's three modes: Free Transform,
Perspective Distort and Free Distort. Before, the modes were reachable
only by modifier keys or `tool.setOption`.

- The buttons set the tool's `constrain` and `mode` options through
  `tool.setOption`, as agents do. Constrain acts as Shift held and is
  greyed in the distort modes, where it does nothing. The modifier keys
  keep working while dragging.
- Theme tokens; Constrain shows the link icon, Free Transform the tool's
  own icon, the distort modes are drawn in code. Tooltips translated
  (cs, es, it, ja, pt-br, ru, zh-hans, zh-hant).
- `ui.inspect` reports where it shows (`freeTransformWidget`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 05:31:43 +07:00
bflatasticandClaude Opus 5.5 ab65474044 macOS: the menus live in the system menu bar, settings in the VectorCraft menu
On macOS the menus are in the system menu bar, laid out as a Mac app's (Illustrator on the Mac as
the reference), and the app settings sit in the VectorCraft menu: About, Settings ▸ (one item per
Preferences page, General… ⌘K), Language ▸, Appearance ▸ (UI brightness), Services, Hide
VectorCraft ⌃⌘H (⌘H stays View › Hide Edges), Hide Others ⌥⌘H, Show All and Quit ⌘Q (the app's own
quit, so unsaved documents are asked about); they leave Edit and Help. Window gets Minimize ⌃⌘M,
Zoom and Bring All to Front; Help is the system's Help menu. Windows, Linux and the web keep the
in-window menu bar unchanged; --in-window-menus or VECTORCRAFT_IN_WINDOW_MENUS keeps it on macOS
too, and so does a menu bar AppKit refuses to build.

One menu model: the in-window menus and the native bar draw the same rows (menus::entry), and the
platform-free native_menu module lays them out, decides when to update (a cheap state hash; labels,
enabled and checked in place, a rebuild only when the structure changes) and hands key equivalents
back to egui as the key presses they were, so shortcuts, user overrides and focused text fields
behave as in the window and nothing fires twice. The muda backend (pinned =0.21.1, macOS only, no
unsafe) is apps/vectorcraft/src/mac_menu.rs, mirroring PhotoCraft's. ui.inspect reports
nativeMenuBar. Translations for the new labels in ja, es, zh-hans, zh-hant, cs and pt-br.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:45:31 +07:00
bflatasticandClaude Opus 5.5 d11daabdc7 Contextual Task Bar: drag it by its handle, pin and reset its position
The grip at the bar's left end now drags the bar anywhere on the canvas
(grab cursor), which always holds it whole, also when the window shrinks.
Unpinned, the bar keeps that offset as it follows the selection (in the
document it was moved in); the "…" button opens a More Options menu with
Hide Bar, Pin Bar Position (checked while pinned: the bar stays where it
is across selection changes and the handle still moves it; unpinned it
follows the selection again from there), Reset Bar Position (unpinned,
back under the selection) and Show Properties Panel, as in Illustrator.
As there, the position and the pin last for the session only.

- UI commands window.taskBar.pin {pinned?} and window.taskBar.reset, so
  the control channel and MCP reach them; ui.inspect reports taskBar
  {pinned, rect}.
- One opt_bool helper reads the {key?: bool} params of the toggling window
  commands (toolbarColumns, floatTools, collapseDock, taskBar.pin).
- New strings in ja, es, zh-hans, zh-hant, pt-br and cs; the dropped
  "Hide Contextual Task Bar" tooltip and per-key es messages removed.
- Headless egui test: dragging the grip moves the bar, the offset follows
  the selection, pin holds it across selection changes and a shrinking
  window, unpin follows from there, reset restores.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:23:15 +07:00
bflatasticandClaude Opus 5.5 e98a880e75 Part of #472: Escape leaves Presentation Mode; dropped documents open as tabs; .ai off-artboard note
Presentation Mode hides the menus and panels, had no Escape and was saved with the
preferences, so it came back on every restart with no way out. Escape now leaves it (as in
Illustrator), F from it goes back to Normal Screen Mode, and the screen mode is no longer
saved: the app always starts in Normal Screen Mode, and preferences saved in Presentation
Mode by older versions open normally. ui.inspect reports `screenMode` on its own.

Desktop file drags carry no pointer position or modifiers (winit 0.30 on Windows, macOS
and X11), so every drop on a window with a document open was taken for the middle of the
canvas and placed. Where the platform doesn't say, a dropped document (native, SVG, PDF,
.ai, EPS, DXF, EMF/WMF) now opens as a tab of its own and a picture or text is placed in
the middle of the view; pictures dropped together with documents are placed first, so the
last document opened is shown. The web, whose drag events give the position, keeps
Illustrator's choice: placed on the canvas, opened off it (the tab bar).

Illustrator's PDF-compatible part leaves out art outside the artboards (it lives in the
private data alone, which isn't read), so it can't open; importing a file with that
private data now says so in a note. Off-page art a PDF does draw already opens on the
pasteboard; a test keeps it so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:01:12 +07:00
bflatasticandClaude Opus 5.5 9a5429a325 MCP: inspect_document slices too, stricter find filters (review of #416)
- The inspect_document tool (and the control channel's document.inspect)
  take {depth?, childLimit?}, so MCP clients get the skeleton without
  going through run_command; the tool description points at the
  drill-down path.
- document.find rejects a filter of the wrong type instead of dropping
  it (which widened the search); one opt_u64 helper parses limit, depth
  and childLimit.
- docs/mcp.md spells out find's matching rules; control-protocol.md
  and ROADMAP updated; MCP and engine tests cover the new paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:17:06 +07:00
bflatasticandClaude Opus 5.5 47bed05ff4 Part of #394: Preferences that act on selection, the wheel, tool tips and the grid
Fifteen Preferences options were stored but read by nothing. They now act,
through the existing plumbing (ToolContext for the tools, the prefs read by the
canvas for the view), and reach agents through prefs.set:

- General: Show Tool Tips (off: no widget's tool tip comes due), Double Click
  To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer,
  Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways), Use Precise
  Cursors (Pen, Eyedropper, Slice and Blend pointers become a crosshair).
- Selection & Anchor Display: Tolerance (objects, anchors and handles, in
  place of the fixed 3 and 4 px), Object Selection by Path Only (compound
  paths too), Command Click to Select Objects Behind (cycles down the objects
  under the pointer; not when Cmd only borrows the selection tool), Zoom to
  Selection (Zoom In/Out centre the selection), anchor Size, Highlight anchors
  on mouse over (Direct Selection).
- Type: Number of Recent Fonts (up to 15 Recent Fonts slots).
- Guides & Grid: guide and grid Color and Style (lines or dots), Grids In Back.

The canvas's context menu, panel drops and hover highlight pick objects like
the selection tools. The control channel gets ui.wheel (with modifiers held
like ui.click) so agents can turn the wheel. The rest of #394 stays open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:22:24 +07:00
BFlatandClaude Opus 5.5 406c27ebd8 Fix #306: render on the power-saving GPU by default, with a preference for the discrete one
On hybrid-graphics laptops (an integrated GPU driving the display plus a
discrete one), egui-wgpu's default power preference is HighPerformance, so
VectorCraft rendered on the discrete GPU and its frames were copied to the
display through the integrated one. On an Asus ZenBook with Intel Iris + Arc
the window flickered whenever the mouse moved over it (#306, #305).

The canvas is rasterized on the CPU (vello_cpu) and the GPU only composites
the canvas texture and draws the UI, so the integrated GPU is plenty. The
desktop app now asks wgpu for the power-saving adapter by default.
Single-GPU machines are unaffected.

- Preferences > Performance > Graphics Processor (`gpuPreference`:
  powerSaving, the default, or highPerformance), with a note that it
  applies after a restart. The app reads it from the saved preferences
  before the window opens. WGPU_POWER_PREF still overrides it.
- Help > About and `ui.inspect` (`graphicsAdapter`) name the adapter in
  use, and it is logged at startup, for future GPU bug reports.
- Docs (README, docs/development.md) and ROADMAP updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:10:17 +07:00
bflatasticandClaude Opus 5.5 cdb734aede Fix #219: right-click on the canvas opens a context menu
Right-clicking the canvas now opens a context menu, as the reference app
does. The object under the pointer is selected first unless it already is.
The menu then lists what applies to the selection: Undo and Redo, Cut, Copy,
Paste, Isolate Selected Group or Exit Isolation Mode, Group and Ungroup,
Join and Average, Make or Release Clipping Mask and Compound Path, Make
Guides, the Transform, Arrange and Select submenus, and Export Selection.
On empty canvas it lists Paste, the zoom commands, the ruler, grid and guide
toggles and Select All. Commands that can't run now are left out (Undo and
Redo stay, greyed out), and a press on the canvas only closes the menu. Agents list the same menu with the
`ui.contextMenu.list` control method.

Root cause: the canvas ignored the secondary button except in the toolbar's
tool flyouts; there was no context menu at all. The menu reuses the menu
bar's items, rendering and dispatch; Object > Transform and Arrange are now
shared lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:28:52 +07:00
bflatasticandClaude Opus 5.5 e9abc62f1a M8.4: Liquify keeps going while held, leaves non-paths alone, strokes apply incrementally
Holding the brush still keeps Twirl, Pucker and Bloat applying, scaled by the
time held: a new Tool::tick (time supplied by the host: canvas frames, or
`holdMs` on ui.pointer / pointer_gesture events) repeats the last point every
0.1 s, one more dab there. Liquify never touches graphs, meshes, type, symbols,
images, or envelopes, repeats and blends with their contents, nor guides in
selection mode; those under the brush come back in `skipped` with a `warning`
that the status bar shows (UiRequest::Status). A drag applies only the dabs
each new sample adds (the stroke's state is kept between previews), giving the
same result as the whole stroke, so journal replay still reproduces it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 12:00:52 +07:00
bflatasticandClaude Opus 5.5 92cadd6083 M8.3: Liquify Tool Options dialogs, pen pressure and Brush Affects
Double-clicking a Liquify tool opens its Tool Options (dialog liquifyOptions):
the shared Global Brush Dimensions (Width, Height, Angle, Intensity, Use
Pressure Pen), the tool's options (Detail, Simplify with its checkbox, Twirl
Rate, Complexity, Brush Affects, Horizontal/Vertical) and Show Brush Size.
The canvas captures pen pressure from pen/touch force, ui.pointer and MCP
pointer events take `pressure`, and object.liquify takes [x, y, pressure].
Fixes: LiquifyParams::to_json now sends affectAnchors/In/Out; Alt-drag sizes
the brush from its current size (Shift keeps proportions) instead of
collapsing it. Hint-bar text for the seven tools. Registers the MCP Liquify
tests file added in M8.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:50:16 +07:00
bflatasticandClaude Opus 5.5 72be324732 Fix #86: modifiers sent with ui.key, ui.click and ui.drag reach every handler
The control channel attached its modifiers only to the synthetic key and
button events, while most handlers read egui's held modifiers
(i.modifiers), which only the keyboard set. The raw input hook now sets
egui's modifiers (a ModifiersChanged event) for the frames synthetic input
spans: a key's press, text and release, a click, and a drag from press
through its moves to release; the keyboard's modifiers, tracked from the
host's events, come back after. ui.key's text now comes with the press.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:49:20 +07:00
bflatasticandClaude Opus 5.5 db0a7ffd8b M4.36: Background Save and Export write a snapshot off the UI thread with status bar progress
- With Preferences > File Handling > Enable Background Save / Export on, File >
  Save, Save As, Save a Copy and exports encode and write a snapshot of the
  document on a worker thread; the call returns at once ({path, background}).
- The status bar shows a spinner and what is being written; ui.inspect lists
  the running jobs under `background`.
- The document takes the path and counts as saved as of the snapshot once the
  file is written: edits made meanwhile keep it modified.
- Quit and Close wait for running saves; the Save Changes dialog saves at once.
- The host gives a thread-safe writer (Services::write_shared, atomic on the
  desktop); without one (web, tests) everything runs at once as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:27:06 +07:00
bflatasticandClaude Opus 5.5 950a941003 M4.33: Export As with Use Artboards (All or a range) and art bounds
File > Export > Export As... (file.exportAs) picks any writable format and Use Artboards: All
or a range writes one file per artboard, <file>-<artboard>.<ext> (a PDF keeps them as pages);
off, the export covers the bounds of the visible art (an artless document is refused before the
save dialog). The format's options dialog follows: PNG/JPEG/WebP Options, SVG Options, or Save
PDF for artboards as pages. document.export takes useArtboards for raster formats (true: one
file per chosen artboard, default all) and PDF (false: the art's bounds), and the app's exports
return every file written and the encoder's warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:14:42 +07:00
bflatasticandClaude Opus 5.5 4a3859c69b M4.26: SVG Options in the engine, a dialog and MCP
document.export, serialize and save, and the Save As / Save a Copy UI
commands, take the SVG options flat or as svg: {…}: styling
(presentation, style, entities, css), outlineText, images (embed or
link; linked files are written next to the SVG), objectIds (layerNames,
minimal, unique), decimals 1–7, minify, responsive, useArtboards and
range "all" (one SVG per artboard), preserveEditing (the SVG reopens as
the full document) and metadata. Unknown keys are rejected and
document.formats lists every option with its default. Results carry the
SVG writer's warnings.

Save writes SVG to an .svg path and Save again reuses its options.
Export As SVG and Save As / Save a Copy to .svg open SVG Options (first
use: Internal CSS and Responsive, then the last-used choices) with a
read-only code view. Save a Copy no longer retargets the document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:55:59 +07:00
Brandon ThomasandClaude Opus 5.5 3e323bfa2a Robustness: VectorCraft never crashes — no panics in shipped code
Shipped code no longer unwraps, expects or panics: every one of the 124
unwrap()/expect()/panic!/unreachable! sites outside tests now handles the
failure (Result/Option, ?, let-else, defaults) and reports errors to the user.

- Lints: workspace clippy denies unwrap_used, expect_used, panic, todo,
  unimplemented and unreachable; clippy.toml allows them in tests, and
  integration tests, examples and testkit opt out explicitly.
- Safety net (vectorcraft_engine::guard): a panic in a top-level command rolls
  the document back and returns EngineError::Internal; a panicking tool is
  reset; the MCP server answers with an internal error and keeps serving; the
  UI frame and each control request are guarded.
- APIs that hid failures now return them: Rendered::to_png/to_jpeg/to_webp
  return Result (JPEG/WebP errors used to write empty files),
  FontDb::face returns Option, Region::top returns Option.
- Fuzzing: engine/tests/import_fuzz.rs feeds garbage, hostile and mutated SVG
  and PDF through import → render → export (30k cases locally, no panics).
- Docs: AGENTS.md/CLAUDE.md rule, docs/development.md "Robustness" section,
  README pointer and a PR template checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:28:59 +07:00
bflatastic 1ae0c22c26 Merge wp/A1 2026-10-04 21:46:02 +07:00
bflatastic 0af6734b70 Merge M3/M4 wave 0: neutral labels and brands gate, per-area paint modules, one engine loader/encoder (M3.7, M3.8, M4.10-M4.13)
# Conflicts:
#	crates/engine/src/cmd/file.rs
#	crates/mcp/src/headless.rs
#	crates/mcp/src/tools.rs
#	crates/ui-egui/src/io.rs
2026-10-04 21:46:01 +07:00
storybored379andClaude Opus 5.5 152228d76d Raster export: refuse sizes the renderer can't produce instead of crashing
PNG/JPEG/WebP export renders the artboard at the requested scale with no size check. The CPU
rasteriser addresses at most 65535 pixels a side (and panics at that edge), so a large artboard at
a print resolution, e.g. the maximum 16383 pt artboard at 300 ppi (~68 000 px), or a screenshot at
scale 16, panicked inside vello_common: the desktop app or the MCP server exited and unsaved work
was lost.

`vectorcraft_render::raster_size` checks the pixel size first (at most 32 768 px a side and 268
megapixels, like Rasterize already refuses oversized images) and the export paths return an error
that names the size: `document.export` / `exportSelection` / `serialize`, the MCP `export` and
`screenshot` tools, File ▸ Export and the control protocol's `ui.render`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:25:18 -07:00
bflatasticandClaude Opus 5.5 d272d32be3 M3.11: Swatch Options — double-click, the options button or the menu item opens a colour swatch's editor (name, process/spot, Global, Gray/RGB/HSB/CMYK/Web sliders, hex) with a live canvas preview; gradients open the Gradient panel and patterns pattern editing; swatch.edit recolours and renames every linked fill, stroke and text run in one undo step, spot forces Global, grouped swatches edit too; swatch.list query
ui.dialog.cancel over the control channel now rolls back a dialog's live preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:32:17 +07:00
bflatasticandClaude Opus 5.5 7bc50901e4 M4.13: review fixes — template sublayers are left out of raster exports too (the check moves into draw_arc), Export Selection skips art under a template layer at any depth, Export for Screens drops @Nx suffixes on SVG/PDF rows, names files without case clashes and calls an unnamed artboard Artboard-N, oversized raster exports are refused instead of writing empty files, a named MCP artboard/range replaces the option picks, and control app.export without a path returns dataBase64 instead of opening a save dialog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:49:45 +07:00
bflatasticandClaude Opus 5.5 4d503c0327 M4.12: one loader and encoder for every frontend — engine fileio (load/encode/export/save, append-only FORMATS table, OPEN_EXTS, document.formats query, typed per-format options, parse_range); document.open reads native/legacy, SVG/SVGZ, PDF/.ai, .ait (opens untitled like native templates) and PNG/JPEG/GIF/WebP/TIFF/BMP; desktop, web, control channel, headless MCP and the CLI open and encode through it; open dialogs list "All readable files" plus one filter per format
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:36:51 +07:00
bflatastic b8701977d4 M4.9: Save / Don't Save / Cancel before closing modified documents — File → Close, Close All, a tab's ×, Quit and the window's close button ask once per modified document (shown first); Cancel stops the whole close/quit, a cancelled Save As keeps the document open; closing keeps per-document views aligned; agents answer the saveChanges dialog with ui.dialog.confirm (discard: true for Don't Save) 2026-10-02 22:42:50 +07:00
Brandon ThomasandClaude Opus 5.5 4878f1c5dd Rename DrawCraft → VectorCraft: crates (vectorcraft-*), apps, binaries, types, env vars, docs, examples (.vectorcraft); old .drawcraft files and preferences still load. Fix: text bounds exact after opening a file; selection no longer marks a document modified (dirty = content differs from the saved document). Screenshots and renders regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:30:09 -04:00
Brandon ThomasandClaude Opus 5.5 acb2566d89 Distortion tools (Width, Liquify, Puppet Warp, Perspective Grid), Patterns + Repeat, colour management (CMS, proofing, separations), Preferences/Shortcuts/Workspaces; original brand mark; fix filter panic in thumbnails; README demo scenes
- New original DrawCraft mark (gradient + pen nib) replaces the two-letter badge
- render: thumbnails and pattern tiles use a single-threaded context (vello's default is
  multithreaded and panics on filters); per-item raster effects detected
- Type tool state no longer leaks across documents
- ui.set {status} for clean screenshots
- README: new hero, feature shots, Made in DrawCraft gallery; examples/neon-drive, ribbons,
  feature-sheet; ASSETS.md updated; heading renamed to Crafting Apps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:44:39 -04:00