diff --git a/crates/mcp/src/ui_catalog.rs b/crates/mcp/src/ui_catalog.rs index 9e4237622f..dfda5f0cae 100644 --- a/crates/mcp/src/ui_catalog.rs +++ b/crates/mcp/src/ui_catalog.rs @@ -1377,13 +1377,13 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "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, no action and no recording, or a stop that would give the action more than 10,000 steps or its set's file more than 8 MiB 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}", + "{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, no action and no recording, or a stop that would give the action more than 10,000 steps or its set's file more than 8 MiB, counting the steps that the recording in progress adds to the set, 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.insertMenuItem", "Insert Menu Item", "", - "{set?: index, action?: index, step?: index, command, params?: object} insert the command of a menu item as a step, without running it; the step plays as choosing the item runs it. command and params (default {}) must be those of a menu item that can be a step: one that runs an engine command, whose label does not end with \"…\", outside the VectorCraft, File, Window and Help menus, and that is not Undo, Redo, a Paste, a saved selection, saved view or recent font item, or a plug-in's item. The place is as ui.action.insertStop takes it: with set and action, after step, else after the action's last step; without them, the recording in progress. The new step becomes the panel's selected step, and its action and set become the selected ones. A command and params that no such menu item has, a param of another type, params that are not an object, no action and no recording, or a step that would give the action more than 10,000 steps or its set's file more than 8 MiB is an error and changes nothing. The panel menu's Insert Menu Item… (dialog `actionInsertMenuItem` with find, command, params, set, action and step) runs this on OK → {set, action, step}, or {recording: true}", + "{set?: index, action?: index, step?: index, command, params?: object} insert the command of a menu item as a step, without running it; the step plays as choosing the item runs it. command and params (default {}) must be those of a menu item that can be a step: one that runs an engine command, whose label does not end with \"…\", outside the VectorCraft, File, Window and Help menus, and that is not Undo, Redo, a Paste, a batch (command.batch), a preference (prefs.*) or action (action.*) command, an Object › Align item, Image Trace's Make or Make and Expand, Pattern's Make or Edit Pattern, a saved selection, saved view or recent font item, or a plug-in's item. The place is as ui.action.insertStop takes it: with set and action, after step, else after the action's last step; without them, the recording in progress. The new step becomes the panel's selected step, and its action and set become the selected ones. A command and params that no such menu item has, a param of another type, params that are not an object, no action and no recording, or a step that would give the action more than 10,000 steps or its set's file more than 8 MiB, counting the steps that the recording in progress adds to the set, is an error and changes nothing. The panel menu's Insert Menu Item… (dialog `actionInsertMenuItem` with find, command, params, set, action and step) runs this on OK → {set, action, step}, or {recording: true}", ), ( "ui.action.continue", diff --git a/crates/ui-egui/src/dialogs/action_insert_menu_item.rs b/crates/ui-egui/src/dialogs/action_insert_menu_item.rs index 77dafac834..2cf0ab6127 100644 --- a/crates/ui-egui/src/dialogs/action_insert_menu_item.rs +++ b/crates/ui-egui/src/dialogs/action_insert_menu_item.rs @@ -208,8 +208,9 @@ mod tests { } /// The list holds the menu items that run an engine command without asking for values first, - /// outside the application, File, Window and Help menus, and leaves out Undo, Redo, the Pastes - /// and the saved-selection, saved-view and recent-font items. + /// outside the application, File, Window and Help menus, and leaves out Undo, Redo, the Pastes, + /// Object › Align, Image Trace's Make and Make and Expand, Pattern's Make and Edit Pattern and the + /// saved-selection, saved-view and recent-font items. #[test] fn the_list_holds_only_menu_items_that_can_be_steps() { let items = menus::insertable_items(true); @@ -224,9 +225,21 @@ mod tests { for command in ["object.group", "object.ungroup", "object.arrange.bringToFront"] { assert!(has(command), "{command}"); } - for command in - ["edit.undo", "edit.redo", "edit.paste", "edit.pasteInFront", "object.move", "object.align", "file.save", "view.zoomIn", "select.recall1"] - { + for command in [ + "edit.undo", + "edit.redo", + "edit.paste", + "edit.pasteInFront", + "object.move", + "object.align", + "imageTrace.make", + "imageTrace.makeAndExpand", + "object.pattern.make", + "object.pattern.edit", + "file.save", + "view.zoomIn", + "select.recall1", + ] { assert!(!has(command), "{command}"); } } diff --git a/crates/ui-egui/src/menus/entries.rs b/crates/ui-egui/src/menus/entries.rs index e2f564b1de..42ad7b6555 100644 --- a/crates/ui-egui/src/menus/entries.rs +++ b/crates/ui-egui/src/menus/entries.rs @@ -144,19 +144,27 @@ pub struct InsertableItem { /// files), Window (panels and workspaces) and Help. const NOT_INSERTED_MENUS: [&str; 4] = ["VectorCraft", "File", "Window", "Help"]; +/// Commands whose menu items do more than run the command, which action playback does not do: +/// Object › Image Trace › Make and Make and Expand trace off the UI thread with progress and Cancel +/// ([`crate::trace_runs::start`]), and Object › Pattern › Make and Edit Pattern open the Pattern +/// Options panel. +const MENU_ONLY_COMMANDS: [&str; 4] = ["imageTrace.make", "imageTrace.makeAndExpand", "object.pattern.make", "object.pattern.edit"]; + /// Whether the item labeled `label` in menu `title`, which runs command `id` with `p`, can be a step /// of an action. It can when it runs an engine command, which action playback runs as the menu /// does; its label does not end with "…", the mark of an item that asks for values first; it is not /// in a menu of [`NOT_INSERTED_MENUS`]; and it is not Undo, Redo, a Paste, an item that stands for a /// saved selection, a saved view or a recent font, a plug-in's item or an action's own command. Nor /// is it Object › Align, whose menu items add the Align To choice and the selection's key to their -/// params when clicked ([`crate::panels::align::with_align_to`]), which playback does not. +/// params when clicked ([`crate::panels::align::with_align_to`]), which playback does not, or another +/// item of [`MENU_ONLY_COMMANDS`]. pub fn insertable(title: &str, label: &str, id: &str, p: &Value) -> bool { use super::slots::{RECENT_FONT_IDS, SAVED_SELECTION_IDS, SAVED_VIEW_IDS}; vectorcraft_engine::find_command(id).is_some() && !label.ends_with('…') && !NOT_INSERTED_MENUS.contains(&title) && !matches!(id, "edit.undo" | "edit.redo" | "command.batch" | "object.align") + && !MENU_ONLY_COMMANDS.contains(&id) && !id.starts_with("edit.paste") && !id.starts_with("prefs.") && !id.starts_with("action.") diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 7d80e762e6..7f16be5e70 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -1378,13 +1378,13 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ crate::panels::actions::INSERT_STOP, "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, no action and no recording, or a stop that would give the action more than 10,000 steps or its set's file more than 8 MiB 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}", + "{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, no action and no recording, or a stop that would give the action more than 10,000 steps or its set's file more than 8 MiB, counting the steps that the recording in progress adds to the set, 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}", ), ( crate::panels::actions::INSERT_MENU_ITEM, "Insert Menu Item", "", - "{set?: index, action?: index, step?: index, command, params?: object} insert the command of a menu item as a step, without running it; the step plays as choosing the item runs it. command and params (default {}) must be those of a menu item that can be a step: one that runs an engine command, whose label does not end with \"…\", outside the VectorCraft, File, Window and Help menus, and that is not Undo, Redo, a Paste, a saved selection, saved view or recent font item, or a plug-in's item. The place is as ui.action.insertStop takes it: with set and action, after step, else after the action's last step; without them, the recording in progress. The new step becomes the panel's selected step, and its action and set become the selected ones. A command and params that no such menu item has, a param of another type, params that are not an object, no action and no recording, or a step that would give the action more than 10,000 steps or its set's file more than 8 MiB is an error and changes nothing. The panel menu's Insert Menu Item… (dialog `actionInsertMenuItem` with find, command, params, set, action and step) runs this on OK → {set, action, step}, or {recording: true}", + "{set?: index, action?: index, step?: index, command, params?: object} insert the command of a menu item as a step, without running it; the step plays as choosing the item runs it. command and params (default {}) must be those of a menu item that can be a step: one that runs an engine command, whose label does not end with \"…\", outside the VectorCraft, File, Window and Help menus, and that is not Undo, Redo, a Paste, a batch (command.batch), a preference (prefs.*) or action (action.*) command, an Object › Align item, Image Trace's Make or Make and Expand, Pattern's Make or Edit Pattern, a saved selection, saved view or recent font item, or a plug-in's item. The place is as ui.action.insertStop takes it: with set and action, after step, else after the action's last step; without them, the recording in progress. The new step becomes the panel's selected step, and its action and set become the selected ones. A command and params that no such menu item has, a param of another type, params that are not an object, no action and no recording, or a step that would give the action more than 10,000 steps or its set's file more than 8 MiB, counting the steps that the recording in progress adds to the set, is an error and changes nothing. The panel menu's Insert Menu Item… (dialog `actionInsertMenuItem` with find, command, params, set, action and step) runs this on OK → {set, action, step}, or {recording: true}", ), ( crate::panels::actions::CONTINUE_PLAYBACK, diff --git a/crates/ui-egui/src/panels/actions.rs b/crates/ui-egui/src/panels/actions.rs index 28f53fdf2c..de6b36bc6e 100644 --- a/crates/ui-egui/src/panels/actions.rs +++ b/crates/ui-egui/src/panels/actions.rs @@ -937,7 +937,33 @@ fn recording_room(app: &VectorcraftApp, step: &(String, Value)) -> Result<(), St Ok(()) } -/// Puts `step` where params `p` say ([`insert_stop`]) → what [`insert_stop`] returns. +/// What Stop Playing/Recording adds to a set ([`recorded_into`]). +struct Recorded { + /// The set's action that the recording records into; None for the new action that the + /// recording adds. + into: Option, + /// The new action's name. + name: String, + /// The steps recorded so far. + steps: Vec<(String, Value)>, +} + +/// What Stop Playing/Recording adds to set `set`; None when the recording in progress adds nothing +/// to it. +fn recorded_into(app: &VectorcraftApp, set: usize) -> Option { + let (recording_set, name, start) = app.ui.recording.as_ref()?; + let into = match app.ui.recording_into { + Some(id) => Some(app.ui.action_sets.get(set)?.actions.iter().position(|a| a.id == id)?), + None if *recording_set == set => None, + None => return None, + }; + let steps = app.session.journal_for_action(app.session.journal.held().unwrap_or(*start)); + Some(Recorded { into, name: name.clone(), steps }) +} + +/// Puts `step` where params `p` say ([`insert_stop`]) → what [`insert_stop`] returns. When the +/// recording in progress adds steps to the set, the limits count them as Stop Playing/Recording +/// would leave the set. fn insert(app: &mut VectorcraftApp, p: &Value, step: (String, Value)) -> Result { let index = |k: &str| p.get(k).and_then(Value::as_u64).and_then(|i| usize::try_from(i).ok()); if p.get("set").is_none() && p.get("action").is_none() && p.get("step").is_none() { @@ -949,19 +975,35 @@ fn insert(app: &mut VectorcraftApp, p: &Value, step: (String, Value)) -> Result< return Ok(json!({"recording": true})); } let (set, at) = index("set").zip(index("action")).ok_or("no such action")?; + let recorded = recorded_into(app, set); + let pending = match &recorded { + Some(r) if r.into == Some(at) => r.steps.len(), + _ => 0, + }; let s = app.ui.action_sets.get_mut(set).filter(|s| at < s.actions.len()).ok_or("no such action")?; let action = &mut s.actions[at]; let after = match p.get("step") { None => action.steps.len(), Some(_) => index("step").filter(|&s| s < action.steps.len()).ok_or("no such action step")? + 1, }; - if action.steps.len() >= files::MAX_ACTION_STEPS { + if action.steps.len() + pending >= files::MAX_ACTION_STEPS { return Err("the action already has 10,000 steps".into()); } let excluded = action.excluded_steps.clone(); action.insert_step(after, step); let id = action.id; - if files::write_set(s).len() > files::MAX_SET_FILE { + let file_len = match recorded { + None => files::write_set(s).len(), + Some(Recorded { into, name, steps }) => { + let mut kept = s.clone(); + match into { + Some(into) => kept.actions[into].steps.extend(steps), + None => kept.actions.push(Action::new(name, steps)), + } + files::write_set(&kept).len() + } + }; + if file_len > files::MAX_SET_FILE { let action = &mut s.actions[at]; action.steps.remove(after); action.excluded_steps = excluded; @@ -4116,6 +4158,12 @@ mod tests { (place(json!({"command": "edit.paste"})), none), // Align, whose menu items add the Align To choice when clicked. (place(json!({"command": "object.align", "params": {"horizontal": "left"}})), none), + // Image Trace, whose menu items trace off the UI thread with progress and Cancel. + (place(json!({"command": "imageTrace.make", "params": {"preset": "Default"}})), none), + (place(json!({"command": "imageTrace.makeAndExpand", "params": {"preset": "Default"}})), none), + // Pattern's Make and Edit Pattern, whose menu items open the Pattern Options panel. + (place(json!({"command": "object.pattern.make"})), none), + (place(json!({"command": "object.pattern.edit"})), none), // Params that no menu item gives its command. (place(json!({"command": "type.changeCase"})), none), (place(json!({"command": "type.changeCase", "params": {"case": "middle"}})), none), @@ -4225,6 +4273,53 @@ mod tests { assert!(files::write_set(&app.ui.action_sets[0]).len() <= files::MAX_SET_FILE); } + /// A step put by its set and action into the action that the recording in progress records into + /// counts the steps recorded so far, which Stop Playing/Recording adds to that action. + #[test] + fn steps_inserted_into_the_action_being_recorded_into_count_the_recorded_steps() { + let (mut app, _) = app_with_three_moves(); + app.ui.action_sets[0].actions.push(Action::new("Other", vec![])); + let into = app.ui.action_sets[0].actions[0].id; + let stored = app.ui.action_sets[0].actions[0].steps.len(); + app.run(START_RECORDING, json!({})).unwrap(); + app.ui.recording_into = Some(into); + for _ in stored..files::MAX_ACTION_STEPS { + app.session.journal.push(("object.group".to_string(), json!({}))); + } + let steps = "the action already has 10,000 steps"; + assert_eq!(app.run(INSERT_MENU_ITEM, json!({"set": 0, "action": 0, "command": "object.group"})).unwrap_err(), steps); + assert_eq!(app.run(INSERT_STOP, json!({"set": 0, "action": 0})).unwrap_err(), steps); + assert_eq!(app.ui.action_sets[0].actions[0].steps.len(), stored, "nothing was inserted"); + assert!(app.run(INSERT_MENU_ITEM, json!({"set": 0, "action": 1, "command": "object.group"})).is_ok(), "another action takes the step"); + app.run(STOP_RECORDING, json!({})).unwrap(); + let saved = files::write_set(&app.ui.action_sets[0]); + assert_eq!(files::read_set(&saved, "Test").unwrap().actions[0].steps.len(), files::MAX_ACTION_STEPS, "and loads again"); + + let (mut app, _) = app_with_three_moves(); + let pad = |n: usize| ("object.move".to_string(), json!({"dx": 1, "dy": 0, "pad": "x".repeat(n)})); + let into = app.ui.action_sets[0].actions[0].id; + let room = files::MAX_SET_FILE - files::write_set(&app.ui.action_sets[0]).len(); + app.run(START_RECORDING, json!({})).unwrap(); + app.ui.recording_into = Some(into); + app.session.journal.push(pad(room - 8)); + let size = "the action set would be larger than 8 MiB"; + assert_eq!(app.run(INSERT_MENU_ITEM, json!({"set": 0, "action": 0, "command": "object.group"})).unwrap_err(), size); + assert_eq!(app.run(INSERT_STOP, json!({"set": 0, "action": 0})).unwrap_err(), size); + // Another action of the set counts them too, for a recording into an action and for a + // recording that adds a new action. + for into_first in [true, false] { + let (mut app, _) = app_with_three_moves(); + app.ui.action_sets[0].actions.push(Action::new("Other", vec![])); + let first = app.ui.action_sets[0].actions[0].id; + app.run(START_RECORDING, json!({})).unwrap(); + app.ui.recording_into = into_first.then_some(first); + app.session.journal.push(pad(room - 8)); + let r = app.run(INSERT_MENU_ITEM, json!({"set": 0, "action": 1, "command": "object.group"})); + assert_eq!(r.unwrap_err(), size, "recording into the first action: {into_first}"); + assert!(app.ui.action_sets[0].actions[1].steps.is_empty(), "recording into the first action: {into_first}"); + } + } + /// A step inserted from the menu saves and loads as the command and params it holds, with its /// place and exclusion. #[test] diff --git a/docs/mcp.md b/docs/mcp.md index 76589bbc60..06cb402d81 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2626,18 +2626,22 @@ already walks Image Trace. Short agent entry points: VectorCraft's own rule. 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, no action and no recording, or a stop that would give the action more than 10,000 steps or its set's file - more than 8 MiB is an error, and then nothing changes. The panel menu's Insert Stop… opens the `actionStopRecord` dialog + more than 8 MiB is an error, and then nothing changes. The limits count the steps that the recording in progress + adds to the set. The panel menu's Insert Stop… opens the `actionStopRecord` dialog (`message`, `allowContinue`, `set`, `action`, `step`), whose OK runs this. - Actions: `ui.action.insertMenuItem {set?, action?, step?, command, params?}` inserts a menu item's command as a step without running it. `command` and `params` (default: `{}`) must be those of an item in the menus that can be a step: an item that runs a document command without opening a dialog first, outside the File, Window and Help menus, and - not Undo, Redo, a Paste command, Object › Align (its items add the Align To choice when clicked), a preference + not Undo, Redo, a Paste command, Object › Align (its items add the Align To choice when clicked), Object › Image + Trace › Make or Make and Expand (the menu traces off the UI thread, with progress and Cancel), Object › Pattern › + Make or Edit Pattern (the menu also opens the Pattern Options panel), a preference toggle, an Actions command or a recent-font, saved-selection or saved-view slot. Disabled items are listed, and playback checks the step when it runs. With `set` and `action`, the step goes into that action, after step `step`, else after its last step, becomes the panel's selected step, and the command returns `{set, action, step}`. Without them it goes into the recording in progress and returns `{recording: true}`. A command no such item runs, params that are not an object, an index past the last set, action or step, no action and no recording, or a step that would give the action more than 10,000 steps or its set's file - more than 8 MiB is an error, and then nothing changes. The panel menu's Insert Menu Item… opens the + more than 8 MiB is an error, and then nothing changes. The limits count the steps that the recording in progress + adds to the set. The panel menu's Insert Menu Item… opens the `actionInsertMenuItem` dialog (`find`, `command`, `params`, `set`, `action`, `step`): typing in Find and pressing Find picks the one item whose menu path, shown name or command id holds every word typed, or lists the matches, and OK runs this. diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 8b2b77a249..e490d1c69a 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -711,7 +711,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Actions panel: expand an action to show its recorded steps, then clear a step's checkmark to keep it in the action but skip it during playback. The action and set checkmarks include or exclude all their steps; a mixed checkmark includes all. Alt/Option-clicking a step checkmark keeps that step as it was and applies the opposite inclusion state to the other steps in its action. This follows the public [Exclude commands from an action](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/exclude-commands-from-actions.html) guide; the scope of the mixed-state and Alt-click behavior is VectorCraft's design. The exclusion survives UI restart, action duplication, and `.vcactions` save/load. Existing version 1 action set files load with every step included. - Actions panel: the panel menu's Playback Options… sets how actions play: Accelerated (the default) plays every step at once, Step by Step plays one step at a time with the canvas drawn after each, and Pause For waits the given number of seconds between steps ([Specify playback speed](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/specify-playback-speed.html); `ui.action.playbackOptions`). VectorCraft's own rules, which the page does not cover: the pause is a whole number of seconds from 1 to 60, the setting is kept with the UI preferences, a paced playback ends at Stop Playing/Recording or when a step comes due while another document is active, each step of a paced playback is its own undo step, and Batch… plays at full speed. - Actions panel: the panel menu's Insert Stop… opens Record Stop, with a Message box and Allow Continue, and adds a stop to the recording in progress, after the selected command, or at the end of the selected action ([Insert stops in actions](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/insert-stops-in-actions.html); `ui.action.insertStop`). A playing action waits at the stop and shows the message so the user can do a manual task, and Play Current Selection plays the rest ([Illustrator CS5 help](https://help.adobe.com/archive/en_US/illustrator/cs5/illustrator_cs5_help.pdf#page=459); `ui.action.continue`). With Allow Continue, the message box also has Continue. VectorCraft's own rules, which the pages do not cover: an empty message still waits, Stop Playing/Recording ends the playback and keeps the steps already played, the rest plays on the document active then, a stop waits at every playback speed, at Accelerated the steps between two stops are one undo step, Batch… refuses an action with an included stop, and Delete Selection on a selected step deletes that step, a stop or a command, without asking (`ui.action.deleteStep`). - - Actions panel: the panel menu's Insert Menu Item… adds a menu command to the recording in progress, after the selected step, or at the end of the selected action, without running it, and playback runs it as the menu does ([Insert non-recordable tasks into actions](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/insert-non-recordable-tasks-into-actions.html); `ui.action.insertMenuItem`). The dialog's Find box picks the one item that matches the words typed, and a click on a listed item picks it. VectorCraft's own rules, which the page does not cover: items that open a dialog, the Object › Align items and the File, Window and Help menu items are not listed, and an action holds at most 10,000 steps and its set file at most 8 MiB, for stops too. + - Actions panel: the panel menu's Insert Menu Item… adds a menu command to the recording in progress, after the selected step, or at the end of the selected action, without running it, and playback runs it as the menu does ([Insert non-recordable tasks into actions](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/insert-non-recordable-tasks-into-actions.html); `ui.action.insertMenuItem`). The dialog's Find box picks the one item that matches the words typed, and a click on a listed item picks it. VectorCraft's own rules, which the page does not cover: items that open a dialog, the Object › Align items, Image Trace's Make and Make and Expand, Pattern's Make and Edit Pattern, and the File, Window and Help menu items are not listed, and an action holds at most 10,000 steps and its set file at most 8 MiB, for stops too. - Actions panel: Delete Selection deletes the selected action set with its actions and keeps the other sets ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.delete`). VectorCraft's own rules, which the overview does not cover: deleting a set asks first, Shift-click on a set's name selects a range of sets to delete together, the panel menu's Delete does the same, and Default Actions and the last set can be deleted. Undo does not bring a deleted set back. - Actions panel: the panel menu's Duplicate copies the selected action, else the current action set with all its actions ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.duplicate`). VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected. - Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used.