ADR 0014 — Zustand persistence
Context
Section titled “Context”All global state (request tabs, collections, environments, history, settings, workflows, Kafka/MQTT connections, AI) lives in Zustand stores. That state must persist across reloads on web and across launches on desktop, and on desktop it must be encrypted at rest. A single persistence story is needed so stores don’t each reinvent storage, and so corrupt/old persisted data can’t crash the app. This sits alongside ADR 0002 (the tab model) and ADR 0004 (the encryption key provider).
Decision
Section titled “Decision”Use Zustand’s persist middleware for every global store, with a platform-selected storage adapter and Zod validation on rehydrate:
- Web —
src/lib/shared/dexie-storage.ts(IndexedDB via Dexie). - Desktop —
src/lib/shared/secure-storage.ts(encrypted electron-store over IPC; the key is wrapped by ElectronsafeStorage→ OS keychain, per ADR 0004). - Persisted blobs are validated against the Zod schemas in
src/lib/shared/store-validators.ts, so malformed or outdated state is rejected rather than silently corrupting the store.
The legacy localStorage adapter has been removed; new persistence must not go through window.localStorage. Secrets within state follow the SecretRef handle pattern (ADR 0007) so plaintext never lands in either store.
Consequences
Section titled “Consequences”Positive
- One persistence pattern across every store and both platforms; adapters are the only platform-specific piece.
- Schema validation on rehydrate turns “corrupt persisted state” from a crash into a recoverable reset.
- Desktop data is encrypted at rest without each store knowing about keys.
Negative
- Two storage backends to test (Dexie vs electron-store), including migration/rehydration edge cases.
- Every persisted store needs a maintained Zod schema, extra surface to keep in step with the store shape.
References
Section titled “References”- Source:
docs/adr/0014-zustand-persistence.md - Related: ADR 0002, ADR 0004, ADR 0007.