Toast
Contributor

Admin State

Zustand state management patterns for the admin panel.

Toast uses TanStack Router + Zustand + local component state, each with a clear job.

ADR: See ADR-018 for the architectural decision.

Pick the smallest state tool first

Use this order before adding state:

  1. URL/search/navigation state → TanStack Router
  2. Canonical route payloads → route loaders
  3. Shared workflow state across multiple components/routes → Zustand
  4. One-component local UI stateuseState

If the state does not need to survive beyond one surface, keep it local.

Current store topology

Route stores

Global workflow stores live in:

apps/admin/src/stores/
├── api-status.store.ts
├── audit.store.ts
├── content-list.store.ts
└── settings.store.ts

Use these for shared route-level workflows like filters, list state, and settings orchestration.

Scoped stores

Editor/session runtime state lives under:

apps/admin/src/stores/scoped/

These stores are created per session/provider lifecycle rather than as one app-global singleton.

Practical examples

Use the router for URL state

  • active route
  • query params
  • pagination/search state that belongs in the URL

Use a route loader for canonical payloads

  • settings page bootstrap data
  • the first load of a content page

Use Zustand for shared workflow state

  • settings editor flow shared across several components
  • content list filters used by multiple child components
  • editor session/runtime state shared across complex subtrees

Use local state for local UI

  • dialog open/closed
  • single input state
  • disclosure toggles

UI boundary

UI surfaces should consume view-model hooks and selectors, not reach deep into store internals.

Rules:

  • use narrow selectors
  • use useShallow for object/array composites
  • keep heavy editor/collab dependencies inside scoped stores

Guardrails

  • no raw fetch() in admin stores or view-model hooks — use apiFetch
  • no full-store subscriptions in UI/view-model code
  • no direct feature-level store.setState outside test/reset/bootstrap paths
  • no persistence of private settings, secrets, or auth artifacts client-side

Persistence rules

User-affine persistence keys must follow:

toast-admin:{siteId}:{userId}:{storeName}:v{N}

On logout, tenant switch, or user switch:

  • reset route stores via resetAdminRouteStores()
  • purge user-affine editor session storage via purgeEditorSessionUserStorage(...)

Enforcement

The canonical enforcement policies (POL-001 through POL-010) live in docs/policies.md.

This page explains the contributor-facing implementation pattern; docs/policies.md is the hard-rules registry.

On this page