=============== LIBRARY RULES =============== From library maintainers: - Prefer website/docs/ai as the canonical source for persistence semantics, invariants, and decision rules. - Treat README.md and src/index.ts as the public package surface and prefer published entrypoints over internal paths. - Use react-mnemonic/core for the lean persisted-state path and react-mnemonic/schema when validation, versioning, or migrations are required. - Durable app state should use MnemonicProvider and useMnemonicKey rather than raw localStorage in consumer-facing code. - Treat excluded build outputs as derived artifacts rather than source-of-truth documentation. # React Mnemonic React Mnemonic is an AI-friendly, persistent, type-safe state management library for React applications. It provides a `useState`-like API through the `useMnemonicKey` hook that persists values to localStorage (or custom storage backends), surviving page reloads and optionally syncing across browser tabs. The library is built with SSR-safety in mind and requires zero runtime dependencies. The library offers three entrypoints: `react-mnemonic/core` for lean persisted-state functionality, `react-mnemonic/schema` for JSON Schema validation, versioning, and migrations, and the root `react-mnemonic` for full backward compatibility. Values are stored in versioned envelopes that enable automatic schema migrations when data shapes evolve between app versions. ## MnemonicProvider The `MnemonicProvider` component creates a namespaced storage scope for child components. All keys written by hooks inside the provider are prefixed with the namespace to prevent collisions between different parts of your application. ```tsx import { MnemonicProvider, useMnemonicKey } from "react-mnemonic/core"; function Counter() { const { value: count, set } = useMnemonicKey("count", { defaultValue: 0, }); return (

Count: {count}

); } export default function App() { return ( ); } // The counter persists in localStorage as "my-app.count" // and survives full page reloads ``` ## useMnemonicKey The `useMnemonicKey` hook provides a `useState`-like API for persistent state. It returns an object with `value`, `set`, `reset`, and `remove` functions for managing persisted data. ```tsx import { useMnemonicKey } from "react-mnemonic/core"; interface UserPreferences { theme: "light" | "dark"; fontSize: number; notifications: boolean; } function SettingsPanel() { const { value: prefs, set, reset, remove } = useMnemonicKey("preferences", { defaultValue: { theme: "light", fontSize: 14, notifications: true, }, onChange: (newPrefs, oldPrefs) => { console.log("Preferences changed from", oldPrefs, "to", newPrefs); }, onMount: (initialPrefs) => { document.body.className = initialPrefs.theme; }, }); return (
); } ``` ## defineMnemonicKey The `defineMnemonicKey` function creates reusable, importable key descriptors that package the storage key and its options into a stable object. This helps keep persistence behavior explicit and consistent across components. ```tsx import { defineMnemonicKey, useMnemonicKey, MnemonicProvider } from "react-mnemonic/core"; // Define once at module scope const themeKey = defineMnemonicKey("theme", { defaultValue: "light" as "light" | "dark" | "system", listenCrossTab: true, }); const sidebarKey = defineMnemonicKey("sidebar-collapsed", { defaultValue: false, }); // Reuse across components function Header() { const { value: theme, set } = useMnemonicKey(themeKey); return ( ); } function Sidebar() { const { value: collapsed, set } = useMnemonicKey(sidebarKey); return ( ); } ``` ## Cross-Tab Synchronization Enable cross-tab synchronization with the `listenCrossTab` option so that changes in one browser tab are reflected in all other tabs reading the same key. ```tsx import { useMnemonicKey } from "react-mnemonic/core"; function ThemeSwitcher() { const { value: theme, set } = useMnemonicKey<"light" | "dark">("theme", { defaultValue: "light", listenCrossTab: true, onChange: (newTheme) => { // Update document when theme changes (including from other tabs) document.documentElement.setAttribute("data-theme", newTheme); }, }); return ( ); } // Changes in one tab automatically sync to all other tabs ``` ## Custom Storage Backends Provide a custom synchronous storage backend by implementing the `StorageLike` interface. This enables use of sessionStorage, in-memory storage for testing, or custom adapters. ```tsx import { MnemonicProvider, useMnemonicKey, type StorageLike } from "react-mnemonic/core"; // In-memory storage for testing const mockStorage: StorageLike = { items: new Map(), getItem(key) { return this.items.get(key) ?? null; }, setItem(key, value) { this.items.set(key, value); }, removeItem(key) { this.items.delete(key); }, get length() { return this.items.size; }, key(index) { return Array.from(this.items.keys())[index] ?? null; }, }; // Custom storage with BroadcastChannel for cross-tab sync const idbCacheStorage: StorageLike = { cache: new Map(), getItem(key) { return this.cache.get(key) ?? null; }, setItem(key, value) { this.cache.set(key, value); // Notify other tabs new BroadcastChannel("app-sync").postMessage({ keys: [key] }); }, removeItem(key) { this.cache.delete(key); }, onExternalChange: (callback) => { const bc = new BroadcastChannel("app-sync"); bc.onmessage = (e) => callback(e.data.keys); return () => bc.close(); }, }; function App() { return ( ); } ``` ## Custom Codecs Create custom codecs using `createCodec` for types that need special serialization, such as `Date`, `Set`, or `Map`. Using a custom codec bypasses JSON Schema validation. ```tsx import { createCodec, useMnemonicKey, CodecError } from "react-mnemonic/core"; // Date codec const DateCodec = createCodec( (date) => date.toISOString(), (str) => new Date(str) ); // Set codec const StringSetCodec = createCodec>( (set) => JSON.stringify([...set]), (str) => new Set(JSON.parse(str)) ); // Map codec const MapCodec = createCodec>( (map) => JSON.stringify([...map.entries()]), (str) => new Map(JSON.parse(str)) ); function LastVisitTracker() { const { value: lastVisit, set } = useMnemonicKey("lastVisit", { defaultValue: new Date(), codec: DateCodec, }); return (

