Miraiclip SDK
Fundamentals

History & transactions

Undo, redo, and grouping edits into one step.

Every successful command is one undo step, with no setup.

project.undo();      // true if there was something to undo
project.redo();      // true if there was something to redo
project.canUndo();
project.canRedo();
project.clearHistory();
FactDetail
What's recordedDocument changes only. setPlayhead and setSelection never create undo steps
ExactnessEach step stores the inverse patches of its change, so undo restores the previous document exactly
Limit500 steps by default; createProject(init, { historyLimit }) changes it. The oldest steps drop first
New editsDispatching after an undo clears the redo stack
LoadingcreateProject(saved) starts with empty history

Transactions

transaction(fn, label?) turns every command dispatched inside fn into one undo step.

project.transaction(() => {
  project.dispatch({ type: "clip/move", payload: { clipId: "intro", startUs: 2_000_000 } });
  project.dispatch({ type: "track/rename", payload: { trackId: "main", name: "Main" } });
}, "Move and rename");

project.undo(); // reverts both

It's all or nothing. If anything inside fn throws, every change made so far in the transaction is rolled back, and the error is rethrown:

try {
  project.transaction(() => {
    project.dispatch({ type: "clip/move", payload: { clipId: "intro", startUs: 3_000_000 } });
    project.dispatch({ type: "clip/move", payload: { clipId: "missing", startUs: 0 } }); // throws
  });
} catch {
  // "intro" is back where it was; no undo step was added
}

Use transactions for any user action that takes several commands: a ripple delete, pasting several clips, applying a template.

applyCommands(project, commands) does the same for an array of commands and returns a result instead of throwing, including the index of the command that failed. It's built for AI agents: Tools from the command catalog.

Labels

The label appears on the history event, so you can show "Undo Move and rename" in a menu:

let lastLabel: string | undefined;

project.events.on("history", ({ kind, label }) => {
  if (kind === "commit") lastLabel = label;
});
kindWhen
commitA command or transaction finished
undoundo() ran; label is the step undone
redoredo() ran

Single dispatches have no label. To label one, wrap it in a transaction.

Undo/redo buttons

canUndo() and canRedo() are methods, not state. Re-read them when history changes:

const undoButton = document.querySelector<HTMLButtonElement>("#undo")!;
const redoButton = document.querySelector<HTMLButtonElement>("#redo")!;

function refresh() {
  undoButton.disabled = !project.canUndo();
  redoButton.disabled = !project.canRedo();
}

project.events.on("history", refresh);
refresh();

On this page