Paste
Clipboard content becomes a planned intent.
Paste is where editors often lose determinism. HTML arrives from arbitrary sources, styles mismatch, and the editor starts pattern-matching rendered structure instead of preserving semantic evidence.
SpineEditor keeps paste on the semantic pipeline. The substrate reads the
clipboard, builds a PastePayload, and dispatches a PasteIntent. The planner
converts the payload into a Fragment, decides whether the supported domain is
structural or inline, and lowers the result through a transaction.
Worked example
Plain text paste:
engine.dispatch({
kind: "paste",
payload: {
kind: "text",
text: "line one\nline two",
},
});
Fragment paste (from an internal copy or a structured source):
engine.dispatch({
kind: "paste",
payload: {
kind: "fragment",
fragment: copiedFragment,
},
});
Both trigger the same trace sequence as any other intent:
intentReceived
intentKind: "paste"
planCompiled
intentKind: "paste"
opCount: ...
transactionApplied
stepCount: ...
history
action: "push"
If schema repair changes the applied document, one or more normalized events appear before history.
Payload shape
type PastePayload = PlainTextPastePayload | FragmentPastePayload;
interface PlainTextPastePayload {
readonly kind: "text";
readonly text: string;
}
interface FragmentPastePayload {
readonly kind: "fragment";
readonly fragment: Fragment;
}
Helpers:
splitPlainTextLines(text)— split on newlines with deterministic handling of trailing empties.plainTextToFragment(text, options?)— wrap lines into paragraph blocks.pastePayloadToFragment(payload)— unify the two cases.isEmptyPastePayload(payload)— cheap short-circuit for no-op pastes.pastePayloadToJSON(payload)— canonical serialization for trace/replay.
Structural vs inline
The paste planner (packages/core/src/plan/plan-paste-structural.ts and siblings) picks between two strategies:
- Structural paste. The incoming fragment contains supported block-level content (paragraphs, lists, media). On the earned domains, the planner inserts or replaces structure without flattening it to plain text.
- Inline paste. The incoming fragment is text-only and merges into the current text run. Marks from the paste site are preserved or stripped by the planner rule.
The choice is deterministic. Unsupported structural domains fail closed into the explicit fallback instead of silently inventing semantics.