Last visit: {lastVisit.toLocaleString()}

); } function TagManager() { const { value: tags, set } = useMnemonicKey>("tags", { defaultValue: (error) => { if (error instanceof CodecError) { console.warn("Failed to decode tags:", error.message); } return new Set(); }, codec: StringSetCodec, }); const addTag = (tag: string) => set(new Set([...tags, tag])); const removeTag = (tag: string) => { const newTags = new Set(tags); newTags.delete(tag); set(newTags); }; return (
{[...tags].map((tag) => ( {tag} ))}
); } ``` ## Server-Side Rendering (SSR) The library is SSR-safe by default. Use `ssr.serverValue` for deterministic server placeholders and `ssr.hydration` to control when storage is read. ```tsx import { MnemonicProvider, useMnemonicKey } from "react-mnemonic"; // Next.js example with server placeholder // app/theme-toggle.tsx "use client"; function ThemeToggle({ serverTheme }: { serverTheme: "light" | "dark" | "system" }) { const { value, set } = useMnemonicKey("theme", { defaultValue: "light" as "light" | "dark" | "system", ssr: { serverValue: serverTheme, // Use server-known value during SSR hydration: "immediate", // Read storage immediately after hydration }, }); return ; } // Delay storage reads until after mount function SidebarState() { const { value: collapsed } = useMnemonicKey("sidebar", { defaultValue: false, ssr: { hydration: "client-only", // Don't read storage until after mount }, }); return ; } // Provider-level SSR defaults function App() { return ( ); } ``` ## Schema Registry and Validation Use `createSchemaRegistry` with JSON Schema definitions for runtime validation. Schema-managed keys store JSON values directly and are validated against their registered schemas. ```tsx import { createSchemaRegistry, MnemonicProvider, useMnemonicKey, type KeySchema, } from "react-mnemonic/schema"; const profileSchemaV1: KeySchema = { key: "profile", version: 1, schema: { type: "object", properties: { name: { type: "string" }, email: { type: "string" }, }, required: ["name", "email"], }, }; const settingsSchema: KeySchema = { key: "settings", version: 1, schema: { type: "object", properties: { theme: { enum: ["light", "dark", "system"] }, fontSize: { type: "integer", minimum: 10, maximum: 24 }, notifications: { type: "boolean" }, }, required: ["theme"], }, }; const registry = createSchemaRegistry({ schemas: [profileSchemaV1, settingsSchema], migrations: [], }); function App() { return ( ); } // Values are automatically validated against the schema function ProfileEditor() { const { value, set } = useMnemonicKey("profile", { defaultValue: { name: "", email: "" }, }); return (
set({ ...value, name: e.target.value })} /> set({ ...value, email: e.target.value })} />
); } ``` ## Typed Schema Builder The `mnemonicSchema` builder creates strongly-typed schemas where TypeScript types are inferred from the schema definition, providing a single source of truth. ```tsx import { mnemonicSchema, defineKeySchema, defineMnemonicKey, createSchemaRegistry, useMnemonicKey, MnemonicProvider, } from "react-mnemonic/schema"; // Build typed schema - TypeScript type is inferred automatically const userSettingsSchema = mnemonicSchema.object({ theme: mnemonicSchema.enum(["light", "dark", "system"]), fontSize: mnemonicSchema.integer({ minimum: 10, maximum: 24 }), locale: mnemonicSchema.string(), notifications: mnemonicSchema.object({ email: mnemonicSchema.boolean(), push: mnemonicSchema.boolean(), frequency: mnemonicSchema.optional(mnemonicSchema.enum(["daily", "weekly", "never"])), }), }); // Inferred type: { theme: "light" | "dark" | "system"; fontSize: number; ... } // Create versioned key schema const settingsKeySchema = defineKeySchema("settings", 1, userSettingsSchema); // Create reusable key descriptor bound to the schema const settingsKey = defineMnemonicKey(settingsKeySchema, { defaultValue: { theme: "system", fontSize: 14, locale: "en-US", notifications: { email: true, push: false }, }, }); const registry = createSchemaRegistry({ schemas: [settingsKeySchema], }); function SettingsPage() { // Type-safe: value is fully typed from the schema const { value, set } = useMnemonicKey(settingsKey); return (
set({ ...value, fontSize: parseInt(e.target.value) })} />
); } ``` ## Schema Migrations Define migrations to automatically transform data when schema versions change. Migrations run once when reading old data, then persist the upgraded value. ```tsx import { createSchemaRegistry, defineKeySchema, defineMigration, mnemonicSchema, MnemonicProvider, useMnemonicKey, type MigrationRule, } from "react-mnemonic/schema"; // Version 1 schema const userSchemaV1 = defineKeySchema("user", 1, mnemonicSchema.object({ name: mnemonicSchema.string(), })); // Version 2 schema (split name into firstName/lastName) const userSchemaV2 = defineKeySchema("user", 2, mnemonicSchema.object({ firstName: mnemonicSchema.string(), lastName: mnemonicSchema.string(), migratedAt: mnemonicSchema.string(), })); // Type-safe migration from v1 to v2 const userV1ToV2 = defineMigration(userSchemaV1, userSchemaV2, (v1) => { const parts = v1.name.split(" "); return { firstName: parts[0] || "", lastName: parts.slice(1).join(" ") || "", migratedAt: new Date().toISOString(), }; }); // Write-time normalizer (same version): runs on every write const trimUserV2: MigrationRule = { key: "user", fromVersion: 2, toVersion: 2, migrate: (value) => { const user = value as { firstName: string; lastName: string; migratedAt: string }; return { ...user, firstName: user.firstName.trim(), lastName: user.lastName.trim(), }; }, }; const registry = createSchemaRegistry({ schemas: [userSchemaV1, userSchemaV2], migrations: [userV1ToV2, trimUserV2], }); function App() { return ( ); } // When reading a v1 user, it's automatically migrated to v2 function UserProfile() { const { value, set } = useMnemonicKey("user", { defaultValue: { firstName: "", lastName: "", migratedAt: "" }, }); return (
set({ ...value, firstName: e.target.value })} placeholder="First name" /> set({ ...value, lastName: e.target.value })} placeholder="Last name" />
); } ``` ## Reconciliation Use the `reconcile` option for conditional, read-time adjustments that should apply after decode and migration, without requiring a full schema version bump. ```tsx import { useMnemonicKey } from "react-mnemonic/schema"; interface AppSettings { theme: "light" | "dark"; marketingOptIn: boolean; newFeatureEnabled: boolean; } function SettingsManager() { const { value, set } = useMnemonicKey("app-settings", { defaultValue: { theme: "light", marketingOptIn: false, newFeatureEnabled: true, }, reconcile: (persisted, { persistedVersion, latestVersion }) => ({ ...persisted, // Force new feature on for users who haven't seen it yet newFeatureEnabled: persistedVersion < (latestVersion ?? 1) ? true : persisted.newFeatureEnabled, // Apply new default for marketing opt-in on older data marketingOptIn: persistedVersion < 2 ? true : persisted.marketingOptIn, }), }); return (
); } ``` ## Recovery Helpers Use `useMnemonicRecovery` for namespace-scoped recovery actions like clearing all data, clearing specific keys, or clearing keys matching a pattern. ```tsx import { useMnemonicRecovery, MnemonicProvider } from "react-mnemonic/schema"; function RecoveryPanel() { const { namespace, canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching, } = useMnemonicRecovery({ onRecover: (event) => { console.log(`Recovery action "${event.action}" cleared:`, event.clearedKeys); // Send to analytics, show toast, etc. }, }); const handleClearFilters = () => { // Clear all keys starting with "filter-" clearMatching((key) => key.startsWith("filter-")); }; const handleFactoryReset = () => { if (confirm("Clear all app data? This cannot be undone.")) { clearAll(); } }; const handleClearSpecific = () => { clearKeys(["user-preferences", "cached-data", "session-state"]); }; return (

Namespace: {namespace}

Keys: {listKeys().join(", ") || "(none)"}

); } ``` ## DevTools Integration Enable DevTools for browser console debugging. Access providers through `window.__REACT_MNEMONIC_DEVTOOLS__` to inspect, modify, and clear persisted state. ```tsx import { MnemonicProvider } from "react-mnemonic"; function App() { return ( ); } // In browser console: // const dt = window.__REACT_MNEMONIC_DEVTOOLS__ // dt.list() // List all registered providers // const provider = dt.resolve("my-app") // provider.dump() // Show all keys and values // provider.get("theme") // Read decoded value // provider.set("theme", "dark") // Write value // provider.remove("theme") // Delete key // provider.clear() // Clear all keys in namespace // provider.keys() // List all keys ``` ## JSON Schema Validation Use `validateJsonSchema` and `compileSchema` for standalone JSON Schema validation. The library supports a subset of JSON Schema sufficient for localStorage state management. ```tsx import { validateJsonSchema, compileSchema, type JsonSchema } from "react-mnemonic/schema"; const userSchema: JsonSchema = { type: "object", properties: { name: { type: "string", minLength: 1, maxLength: 100 }, age: { type: "integer", minimum: 0, maximum: 150 }, email: { type: "string" }, roles: { type: "array", items: { enum: ["admin", "user", "guest"] }, minItems: 1, }, }, required: ["name", "email"], additionalProperties: false, }; // One-off validation const errors = validateJsonSchema( { name: "Alice", email: "alice@example.com", age: 30, roles: ["user"] }, userSchema ); if (errors.length > 0) { errors.forEach((err) => { console.error(`${err.path}: ${err.message} (${err.keyword})`); }); } else { console.log("Valid!"); } // Pre-compiled validator for repeated use (cached automatically) const validate = compileSchema(userSchema); const result = validate({ name: "", email: "test@example.com" }); // result: [{ path: "/name", message: "String length 0 is less than minLength 1", keyword: "minLength" }] ``` ## Summary React Mnemonic is ideal for applications that need persistent client-side state with type safety and SSR compatibility. Common use cases include user preferences (themes, layouts, sidebar states), form drafts, shopping cart data, feature flags, and any UI state that should survive page reloads. The library excels when you need schema validation and automatic migrations to safely evolve data shapes over time. Integration patterns typically involve wrapping your app in a `MnemonicProvider` at the root level, defining key descriptors at module scope for reuse across components, and optionally setting up a schema registry for validation and migrations. For testing, provide a mock `StorageLike` implementation. For SSR frameworks like Next.js or Remix, use the `ssr` options to control server placeholders and hydration timing. The DevTools integration helps during development for inspecting and debugging persisted state directly from the browser console.