From e40f820fdcbaa6bc4a23c94e1a04edcb3417a5dd Mon Sep 17 00:00:00 2001 From: Gigi Date: Wed, 22 Oct 2025 11:25:30 +0200 Subject: [PATCH] fix: handle empty d-tags separately in bookmark hydration Separate fetching of articles with empty vs non-empty d-tags to work around relay filter handling issues. For empty d-tags, fetch all events of that kind/author and filter client-side. --- src/services/bookmarkController.ts | 81 ++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/src/services/bookmarkController.ts b/src/services/bookmarkController.ts index 81ee1791..7bf60b33 100644 --- a/src/services/bookmarkController.ts +++ b/src/services/bookmarkController.ts @@ -197,30 +197,69 @@ class BookmarkController { // Fetch each group for (const [kind, byPubkey] of filtersByKind) { for (const [pubkey, identifiers] of byPubkey) { - console.log('[BookmarkController] Fetching kind', kind, 'from pubkey', pubkey.slice(0, 8), 'with', identifiers.length, 'identifiers:', identifiers) - await queryEvents( - this.relayPool, - { kinds: [kind], authors: [pubkey], '#d': identifiers }, - { - onEvent: (event) => { - // Check if hydration was cancelled - if (this.hydrationGeneration !== generation) return + // Separate empty and non-empty identifiers + const nonEmptyIdentifiers = identifiers.filter(id => id && id.length > 0) + const hasEmptyIdentifier = identifiers.some(id => !id || id.length === 0) + + console.log('[BookmarkController] Fetching kind', kind, 'from pubkey', pubkey.slice(0, 8), 'with', identifiers.length, 'identifiers:', identifiers, '(non-empty:', nonEmptyIdentifiers.length, 'empty:', hasEmptyIdentifier, ')') + + // Fetch events with non-empty d-tags + if (nonEmptyIdentifiers.length > 0) { + await queryEvents( + this.relayPool, + { kinds: [kind], authors: [pubkey], '#d': nonEmptyIdentifiers }, + { + onEvent: (event) => { + // Check if hydration was cancelled + if (this.hydrationGeneration !== generation) return - const dTag = event.tags?.find((t: string[]) => t[0] === 'd')?.[1] || '' - const coordinate = `${event.kind}:${event.pubkey}:${dTag}` - console.log('[BookmarkController] Hydrated article:', coordinate, getArticleTitle(event) || 'No title') - idToEvent.set(coordinate, event) - idToEvent.set(event.id, event) - - // Add to external event store if available - if (this.externalEventStore) { - this.externalEventStore.add(event) + const dTag = event.tags?.find((t: string[]) => t[0] === 'd')?.[1] || '' + const coordinate = `${event.kind}:${event.pubkey}:${dTag}` + console.log('[BookmarkController] Hydrated article (non-empty d):', coordinate, getArticleTitle(event) || 'No title') + idToEvent.set(coordinate, event) + idToEvent.set(event.id, event) + + // Add to external event store if available + if (this.externalEventStore) { + this.externalEventStore.add(event) + } + + onProgress() } - - onProgress() } - } - ) + ) + } + + // Fetch events with empty d-tag separately (without '#d' filter) + if (hasEmptyIdentifier) { + console.log('[BookmarkController] Fetching events with empty d-tag for kind', kind, 'pubkey', pubkey.slice(0, 8)) + await queryEvents( + this.relayPool, + { kinds: [kind], authors: [pubkey] }, + { + onEvent: (event) => { + // Check if hydration was cancelled + if (this.hydrationGeneration !== generation) return + + // Only process events with empty d-tag + const dTag = event.tags?.find((t: string[]) => t[0] === 'd')?.[1] || '' + if (dTag !== '') return + + const coordinate = `${event.kind}:${event.pubkey}:` + console.log('[BookmarkController] Hydrated article (empty d):', coordinate, getArticleTitle(event) || 'No title') + idToEvent.set(coordinate, event) + idToEvent.set(event.id, event) + + // Add to external event store if available + if (this.externalEventStore) { + this.externalEventStore.add(event) + } + + onProgress() + } + } + ) + } } } console.log('[BookmarkController] Coordinate hydration complete')