Skip to main content

State Architecture

Off Grid uses Zustand for state management with AsyncStorage persistence. State is split across five stores, each with a single responsibility.

Store Overview


appStore

Path: src/stores/appStore.ts:122 Responsibilities:
  • Downloaded models (text, image, Whisper)
  • Active model IDs
  • Settings (temperature, context length, GPU config, image gen params)
  • Device hardware info (RAM, CPU)
  • Gallery (generated images metadata)
  • Background generation state (progress, status, preview path)

Schema

Key Actions

Model Management:
Settings:
Gallery:

Persistence Strategy

What is NOT persisted:
  • downloadProgress (ephemeral download state)
  • deviceInfo (recalculated on app start)
  • isGeneratingImage, imageGenerationProgress, imageGenerationStatus (runtime state)

chatStore

Path: src/stores/chatStore.ts:48 Responsibilities:
  • Conversations (title, messages, metadata)
  • Streaming state (current streaming message, thinking indicator)
  • Message operations (add, update, delete)

Schema

Key Actions

Conversation Management:
Message Operations:
Streaming:

Persistence Strategy

Streaming State is NOT Persisted: Rationale: If the app crashes mid-generation, the partial streaming message is lost. On restart, the conversation shows the last finalized message. Conversation Title Auto-Generation:

projectStore

Path: src/stores/projectStore.ts Responsibilities:
  • Custom system prompts (called “projects”)
  • Active project selection
Projects allow users to define reusable system prompts (e.g., “Python Tutor”, “Creative Writer”) that apply to conversations.

Schema

Key Actions


authStore

Path: src/stores/authStore.ts Responsibilities:
  • Passphrase lock state
  • Authentication status

Schema


whisperStore

Path: src/stores/whisperStore.ts Responsibilities:
  • Whisper model selection (Tiny, Base, Small)
  • Transcription configuration

Schema


Service-Store Synchronization

Services update stores to maintain UI reactivity. The pattern is unidirectional: services write to stores, UI components read from stores.

Pattern: Service Updates Store

Why Both Service State AND Store State?

Service state:
  • Maintains generation across component unmounts
  • Holds complex objects not suitable for AsyncStorage persistence
  • Allows services to communicate with each other
Store state:
  • Triggers React re-renders via Zustand subscriptions
  • Persists critical fields to AsyncStorage
  • Provides global access to UI components

Data Flow Diagram


Persistence Implementation

AsyncStorage Adapter

Zustand’s persist middleware uses createJSONStorage(() => AsyncStorage) to automatically save/restore state.
Persistence Keys:

Migration Example

When adding new fields or changing defaults, use the merge option:

Unidirectional Data Flow

Rule: Services write to stores. UI reads from stores. UI never writes to stores directly (only via service methods). Example: Load Model Flow
Anti-Pattern (don’t do this):
Correct Pattern: