From 13098fa9f403c25252112e656ad065da3f78632b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabio=20William=20Concei=C3=A7=C3=A3o?= Date: Wed, 11 Nov 2026 08:30:33 +0000 Subject: [PATCH] MCP: list UI_COMMANDS in command_list and close catalogue gaps (#6460) Headless command_list exposes the desktop UI catalogue with ui: true, docs clarify effect vs command ids, and prompts cover spelling, variables and path tools. --- crates/mcp/src/headless.rs | 35 ++- crates/mcp/src/lib.rs | 1 + crates/mcp/src/prompts.rs | 110 +++++++ crates/mcp/src/tests.rs | 71 +++++ crates/mcp/src/tools/definitions.rs | 4 +- crates/mcp/src/ui_catalog.rs | 455 ++++++++++++++++++++++++++++ docs/gaps.md | 2 + docs/mcp.md | 47 ++- docs/roadmap.md | 1 + docs/target-app-parity.md | 3 +- 10 files changed, 709 insertions(+), 20 deletions(-) create mode 100644 crates/mcp/src/ui_catalog.rs diff --git a/crates/mcp/src/headless.rs b/crates/mcp/src/headless.rs index 32735d9a0..4d4a746dd 100644 --- a/crates/mcp/src/headless.rs +++ b/crates/mcp/src/headless.rs @@ -129,10 +129,17 @@ impl Headless { fn exec(&mut self, id: &str, params: &Value) -> Result { // A key the command's description doesn't name is an error, not a default (#1442). match HOST_COMMANDS.iter().find(|c| c.0 == id) { - Some((_, _, doc)) => vectorcraft_engine::check_keys(id, doc, params), - None => self.session.check_params(id, params), + Some((_, _, doc)) => vectorcraft_engine::check_keys(id, doc, params).map_err(|e| e.to_string())?, + None if vectorcraft_engine::find_command(id).is_some() => { + self.session.check_params(id, params).map_err(|e| e.to_string())?; + } + None if crate::ui_catalog::UI_COMMANDS.iter().any(|(cid, ..)| *cid == id) => { + return Err(format!( + "`{id}` is a desktop UI command (`ui: true`); start `vectorcraft --control 7979` and use `vectorcraft-cli mcp --connect`" + )); + } + None => self.session.check_params(id, params).map_err(|e| e.to_string())?, } - .map_err(|e| e.to_string())?; match id { "file.open" => self.open(params), "file.save" => self.save(params), @@ -146,9 +153,31 @@ impl Headless { fn commands(&self) -> Value { let mut v: Vec = self.session.commands().into_iter().map(|c| serde_json::to_value(c).unwrap_or_default()).collect(); + let mut seen: std::collections::HashSet = + v.iter().filter_map(|c| c.get("id").and_then(Value::as_str).map(str::to_owned)).collect(); for (id, label, params) in HOST_COMMANDS { + if !seen.insert((*id).to_owned()) { + continue; + } v.push(json!({"id": id, "label": label, "menu": [], "shortcut": null, "params": params, "enabled": true, "host": true})); } + // Desktop menu commands (`UI_COMMANDS`): listed for discovery with `ui: true`. Running one + // that only the desktop app implements still needs `--connect` (see `exec`). + for (id, label, shortcut, params) in crate::ui_catalog::UI_COMMANDS { + if !seen.insert((*id).to_owned()) { + continue; + } + let sc = if shortcut.is_empty() { Value::Null } else { json!(shortcut) }; + v.push(json!({ + "id": id, + "label": label, + "menu": [], + "shortcut": sc, + "params": params, + "enabled": true, + "ui": true, + })); + } Value::Array(v) } diff --git a/crates/mcp/src/lib.rs b/crates/mcp/src/lib.rs index 785746a38..d2f114536 100644 --- a/crates/mcp/src/lib.rs +++ b/crates/mcp/src/lib.rs @@ -22,6 +22,7 @@ mod prompts; mod resources; mod server; mod tools; +mod ui_catalog; pub use backend::{Backend, Remote}; pub use headless::Headless; diff --git a/crates/mcp/src/prompts.rs b/crates/mcp/src/prompts.rs index 8ad74c6ba..95363fde0 100644 --- a/crates/mcp/src/prompts.rs +++ b/crates/mcp/src/prompts.rs @@ -365,6 +365,116 @@ Method: Live effects are kept on export (geometry baked; shadows, glows and blur become SVG filters), and text stays text unless you pass outlineText.", }, + PromptDef { + name: "check-spelling", + title: "Check spelling", + description: "Run text.spelling.check on the selection or document, then apply suggestions or dictionary changes.", + arguments: &[ + PromptArg { + name: "scope", + description: "What to check", + required: false, + default: "the selection, or every story when nothing is selected", + values: Values::Fixed(&[ + "the selection, or every story when nothing is selected", + "the active story only", + "every story in the document", + ]), + }, + PromptArg { + name: "action", + description: "What to do with each unknown word", + required: false, + default: "list suggestions and ask before changing", + values: Values::Fixed(&[ + "list suggestions and ask before changing", + "apply the first suggestion for each word", + "add unknown words to the custom dictionary", + "ignore all for this document", + ]), + }, + ], + text: "\ +Check spelling in VectorCraft for {scope}, then {action}. + +Method: +1. inspect_document to see the selection and type objects. +2. run_command text.spelling.check (and text.spelling.suggest for each unknown word). Offsets are + byte ranges for text.editRange / text.spelling.changeWord. +3. For a fix, text.spelling.changeWord; for the custom dictionary, text.spelling.addWord / + removeWord / dictionary; for this document only, text.spelling.ignoreAll. +4. Prefer those commands over inventing replacements. screenshot if the layout may reflow. + +Desktop-only Auto Spell Check is edit.autoSpellCheck (ui: true); use --connect for the underlines.", + }, + PromptDef { + name: "variables-bind", + title: "Bind variables", + description: "Define text, visibility or image variables, bind them to art, and load or save a variable library.", + arguments: &[ + PromptArg { + name: "kind", + description: "Which variable kinds to set up", + required: false, + default: "text", + values: Values::Fixed(&["text", "visibility", "image", "graph", "mixed"]), + }, + PromptArg { + name: "source", + description: "Where the data comes from", + required: false, + default: "define variables on the selection by hand", + values: Values::Fixed(&[ + "define variables on the selection by hand", + "import a CSV or TSV data set", + "load a variable library XML", + ]), + }, + ], + text: "\ +Set up {kind} variables in VectorCraft from {source}. + +Method: +1. inspect_document and variable.list (or the Variables panel commands) to see what exists. +2. variable.define / variable.bind for text, visibility, image or graph bindings; one text + variable per type object. +3. For CSV/TSV, dataset.import; for a library file, variable.loadLibrary / variable.saveLibrary. +4. Switch data sets with dataset.apply and verify with inspect_document / screenshot. +5. Prefer run_command over inventing panel clicks. Undo covers each binding step.", + }, + PromptDef { + name: "path-tools", + title: "Path Tools add-on", + description: "Use addon.pathtools.* commands (and addon.path.* live effects) to edit path geometry.", + arguments: &[ + PromptArg { + name: "task", + description: "What to do to the selection", + required: true, + default: "", + values: Values::Fixed(&[ + "extend open ends", + "close paths", + "add anchors along curves", + "simplify anchors", + "split at anchors", + "expand dashes to geometry", + "create a parametric shape", + ]), + }, + ], + text: "\ +Perform this Path Tools task in VectorCraft: {task}. + +Method: +1. command_list with filter \"addon.pathtools\" for the command ids (extend, close, addPoints, + simplify, crossings, shape.create, …). Those are commands. +2. Live Path Effects keep the effect ids addon.path.* (dashes, addAnchors, extend, close, + smooth, retract, reverse, simplify, split). Apply them with apply_effect / effect.apply; + they are not command ids. +3. inspect_document the selection, run the command or effect, then screenshot. +4. Expand Appearance (effect.expandAppearance) only when the brief needs baked geometry.", + }, ]; /// The message body as a client should receive it. diff --git a/crates/mcp/src/tests.rs b/crates/mcp/src/tests.rs index dba3ed9de..16e15bed3 100644 --- a/crates/mcp/src/tests.rs +++ b/crates/mcp/src/tests.rs @@ -209,9 +209,80 @@ fn tool_schemas_are_valid() { "export", "undo", "redo", + "command_list", + "command_run", + "command_batch", + "doc_inspect", + "render_preview", + "ui_inspect", + "ui_screenshot", + "type_text", + "answer_dialog", + "add_text", + "apply_effect", + "pathfinder", + "transform", + "create_graph", + "text_wrap", ] { assert!(names.contains(want), "missing tool {want}"); } + assert_eq!(tools.len(), 33, "parity docs and #6460 expect 33 MCP tools"); +} + +/// Headless `command_list` must expose engine commands that a stale release CLI used to omit (#6460). +#[test] +fn headless_command_list_includes_post_022_engine_commands() { + let mut s = server(); + rpc(&mut s, 1, "initialize", json!({"protocolVersion": PROTOCOL_VERSION})); + s.handle_line(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#); + let want = [ + "actionSet.read", + "engine.apiReference", + "script.run", + "script.stop", + "text.spelling.check", + "document.move", + "document.compare", + "edit.newDocumentFromState", + ]; + for (n, id) in want.iter().enumerate() { + let r = call(&mut s, 10 + n as u64, "command_list", json!({"filter": id, "brief": true})); + assert_eq!(r["isError"], false, "{id}: {r}"); + let list: Value = serde_json::from_str(&text_of(&r)).unwrap(); + assert!( + list.as_array().unwrap().iter().any(|c| c["id"] == *id), + "missing {id} in {list}" + ); + } +} + +/// Headless `command_list` lists desktop UI commands with `ui: true` (#6460). +#[test] +fn headless_command_list_includes_ui_commands() { + let mut s = server(); + rpc(&mut s, 1, "initialize", json!({"protocolVersion": PROTOCOL_VERSION})); + s.handle_line(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#); + let want = [ + "addon.arrange.zoomSelection", + "file.newDialog", + "view.fitArtboard", + "view.rotateReset", + "app.quit", + "window.arrange.tile", + "align.horizontalLeft", + "ui.action.play", + ]; + for (n, id) in want.iter().enumerate() { + let r = call(&mut s, 20 + n as u64, "command_list", json!({"filter": id, "brief": true})); + assert_eq!(r["isError"], false, "{id}: {r}"); + let list: Value = serde_json::from_str(&text_of(&r)).unwrap(); + let entry = list.as_array().unwrap().iter().find(|c| c["id"] == *id).unwrap_or_else(|| panic!("missing {id} in {list}")); + assert_eq!(entry["ui"], true, "{entry}"); + } + let r = call(&mut s, 40, "command_run", json!({"id": "view.fitArtboard", "params": {}})); + assert_eq!(r["isError"], true, "{r}"); + assert!(text_of(&r).contains("--connect"), "{r}"); } // ---------- headless end-to-end ---------- diff --git a/crates/mcp/src/tools/definitions.rs b/crates/mcp/src/tools/definitions.rs index 27d92394f..2480f4ad6 100644 --- a/crates/mcp/src/tools/definitions.rs +++ b/crates/mcp/src/tools/definitions.rs @@ -98,7 +98,7 @@ pub fn tool_definitions() -> Vec { tool( "command_list", "List commands", - "Discover command ids, labels, parameter descriptions and enabled state. A command with a parameter schema also lists it as schema (JSON Schema 2020-12) and as inputSchema (the same schema with an object root, the form of a tool's inputSchema). Returns an array. The whole catalogue is large: narrow it with filter (or query), menu, and brief. list_commands lists the same commands; it takes enabledOnly instead of enabled_only and returns {count, commands}.", + "Discover command ids, labels, parameter descriptions and enabled state. Lists engine and plug-in commands, and desktop UI menu commands (ui: true; running a UI-only id headless needs --connect). A command with a parameter schema also lists it as schema (JSON Schema 2020-12) and as inputSchema (the same schema with an object root, the form of a tool's inputSchema). Returns an array. The whole catalogue is large: narrow it with filter (or query), menu, and brief. list_commands lists the same commands; it takes enabledOnly instead of enabled_only and returns {count, commands}. Effect ids (effect.list) are not command ids.", obj( json!({"filter": string("Case-insensitive id, label or menu substring"), "query": string("Same as filter"), "menu": string(MENU_FILTER), "enabled_only": {"type":"boolean"}, "brief": {"type":"boolean", "description": BRIEF}}), &[], @@ -156,7 +156,7 @@ pub fn tool_definitions() -> Vec { tool( "list_commands", "List commands", - "List VectorCraft commands (id, label, menu path, shortcut, parameter description, enabled state, and schema and inputSchema for a command with a parameter schema). Every editing action is a command; run any of them with run_command. Coordinates are points in document space, y down, origin at the first artboard's top-left. command_list lists the same commands as a bare array and takes enabled_only.", + "List VectorCraft commands (id, label, menu path, shortcut, parameter description, enabled state, and schema and inputSchema for a command with a parameter schema). Includes engine commands and desktop UI menu commands (ui: true; a UI-only id run headless needs --connect). Every editing action is a command; run any of them with run_command (or invoke_menu for a menu path). Coordinates are points in document space, y down, origin at the first artboard's top-left. command_list lists the same commands as a bare array and takes enabled_only. Effect ids (effect.list) are not command ids.", obj( json!({"filter": string("Case-insensitive substring matched against id, label and menu path (e.g. \"align\", \"Object\")"), "query": string("Same as filter"), "menu": string(MENU_FILTER), "enabledOnly": {"type": "boolean", "description": "Only commands that can run right now"}, "brief": {"type": "boolean", "description": BRIEF}}), &[], diff --git a/crates/mcp/src/ui_catalog.rs b/crates/mcp/src/ui_catalog.rs new file mode 100644 index 000000000..c877045c2 --- /dev/null +++ b/crates/mcp/src/ui_catalog.rs @@ -0,0 +1,455 @@ +//! Desktop UI command catalogue for headless MCP discovery (`ui: true`). +//! Generated from `crates/ui-egui/src/menus/ui_commands.rs`. Re-run the extractor when that table changes. + +/// `(id, label, shortcut, params doc)` — same shape as the desktop `UI_COMMANDS` table. +pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ + ("addon.pathtools.cornerTool", "Corner Tool", "", "{} choose the Corner Tool and show the Anchor Inspector panel with its radius and kind"), + ("addon.pathtools.sharedEdgeTool", "Shared Edge Merge Tool", "", "{} choose the Shared Edge Merge Tool and show the Anchor Inspector panel with its brush, coincidence and Keep Shared Points Together"), + ("addon.arrange.zoomSelection", "Zoom to Selection", "", "{} zoom and centre the active view so the selection's visual bounds fill the window, with a margin (as view.fitArtboard fits the artboard); off with nothing selected"), + ("app.language", "Interface Language", "", "{lang: auto|} the interface language, persisted as the `interfaceLanguage` preference (`auto` follows the system locale; codes: prefs.list › interfaceLanguage, e.g. en, ja, cs, es, zh-hant)"), + ("file.open", "Open…", "Cmd+O", "{path?, params?} → with a path, document.open's result {index, title, format, warnings, …} (null when a dialog asks first or a library loads); `params` are the parameters of the importer plug-in that opens the file"), + ("file.openWith", "Open With", "", "{importer: \\\"plugin.\\\" (an importer plug-in's format id, see document.formats), path?} File › Open With: open the file with that importer (document.open's importer), also a file whose extension a built-in format reads; without a path pick one of the files with the extensions it lists (the web opens File › Open's picker, and the file opens as File › Open opens it) → with a path, document.open's result; a picked file is read off the UI thread on the desktop, {background: true, status}, and opens once the importer returns (file.cancelOpen stops it) | null while the picker is open"), + ("file.save", "Save", "Cmd+S", "{path?, format?, options?, svg?: {…SVG options}} = document.save, written through the app (a document saved as SVG, PDF or .ai, or opened from one Save can write back, saves as that again, with the same options); with no path known (never saved, converted) the Save As panel asks first"), + ("file.place", "Place…", "Cmd+Shift+P", "{path? | paths?} no file: pick files, then the Place dialog (Link, Template, Replace); paths: that dialog for them; path (or name+dataBase64, …file.place params): file.place, centred in the view (a PDF with several pages or a password and no page opens the Place PDF dialog first)"), + ("file.openRecent1", "Open Recent File 1", "", "{}"), + ("file.openRecent2", "Open Recent File 2", "", "{}"), + ("file.openRecent3", "Open Recent File 3", "", "{}"), + ("file.openRecent4", "Open Recent File 4", "", "{}"), + ("file.openRecent5", "Open Recent File 5", "", "{}"), + ("file.openRecent6", "Open Recent File 6", "", "{}"), + ("file.openRecent7", "Open Recent File 7", "", "{}"), + ("file.openRecent8", "Open Recent File 8", "", "{}"), + ("file.openRecent9", "Open Recent File 9", "", "{}"), + ("file.openRecent10", "Open Recent File 10", "", "{}"), + ("view.goto1", "Saved View 1", "", "{} go to the 1. saved view"), + ("view.goto2", "Saved View 2", "", "{} go to the 2. saved view"), + ("view.goto3", "Saved View 3", "", "{} go to the 3. saved view"), + ("view.goto4", "Saved View 4", "", "{} go to the 4. saved view"), + ("view.goto5", "Saved View 5", "", "{} go to the 5. saved view"), + ("view.goto6", "Saved View 6", "", "{} go to the 6. saved view"), + ("view.goto7", "Saved View 7", "", "{} go to the 7. saved view"), + ("view.goto8", "Saved View 8", "", "{} go to the 8. saved view"), + ("view.goto9", "Saved View 9", "", "{} go to the 9. saved view"), + ("view.goto10", "Saved View 10", "", "{} go to the 10. saved view"), + ("view.goto11", "Saved View 11", "", "{} go to the 11. saved view"), + ("view.goto12", "Saved View 12", "", "{} go to the 12. saved view"), + ("view.goto13", "Saved View 13", "", "{} go to the 13. saved view"), + ("view.goto14", "Saved View 14", "", "{} go to the 14. saved view"), + ("view.goto15", "Saved View 15", "", "{} go to the 15. saved view"), + ("view.goto16", "Saved View 16", "", "{} go to the 16. saved view"), + ("view.goto17", "Saved View 17", "", "{} go to the 17. saved view"), + ("view.goto18", "Saved View 18", "", "{} go to the 18. saved view"), + ("view.goto19", "Saved View 19", "", "{} go to the 19. saved view"), + ("view.goto20", "Saved View 20", "", "{} go to the 20. saved view"), + ("view.goto21", "Saved View 21", "", "{} go to the 21. saved view"), + ("view.goto22", "Saved View 22", "", "{} go to the 22. saved view"), + ("view.goto23", "Saved View 23", "", "{} go to the 23. saved view"), + ("view.goto24", "Saved View 24", "", "{} go to the 24. saved view"), + ("view.goto25", "Saved View 25", "", "{} go to the 25. saved view"), + ("select.recall1", "Saved Selection 1", "", "{} select the 1. saved selection"), + ("select.recall2", "Saved Selection 2", "", "{} select the 2. saved selection"), + ("select.recall3", "Saved Selection 3", "", "{} select the 3. saved selection"), + ("select.recall4", "Saved Selection 4", "", "{} select the 4. saved selection"), + ("select.recall5", "Saved Selection 5", "", "{} select the 5. saved selection"), + ("select.recall6", "Saved Selection 6", "", "{} select the 6. saved selection"), + ("select.recall7", "Saved Selection 7", "", "{} select the 7. saved selection"), + ("select.recall8", "Saved Selection 8", "", "{} select the 8. saved selection"), + ("select.recall9", "Saved Selection 9", "", "{} select the 9. saved selection"), + ("select.recall10", "Saved Selection 10", "", "{} select the 10. saved selection"), + ("select.recall11", "Saved Selection 11", "", "{} select the 11. saved selection"), + ("select.recall12", "Saved Selection 12", "", "{} select the 12. saved selection"), + ("select.recall13", "Saved Selection 13", "", "{} select the 13. saved selection"), + ("select.recall14", "Saved Selection 14", "", "{} select the 14. saved selection"), + ("select.recall15", "Saved Selection 15", "", "{} select the 15. saved selection"), + ("select.recall16", "Saved Selection 16", "", "{} select the 16. saved selection"), + ("select.recall17", "Saved Selection 17", "", "{} select the 17. saved selection"), + ("select.recall18", "Saved Selection 18", "", "{} select the 18. saved selection"), + ("select.recall19", "Saved Selection 19", "", "{} select the 19. saved selection"), + ("select.recall20", "Saved Selection 20", "", "{} select the 20. saved selection"), + ("select.recall21", "Saved Selection 21", "", "{} select the 21. saved selection"), + ("select.recall22", "Saved Selection 22", "", "{} select the 22. saved selection"), + ("select.recall23", "Saved Selection 23", "", "{} select the 23. saved selection"), + ("select.recall24", "Saved Selection 24", "", "{} select the 24. saved selection"), + ("select.recall25", "Saved Selection 25", "", "{} select the 25. saved selection"), + ("type.recentFont1", "Recent Font 1", "", "{} apply the 1. most recently used font"), + ("type.recentFont2", "Recent Font 2", "", "{} apply the 2. most recently used font"), + ("type.recentFont3", "Recent Font 3", "", "{} apply the 3. most recently used font"), + ("type.recentFont4", "Recent Font 4", "", "{} apply the 4. most recently used font"), + ("type.recentFont5", "Recent Font 5", "", "{} apply the 5. most recently used font"), + ("type.recentFont6", "Recent Font 6", "", "{} apply the 6. most recently used font"), + ("type.recentFont7", "Recent Font 7", "", "{} apply the 7. most recently used font"), + ("type.recentFont8", "Recent Font 8", "", "{} apply the 8. most recently used font"), + ("type.recentFont9", "Recent Font 9", "", "{} apply the 9. most recently used font"), + ("type.recentFont10", "Recent Font 10", "", "{} apply the 10. most recently used font"), + ("type.recentFont11", "Recent Font 11", "", "{} apply the 11. most recently used font"), + ("type.recentFont12", "Recent Font 12", "", "{} apply the 12. most recently used font"), + ("type.recentFont13", "Recent Font 13", "", "{} apply the 13. most recently used font"), + ("type.recentFont14", "Recent Font 14", "", "{} apply the 14. most recently used font"), + ("type.recentFont15", "Recent Font 15", "", "{} apply the 15. most recently used font"), + ("file.clearRecent", "Clear Recent Files", "", "{}"), + ("type.findFont", "Find Font…", "", "{} open the Find Font dialog (engine: text.fonts / text.replaceFont / select.font)"), + ("ui.missingFontsDialog", "Missing Fonts Dialog", "", "{folder?} open Missing Fonts (dialog `missingFonts`) for the fonts the active document uses that aren't available (text.missingFonts); with folder, it looks for their files there at once (text.findFontFiles). An error when no font is missing. Opening a document whose fonts are missing shows it after the missing linked file questions (dialog `missingLinks`). Disabled on the web"), + ("ui.findFontsInFolder", "Find Fonts in Folder…", "", "{folder?} Type › Find Font's Find in Folder…: asks for a folder (or takes folder), then opens Missing Fonts looking there for the files of the fonts the active document misses (text.findFontFiles). Disabled where there is no folder picker (the web)"), + ("file.recentFiles", "Recent Files", "", "{} → [path…] most recent first"), + ("file.export.svg", "Export As SVG…", "", "{} opens SVG Options; with params = document.export {path?, svg?: {…SVG options}, range?…} (a .svgz path writes it gzipped)"), + ("file.export.png", "Export As PNG…", "", "{path?, …document.export options}; no path: pick the file, then PNG Options"), + ("file.exportForScreens", "Export for Screens…", "Cmd+Alt+E", "{} opens the dialog (on the document's last settings; while the Artboard tool is in use, with the artboards it has selected chosen); with params = document.exportForScreens (no folder on the web: downloads the files or the zip; openLocation: shows the first file in the file manager; a folder on the desktop with Background Export on: the files are written in the background → {background: true, status})"), + ("file.documentSetup", "Document Setup…", "Cmd+Alt+P", "{}"), + ("file.newDialog", "New…", "Cmd+N", "{} opens the New Document dialog"), + ("file.newWithoutDialog", "New Without Dialog", "Cmd+Alt+N", "{} makes a document with the settings the New Document dialog opens on (the first Recent entry, else the Print category's Letter), named after the next untitled document, without opening the dialog → {index, previewMode}"), + ("app.home", "Home", "", "{} shows the Home screen (new file presets, Open) over the open documents; choosing a document tab returns to it"), + ("edit.preferences", "Preferences…", "Cmd+K", "{category?} open Preferences (engine: prefs.get / prefs.set / prefs.list)"), + ("edit.keyboardShortcuts", "Keyboard Shortcuts…", "Cmd+Alt+Shift+K", "{}"), + ("edit.autoSpellCheck", "Auto Spell Check", "", "{} toggle a red underline under the words of the document's type its dictionaries don't know (text.spelling.check lists them), kept up to date as the type changes; type with effects isn't underlined → whether Auto Spell Check is on"), + ("shortcuts.set", "Set Keyboard Shortcut", "", "{id: command id or tool:, shortcut: \\\"Cmd+Shift+K\\\" | \\\"\\\" (none) | null (default), force?}"), + ("shortcuts.list", "List Keyboard Shortcuts", "", "{query?} → [{id, label, group, shortcut, default, overridden}]"), + ("shortcuts.conflicts", "Keyboard Shortcut Conflicts", "", "{} → [{shortcut, ids}]"), + ("shortcuts.reset", "Reset Keyboard Shortcuts", "", "{}"), + ("shortcuts.preset", "Keyboard Shortcut Set", "", "{name: \\\"VectorCraft Defaults\\\" | \\\"Classic Defaults\\\"} (names of earlier versions are accepted)"), + ("shortcuts.export", "Export Keyboard Shortcuts…", "", "{path?}"), + ("shortcuts.exportText", "Export Keyboard Shortcuts as Text…", "", "{path?} save the current set's tools and menu commands with their shortcuts as a UTF-8 text file (Keyboard Shortcuts › Export Text…): the set's name, then a Tools and a Menu Commands section in the dialog's order, one `nameshortcut` line per row, empty after the tab when it has none; without `path`, pick a file"), + ("shortcuts.import", "Import Keyboard Shortcuts…", "", "{path? | data?}"), + ("ui.shortcutImportDialog", "Import Shortcut Set…", "", "{path? | dataBase64?, name?} open Import Shortcut Set for a shortcut set another app saved as text (.kys; without a file, pick one): each entry matched to a tool, command or panel, and its key and modifier codes decoded (dialog `shortcutImport`: name, start: defaults|current, remember, rows: [{name, section, line, key, modifiers, target: entry id | \\\"\\\" (no equivalent) | null (not chosen), from, choices, on}], keys and mods: {code: {text, from, votes, names}}); Apply assigns the ready rows over Edit › Keyboard Shortcuts' working set, remembers the matches (importMatches.set) and shows Keyboard Shortcuts, whose OK keeps them. File › Open of a .kys opens it too"), + ("view.outline", "Outline", "Cmd+Y", "{} toggle Outline/Preview"), + ("view.pixelPreview", "Pixel Preview", "Cmd+Alt+Y", "{}"), + ("view.trimView", "Trim View", "", "{} toggle: hide everything outside the artboards"), + ("view.cornerWidget", "Hide Corner Widget", "", "{} toggle the live corner widgets"), + ("view.snapToPixel", "Snap to Pixel", "", "{} toggle: drawing and moving land on whole pixels"), + ("view.snapToGlyph", "Snap to Glyph", "", "{} toggle: drawing snaps to live-text glyph bounds, and on a single line of type to its baseline, x-height, character widths and glyph anchor points"), + ("view.preciseCursors", "Use Precise Cursors", "", "{} toggle General › Use Precise Cursors: the drawing tools' pointers become a crosshair → true when on"), + ("imageTrace.preview", "Image Trace Preview", "", "{on?: bool} turn the Image Trace panel's Preview on (true), off (false) or toggle it (omitted), as its checkbox does: on, setting changes trace the selected Image Trace object at once; off, they wait for Trace; returns the new state"), + ("view.textThreads", "Hide Text Threads", "Cmd+Shift+Y", "{} toggle the thread lines between threaded text frames"), + ("view.gradientAnnotator", "Hide Gradient Annotator", "Cmd+Alt+G", "{} toggle the Gradient tool's annotator"), + ("view.livePaintGaps", "Show Live Paint Gaps", "", "{} toggle drawing the gaps of the selected Live Paint groups (those livePaint.info lists) in each group's Gap Preview Color while edges show → true when on"), + ("type.hiddenCharacters", "Show Hidden Characters", "Cmd+Alt+I", "{} toggle markers for spaces, paragraph ends and story ends"), + ("type.alignLeft", "Align left", "Cmd+Shift+L", "{} align the selected type's paragraphs (or those the Type tool's selection touches) left"), + ("type.alignCenter", "Align center", "Cmd+Shift+C", "{} align the selected type's paragraphs (or those the Type tool's selection touches) centred"), + ("type.alignRight", "Align right", "Cmd+Shift+R", "{} align the selected type's paragraphs (or those the Type tool's selection touches) right"), + ("type.justifyLeft", "Justify with last line aligned left", "Cmd+Shift+J", "{} justify the selected type's paragraphs, last line aligned left"), + ("type.justifyAll", "Justify all lines", "Cmd+Shift+F", "{} justify every line of the selected type's paragraphs"), + ("type.applyBullets", "Apply Bullets", "", "{bullet?: \\\"dot\\\"|\\\"dash\\\"|\\\"circle\\\"|\\\"square\\\"|\\\"none\\\"} Type › Bullets and Numbering › Apply Bullets: the selected type's paragraphs (or those the Type tool's selection touches) become a bulleted list with that bullet (text.setFormat {bullet}); without it, a list keeps its bullet and other paragraphs take the dot (text.setFormat {list: \\\"bullet\\\"}); \\\"none\\\" turns the list off"), + ("type.applyNumbers", "Apply Numbers", "", "{number?: \\\"arabic\\\"|\\\"upperLetters\\\"|\\\"lowerLetters\\\"|\\\"upperRoman\\\"|\\\"lowerRoman\\\"|\\\"twoDigits\\\"|\\\"threeDigits\\\"|\\\"none\\\"} Type › Bullets and Numbering › Apply Numbers: the selected type's paragraphs (or those the Type tool's selection touches) become a numbered list in that number style (text.setFormat {number}); without it, a numbered list keeps its style and other paragraphs are numbered 1, 2, 3 (text.setFormat {list: \\\"number\\\"}); \\\"none\\\" turns the list off"), + ("ui.bulletsAndNumbering", "Bullets and Numbering…", "", "{tab?: \\\"bullets\\\"|\\\"numbers\\\"} open Bullets and Numbering (More Options in the Paragraph panel's Bullets and Numbered List presets) on the paragraphs the Paragraph panel acts on, filled in with the values it shows, on that tab (without it, Numbers when the paragraphs are a numbered list, else Bullets) (dialog `bulletsAndNumbering`: tab (bullets|numbers), list (none|bullet|number: the kind of the preset picked), bullet (dot|dash|circle|square), number (arabic|upperLetters|lowerLetters|upperRoman|lowerRoman|twoDigits|threeDigits), listAlign (left|right), listLevel (1..9; 1 for paragraphs in no list), leftIndent, firstLineIndent (pt)): the type shows the dialog's values while it is open, OK runs text.setFormat with the values that changed (and the indents it shows when a preset puts the paragraphs in a list), as one undo step, and Cancel puts the type back; a preset picked for paragraphs in no list whose indents are 0 fills in 18 and -18 pt; an error with no type selected or an unknown tab"), + ("type.convertListToText", "Convert to Text", "", "{} Type › Bullets and Numbering › Convert to Text: the bullets and numbers of the selected type's list paragraphs (or of those the Type tool's selection touches) become characters of the text, followed by a tab, and the lists are turned off (text.convertListToText); the Type tool's anchor and caret move past the characters inserted at or before them, into the frame the caret's character moved to in threaded type"), + ("align.horizontalLeft", "Horizontal Align Left", "", "{} the Align panel's Horizontal Align Left button: object.align {horizontal: \\\"left\\\"} with to: \\\"key\\\" while there is a key, else \\\"artboard\\\" for one selected object without selected anchor points, else the Align To choice"), + ("align.horizontalCenter", "Horizontal Align Center", "", "{} the Align panel's Horizontal Align Center button: object.align {horizontal: \\\"center\\\"} with to: \\\"key\\\" while there is a key, else \\\"artboard\\\" for one selected object without selected anchor points, else the Align To choice"), + ("align.horizontalRight", "Horizontal Align Right", "", "{} the Align panel's Horizontal Align Right button: object.align {horizontal: \\\"right\\\"} with to: \\\"key\\\" while there is a key, else \\\"artboard\\\" for one selected object without selected anchor points, else the Align To choice"), + ("align.horizontalVerticalCenter", "Horizontal & Vertical Align Center", "", "{} the Align panel's Horizontal & Vertical Align Center button: object.align {horizontal: \\\"center\\\", vertical: \\\"center\\\"} with to: \\\"key\\\" while there is a key, else \\\"artboard\\\" for one selected object without selected anchor points, else the Align To choice"), + ("align.verticalTop", "Vertical Align Top", "", "{} the Align panel's Vertical Align Top button: object.align {vertical: \\\"top\\\"} with to: \\\"key\\\" while there is a key, else \\\"artboard\\\" for one selected object without selected anchor points, else the Align To choice"), + ("align.verticalCenter", "Vertical Align Center", "", "{} the Align panel's Vertical Align Center button: object.align {vertical: \\\"center\\\"} with to: \\\"key\\\" while there is a key, else \\\"artboard\\\" for one selected object without selected anchor points, else the Align To choice"), + ("align.verticalBottom", "Vertical Align Bottom", "", "{} the Align panel's Vertical Align Bottom button: object.align {vertical: \\\"bottom\\\"} with to: \\\"key\\\" while there is a key, else \\\"artboard\\\" for one selected object without selected anchor points, else the Align To choice"), + ("align.distributeVerticalTop", "Vertical Distribute Top", "", "{} the Align panel's Vertical Distribute Top button: object.distribute {vertical: \\\"top\\\"} with to: \\\"key\\\" while there is a key, else the Align To choice, and spacing: the Distribute Spacing value while there is a key"), + ("align.distributeVerticalCenter", "Vertical Distribute Center", "", "{} the Align panel's Vertical Distribute Center button: object.distribute {vertical: \\\"center\\\"} with to: \\\"key\\\" while there is a key, else the Align To choice, and spacing: the Distribute Spacing value while there is a key"), + ("align.distributeVerticalBottom", "Vertical Distribute Bottom", "", "{} the Align panel's Vertical Distribute Bottom button: object.distribute {vertical: \\\"bottom\\\"} with to: \\\"key\\\" while there is a key, else the Align To choice, and spacing: the Distribute Spacing value while there is a key"), + ("align.distributeHorizontalLeft", "Horizontal Distribute Left", "", "{} the Align panel's Horizontal Distribute Left button: object.distribute {horizontal: \\\"left\\\"} with to: \\\"key\\\" while there is a key, else the Align To choice, and spacing: the Distribute Spacing value while there is a key"), + ("align.distributeHorizontalCenter", "Horizontal Distribute Center", "", "{} the Align panel's Horizontal Distribute Center button: object.distribute {horizontal: \\\"center\\\"} with to: \\\"key\\\" while there is a key, else the Align To choice, and spacing: the Distribute Spacing value while there is a key"), + ("align.distributeHorizontalRight", "Horizontal Distribute Right", "", "{} the Align panel's Horizontal Distribute Right button: object.distribute {horizontal: \\\"right\\\"} with to: \\\"key\\\" while there is a key, else the Align To choice, and spacing: the Distribute Spacing value while there is a key"), + ("align.distributeVerticalSpace", "Vertical Distribute Space", "", "{} the Align panel's Vertical Distribute Space button: object.distributeSpacing {axis: \\\"vertical\\\"} with to: \\\"key\\\" while there is a key, else the Align To choice, and spacing: the Distribute Spacing value while there is a key"), + ("align.distributeHorizontalSpace", "Horizontal Distribute Space", "", "{} the Align panel's Horizontal Distribute Space button: object.distributeSpacing {axis: \\\"horizontal\\\"} with to: \\\"key\\\" while there is a key, else the Align To choice, and spacing: the Distribute Spacing value while there is a key"), + ("type.bold", "Bold", "", "{} the selected type (or the Type tool's selection) in its family's Bold face, or back to the regular face when it is bold; keeps italics. A family without that face is left as it is, with a message. While the Type tool edits text, Cmd+Shift+B"), + ("type.italic", "Italic", "", "{} the selected type (or the Type tool's selection) in its family's Italic (or Oblique) face, or back upright when it is italic; keeps the weight. A family without that face is left as it is, with a message. While the Type tool edits text, Cmd+Shift+I"), + ("type.highlightFontName", "Highlight Font Name", "Cmd+Alt+Shift+F", "{} show the Character panel if it is hidden and open its font family menu, whose search field takes the keyboard focus: type to search, Enter applies the highlighted font → {field: \\\"fontName\\\"}. Without selected type (or text the Type tool edits) the panel shows, and the error says to select type first"), + ("type.highlightKerning", "Highlight Kerning", "Cmd+Alt+K", "{} show the Character panel if it is hidden and give its Kerning field the keyboard focus with its value selected: type a value and press Enter → {field: \\\"kerning\\\"}. Without selected type (or text the Type tool edits) the panel shows, and the error says to select type first"), + ("type.highlightTracking", "Highlight Tracking", "", "{} show the Character panel if it is hidden and give its Tracking field the keyboard focus with its value selected: type a value and press Enter → {field: \\\"tracking\\\"}. Without selected type (or text the Type tool edits) the panel shows, and the error says to select type first"), + ("type.underline", "Underline", "Cmd+Shift+U", "{} the Character panel's Underline button: underline the selected type (or the Type tool's selection), or remove the underline when the text the Character panel shows is underlined (text.setFormat {underline}) → underlined"), + ("type.lineComposer", "Toggle Line Composer", "Cmd+Alt+Shift+C", "{} the selected type's paragraphs (or those the Type tool's selection touches) use the Single-line Composer when they all use the Every-line Composer, else the Every-line Composer (text.setFormat {composer}) → \\\"singleLine\\\" | \\\"everyLine\\\""), + ("type.hyphenate", "Hyphenate", "Cmd+Alt+Shift+H", "{} the Paragraph panel's Hyphenate checkbox: hyphenation off for the selected type's paragraphs (or those the Type tool's selection touches) when they all hyphenate, else on (text.setFormat {hyphenate}) → hyphenate"), + ("effect.last", "Last Effect…", "Cmd+Alt+Shift+E", "{} open the dialog of the last effect applied"), + ("view.zoomIn", "Zoom In", "Cmd+=", "{}"), + ("view.zoomOut", "Zoom Out", "Cmd+-", "{}"), + ("view.fitArtboard", "Fit Artboard in Window", "Cmd+0", "{}"), + ("view.fitAll", "Fit All in Window", "Cmd+Alt+0", "{}"), + ("view.actualSize", "Actual Size", "Cmd+1", "{} 100%: with Display Print Size at 100% Zoom off, one document point per screen point; on, one document inch fills 96 screen points (an inch at the system's reference density, whatever the display scaling)"), + ("view.setZoom", "Set Zoom", "", "{zoom: percent, center?: [x,y]}"), + ("view.goToArtboard", "Go to Artboard", "", "{index: 0-based number | \\\"first\\\" | \\\"previous\\\" | \\\"next\\\" | \\\"last\\\"} make that artboard the status bar navigator's (the one Fit Artboard in Window and Actual Size show) and fit it in the window → {index}"), + ("view.firstArtboard", "First Artboard", "", "{} the status bar navigator's First Artboard button: view.goToArtboard {index: \\\"first\\\"} → {index}"), + ("view.previousArtboard", "Previous Artboard", "", "{} the navigator's Previous Artboard button: view.goToArtboard {index: \\\"previous\\\"} → {index}"), + ("view.nextArtboard", "Next Artboard", "", "{} the navigator's Next Artboard button: view.goToArtboard {index: \\\"next\\\"} → {index}"), + ("view.lastArtboard", "Last Artboard", "", "{} the navigator's Last Artboard button: view.goToArtboard {index: \\\"last\\\"} → {index}"), + ("view.edges", "Hide Edges", "Cmd+H", "{}"), + ("view.artboards", "Hide Artboards", "Cmd+Shift+H", "{}"), + ("view.template", "Hide Template", "Cmd+Shift+W", "{} template layers left off the canvas (Show Template draws them again, dimmed as before) → shown"), + ("view.rulers", "Show Rulers", "Cmd+R", "{}"), + ("view.videoRulers", "Show Video Rulers", "", "{} the rulers show (as Show Rulers shows them) and count the active artboard in video pixels from its top-left corner, a pixel as wide as its Video Ruler Pixel Aspect Ratio says (artboard.setProps videoRulerPixelAspectRatio) and one point tall; again (Hide Video Rulers), they count in the General unit from the ruler origin as before → whether video rulers show"), + ("view.boundingBox", "Hide Bounding Box", "Cmd+Shift+B", "{}"), + ("view.guides", "Hide Guides", "Cmd+;", "{}"), + ("view.smartGuides", "Smart Guides", "Cmd+U", "{}"), + ("view.grid", "Show Grid", "Cmd+'", "{}"), + ("view.snapToGrid", "Snap to Grid", "Cmd+Shift+'", "{}"), + ("view.snapToPoint", "Snap to Point", "Cmd+Alt+'", "{}"), + ("view.presentation", "Presentation Mode", "Shift+F", "{}"), + ("view.screenMode", "Screen Mode", "F", "{mode?: 0..2} (no param cycles)"), + ("view.rotate", "Rotate View", "", "{angle: degrees, clockwise on screen as the status bar shows it} turn the view to that angle about the center of the canvas; returns {angle}, normalized to (-180, 180]"), + ("view.rotateToSelection", "Rotate View to Selection", "", "{} turn the view about the center of the canvas to the angle the selected objects share (selectionRotation in document.inspect, 0 when they differ), so their bounding box is square to the window; returns {angle}"), + ("view.rotateReset", "Reset Rotate View", "Cmd+Shift+1", "{}"), + ("window.control", "Control", "", "{}"), + ("window.helpBar", "Help Bar", "", "{} toggle the tool-help strip independently of the status bar; returns the new visibility"), + ("window.control.dock", "Dock Control Bar", "", "{edge: \\\"top\\\"|\\\"bottom\\\"} dock the Control bar to the top of the window, under the menu bar, or to its bottom, as the bar's panel menu (Dock to Top, Dock to Bottom) and a drag of its gripper do; kept with the workspace; another edge is an error and changes nothing; returns {edge}"), + ("window.toolbar", "Tools", "", "{}"), + ("window.toolbarColumns", "Toolbar: Single/Double Column", "", "{double?: bool, toolbar?: name} show the toolbar's tools (with `toolbar`, that toolbar made with New Toolbar's) in two columns (true), one (false) or toggle (omitted), as the double arrow at the top of the toolbar does; returns the new state"), + ("window.toolbarAdvanced", "Toolbar: Advanced / Basic", "", "{}"), + ("window.toolsBesideCanvas", "Keep Tools Beside the Canvas", "", "{on?: bool} dock the Tools panel between the left-side panels and the canvas (true) or at the window's left edge (false, the default), or toggle (omitted or null; any other value is an error); a floating Tools panel stays floating and docks at the chosen place when it is docked again. Kept with the workspace; a workspace saved without it docks Tools at the window's edge. Returns the new state"), + ("window.floatTools", "Float Tool Group", "", "{tool: id, floating?: bool, toolbar?: name} float the toolbar group holding `tool` (in the current Basic or Advanced layout; with `toolbar`, in that toolbar made with New Toolbar) as its own strip of tool buttons (true), put it back in the toolbar (false) or toggle (omitted), as dragging or clicking a flyout's tear-off bar and the strip's × do; returns the new state"), + ("toolbar.show", "Toolbar: Show Controls", "", "{control?: \\\"fillStroke\\\"|\\\"coloring\\\"|\\\"drawMode\\\"|\\\"screenMode\\\", on?: bool} show (true), hide (false) or toggle (omitted) one of the controls under the toolbar's tools (Fill and Stroke, Coloring, Draw Mode, Screen Mode), as the All Tools drawer's Show section does; saved with the preferences; returns every control's state"), + ("window.editToolbar", "Edit Toolbar", "", "{open?: bool, toolbar?: name} open (true), close (false) or toggle (omitted) the All Tools drawer beside the toolbar (with `toolbar`, beside that toolbar made with New Toolbar, which shows), as the toolbar's ••• button does; while it is open, tools drag from it onto the toolbar and from the toolbar into it (Escape or a click outside closes it); returns the new state"), + ("toolbar.addTool", "Toolbar: Add Tool", "", "{tool: id, slot?: n, place?: \\\"before\\\"|\\\"after\\\"|\\\"group\\\", toolbar?: name} put `tool` in the current (Basic or Advanced) toolbar (with `toolbar`, in that toolbar made with New Toolbar), moving it if it is there already: before (default) or after slot `slot` (0 = the first; omitted: at the end) in its own slot, or into that slot's group (its flyout), as dropping a tool from the Edit Toolbar drawer does; saved with the preferences; returns the toolbar's slots (lists of tool ids)"), + ("toolbar.removeTool", "Toolbar: Remove Tool", "", "{tool: id, group?: bool, toolbar?: name} take `tool` (with `group`, its whole group) out of the current toolbar (with `toolbar`, out of that toolbar made with New Toolbar; one not in it stays out), as dragging it into the Edit Toolbar drawer does; returns the toolbar's slots"), + ("toolbar.reset", "Toolbar: Reset", "", "{toolbar?: name} put the current (Basic or Advanced) toolbar back as it ships, undoing Edit Toolbar's changes (with `toolbar`, make that toolbar made with New Toolbar blank again); returns its slots"), + ("window.toolbar.new", "New Toolbar…", "", "{name?: string} make a blank toolbar called `name`, of at most 200 characters (omitted: a dialog asks for the name), floating beside the Tools panel and listed under Window › Toolbars; its ••• button opens the All Tools drawer to fill it (window.editToolbar with `toolbar`); it is kept with the current workspace; returns {toolbar: name}"), + ("window.toolbar.custom", "Show Toolbar", "", "{name: string, show?: bool} show (true), hide (false) or toggle (omitted) the toolbar made with New Toolbar called `name`, as its item under Window › Toolbars does (its title bar's × hides it); shown after Tab hid the toolbars, it shows alone and the others are turned off; returns the new state"), + ("window.toolbar.manage", "Manage Toolbars…", "", "{} open the Manage Toolbars dialog, which lists the toolbars made with New Toolbar: a name typed for the selected one renames it, + copies it (none selected: makes a blank toolbar) and the trash can deletes it"), + ("window.toolbar.rename", "Rename Toolbar", "", "{name: string, to: string} give the toolbar made with New Toolbar called `name` the name `to` (not empty, at most 200 characters, not another toolbar's whatever the case), as typing a name in Manage Toolbars does; returns {toolbar: name}"), + ("window.toolbar.duplicate", "Duplicate Toolbar", "", "{name?: string} copy the toolbar made with New Toolbar called `name`, with its tools, as \\\"`name` Copy\\\" (omitted: make a blank toolbar called \\\"Toolbar N\\\"), as Manage Toolbars' + does; the copy shows and is kept with the current workspace; returns {toolbar: name}"), + ("window.toolbar.delete", "Delete Toolbar", "", "{name: string} delete the toolbar made with New Toolbar called `name`, and the groups torn off it, from the current workspace, as Manage Toolbars' trash can does"), + ("window.toolbar.list", "List Toolbars", "", "{} the toolbars made with New Toolbar in the current workspace: [{name, shown, double, slots}]"), + ("window.taskBar", "Contextual Task Bar", "", "{}"), + ("window.pathfinder.compoundShapes", "Shape Mode Buttons Make Compound Shapes", "", "{} toggles the Compound Shapes add-on's setting: on, the Pathfinder panel's Shape Mode buttons make a compound shape and Alt-click gives the permanent Pathfinder result; off (the default), the other way round. Members of a compound shape selected always take the mode"), + ("window.taskBar.pin", "Pin Bar Position", "", "{pinned?: bool} keep the Contextual Task Bar where it is instead of following the selection (true), let it follow the selection again from where it is (false) or toggle (omitted), as the bar's More Options menu does; returns the new state"), + ("window.taskBar.reset", "Reset Bar Position", "", "{} unpin the Contextual Task Bar and put it back under the selection"), + ("window.dock", "Panels", "Tab", "{} hide the panels, the toolbar and the Control bar, or show again the ones the last Tab hid (all three when none of them shows and Tab hid none); returns whether it showed them"), + ("window.panels", "Panels Except Tools and Control", "Shift+Tab", "{} show/hide the dock and the floating panels (hiding them closes a panel popped out of the icon column), leaving the toolbar and the Control bar as they are; returns whether the panels show"), + ("window.panel", "Show Panel", "", "{panel: id, toggle?: bool} e.g. layers, swatches, stroke (case-insensitive; display labels like \\\"Layers\\\" work too); toggle (the Window menu) hides a panel that shows, and shows it again where it was"), + ("window.panel.layout", "Change Panel Layout", "", "{action} serialized docking action, including floating geometry, divider sizes and accordion state"), + ("window.panel.move", "Move Panel", "", "{panel, anchor, zone?: tab|center|left|right|top|bottom|above|below, before?: panel, group?: bool, placement?} move a panel (with `group`, its whole group) into the group holding `anchor`: as a tab (before the `before` panel; null appends), a split beside it, or (`above` / `below`) a group of its own above or below it in its dock column or floating window; `placement` (a serialized Placement) replaces zone and before"), + ("window.panel.activate", "Activate Panel", "", "{panel} show and select a panel in its current group"), + ("window.panel.close", "Close Panel", "", "{panel} hide a panel"), + ("window.panel.collapse", "Collapse Panel Group", "", "{panel, collapsed?: bool} minimize the group holding a panel to its tabs (in a floating window, the window to its title bar), or restore it, as a double-click on a tab or the drag bar does; the same as window.panel.minimize {panel, minimized}. Without `collapsed` it toggles. Returns {panel, collapsed, layout}"), + ("window.panel.float", "Float Panel", "", "{panel: id or \\\"tools\\\", x?, y?, width?, height?, group?: bool, column?: bool or n, onto?: id, above?: id, below?: id, collapsed?: bool} float a panel (with `group`, its whole group; with `column`, its whole dock column or floating window) as a floating window with its top-left corner at x, y in window points (default: inside the dock area, cascaded), as dragging its tab out does; a whole floating window keeps its rect unless given one. `onto` makes it a tab of the floating group holding that panel instead; `above` / `below` a group of its own above or below the group holding that panel, in its floating window or dock column. `collapsed` then minimizes (true) or restores (false) its window or group. A hidden panel shows first. \\\"tools\\\" floats the Tools panel at x, y. Returns {layout, closed, floating}"), + ("window.panel.dock", "Dock Panel", "", "{panel: id or \\\"tools\\\", anchor?: id, zone?: tab|center|left|right|top|bottom|above|below, before?: id, group?: bool, column?: bool or n, side?: Start|End, onto?: id, above?: id, below?: id, icon?: bool, collapsed?: bool} dock a panel (with `group`, its whole group): with no destination a floating panel goes back where it floated from (else joins the first dock group) and a hidden one shows where it was; with `anchor` (or `onto`) in the group holding that panel, `zone` as for window.panel.move; with `side`, as a new innermost column of that dock (`column: true` then moves its whole dock column or floating window); `column: n` with no other destination docks its group, or the whole floating window it floats in, as a new dock column at that place (1: next to the icon column, counting inwards; `true`: innermost); `icon: true` puts it in a row of its own at the end of the icon column; `above` / `below` as a group of its own above or below the group holding that panel. `collapsed` then minimizes or restores its group. \\\"tools\\\" docks the Tools panel at the left edge of the window, or beside the canvas inside any left-side dock while window.toolsBesideCanvas is on; with side: right (or End) on the right, between the canvas and the panels, as dropping it there or on the window's right edge does (default: the side it docked at last; left or Start docks it on the left), returning {panel, floating, pos, side}. Returns {layout, closed, floating}"), + ("window.collapseDock", "Collapse Panels to Icons", "", "{collapsed?: bool} collapse every expanded dock column to icons (true), expand the outer dock column, Properties | Layers | Libraries (false), or toggle the outer column (omitted), as the double arrow at the top of the dock does; returns the new state"), + ("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"), + ("window.workspace", "Workspace", "", "{name} switch workspace (Essentials, Essentials Classic, Painting, …): the one left keeps its layout changes, and the one switched to comes back as it was left (as saved if never changed)"), + ("window.workspace.reset", "Reset Essentials", "", "{} reset the current workspace to the way it was saved, dropping the layout changes made in it"), + ("window.workspace.new", "New Workspace…", "", "{name?} save the current layout"), + ("window.workspace.manage", "Manage Workspaces…", "", "{}"), + ("window.workspace.delete", "Delete Workspace", "", "{name}"), + ("window.workspace.rename", "Rename Workspace", "", "{name, to}"), + ("window.workspace.list", "List Workspaces", "", "{}"), + ("window.newWindow", "New Window", "", "{} open another view of the current document, with its own zoom, center, rotation, artboard and Outline or Preview, as a tab of its own (titled with the view's number, \\\"Name:2\\\") or, when the current view floats, in a floating window; it becomes the current window; the Arrange commands treat each view as a window; file.close closes the current view and the last view closes the document; returns {index, view}"), + ("window.arrange.cascade", "Cascade", "", "{} put every open document in a floating window of its own and stack the windows from the upper left of the document area to its lower right, 30 pt apart, in tab order, each fitted to its window, the current document's in front; on the desktop each is an OS window, on the web and Wayland a window inside the app's window; returns {floating} (document indices)"), + ("window.arrange.tile", "Tile", "", "{} show every open document and each further view of one (window.newWindow) at once, edge to edge in the document area, one pane each in a grid (columns: the square root of the number of panes, rounded up), each fitted to its pane; the current window's pane takes the tools, a click in another pane makes its window the current one; the tabs stay; windows floating come back under the tabs first; with fewer than two windows open the view stays as it is; returns {tiled, panes}"), + ("window.arrange.floatInWindow", "Float in Window", "", "{index?, view?, at?: [x, y]} move the current window, or document `index`'s view `view` (its current view without one; it becomes the current window), out of the tab strip into a floating window of its own, fitted to it, with the window's top-left corner at `at` (main window points) or else at the next place of a cascade; dragging a document tab more than 24 pt above or below the tab strip does this at the pointer; the window holding the current document takes the tools, a click in another document's window makes its document the current one; a document already floating stays where it is; returns {floating} (document indices)"), + ("window.arrange.floatAllInWindows", "Float All in Windows", "", "{} move every document under the tab strip into a floating window of its own, at the next places of a cascade; returns {floating} (document indices)"), + ("window.arrange.consolidateAllWindows", "Consolidate All Windows", "", "{index?, view?} show one document at a time again, under its tab, after Tile, Float in Window, Float All in Windows or Cascade; returns {tiled: false, floating: []}; with `index`, only document `index` (its view `view`, else its current view) goes back under its tab and becomes the current one, the other windows and Tile staying as they are, as when a document window is dragged by its title bar (an OS window by the tab at its top) onto the tab strip; returns {tiled, floating} (document indices)"), + ("window.nextDocument", "Next Document", "Cmd+F6", "{} make the open document after the current one in tab order the current one, as a click on its tab does (after the last document it goes to the first, and from the Home screen to the first); each view window.newWindow opened is a step of its own; returns {index}, with `view` for a document with more than one view"), + ("window.previousDocument", "Previous Document", "Cmd+Shift+F6", "{} make the open document before the current one in tab order the current one, as a click on its tab does (before the first document it goes to the last, and from the Home screen to the last); each view window.newWindow opened is a step of its own; returns {index}, with `view` for a document with more than one view"), + ("tool.select", "Select Tool", "", "{tool: id} (see tools)"), + ("tool.setOption", "Tool Option", "", "{key, value} | {values: {key: value…}}, tool?: id (default: the active tool) → the tool's options (`{}` reads them). The options a tool keeps (Liquify brush and tool options, Mirror & Cut, Puppet Warp, drawing tools…) last across tool switches and are saved with the preferences; another tool's are stored for when it is chosen"), + ("effect.dialog", "Effect…", "", "{effect: id, index?: int (edit that applied effect in place, prefilled; OK runs effect.setParams), item?: appearance item index|null (the fill/stroke whose effects, null the object's; default: the Appearance panel's active item), aligned?: bool (index and item are rows of the first selected object's stack, which OK edits in each object where it has them, as effect.setParams does)} open the effect's dialog with live preview; without `index`, an effect already in that list first opens `effectExists` (confirm edits it, discard adds another) → {pending: \\\"effectExists\\\"}"), + ("ui.paramDialog", "Command Dialog", "", "{command, label?, params} open a parameter dialog for any command"), + ("ui.recolorDialog", "Recolor Artwork…", "", "{colors?: n (an n-colour job: n rows, Scale Tints) | [colour] (new colours to assign, in order; with no art selected and no group they are the rows, and OK saves them as a new colour group, field `groupName`: the Color Guide's Edit or Apply Colors), library?: id or name; or \\\"document\\\" (Limit to Library; \\\"\\\" the first library), group?: colour group (Edit or Apply Color Group: OK rewrites the group with the new colours and recolours the selected art, if any)} open Recolor Artwork (dialog `recolor`; engine: recolor.reduce / recolor.apply)"), + ("ui.cropImage", "Crop Image", "", "{} show a crop box on the selected image (tool `cropImage`): it starts on the part over the image's artboard; drag its handles or inside it, then Enter or the Control bar's Apply crops the image to it (object.cropImage {rect}), Escape or Cancel leaves it as it is, both back to the Selection tool. tool.setOption {key: \\\"rect\\\", value: [x, y, width, height]} sets the box"), + ("effect.applyLast", "Apply Last Effect", "Cmd+Shift+E", "{}"), + ("pathfinder.repeat", "Repeat Pathfinder", "Cmd+4", "{} repeat the last successful Pathfinder panel operation, direct object.pathfinder.* command or command.batch step on the selection with current Pathfinder Options; an error until one has run"), + ("file.export.pdf", "Save as PDF…", "", "{} opens the Save PDF dialog; with params = document.exportPdf options written to path (asked when missing; viewAfterSaving opens the file) → {path, bytes, warnings}"), + ("help.about", "About VectorCraft", "", "{}"), + ("help.checkForUpdates", "Check for Updates…", "", "{}"), + ("help.commandPalette", "Search Commands…", "Cmd+Shift+/", "{}"), + ("help.safeMode", "Safe Mode…", "", "{} open the Safe Mode dialog (dialog `safeMode`): whether VectorCraft is in Safe Mode, and the disabled plug-ins, each with Enable on Re-launch (engine: plugin.setEnabled). Closing it leaves Safe Mode as it is; Safe Mode is chosen at launch (--safe-mode, or the choice after a launch that stopped while a plug-in was loading, dialog `pluginStopped`)"), + ("app.quit", "Quit VectorCraft", "Cmd+Q", "{}"), + ("ui.swatchOptions", "Swatch Options…", "", "{name} edit a swatch: Swatch Options for a colour (dialog `swatchOptions`, engine: swatch.edit) or a gradient (the same dialog with `name` only; it shows the gradient), pattern editing for a pattern"), + ("ui.newSwatch", "New Swatch…", "", "{spot?, global?, group?: colour group name} open New Swatch for the active fill or stroke (dialog `newSwatch`, engine: swatch.new); a color opens as a spot color with `spot`, and as a global process color with `global` alone"), + ("ui.newColorGroup", "New Color Group…", "", "{swatches?: [names]} open New Color Group, from those swatches or the selected artwork (dialog `newColorGroup`, engine: swatch.newGroup)"), + ("ui.colorPicker", "Color Picker…", "", "{stroke?: bool (default: the active proxy), artboard?: index (the picker is for that artboard's background color instead), color?: \\\"#rrggbb\\\"|[r,g,b]|{c,m,y,k}|{gray} (default: the proxy's color, or the artboard's color, white when it has none)} open the Color Picker (fields: hex or color, channel, webOnly, swatches); RGB, HSB and hex values use the working RGB space; OK runs paint.setFill / paint.setStroke, or artboard.setProps {index, color} for an artboard"), + ("ui.sampleScreenColor", "Sample Color from Screen", "", "{} or {stroke?: bool (default: the active proxy), picker?: bool (the open Color Picker instead)} show the system's color sampler (macOS; browsers with an EyeDropper) and answer at once; the color the person picks anywhere on the screen then runs paint.setFill / paint.setStroke (one undo step) or becomes the Color Picker's color (field `color`), converted from display sRGB into the working RGB space; Escape cancels it and the colors stay as they were. ui.inspect reports `screenSampler: true` until it answers. An error without a sampler, while one is open, or with `picker` and no Color Picker open"), + ("ui.newBrush", "New Brush…", "", "{ids?} (default: the selected art) open New Brush, what the Brushes panel's New Brush button and art dropped on the panel open (dialog `newBrush`, field `type`: calligraphic|scatter|art|bristle|pattern, Art when there is art, else Calligraphic; scatter, art and pattern need art, else OK gives the error `nothing selected`); OK opens that type's Brush Options for the new brush (dialog `brushOptions` with the fields of ui.brushOptions and `__create`; name: New Brush, numbered when taken; the `ids` given in `__ids`), whose OK runs brush.new {type, name, params, ids?} (a name another brush has gets a number) and whose Cancel adds no brush"), + ("ui.brushOptions", "Brush Options…", "", "{name?} (default: the selected path's brush, else the current brush) open its Brush Options, what double-clicking a brush in the Brushes panel opens (dialog `brushOptions`): Calligraphic (fields name, angle −180..180, roundness 0..100, size 0..1296 pt, angleMode/roundnessMode/sizeMode: fixed|random|pressure, angleVariation/roundnessVariation/sizeVariation), Bristle (fields name, shape, size, length, density, thickness, opacity, stiffness), Scatter (fields sizeMin/sizeMax/sizeMode, spacingMin/spacingMax/spacingMode, scatterMin/scatterMax/scatterMode, rotationMin/rotationMax/rotationMode with modes fixed|random|pressure, rotationRelativeTo: page|path), Art (fields width %, scaleMode: proportional|stretch|betweenGuides, guideStart/guideEnd % of the art length, direction: leftToRight|rightToLeft|topToBottom|bottomToTop, flipAlong, flipAcross) or Pattern (fields scale %, spacing %, fit: stretch|addSpace|approximate, flipAlong, flipAcross); Scatter, Art and Pattern also colorization: none|tints|tintsAndShades|hueShift, keyColor, and tips: true opens Colorization Tips (what each method does, beside a sample of the brush painted with it), as the Tips button beside Method does; OK runs brush.options"), + ("ui.graphicStyleOptions", "Graphic Style Options…", "", "{name?} open Graphic Style Options (dialog `graphicStyleOptions`, field `name`): for style `name` OK renames it (graphicStyle.rename); without, OK makes a new style of that name from the selection (graphicStyle.new)"), + ("ui.charStyleOptions", "Character Style Options", "", "{name} open Character Style Options (dialog `charStyleOptions`); OK runs charStyle.edit with name, to and sparse attrs in one undo step; Cancel restores a live preview"), + ("ui.charStyleOptions.resetPanel", "Reset Panel", "", "{} clear the settings of the section shown, Basic Character Formats, Advanced Character Formats, Character Color or OpenType Features, in the open Character Style Options or New Character Style dialog; available only on those sections"), + ("ui.newCharStyle", "New Character Style…", "", "{} open New Character Style (dialog `newTextStyle`, field `name`, filled in with the name charStyle.new {} would give the style), what Alt-clicking the Character Styles panel's New button opens; OK runs charStyle.new with the name and the Type tool's selected characters (else the selected type) as they were when the dialog opened; Cancel adds no style"), + ("ui.newParaStyle", "New Paragraph Style…", "", "{} open New Paragraph Style (dialog `newTextStyle`, field `name`, filled in with the name paraStyle.new {} would give the style), what Alt-clicking the Paragraph Styles panel's New button opens; OK runs paraStyle.new with the name and the paragraph of the Type tool's selection (else the selected type) as it was when the dialog opened; Cancel adds no style"), + ("ui.symbolOptions", "Symbol Options…", "F8", "{name?} open Symbol Options (dialog `symbolOptions`, fields `name`, `exportType`: movieClip|graphic, `registration` 0..8). Without `name` and with a selection: New Symbol (default name the first free \\\"New Symbol\\\"/\\\"New Symbol N\\\", Export Type Movie Clip, Registration centre); OK runs symbol.new {name, exportType, registration}. With `edit: true` or `name`: edit that symbol; OK runs symbol.options {name, newName?, exportType, registration}"), + ("tool.options", "Tool Options…", "", "{tool: id} what double-clicking a tool button opens: hand → fits the artboard in the window (view.fitArtboard), zoom → 100% (view.actualSize), rotate|scale|reflect|shear → that Object › Transform dialog (dialog `rotate`, `scale`, `reflect` or `shear`; error `nothing selected` without a selection), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height, showCenterMark, showCrossHairs; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \\\"flare\\\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x, y and preview (on initially), previews one there and accepts it as one Undo step; Cancel rolls back the preview), lineSegment|arc|rectangularGrid|polarGrid → that tool's Tool Options, its click dialog without x and y (dialog kind = the tool id, `__toolOptions: true`; lineSegment: length (pt), angle (°), fillLine; arc: lengthX, lengthY (pt), reference (0–8), arcType: open|closed, baseAlong: x|y, slope (−100…100), fillArc; rectangularGrid: width, height (pt, the Default Size), reference (0–8), rows, columns, rowSkew, columnSkew (%), frame, fill; polarGrid: the same with concentric, radial, concentricSkew, radialSkew and compound for rows, columns, the skews and frame; OK runs tool.setOption {tool, values} and draws nothing: the next drag uses them (Fill Line, the arc's type, base, slope and fill, the grids' dividers, skews and switches) and a click's dialog opens at them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), typeOnPath|verticalTypeOnPath → Type on a Path Options for the selected type on a path (dialog `command` running type.pathOptions; error `nothing selected` without a selection, and the command's error when no type on a path is selected),symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (px for pencil and paintbrush, the same at any zoom; pt for smooth and blobBrush), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), livePreview (pencil, on: off draws a thin line while the stroke is drawn, and the path's fill and stroke on release), size (pt), angle (°, −180–180; 0) and roundness (%, 1–100; 100) of the eraser's tip; OK runs tool.setOption {tool, values}), livePaintSelection → Live Paint Selection Tool Options (dialog `livePaintSelectionOptions`, fields selectFills and selectStrokes (both on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \\\"livePaintSelection\\\", values}; a color with an alpha part (#rrggbbaa or #rgba) is an error and OK changes nothing), livePaintBucket → Live Paint Bucket Options (dialog `livePaintBucketOptions`, fields paintFills (on) and paintStrokes (off), highlight (on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the face highlight's width, 1–20 px; 4; an edge's highlight is half as wide); OK runs tool.setOption {tool: \\\"livePaintBucket\\\", values}; a color with an alpha part (#rrggbbaa or #rgba) is an error and OK changes nothing), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \\\"shapeBuilder\\\", values}; a highlightColor with an alpha part (#rrggbbaa or #rgba) is an error and OK changes nothing), objectsOnPath → Objects on Path Tool Options (dialog `objectsOnPathOptions`, fields pivot (0–8, row by row from the top left; 4, the center) and rotate (°, −360–360; 0); Reset puts 4 and 0 back; Save runs tool.setOption {tool: \\\"objectsOnPath\\\", values}: the objects attached next use them, groups attached before keep theirs)"), + ("ui.colorGuideOptions", "Color Guide Options…", "", "{} open Color Guide Options (dialog `colorGuideOptions`, fields `steps` 1–20 and `amount` 0–100): OK sets the Color Guide panel's variation grid (read back with `ui.inspect`: ui.color_guide; engine: color.harmony)"), + ("ui.colorBalanceDialog", "Adjust Color Balance…", "", "{} open Adjust Colors for the selection (dialog `colorBalance`, fields `mode` gray|rgb|cmyk|global, channels r g b / c m y k / gray / tint (global) −100..100, `convert`, `fill`, `stroke`, `preview`): previews live, OK runs edit.colors.adjustBalance as one undo step"), + ("ui.saturateDialog", "Saturate…", "", "{} open Saturate for the selection (dialog `saturate`, fields `intensity` −100..100, `preview`): previews live, OK runs edit.colors.saturate as one undo step"), + ("window.swatchLibrary", "Swatch Library", "", "{library: id or name (see swatch.library.list), or the id of a persistent panel's missing library | null (close every swatch library panel)} open the swatch library in a read-only panel of its own, floating (panel id `library:swatches:`, which window.panel.* moves, docks and closes), or bring its panel forward when it is open; clicking a swatch there runs swatch.library.add with apply. ui.libraryPanel sets its view, find field and Persistent (ui.inspect: ui.library_panels) → {open: id, panel, name, count}"), + ("window.swatchLibrary.other", "Other Library…", "", "{path?} (default: pick a file) load a .vcswatches, .gpl, .ase or .acb (color book) library, or another document's swatches (engine: swatch.library.load), and open it in its library panel"), + ("ui.libraryPanel", "Library Panel Options", "", "{panel: a library panel's id (`library:swatches:pastels`, as window.swatchLibrary answers), library?: id or name of another library of its kind to show in the panel (Previous / Next Library; one open in a panel of its own is brought forward instead), view?: smallThumb|mediumThumb|largeThumb|smallList|largeList, showFind?: bool, persistent?: bool (reopen the panel when the app starts)} set an open library panel, or a closed persistent one's view, Show Find Field and Persistent; kept with the preferences (ui.inspect: ui.library_panels) → {panel, kind, library, view, showFind, persistent, open}"), + ("ui.libraryPanel.locate", "Locate Library…", "", "{panel: an open library panel's id, path?} (default: pick a file) Locate Library… of a persistent library panel whose library's file is missing: load the library from the file, as Other Library… does, and show it in the panel, which keeps its view, Show Find Field and Persistent. When the file's library has a panel open already, the panel closes and that one is brought forward, made persistent when the closed one was → what ui.libraryPanel answers"), + ("ui.saveSwatchLibrary", "Save Swatch Library…", "", "{names?: [the swatches selected in the Swatches panel]} open Save Swatch Library (dialog `saveSwatchLibrary`: name, format: vcswatches|gpl|ase|css, user: save to the user library folder, selectedOnly); OK runs swatch.library.save"), + ("window.userSwatchLibrary1", "User Swatch Library 1", "", "{} open the 1. User Defined swatch library (swatch.library.list, category user)"), + ("window.userSwatchLibrary2", "User Swatch Library 2", "", "{} open the 2. User Defined swatch library"), + ("window.userSwatchLibrary3", "User Swatch Library 3", "", "{} open the 3. User Defined swatch library"), + ("window.userSwatchLibrary4", "User Swatch Library 4", "", "{} open the 4. User Defined swatch library"), + ("window.userSwatchLibrary5", "User Swatch Library 5", "", "{} open the 5. User Defined swatch library"), + ("window.userSwatchLibrary6", "User Swatch Library 6", "", "{} open the 6. User Defined swatch library"), + ("window.userSwatchLibrary7", "User Swatch Library 7", "", "{} open the 7. User Defined swatch library"), + ("window.userSwatchLibrary8", "User Swatch Library 8", "", "{} open the 8. User Defined swatch library"), + ("window.userSwatchLibrary9", "User Swatch Library 9", "", "{} open the 9. User Defined swatch library"), + ("window.userSwatchLibrary10", "User Swatch Library 10", "", "{} open the 10. User Defined swatch library"), + ("ui.mergeGraphicStyles", "Merge Graphic Styles…", "", "{names: [two or more styles]} open Graphic Style Options (dialog `graphicStyleOptions`, field `name`) to name the style OK merges from them (graphicStyle.merge)"), + ("ui.layerOptions", "Options for Selection…", "", "{ids?|id?} open Layer Options for Layers panel rows (default: the highlighted rows, else the current layer), filled in from the first: dialog `layerOptions`, fields name, color (a preset name such as \\\"Light Blue\\\" or #rrggbb), template, locked, visible, printable, preview, dimImages (bool), dimPercent (0–100); an object's row has name, visible and locked. OK runs layer.setProps (one undo step); for several rows it sends only the fields set in the dialog"), + ("ui.rename", "Rename", "", "{id?} open the Layers panel's name field on the row of layer, group or object id, as double-clicking the row's name does (the panel is shown and the layers around the row open): Enter renames it with layer.setProps (one undo step), Escape keeps the name. Without id: the highlighted row when a Layers panel row was clicked last, else the one selected object or group, else the one highlighted row, else the current layer. Several rows or objects, or a row the panel's search, filter or Show Layers Only hides, is an error and nothing opens → {id, name}"), + ("ui.newLayer", "New Layer Options…", "Cmd+Alt+L", "{sublayer?: bool} open Layer Options for a new layer (or a sublayer of the current layer), named and coloured as it would be: dialog `layerOptions` with mode new|newSublayer; OK runs layer.new or layer.newSublayer with the fields"), + ("ui.newArtboard", "New Artboard Options…", "", "{} open Artboard Options for a new artboard, filled in as artboard.new {} would make it (Artboard N, the last artboard's size, 20 pt to its right): dialog `artboardOptions` with new: true, fields name, x, y, width, height, showCenterMark, showCrossHairs; OK runs artboard.new with the fields and makes the new artboard the active one; Cancel adds nothing"), + ("artboard.selectAll", "Select All Artboards", "", "{} the Artboard tool, chosen first when another tool is in use, selects every artboard, and the active artboard stays active; the art's selection stays as it is, and no undo step is recorded → {selected: [index…]} (the active artboard first)"), + ("ui.layersPanelOptions", "Panel Options…", "", "{} open the Layers panel's Panel Options: dialog `layersPanelOptions`, fields layersOnly, locateSelected (Locate Selected Objects, off by default: when the last selected object changes outside the panel, on the canvas or by a command, the panel opens the layers and groups around it and scrolls to its row, as after layer.locate, without highlighting the row; when Show Layers Only, a search or the filter leaves the object without a row, the panel opens no rows for it and scrolls as with the option off; a change made during a drag is located when the drag ends), rowSize (small|medium|large|other), otherSize (12–100 pt), thumbLayers, thumbGroups, thumbObjects; OK applies them (kept with the UI state)"), + ("ui.navigatorPanelOptions", "Panel Options…", "", "{color?: #rrggbb or a preset name (Light Red, the default, then the layer colors such as \\\"Light Blue\\\"), solidDashes?: bool (Draw Dashed Lines as Solid Lines, off by default: the Navigator's thumbnail draws dashed strokes without their dashes; the canvas, printing and exports keep them)} set the Navigator panel's options, the color its view box is drawn in and solid dashes (kept with the UI state) → {color, solidDashes}; with neither, open its Panel Options (dialog `navigatorPanelOptions`, fields color, solidDashes; OK sets them); an error for a value that is not a color or not true or false, which changes nothing"), + ("ui.layersExpand", "Expand Layers Panel Rows", "", "{ids?: [id…] (default: every row that holds others), open?: bool (default true)} open or close rows of the Layers panel, as clicking their triangles does → {count}"), + ("ui.layersFilter", "Filter Layers Panel Rows", "", "{objects?: [paths|shapes|text|images|effects|groups|linkedFiles|guides], layers?: [locked|hidden]} set the Layers panel's Filter (By Object, By Layer): a row stays when it is of a kind in `objects` and in a state in `layers` (its own lock or eye; an empty list takes every row), or when a row inside it stays; Search All narrows the rows further. A key left out keeps its list. The filter lasts the session and does not change the document, its open rows or the selection → {objects, layers}"), + ("ui.tileEdgeColor", "Tile Edge Color…", "", "{} open Tile Edge Color (dialog `tileEdgeColor`, field `color`: #rrggbb or a preset name such as \\\"Light Blue\\\"); OK sets the preference patternTileEdgeColor (prefs.set), the colour of the tile edge and swatch bounds in pattern editing mode; a color with an alpha part (#rrggbbaa or #rgba) is an error and OK changes nothing"), + ("ui.flattenTransparencyDialog", "Flatten Transparency…", "", "{} open Flatten Transparency for the selection (dialog `flattenTransparency`, fields `preset` (a preset name: setting it loads its options), balance 0..100, lineArtPpi, gradientPpi 1..2400, textToOutlines, strokesToOutlines, clipComplexRegions, antiAlias, preserveAlpha, preserveOverprints, `preview` (off at first)): OK runs object.flattenTransparency with those options as one undo step"), + ("ui.flattenerPresetsDialog", "Transparency Flattener Presets…", "", "{selected?: preset name} open the presets manager (dialog `flattenerPresets`, fields `selected`, then the selected preset's `name` and option keys: setting them on a saved preset saves it, a new `name` renames it; built-in presets don't change). New, Delete, Import… and Export… run flattener.presets.save / delete / import / export"), + ("ui.imageTracePresetDialog", "Save as New Preset…", "", "{params?: tracing settings (default: the Default preset's), rename?: a saved preset's name} the Image Trace panel's Manage Presets: open Save as New Preset… for those settings, or with `rename`, Rename… for that saved preset (dialog `imageTracePreset`, field `name`: a new \\\"Tracing Preset N\\\" or the preset's name); OK runs imageTrace.presets.save with the name (as `newName` when renaming), and the panel shows the preset it made from its settings"), + ("ui.flattenerPreview", "Flattener Preview", "", "{highlight?: none|rasterizedRegions|transparentObjects|allAffected|expandedPatterns|outlinedStrokes|outlinedText|allRasterized, overprints?: preserve|simulate|discard, preset?: name (loads its options), options?: {option keys, over the preset's}, showOptions?} set the Flattener Preview panel (ui.inspect: ui.flattener_preview), show it and refresh its snapshot → what flattener.preview answers for it"), + ("window.graphicStyleLibrary", "Graphic Style Library", "", "{library: id or name (see graphicStyle.libraries), or the id of a persistent panel's missing library | null (close every graphic style library panel)} open the graphic style library in a read-only panel of its own, floating (panel id `library:graphicStyles:`), or bring its panel forward when it is open; clicking a style there runs graphicStyle.addFromLibrary with apply (Alt: add). ui.libraryPanel sets its view, find field and Persistent → {open: id, panel, name, count}"), + ("window.graphicStyleLibrary.other", "Other Library…", "", "{path?} (default: pick a file) load a .vcstyles library, or another document's graphic styles (engine: graphicStyle.loadLibrary), and open it in its library panel"), + ("ui.saveGraphicStyleLibrary", "Save Graphic Style Library…", "", "{names?: [the styles selected in the Graphic Styles panel]} open Save Graphic Style Library (dialog `saveGraphicStyleLibrary`: name, user: save to the user library folder, selectedOnly); OK runs graphicStyle.saveLibrary"), + ("ui.loadCharStyles", "Load Character Styles…", "", "{path?} (default: pick a document in an open dialog; cancelling it loads nothing) add that document's character styles to the active one (engine: charStyle.load), the Character Styles panel menu's Load Character Styles…"), + ("ui.loadParaStyles", "Load Paragraph Styles…", "", "{path?} (default: pick a document in an open dialog; cancelling it loads nothing) add that document's paragraph styles to the active one (engine: paraStyle.load), the Paragraph Styles panel menu's Load Paragraph Styles…"), + ("ui.loadAllStyles", "Load All Styles…", "", "{path?} (default: pick a document in an open dialog; cancelling it loads nothing) add that document's character and paragraph styles to the active one (engine: textStyle.loadAll), Load All Styles… in either panel's menu"), + ("window.userGraphicStyleLibrary1", "User Graphic Style Library 1", "", "{} open the 1. User Defined graphic style library (graphicStyle.libraries, category user)"), + ("window.userGraphicStyleLibrary2", "User Graphic Style Library 2", "", "{} open the 2. User Defined graphic style library"), + ("window.userGraphicStyleLibrary3", "User Graphic Style Library 3", "", "{} open the 3. User Defined graphic style library"), + ("window.userGraphicStyleLibrary4", "User Graphic Style Library 4", "", "{} open the 4. User Defined graphic style library"), + ("window.userGraphicStyleLibrary5", "User Graphic Style Library 5", "", "{} open the 5. User Defined graphic style library"), + ("window.userGraphicStyleLibrary6", "User Graphic Style Library 6", "", "{} open the 6. User Defined graphic style library"), + ("window.userGraphicStyleLibrary7", "User Graphic Style Library 7", "", "{} open the 7. User Defined graphic style library"), + ("window.userGraphicStyleLibrary8", "User Graphic Style Library 8", "", "{} open the 8. User Defined graphic style library"), + ("window.userGraphicStyleLibrary9", "User Graphic Style Library 9", "", "{} open the 9. User Defined graphic style library"), + ("window.userGraphicStyleLibrary10", "User Graphic Style Library 10", "", "{} open the 10. User Defined graphic style library"), + ("window.symbolLibrary", "Symbol Library", "", "{library: id or name (see symbol.libraries) | null (close)} open a symbol library in a library panel of its own (UI state `library_panels`, kind symbols); clicking a symbol there runs symbol.addFromLibrary, dragging one onto the canvas runs it with place → {open, panel, name, count}"), + ("window.symbolLibrary.other", "Other Library…", "", "{path?} (default: pick a file) load the symbols of another document or a saved symbol library (engine: symbol.loadLibrary), and open them in a library panel"), + ("ui.saveSymbolLibrary", "Save Symbols…", "", "{names?: [the symbol selected in the Symbols panel]} open Save Symbol Library (dialog `saveSymbolLibrary`: name, user: save to the user library folder, selectedOnly); OK runs symbol.saveLibrary"), + ("window.userSymbolLibrary1", "User Symbol Library 1", "", "{} open the 1. User Defined symbol library (symbol.libraries, category user)"), + ("window.userSymbolLibrary2", "User Symbol Library 2", "", "{} open the 2. User Defined symbol library"), + ("window.userSymbolLibrary3", "User Symbol Library 3", "", "{} open the 3. User Defined symbol library"), + ("window.userSymbolLibrary4", "User Symbol Library 4", "", "{} open the 4. User Defined symbol library"), + ("window.userSymbolLibrary5", "User Symbol Library 5", "", "{} open the 5. User Defined symbol library"), + ("window.userSymbolLibrary6", "User Symbol Library 6", "", "{} open the 6. User Defined symbol library"), + ("window.userSymbolLibrary7", "User Symbol Library 7", "", "{} open the 7. User Defined symbol library"), + ("window.userSymbolLibrary8", "User Symbol Library 8", "", "{} open the 8. User Defined symbol library"), + ("window.userSymbolLibrary9", "User Symbol Library 9", "", "{} open the 9. User Defined symbol library"), + ("window.userSymbolLibrary10", "User Symbol Library 10", "", "{} open the 10. User Defined symbol library"), + ("window.brushLibrary", "Brush Library", "", "{library: id or name (see brush.libraries) | null (close)} open a brush library in a library panel of its own (UI state `library_panels`, kind brushes); clicking a brush there runs brush.addFromLibrary (with apply when paths are selected), dragging one onto a path runs it with apply on that path, dragging one onto the Brushes panel adds it → {open, panel, name, count}"), + ("window.brushLibrary.other", "Other Library…", "", "{path?} (default: pick a file) load the brushes of another document or a saved brush library (engine: brush.loadLibrary), and open them in a library panel"), + ("ui.saveBrushLibrary", "Save Brushes…", "", "{names?: [the brushes selected in the Brushes panel]} open Save Brush Library (dialog `saveBrushLibrary`: name, user: save to the user library folder, selectedOnly); OK runs brush.saveLibrary"), + ("window.userBrushLibrary1", "User Brush Library 1", "", "{} open the 1. User Defined brush library (brush.libraries, category user)"), + ("window.userBrushLibrary2", "User Brush Library 2", "", "{} open the 2. User Defined brush library"), + ("window.userBrushLibrary3", "User Brush Library 3", "", "{} open the 3. User Defined brush library"), + ("window.userBrushLibrary4", "User Brush Library 4", "", "{} open the 4. User Defined brush library"), + ("window.userBrushLibrary5", "User Brush Library 5", "", "{} open the 5. User Defined brush library"), + ("window.userBrushLibrary6", "User Brush Library 6", "", "{} open the 6. User Defined brush library"), + ("window.userBrushLibrary7", "User Brush Library 7", "", "{} open the 7. User Defined brush library"), + ("window.userBrushLibrary8", "User Brush Library 8", "", "{} open the 8. User Defined brush library"), + ("window.userBrushLibrary9", "User Brush Library 9", "", "{} open the 9. User Defined brush library"), + ("window.userBrushLibrary10", "User Brush Library 10", "", "{} open the 10. User Defined brush library"), + ("ui.expandDialog", "Expand…", "", "{} open Expand for the selection (dialog `expand`, fields object, fill, stroke (all on; one the selection has nothing for is disabled, see object.expand.info), gradient: objects|mesh, steps 1..1000 (255)): OK runs object.expand with them as one undo step; dimmed, and an error, while the selection has an appearance effect.expandAppearance expands (object.expand.info's appearance)"), + ("attributes.openUrl", "Browser", "", "{url?} open url, else the selection's URL (attributes.info), in the web browser (the Attributes panel's Browser button; http, https and mailto URLs only; not over the control channel or MCP) → {url}"), + ("ui.javascriptFilesDialog", "Link JavaScript Files…", "", "{} open JavaScript Files for the active document (dialog `javascriptFiles`, fields `file`: a path or URL and `selected`: a linked file; the SVG Interactivity panel's Link JavaScript Files button): its list shows svgInteractivity.info's files, Add runs svgInteractivity.addFile with file and Remove runs svgInteractivity.removeFile with selected"), + ("ui.svgFilterDialog", "Apply SVG Filter…", "", "{index?, item?} open Apply SVG Filter for the active document (dialog `svgFilter`, fields `selected`: a filter's name, `code`: the code being edited, `editing`: \\\"\\\", \\\"new\\\" or the name of the filter being edited; with index, for the applied svg.filter effect at index of item, as effect.dialog opens it): its list shows svgFilter.list's filters; OK runs svgFilter.apply with selected, or effect.setParams on the applied effect; New and Edit open the code, Save runs svgFilter.define, Delete runs svgFilter.delete and Import… svgFilter.import with a picked SVG file"), + ("ui.importSvgFilter", "Import SVG Filter…", "", "{} pick an SVG file and add its filters to the active document's SVG filters (svgFilter.import) → svgFilter.import's result"), + ("ui.spotColors", "Spot Colors…", "", "{} open Spot Colors (dialog `spotColors`, field `useLab`: true shows and separates Lab spot colours from their Lab values, false from their CMYK equivalents); OK runs swatch.spotOptions as one undo step"), + ("ui.menuDialog", "Menu Dialog", "", "{command: object.move|object.rotate|object.scale|object.reflect|object.shear|object.transformEach|path.average|path.averageJoin|object.path.offsetPath|object.path.simplify|object.path.smooth|object.path.splitIntoGrid|object.vectorHalftone|artboard.rearrange|artboard.rename|object.envelope.makeWithWarp|object.envelope.resetWithWarp|object.envelope.makeWithMesh|object.envelope.resetWithMesh|object.envelope.options|addon.arrange.grid|addon.arrange.replace|addon.arrange.size} open the dialog that command's menu item opens (dialog kind: move, rotate, scale, reflect, shear, transformEach, …, vectorHalftone, rearrangeArtboards, renameArtboards, envelopeWarp, envelopeMesh, envelopeOptions, arrangeGrid, arrangeReplace, arrangeSize; Scale and Transform Each have `corners` and `strokes`, from the preferences, which OK updates; Transform Each opens on the values it was last confirmed with (not `copy`), and its Copy button confirms with `copy: true`; the envelope dialogs start from the selected envelope, Make opens as Reset (`reset: true`) while one is selected)"), + ("ui.widthPointEdit", "Width Point Edit…", "", "{id, index?: existing width point, t?: 0..1 for a new point, adjustAdjoining?: bool (false)} open Width Point Edit on path `id` (dialog `widthPoint`: side1, side2 (pt), linked, adjustAdjoining; double-clicking the stroke opens it too, Shift on a width point enabling adjoining adjustment): OK runs stroke.widthPoint.set; discard: true (Delete) removes an existing point or discards a prospective point"), + ("ui.corners", "Corners…", "", "{id?, corners?: [i…]} open Corners for path `id` (default: the selected one) and its corners (anchor indices of the path with its corners uncut, as object.setLiveShape takes them: a rectangle's 0 top-left, 1 top-right, 2 bottom-right, 3 bottom-left; default: the Direct-Selected corners, else every corner) (dialog `corners`: kind (round|invertedRound|chamfer), radius (pt); double-clicking a corner widget opens it too): OK runs object.setLiveShape"), + ("ui.simplifyAuto", "Auto-Simplify", "", "{} the open Simplify slider (Object › Path › Simplify opens it: dialog `simplifySlider` with field `level` 0..100, object.path.simplify's level, opening at the level object.path.simplify {auto: true} picks for the selection, previewed on the canvas until ui.dialog.confirm keeps it as one undo step; after OK in the Simplify dialog with `retain` ticked, the menu item opens the dialog `simplify` with the fields of that OK instead) moves to the level object.path.simplify {auto: true} picks for the selection and previews it → {level}; an error when the slider isn't open"), + ("ui.smoothAuto", "Auto-Smooth", "", "{} the open Smooth slider (Object › Path › Smooth opens it: dialog `smoothSlider` with field `level` 0..100, object.path.smooth's level, opening at the level object.path.smooth {auto: true} picks, previewed on the canvas until ui.dialog.confirm keeps it as one undo step) moves to the level object.path.smooth {auto: true} picks and previews it → {level}; an error when the slider isn't open"), + ("ui.simplifyMoreOptions", "More Options", "", "{} the Simplify dialog (dialog `simplify`: tolerance (pt), angleThreshold, straightLines, showOriginal, retain: ticked, OK makes Object › Path › Simplify open this dialog with these fields next time) takes the open Simplify slider's place, at the slider's tolerance → {tolerance}; an error when the slider isn't open"), + ("ui.dialogReset", "Reset", "", "{} the open dialog's Reset, which its Cancel button reads while Alt (Option on macOS) is held (Flare Tool Options, dialog `flareOptions`): its fields go back to their defaults and the dialog stays open, so OK applies them → the dialog; an error when no dialog is open or the open one has no Reset"), + ("ui.justification", "Justification…", "", "{} open Justification (the Paragraph panel menu's Justification…) on the paragraphs the Paragraph panel acts on, filled in with the values it shows, with Preview (dialog `justification`: wordMin, wordDesired, wordMax 0..1000, letterMin, letterDesired, letterMax -100..500, glyphMin, glyphDesired, glyphMax 50..200 (percent), autoLeading 0..500 (percent), singleWord (fullJustify|alignLeft|alignCenter|alignRight), preview): OK runs text.setFormat with the values that changed, as one undo step, and is disabled while a row's Minimum exceeds its Desired or Desired its Maximum; an error with no type selected"), + ("ui.hyphenation", "Hyphenation…", "", "{} open Hyphenation (the Paragraph panel menu's Hyphenation…) on the paragraphs the Paragraph panel acts on, filled in with the values it shows (dialog `hyphenation`: hyphenate, wordsLongerThan 1..25, afterFirst 1..15, beforeLast 1..15, hyphenLimit 0..25 (0: no limit), hyphenationZone (pt), hyphenateCapitalized): OK runs text.setFormat with the values that changed, as one undo step; an error with no type selected"), + ("ui.tateChuYokoSettings", "Tate-chu-yoko Settings…", "", "{} open Tate-chu-yoko Settings (the Character panel menu's Tate-chu-yoko Settings…, with Show East Asian Options) on the characters the Character panel acts on, filled in with the values they share (dialog `tateChuYokoSettings`: tcyUpDown, tcyLeftRight (pt, -1296..1296)): OK runs text.setFormat (text.setRangeStyle while the Type tool edits text) with the values that changed, as one undo step; an error with no type selected"), + ("ui.colorGuideLimit", "Limit Color Guide to Library", "", "{library: swatch library id or name (see swatch.library.list) | \\\"document\\\" (the document's swatches) | \\\"\\\" or null (no limit)} limit the Color Guide panel's colours to that library: every harmony colour and variation snaps to its nearest colour, as color.harmony {limitTo} answers (ui.inspect: ui.color_guide_limit) → {limitTo, name}"), + ("ui.colorPanel", "Color Panel Options", "", "{mode?: grayscale|rgb|hsb|cmyk|websafe, options?: bool} pick the Color panel's mode as its panel menu does (the active color converts to the mode's model as one undo step), and Show Options (true) or Hide Options (false); both are kept with the preferences; RGB, HSB, Web Safe RGB and hex values use the working RGB space (ui.inspect: ui.color_mode, ui.color_hide_options) → {mode (null: none picked yet), options, shown: the mode the panel shows the color in, or tint for a tint of a global or spot swatch}"), + ("ui.savePdfDialog", "Save PDF Dialog", "", "{path?, preset?, range?, …document.exportPdf options} open the Save PDF dialog with these options (fields = the options, sections are objects; OK writes path, else asks)"), + ("file.exportAs", "Export As…", "", "{format?, useArtboards?, range?} opens Export As (then the format's options); with {path, …document.export options} writes the file(s) → {path, warnings, files?}"), + ("ui.fileInfoDialog", "File Info Dialog", "", "{} open File Info (dialog `fileInfo`: the file.info fields, plus __keyword, keywords typed but not added yet); OK runs file.info. The File Info… menu item opens it too"), + ("ui.rasterEffectsSettingsDialog", "Document Raster Effects Settings Dialog", "", "{} open Document Raster Effects Settings (dialog `rasterEffectsSettings`: the document.rasterEffectsSettings fields); OK runs it. The menu item opens it too"), + ("ui.effectGalleryDialog", "Effect Gallery Dialog", "", "{} open Effect Gallery (dialog `effectGallery`: browse raster filters, build a stack, OK applies it as separate effects in one undo step). Effect → Effect Gallery… opens it too"), + ("ui.pdfPresetsDialog", "PDF Presets…", "", "{selected?: preset name} open Edit → PDF Presets (dialog `pdfPresets`, field `selected`): the built-in presets (read-only) and the saved ones, with the selected one's description and settings. New… and Edit… open the preset editor (ui.pdfPresetDialog); Delete, Import… and Export… run pdf.preset.delete / import / export"), + ("ui.pdfPresetDialog", "PDF Preset", "", "{name?: a saved preset to edit | preset?: the preset a new one starts from (default VectorCraft Default)} open the preset editor (dialog `pdfPreset`: the Save PDF dialog's option fields plus `name` and `description`); OK runs pdf.preset.save and returns to PDF Presets"), + ("file.openRecent11", "Open Recent File 11", "", "{}"), + ("file.openRecent12", "Open Recent File 12", "", "{}"), + ("file.openRecent13", "Open Recent File 13", "", "{}"), + ("file.openRecent14", "Open Recent File 14", "", "{}"), + ("file.openRecent15", "Open Recent File 15", "", "{}"), + ("file.openRecent16", "Open Recent File 16", "", "{}"), + ("file.openRecent17", "Open Recent File 17", "", "{}"), + ("file.openRecent18", "Open Recent File 18", "", "{}"), + ("file.openRecent19", "Open Recent File 19", "", "{}"), + ("file.openRecent20", "Open Recent File 20", "", "{}"), + ("file.openRecent21", "Open Recent File 21", "", "{}"), + ("file.openRecent22", "Open Recent File 22", "", "{}"), + ("file.openRecent23", "Open Recent File 23", "", "{}"), + ("file.openRecent24", "Open Recent File 24", "", "{}"), + ("file.openRecent25", "Open Recent File 25", "", "{}"), + ("file.openRecent26", "Open Recent File 26", "", "{}"), + ("file.openRecent27", "Open Recent File 27", "", "{}"), + ("file.openRecent28", "Open Recent File 28", "", "{}"), + ("file.openRecent29", "Open Recent File 29", "", "{}"), + ("file.openRecent30", "Open Recent File 30", "", "{}"), + ("file.reveal", "Show in Folder", "", "{} show the document's file in the system file manager (desktop) → {path}"), + ("file.cancelExport", "Cancel Export", "", "{} stop every export running in the background (Background Export): an export stops between artboards and writes no file once cancelled → {cancelled: n}"), + ("file.exportInBackground", "Continue in Background", "", "{} close the export progress dialog; the exports go on, shown in the status bar → {hidden: n}"), + ("file.cancelOpen", "Cancel Opening", "", "{} stop opening every file being read in the background (its progress window's Cancel): the files don't open → {cancelled: n}"), + ("file.openInBackground", "Continue in Background", "", "{} close the progress window of the files being opened; they go on being read, shown in the status bar → {hidden: n}"), + ("ui.dxfOptionsDialog", "DXF Options Dialog", "", "{path?, useArtboards?, range?, …document.exportDxf options} open DXF Options (dialog `dxfOptions`: fields = these options over the ones used last, a dxfPreset's options under them; the dxfPreset field shows cnc while every field it sets matches, else custom); OK checks them, remembers them and writes path (else asks). Export As… → DXF opens it too"), + ("links.editOriginal", "Edit Original", "", "{id?} open the file of linked image `id` (default: the selected linked image) in the system's default app for its type (Links panel, Edit › Edit Original; only a regular file with the extension of a format document.open reads, and not over the control channel or MCP); a placed document's file opens here in a new tab, and saving it updates the documents placing it → {path}; edits saved elsewhere show after links.update"), + ("links.reveal", "Show in Folder", "", "{id?} show the file of linked image `id` (default: the selected linked image) in the system's file manager → {path}"), + ("ui.placementOptionsDialog", "Placement Options…", "", "{ids?} open Placement Options for images `ids` (default: the selected ones; dialog `placementOptions`: ids, preserve, align, clip, see links.placementOptions); OK runs links.placementOptions"), + ("ui.relinkDialog", "Relink…", "", "{ids?: [image or placed document ids]} open Relink for `ids` (default: the selected ones; dialog `relink`: ids, links ([{name, ids}], one row per linked file), scope (Replace: each|selected (default)|all|missingInFolder, see links.relink), path (picked when empty), paths (with each: the files chosen so far, one per row of links)); OK runs links.relink with them, one undo step, except with each: OK takes path, or picks a file, for the next row of links, and the last row's OK relinks them all. An error when nothing is given or selected"), + ("ui.findLinksInFolder", "Find Linked Files in Folder…", "", "{folder?, ids?: [image or placed document ids]} the Links panel's Find in Folder…: asks for a folder (or takes folder), then opens Find Linked Files (dialog `findLinks`) looking in it and its subfolders for the files of linked objects `ids` (default: the selected linked images and placed documents, else every missing file; links.findFiles). An error when there is nothing to look for. Disabled where there is no folder picker (the web)"), + ("ui.packageDialog", "Package…", "Cmd+Alt+Shift+P", "{} open Package for the saved document (dialog `package`: folder, name, copyLinks, linksFolder, relink, copyFonts, report; a document never saved asks to Save As first); OK saves unsaved changes, runs file.package (the web downloads the zip) and offers file.showPackage"), + ("ui.action.rename", "Rename Action", "", "{set: index, action: index, name} rename the Actions panel's action at action in set (0 is the top set) to name, with spaces trimmed from both ends; its steps, function key and color stay. An index past the last set or action, or an empty name, is an error and renames nothing → {name}"), + ("ui.action.stepsIncluded", "Include Action Steps", "", "{set: index, action?: index, step?: index, included: bool, others?: bool} include or exclude the playback of every step in a set, every step in one action, or one step. With a step and others: true, leave that step unchanged and apply included to every other step in its action (Alt/Option-click). Invalid indexes change nothing → {set, action?, step?, included}"), + ("ui.action.new", "New Action", "", "{set: index, name, key?: 0-15, cmd?, shift?, color?: red|yellow|green|blue|violet} add an action named name (spaces trimmed from both ends) after the last action of the Actions panel's set at set (0 is the top set) and start recording into it; Stop Playing/Recording (ui.action.stop) ends the recording. key 1 to 15 is the function key F1 to F15 that plays the action (0 or null: none), with cmd (Ctrl, Command on macOS) and shift; color is its Button Mode color (null or \\\"\\\": none), which the panel does not show. Action function keys take priority over command shortcuts and preserve the command's binding. On Windows F1, Ctrl+F4 and Ctrl+F6 cannot be used. The set becomes current and the new action is selected. The panel's Create New Action button and menu's New Action… (dialog `actionNew` with set, name, key, cmd, shift and color) run this on Record. While an action is recording, and for an index past the last set, an empty name, or a key or color that is not one of these, it is an error and nothing is added → {set, action}"), + ("ui.action.options", "Action Options", "", "{set: index, action: index, name?, key?: 0-15, cmd?, shift?, color?: red|yellow|green|blue|violet} change the Actions panel's action at action in set (0 is the top set); its steps stay. name renames it (spaces trimmed from both ends), key 1 to 15 with cmd (Ctrl, Command on macOS) and shift sets the function key F1 to F15 that plays it (0 or null: none), and color sets its Button Mode color (null or \\\"\\\": none); a param left out keeps what the action has, and cmd or shift alone changes that modifier of its key. Action function keys take priority over command shortcuts and preserve the command's binding. The panel menu's Action Options… (dialog `actionSetOptions` with set, action, name, key, cmd, shift and color) runs this for the selected action. An index past the last set or action, an empty name, or a key or color that is not one of these is an error and changes nothing → {name, key, color}"), + ("ui.action.play", "Play Action", "", "{action: name, set?: name} play the Actions panel's action named action at the speed ui.action.playbackOptions sets; set names the set to look in (default: every set). Accelerated plays it as one undoable batch → what command.batch returns. Step by Step and Pause For play its first step now and the others one at a time, each its own undo step, until the last, ui.action.stop, or a step that comes due while a different document is active than after the step before → {results: [the first step's result], pending: the steps left}. An action with a stop, at any speed, plays up to the stop and waits until ui.action.continue or ui.action.stop; at Accelerated the steps between stops are one undoable batch each → {results, pending, atStop?: true when it already waits}. A name no action has, or one that more than one action in the sets looked in has, or an action played while another is still playing step by step or with pauses, is an error and nothing plays"), + ("ui.action.playbackOptions", "Playback Options", "", "{speed?: accelerated|step|pause, seconds?: 1-60} set how the Actions panel plays actions: accelerated (the default) plays every step at once, step plays one step at a time and draws the window after each, and pause waits seconds (a whole number, 1 to 60, VectorCraft's own range) between steps. A param left out keeps its value; the setting is kept with the UI preferences. Any other speed or seconds is an error and changes nothing. The panel menu's Playback Options… (dialog `actionPlayback` with speed and seconds) runs this on OK → {speed, seconds}"), + ("ui.action.record", "Begin Recording", "", "{set?: index} begin recording an automatically named Action N in the Actions panel's set at set (0 is the top set), by default the current set or the first set; with no sets, make Set 1. The set becomes current, and ui.action.stop adds the recorded action after that set's existing actions. An explicit invalid set or an existing recording is an error and changes nothing → {set, name}"), + ("ui.action.stop", "Stop Playing/Recording", "", "{} end the Actions panel's recording and keep its steps, in the action ui.action.new made for it, else in a new action after the last action of its set, as the panel's Stop Playing/Recording button does. It also ends an action playing step by step or with pauses, or one waiting at a stop, whose message box closes: the steps played stay and the rest do not run. With nothing recording or playing it changes nothing → {stopped}"), + ("ui.action.insertStop", "Insert Stop", "", "{set?: index, action?: index, step?: index, message?, allowContinue?: bool} insert a stop, at which a playing action waits and shows message. With set and action (0 is the top set or action), the stop goes into that action, after step, else after its last step; without them it goes into the recording in progress, after the steps recorded so far. message defaults to none, and allowContinue, which offers Continue in the message box, defaults to false. A stop in an action becomes the panel's selected step, and its action and set become the selected ones. An index past the last set, action or step, a param of another type, params that are not an object, or no action and no recording is an error and changes nothing. The panel menu's Insert Stop… (dialog `actionStopRecord` with message, allowContinue, set, action and step) runs this on OK → {set, action, step}, or {recording: true}"), + ("ui.action.continue", "Continue Action", "", "{} play on from the stop the Actions panel's playing action waits at, on the document active now, and close the stop's message box (dialog `actionStopMessage`): the next step runs at once, or at Accelerated the steps up to the next stop as one undo step. The panel's Play Current Selection runs this while an action waits. With no action waiting at a stop it is an error → {results, pending, atStop?}"), + ("ui.action.deleteStep", "Delete Action Step", "", "{set: index, action: index, step: index} delete one step, a stop or a command, of the Actions panel's action at action in set (0 is the top set, action or step). The steps after it keep whether they are excluded, and no step of that action stays selected. A Record Stop dialog open on that step closes, and one open on a later step of the action follows it up. A missing or invalid index is an error and changes nothing. Undo does not bring the step back. The panel's Delete Selection runs this for the selected step → {set, action, step}"), + ("ui.batchDialog", "Batch…", "", "{set?: index, action?: index} open Batch (dialog `actionBatch`) for the action at set/action (default: the first action of the first set); OK runs action.batch with the dialog's source, destination and overrides"), + ("ui.actionSet.save", "Save Actions…", "", "{set?: index, id?, path?} write the Actions panel's set at set (0 is the top set; default: the current set) with its actions to an action set file (.vcactions) at path, else a picked file named after the set (the web downloads it). id holds the panel's own set id, which its menu passes: the set with this id is saved wherever it is now, and set is not read. A set over 8 MiB as a file, with more than 1,000 actions or with an action of more than 10,000 steps is not written; the panel menu's Save Actions… → {path}"), + ("ui.actionSet.load", "Load Actions…", "", "{path? | data?: the file's text} read an action set file (.vcactions) at path, else a picked one, and add its set after the Actions panel's sets; a set named like one in the panel gets the next free number (Print 2), and the other sets stay. Files of another version, over 8 MiB, with more than 1,000 actions or with an action of more than 10,000 steps are refused; the panel menu's Load Actions… (disabled where there is no open dialog, the web) → {set: index, name, actions: n}. An action set another app saved as text (.aia, starting /version; read with actionSet.read) comes in with its actions. A step with a label and no recorded values comes in when exactly one menu command has that label (in English or the interface language, any case, no final …), and that command is directly in a top menu, can be recorded in actions, is what its menu bar item runs with no values and opens no dialog (never a submenu's item, a file or document command, Paste, Undo, Redo or an add-on's command), with no params and excluded from playback when it is off in the file; the other steps, every step with no label and every step recorded with values (a recorded menu item too) among them, are left out → {set, name, actions, skipped: [{action, step: its label, else its name}] (the first 1,000), leftOut: n}, and the status bar says how many were left out; a set with no name takes the file's name, else Actions; a file actionSet.read marks truncated is refused, and text that starts /version and isn't an action set gives \\\"not an action set file\\\""), + ("ui.actionSet.delete", "Delete Action Sets", "", "{sets?: [index], ids?: [id]} delete the Actions panel's sets at these indexes (0 is the top set), with their actions (default: the current set). ids holds the panel's own set ids, which its question passes: the sets with these ids are deleted wherever they are now, sets is not read, and when one of them no longer exists nothing is deleted. The panel's Delete Selection on selected sets (Shift-click adds a range) asks first, and OK runs this. A recording into a deleted set stops and keeps nothing. An index past the last set is an error → {deleted: n}"), + ("ui.actionSet.duplicate", "Duplicate Action or Action Set", "", "{set?: index, action?: index} copy the Actions panel's action at action in set (0 is the top set) to right after it in the same set; without action, copy set with all its actions to right after it (default: the current set). The copy is named after the original with \\\" copy\\\" added, a copied set gets an id of its own, and the copy is selected. The panel menu's Duplicate runs this for the selected action, else the current set. An index past the last set or action is an error → {set, action?}"), + ("file.showPackage", "Show Package", "", "{folder} show a package folder (file.package's folder) in the file manager → {folder}"), + ("docInfo.save", "Save Document Info…", "", "{path?, selectionOnly?, category?} write Document Info's text report (document.info {format: \\\"text\\\"}) to path, else a picked file (the web downloads it); with category (a document.info category id) only that category, else every category. The panel menu's Save… passes the category the panel shows and its Selection Only → {path}"), + ("ui.epsOptionsDialog", "EPS Options Dialog", "", "{path?, useArtboards?, range?, …document.exportEps options} open EPS Options (dialog `epsOptions`: fields = these options over the ones used last); OK checks them, remembers them and writes path (else asks). Export As… → EPS opens it too"), + ("file.saveForWeb", "Save for Web (Legacy)…", "Cmd+Alt+Shift+S", "{} open Save for Web (dialog `saveForWeb`: fields = the webExport.settings settings, `preset` loads a preset, __view: original|optimized|2up|4up, __zoom, __kbps; Save… remembers the settings and writes the files, Done only remembers them); with settings = document.exportForWeb, written to path, else a picked file (the web downloads the files) → {path, files, bytes}"), + ("file.saveForWeb.browser", "Preview in Browser", "", "{…document.exportForWeb settings} write the HTML page and its images to a temporary folder and open the page in the default browser (desktop) → {path}"), + ("file.exportSelection", "Export Selection…", "", "{multiple?: false} collect the selected objects as assets (assets.add: one of them all, as one image; true: one per object) and open Export for Screens on its Assets tab with them checked → {assets: [asset id…]}; document.exportSelection exports the selection in one go instead"), + ("css.copy", "Copy Selected Style", "", "{scope?: selection|all (default selection), …css.selection options} copy the CSS of the selection (css.selection; all: css.generate) to the clipboard (CSS Properties panel) → {css, rules: n}"), + ("css.exportFile", "Export CSS…", "", "{path?, scope?: selection|all (default selection), …css.selection options} css.export written to path, else a picked file (the web downloads it), with the pictures of rasterized art next to it (CSS Properties panel: Export Selected CSS…, Export All…) → {path, rules, images: [path…], warnings (css.export's; the status bar shows the first)}"), + ("file.print", "Print…", "Cmd+P", "{} open the Print dialog (dialog `print`: the print.setup settings, preset (setting it loads that print preset; Save Preset… saves them as one), printer, toFile; Print keeps the settings with the document and prints, Done (discard: true) only keeps them); with params {preset? (a print preset's name: its settings instead of the document's), settings? (over the document's or the preset's), printer? (print.printers name; \\\"\\\" the default), toFile?, path?, format?: pdf|postscript, level?, flattenerPreset? (as engine file.print)} print without it: the print-ready PDF (engine file.print) goes to the printer → {pages, printer, printed: true, warnings}; with toFile, a path or no printing here (no print service) it is saved as a PDF at path (else a picked file, the web downloads it) → {path, pages, printed: false, warnings}; a PostScript file (format postscript, or a .ps path) is always saved that way"), + ("print.printers", "Printers", "", "{} → {printers: [{name, default}] (the system's printers), service (printing is available; else Print saves a PDF), setup (print.printerSetup can open the printer settings)}"), + ("print.printerSetup", "Printer Setup…", "", "{printer?} open the system's settings of the printer (the Print dialog's Setup…; desktop)"), + ("ui.printPresetsDialog", "Print Presets…", "", "{selected?: preset name} open Edit → Print Presets (dialog `printPresets`, field `selected`): [Default] (protected) and the saved presets, with how the selected one differs from [Default]. New… and Edit… open the preset editor (ui.printPresetDialog); Delete, Import… and Export… run print.presets.delete / import / export"), + ("ui.printPresetDialog", "Print Preset", "", "{name?: a saved preset to edit | preset?: the preset a new one starts from (default [Default])} open the preset editor (dialog `printPreset`: the Print dialog's settings fields plus `name`); OK runs print.presets.save and returns to Print Presets"), + ("plugin.dialog", "Plug-in…", "", "{id: an object filter plug-in (plugin.list), params?: {…}} Object › Plug-ins: run the filter at once when it takes no parameters or params are given (plugin.run), else open its dialog (dialog `plugin`: a field per parameter, `__plugin` the id, live preview; OK runs plugin.run as one undo step)"), + ("ui.installPlugin", "Install Plug-in…", "", "{path?: a .wasm file | dataBase64?: the module, name?: the file name shown as its source (with dataBase64), replace?: bool (true: the user agreed it takes the place of the plug-in with its id, as plugin.install), hash?: the module's SHA-256 (as plugin.install)} install a WebAssembly plug-in (plugin.install); without a path pick one (the web opens the browser's file picker; File › Open of a .wasm installs it too). A plug-in that lists permissions the user hasn't granted it asks the user first, showing its name, id, version, file SHA-256 and what each permission lets it do, so does a filter that asks for a heavy budget, and so does one without replace that would take the place of another module with its id and get its settings and cache (needsReplace in plugin.inspect), showing its id and file SHA-256: OK installs it if the file still has the SHA-256 shown, Cancel installs nothing. Only a person can press OK when it asks about permissions or a heavy budget: ui.dialog.confirm, answer_dialog and synthetic input are refused → plugin.install's result | {pending: \\\"pluginInstall\\\"} while it asks about permissions or a heavy budget | {pending: \\\"confirm\\\"} while it asks only about replacing"), + ("plugin.cancelRuns", "Cancel Plug-in Run", "", "{} stop every heavy filter running in the background (its progress window's Cancel): it stops at the next slice of its budget and changes nothing → {cancelled: n}"), + ("plugin.hideRuns", "Continue in Background", "", "{} close the progress window of the heavy filters running in the background; they go on, shown in the status bar → {hidden: n}"), + ("imageTrace.cancelRuns", "Cancel Image Trace", "", "{} stop every Image Trace running in the background (its progress window's Cancel): it stops at its next colour and changes nothing → {cancelled: n}"), + ("imageTrace.hideRuns", "Continue in Background", "", "{} close the progress window of the Image Traces running in the background; they go on, shown in the status bar → {hidden: n}"), + ("ui.runScript", "Other Script…", "Cmd+F12", "{path? | source?, name?} run a .js or .jsx file (script.run), after the warning unless Preferences › General › Show External Script Warning is off (dialog `scriptRun`: `name`, `script`, `dontShow`); without a file pick one (the web opens the browser's file picker)"), + ("ui.checkScript", "Check Script…", "", "{path? | data? | dataBase64?, name?, includes?: true with a path} check a script written for the reference app without running it (script.check; with a path, the files its #include lines name are checked too) and show the report (dialog `scriptCheck`: `report`, script.check's result; Copy Report copies it as JSON); without a file pick one (the web opens the browser's file picker; File › Open of a .jsx, .jsxinc or .jsxbin file shows the report too)"), + ("ui.perspectiveGridDialog", "Define Perspective Grid…", "", "{} open View › Perspective Grid › Define Grid (dialog `perspectiveGrid`, prefilled from the grid: the fields of perspective.grid.define); OK runs perspective.grid.define"), + ("ui.perspectivePresetsDialog", "Perspective Grid Presets…", "", "{selected?} open Edit › Perspective Grid Presets (dialog `perspectiveGridPresets`, field `selected`): New… and Edit… open the preset editor (dialog `perspectiveGrid` with `__mode` edit), whose OK runs perspective.presets.save and comes back; Delete, Import… and Export… run perspective.presets.*"), + ("ui.savePerspectivePreset", "Save Grid as Preset…", "", "{} open View › Perspective Grid › Save Grid as Preset (dialog `perspectiveGrid` with `__mode` save, `name` a new preset name): OK runs perspective.presets.save with the fields"), + ("ui.perspectiveUserPreset1.1", "One Point Perspective Preset 1", "", "{} apply the 1. saved 1-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset1.2", "One Point Perspective Preset 2", "", "{} apply the 2. saved 1-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset1.3", "One Point Perspective Preset 3", "", "{} apply the 3. saved 1-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset1.4", "One Point Perspective Preset 4", "", "{} apply the 4. saved 1-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset1.5", "One Point Perspective Preset 5", "", "{} apply the 5. saved 1-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset2.1", "Two Point Perspective Preset 1", "", "{} apply the 1. saved 2-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset2.2", "Two Point Perspective Preset 2", "", "{} apply the 2. saved 2-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset2.3", "Two Point Perspective Preset 3", "", "{} apply the 3. saved 2-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset2.4", "Two Point Perspective Preset 4", "", "{} apply the 4. saved 2-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset2.5", "Two Point Perspective Preset 5", "", "{} apply the 5. saved 2-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset3.1", "Three Point Perspective Preset 1", "", "{} apply the 1. saved 3-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset3.2", "Three Point Perspective Preset 2", "", "{} apply the 2. saved 3-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset3.3", "Three Point Perspective Preset 3", "", "{} apply the 3. saved 3-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset3.4", "Three Point Perspective Preset 4", "", "{} apply the 4. saved 3-point perspective grid preset (perspective.grid.preset)"), + ("ui.perspectiveUserPreset3.5", "Three Point Perspective Preset 5", "", "{} apply the 5. saved 3-point perspective grid preset (perspective.grid.preset)"), + ("ui.blendOptions", "Blend Options…", "", "{} open Blend Options (Object › Blend › Blend Options…, the Blend tool's double-click, Alt-click and toolbar button; dialog `blendOptions`: spacing smooth|steps|distance, steps, distance (pt), orientation page|path, preview) on the selected blend's options, previewed live: OK runs object.blend.options as one undo step; with no blend selected it sets what new blends start with"), + ("ui.livePaintGapOptions", "Gap Options…", "", "{} open Gap Options (Object › Live Paint › Gap Options…; dialog `livePaintGapOptions`: detect, size small|medium|large|custom, custom (pt), color (#rrggbb or a layer color's name), closeGaps, preview) on the selected Live Paint groups, filled in from the first; with preview on, the options apply live and the gaps found draw on the canvas in the preview color: OK runs livePaint.gapOptions as one undo step; a color with an alpha part (#rrggbbaa or #rgba) is an error and OK changes nothing, and with preview on a #rrggbbaa color is not previewed: the groups keep their own color and the gaps draw in black"), + ("ui.perspectivePlane", "Perspective Plane Options…", "", "{plane?: left|right|ground (default: the active plane)} open the plane's options, as double-clicking its plane widget does (dialog `perspectivePlane`: location (pt along the plane's normal), objects: none|move|copy): OK runs perspective.plane.move"), +]; + diff --git a/docs/gaps.md b/docs/gaps.md index 19376c4bf..07308c801 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -695,6 +695,7 @@ macOS cursor delivery (#2292) is narrowed to a native host adapter using the exi - **Estimate:** 4–8 h. - **Done (2026-10-16):** the MCP server hears the client during a long call: `notifications/cancelled` stops a queued or running export (PDF pages, flattening, baking, drawing, artboards) without writing anything or changing the document, `ping` and the list methods answer at once, any other request that would wait behind a call running for two seconds or more is refused as busy (`-32003`, naming the call), and `_meta.progressToken` gets page progress (#2165, #2171, #2172). Reading the document during an export is not served (refused as busy); estimate unchanged. - **Exposed in MCP:** `add_text {vertical: true}` uses the existing vertical point, area and path type commands. Horizontal writing remains the default; the broader automation gap and estimate are unchanged. +- **Done (#6460):** headless and remote `command_list` list desktop `UI_COMMANDS` (`ui: true`); UI-only runs still need `--connect`; effect ids are not command ids; 33 tools; eight prompts (spelling, variables, path-tools added); `vectorcraft://commands/reference` in the rebuilt CLI; prefer a checkout-built CLI when a release binary lags. Estimate unchanged. ## G15 Performance budgets and a GPU renderer @@ -852,6 +853,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv | Date | Change | Summary | |---|---|---| +| 2026-11-10 | minor | G6: MCP catalogue (#6460): headless lists `UI_COMMANDS` (`ui: true`), eight prompts, effect vs command ids, 33 tools, commands/reference; estimate unchanged. | | 2026-11-11 | minor | G5: built-in Status Effects graphic style library (Poison Tint, Freeze Edge, Burn Aura, Stun Pulse, Stealth Fade, Shield Buff); Missing trimmed; estimate 7–17 → 6–16 h; MCP docs list UI Frames and Map Markers under Symbol libraries. | | 2026-11-11 | minor | G5: built-in Inventory Icons symbol library (Sword, Shield, Potion, Key, Gem; no default 9-slice); Missing trimmed; estimate 8–18 → 7–17 h. | | 2026-11-10 | minor | G4 type: Edit › Edit Custom Dictionary… adds, deletes and changes the custom dictionary's words, #4936; the Check Spelling dialog remains; scores and estimates unchanged. | diff --git a/docs/mcp.md b/docs/mcp.md index 7d66c7c0b..28a04d449 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -14,6 +14,17 @@ It has two backends: With no flag, the server tries `127.0.0.1:7979` and falls back to headless. Logs go to stderr; stdout carries only protocol messages. +### Command catalogue (headless vs remote) + +`command_list` / `list_commands` / `vectorcraft://commands` return the live catalogue for the backend in use: + +- **Headless:** engine and plug-in commands, plus the desktop menu catalogue (`UI_COMMANDS`) marked `"ui": true` so agents can discover ids such as `view.fitArtboard`, `file.newDialog` and `window.*`. Running a UI-only command that the headless host does not implement returns an error that points at `--connect`. +- **Remote:** the same lists, with each UI entry's `enabled` taken from the live app. Run UI commands with `command_run` / `run_command` or `invoke_menu`. + +Effect ids from `effect.list` / `apply_effect` / `vectorcraft://effect/{id}` (for example `warp.arc`, `blur.gaussian`, `threeD.revolve`, `adjust.hueSaturation`, `addon.path.dashes`, `addon.color.halftone`) are not command ids. Apply one with `effect.apply` or the `apply_effect` tool; do not pass an effect id to `command_run`. Path Effects keep the `addon.path.*` effect ids; the Path Tools add-on's *commands* are `addon.pathtools.*`. + +A release `vectorcraft-cli` on `PATH` can lag the checkout. Prefer `cargo install --path apps/vectorcraft-cli --locked` and put `$CARGO_HOME/bin` first in the MCP client's environment, or set the client's `command` to that binary's absolute path (#6460). + ## Install and register From the repository root, with Rust 1.95+ and the platform's source-build prerequisites installed: @@ -195,7 +206,7 @@ dropped; the call reports uncertainty like any reply that never arrived whole. ### Prompts -`prompts/list` offers five workflows; `prompts/get` returns one templated user message with its +`prompts/list` offers eight workflows; `prompts/get` returns one templated user message with its arguments filled in. A missing required argument is `-32602`. | Prompt | Arguments (required first) | @@ -205,6 +216,9 @@ arguments filled in. A missing required argument is `-32602`. | `recolor` | `palette`, `method` | | `trace-and-style` | `path`, `preset`, `effect` | | `export-set` | `formats`, `directory`, `scale` | +| `check-spelling` | `scope`, `action` | +| `variables-bind` | `kind`, `source` | +| `path-tools` | `task` | ```sh echo '{"jsonrpc":"2.0","id":1,"method":"prompts/get","params":{"name":"poster","arguments":{"brief":"a jazz festival"}}}' \ @@ -374,11 +388,9 @@ The older names below remain listed because existing workflows use them. | Tool | Arguments and result | |---|---| -| `command_list` | Optional `filter` (or `query`), `menu` (a menu path substring, levels joined with ` > `), `enabled_only`, `brief` (no `params` text, `schema` or `inputSchema`); returns the command array. A command with a parameter schema lists `schema` and `inputSchema` (see [Parameter schemas](#parameter-schemas)). `list_commands` lists the same commands; it takes `enabledOnly` and returns `{count, commands}` | +| `command_list` | Optional `filter` (or `query`), `menu` (a menu path substring, levels joined with ` > `), `enabled_only`, `brief` (no `params` text, `schema` or `inputSchema`); returns the command array for the backend in use (see [Command catalogue](#command-catalogue-headless-vs-remote)). A command with a parameter schema lists `schema` and `inputSchema` (see [Parameter schemas](#parameter-schemas)). `list_commands` lists the same commands; it takes `enabledOnly` and returns `{count, commands}` | | `command_run` | `id`, optional `params`; runs the command through the existing backend. An unknown key in `params` is an error that names it and the nearest key the command takes (`invalid parameters for `file.new`: unknown parameter `widht`; did you mean `width`?`), so a misspelt key no longer runs the command with its default (see [Parameter keys](#parameter-keys)). `run_command` runs a command the same way and takes its id as `command` | | `command_batch` | `steps: [{id, params?}]` (at most 256), optional `stop_on_error` (default true), `atomic` and `label`; returns `completed`, `failed`, `results: [{ok, result\|error}]` and `created` (the ids the `draw_path` / `draw_shape` steps made, in order). A step's `id` is a command id, or `draw_path` / `draw_shape` with that tool's arguments as `params` (checked as `tools/call` checks them; the step's result is the tool's, so `"$N.id"` is the new object). Each edit has its own undo step, or with `atomic: true` the batch's edits are one undo step named `label` (else after its first edit; `edit.undoGroup.begin`/`end` underneath), and the edits made in a document that a step opens or creates (`document.open`, `file.new`) are one undo step there, named the same, also when no document was open before the batch. In a document that was open before the batch and that a step switches to (`document.activate`), each edit stays its own undo step; failures set `isError`. A param string `"$N.path"` or `"$last.path"` takes a value from an earlier step's result (see `run` below). Kept step results total at most 8 MiB; past that the batch stops with the reason. This is the MCP tool, not the engine command `command.batch` (that one takes `commands: [{command, params}]` and is what the Actions panel plays); use `command_run` with `command.batch` when replaying a recorded action | -| `command_list` | Optional `filter` (or `query`), `menu` (a menu path substring, levels joined with ` > `), `enabled_only`, `brief` (no `params` text, `schema` or `inputSchema`); returns the command array | -| `command_run` | `id`, optional `params`; runs the command through the existing backend. An unknown key in `params` is an error that names it and the nearest key the command takes (`invalid parameters for `file.new`: unknown parameter `widht`; did you mean `width`?`), so a misspelt key no longer runs the command with its default (see [Parameter keys](#parameter-keys)) | | `doc_inspect` | Optional `depth`, `childLimit`, `ids`, `kind`, `limit`, `offset`; same summary as `inspect_document` | | `render_preview` | Optional `max_side` (1–4096, default 1024), `artboard` (0-based, default 0) and `traceViews`; inline PNG of that artboard without editing it, drawn as `export` draws a PNG with `background: "white"` at the scale that fits `max_side` (at most 1). With `traceViews: true`, Image Trace objects draw in the view set with `imageTrace.setView` (outlines, the source image…), as in the app's window; by default they draw their tracing result. Artboards too large to render within the allocation bound return a tool error | | `ui_inspect`, `ui_screenshot` | Connected desktop state/window capture; tool errors in headless mode. `ui_inspect` is the same call as `inspect_ui`, and `ui_screenshot` is `screenshot` with `window: true`. `ui_screenshot` takes an optional `max_side` (1–4096), as `screenshot {window: true}` does: a capture whose longer side is larger is scaled down to `max_side` pixels on that side, and the reply's text gives the returned size as `width`/`height` and the capture's as `capturedWidth`/`capturedHeight`. Without `max_side` the full capture comes back | @@ -464,7 +476,7 @@ opens the picker through `run_command` with the remote backend. | `press_key` | `{key, mods?}` | Remote: a real key event. Headless: runs the command or tool bound to that shortcut, or sends the key to the busy tool (digits too: `5` while dragging with the Perspective Selection tool; `e` while dragging a Selection or Group Selection marquee; `c` and `f` while dragging out an arc; `f`, `v`, `x` and `c` while dragging out a rectangular or polar grid). | | `type_text` | `{text}` | Remote only. | | `answer_dialog` | `{set?: [{field, value?}], action?}` | Remote only. Sets fields of the app's open dialog in the order given with one `ui.dialog.set {fields, action?}` (a missing `value` is `null`). The app stores all of them or none, then answers that same dialog with `action` in the same request: `confirm` (OK) or `cancel`. Another connection cannot open a dialog between the two steps. When the app refuses the `ui.dialog.set`, for example because no dialog is open, the call is an error and the dialog is not confirmed or canceled. `action` alone is sent as `ui.dialog.confirm` or `ui.dialog.cancel`. Give `set`, `action` or both; `set` holds at most 256 fields. `inspect_ui` shows the open dialog and its fields as `ui.dialog`. Returns `{dialog, result}`: the dialog after the fields were set (or `null`) and the reply to `confirm` or `cancel` (or `null`). After `invoke_menu` `app.quit`, `file.close` or `file.closeAll` replies `{"pending": "saveChanges"}`, `{action: "confirm"}` saves, `{set: [{field: "discard", value: true}], action: "confirm"}` closes without saving and `{action: "cancel"}` cancels the close or quit. | -| `invoke_menu` | `{command, params?}` | Invokes a menu item by command id. Includes UI commands such as `view.*` and `window.*` in remote mode. | +| `invoke_menu` | `{command, params?}` | Invokes a menu item by command id. UI commands such as `view.*` and `window.*` are in `command_list` with `ui: true`; running them needs the desktop app (`--connect`). | | `open_panel` | `{panel}` | Remote only. `panel` is a panel id (`layers`, `swatches`, `colorGuide`, …, as `window.panel` takes) or its display label (`"Color Guide"`), in any case. | | `screenshot` | `{path?, scale?, artboard?, window?, max_side?, traceViews?}` | Returns MCP image content (`image/png`, base64) plus a text block. Renders the artboard with the pixels of a PNG `export` with `background: "white"` at the same scale; `traceViews:true` draws Image Trace objects in the view set with `imageTrace.setView`, as in the app's window; `window:true` captures the app window (remote only), as `ui_screenshot` does, and `max_side` (1–4096, window captures only) scales the returned image down as described for `ui_screenshot`. With `path` the backend (the headless session or the app) writes the PNG there too; a window capture written there keeps its full size with `max_side`. | | `open_file` | `{path, importer?, params?}` | Opens any readable file as a new active document: `.vectorcraft`/`.drawcraft`, `.vctemplate`, `.svg`/`.svgz`, `.pdf`/`.ai`, `.ait`, `.eps`/`.epsf`, `.ps`, `.dxf`, `.emf`, `.wmf`, PNG (`.png`/`.pns`), JPEG, GIF, WebP, TIFF, BMP (`.bmp`/`.dib`/`.rle`), Targa (`.tga`/`.vda`/`.icb`/`.vst`), PCX, Pixar (`.pxr`), JPEG 2000 (`.jpf`/`.jpx`/`.jp2`/`.j2k`/`.j2c`/`.jpc`), HEIC/HEIF (`.heic`/`.heif`), JPEG XL (`.jxl`), PSD (`.psd`/`.pdd`)/PSB (an image opens as a document of its pixel size; a Photoshop file as its merged image; a JPEG XL file as its first frame). Templates (`.vctemplate`, `.ait`) open as a new untitled document. PDF, `.ai` and SVG files saved with Preserve Editing reopen as the document they carry. The reply is `document.open`'s: its `warnings` say what didn't come in as it was (an EPS whose PostScript can't be read opens as its preview, and the warning names the PostScript error, the operator and the procedure). A file of an extension only an installed importer plug-in lists opens with that importer ([Plug-ins](#plug-ins)); `importer: "plugin."` opens the file with the importer it names, which must list the file's extension, also one a built-in format reads (in the app, as File › Open With does), and `params` gives that importer's parameters (their defaults when omitted). `run_command document.formats` lists the formats. | @@ -1419,8 +1431,9 @@ vectorcraft-cli run --cmd file.new --params '{"width":600,"height":200}' --cmd ### Parameter schemas -Some commands have a machine-readable parameter schema: at present `view.capsLock`, `view.saved.new`, -`view.saved.edit`, `view.saved.list`, `view.transparencyGrid`, `view.snapToGlyph.options` and `engine.apiReference`. The engine's +Some commands have a machine-readable parameter schema: at present a small set including `view.capsLock`, `view.saved.new`, +`view.saved.edit`, `view.saved.list`, `view.transparencyGrid`, `view.snapToGlyph.options` and `engine.apiReference` +(most of the catalogue still documents params in prose only; schemas grow command by command). The engine's `engine.commands` list gives such a command a `schema` field: an object schema in the shape of JSON Schema 2020-12 (https://json-schema.org/draft/2020-12). Its root has `$schema` (the 2020-12 dialect), `description` when the command has a summary, and `deprecated: true` when the command is deprecated. The schema uses `type`, `properties`, `required` and @@ -4570,7 +4583,7 @@ draws, and View › Snap to Grid snaps to its subdivisions. Changing either sets every document they read except a native file and the native document that an SVG, PDF, `.ai` or EPS file carries (`restored: true`), which keep the grid they were saved with. -`view.rotate {angle}` (desktop app) turns the view to `angle` degrees about the center of the canvas; a positive angle +`view.rotate {angle}` (desktop app, `ui: true`) turns the view to `angle` degrees about the center of the canvas; a positive angle turns the art clockwise on screen. View › Rotate View and the status bar's angle menu run it. It returns `{angle}`, the angle it set, normalized to (-180, 180], and `view.rotateReset` turns the view back to 0, returning `null` on success. @@ -4578,7 +4591,9 @@ Select the Rotate View Tool with `select_tool` and the tool id `rotateView`. Its drags turn the canvas about its center; Shift snaps to 15°. Document coordinates for a rotation gesture use the view at the initial down event, including when the gesture spans several calls. The control channel rejects malformed rotation packets without changing the angle and cancels -the current press. Start a new down event after an error. +the current press. Start a new down event after an error. Menu commands that only the desktop view +implements (`view.rotate`, `view.rotateReset`, `view.rotateToSelection`) are listed with `ui: true` +and need `--connect` when the headless host does not run them. Reset Rotate View is the command `view.rotateReset`: ```json @@ -4908,7 +4923,10 @@ settings and the next untitled name, without opening the dialog, and returns wha open document in tab order the current one, as a click on its tab does, and return `{index}`. After the last document comes the first, and before the first document comes the last. From the Home screen they go to the first and the last document. With no document open they fail with "no document open". `document.activate {index}` makes a given document -the current one. +the current one. `document.move {from?, to}` reorders open documents the way dragging a document tab along the tab strip +does: `from` defaults to the current document, `to` is the destination index, documents between them shift by one, the +current document stays current, and each document keeps its own view (#5663). It is an engine command, so headless and +remote `command_list` both list it. `window.arrange.tile` (Window › Arrange › Tile) shows every open document in its own pane, edge to edge in the document area, in a grid of as many columns as the square root of the number of documents, rounded up, and fits each document to its pane; with fewer than two documents open it leaves the view and `tiled` as they are. It returns `{tiled, panes}`. @@ -8460,10 +8478,11 @@ plain path from then on. ### Path Effects (live) Effect › Path Effects holds the add-on's live geometry effects, applied with `effect.apply {effect, params}` like any -other effect, with no shortcuts. Each draws, measures, exports and stacks as the other geometry effects do; the object's -own path stays as it is until Expand Appearance (`effect.expandAppearance`) writes the result. `effect.list` documents -every parameter. Out-of-range values are clamped, and an effect whose result would pass the geometry effects' budget -(2 million anchors) leaves the path as it is. +other effect, with no shortcuts. The ids below (`addon.path.*`) are **effect** ids, not command ids; the Path Tools +add-on's commands are `addon.pathtools.*` (see above). Each effect draws, measures, exports and stacks as the other +geometry effects do; the object's own path stays as it is until Expand Appearance (`effect.expandAppearance`) writes +the result. `effect.list` documents every parameter. Out-of-range values are clamped, and an effect whose result would +pass the geometry effects' budget (2 million anchors) leaves the path as it is. | Id | Menu item | Parameters | | --- | --- | --- | diff --git a/docs/roadmap.md b/docs/roadmap.md index 82362eb94..241e786f9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -52,6 +52,7 @@ M7 type: Touch Type moves a right-to-left character along its line and up from i | 2026-11-07 | minor | M4/M7: `.ai`/EPS type from the text document keeps auto leading at the paragraph's share, the paragraph's hyphenation options and may show more of the last lines the file sets in an area frame of its own, when growing the frame by its lines' deepest descent plus 0.05 pt shows them, and a line a soft return ends is justified as a wrapped line is (#6137); a user's `.ai` that opened from its PDF part now opens from its layers. Limits: frames grow only for area type of one frame, horizontal, by its lines' deepest descent plus 0.05 pt; type in a font that isn't installed, and hyphenation in a language that isn't imported, still lay out differently. Scores and estimates unchanged. | | 2026-11-08 | minor | Character Styles panel: a double-click on a user style's row opens its Character Style Options (`ui.charStyleOptions`); the dialog keeps a fixed width and scrolls its sections in a shorter window, so OK and Cancel stay on screen. Tests `panels::text_styles::tests::double_clicking_a_character_style_row_opens_its_options` and `dialogs::char_style_options::tests::the_sections_scroll_in_a_window_shorter_than_the_general_list`. Scores and estimates unchanged. | | 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. | +| 2026-11-10 | minor | M13 / G6: MCP catalogue (#6460): headless lists UI commands, eight prompts, 33 tools, effect vs command ids; scores and estimates unchanged. | | 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-11-02 | minor | Selection QoL: saved marquee modes, screen drag direction with a 3 px dead zone, and translated mode/E feedback (#5313); VectorCraft addition, scores and estimates unchanged. | | 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 07d626e5a..f8d2b557f 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -741,7 +741,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Open Recent Files under `--automation-read-root`: the control channel's `ui.menu.list` and `file.recentFiles` leave out the recent files outside the read roots, the other rows keep their ids, and `file.openRecentN` for one outside them answers `no such recent file`, as an empty slot does. `crates/ui-egui/src/tests/automation.rs`. - Scripts: `alert`, `confirm` and `prompt` open dialogs during a run, each with a Stop button that ends the run and puts every document back. In the app the run waits at the call for the person's answer; a run over MCP or the command line takes its answers in order from `script.run`'s `answers`, and the report lists each dialog with its answer. `crates/engine/src/tests/script_dialogs.rs`, `crates/ui-egui/src/tests/script_dialogs.rs`. - Parameter keys: the engine refuses a key that a command's params description doesn't name before the command runs, in every build and for every caller, including the app's own calls, the commands a command runs and the steps of `command.batch` and `action.batch`. An action recorded by an older version still plays from the Actions panel and the Batch dialog (`Session::replay`). `crates/engine/tests/it/param_keys.rs`. - - MCP server with 25 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, any command), attached to the running app or headless. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings. `--automation-read-root` / `--automation-write-root` (on `vectorcraft-cli mcp` and on `vectorcraft --control`) confine the files commands read and write to chosen folders, links followed, with the flags PhotoCraft takes (#832); each flag may be given more than once, a path inside any of its folders being allowed (#3546, `file_access::tests::several_roots_of_a_kind_each_grant_their_access`, `cli::mcp_automation_roots_take_several_folders`). A long export can be cancelled (`notifications/cancelled` stops it at the next page or group and writes nothing), reports page progress to a client that sent a progress token, and does not make other requests wait: engine-free ones answer at once, the rest are refused as busy after two seconds (#2165, #2171, #2172). + - MCP server with 33 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, undo/redo, UI inspect/screenshot aliases, any command), attached to the running app or headless. Headless and remote `command_list` include desktop `UI_COMMANDS` (`ui: true`); a UI-only id still needs `--connect` to run. Eight prompts (poster, icon-set, recolor, trace-and-style, export-set, check-spelling, variables-bind, path-tools). Effect ids are not command ids. Prefer a CLI built from the checkout when a release binary lags (#6460). `vectorcraft://commands/reference` serves the API reference. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings. `--automation-read-root` / `--automation-write-root` (on `vectorcraft-cli mcp` and on `vectorcraft --control`) confine the files commands read and write to chosen folders, links followed, with the flags PhotoCraft takes (#832); each flag may be given more than once, a path inside any of its folders being allowed (#3546, `file_access::tests::several_roots_of_a_kind_each_grant_their_access`, `cli::mcp_automation_roots_take_several_folders`). A long export can be cancelled (`notifications/cancelled` stops it at the next page or group and writes nothing), reports page progress to a client that sent a progress token, and does not make other requests wait: engine-free ones answer at once, the rest are refused as busy after two seconds (#2165, #2171, #2172). - MCP protocol: five prompts (`prompts/list` / `prompts/get`: poster, icon set, recolour, trace-and-style, export set), `completion/complete` for prompt arguments and template variables read from the live catalogues (formats, effect ids, command ids, trace presets, swatch and object ids), four resource templates (`vectorcraft://object/{id}`, `//command/{id}`, `//effect/{id}`, `//swatch/{name}`) so an agent reads one thing instead of the whole document, and `logging/setLevel` with `notifications/message`. `window.panel` ids are case-insensitive (display labels work too), `vectorcraft://object/{id}` reads through `document.node {summary: true}` (the same reply bytes from one lookup instead of summarizing the whole document: a leaf read on a 2,000-object document drops from ~20 ms to ~0.06 ms), large containers slice with `document.node {depth?, childLimit?}` (truncated levels report `childCount`), `document.inspect` and the `inspect_document` tool slice the same way so a depth-0 read is the layer skeleton (~9.7 KB instead of ~2 MB on a 2,000-object document), `document.find` locates nodes by name/kind/text with ancestor paths, and the CLI sweeps the protocol surface over stdio against the compiled binary. `docs/mcp.md` has a Protocol section covering the capability table and what is deliberately absent (subscriptions, progress, cancellation, sampling, elicitation, Streamable HTTP — all of which need the transport to interleave reads). - Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`). A `run` step, like a `command_batch` step over MCP, can use an earlier step's result (`"$1.id"`, #785); `run --commands FILE` (or `-` for standard input) reads the steps from a JSON array or JSON Lines, past the Windows command-line limit (#3147, `apps/vectorcraft-cli/tests/it/cli.rs` `run_reads_*`, `run_refuses_malformed_commands_naming_the_step`). - File › Scripts › Check Script… reads a script written for Illustrator (`.jsx`, `.jsxinc` or a compiled `.jsxbin`) without running any of it and reports what it needs (the document model, files, dialogs, menu commands, features only Illustrator has), what it would do outside the document, the menu commands and document-model members it uses, its directives and included files, and the problems that would stop it loading (`script.check`, which also reads `.js` files); File › Open and drag-and-drop show the report for `.jsx`, `.jsxinc` and `.jsxbin` files. @@ -1973,6 +1973,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s | Date | Change | Summary | |---|---|---| +| 2026-11-10 | minor | Automation: MCP tool count 33; headless `command_list` lists `UI_COMMANDS` (`ui: true`); eight prompts; effect-id vs command-id clarified (#6460); scores and hours unchanged. | | 2026-11-11 | minor | Bundled content: built-in Status Effects graphic style library; style libraries evidence updated; ~40% → ~42%; MCP Symbol libraries list UI Frames and Map Markers; more built-in graphic-style libraries remain. | | 2026-11-11 | minor | Bundled content: built-in Inventory Icons symbol library (Sword, Shield, Potion, Key, Gem); symbol libraries evidence updated; ~38% → ~40%; more built-in symbol libraries remain. | | 2026-11-10 | minor | Type advanced: the Edit Custom Dictionary dialog, #4936; evidence added; the Check Spelling dialog remains; scores and hours unchanged. |