Technical DocumentationStore Architecture

Store Architecture

Writan uses Zustand for state management, with a local-first architecture backed by IndexedDB and synced to Supabase.

Stores

projectStore — Data State

Located at lib/stores/projectStore.ts. Manages all project data and operations:

  • Data: projects, nodes, pips, tree hierarchy
  • Project CRUD: create, rename, delete projects
  • Node operations: add, rename, delete, reorder nodes; update prose, data blocks, generation layer, prose settings, descriptions
  • Pip management: create craft pips, update pip status with dual-write (pips table + node.generation.decisions)
  • Undo system: 30-level undo stack using node/pip snapshots

uiStore — UI State

Located at lib/stores/uiStore.ts. Manages transient UI state:

  • Sidebar collapse states and widths
  • Selected node, expanded nodes
  • Active pip, review filters
  • Centre view mode (tree/notes/output) and per-node view mode overrides
  • Output column visibility and follow mode
  • Focus mode, settings modal, left sidebar mode
  • Prose settings modal, library detail modal
  • Submission log modal visibility

ruleConfigStore — Rule Configuration

Located at lib/stores/ruleConfigStore.ts. Tracks which writing rules are enabled/disabled:

  • Persists to localStorage
  • Can disable by authority prefix (e.g. all Orwell rules) or individual rule ID
  • Used by analyzeNode() to filter which detectors run

submissionLogStore — API Request Log

Located at lib/stores/submissionLogStore.ts. Tracks Claude API submissions for debugging:

  • In-memory only (session-scoped, not persisted)
  • Records request/response, model, token usage, duration, status
  • Maximum 100 entries per session
  • Surfaced via floating log button and modal

Data Flow Pattern

Writan follows a local-first pattern:

User action
  → Write to IndexedDB (Dexie)
  → Queue sync operation
  → Trigger sync (batches to Supabase every 3 seconds, max 50 entries)
  → Update Zustand state
  → Rebuild the tree view

Sync Engine (lib/sync/syncEngine.ts)

  • Coalesces operations (insert + delete = cancel, multiple updates = last wins)
  • Batches up to 50 entries per sync cycle
  • Supports offline-first — queued operations persist in IndexedDB
  • Checks navigator.onLine before attempting sync

Local Database (lib/db/localDb.ts)

IndexedDB via Dexie stores: projects, nodes, pips, user settings, and the sync queue.

Component Patterns

  • All components are 'use client' — no React Server Components yet
  • React hooks must be declared before any early returns (ESLint requirement)
  • Popups and tooltips use createPortal(el, document.body) for z-index stacking
  • The TipTap editor syncs via onUpdate with a 500ms debounced auto-save
  • Modals are capped to viewport height with scrollable body