Programmatic edits

Drive editor changes from application code with intents, commands, and runtime actions.

Programmatic edits enter through the same semantic pipeline as browser input:

  • engine.dispatch(intent) — direct semantic edit
  • engine.dispatchCommand(commandId, args?) — named action by id

Both routes produce an intent, compile a plan, lower a transaction, apply steps, normalize, commit, and record history when the transaction is meaningful. Treat engine.state as a committed snapshot; the DOM runtime renders the next snapshot after the engine commits.

Examples assume an engine from Quickstart. Browser UI examples also assume a mounted runtime.

Choose intent or command

UseWhen
dispatch(intent)The application already knows the semantic edit to request.
dispatchCommand(id, args?)The same action is shared by a toolbar, shortcut, menu, or plugin.
runtime.invokeAction(id, args?)Browser UI wants the DOM runtime's readonly and action-state checks.

Insert text at the current selection

engine.dispatch({
  kind: "typeText",
  text: "Hello, SpineEditor",
});

A selected range is replaced by the inserted text.

Move the selection explicitly

import { pos, textSelection } from "@spine-editor/core";

engine.dispatch({
  kind: "setSelection",
  selection: textSelection(pos(0), pos(0)),
});

Selection changes are semantic state changes. In a mounted browser runtime, the runtime projects the committed selection back into DOM after render.

Insert a block at a known position

import {
  branchNode,
  fragment,
  textNode,
  type NodeTypeName,
} from "@spine-editor/core";

engine.dispatch({
  kind: "insertFragment",
  parentPath: [],
  index: 1,
  fragment: fragment([
    branchNode("paragraph" as NodeTypeName, [
      textNode("Inserted from app code"),
    ]),
  ]),
});

parentPath: [] is the document root. index: 1 inserts after the first root child.

Structural paths are semantic NodePath arrays instead of DOM indexes. They describe child indexes from the root, so [0, 2] means the third child of the first root child.

Replace the current selection with multi-line content

engine.dispatch({
  kind: "paste",
  payload: { kind: "text", text: "alpha\nbeta" },
});

For pre-shaped structural content, pass { kind: "fragment", fragment } instead.

Apply common command actions

import { CoreCommandIds } from "@spine-editor/core";

engine.dispatchCommand(CoreCommandIds.setBlockType, {
  toType: "heading",
  attrs: { level: 2 },
});

engine.dispatchCommand(CoreCommandIds.setBlockAlignment, "center");
engine.dispatchCommand(CoreCommandIds.setListType, "bullet");
engine.dispatchCommand(CoreCommandIds.adjustBlockIndent, 1);

Commands are useful when app code and UI controls should share one named behavior. They inspect the current semantic selection, produce an intent, and let the engine complete the rest of the pipeline.

Retag or format selected content

import type { MarkTypeName, NodeTypeName } from "@spine-editor/core";

engine.dispatch({
  kind: "setSelectedBlockType",
  toType: "heading" as NodeTypeName,
  attrs: { level: 2 },
});

engine.dispatch({
  kind: "toggleMark",
  markType: "strong" as MarkTypeName,
});

Block retagging follows the current semantic selection. Inline marks apply to selected text or become stored marks for later typing.

Invoke built-in commands

import { CoreCommandIds } from "@spine-editor/core";

engine.dispatchCommand(CoreCommandIds.pasteText, "alpha\nbeta");
engine.dispatchCommand(CoreCommandIds.insertParagraphBreak);
engine.dispatchCommand(CoreCommandIds.undo);

dispatchCommand returns DispatchResult | null (null when no handler is registered).

Use DOM runtime actions from browser UI

function invokeToolbarAction(
  actionId: string,
  args?: Parameters<typeof runtime.invokeAction>[1],
) {
  const action = runtime.getActions().find((item) => item.id === actionId);
  if (action?.enabled !== true) return false;
  return runtime.invokeAction(actionId, args);
}

invokeToolbarAction("inline.bold");
invokeToolbarAction("block.heading.2");
invokeToolbarAction("inline.textColor.set", "#1f4e79");

The runtime action catalog is the ergonomic browser-facing layer for toolbars and menus. It exposes labels, groups, active state, values, argument requirements, and enabled state from the current engine state.

Read the result

const result = engine.dispatch({ kind: "typeText", text: "!" });

if (result.applied) {
  console.log(result.stateAfter);
  console.log(result.transaction);
}

A no-op dispatch returns applied: false with stoppedAt: "noOp". A successful edit includes the transaction, mapping, normalized state, and optional history entry.

Respect the layer boundary

  • Application code may dispatch intents and commands.
  • Planner rules may patch plan operations.
  • Normalizer rules may repair the applied document before commit.
  • DOM code should route edits through runtime actions, commands, or intents.
  • Browser evidence should become semantic input before the engine commits.