From da751fd73caab39a190343d944647ab60a2f162b Mon Sep 17 00:00:00 2001 From: codytseng Date: Thu, 26 Feb 2026 22:34:39 +0800 Subject: [PATCH] fix: resolve duplicate notifications in notification center Use compareEvents (which considers both created_at and id) instead of simple created_at comparison for sorting events in timeline subscription and load-more logic. This ensures consistent ordering with mergeTimelines and handleNewEvent deduplication, preventing duplicates when events share the same timestamp. Also add Set-based dedup in notification list memo as an additional safety net. Co-Authored-By: Claude Opus 4.6 --- src/components/NotificationList/index.tsx | 8 +++++++- src/services/client.service.ts | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/NotificationList/index.tsx b/src/components/NotificationList/index.tsx index 1764f22..0ee76d0 100644 --- a/src/components/NotificationList/index.tsx +++ b/src/components/NotificationList/index.tsx @@ -213,7 +213,13 @@ const NotificationList = forwardRef((_, ref) => { }, [timelineKey, until, pubkey, setEvents, setUntil]) const notifications = useMemo(() => { - const filteredEvents = events.filter((evt) => evt.pubkey !== pubkey) + const idSet = new Set() + const filteredEvents = events.filter((evt) => { + if (evt.pubkey === pubkey) return false + if (idSet.has(evt.id)) return false + idSet.add(evt.id) + return true + }) if (storedEvents.length === 0) return filteredEvents const filteredStoredEvents = storedEvents.filter((evt) => evt.pubkey !== pubkey) diff --git a/src/services/client.service.ts b/src/services/client.service.ts index e435512..18f05ad 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -408,7 +408,7 @@ class ClientService extends EventTarget { events.push(evt) }) }) - return events.sort((a, b) => b.created_at - a.created_at).slice(0, limit) + return events.sort((a, b) => compareEvents(b, a)).slice(0, limit) } subscribe( @@ -655,11 +655,11 @@ class ClientService extends EventTarget { return onEvents([...events], !!eosedAt) } if (!eosed) { - events = events.sort((a, b) => b.created_at - a.created_at).slice(0, filter.limit) + events = events.sort((a, b) => compareEvents(b, a)).slice(0, filter.limit) return onEvents([...events.concat(cachedEvents).slice(0, filter.limit)], false) } - events = events.sort((a, b) => b.created_at - a.created_at).slice(0, filter.limit) + events = events.sort((a, b) => compareEvents(b, a)).slice(0, filter.limit) if (needSaveToDb) { indexedDb.putEvents( events.map((evt) => ({ event: evt, relays: this.getEventHints(evt.id) })) @@ -732,7 +732,7 @@ class ClientService extends EventTarget { events.forEach((evt) => { this.addEventToCache(evt) }) - events = events.sort((a, b) => b.created_at - a.created_at).slice(0, limit) + events = events.sort((a, b) => compareEvents(b, a)).slice(0, limit) // Prevent concurrent requests from duplicating the same event const lastRefCreatedAt = refs.length > 0 ? refs[refs.length - 1][1] : dayjs().unix()