Documentation
Architecture
State Management

State Management

Store responsibilities

The central client state store (Zustand + Immer) is responsible for:

  • Holding the complete board state that the UI renders (board, notes, Jira items)
  • Tracking UI state (selected note, open overlays, active editor, search state)
  • Dispatching user actions that:
    1. Update local state immediately (optimistic render)
    2. Persist to IndexedDB (offline durability)
    3. Queue a sync push to D1 (server persistence)
    4. Optionally queue a changelog write to R2

Optimistic update pattern

All mutations follow this sequence:

User action
  → Update Zustand store immediately (instant render)
  → Save to IndexedDB (offline durability)
  → schedulePush() to D1SyncManager (server persistence)
  → optionally queue ChangeLogger entry (audit trail)

Why this matters: The UI never waits for a network round-trip. The user's action takes effect instantly. If the push fails, a retry happens automatically.

Example: creating a note

createNote: (note) => {
  // 1. Optimistic local update — instant
  set((s) => { s.notes[note.id] = note })
  // 2. Push to server — asynchronous
  if (syncManager.ready) {
    syncManager.createNote(note)
  }
},

Store shape overview

State sliceWhat it holds
boardBoard metadata (name, dimensions, ownerEmail)
notesRecord of all notes by ID
jiraItemsRecord of all Jira work items by ID
selectedNoteIdCurrently focused note
overlayNoteIdNote open in the full-screen editor overlay
activeJiraItemIdJira item open in the editor
isAiPanelOpenWhether the AI chat panel is visible
phases / currentPhaseIdPM lifecycle phase config
searchQuery / searchMatchIdsFull-text search state
canEditWhether the current user can make edits
undoStackLast 20 deleted notes (for Ctrl+Z restore)
subagentFloating subagent panel position and state
connectionSync connection status

Selector discipline

Use Zustand selectors to subscribe only to the slices you need:

// ✓ Subscribe to a specific slice — re-renders only when notes change
const notes = useBoardStore((s) => s.notes)
 
// ✗ Subscribe to entire store — re-renders on any state change
const store = useBoardStore()

Write permissions

The canEdit flag is set from the board access check response. All write operations in components check canEdit before proceeding. The Bottom Toolbar, canvas double-click (create note), and the note editor are all gated on this flag.

Undo delete

Deleting a note snapshots it into undoStack (max 20 entries). Ctrl / Cmd + Z on the canvas triggers undo(), which re-creates the note in local state and pushes it to D1.

See also