How Modly Handles State Management: Zustand Architecture and Store Patterns

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 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 Current page routing (generate, workflows, models, settings). In-memory only.
workflowsStore 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 Installed model and processing extensions, loading states. In-memory only (re-fetched from backend).
agentStore 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, the navigation state defines both data and actions within the creator function:

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 (lines 58-71) to save only essential UI preferences between sessions:

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 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 component references useAppStore to react to backend status:

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 (lines 18-23), the pushMeshUrl action updates mesh history that other UI areas depend on:

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 (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:

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>}
    </>
  )
}

The navigation store provides simple page routing without persistence:

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:

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:

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 Central application state, mesh history, backend control, and persisted UI preferences.
src/shared/stores/navStore.ts Lightweight navigation state for SPA routing.
src/shared/stores/workflowsStore.ts Workflow list management, folder metadata, and legacy format migration.
src/shared/stores/extensionsStore.ts Dynamic extension registry with loading states.
src/shared/stores/agentStore.ts AI assistant configuration with persistence.
src/areas/generate/GeneratePage.tsx Example component consuming useAppStore.
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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →