The folder the Additional Plug-ins Folder preference or plugin.reload last loaded is kept for the
whole process. The six engine tests that set the preference or run plugin.reload now hold one lock,
so another test loading its own folder between two steps cannot change what the next step loads or
lists.
Clearing Preferences > Plug-ins & Scratch Disks > Additional Plug-ins Folder
left the folder recorded as the one last loaded. Setting the same folder
again then matched that record, and nothing in it was installed, so a
plug-in removed in the meantime stayed removed. Clearing the preference now
forgets the folder when it is the one last loaded.
While the app draws a document on screen, the live-effect plug-in runs of
its first view share a 2,000 ms budget (VectorCraft's own value); each run
takes the lesser of its 5,000 ms limit and what is left. A stage left out
for time, or one that times out, fails, has no plug-in or meets Safe Mode,
draws its input art unchanged and is deferred, and the status bar says how
many. Later drawings leave a deferred stage as it is until its input or
parameters change.
- effect.deferredStatus lists the deferred stages with object, effect
index, fill or stroke, plug-in and reason, objects in symbol, pattern,
brush and opacity-mask art included.
- Effect › Refresh Deferred Effects (effect.refreshDeferred {ids?}) runs
them again with a fresh budget and returns {refreshed, stillDeferred,
failed}.
- Each document has its own budget and deferred stages; exports, Expand
Appearance and commands run every stage under the per-call limit.
- The web build has no clock for the budget: its first view runs no
plug-in effect, and a refresh runs them.
Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md.
A command's result describes the document, so a plug-in without doc.read
gets -6 for every call, whether or not the id names a command. A run that
calls commands commits an edit still in progress first and prepares its
input again when that changed the document or the selection.
plugin.install and plugin.remove count an entry of grants.json that this
version can't use when it can't be removed. plugin.install stops counting
a grant it can't remove when the old module can't be written back to the
Plug-ins folder.
A filter run kept a second copy of every read-only object as JSON values.
The run now keeps where each object is in the input, and checks an output
entry against it by parsing that one object, unless the entry is `{"id": …}`
alone.
An image that reaches the decoder now counts toward the 16 MiB pixel total
even when decoding fails, so broken images use up the same total as images
that decode.
The plugin.run text, docs/mcp.md and the progress line state the paint
order and that rule, and docs/plugins.md states that a CMYK image's pixels
are sRGB. New tests cover the per-image 1 MiB, the 16 MiB pixel total and
the 16 MiB text total at and one past their values, memory-budget failures
before the plug-in starts, images that are not decoded, CMYK images
included, and leading per run.
When loading the plug-in store can't remove the grant of an id whose stored
module lists no permissions, or can't read grants.json, the entry stays on
disk and still approved the bytes it covers. plugin.install has the same case
when the grant can't be removed and no earlier module can be put back.
The session now keeps those ids and does not count their grants in
plugin.inspect, plugin.install, plugin.reload or a later load of the store. A
successful update of the grant for the id ends that, and so does attaching a
different store.
Only a painted effect's references are checked, so a refusal recorded for a painted effect stayed in plugin.info after an effect that paints nothing replaced it under the same id.
The unread grants test replaces the plain module it installed earlier,
as plugin.install now asks. A grants file over 1 MiB approves nothing,
so the new module stays in the store.
A painted effect's cached result passed its swatch and pattern check and cleared the error of a failed run, so plugin.info reported none while the effect still failed on another object. A run and a reference check now record their errors separately, and each clears only its own.
A grants file that can't be opened or read may name the old module once
it reads again. plugin.remove and an install of a module listing no
permissions now fail on it and keep the module. A file over 1 MiB or one
that doesn't hold a list still approves nothing and doesn't stop them.
Add tests that an action step running plugin.install with grant is
refused, that ui.key, ui.click and ui.pointer sent through the control
path at the open approval dialog approve nothing, with OK focused too,
that MCP answer_dialog reports the app's refusal, that ui.installPlugin
given grant or allowHeavy approves nothing, and that a module recorded
in grants.json installs from a session with that store and no flag
while other bytes under its id are refused.
The batch assertion now checks the refusal's message. The docs and
comments state that Enter confirms the dialog only when OK has the
keyboard focus.
plugin.install of a module that lists no permissions ignored the result
of removing the id's grant. When grants.json could not be updated, the
formerly approved bytes could still be installed without approval. The
store now gets the old module back, so the grant and the module it
covers stay together. The new module stays when the old one can't be
read back and when grants.json can't be read, since such a file approves
nothing. Deleting the last grant now reports a failure.
plugin.remove had the same fault. It now moves the settings and cache
aside and removes the grant before anything is deleted. When the grant
can't be removed, the folder is moved back, the command returns an
error and the plug-in stays installed.
A settings get no longer reads and parses settings.json each time. The
parsed settings are kept per plug-in folder and read again only when the
file's size or modified time changes. A set to the value a key already
has writes nothing, and every other change is written with an atomic
write before the call returns. plugin.remove drops the kept copy.
cacheBytes in plugin.list, plugin.info and plugin.install comes from a
total kept while the cache folder keeps its modified time, instead of a
listing of up to 8,192 items per call. A cache write still lists the
folder to enforce the caps.
docs/plugins.md states what a run counts and that a cache call moves a
whole entry in one step. docs/plugins.md and docs/mcp.md now say when a
later plugin.remove retries a leftover .removed folder.
A path whose live effect paints its pieces was measured with the
flattened output and the source appearance's strokes, so a piece with a
wider stroke of its own could be culled while the stroke was on screen,
and Rasterize and raster effects worked over bounds that were too small.
visual_bounds and shape_bounds now measure the art reshape returns, each
piece with its own appearance, and a raster effect on a fill or stroke
spreads that art.
A piece linked to a global swatch is drawn in the swatch's current color at its own tint, and kept art and rasters follow a color or spot edit and its Undo. A value of `paintOutput` other than true is refused by a translated error. Tests in the effects crate pass the new document argument.
The test for a changed module under a granted id now checks that the next
start, a folder load and a refused install leave the id's settings and
cache unattached.
`plugin.clearCache` without an id empties the cache of every plug-in
installed in the process. In the engine's library tests it could empty
the cache of another storage test running at the same time, and its
`bytesFreed` could count that test's cache. It now runs in
`crates/engine/tests/plugin_clear_cache.rs`, and the library test checks
that clearing one plug-in's cache leaves the other plug-in's cache.
The storage fuzz test starts each case with an empty plug-in store,
deletes the store when the case ends, and installs `fuzz.storage` with
`replace: true`, so the cases that shrink a failure install their
module.
docs/mcp.md says that files `plugin.remove` can't delete stay in the
folder renamed with `.removed` until the plug-in is removed again.