Skip to content

feat: architect local state persistence with encrypted IndexedDB - #102

Merged
Lakes41 merged 2 commits into
Adamantine-guild:mainfrom
DevSolex:feat/encrypted-indexeddb-persistence-75
Jul 29, 2026
Merged

feat: architect local state persistence with encrypted IndexedDB#102
Lakes41 merged 2 commits into
Adamantine-guild:mainfrom
DevSolex:feat/encrypted-indexeddb-persistence-75

Conversation

@DevSolex

Copy link
Copy Markdown
Contributor

Summary

Closes #75

Implements a robust async client-side storage manager using IndexedDB with AES-GCM encryption for sensitive cached data, replacing direct localStorage reads/writes for user preferences and transaction history.

Changes

New files

  • lib/store/persistence/idb.ts — Encrypted IDB key-value store using Dexie + Web Crypto API (AES-GCM 256-bit, non-extractable key). Degrades gracefully to an in-memory store on SSR or when IndexedDB is blocked (private browsing).
  • lib/store/userSettingsStore.ts — Async store for theme preference, custom token lists, and arbitrary UI preferences. Runs a one-time migration from localStorage on first access.
  • lib/store/transactionHistoryStore.ts — Async encrypted store for local transaction history. Prepend-latest ordering, configurable cap (500 entries), upsert-by-hash, one-time migration from localStorage.

Modified files

  • lib/theme.tslocalStorage is now bootstrap-only (no-flash inline script + synchronous first-paint read). All writes go to IDB.
  • components/ThemeToggle.tsx — Theme writes routed through UserSettingsStore (IDB) instead of localStorage.

Tests

  • lib/store/persistence/idb.test.ts — 18 tests: MemoryPersistenceStore CRUD, migrateFromLocalStorage idempotency, singleton behaviour.
  • lib/store/persistence/userSettingsStore.test.ts — 18 tests: theme, custom tokens (dedup, case-insensitive remove), preferences, getAll, clear, singleton.
  • lib/store/persistence/transactionHistoryStore.test.ts — 18 tests: CRUD, upsert, trim to maxEntries, chain filtering, status updates, singleton.
  • components/ThemeToggle.test.tsx — Updated to assert IDB persistence (not localStorage) on toggle.

Acceptance criteria

  • App state loads asynchronously without blocking UI rendering
  • Exceeds the 5 MB browser limit safely (IndexedDB)
  • Sensitive cached data (tx history) encrypted at rest using local browser crypto keys
  • Degrades gracefully to memory storage if IndexedDB is blocked by private browsing settings

Test results

 Adamantine-guild#75)

- Add lib/store/persistence/idb.ts: async IDB key-value store with AES-GCM
  encryption via Web Crypto API; falls back to MemoryPersistenceStore on
  SSR or when IndexedDB is blocked (private browsing)
- Add lib/store/userSettingsStore.ts: async store for theme, custom token
  lists, and arbitrary preferences; one-time migration from localStorage
- Add lib/store/transactionHistoryStore.ts: async encrypted store for
  local transaction history cache; prepend-latest, configurable cap (500),
  upsert-by-hash, one-time migration from localStorage
- Update lib/theme.ts: document that writes go to IDB; localStorage is
  now bootstrap-only (no-flash script + first-paint sync read)
- Update components/ThemeToggle.tsx: all theme writes go through
  UserSettingsStore (IDB) instead of localStorage directly
- Add 54 tests covering: MemoryPersistenceStore CRUD, migrateFromLocalStorage
  idempotency, singleton behaviour, UserSettingsStore theme/tokens/prefs,
  TransactionHistoryStore CRUD/upsert/trim/filter
- Update ThemeToggle.test.tsx to assert IDB persistence instead of
  localStorage writes

Acceptance criteria met:
  ✓ App state loads asynchronously without blocking UI rendering
  ✓ No 5 MB quota limit (IndexedDB)
  ✓ Sensitive cached data (tx history) encrypted at rest with browser
    crypto keys (AES-GCM 256-bit, non-extractable)
  ✓ Degrades gracefully to MemoryPersistenceStore when IndexedDB is
    blocked or unavailable
@Lakes41

Lakes41 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

This PR cannot be merged automatically because it has merge conflicts.

Please update the branch with the latest base branch and resolve the conflicts.

After the conflicts are resolved and checks pass, the automation can review it again.

Conflict: upstream added a Zustand slippage store to userSettingsStore.ts
while this branch added an IDB-encrypted async store.

Resolution: combined both into a single file —
  - useUserSettingsStore (Zustand + persist): slippage tolerance with
    validation helpers and constants, used by SlippageSettings component
  - UserSettingsStore / getUserSettingsStore: IDB-backed async store for
    theme, custom tokens, and arbitrary preferences (issue Adamantine-guild#75)

All 162 tests pass.
@Lakes41
Lakes41 merged commit 77d781c into Adamantine-guild:main Jul 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Architect Local State Persistence with Encrypted IndexedDB

2 participants