From 6b747f5aef49d062b724859c9aed5306c62cd1e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 09:24:33 +0000 Subject: [PATCH] refactor: simplify group name fix by removing IndexedDB caching The eventStore already caches kind 39000 events, so the additional IndexedDB layer adds complexity without significant benefit. Simplified approach: 1. Sync extraction: Extract names directly from groupMetadataMap when kind 39000 events are already in eventStore (instant) 2. Async only when needed: Profile fallback runs only for groups where groupId is a pubkey but no NIP-29 metadata exists Removed: - CachedGroupMetadata interface and DB version 18 - loadCachedGroupMetadata, cacheGroupMetadata, cacheGroupMetadataBatch - cacheLoaded state and cache loading effect https://claude.ai/code/session_01CCxAcUsRBkWSL6as1wtFoA --- src/components/GroupListViewer.tsx | 69 +++--------------------- src/lib/chat/group-metadata-helpers.ts | 73 -------------------------- src/services/db.ts | 30 ----------- 3 files changed, 7 insertions(+), 165 deletions(-) diff --git a/src/components/GroupListViewer.tsx b/src/components/GroupListViewer.tsx index 88eedd4..bde249d 100644 --- a/src/components/GroupListViewer.tsx +++ b/src/components/GroupListViewer.tsx @@ -18,8 +18,6 @@ import * as VisuallyHidden from "@radix-ui/react-visually-hidden"; import { resolveGroupMetadata, extractMetadataFromEvent, - loadCachedGroupMetadata, - cacheGroupMetadataBatch, getGroupCacheKey, isValidPubkey, type ResolvedGroupMetadata, @@ -346,42 +344,16 @@ export function GroupListViewer({ identifier }: GroupListViewerProps) { ); }, [groups]); - // Resolve metadata with a multi-tier approach to prevent flicker: - // 1. Load from IndexedDB cache immediately (fastest, prevents flicker on subsequent visits) - // 2. Use sync extraction from kind 39000 events (fast, no async) - // 3. Only async-resolve for groups needing profile fallback (slow, only when needed) + // Resolve metadata with sync extraction + async-only-when-needed: + // 1. Sync extraction from kind 39000 events (fast, no async) + // 2. Only async-resolve for groups needing profile fallback (groupId is pubkey) const [resolvedMetadataMap, setResolvedMetadataMap] = useState< Map >(new Map()); - // Track if cache has been loaded to avoid flashing groupId before cache loads - const [cacheLoaded, setCacheLoaded] = useState(false); - - // Step 1: Load from IndexedDB cache immediately on mount (fast path for returning users) + // Sync extraction from kind 39000 + async profile fallback only when needed useEffect(() => { - if (groups.length === 0) { - setCacheLoaded(true); - return; - } - - loadCachedGroupMetadata(groups).then((cached) => { - if (cached.size > 0) { - setResolvedMetadataMap((prev) => { - // Merge cached with existing (cached is baseline, existing may have fresher data) - const merged = new Map(cached); - for (const [key, value] of prev) { - merged.set(key, value); - } - return merged; - }); - } - setCacheLoaded(true); - }); - }, [groups]); - - // Step 2 & 3: Sync extraction from kind 39000 + async profile fallback - useEffect(() => { - if (groups.length === 0 || !cacheLoaded) return; + if (groups.length === 0) return; // Determine which groups need async resolution (no NIP-29 metadata, groupId is pubkey) const needsAsyncResolution: Array<{ groupId: string; relayUrl: string }> = @@ -417,7 +389,7 @@ export function GroupListViewer({ identifier }: GroupListViewerProps) { // No NIP-29 metadata, not a pubkey - use groupId as name updated.set(key, { name: group.groupId, source: "fallback" }); } - // For pubkey groups without metadata, keep existing cached value (don't overwrite) + // For pubkey groups without metadata, keep existing value (don't overwrite) } return updated; @@ -456,39 +428,12 @@ export function GroupListViewer({ identifier }: GroupListViewerProps) { } return updated; }); - - // Cache the resolved metadata for future visits - cacheGroupMetadataBatch(resolved); } }; resolveProfileMetadata(); } - - // Cache sync-resolved NIP-29 metadata - const toCache: Array<{ - relayUrl: string; - groupId: string; - metadata: ResolvedGroupMetadata; - }> = []; - - for (const group of groups) { - if (group.groupId === "_") continue; - - const metadataEvent = groupMetadataMap?.get(group.groupId); - if (metadataEvent && metadataEvent.kind === 39000) { - toCache.push({ - relayUrl: group.relayUrl, - groupId: group.groupId, - metadata: extractMetadataFromEvent(group.groupId, metadataEvent), - }); - } - } - - if (toCache.length > 0) { - cacheGroupMetadataBatch(toCache); - } - }, [groups, groupMetadataMap, cacheLoaded]); + }, [groups, groupMetadataMap]); // Subscribe to latest messages (kind 9) for all groups to get recency // NOTE: Separate filters needed to ensure we get 1 message per group (not N total across all groups) diff --git a/src/lib/chat/group-metadata-helpers.ts b/src/lib/chat/group-metadata-helpers.ts index f33a72e..b13b5ec 100644 --- a/src/lib/chat/group-metadata-helpers.ts +++ b/src/lib/chat/group-metadata-helpers.ts @@ -2,7 +2,6 @@ import { firstValueFrom } from "rxjs"; import { kinds } from "nostr-tools"; import { profileLoader } from "@/services/loaders"; import { getProfileContent } from "applesauce-core/helpers"; -import db, { type CachedGroupMetadata } from "@/services/db"; import type { NostrEvent } from "@/types/nostr"; /** @@ -49,78 +48,6 @@ export function extractMetadataFromEvent( }; } -/** - * Load cached group metadata from IndexedDB - * Returns a Map of cache key -> metadata for fast lookups - */ -export async function loadCachedGroupMetadata( - groups: Array<{ groupId: string; relayUrl: string }>, -): Promise> { - const keys = groups.map((g) => getGroupCacheKey(g.relayUrl, g.groupId)); - const cached = await db.groupMetadata.bulkGet(keys); - - const map = new Map(); - for (const entry of cached) { - if (entry) { - map.set(entry.key, { - name: entry.name, - description: entry.description, - icon: entry.icon, - source: entry.source, - }); - } - } - return map; -} - -/** - * Save resolved group metadata to IndexedDB cache - */ -export async function cacheGroupMetadata( - relayUrl: string, - groupId: string, - metadata: ResolvedGroupMetadata, -): Promise { - const key = getGroupCacheKey(relayUrl, groupId); - const entry: CachedGroupMetadata = { - key, - groupId, - relayUrl, - name: metadata.name, - description: metadata.description, - icon: metadata.icon, - source: metadata.source, - updatedAt: Date.now(), - }; - await db.groupMetadata.put(entry); -} - -/** - * Batch save multiple group metadata entries - */ -export async function cacheGroupMetadataBatch( - entries: Array<{ - relayUrl: string; - groupId: string; - metadata: ResolvedGroupMetadata; - }>, -): Promise { - const now = Date.now(); - const records: CachedGroupMetadata[] = entries.map( - ({ relayUrl, groupId, metadata }) => ({ - key: getGroupCacheKey(relayUrl, groupId), - groupId, - relayUrl, - name: metadata.name, - description: metadata.description, - icon: metadata.icon, - source: metadata.source, - updatedAt: now, - }), - ); - await db.groupMetadata.bulkPut(records); -} - /** * Resolve group metadata with profile fallback * diff --git a/src/services/db.ts b/src/services/db.ts index c2f1942..4dd8206 100644 --- a/src/services/db.ts +++ b/src/services/db.ts @@ -108,17 +108,6 @@ export interface GrimoireZap { comment?: string; // Optional zap comment/message } -export interface CachedGroupMetadata { - key: string; // Primary key: "relayUrl'groupId" - groupId: string; - relayUrl: string; - name?: string; - description?: string; - icon?: string; - source: "nip29" | "profile" | "fallback"; - updatedAt: number; -} - class GrimoireDb extends Dexie { profiles!: Table; nip05!: Table; @@ -132,7 +121,6 @@ class GrimoireDb extends Dexie { spellbooks!: Table; lnurlCache!: Table; grimoireZaps!: Table; - groupMetadata!: Table; constructor(name: string) { super(name); @@ -400,24 +388,6 @@ class GrimoireDb extends Dexie { grimoireZaps: "&eventId, senderPubkey, timestamp, [senderPubkey+timestamp]", }); - - // Version 18: Add group metadata caching to prevent name flicker - this.version(18).stores({ - profiles: "&pubkey", - nip05: "&nip05", - nips: "&id", - relayInfo: "&url", - relayAuthPreferences: "&url", - relayLists: "&pubkey, updatedAt", - relayLiveness: "&url", - blossomServers: "&pubkey, updatedAt", - spells: "&id, alias, createdAt, isPublished, deletedAt", - spellbooks: "&id, slug, title, createdAt, isPublished, deletedAt", - lnurlCache: "&address, fetchedAt", - grimoireZaps: - "&eventId, senderPubkey, timestamp, [senderPubkey+timestamp]", - groupMetadata: "&key, groupId, relayUrl, updatedAt", - }); } }