Skip to content

Krille/refactor-archive-room-handling - #2217

Merged
krille-chan merged 2 commits into
mainfrom
krille/refactor-archive-room-handling
Sep 2, 2026
Merged

Krille/refactor-archive-room-handling#2217
krille-chan merged 2 commits into
mainfrom
krille/refactor-archive-room-handling

Conversation

@krille-chan

@krille-chan krille-chan commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

closes #2031

Test out with:

Client(
  // ...
 syncFilter: Filter(
    room: RoomFilter(
      includeLeave: true,
      state: StateFilter(lazyLoadMembers: true),
    ),
  ),
);

If you don't change the syncFilter, the behavior should be as before, just that you now have to cache the archive by yourself when requesting it.

@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch from 7320e86 to 44c54ee Compare January 2, 2026 14:32
@codecov

codecov Bot commented Jan 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.08333% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.39%. Comparing base (bb25788) to head (1500205).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
lib/src/client.dart 78.94% 8 Missing ⚠️
lib/src/database/matrix_sdk_database.dart 50.00% 2 Missing ⚠️
lib/src/room.dart 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2217      +/-   ##
==========================================
- Coverage   60.45%   60.39%   -0.07%     
==========================================
  Files         162      162              
  Lines       20572    20561      -11     
==========================================
- Hits        12436    12417      -19     
- Misses       8136     8144       +8     
Files with missing lines Coverage Δ
lib/src/timeline.dart 71.86% <100.00%> (+2.32%) ⬆️
lib/src/room.dart 77.87% <66.66%> (-0.46%) ⬇️
lib/src/database/matrix_sdk_database.dart 90.03% <50.00%> (-1.65%) ⬇️
lib/src/client.dart 78.33% <78.94%> (+<0.01%) ⬆️

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bb25788...1500205. Read the comment docs.

@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch from 44c54ee to 129b959 Compare May 15, 2026 06:52
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch 2 times, most recently from 44634d1 to 84eccf8 Compare June 19, 2026 13:24
@krille-chan
krille-chan marked this pull request as ready for review June 19, 2026 13:24
Comment thread lib/src/client.dart Outdated
Comment thread lib/src/client.dart
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch 3 times, most recently from 4f2d4cf to 8b3c7bf Compare June 26, 2026 08:45
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch from 8b3c7bf to 8bb6523 Compare July 6, 2026 06:33
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes persistence and in-memory representation of left rooms and removes several public APIs; misconfigured sync filters or apps relying on archivedRooms/getRoomById for left rooms could show wrong room lists or lose history.

Overview
Removes the built-in archivedRooms cache (archivedRooms, getArchiveRoomFromCache, clearArchivesFromCache, _storeArchivedRoom). loadArchiveWithTimeline() still returns ephemeral ArchivedRoom lists from a one-off sync but no longer updates client state. getRoomById no longer resolves left rooms unless they appear in rooms.

Optional syncFilter.room.includeLeave: true keeps left rooms in client.rooms with Membership.leave, persists their timeline/state/account data on sync, and skips forgetRoom on leave updates. Without it, behavior stays closer to before: left rooms are removed from the list and the DB is cleared on leave.

Timelines for left rooms load from the DB when included in sync; otherwise history pagination uses inMemoryOnly server fetches via prev_batch. Room.forget() removes the room from rooms instead of the old archive list.

Apps that want archived/left rooms in the main room list must set Client(syncFilter: Filter(room: RoomFilter(includeLeave: true, …))) or keep their own cache after loadArchiveWithTimeline().

Reviewed by Cursor Bugbot for commit 33169d1. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for all 4 issues found in the latest run.

  • ✅ Fixed: Leave removes room with includeLeave
    • Left rooms are now retained in client.rooms with leave membership and prev_batch when includeLeave is enabled.
  • ✅ Fixed: Archived getTimeline skips database
    • getTimeline now reads persisted events from the database for archived rooms as well as active rooms.
  • ✅ Fixed: Leave update skips DB membership
    • storeRoomUpdate now updates existing room rows for leave updates, including membership, prev_batch, and lastEvent.
  • ✅ Fixed: Re-invite skips membership update
    • Existing rooms now update their in-memory and persisted membership when an invite update arrives.

Create PR

Or push these changes by commenting:

@cursor push 20b0a05559
Preview (20b0a05559)
diff --git a/lib/src/client.dart b/lib/src/client.dart
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -2983,12 +2983,26 @@
     }
     // If the membership is "leave" then remove the item and stop here
     else if (membership == Membership.leave) {
-      if (syncFilter.room?.includeLeave == true && !found) {
-        rooms.add(room);
+      final leftUpdate = chatUpdate as LeftRoomUpdate;
+      if (syncFilter.room?.includeLeave == true) {
+        final membershipChanged = found && room.membership != membership;
+        room.membership = membership;
+        room.prev_batch = leftUpdate.timeline?.prevBatch ?? room.prev_batch;
+        if (!found) rooms.add(room);
+        if (membershipChanged) {
+          // ignore: deprecated_member_use_from_same_package
+          room.onUpdate.add(room.id);
+        }
       } else if (found) {
         rooms.removeAt(roomIndex);
       }
     }
+    // Update membership for existing invites.
+    else if (found && chatUpdate is InvitedRoomUpdate) {
+      rooms[roomIndex].membership = membership;
+      // ignore: deprecated_member_use_from_same_package
+      rooms[roomIndex].onUpdate.add(rooms[roomIndex].id);
+    }
     // Update notification, highlight count and/or additional information
     else if (found &&
         chatUpdate is JoinedRoomUpdate &&

diff --git a/lib/src/database/matrix_sdk_database.dart b/lib/src/database/matrix_sdk_database.dart
--- a/lib/src/database/matrix_sdk_database.dart
+++ b/lib/src/database/matrix_sdk_database.dart
@@ -1305,8 +1305,10 @@
                 lastEvent: lastEvent,
               ).toJson(),
       );
-    } else if (roomUpdate is JoinedRoomUpdate) {
+    } else {
       final currentRoom = Room.fromJson(copyMap(currentRawRoom), client);
+      final joinedUpdate = roomUpdate is JoinedRoomUpdate ? roomUpdate : null;
+      final leftUpdate = roomUpdate is LeftRoomUpdate ? roomUpdate : null;
       await _roomsBox.put(
         roomId,
         Room(
@@ -1314,17 +1316,22 @@
           id: roomId,
           membership: membership,
           highlightCount:
-              roomUpdate.unreadNotifications?.highlightCount ??
+              joinedUpdate?.unreadNotifications?.highlightCount ??
               currentRoom.highlightCount,
           notificationCount:
-              roomUpdate.unreadNotifications?.notificationCount ??
+              joinedUpdate?.unreadNotifications?.notificationCount ??
               currentRoom.notificationCount,
-          prev_batch: roomUpdate.timeline?.prevBatch ?? currentRoom.prev_batch,
-          summary: RoomSummary.fromJson(
-            currentRoom.summary.toJson()
-              ..addAll(roomUpdate.summary?.toJson() ?? {}),
-          ),
-          lastEvent: lastEvent,
+          prev_batch:
+              joinedUpdate?.timeline?.prevBatch ??
+              leftUpdate?.timeline?.prevBatch ??
+              currentRoom.prev_batch,
+          summary: joinedUpdate == null
+              ? currentRoom.summary
+              : RoomSummary.fromJson(
+                  currentRoom.summary.toJson()
+                    ..addAll(joinedUpdate.summary?.toJson() ?? {}),
+                ),
+          lastEvent: lastEvent ?? currentRoom.lastEvent,
         ).toJson(),
       );
     }

diff --git a/lib/src/room.dart b/lib/src/room.dart
--- a/lib/src/room.dart
+++ b/lib/src/room.dart
@@ -1701,11 +1701,9 @@
 
     var events = <Event>[];
 
-    if (!isArchived) {
-      await client.database.transaction(() async {
-        events = await client.database.getEventList(this, limit: limit);
-      });
-    }
+    await client.database.transaction(() async {
+      events = await client.database.getEventList(this, limit: limit);
+    });
 
     var chunk = TimelineChunk(events: events);
     // Load the timeline arround eventContextId if set

diff --git a/test/fake_client.dart b/test/fake_client.dart
--- a/test/fake_client.dart
+++ b/test/fake_client.dart
@@ -20,6 +20,7 @@
 Future<Client> getClient({
   Duration sendTimelineEventTimeout = const Duration(minutes: 1),
   String? databasePath,
+  Filter? syncFilter,
 }) async {
   try {
     vodInit ??= vod.init(
@@ -37,6 +38,7 @@
     database: await getDatabase(databasePath: databasePath),
     onSoftLogout: (client) => client.refreshAccessToken(),
     sendTimelineEventTimeout: sendTimelineEventTimeout,
+    syncFilter: syncFilter,
   );
   FakeMatrixApi.client = client;
   await client.checkHomeserver(

diff --git a/test/room_archived_test.dart b/test/room_archived_test.dart
--- a/test/room_archived_test.dart
+++ b/test/room_archived_test.dart
@@ -73,6 +73,122 @@
       expect(eventsFromStore.isEmpty, true);
     });
 
+    test('includeLeave keeps left room in rooms and database', () async {
+      await client.dispose().onError((e, s) {});
+      client = await getClient(
+        syncFilter: Filter(
+          room: RoomFilter(
+            state: StateFilter(lazyLoadMembers: true),
+            includeLeave: true,
+          ),
+        ),
+      );
+      client.rooms.clear();
+      await client.database.clearCache();
+
+      const roomId = '!includeLeaveRoom:example.com';
+      await client.handleSync(
+        SyncUpdate(
+          nextBatch: 't_join',
+          rooms: RoomsUpdate(
+            join: {
+              roomId: JoinedRoomUpdate(
+                timeline: TimelineUpdate(
+                  prevBatch: 'join_batch',
+                  events: [
+                    MatrixEvent(
+                      type: EventTypes.Message,
+                      senderId: '@alice:example.com',
+                      eventId: '\$include-leave-join',
+                      originServerTs: DateTime.fromMillisecondsSinceEpoch(1),
+                      content: {'msgtype': 'm.text', 'body': 'joined'},
+                    ),
+                  ],
+                ),
+              ),
+            },
+          ),
+        ),
+      );
+
+      await client.handleSync(
+        SyncUpdate(
+          nextBatch: 't_leave',
+          rooms: RoomsUpdate(
+            leave: {
+              roomId: LeftRoomUpdate(
+                timeline: TimelineUpdate(
+                  prevBatch: 'leave_batch',
+                  events: [
+                    MatrixEvent(
+                      type: EventTypes.Message,
+                      senderId: '@alice:example.com',
+                      eventId: '\$include-leave-left',
+                      originServerTs: DateTime.fromMillisecondsSinceEpoch(2),
+                      content: {'msgtype': 'm.text', 'body': 'left'},
+                    ),
+                  ],
+                ),
+              ),
+            },
+          ),
+        ),
+      );
+
+      final room = client.getRoomById(roomId);
+      expect(room, isNotNull);
+      expect(room!.membership, Membership.leave);
+      expect(room.prev_batch, 'leave_batch');
+
+      final storedRoom = await client.database.getSingleRoom(client, roomId);
+      expect(storedRoom?.membership, Membership.leave);
+      expect(storedRoom?.prev_batch, 'leave_batch');
+      expect(storedRoom?.lastEvent?.eventId, '\$include-leave-left');
+
+      final timeline = await room.getTimeline();
+      expect(
+        timeline.events.map((event) => event.eventId),
+        contains('\$include-leave-left'),
+      );
+    });
+
+    test('reinvite updates kept left room membership', () async {
+      await client.dispose().onError((e, s) {});
+      client = await getClient(
+        syncFilter: Filter(
+          room: RoomFilter(
+            state: StateFilter(lazyLoadMembers: true),
+            includeLeave: true,
+          ),
+        ),
+      );
+      client.rooms.clear();
+      await client.database.clearCache();
+
+      const roomId = '!reinviteLeftRoom:example.com';
+      await client.handleSync(
+        SyncUpdate(
+          nextBatch: 't_join',
+          rooms: RoomsUpdate(join: {roomId: JoinedRoomUpdate()}),
+        ),
+      );
+      await client.handleSync(
+        SyncUpdate(
+          nextBatch: 't_leave',
+          rooms: RoomsUpdate(leave: {roomId: LeftRoomUpdate()}),
+        ),
+      );
+
+      await client.handleSync(
+        SyncUpdate(
+          nextBatch: 't_invite',
+          rooms: RoomsUpdate(invite: {roomId: InvitedRoomUpdate()}),
+        ),
+      );
+
+      expect(client.getRoomById(roomId)?.membership, Membership.invite);
+    });
+
     test('discard room from archives when membership change', () async {
       await client.loadArchiveWithTimeline();
       await client.handleSync(

You can send follow-ups to the cloud agent here.

Comment thread lib/src/client.dart
Comment thread lib/src/room.dart
Comment thread lib/src/database/matrix_sdk_database.dart
Comment thread lib/src/client.dart
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch from 8bb6523 to 590dd1b Compare July 13, 2026 08:50

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for all 3 issues found in the latest run.

  • ✅ Fixed: Archive timeline state ignored
    • Archived room timeline state events are now applied to the room without storing them before last-event calculation.
  • ✅ Fixed: Forget leaves room in list
    • A successfully forgotten room is now removed from the in-memory client room list.
  • ✅ Fixed: History mutates rooms list
    • History backfill sync handling now skips room-list membership updates for non-joined rooms.

Create PR

Or push these changes by commenting:

@cursor push 9299f8f186
Preview (9299f8f186)
diff --git a/lib/src/client.dart b/lib/src/client.dart
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -1223,6 +1223,17 @@
               <String, BasicEvent>{},
         );
         entry.value.state?.forEach(room.setState);
+        final timelineStateEvents = entry.value.timeline?.events
+            ?.where((event) => event.stateKey != null)
+            .toList();
+        if (timelineStateEvents != null && timelineStateEvents.isNotEmpty) {
+          await _handleRoomEvents(
+            room,
+            timelineStateEvents,
+            EventUpdateType.timeline,
+            store: false,
+          );
+        }
         final timeline = Timeline(
           room: room,
           chunk: TimelineChunk(
@@ -2480,15 +2491,27 @@
   }
 
   /// Use this method only for testing utilities!
-  Future<void> handleSync(SyncUpdate sync, {Direction? direction}) async {
+  Future<void> handleSync(
+    SyncUpdate sync, {
+    Direction? direction,
+    bool updateRoomList = true,
+  }) async {
     // ensure we don't upload keys because someone forgot to set a key count
     sync.deviceOneTimeKeysCount ??= {
       'signed_curve25519': encryption?.olmManager.maxNumberOfOneTimeKeys ?? 100,
     };
-    await _handleSync(sync, direction: direction);
+    await _handleSync(
+      sync,
+      direction: direction,
+      updateRoomList: updateRoomList,
+    );
   }
 
-  Future<void> _handleSync(SyncUpdate sync, {Direction? direction}) async {
+  Future<void> _handleSync(
+    SyncUpdate sync, {
+    Direction? direction,
+    bool updateRoomList = true,
+  }) async {
     final syncToDevice = sync.toDevice;
     if (syncToDevice != null) {
       await _handleToDeviceEvents(syncToDevice);
@@ -2497,7 +2520,11 @@
     if (sync.rooms != null) {
       final join = sync.rooms?.join;
       if (join != null) {
-        await _handleRooms(join, direction: direction);
+        await _handleRooms(
+          join,
+          direction: direction,
+          updateRoomList: updateRoomList,
+        );
       }
       // We need to handle leave before invite. If you decline an invite and
       // then get another invite to the same room, Synapse will include the
@@ -2505,11 +2532,19 @@
       // will only be included in leave.
       final leave = sync.rooms?.leave;
       if (leave != null) {
-        await _handleRooms(leave, direction: direction);
+        await _handleRooms(
+          leave,
+          direction: direction,
+          updateRoomList: updateRoomList,
+        );
       }
       final invite = sync.rooms?.invite;
       if (invite != null) {
-        await _handleRooms(invite, direction: direction);
+        await _handleRooms(
+          invite,
+          direction: direction,
+          updateRoomList: updateRoomList,
+        );
       }
     }
     for (final newPresence in sync.presence ?? <Presence>[]) {
@@ -2645,6 +2680,7 @@
   Future<void> _handleRooms(
     Map<String, SyncRoomUpdate> rooms, {
     Direction? direction,
+    bool updateRoomList = true,
   }) async {
     var handledRooms = 0;
     for (final entry in rooms.entries) {
@@ -2657,7 +2693,11 @@
       final id = entry.key;
       final syncRoomUpdate = entry.value;
 
-      final room = await _updateRoomsByRoomUpdate(id, syncRoomUpdate);
+      final room = await _updateRoomsByRoomUpdate(
+        id,
+        syncRoomUpdate,
+        updateRoomList: updateRoomList,
+      );
 
       // Is the timeline limited? Then all previous messages should be
       // removed from the database!
@@ -2960,8 +3000,9 @@
 
   Future<Room> _updateRoomsByRoomUpdate(
     String roomId,
-    SyncRoomUpdate chatUpdate,
-  ) async {
+    SyncRoomUpdate chatUpdate, {
+    bool updateRoomList = true,
+  }) async {
     // Update the chat list item.
     // Search the room in the rooms
     final roomIndex = rooms.indexWhere((r) => r.id == roomId);
@@ -2989,13 +3030,13 @@
               : Room(id: roomId, membership: membership, client: this));
 
     // Does the chat already exist in the list rooms?
-    if (!found && membership != Membership.leave) {
+    if (updateRoomList && !found && membership != Membership.leave) {
       final position = membership == Membership.invite ? 0 : rooms.length;
       // Add the new chat to the list
       rooms.insert(position, room);
     }
     // If the membership is "leave" then remove the item and stop here
-    else if (membership == Membership.leave) {
+    else if (updateRoomList && membership == Membership.leave) {
       if (syncFilter.room?.includeLeave == true && !found) {
         rooms.add(room);
       } else if (found) {

diff --git a/lib/src/room.dart b/lib/src/room.dart
--- a/lib/src/room.dart
+++ b/lib/src/room.dart
@@ -1424,6 +1424,7 @@
   Future<void> forget() async {
     await client.database.forgetRoom(id);
     await client.forgetRoom(id);
+    client.rooms.removeWhere((room) => room.id == id);
     return;
   }
 
@@ -1548,6 +1549,7 @@
           ),
         ),
         direction: direction,
+        updateRoomList: membership == Membership.join,
       );
     }

You can send follow-ups to the cloud agent here.

Comment thread lib/src/client.dart
Comment thread lib/src/client.dart
Comment thread lib/src/room.dart
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch 3 times, most recently from 5b0b6ef to 510c910 Compare July 27, 2026 07:58
Comment thread lib/src/timeline.dart
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch from 510c910 to f4ed709 Compare July 27, 2026 08:03

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Archive history uses empty pagination token
    • The archived-room Timeline's TimelineChunk now sets prevBatch from the sync leave timeline's prevBatch so getRoomEvents paginates with the correct server token instead of an empty one.

Create PR

Or push these changes by commenting:

@cursor push 28abf2aa64
Preview (28abf2aa64)
diff --git a/lib/src/client.dart b/lib/src/client.dart
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -1231,6 +1231,7 @@
         final timeline = Timeline(
           room: room,
           chunk: TimelineChunk(
+            prevBatch: entry.value.timeline?.prevBatch ?? '',
             events:
                 entry.value.timeline?.events?.reversed
                     .toList() // we display the event in the other sence

You can send follow-ups to the cloud agent here.

Comment thread lib/src/client.dart
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch 2 times, most recently from 494f181 to 8744301 Compare July 30, 2026 11:38

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Left rooms leak from database
    • Captured whether the room was locally known before _updateRoomsByRoomUpdate removes it, so the storeRoomUpdate/forgetRoom call still runs for previously-known left rooms instead of being skipped by the unknown-room check.

Create PR

Or push these changes by commenting:

@cursor push bcbb33a0b4
Preview (bcbb33a0b4)
diff --git a/lib/src/client.dart b/lib/src/client.dart
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -2679,6 +2679,11 @@
       final id = entry.key;
       final syncRoomUpdate = entry.value;
 
+      // A left room gets removed from `rooms` by `_updateRoomsByRoomUpdate`, so
+      // remember whether it was locally known to still forget it from the
+      // database below.
+      final roomWasKnown = getRoomById(id) != null;
+
       final room = await _updateRoomsByRoomUpdate(id, syncRoomUpdate);
 
       // Is the timeline limited? Then all previous messages should be
@@ -2782,7 +2787,9 @@
           await _handleRoomEvents(room, state, EventUpdateType.inviteState);
         }
       }
-      if (syncRoomUpdate is LeftRoomUpdate && getRoomById(id) == null) {
+      if (syncRoomUpdate is LeftRoomUpdate &&
+          !roomWasKnown &&
+          getRoomById(id) == null) {
         Logs().d('Skip store LeftRoomUpdate for unknown room', id);
         continue;
       }

You can send follow-ups to the cloud agent here.

Comment thread lib/src/client.dart
@td-famedly

Copy link
Copy Markdown
Member

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 8744301. Configure here.

@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch from 8744301 to 33169d1 Compare August 6, 2026 06:14

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Assertion-free leftover archive test
    • Restored assertions verifying the room loads into the archive but not the live cache, then transitions to Membership.invite in client.rooms after the invite sync.

Create PR

Or push these changes by commenting:

@cursor push 3e553c853e
Preview (3e553c853e)
diff --git a/test/room_archived_test.dart b/test/room_archived_test.dart
--- a/test/room_archived_test.dart
+++ b/test/room_archived_test.dart
@@ -76,7 +76,10 @@ void main() async {
     });
 
     test('discard room from archives when membership change', () async {
-      await client.loadArchiveWithTimeline();
+      final archive = await client.loadArchiveWithTimeline();
+      expect(archive.any((a) => a.room.id == '!5345234235:example.com'), true);
+      expect(client.getRoomById('!5345234235:example.com'), null);
+
       await client.handleSync(
         SyncUpdate(
           nextBatch: 't_456',

@@ -76,7 +76,10 @@ void main() async {
     });
 
     test('discard room from archives when membership change', () async {
-      await client.loadArchiveWithTimeline();
+      final archive = await client.loadArchiveWithTimeline();
+      expect(archive.any((a) => a.room.id == '!5345234235:example.com'), true);
+      expect(client.getRoomById('!5345234235:example.com'), null);
+
       await client.handleSync(
         SyncUpdate(
           nextBatch: 't_456',
@@ -85,6 +88,10 @@ void main() async {
           ),
         ),
       );
+
+      final invitedRoom = client.getRoomById('!5345234235:example.com');
+      expect(invitedRoom != null, true);
+      expect(invitedRoom!.membership, Membership.invite);
     });
 
     test('logout', () async {

@@ -85,6 +88,10 @@ void main() async {
           ),
         ),
       );
+
+      final invitedRoom = client.getRoomById('!5345234235:example.com');
+      expect(invitedRoom != null, true);
+      expect(invitedRoom!.membership, Membership.invite);
     });
 
     test('logout', () async {

You can send follow-ups to the cloud agent here.

Comment thread test/room_archived_test.dart

@td-famedly td-famedly left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oo much cleaner

@td-famedly

Copy link
Copy Markdown
Member

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Leave rooms miss prev_batch
    • Added a LeftRoomUpdate branch in both _updateRoomsByRoomUpdate and storeRoomUpdate to copy chatUpdate.timeline?.prevBatch onto leave rooms, matching join rooms so history pagination works.

Create PR

Or push these changes by commenting:

@cursor push 15c8037475
Preview (15c8037475)
diff --git a/lib/src/client.dart b/lib/src/client.dart
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -3008,6 +3008,13 @@
                   summary: chatUpdate.summary,
                   client: this,
                 )
+              : chatUpdate is LeftRoomUpdate
+              ? Room(
+                  id: roomId,
+                  membership: membership,
+                  prev_batch: chatUpdate.timeline?.prevBatch,
+                  client: this,
+                )
               : Room(id: roomId, membership: membership, client: this));
 
     // Does the chat already exist in the list rooms?

diff --git a/lib/src/database/matrix_sdk_database.dart b/lib/src/database/matrix_sdk_database.dart
--- a/lib/src/database/matrix_sdk_database.dart
+++ b/lib/src/database/matrix_sdk_database.dart
@@ -1297,6 +1297,9 @@
                 client: client,
                 id: roomId,
                 membership: membership,
+                prev_batch: roomUpdate is LeftRoomUpdate
+                    ? roomUpdate.timeline?.prevBatch
+                    : null,
                 lastEvent: lastEvent,
               ).toJson(),
       );

You can send follow-ups to the cloud agent here.

Comment thread lib/src/client.dart
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch from 33169d1 to 25836ad Compare September 2, 2026 06:21
@krille-chan
krille-chan force-pushed the krille/refactor-archive-room-handling branch from 25836ad to 1500205 Compare September 2, 2026 06:24
@krille-chan

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1500205. Configure here.

@krille-chan
krille-chan merged commit 89d07a6 into main Sep 2, 2026
17 checks passed
@krille-chan
krille-chan deleted the krille/refactor-archive-room-handling branch September 2, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make it possible to keep leave rooms when syncfilter includes them

2 participants