#2227 renamed them Flip Horizontal and Flip Vertical; what they do is unchanged. The reference app's Properties panel calls them Flip Along Horizontal Axis and Flip Along Vertical Axis (the Transform panel's menu keeps Flip Horizontal and Flip Vertical), so the labels and their catalog rows come back.
A Ctrl/Cmd+Alt-click on a row's eye in the Layers panel ran Hide Others
or Show All Layers, as an Alt-click does. It now runs Outline Others for
the row's layer (layer.outlineOthers), or Preview All Layers
(layer.previewAll) when every other top-level layer is in Outline
already. Each is one undo step. Alt-click and Ctrl/Cmd-click on an eye
keep their actions.
Docs: docs/mcp.md, docs/target-app-parity.md.
In the Color Picker, Tab out of an edited field gives the next field the
focus in the same frame, and the picker applies the edit once it has
drawn its fields. The # field after Y took the focus showing the color
from before the edit and kept that text while it had the focus, so the
next Tab committed it and reset every other field. Shift+Tab into the #
field from K had the same effect. Swatch Options and New Swatch draw a
copy of the field, and Tab from the last component field into it had the
same effect there.
widgets::hex_field and the Swatch Options copy now show the current
color until something is typed into them, also while they have the
focus, and commit only what was typed. The change covers the # fields of
the Color Picker, Swatch Options, New Swatch, the Color panel, Color
Themes, Recolor Artwork, Document Setup and Define Perspective Grid.
widgets::opt_field, which draws the Stroke panel's dash and gap fields,
kept its text the same way: passing through a blank gap after typing a
dash committed the blank, and the panel applied the same dash pattern
again. It gets the same change. When its value changes while it has the
focus and nothing has been typed into it, all of the new value is
selected, as on keyboard focus.
The Color Picker drew its value fields as one grid, row by row. Tab
moves through the fields in the order they are drawn, so it went H, L,
S, a, B, b, R, C, G, M, B, Y, # and K, from one color model to the
other on every row.
HSB, RGB and the hex field are now in one grid, and Lab and CMYK are in
a second grid beside it. Tab goes through H, S, B, R, G, B and #, then
L, a, b, C, M, Y and K. The dialog looks as before: K's row is as tall
as the hex field's row beside it.
The Color panel and the Color Picker preview a colour in an interaction.
While typing, that interaction joined the open typing session and
previewed from the document before the typing, so new type came back as
its placeholder text. Typing in progress is now kept as its own step
before another interaction begins, as Undo already does.
object.setBounds read x, y, width and height with f64_or, which falls back
to the current value for anything that isn't a JSON number, so
{"width": "60 mm"} returned ok and changed nothing. object.move,
object.nudge, object.rotate, object.scale (sy) and object.shear did the
same with their optional numbers. A value that is neither a number nor
null is now a BadParams error naming the key, as a missing required
number already is.
The importer wraps its reason in "the file's layers weren't read ({why}):
it comes in as its page, in one layer" (and, for an .ai, "... from its
editing data ({why}): they come from its PDF part"). The reasons had @msg
rows, the wrappers none, so after "Opened with 1 note(s):" the whole note
stayed English. The two templates are now in every language that
translates messages (and pt-br, which has the reasons), and the reason
inside is translated through them. These notes are pushed as warnings, so
the message scan doesn't see them: a test checks them in each language.
"<Clip Group>" already has a French entry on main; layer names such as
"Layer 1" are stored names and stay as they are.
The profile is registered in the process's colour registry only, so the
next `vectorcraft-cli run` (or the next launch of the app) doesn't know it
and edit.colorSettings falls back to the built-in profile. The command's
description now says so, and that the profile has to be loaded again in
each run that names it. Keeping profiles across runs would need a place in
the preferences to remember them, which is left for the maintainers.
The tool's default key is "+", and `shortcuts::parse` split a bare "+" as a
separator with nothing on either side: the key part came out empty, so the
shortcut didn't parse, `tool_for_key` found no tool for a typed "+" and the
key did nothing. "-" (Delete Anchor Point) has no separator to trip on, which
is why it worked. A bare "+" is now the key itself, the "=" key, as "Cmd++"
already was, so "+" typed with Shift on a US layout, by a layout's own "+"
key or on the numpad selects the tool.
The reference app resets tracking and kerning to 0 with Cmd/Ctrl+Alt+Q.
VectorCraft had no command for it.
type.resetTracking (Reset Tracking and Kerning, default Cmd+Alt+Q) sets
tracking to 0 and kerning to Auto, which the Character panel shows as 0,
in the text the Type tool has selected, or else in all the text of
every selected type object. At a caret it resets only the kerning of
the character before it, where Alt+Left/Right kerns. It is one undo
step. The range logic of type.step moves into a shared type_ranges.
When a call gives ids, type.step and type.resetTracking change all the
text of those type objects even while the Type tool edits text, as
paint.setFill does. Before, type.step changed the Type tool's selected
text instead.
Docs: docs/mcp.md, docs/target-app-parity.md.
inflate built an edge index (Edges) of the shape's rings, and
Membrane::solve, which inflate calls first, built a second index of the
same rings. Membrane::solve now takes the index that inflate builds.
In a release build, one Edges::new takes 0.08 ms of the 27 ms that
inflate takes on a ring of 2,000 points, and 2.0 ms of 219 ms on a ring
of 1,000 spikes whose 2,000 edges fill 693,816 bucket entries. The
facets do not change: for nine shapes, three views and 96 combinations
of the Inflate settings, the Debug text of all 2,592 results is byte
for byte the same on main and with the change.
document.export, document.serialize and document.exportSelection write
a PDF-compatible .ai (format: "ai"), and document.formats lists ai as
writable, but none of their params texts named it. The params text of
document.exportSelection named only png, jpg, webp, svg, svgz and pdf,
although the command writes all 20 writable formats. The description
of the MCP export tool and its row in docs/mcp.md named every format
of the tool's format enum except ai, and the range text and the row
said that only PDF writes several artboards.
The three params texts now list every format their command writes,
and document.export's range and useArtboards texts name .ai beside
PDF. The MCP export tool's description names ai, and its artboard and
range texts say that .ai, like PDF, writes one page per artboard. The
range text and the row also say that SVG, SVGZ and EPS write one file
per chosen artboard, and that rasters, DXF, EMF and WMF do so with
options.useArtboards: true.
Docs: docs/mcp.md (the export tool).
The document.exportForScreens params text listed png, png8, jpg, webp,
gif, svg, svgz and pdf for a row's format, and its settings keys left
out svgz. A row of another format VectorCraft writes, such as eps, was
refused with "Export for Screens writes png, png8, jpg, webp, gif, svg,
svgz or pdf". Rows of tiff, bmp, tga and psd are written too, and the
Export for Screens dialog offers TIFF rows.
The params text, its settings keys and the error now list the 12
formats a row writes: png, png8, jpg, webp, gif, tiff, bmp, tga, psd,
svg, svgz and pdf. Which formats a row accepts does not change.
The Print section of docs/mcp.md said that File → Print writes the job
as a print-ready PDF and that there is no printer driver. The desktop
app sends the PDF to the system's print service, the web build opens
the browser's print dialog on it, and the PDF File entry in the
Printer list saves it. The section now says so, points to Printing,
and marks the `file.print` reply that follows as the headless one.
The Russian catalog's Isolate row began with a Latin "A" (U+0041),
"Aизолировать", and the Create New Sublayer and New Sublayer… rows
spelled "подслой" with a Latin "c" (U+0063). The rows now read
"Изолировать", "Создать новый подслой" and "Новый подслой…".
A new test fails when a word in any catalog's translation holds letters
from more than one of the Latin, Cyrillic and Greek scripts.
hayro-syntax's Dict::entries still unwraps each value it reads, so a
dictionary cut short (or with a stray token) aborts the import. The
craft-libs ArrayIter now stops at the first unreadable item instead of
panicking, which also means a hostile item after a self-referencing
function or colour space is silently dropped from nests_without_end.
- import_color: `readable(array)` checks every item parses; `items()`
yields nothing for an unreadable array; `entries()` reads a dictionary
key by key through get_raw, skipping values that don't parse.
- pages/editing/import_scan: use the helpers instead of raw_iter() and
Dict::entries().
- nests_without_end refuses a file with an unreadable array.
- Tests: two refusal cases in pdf tests and an .ai cut inside an array
or dictionary in engine/tests/import_fuzz.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcHisW1vt6pkVPYbzL6Cpg