Usage
React
Effect stream
Node IDs
addNode() returns the new node’s ID as a string ("wall_ms:logical:node_id"). Pass this ID to moveNode(), updateNode(), and deleteNode().
Sibling ordering
Siblings are ordered lexicographically by theirposition string, with ties broken by author ID descending. Use a fractional indexing scheme (e.g. "a0", "a0V", "b0") to insert between existing siblings without rewriting all positions.
API
| Method | Description |
|---|---|
addNode(parentId, position, value, ttlMs?) | Add a new node. Returns the new node ID. parentId is null for root nodes. |
moveNode(nodeId, newParentId, newPosition, ttlMs?) | Move node to a new parent and/or position. Cycle-creating moves are discarded. |
updateNode(nodeId, value, ttlMs?) | Update node value. Last-write-wins via HLC timestamp. |
deleteNode(nodeId, ttlMs?) | Tombstone-delete a node. Children are preserved. |
value() | Returns { roots: TreeNodeValue[] } — the full visible tree. |
onChange(fn) | Subscribe — returns unsubscribe function. |
stream() | Returns an Effect Stream<{ roots: TreeNodeValue[] }> that emits on every change. |
Conflict semantics
- Concurrent adds to the same parent converge — siblings are ordered by
(position, id desc) - Concurrent moves: both moves apply in HLC order. The last move (highest
op_id) wins for each node’s parent pointer - Cycle prevention: a move that would place a node under its own descendant is silently discarded
- Tombstones: deleted nodes are never GC’d — children are preserved and restored if a concurrent move targets a deleted parent
- Value updates: last-write-wins via the
updated_atHLC timestamp
CRDT key prefix
Use thetr: prefix by convention: