@@ -428,13 +428,25 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra
428428
429429 const rootEvent = room . findEventById ( threadRootId ) ;
430430
431- // When the drawer is opened with classic sync, room.createThread() may have
432- // been called with empty initialEvents so thread.events only has the root.
433- // Backfill events from the main room timeline into the Thread object so the
434- // authoritative source is populated for subsequent renders and receipts.
431+ // When the drawer is opened with classic sync (no server-side thread support),
432+ // room.createThread() may have been called with empty initialEvents so
433+ // thread.events only has the root. Backfill events from the main room
434+ // timeline so the authoritative source is populated for subsequent renders.
435+ //
436+ // IMPORTANT: skip this backfill when server-side thread support is active
437+ // (initialEventsFetched starts false). In that case the SDK will call
438+ // updateThreadMetadata() → resetLiveTimeline() + paginateEventTimeline()
439+ // automatically. Calling thread.addEvents() ourselves first would trigger
440+ // that same cascade prematurely and cause a flood of
441+ // "EventTimelineSet.addEventToTimeline: Ignoring event=…" warnings because
442+ // canContain() fails while the timeline is in the middle of being reset and
443+ // repopulated.
435444 useEffect ( ( ) => {
436445 const thread = room . getThread ( threadRootId ) ;
437446 if ( ! thread ) return ;
447+ // initialEventsFetched === false ↔ Thread.hasServerSideSupport is set.
448+ // The SDK handles initialization itself; our manual backfill must not run.
449+ if ( ! thread . initialEventsFetched ) return ;
438450 const hasRepliesInThread = thread . events . some (
439451 ( ev ) => ev . getId ( ) !== threadRootId && ! reactionOrEditEvent ( ev )
440452 ) ;
0 commit comments