# How Modly Handles State Management: Zustand Architecture and Store Patterns

> Discover how Modly handles state management using Zustand architecture and domain-specific stores. Learn about selective localStorage persistence for efficient data handling in your web applications.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: architecture
- Published: 2026-08-19

---

**Modly manages client-side state using Zustand through isolated, domain-specific stores under `src/shared/stores/`, with selective `localStorage` persistence for UI preferences and workflow metadata while keeping navigation and extension data in-memory only.**

The open-source 3D generation application Modly (lightningpixel/modly) implements a lightweight yet robust state management system built on Zustand. By fragmenting global state into logical domains—each with tailored persistence strategies—the codebase maintains type safety, minimizes re-renders, and ensures critical user preferences survive browser sessions.

## Store Architecture Overview

Modly organizes state into five distinct stores, each exported as a typed React hook. This separation prevents unnecessary coupling between unrelated domains like 3D generation settings and AI assistant configuration.

| Store | Location | Purpose | Persistence Strategy |
|-------|----------|---------|---------------------|
| **`appStore`** | [`src/shared/stores/appStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/appStore.ts) | Global UI state, backend connection status, generation options, mesh history, and toast notifications. | Persisted via `zustand/middleware` `persist` to `localStorage`. |
| **`navStore`** | [`src/shared/stores/navStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/navStore.ts) | Current page routing (`generate`, `workflows`, `models`, `settings`). | In-memory only. |
| **`workflowsStore`** | [`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts) | Workflow registry, tab management, folder organization, and legacy migration logic. | Partial: folder metadata manually persisted to `localStorage`. |
| **`extensionsStore`** | [`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts) | Installed model and processing extensions, loading states. | In-memory only (re-fetched from backend). |
| **`agentStore`** | [`src/shared/stores/agentStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/agentStore.ts) | AI-assistant configuration (thinking mode, temperature). | Persisted via `persist` middleware. |

## Core State Management Patterns

### Store Creation with TypeScript Safety

Each store follows a consistent factory pattern using Zustand’s `create` function with explicit TypeScript interfaces. In [`src/shared/stores/navStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/navStore.ts), the navigation state defines both data and actions within the creator function:

```typescript
export const useNavStore = create<NavState>((set) => ({
  currentPage: 'generate',
  navigate: (page) => set({ currentPage: page })
}))

```

This pattern ensures compile-time safety while providing a minimal API surface for components.

### Selective Persistence to localStorage

Not all state warrants disk storage. Modly applies the `persist` middleware strategically in [`src/shared/stores/appStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/appStore.ts) (lines 58-71) to save only essential UI preferences between sessions:

```typescript
persist(
  (set, get) => ({ /* state & actions */ }),
  {
    name: 'modly-store',
    partialize: (state) => ({
      generationOptions: state.generationOptions,
      showRamIndicator: state.showRamIndicator,
      lighting: state.lighting,
      // Only critical UI settings persist
    })
  }
)

```

For workflow folder metadata, [`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts) implements custom serialization via `storeFolders` and `readStoredFolders` helpers, using the key `modly-workflow-folders` to isolate folder colors and bookmarks from the full workflow state.

### Reactive Consumption via Hooks

Components consume state through the exported hooks, triggering automatic re-renders when observed values change. The [`GeneratePage.tsx`](https://github.com/lightningpixel/modly/blob/main/GeneratePage.tsx) component references `useAppStore` to react to backend status:

```tsx
const { backendStatus, initApp } = useAppStore()
const { currentPage, navigate } = useNavStore()

```

Because Zustand hooks return the current snapshot, UI components remain synchronized without prop drilling or context providers.

### Cross-Store Coordination

Stores communicate through Zustand’s `get()` method to maintain referential integrity. In [`src/shared/stores/appStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/appStore.ts) (lines 18-23), the `pushMeshUrl` action updates mesh history that other UI areas depend on:

```typescript
pushMeshUrl: (url) => {
  const { meshHistory, historyIndex } = get()
  const next = [...meshHistory.slice(0, historyIndex + 1), url]
  set({ meshHistory: next, historyIndex: next.length - 1 })
}

```

This technique keeps related state in sync across the application without creating circular dependencies.

### Legacy Migration Handling

The `workflowsStore` contains robust migration logic to upgrade older workflow formats to the current node/edge schema. The `migrateWorkflow` function in [`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts) (lines 26-67) ensures persisted files remain functional after application updates, preventing data loss during version transitions.

## Implementation Examples

### Accessing Global Application State

Components import `useAppStore` to read backend connectivity and toast notifications:

```tsx
import { useAppStore } from '@shared/stores/appStore'

export function Header() {
  const { backendStatus, showToast, hideToast, toast } = useAppStore()
  
  return (
    <>
      <span>Backend: {backendStatus}</span>
      {toast && <div onClick={hideToast}>{toast.message}</div>}
    </>
  )
}

```

### Navigation Between Application Areas

The navigation store provides simple page routing without persistence:

```tsx
import { useNavStore } from '@shared/stores/navStore'

export function Sidebar() {
  const { currentPage, navigate } = useNavStore()
  
  return (
    <ul>
      {['generate', 'workflows', 'models', 'settings'].map((page) => (
        <li 
          key={page} 
          className={currentPage === page ? 'active' : ''} 
          onClick={() => navigate(page as any)}
        >
          {page}
        </li>
      ))}
    </ul>
  )
}

```

### Imperative Store Updates for Workflow Folders

Folder UI state updates bypass React hooks when called from non-component logic:

```typescript
import { useWorkflowsStore } from '@shared/stores/workflowsStore'

// Updates both the store and localStorage via internal helper
useWorkflowsStore.getState().setFolderColor('MyFolder', '#ff8800')

```

### Managing Persisted Generation Options

Toggle switches persist user preferences automatically through the app store:

```tsx
import { useAppStore } from '@shared/stores/appStore'

function TextureToggle() {
  const { generationOptions, setGenerationOptions } = useAppStore()
  
  const toggle = () => setGenerationOptions({ 
    enableTexture: !generationOptions.enableTexture 
  })
  
  return (
    <button onClick={toggle}>
      Texture: {generationOptions.enableTexture ? 'On' : 'Off'}
    </button>
  )
}

```

## Key Files Reference

| File | Role |
|------|------|
| [`src/shared/stores/appStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/appStore.ts) | Central application state, mesh history, backend control, and persisted UI preferences. |
| [`src/shared/stores/navStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/navStore.ts) | Lightweight navigation state for SPA routing. |
| [`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts) | Workflow list management, folder metadata, and legacy format migration. |
| [`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts) | Dynamic extension registry with loading states. |
| [`src/shared/stores/agentStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/agentStore.ts) | AI assistant configuration with persistence. |
| [`src/areas/generate/GeneratePage.tsx`](https://github.com/lightningpixel/modly/blob/main/src/areas/generate/GeneratePage.tsx) | Example component consuming `useAppStore`. |
| [`src/areas/settings/components/ApplicationSection.tsx`](https://github.com/lightningpixel/modly/blob/main/src/areas/settings/components/ApplicationSection.tsx) | Settings UI consuming multiple stores. |

## Summary

- **Modly uses Zustand** to implement five isolated domain stores under `src/shared/stores/`, each handling specific application concerns.
- **Persistence is selective**: `appStore` and `agentStore` use Zustand’s `persist` middleware with `partialize` to limit `localStorage` writes, while `workflowsStore` implements custom folder serialization.
- **Navigation and extensions remain ephemeral**, re-initializing on each session to ensure fresh data.
- **TypeScript interfaces** enforce contract boundaries across stores, while `get()` enables cross-store coordination without tight coupling.
- **Legacy migration logic** in `workflowsStore` ensures backward compatibility for saved user data.

## Frequently Asked Questions

### What state management library does Modly use?

Modly uses **Zustand**, a minimal, hook-based state management library. Each store is created via the `create` function from `zustand`, configured with TypeScript interfaces for type safety, and consumed through exported hooks like `useAppStore`.

### Which Modly stores persist data to localStorage?

The `appStore` (UI preferences, generation options, mesh history) and `agentStore` (AI assistant settings) persist to `localStorage` using Zustand’s `persist` middleware. The `workflowsStore` manually persists only folder metadata (names, colors, bookmarks) via custom helpers, while `navStore` and `extensionsStore` remain in-memory only.

### How does Modly handle navigation state?

Navigation state lives in [`src/shared/stores/navStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/navStore.ts) as an in-memory store tracking the current page (`generate`, `workflows`, `models`, or `settings`). The `navigate` action updates this state, triggering re-renders in components that consume `useNavStore`, but the value resets when the application reloads.

### Can Modly stores communicate with each other?

Yes. Modly stores reference their own state via the `get()` method passed to Zustand’s creator function, as seen in [`appStore.ts`](https://github.com/lightningpixel/modly/blob/main/appStore.ts) where `pushMeshUrl` reads current history before appending new entries. This pattern allows stores to react to internal state changes without requiring external event emitters or global dispatchers.