diff --git a/src/app/hooks/timeline/useProcessedTimeline.test.tsx b/src/app/hooks/timeline/useProcessedTimeline.test.tsx index 026cf89f93..7971cba882 100644 --- a/src/app/hooks/timeline/useProcessedTimeline.test.tsx +++ b/src/app/hooks/timeline/useProcessedTimeline.test.tsx @@ -187,6 +187,12 @@ const dividerIds = (processed: ProcessedEvent[]) => processed.filter((e) => e.willRenderNewDivider).map((e) => e.id); describe('useProcessedTimeline new-messages divider', () => { + it('deduplicates events from overlapping timeline windows', () => { + const event = createEvent({ id: '$duplicate' }); + + expect(renderedIds(processTimeline([event, event], undefined))).toEqual(['$duplicate']); + }); + it('renders an event that is still encrypted', () => { const processed = processTimeline( [ diff --git a/src/app/hooks/timeline/useProcessedTimeline.ts b/src/app/hooks/timeline/useProcessedTimeline.ts index 5e99efe8a1..fbd4709a2b 100644 --- a/src/app/hooks/timeline/useProcessedTimeline.ts +++ b/src/app/hooks/timeline/useProcessedTimeline.ts @@ -447,6 +447,7 @@ const mergeRelationEdits = ( type TimelineProcessingState = { prevEvent?: MatrixEvent; prevIteratedEventId?: string; + seenEventIds: Set; isPrevRendered: boolean; newDivider: boolean; dayDivider: boolean; @@ -459,6 +460,7 @@ type TimelineProcessingOptions = Omit< ResolvedHiddenEventSettings; const emptyProcessingState = (): TimelineProcessingState => ({ + seenEventIds: new Set(), isPrevRendered: false, newDivider: false, dayDivider: false, @@ -488,7 +490,7 @@ const processTimelineItems = ( hideMemberInReadOnly, skipThreadFilter, } = options; - const state = { ...initialState }; + const state = { ...initialState, seenEventIds: new Set(initialState.seenEventIds) }; const result: ProcessedEvent[] = []; for (const item of items) { @@ -498,6 +500,8 @@ const processTimelineItems = ( const { threadRootId } = mEvent; const mEventId = mEvent.getId(); if (!mEventId) continue; + if (state.seenEventIds.has(mEventId)) continue; + state.seenEventIds.add(mEventId); if (!state.newDivider && readUptoEventId) { state.newDivider = state.prevIteratedEventId === readUptoEventId;