openDatabase() now catches errors from idb's openDB() (which throws
synchronously when indexedDB is undefined) and returns null. All
consumers — profileCache, nip05Cache, dmMessageStore — check for null
and silently degrade to in-memory only.
The DM message store also stops re-throwing errors, which previously
could produce unhandled rejections in DMProvider.
The IndexedDB profile cache (d9e943d0) introduced a regression where
parseAuthorEvent() — which runs the full Zod n.metadata().parse() chain —
was called synchronously on every render via TanStack Query's initialData
option. With 50+ NoteCards in a feed and no React.memo, this caused ~970ms
of Zod validation + ~1.3s of Nostrify core overhead per tab navigation.
Fix by:
1. Storing pre-parsed metadata in ProfileCacheEntry so useAuthor can
provide initialData without re-running Zod on every render
2. Wrapping NoteCard in React.memo to prevent re-renders when props
haven't changed (event objects are referentially stable from the
query cache)
Replace three separate per-hostname databases (nostr-nip05-cache-*,
nostr-profile-cache-*, nostr-dm-store-*) with one shared 'ditto'
database containing three object stores: nip05, profiles, messages.
A single db.ts module manages the connection and upgrade logic, and
caches the promise so only one connection is created per page lifetime.
Profile events (kind 0) are now persisted in IndexedDB and hydrated into
memory before React renders. useAuthor seeds TanStack Query with
initialData from the cache so names, avatars, and metadata render
instantly on repeat visits instead of showing skeletons.
Cache writes happen from three sites: useAuthor (single profile fetch),
useAuthors (batch fetch), and useFeed (community feed metadata prefetch).
Only newer events overwrite older ones to prevent out-of-order downgrades.
Staleness tiers match NIP-05: < 5 min fresh, 5 min - 7 d background
refetch, > 7 d expired (skeleton until fresh data arrives).