Skip to content

State Management

Diagrammer uses Zustand with Immer middleware for state management. Stores are split by responsibility so that changes to ephemeral UI state (cursor position, active tool) don't trigger re-renders on document data, and vice versa.

Store Architecture

Core Stores

DocumentStore

The single source of truth for all document content. Holds the shape map, connection data, groups, and the shapeOrder array that determines z-ordering.

typescript
// All mutations go through Immer — never mutate directly
documentStore.updateShape(id, { fill: '#ff0000' });
documentStore.addShapes([newShape]);
documentStore.removeShapes([id1, id2]);

WARNING

Never read shape data from anywhere other than DocumentStore. Other stores may hold derived or cached data, but DocumentStore is authoritative.

SessionStore

Ephemeral UI state that doesn't need to be persisted or synced:

FieldPurpose
selectedShapeIdsCurrently selected shapes
cameraStatePan offset and zoom level
activeToolCurrently active tool name
cursorCurrent cursor style
hoveredShapeIdShape under cursor

HistoryStore

Implements undo/redo using complete document snapshots. Each mutation that should be undoable pushes a snapshot onto the history stack.

typescript
historyStore.pushSnapshot(documentState);
historyStore.undo(); // Restores previous snapshot
historyStore.redo(); // Re-applies undone snapshot

PageStore

Manages multi-page document structure — page ordering, active page, page metadata. Each page has its own set of shapes in DocumentStore.

PersistenceStore

Coordinates saving and loading documents:

  • Auto-save with configurable interval
  • localStorage for document metadata
  • IndexedDB (via BlobStorage) for binary content

Collaboration Stores

These stores manage real-time multi-user state:

StoreResponsibility
connectionStoreWebSocket connection lifecycle, auth token, reconnection backoff
collaborationStoreActive session info, connected users
presenceStoreRemote cursor positions, remote selections — updated via Yjs awareness
teamStoreTeam membership, document sharing
userStoreCurrent user identity, JWT tokens
documentRegistryUnified index of local, remote, and cached documents

Storage Layer

BlobStorage

Content-addressed storage using SHA-256 hashing. Stored in IndexedDB.

  • Deduplication — identical content is stored once regardless of how many shapes reference it
  • Reference counting — tracks which documents use which blobs
  • Garbage collectionBlobGarbageCollector cleans up orphaned blobs

TeamDocumentCache

IndexedDB cache for offline access to team documents. Uses LRU eviction when storage limits are approached. StorageQuotaMonitor tracks usage proactively.

TrashStorage

Soft-delete with configurable retention period. Documents go to trash first and can be recovered before permanent deletion.

Immer Patterns

All state mutations use Immer's produce pattern (via Zustand middleware). This means you write mutations that look like direct mutation but produce immutable updates with structural sharing:

typescript
// Inside a Zustand store with Immer middleware
updateShape: (id, updates) =>
  set((state) => {
    const shape = state.shapes[id];
    if (shape) {
      Object.assign(shape, updates); // Looks mutable, but Immer makes it immutable
    }
  }),

Structural sharing ensures unchanged objects are reused — if you update one shape, all other shapes keep the same object references, so React components subscribed to them don't re-render.