Skip to content

File › Scripts: run JavaScript scripts over the command registry (G6) - #1183

Open
LloydNicholson wants to merge 34 commits into
storytold:mainfrom
LloydNicholson:scripting-1161
Open

LloydNicholson wants to merge 34 commits into
storytold:mainfrom
LloydNicholson:scripting-1161

Conversation

@LloydNicholson

@LloydNicholson LloydNicholson commented Oct 11, 2026 •

Copy link
Copy Markdown

Closes #1161. Part of gap G6.

What

  • File › Scripts › Other Script… (Cmd/Ctrl+F12, desktop and web) and vectorcraft-cli script FILE.js [DOCUMENT] [--out FILE]... [--arg KEY=VALUE]... run a JavaScript file over the command registry. Scripts get a global vc (run, document, commands, log, args) and alert. Guide: docs/scripting.md.

  • The Scripts menu lists scripts to run again (desktop):

    • the .js files in VectorCraft Scripts in the documents folder, with subfolders as submenus. Open Scripts Folder creates and shows it, and the menu follows the folder without a restart;
    • the last ten scripts run from anywhere else, newest first, with Clear Recent Scripts. A recent script that's been moved or deleted leaves the list when chosen.

    The web keeps Other Script… only, because the browser gives no file paths.

  • Undo and failures. A run is one undo step per document it changes. An uncaught error, a rejected promise or a limit rolls everything back: every document (including ones the script opened, closed or undid steps in), paint defaults, the clipboard and the Untitled-N count.

  • Engine. command.batch is now a loop over public Session::{begin_batch, batch_step, finish_batch, roll_back_batch}. Scripts use the same pieces, and its error texts and journaling are unchanged. Two fixes come with it and apply to command.batch too:

    • A batch with an edit.undo step now brings that step back when it fails.
    • Typing still in progress is kept as its own step before a batch starts.
  • New L6 crate vectorcraft-script. It embeds Boa 0.22 (MIT OR Unlicense), with its annex-b legacy web features on and Temporal and Intl off. Scripts reach nothing but commands; --automation-read-root and --automation-write-root confine them on the CLI.

  • Limits (none can be caught by the script):

    • 10,000,000 loop iterations per run;
    • recursion 512;
    • 100,000 commands;
    • an instruction budget of about 8 s of work;
    • 1 MiB of source;
    • source nested more than 256 levels of brackets, or with very long expression chains, is refused with a position (also in eval, new Function and JSON.parse).
  • Stack. Boa's parser needs about 28 KB of stack per nesting level, so wasm's default 1 MiB stack overflowed at about 36 levels. Native builds now run each script on a thread with a 256 MiB stack, and the web build's stack is 32 MiB (.cargo/config.toml).

Not included: Illustrator's scripting object model (.jsx scripts written for Illustrator don't run) and recording scripts into Actions.

Testing

  • crates/script/tests/api.rs covers:
    • one undo step and full roll back;
    • try/catch around failing commands;
    • limits that can't be caught (including a caught command flood);
    • error positions;
    • JSON at the boundary;
    • BOM, CRLF, empty and non-UTF-8 sources;
    • hostile scripts: deep nesting, long chains, deep eval and JSON.parse, values that share objects, and an async throw.
  • Corpus of 40 scripts (crates/script/tests/corpus), each with a checked outcome:
    • generative art, bulk edits, layout, text, export, reports and failures;
    • language coverage, including Annex B;
    • four unmodified MIT libraries driven by original scripts: tinycolor 1.4.2, Alea (seedrandom 3.0.5), simplex-noise 2.4.0 and bezier-easing 2.1.0. tinycolor is what showed Annex B was needed.
  • script_fuzz.rs:
    • random token soup, including bracket runs, eval and JSON.parse, never panics or crashes, and a failed run changes nothing;
    • every command run from a failing script rolls back exactly. This sweep found the edit.undo roll-back gap.
  • stack.rs: deep data built at run time uses the script's own stack.
  • Engine: batch-run tests, and typing in progress across a batch.
  • UI: a headless test of the menu item, picked scripts, the error and alert dialogs, the web inbox, and the Scripts menu: folder listing with submenus, the recent list (newest first, ten at most, no duplicates), a missing file dropped with a message, Clear, Open Scripts Folder, and the shortcut only on Other Script….
  • CLI: end-to-end tests.
  • Checked by hand in the desktop app: a 12-petal drawing script is one undo step and shows its alert; a failing script shows file.js:LINE:COL plus its log and leaves the page untouched. Two scripts run from Downloads appear under File › Scripts, newest first.
  • cargo xtask ci is green (fmt, clippy, tests, assets, brands, layers, wasm).
  • Web build size: 73,808,094 → 80,976,908 bytes (+7,168,814, +9.7%). That's the raw release .wasm from cargo build --release --target wasm32-unknown-unknown -p vectorcraft-web, measured without trunk or wasm-opt. The 32 MiB stack is confirmed from the module's __stack_pointer; deep scripts weren't run in a browser.

