History
Undo / redo is backed by @oh-just-another/history โ a transactional, invertible patch stack. The engine drives it for you; you can also use the kernel directly for headless replay or server-side audit.
Undo and redo on the editorโ
import { Editor } from "@oh-just-another/state";
if (editor.canUndo) editor.undo();
if (editor.canRedo) editor.redo();
With the React component, the same verbs are on the EditorAPI handle, and the typed history event reports availability changes:
import { useRef } from "react";
import { Editor, type EditorAPI } from "@oh-just-another/editor";
function Host() {
const ref = useRef<EditorAPI>(null);
return (
<>
<button onClick={() => ref.current?.undo()}>Undo</button>
<Editor ref={ref} />
</>
);
}
The history kernelโ
History owns no scene: push records a patch, undo() returns the inverse to apply, redo() returns the original. The caller applies the returned patch with apply from @oh-just-another/scene.
import { History } from "@oh-just-another/history";
import { apply } from "@oh-just-another/scene";
const history = new History({ limit: 100 });
history.push(patch);
scene = apply(scene, history.undo()!); // inverse
scene = apply(scene, history.redo()!); // original
Transactionsโ
Many patches from a single gesture collapse into one undo step. By default a transaction merges per entity โ a 100-tick drag of one shape becomes one record.
const tx = history.transaction();
for (const p of dragPatches) tx.add(p);
tx.commit();
Selection is conventionally excluded from history โ never push selection-only changes.