@@ -76,6 +76,10 @@ export class RoomListViewModel
7676 // State tracking
7777 private activeFilter : FilterEnum | undefined = undefined ;
7878 private roomsResult : RoomsResult ;
79+ /**
80+ * List of sections to display in the room list, derived from roomsResult and section header view model expansion state.
81+ */
82+ private sections : Section [ ] = [ ] ;
7983 private lastActiveRoomPosition : StickyRoomPosition | undefined = undefined ;
8084
8185 // Child view model management
@@ -116,11 +120,12 @@ export class RoomListViewModel
116120 filterKeys : undefined ,
117121 } ,
118122 isFlatList,
119- sections,
123+ sections : toRoomListSection ( sections ) ,
120124 canCreateRoom,
121125 } ) ;
122126
123127 this . roomsResult = roomsResult ;
128+ this . sections = sections ;
124129
125130 // Build initial roomsMap from roomsResult
126131 this . updateRoomsMap ( roomsResult ) ;
@@ -301,7 +306,7 @@ export class RoomListViewModel
301306 if ( ! currentRoomId ) return ;
302307
303308 const { delta, unread } = payload ;
304- const rooms = this . roomsResult . sections . flatMap ( ( section ) => section . rooms ) ;
309+ const rooms = this . sections . flatMap ( ( section ) => section . rooms ) ;
305310
306311 const filteredRooms = unread
307312 ? // Filter the rooms to only include unread ones and the active room
@@ -393,9 +398,7 @@ export class RoomListViewModel
393398 return undefined ;
394399 }
395400
396- const index = this . roomsResult . sections
397- . flatMap ( ( section ) => section . rooms )
398- . findIndex ( ( room ) => room . roomId === roomId ) ;
401+ const index = this . sections . flatMap ( ( section ) => section . rooms ) . findIndex ( ( room ) => room . roomId === roomId ) ;
399402 return index >= 0 ? index : undefined ;
400403 }
401404
@@ -485,18 +488,18 @@ export class RoomListViewModel
485488 // Rebuild roomsMap with the reordered rooms
486489 this . updateRoomsMap ( this . roomsResult ) ;
487490
488- // Calculate the active room index after applying sticky logic
489- const activeRoomIndex = this . getActiveRoomIndex ( roomId ) ;
490-
491491 // Track the current active room position for future sticky calculations
492492 this . lastActiveRoomPosition = roomId ? this . findRoomPosition ( this . roomsResult . sections , roomId ) : undefined ;
493493
494494 // Build the complete state atomically to ensure consistency
495- // roomIds and roomListState must always be in sync
496495 const { sections, isFlatList } = computeSections (
497496 this . roomsResult ,
498497 ( tag ) => this . roomSectionHeaderViewModels . get ( tag ) ?. isExpanded ?? true ,
499498 ) ;
499+ this . sections = sections ;
500+
501+ // Calculate the active room index from the computed sections (which exclude collapsed sections' rooms)
502+ const activeRoomIndex = this . getActiveRoomIndex ( roomId ) ;
500503
501504 // Update filter keys - only update if they have actually changed to prevent unnecessary re-renders of the room list
502505 const previousFilterKeys = this . snapshot . current . roomListState . filterKeys ;
@@ -511,13 +514,16 @@ export class RoomListViewModel
511514 const isRoomListEmpty = this . roomsResult . sections . every ( ( section ) => section . rooms . length === 0 ) ;
512515 const isLoadingRooms = RoomListStoreV3 . instance . isLoadingRooms ;
513516
517+ const viewSections = toRoomListSection ( this . sections ) ;
518+ const previousSections = this . snapshot . current . sections ;
519+
514520 // Single atomic snapshot update
515521 this . snapshot . merge ( {
516522 isLoadingRooms,
517523 isRoomListEmpty,
518524 activeFilterId,
519525 roomListState,
520- sections,
526+ sections : keepIfSame ( previousSections , viewSections ) ,
521527 isFlatList,
522528 } ) ;
523529 }
@@ -545,25 +551,31 @@ export class RoomListViewModel
545551 * Compute the sections to display in the room list based on the rooms result and section expansion state.
546552 * @param roomsResult - The current rooms result containing sections and rooms
547553 * @param isSectionExpanded - A function that takes a section tag and returns whether that section is currently expanded
548- * @returns An object containing the computed sections with room IDs (empty if section is collapsed) and a boolean indicating if the list should be displayed as a flat list (only one section with all rooms)
554+ * @returns An object containing the computed sections ( with rooms removed for collapsed sections ) and a boolean indicating if this is a flat list (only one section with all rooms)
549555 */
550556function computeSections (
551557 roomsResult : RoomsResult ,
552558 isSectionExpanded : ( tag : string ) => boolean ,
553- ) : { sections : RoomListSection [ ] ; isFlatList : boolean } {
559+ ) : { sections : Section [ ] ; isFlatList : boolean } {
554560 const sections = roomsResult . sections
555- . map ( ( { tag, rooms } ) => ( {
556- id : tag ,
557- roomIds : rooms . map ( ( room ) => room . roomId ) ,
558- } ) )
559561 // Only include sections that have rooms
560- . filter ( ( section ) => section . roomIds . length > 0 )
562+ . filter ( ( section ) => section . rooms . length > 0 )
561563 // Remove roomIds for sections that are currently collapsed according to their section header view model
562564 . map ( ( section ) => ( {
563565 ...section ,
564- roomIds : isSectionExpanded ( section . id ) ? section . roomIds : [ ] ,
566+ rooms : isSectionExpanded ( section . tag ) ? section . rooms : [ ] ,
565567 } ) ) ;
566- const isFlatList = sections . length === 1 && sections [ 0 ] . id === CHATS_TAG ;
568+ const isFlatList = sections . length === 1 && sections [ 0 ] . tag === CHATS_TAG ;
567569
568570 return { sections, isFlatList } ;
569571}
572+
573+ /**
574+ * Convert from the internal Section type used in the view model to the RoomListSection type used in the snapshot.
575+ */
576+ function toRoomListSection ( sections : Section [ ] ) : RoomListSection [ ] {
577+ return sections . map ( ( { tag, rooms } ) => ( {
578+ id : tag ,
579+ roomIds : rooms . map ( ( room ) => room . roomId ) ,
580+ } ) ) ;
581+ }
0 commit comments