effect.deferredStatus lists a stage in the art of a symbol, pattern, brush
or opacity mask by the id of the object in that art. effect.refreshDeferred
took such an id only while the object still had a deferred stage; an
existing object there without one was refused as no such object, while a
layer object without a stage was accepted. An id is now valid when it names
an object in the layers or in that art, an object without a stage adds
nothing to the counts, and an id of no object is an error.
The params text and docs/mcp.md said `id` names one object, but the params
text did not list the key, so `{id: N}` was refused as an unknown
parameter. It is now `{ids?|id?}`, and the command checks only the form of
the ids before it looks them up itself.
The requested ids are held in a set while the art is walked, and only those
with a stage are handed to the refresh.
registry::grant takes a GrantToken, which the engine makes after a
person's approval and when it loads grants.json. registry::install,
install_bytes and folder loads refuse a module that lists permissions no
recorded grant gives this very module (Error::NeedsApproval), and leave
the plug-in under its id and the revision as they were. A folder load's
approve callback only narrows what installs. A module without
permissions, or with an empty list, installs as before.
A folder load in a session with a plug-in store records the store's
grant for each module it approves, so those modules still load.
docs/plugins.md says what the approval does not protect against: native
code linked into the app, and another program running as the same user,
which can edit the Plug-ins folder and its unsigned grants.json.
The grants that should be gone from grants.json but could not be removed
were kept per Session, so a second Session attached to the same Plug-ins
folder in the same process still counted them for plugin.inspect,
plugin.install and loading plug-ins. They are now kept for the process,
by the folder's canonical path and the plug-in id, and a successful
write of grants.json for the id in any session makes the grant count
again. Attaching another folder no longer clears them.
A failed removal does not override a write another session made for
the id in the meantime. The process keeps the latest 256 grant writes
for that check. Another process on the folder still reads grants.json
as it is written.
When plugin.remove could not remove the grant and then could not rename
the plug-in's settings folder back from <folder>.removed, the failure was
only logged: the plug-in got empty settings, and the next removal deleted
the .removed folder with the settings in it.
- The error now names the .removed folder, the plug-in folder and the
reason, and the plug-in gets no storage until the settings are back.
- The next load, install or plugin.remove of the plug-in renames a kept
folder back while the plug-in has no folder of its own. When it has one,
that folder is used and the kept one stays until plugin.remove deletes
both. A removal while a kept .removed folder exists renames the plug-in
folder to <folder>.removing.
- Before deleting a renamed folder, the store writes an empty
<folder>.removed.delete file and removes it once the folder is gone, so
what a failed deletion leaves is not moved back. When that file can't
be written, nothing is deleted.
- A .removed folder without a .delete file, including one an earlier
version left, counts as kept settings: a new install of the id needs
replace: true, and plugin.remove of the id deletes it.
every_command_survives_empty_params and
read_only_commands_read_the_instance_of_a_picked_object run every command
with {}. That includes plugin.clearCache, which without an id empties the
cache of every plug-in installed in the process. When either sweep ran
during plugin_storage_is_capped_and_survives_a_damaged_settings_file
(called from plugins_keep_settings_and_a_cache_of_their_own_in_the_store),
it deleted that test's cache entries Tile and tile. The test's
st.clear_cache() == MAX_CACHE_BYTES + 3 assertion after the
MAX_CACHE_SCAN folders then failed with 536870912 bytes freed.
Both sweeps now leave plugin.clearCache out.
tests/plugin_clear_cache.rs already runs it with {} in a test binary of
its own. A new test runs the other plug-in commands with {} in the
selection states that every_command_survives_empty_params uses, and checks
that an installed plug-in's cache keeps its 3 bytes.
With Real-time Drawing off, a tool stores its latest preview and applies
it only when the drag is released. A command that ends the drag first
(Undo, Redo, a filter that calls commands, a plug-in command that calls
commands, and the other callers of settle_interaction) committed the
interaction without that preview, so the drag was dropped: Undo then
took back the step before it, and a calling filter was given the
objects where they were before the drag.
settle_interaction now applies such a preview first, as the tool's own
commit does, and cancels the drag when the preview fails. The re-prepare
of a calling filter's input after settling is now reached, and tests
cover it at both plugin.run and plugin.<plug-in>.<command>.
When plugin.install can't write or remove a grant and the write that puts
the old module back reports a failure, install now reads the stored module
again and turns the plug-in on only when the store holds the new module. A
put-back that wrote the old bytes and then failed (for example at its last
flush) leaves a disabled plug-in disabled. This applies to modules that
list permissions and to modules that don't.
When plugin.install writes a module that lists permissions, its grant
can't be written, and the old module can't be put back (or, on a first
install, the new module can't be removed), the store holds the new
module. install now takes put_back_plugin's result, so the plug-in's
disabled entries (by id and by stored file) are cleared as for any
install the store holds. The old grant, or none, still doesn't cover the
new module, so the next start skips it until it is approved.
Docs: docs/plugins.md, docs/mcp.md, docs/target-app-parity.md.
A test-only switch makes removing grants.json fail on the test's thread while the file still reads. The new test replaces an approved plug-in with one that asks for no permissions while the switch is on, and checks that the store gets the approved module back and keeps the grant unchanged. The Windows test that holds the file open without delete sharing stays.
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.