Operations
LWW merge, undo/redo History, and clipboard serialization
LWW Merge
BoardKit uses Last-Writer-Wins (LWW) conflict resolution for concurrent edits. When two clients modify the same element, the one with the later updatedAt timestamp wins. If timestamps are equal, the remote version wins (server authority).
mergeElement
function mergeElement(local: Element, remote: Element): Element;Compares updatedAt timestamps. Returns the local element if it's newer, otherwise returns the remote element.
mergeScene
function mergeScene(local: SceneState, remote: ElementMutation[]): SceneState;Applies a batch of remote mutations to a local scene using LWW per element:
- create: If element exists locally, apply LWW merge. If not, add it.
- update: If element exists locally, apply LWW merge.
- delete: Remove the element from the local scene.
import { mergeScene } from '@hfu.digital/boardkit-core';
const updatedScene = mergeScene(localScene, remoteMutations);History (Undo/Redo)
The History class implements a command-based undo/redo stack.
Command Interface
interface Command {
execute(): ElementMutation[];
undo(): ElementMutation[];
}History Class
class History {
constructor(maxDepth?: number); // default: 100
push(command: Command): void;
undo(): ElementMutation[] | null;
redo(): ElementMutation[] | null;
canUndo(): boolean;
canRedo(): boolean;
clear(): void;
}| Method | Description |
|---|---|
push(command) | Adds a command to the undo stack. Clears the redo stack. Evicts oldest command if maxDepth is exceeded. |
undo() | Pops from undo stack, pushes to redo stack, returns the undo mutations. Returns null if stack is empty. |
redo() | Pops from redo stack, pushes to undo stack, returns the execute mutations. Returns null if stack is empty. |
canUndo() | Returns true if the undo stack is non-empty |
canRedo() | Returns true if the redo stack is non-empty |
clear() | Empties both stacks |
import { History } from '@hfu.digital/boardkit-core';
const history = new History(50); // max 50 undo steps
history.push(myCommand);
history.canUndo(); // true
history.undo(); // returns myCommand.undo()
history.canRedo(); // true
history.redo(); // returns myCommand.execute()Clipboard
Serialization and deserialization of selected elements for copy/paste operations.
serializeSelection
function serializeSelection(elements: Element[]): string;Serializes elements to JSON, including their centroid for relative positioning on paste.
deserializeSelection
function deserializeSelection(data: string, pastePosition: Point): Element[];Deserializes elements from JSON, assigns new IDs, and offsets positions relative to the paste location.
import { serializeSelection, deserializeSelection } from '@hfu.digital/boardkit-core';
// Copy
const clipboard = serializeSelection(selectedElements);
// Paste at a new position
const pasted = deserializeSelection(clipboard, { x: 200, y: 300 });
// pasted elements have new IDs and positions offset from the paste pointThe offset calculation uses the centroid of the original selection as the origin, so pasted elements maintain their relative positions to each other.
ElementMutation
The mutation type used by both operations and the collaboration protocol:
interface ElementMutation {
type: 'create' | 'update' | 'delete';
elementId: string;
pageId: string;
data?: Partial<Element>;
timestamp: number;
}