Skip to main content

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.