Commit Graph
74 Commits
Author SHA1 Message Date
dotCooCoo 36755be821 Merge main into fix/plugin-grant-app-approval 2026-11-08 19:30:19 -08:00
BFlat 0089e494f5 Merge pull request #6269 from dotCooCoo/fix/plugin-storage-kept-settings
Plug-ins: settings and the cache total stay in memory between storage calls
2026-11-09 11:13:41 +08:00
dotCooCoo 4941b377f9 Merge main into fix/plugin-grant-app-approval 2026-11-08 19:01:49 -08:00
dotCooCoo 6b2559b63e Docs: a delete of the last setting removes the settings file 2026-11-08 18:33:23 -08:00
dotCooCoo 3de6da24be Docs: a delete of a key with no value writes no settings file 2026-11-08 18:06:41 -08:00
dotCooCoo bd5ea2a1bf Merge main into feat/plugin-effect-paint 2026-11-08 17:59:33 -08:00
dotCooCoo 0f002e7625 Plug-ins: tests cover each route that cannot approve an install
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.
2026-11-08 17:47:02 -08:00
dotCooCoo d6a91b6142 Merge main into feat/plugin-effect-paint 2026-11-08 17:26:35 -08:00
BFlat b652599548 Merge pull request #6264 from dotCooCoo/feat/plugin-importers
Plug-ins: importer plug-ins open file types VectorCraft does not read
2026-11-09 09:24:07 +08:00
dotCooCoo c629753af1 Plug-ins: settings and the cache total stay in memory between storage calls
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.
2026-11-08 17:17:34 -08:00
dotCooCoo 918ef940cb Merge main into feat/plugin-importers 2026-11-08 16:45:20 -08:00
dotCooCoo 727a611ef7 Merge fix/plugin-grant-module-hash into fix/plugin-grant-app-approval 2026-11-08 16:35:45 -08:00
dotCooCoo 627a40ec36 Merge fix/plugin-grant-module-hash into fix/plugin-grant-app-approval 2026-11-08 16:19:43 -08:00
dotCooCoo 988da4538a Merge main into fix/plugin-grant-module-hash 2026-11-08 15:55:53 -08:00
dotCooCoo 3a56f51cf3 Docs: painted live effects state what hit tests and Pathfinder use 2026-11-08 15:43:58 -08:00
dotCooCoo 64c7c2703e Merge main into feat/plugin-menu-commands 2026-11-08 14:41:13 -08:00
dotCooCoo e4e6e0d382 Plug-ins: a painted piece linked to a global swatch follows its color
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.
2026-11-08 13:45:17 -08:00
dotCooCoo 36bca85879 Plug-ins: app.open passes params to the importer File › Open picks
app.open with params and no importer ran file.open with the path alone, so the desktop app ran the importer on its defaults while headless app.open passed the params to document.open. file.open now carries params to the importer that opens the file.

The importer parameters test installs its module with replace from the second pass on, as plugin.install requires. The permission table in docs/plugins.md lists doc.read for filter, effect and exporter.
2026-11-08 12:59:17 -08:00
dotCooCoo 11703bd350 Merge main into feat/plugin-effect-paint 2026-11-08 12:56:42 -08:00
dotCooCoo a717e34f02 Merge main into fix/plugin-grant-module-hash 2026-11-08 12:55:59 -08:00
dotCooCoo d5bb58aad5 Merge main into feat/plugin-importers 2026-11-08 12:38:22 -08:00
dotCooCoo 9518dc0e3a Merge main into feat/plugin-importers 2026-11-08 12:33:17 -08:00
dotCooCoo 85a933ad4f Keyboard Shortcuts: each Object › Plug-ins filter has a row of its own
Squashed from #6246 onto main.
2026-11-09 03:14:41 +07:00
dotCooCoo a224331554 Plug-ins: a failed store write keeps the stored module and its grant together
plugin.install wrote the grant and the module as two separate writes,
and either could fail with only a log record. With a grant tied to the
module's hash, the stored pair then no longer matched and the plug-in
was skipped at the next start.

