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:
- URL/search/navigation state → TanStack Router
- Canonical route payloads → route loaders
- Shared workflow state across multiple components/routes → Zustand
- One-component local UI state →
useState
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.tsUse 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
useShallowfor object/array composites - keep heavy editor/collab dependencies inside scoped stores
Guardrails
- no raw
fetch()in admin stores or view-model hooks — useapiFetch - no full-store subscriptions in UI/view-model code
- no direct feature-level
store.setStateoutside 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.