Known limits (in docs/scripting.md)

  • Built-in functions run outside the limits. A script that hands one an enormous input can freeze the app or use up its memory: a billion items, a billion-character string, or data deliberately built about 100,000 levels deep (measured: 50,000 levels fine, 200,000 overflows even the 256 MiB stack).
  • Undo inside a script uses up the step before the script.
  • A failed Save As inside a script leaves the document pointing at the new path.
  • Scripts started from the menu don't get the Artboards panel's active artboard.

🤖 Generated with Claude Code

lloyd-nich and others added 16 commits October 11, 2026 13:53
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atch uses them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…one undo step, full roll back

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ort, reports, failures, language)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…easing) in the script corpus

tinycolor needs String.prototype.substr, one of the legacy web features
(Annex B) every browser has: Boa's annex-b feature is now on, covered by
lang_legacy_web.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Undo (and Type, inline graphics) commit the interaction in progress, which
dropped the batch's own and its snapshot with it; and the undo history
wasn't part of the roll back. Batches keep their interaction through such
steps and restore each document's history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rom a failed script

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A batch (or script) started while the Type tool was typing joined that
typing's interaction: undoing it took the typing too, and a failed batch
threw the typed text away. Like Undo, a batch now keeps what is in
progress first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ues or drop async errors

- Scripts nested more than 256 levels of brackets, or with expressions
  of more than ~2,500 chained operators, are refused with a position
  before Boa parses them (its parser and compiler recurse per level and
  a stack overflow aborts the app); eval, new Function and JSON.parse
  check their text the same way.
- Native builds run each script on a thread with a 256 MiB stack (in the
  caller's automation-root scope); the web build's stack is 32 MiB
  instead of wasm's 1 MiB.
- Values passed to commands, logged or returned hold at most 1,000,000
  entries in all: a value reusing the same objects no longer expands
  exponentially.
- A promise rejection nothing handles (a throw in an async function)
  fails the run and rolls it back; an async main's value is the
  script's value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n lately

The .js files in VectorCraft Scripts (in the documents folder; subfolders
as submenus) are listed by name and followed without a restart, then the
last ten scripts run from elsewhere, newest first, then Other Script…,
Open Scripts Folder and Clear Recent Scripts. A recent script that's gone
leaves the list with a message. The web keeps Other Script… only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…akes the last colour set

A loop, recursion or instruction limit has no place of its own: its error
used to borrow an earlier call's line from the backtrace. The guide now
says paint.setFill/setStroke also set the defaults new art gets, with an
example that colours text explicitly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lloyd-nich and others added 13 commits October 11, 2026 17:04
…g-1161

# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	docs/gaps.md
#	docs/target-app-parity.md
Czech now translates status messages (upstream); the three from File ›
Scripts get their rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lloyd-nich and others added 3 commits October 12, 2026 03:31
Hungarian became a complete language on main; the scripting strings
need its translations for the i18n completeness tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lloyd-nich and others added 2 commits October 12, 2026 04:58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Messhias

Copy link
Copy Markdown
Contributor

Big G6 step: Boa over the command registry, CLI script, Scripts menu, and wasm stack bump are the right shape for a Rust-native suite (no handwritten JS runtime of our own).

A few points aligned with how this repo usually lands features:

  1. Product call. Illustrator's ExtendScript/object model is explicitly out of scope; this is "JS over our commands". That is a real product decision for the owners (same class as other beyond-parity automation). Worth keeping the ROADMAP wording honest: command-registry scripting, not AI-compatible ExtendScript.
  2. Sandbox / roots. Please confirm scripts honor --automation-read-root / --automation-write-root (and the engine-level confinement landing for mcp: add --automation-read-root / --automation-write-root so the server can be confined to a folder #832) for every path a script can pass through vc.run / exports. Fail closed when confined.
  3. Resource limits. Good that Boa runs with an instruction budget (fuzz). Caps on wall time, memory, and nested vc.run / command.batch depth would match the "never crash / untrusted input" rule; script files and args are untrusted.
  4. One change surface. Third-party fixtures under crates/script/tests/thirdparty need clear licence notes (NOTICE / test-only). Also please keep people.toml to the author's own entry only if you add credit.

I have not run the full suite on this branch; happy to re-check once the confinement story is explicit in docs/scripting.md.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File › Scripts: run JavaScript scripts over the command registry (G6)

3 participants