My App
BoardKitCore

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;
}
MethodDescription
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 point

The 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;
}

On this page