The module is now written first and its grant only once it is there.
When the grant can't be written, the store gets its old module back, or
loses the new one on a first install, and a disabled plug-in stays
disabled. A stored module that can't be read back is not replaced by
one that needs a grant.
2026-11-08 12:04:03 -08:00
dotCooCoo 218c711181 Plug-ins: a plug-in keeps settings and a cache of its own
Squashed from #6228 onto main.
2026-11-09 02:54:33 +07:00
dotCooCoo 1b30f960e6 Merge main into fix/plugin-grant-app-approval 2026-11-08 11:27:11 -08:00
dotCooCoo a4a516e82f Plug-ins: only a person can approve a plug-in's permissions and heavy budget
`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.
2026-11-08 11:03:38 -08:00
dotCooCoo 64817c2e8c Merge main into feat/plugin-effect-paint 2026-11-08 10:46:47 -08:00
dotCooCoo 79351d7063 Merge main into feat/plugin-importers 2026-11-08 10:25:28 -08:00
dotCooCoo f2e18da4fa Merge main into fix/plugin-grant-module-hash 2026-11-08 10:25:03 -08:00
dotCooCoo 21db71b9a6 Plug-ins: Expand works on each painted piece and stacked painted fills keep the others
Object > Expand of gradient and pattern fills now expands the pieces a
painted live effect draws one by one, so a piece keeps its own paints.
A later painted effect on a fill replaces only the first fill of a piece
and leaves the piece's other fills in place.

Document Info passes the document when it reads an image's painted fills.
2026-11-08 10:11:18 -08:00
dotCooCoo 054c14a819 Plug-ins: a file only an importer reads opens when dropped on the canvas
File › Place reads only built-in formats, so a dropped file that only an
importer reads failed with a place error. It now opens as File › Open
opens it, with or without a drop position, also when an exporter lists
the same extension.
2026-11-08 09:38:30 -08:00
dotCooCoo d61a7abc96 Save: include a PDF in new native files by default
Squashed from #5320 onto main.
2026-11-09 00:25:34 +07:00
dotCooCoo 3d80b6a133 Plug-ins: the kind-mismatch error reads correctly for importers and exporters, and a failed import leaves the open document alone
A plug-in of the wrong kind now gets "is a plug-in of kind `exporter`, not an importer", with rows in every complete-message catalog. The importer failure test also covers an open, edited document. docs/plugins.md states that Open Recent does not remember Open With's importer or parameters.
2026-11-08 09:13:30 -08:00
dotCooCoo 7aa7890a10 Plug-ins: a painted result names swatches and patterns of the document it draws in
The reference check now applies wherever a painted effect's geometry is
used: a result whose swatch or pattern is refused keeps its input instead
of giving its geometry. Pathfinder art of type, reshaped groups, compound
shapes, image fills, the renderer's bounds and Object > Expand look the
references up in the document, and the renderer drops its kept bounds and
geometry when the names of the swatches or patterns change.
2026-11-08 09:04:23 -08:00
dotCooCoo 91d9639e6a Plug-ins: a permission grant covers only the module that was approved
A grant records the module's SHA-256, but only its permissions were
compared, so a module with other bytes under a granted id inherited the
grant when it listed no new permission.

A grant now covers a module only when the id, the SHA-256 and every
listed permission match. plugin.inspect reports needsApproval for a
changed module, plugin.install refuses it without a new grant and leaves
the installed module and its grant as they are, Install Plug-in and
File > Open ask again, and the plug-in store and plug-in folders skip it.
The same bytes install again without asking, and a module with no
permissions list or an empty one needs no grant. A grant whose hash is
not 64 lower-case hexadecimal digits covers nothing.

plugin.install still trusts the client that sends grant; the docs say so.
2026-11-08 08:40:00 -08:00
dotCooCoo 2772a44efd Plug-ins: Open With runs an importer off the UI thread, and Cancel stops it
File › Open With reads a picked file on the thread File › Open uses, with the importer and its parameters, so a slow importer does not freeze the window. A call that names path still gets document.open's result. Cancel in the progress window now raises a cancel token on that thread, which stops a running importer between two slices of its budget.
2026-11-08 08:09:23 -08:00
dotCooCoo edd6acd9c0 Plug-ins: File › Open lists only the importer extensions it opens with an importer
An importer may declare an extension that File › Open handles another
way: a plug-in, shortcut set, script, preset, library or swatch library
such as .ase, or one a built-in format reads. "All readable files" and
the importer's own filter no longer list those extensions, and an
importer with none left gets no filter. Open With still opens them.
2026-11-08 07:31:40 -08:00
dotCooCoo 9f2db12ec2 Plug-ins: a painted live effect's paint keys are checked at every level
A key that a paint does not have, such as `opacity` on a solid fill, was
dropped when the paint was read, and the fill was drawn opaque. The
result is now refused and `lastError` names the field, for keys on the
paint and on its colors, gradient, stops, placement and freeform points.
2026-11-08 07:24:55 -08:00
dotCooCoo d8afed1562 Plug-ins: painted pieces keep their swatches in Pathfinder and baked art
Pathfinder effects, compound shapes, Expand Appearance and pattern
expansion now pass the document to the effects they evaluate, so a
painted piece that names a swatch or pattern takes part as drawn instead
of being refused. A path with a painted effect takes part in Pathfinder
and compound shapes as its pieces, each with its own paint, inside the
path's opacity and blend mode.

The renderer's kept effect art and live evaluations now count the
document's swatch and pattern names for objects with a plug-in effect,
so deleting or restoring a swatch a piece names redraws the object.
2026-11-08 05:52:39 -08:00
dotCooCoo d65207dc62 Plug-ins: only File › Open lists importers' files, and imported art at the canvas edge keeps an editable artboard
New from Template, Load Character/Paragraph/All Styles, Recolor's Other
Library… and the library panels' Other Library… read files with the
built-in readers, so their open dialogs now use builtin_open_filters(),
which leaves out the extensions only importer plug-ins read. File › Open
keeps open_filters() with the importers' files.

The artboard of a document an importer opens is now kept within
±4,000,000 pt. Art at the edge (a zero-width path at x = 4,000,000, or a
stroke past the edge) gave an artboard beyond it, and every later edit
of the document was rejected.
2026-11-08 05:35:23 -08:00
dotCooCoo 58f5789e9a Plug-ins: Open With passes an importer its parameters and adds the file to Open Recent
File › Open With asks for the parameters of an importer that declares
any, at their defaults, once the file is picked (dialog `pluginImport`),
and opens the file with the values set. `file.openWith`, `app.open` and
MCP's `open_file` take `params` and pass them to the importer;
`app.open` without them runs the importer on their defaults, with no
dialog. A file opened with Open With is added to Open Recent, as File ›
Open adds the files it opens.
2026-11-08 05:07:08 -08:00
dotCooCoo 7a38e2c990 Merge main into feat/plugin-menu-commands 2026-11-08 02:04:06 -08:00
dotCooCoo a3c37ea1f4 Merge main into feat/plugin-importers 2026-11-07 23:49:11 -08:00
dotCooCoo c2f0a5667f Merge main into feat/plugin-effect-paint
# Conflicts:
#	crates/engine/src/cmd/plugin.rs
#	crates/engine/src/store.rs
#	crates/engine/src/tests/plugins.rs
#	crates/engine/tests/plugin_fuzz.rs
#	crates/plugins/src/lib.rs
#	crates/plugins/src/manifest.rs
#	crates/plugins/src/permissions.rs
#	crates/plugins/src/runtime.rs
#	crates/ui-egui/src/i18n/bg.tsv
#	crates/ui-egui/src/i18n/bs.tsv
#	crates/ui-egui/src/i18n/ca.tsv
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/da.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fi.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hi.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ka.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sv.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	crates/ui-egui/src/io/open.rs
#	crates/ui-egui/src/tests/plugins.rs
#	docs/mcp.md
#	docs/plugins.md
#	docs/target-app-parity.md
2026-11-07 23:43:15 -08:00
dotCooCoo b109378ae6 Plug-ins: live-effect plug-ins at API level 2 can paint the pieces they return
A live effect whose manifest has "api": 2 and "paintOutput": true returns
{"objects": [...]} as pieces, each drawn by itself in order with fills and
strokes of its own in the forms a filter returns. A piece without fills or
strokes keeps the ones the effect was given, and an empty list paints none.
An effect on a fill returns only fills, one on a stroke only strokes.

The whole result is checked before any of it is drawn: the output limits,
finite numbers in range with nothing clamped, known keys only, and every
swatch and pattern it names in the document. One fault draws that step of
the stack unchanged, keeps the effect record and puts the plug-in's name
and the field in plugin.info's lastError. Each piece counts against the
geometry-effect budget, so the painted effects of one stack make at most
100,000 pieces.

The pieces are drawn, measured and exported to PDF and SVG from the
appearance stack without changing the document, and Expand Appearance
writes them as ordinary paths in one undo step. The plug-in API level
rises to 2; plugin.list and plugin.info give api and paintOutput. Effects
without the key behave as before.
2026-11-07 23:22:11 -08:00
dotCooCoo fd94ebe86c Plug-ins: importer plug-ins open file types VectorCraft does not read
An importer plug-in (kind "importer", permission import.read) lists 1 to 8
file extensions and turns a file's bytes into objects, which open as a new
document whose artboard is their bounds. File › Open lists its extensions,
File › Open With (file.openWith) opens a file with a chosen importer, and
document.open takes importer and params. A built-in format that reads the
file by its content or extension always opens it; among importers listing
the same extension, the one whose id sorts first opens it. An importer reads
at most 128 MiB, and Place still reads only the built-in formats.

Docs: docs/plugins.md, docs/mcp.md, docs/gaps.md, docs/target-app-parity.md.
2026-11-07 23:05:09 -08:00
dotCooCoo 960bc64197 Plug-ins: recording a grant keeps the grants file entries it can't read
A grants.json entry this version can't use (a permission it doesn't know,
an invalid id) is now skipped on its own instead of dropping every grant in
the file, and recording or removing a grant writes it back unchanged,
except an entry for that plug-in. A grants.json that is too large, can't be
read or is not a JSON list is left as it is, and a rewrite that would pass
the 1 MiB read limit is written compactly, or not at all.
2026-11-08 13:21:16 +07:00
dotCooCoo b071fb109c Plug-ins: the host import fuzz test reaches vc_log's text checks
The fuzz test for hostile host import arguments drew vc_log's level from every i32, so almost
every call stopped at the level check and never read memory. Levels are now also drawn from -1
to 4. The module docs, a test doc comment and docs/plugins.md now say that with an unknown total
each change of done is passed on.
2026-11-08 13:21:16 +07:00
dotCooCoo 1c676fc372 Plug-ins: API level 2 lets a plug-in report progress, see Cancel and write log lines
A plug-in whose manifest gives "api": 2 and lists the new `progress`
permission may import three host functions from module `env`:
vc_progress(done, total), vc_cancelled() -> i32 and
vc_log(level, ptr, len) -> i32. Installing checks every import and
refuses, by name, one the host doesn't offer, one with another type and
one the manifest doesn't allow.

- Progress is clamped and passed on once per tenth of a percent (past
  2,000 reports, once per 1,000 changes). A heavy filter's progress
  window and the export's progress window show it as a percentage bar,
  and their Cancel makes vc_cancelled return 1. A live effect's progress
  is not shown.
- A run cancelled before it returns ends as cancelled, and one that
  returns after its time budget ran out ends at that budget, so what a
  plug-in returns after it sees vc_cancelled changes nothing. This also
  applies to a plug-in of API level 1 that finishes after a Cancel or
  past its time budget.
- Log text must lie in the module's memory and be UTF-8. Lines go to the
  app's log as "<id>: <text>" with hidden characters escaped, at most
  1,024 characters a line, 200 lines and 32 KiB a run, and the host reads
  at most 1 MiB of log text a run.
- plugin.list and plugin.info give the manifest's `api`; the install
  prompt names the new permission in plain words.

Docs: docs/plugins.md, docs/mcp.md, docs/target-app-parity.md.
2026-11-08 13:21:15 +07:00