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) => ({