From 233c5a34d33a2089ad410fef648f306e09b06d25 Mon Sep 17 00:00:00 2001 From: CleanDev-Fix <219162456+CleanDev-Fix@users.noreply.github.com> Date: Sun, 21 Jun 2026 08:44:32 -0400 Subject: [PATCH] fix: deduplicate dashboard notification events --- dashboard/src/store/eventStore.test.tsx | 31 +++++++++++++++++++++++++ dashboard/src/store/eventStore.ts | 17 ++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/dashboard/src/store/eventStore.test.tsx b/dashboard/src/store/eventStore.test.tsx index 5f51174..c7771aa 100644 --- a/dashboard/src/store/eventStore.test.tsx +++ b/dashboard/src/store/eventStore.test.tsx @@ -8,6 +8,37 @@ import { useEventStore } from '../store/eventStore'; import { generateMockEvents } from '../utils/eventData'; describe('event store selective subscriptions', () => { + it('deduplicates events before rendering notification rows', () => { + const [firstEvent, secondEvent] = generateMockEvents(2); + + useEventStore.getState().setEvents([firstEvent, firstEvent, secondEvent]); + + render( +
+ +
+ ); + + expect(screen.getAllByRole('article')).toHaveLength(2); + expect(screen.getAllByText(`Ledger ${firstEvent.ledger}`)).toHaveLength(1); + expect(screen.getAllByText(`Ledger ${secondEvent.ledger}`)).toHaveLength(1); + }); + + it('does not append duplicate notifications after repeated refresh data', () => { + const [firstEvent, secondEvent] = generateMockEvents(2); + + useEventStore.getState().setEvents([firstEvent]); + useEventStore.getState().appendEvents([firstEvent, secondEvent, secondEvent]); + + const storedEvents = useEventStore.getState().events; + + expect(storedEvents).toHaveLength(2); + expect(storedEvents.map((event) => event.eventId)).toEqual([ + firstEvent.eventId, + secondEvent.eventId, + ]); + }); + it('filter updates do not require reloading the full event collection', async () => { useEventStore.setState({ events: generateMockEvents(100), diff --git a/dashboard/src/store/eventStore.ts b/dashboard/src/store/eventStore.ts index 1bdb473..5dcd525 100644 --- a/dashboard/src/store/eventStore.ts +++ b/dashboard/src/store/eventStore.ts @@ -16,6 +16,19 @@ interface EventStoreState { setError: (error: string | null) => void; } +function dedupeEventsById(events: BlockchainEvent[]): BlockchainEvent[] { + const seenEventIds = new Set(); + + return events.filter((event) => { + if (seenEventIds.has(event.eventId)) { + return false; + } + + seenEventIds.add(event.eventId); + return true; + }); +} + export const useEventStore = create((set) => ({ events: [], filters: { @@ -25,10 +38,10 @@ export const useEventStore = create((set) => ({ }, isLoading: false, error: null, - setEvents: (events) => set({ events }), + setEvents: (events) => set({ events: dedupeEventsById(events) }), appendEvents: (events) => set((state) => ({ - events: [...state.events, ...events], + events: dedupeEventsById([...state.events, ...events]), })), setSearch: (search) => set((state) => ({