Skip to content

feat(commands): command macros: Save As Command, saved commands and Manage Saved Commands (M6) - #338

Open
tanish2k09 wants to merge 8 commits into
mainfrom
m6/command-macros
Open

tanish2k09 wants to merge 8 commits into
mainfrom
m6/command-macros

Conversation

@tanish2k09

@tanish2k09 tanish2k09 commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

What and why

Command macros (saved commands), the M6 roadmap task, built the way Animate does it: pick steps in the History panel, Save As Command, and they come back at the end of the Commands menu. They run on whatever is selected, as one undo step, and can have a keyboard shortcut. Design: ADR 0097.

  • History records each step's commands. Every undo step keeps the (id, params) of the top-level commands that made it: one for a plain edit, all of them for a gesture or a marquee cut plus its edit. Undo and redo carry them along, and a live preview's revert drops what it reverted. A drag's moves add up into one (element.translate is coalesced). Each step is capped at 4096 commands / 1 MiB, and that memory counts against the undo budget.
  • Save As Command (Ctrl+click picks steps, Shift+click picks a range; each row's context menu works too) shows the picked steps:
    • Params naming this document's layers or objects are dropped where the command's params doc says it then acts on the selection (or the active layer); the dialog shows "acts on the selection".
    • Anything else is refused, with the step named and the reason. For example: step 4 “Move Points” can't be kept: it works on particular objects of this document. Clipboard steps and plug-in runs are refused the same way.
  • A run is transactional. macro.run runs its steps nested. At the first failure it reports “Turn and Fade” stopped at step 3 of 3 (Taller row): … Nothing was changed. and the session restores the document, selection and playhead.
  • Policy: a macro may call exactly what a plug-in may (plugin_may_call) and never macro.*. Plug-ins may not call macro.* at all, so the two can't recurse into each other.
  • Files: Commands/<name>.json in the settings folder ({"format":"anicraft-command","version":1,"steps":[…]}), with the order kept in .order.json. They are small, shareable and read with bounds. anicraft-cli run scripts (bare arrays) are read too.
  • Commands: macro.run, macro.list, macro.history, macro.save, macro.rename, macro.move, macro.delete, macro.import. All of them work headless, from anicraft-cli run/exec, and through MCP anicraft_run. Replacing a file asks first; deleting always asks (ADR 0004).
  • Desktop:
    • The Commands menu lists saved commands with their keys. Run Command… runs a file.
    • Manage Saved Commands… has Run, ↑/↓ reorder, Shortcut…, Rename…, Delete… and Add from File…. Shortcut… opens the existing Keyboard Shortcuts editor on that command, already waiting for keys. A macro is a macro:<name> action under Commands, with the usual clash checks; renaming moves its keys and deleting drops them.
  • Also fixed in execute_nested (benefits plug-ins too):
    • It now refuses timeline commands inside an open group, as execute does (ADR 0073).
    • It finishes each nested command's motion-tween auto-key on its own, so a step in a macro keys a tween exactly as the command alone would. There is a test for this.

Not in this slice

  • Replay Steps without saving.
  • Editing a macro's steps or params in the app.
  • Macros that ask for a value when they run.
  • Saved commands in the browser build (it has no folder) and in the command palette.
  • Recording selection changes as steps.

How it was tested

  • cargo test --workspace: green, 1633 tests after rebasing on main (Windows 10). New tests:
    • Engine (macro_tests, 10):
      • Record and replay equal the original edits: a drag (30 moves recorded as 1), a turn and a fade are saved from History and run on a fresh selection in another document. The rectangle comes out identical, the unselected oval is untouched, there is one undo step, and the run itself can be saved again.
      • Rollback on failure: a failing step 3 of 3 leaves the document, state, history and selection unchanged and names the step.
      • Rewriting: a layer id becomes the active layer, an optional element id becomes the selection, and a required element id is refused with step 4 named.
      • Policy: macro and plug-in calls; clipboard steps are refused.
      • Folder handling: order, move, rename, replace and delete confirmations, import, run from a path, and junk files reported as problems.
      • Hostile files never panic and never change the document. Inputs include empty, BOM, 5000-deep nesting, 1001 steps, >1 MiB, wrong types, unknown, file.*, macro.* and plugin.* commands, control characters, every truncation and byte flip of a good file, and hostile params to every macro.* command.
      • Live preview reverts, the 4096-command cap, and a step inside a motion tween keying it exactly as the command alone does.
    • App (3): Ctrl+click picks → Save As Command → the list; a bound Ctrl+Alt+R runs it as one step; a rename moves the key; a delete asks first and drops the key. Plus a refused step named in the dialog, and keymap binding, clashes and keys kept before the folder is read.
    • CLI: run takes a saved command file and names the failing step.
  • panic_hunt_every_command covers the macro.* commands. It first took over 40 min because of macro.history over 10,000 fuzz steps plus find_command rebuilding the registry on every call. After the fix (perf(engine) commit: registry indexed once, history listing checks without copying) it takes 144 s in debug on this machine.
  • cargo clippy --all-targets -- -D warnings and cargo deny check licenses bans sources are clean. The pseudo-locale and narrow-dock tests pass, and the History footer wraps at 200 pt.

Screenshots

  • --open savecommand: three edits on the sample picked in History, and Save As Command listing them (1 Rotate 90° CW, 2 Colour Effect, 3 Move), with a name field and Save/Cancel.
  • --open managecommands: Manage Saved Commands with three commands. Each row has a step count, a shortcut (Ctrl+Alt+R on Quarter Turn), Run, chevron reorder buttons (hidden at the ends), Shortcut…, Rename… and Delete…, followed by Add from File… and the folder.
  • cargo run -- --screenshot shots/now.png: the default window is unchanged, except for the History panel's footer hint.

Summary by CodeRabbit

  • New Features
    • Save selected History steps as reusable commands, then run them together as one undoable step.
    • Manage saved commands by renaming, deleting, reordering, importing, and assigning shortcuts.
    • Run saved commands from the desktop app, CLI, or MCP tools. If a step fails, the document remains unchanged and the failed step is reported.
  • Documentation
    • Added a guide covering saved commands, sharing, shortcuts, and usage from command-line and MCP tools.

…acro.* commands (M6)

Every undo step keeps the commands it ran; Save As Command rewrites ids of
this document to act on the selection or active layer, or names the step it
can't keep. macro.run is one transactional undo step; macros call only what
a plug-in may, and plug-ins can't call macro.*. anicraft-cli run reads saved
command files. ADR 0097.
… without copying them (M6)

find_command rebuilt the whole registry (~360 specs) on every call, and
macro.history cloned every recorded command's params to list a step.
panic_hunt_every_command (which runs macro.history about 600 times per
session over up to 10,000 steps): over 40 min before, 144 s after (debug,
Windows, this machine). The CLI test's failing file now selects first.
@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Explain Change →

Note

CodeRabbit ASCII loading animation

Currently processing new changes in this PR. This may take a few minutes, please wait...

Review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0b265dc4-34c6-4e2b-81e0-385b3e4c2bda
Commits

Reviewing files that changed from the base of the PR and between 6a4a19a and 8bf4599.

Files selected for processing: 29

  • CHANGELOG.md
  • crates/cli/src/lib.rs
  • crates/engine/src/cmds_macro.rs
  • crates/engine/src/cmds_plugin.rs
  • crates/engine/src/cmds_template.rs
  • crates/engine/src/confirm.rs
  • crates/engine/src/history.rs
  • crates/engine/src/lib.rs
  • crates/engine/src/macro_tests.rs
  • crates/engine/src/plugin_tests.rs
  • crates/guide/src/lib.rs
  • docs/adr/0097-command-macros.md
  • docs/guide/coming-from-animate.md
  • docs/guide/index.md
  • docs/guide/reference/commands.md
  • docs/guide/saved-commands.md
  • docs/guide/workspace.md
  • docs/mcp.md
  • docs/plugins.md
  • docs/roadmap.md
  • src/app.rs
  • src/dialogs.rs
  • src/keymap.rs
  • src/macros_ui.rs
  • src/macros_ui_tests.rs
  • src/main.rs
  • src/panels.rs
  • src/platform.rs
  • src/plugins_ui.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant