Skip to content

Commit 38057ee

Browse files
committed
fix: reset live timeline on room navigation and reconnect
The Matrix SDK for sliding sync deliberately does not call resetLiveTimeline() on limited:true (the handling is in a commented-out TODO block). This means events from a previous visit remain in the live timeline and new events get appended after them, with the backward pagination token pointing to the gap between old and new events rather than to before all events. The result is that new messages appear out of order or the gap is unreachable via the UI. Two fixes: 1. useSlidingSyncActiveRoom: reset the live timeline synchronously before the subscription request is sent on room navigation. This ensures the fresh initial:true response populates a clean timeline, not one polluted with events from the previous visit. Also removes the 100ms delay that was added as an incomplete workaround for the same issue. 2. SlidingSyncManager.onLifecycle: in RequestFinished state (fires before any room data listeners run), reset the live timeline for active-room subscriptions that receive initial:true or limited:true. This covers the reconnect/background case where the pos token expires and all active rooms get a fresh initial:true in the same sync cycle.
1 parent 9f45a73 commit 38057ee

2 files changed

Lines changed: 24 additions & 11 deletions

File tree

src/app/hooks/useSlidingSyncActiveRoom.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,7 @@ export const useSlidingSyncActiveRoom = (): void => {
2424
const manager = getSlidingSyncManager(mx);
2525
if (!manager) return undefined;
2626

27-
// Wait for the room to be initialized from list sync before subscribing
28-
// with the full timeline limit. This prevents timeline ordering issues where
29-
// the room might be receiving events from list expansion while we're also
30-
// trying to load a large timeline, causing events to be added out of order.
31-
const timeoutId = setTimeout(() => {
32-
manager.subscribeToRoom(roomId);
33-
}, 100);
34-
35-
return () => {
36-
clearTimeout(timeoutId);
37-
};
27+
manager.subscribeToRoom(roomId);
28+
return undefined;
3829
}, [mx, roomId]);
3930
};

src/client/slidingSync.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
MSC3575List,
99
MSC3575RoomData,
1010
MSC3575RoomSubscription,
11+
MSC3575SlidingSyncResponse,
1112
MSC3575_WILDCARD,
1213
RoomMemberEvent,
1314
SlidingSync,
@@ -350,6 +351,27 @@ export class SlidingSyncManager {
350351
return;
351352
}
352353

354+
// Before room data is processed, reset live timelines for active rooms that
355+
// are receiving a full refresh (initial: true) or a post-gap update
356+
// (limited: true). The SDK deliberately does not call resetLiveTimeline() for
357+
// sliding sync, so events from previous visits accumulate in the live
358+
// timeline alongside new events. Resetting here — before the SDK's
359+
// onRoomData listener runs — ensures the fresh batch lands on a clean
360+
// timeline with a correct backward pagination token.
361+
if (state === SlidingSyncState.RequestFinished && resp && !err) {
362+
const rooms = (resp as MSC3575SlidingSyncResponse).rooms ?? {};
363+
Object.entries(rooms)
364+
.filter(([, roomData]) => roomData.initial || roomData.limited)
365+
.filter(([roomId]) => this.activeRoomSubscriptions.has(roomId))
366+
.forEach(([roomId]) => {
367+
const room = this.mx.getRoom(roomId);
368+
if (!room) return;
369+
const timelineSet = room.getUnfilteredTimelineSet();
370+
if (timelineSet.getLiveTimeline().getEvents().length === 0) return;
371+
timelineSet.resetLiveTimeline();
372+
});
373+
}
374+
353375
if (err || !resp || state !== SlidingSyncState.Complete) return;
354376

355377
// Track what changed in this sync cycle

0 commit comments

Comments
 (0)