`plugin.install` accepted `grant` and `allowHeavy` from any client, so
the program installing a plug-in could answer its own permission
prompt.
The desktop app now shows an approval dialog that names the module,
its hash and what it asks for. Confirming it gives the engine a
one-use approval tied to that module id, hash and permission list. A
control or MCP request and a frame of synthetic input cannot confirm
the dialog, and Enter does not confirm it.
Without such an approval, `grant` and `allowHeavy` are refused. The
new `--allow-plugin-grants` flag on `mcp` and `run` lets a person who
starts a headless session accept them from requests. The flag cannot
be combined with `--connect`. A module already approved in the desktop
app still installs through its stored grant.
Installing a module that lists no permissions under a granted id now
forgets the grant, in the process and in grants.json, so the formerly
approved bytes need a new approval. A refused install leaves the grant.
Attaching a plug-in store forgets the grant of an id whose stored module
lists no permissions. A plug-in folder load forgets the process's grant
for such an id and leaves grants.json alone, because the store keeps the
module its grant covers.
The Export for Screens dialog and the Asset Export panel open
remembered settings and presets without the row and settings keys the
export no longer reads, and without settings for a format it doesn't
write, so their export and Save Preset are not refused.
A format row and a format's settings take `params`, an exporter
plug-in's own, and `security.passwordRequired`, which remembered
settings hold where PDF passwords were left out, and `settings` takes an
exporter plug-in's format, as before the key check. assets.export and assets.settings.set check the
rows and settings they are given as document.exportForScreens does.
An unknown top-level key of document.exportForScreens gets the
command's usual "the command takes" error.
The MCP save and Export for Screens calls no longer check the params
themselves: Session::execute checks them before anything runs.
swatch.new stored a gradient or pattern as it was given. A gradient
swatch made from a gradient swatch by name, from a fill linked to one,
or from a gradient that names one (the Gradient panel's add to
swatches) kept the link to that swatch, so copying art that uses the
new swatch brought the other swatch along too. A pattern swatch made
from a current fill picked up from art with transformed tiles kept
that transform, and art the swatch was applied to took it in place of
its own.
The new swatch now drops the gradient's link and stores the pattern
untransformed. A gradient swatch keeps its aspect as before.
SVG viewers center each <textPath> glyph on its midpoint on the path and
turn it to follow the path, which is how the Rainbow effect draws. Type on
a path with any other effect and no spacing was written as a plain
<textPath>, so a viewer drew it as Rainbow. It is now written as outlines
with a warning, as type with an end bracket, Align to Path or Spacing
already is.
Docs: docs/mcp.md, docs/target-app-parity.md.
A top object's corner test treats a straight segment as having its
handles a third of the way along it, so an anchor between two straight
segments is a corner only when it turns out of the outline. docs/mcp.md
and the doc comment now say so, and a new test covers an inward anchor
between straight segments. The bulging-curve corner test checks against
a fixed point instead of the same nearest-point routine the code uses.
Saved corners from an .ai file stay on the same anchors when the
envelope is reflected, as they already did when it was moved or
rotated. A reflected outline runs counter-clockwise; the corners were
then mapped as if the outline had been drawn the other way round, no
longer fit it, and were found again from the outline.
On a tie between two corner anchors, or two nearest outline points,
the one met first along the outline in the order it was drawn wins,
for outlines drawn counter-clockwise too.
The docs state the corner rule in full: a handle 0.01 pt long or
shorter makes an anchor a corner whichever way it turns; otherwise its
tangent unit vectors must differ by 0.01 or more in x or y and turn
out of the outline. Content spreads evenly along a segment without
handles.
An anchor with a handle shorter than 0.01 pt counts as a corner of a top
object whichever way it turns; only an anchor whose tangents differ must
turn out of the outline. docs/mcp.md and the progress line in
docs/target-app-parity.md now say so.
A top-object envelope splits its outline into four sides at four corners
and maps the content onto the Coons patch of those sides. The sides
shared out the content by length, and the corners were the anchors or
curve points nearest the bounding box corners.
Each piece of a side between anchors and corners now takes a share of the
content's width or height in proportion to the length of its control
polygon, spread along the piece by its curve parameter. A straight
segment spreads it evenly by length. A rounded rectangle stays close to
the identity, and the content follows the curves of a side whose segments
differ in size.
Each corner is now the nearest anchor to its bounding box corner among
the sharp anchors that point out of the outline and lie within 47.5% of
the box's width and height from that corner. A handle shorter than
0.01 pt, or tangent unit vectors that differ by 0.01 or more in x or y,
makes an anchor sharp, and the first one along the outline wins a tie.
Without such an anchor, the corner is the outline's nearest point to the
box corner, moved onto the end anchor of its segment within 0.5% of the
segment's parameter. Envelopes opened from .ai files keep the corners the
file saves.
These are VectorCraft's own rules. Existing top-object envelopes redraw by
them. Gradient meshes made from a path keep their sides measured by length.
- crates/doc/src/live/mesh.rs: Coons::from_top_object and the corner
search, used for top-object envelopes.
- EnvelopeKind::TopObject gains an optional `corners` field (anchor
indices); the .ai importer fills it from the mesh's corners.
- Params text of object.envelope.makeWithTopObject, docs/mcp.md
(Envelopes) and a progress line in docs/target-app-parity.md.
Covers the other places a Warp now moves placed gradients: a group's
and a nested group's own strokes and a compound path's stroke (with the
stroke-inflated bounds), the art of a symbol instance and the area of a
compound shape with a Warp, and a stroke's own Warp on the canvas. A
new test checks that Arc's no-bend threshold for gradients matches the
one the warp uses, and the no-bend test now covers all fifteen styles.
The render test's Fisheye case, which left the square's bounds as they
were, is replaced by a vertical Bulge, and each style there is checked
to move the outline's left or right edge. The Object > Expand test
checks that it measures the ramp and not an edge of the outline.
The params text and docs/mcp.md now state which bounds each gradient
follows, and that a warp moves them with a bend or a distortion.
- text.spelling.check gives craft-proof one paragraph at a time, as
check_text_with expects: its language detection reads a paragraph, so a
Vietnamese paragraph no longer leaves the English ones after it unchecked,
and the check stops at the paragraph where the 10,000-word list fills up
instead of proofing the rest of the text first.
- Given `ids` are checked in paint order, as the result promises, not in the
order they were passed.
- Docs: English words come from the first dictionary craft-proof finds
(dictionaries folder Hunspell, SCOWL from craft-i18n, system Hunspell),
with the Moby word list as the last fallback, not from Moby alone.
- Doc comments on the command functions and test helpers.
File › Open and File › Place read HEIC and HEIF still images (.heic,
.heif) through heic-rs 0.1.4, the pure-Rust decoder photocraft-heif
uses, and store the pixels as PNG. Single pictures and grids of tiles,
8 to 10 bits, with or without an alpha image, are read with the
container's rotation, mirror and crop applied. A grid without a color
box of its own takes its tiles'. When neither the image nor its tiles
have an nclx box, the HEVC stream's video signal type gives the
samples' matrix and range. An RGB ICC profile, or Display P3 with the
sRGB curve named by an nclx box or, without a color box, by the
stream, is converted to sRGB. The EXIF resolution tags size the image;
without them it is 72 ppi. When the container's rotations turn the
image an odd number of quarter turns, the resolution's axes swap with
the pixels'.
Image sequences, images that aren't HEVC-coded, grids of cropped tiles
and images past the pixel allowance are refused with a message before
the pixels are decoded. The allowance counts each coded picture at the
size its sequence parameter sets give it.
The decoder is in the engine's `heif` feature, which is on by default
and listed by the desktop, CLI and web apps. A build without it still
recognizes the files and refuses them with a message.
window.toolsBesideCanvas reads on with opt_bool, which treats null like an omitted value. docs/mcp.md and the params text now say that an omitted or null on toggles the setting and that any other non-boolean value is an error.
Docked Tools stays at the window's left edge again, outside any panels
docked on the left. Window › Toolbars › Keep Tools Beside the Canvas
(`window.toolsBesideCanvas {on?}`) docks it between those panels and the
canvas instead.
- The setting is its own field in the UI state and in workspaces, and a
missing field reads as off, so saved state and workspaces keep Tools at
the window's edge. Built-in workspaces have it off.
- Off, app.rs draws docked Tools before the docking layout, the canvas
reserves no width for it, and only the full-height window-edge strip
docks a floating Tools panel. On, the canvas panel draws it, the canvas
keeps room for it, and both the window edge and the canvas edge dock it
beside the canvas.
- Turning the setting off forgets the canvas-edge strip, so a strip laid
out while it was on does not dock Tools. A floating Tools panel stays
floating when the setting changes.
- `window.panel.dock {panel: "tools"}` docks Tools where the setting says.
- The menu label has a row in every catalog.
Docs: docs/mcp.md, docs/control-protocol.md, the ROADMAP progress log and
docs/target-app-parity.md. The headline-numbers row and the paragraphs
under the status lines are gone, and scores are unchanged.
With Snap to Grid on, a move of objects on one perspective plane at
different depths applied the snap correction only to the objects at the
first object's depth, and every other object moved by the pointer's
offset read at its own depth. Those objects left the grid and lost their
spacing.
Every object on the first object's plane now moves by that object's
plane offset plus the correction, so the edge of the joint bounds nearer
a gridline lands on it and the objects keep their spacing. A selection
at one depth snaps as before, and objects on other planes keep their own
offset.
Typed list detection gave every bulleted trigger the filled dot. Now
`*` and a space start a list with the filled dot (•), `+` with the
hollow circle (◦), `#` with the filled square (▪) and `>` with the dash
(–), each one of the existing Bullets presets. `1.`, `1)`, `A.` and `A)`
start the same numbered lists as before.
Only the list a typed trigger starts changes. Lists already in a
document keep their bullets, pasted triggers start no list, and Undo
and Redo of a typed list work as before.
Docs: docs/mcp.md (typed list paragraph), docs/target-app-parity.md.
Preferences > Type gets Start Lists From Typed Markers (`typedLists`, on
by default). With it off, a list marker and a space typed with the Type
tool at the start of a paragraph (`1. `, `1) `, `A. `, `A) `, `* `,
`# `, `> `, `+ `) stay as text and start no list. `text.startList`,
`text.setFormat {list}` and the Bullets and Numbered List buttons work
as before.
The preference reaches the Type tool through `ToolContext::typed_lists`.
The label has a row in every catalog.