@@ -219,12 +219,29 @@ impl BaseClient {
219219 )
220220 . await ?;
221221
222- // Notify subscribers (e.g. open timelines) about global profile updates, which
223- // don't otherwise touch any room and so trigger no other broadcast .
222+ // Profile-only updates don't modify any rooms, so nothing else broadcasts
223+ // them. Surface the change so subscribers can react accordingly .
224224 if !extensions. profiles . is_empty ( ) {
225225 let _ = self
226226 . global_profile_updates_sender
227227 . send ( extensions. profiles . keys ( ) . cloned ( ) . collect ( ) ) ;
228+
229+ // Nudge `RoomInfo` so hero status/call fields are re-read.
230+ #[ cfg( feature = "unstable-msc4426" ) ]
231+ for room in self . state_store . rooms ( ) {
232+ if room
233+ . hero_user_ids ( )
234+ . iter ( )
235+ . any ( |user_id| extensions. profiles . contains_key ( user_id) )
236+ {
237+ let _ = self . state_store . room_info_notable_update_sender . send (
238+ crate :: RoomInfoNotableUpdate {
239+ room_id : room. room_id ( ) . to_owned ( ) ,
240+ reasons : crate :: RoomInfoNotableUpdateReasons :: HEROES ,
241+ } ,
242+ ) ;
243+ }
244+ }
228245 }
229246
230247 let mut context = processors:: Context :: default ( ) ;
@@ -1643,6 +1660,62 @@ mod tests {
16431660 ) ;
16441661 }
16451662
1663+ #[ cfg( feature = "unstable-msc4426" ) ]
1664+ #[ async_test]
1665+ async fn test_hero_global_profile_update_triggers_notable_update ( ) {
1666+ use ruma:: profile:: UserProfileUpdate ;
1667+
1668+ let client = logged_in_base_client ( None ) . await ;
1669+ let room_id = room_id ! ( "!r:e.uk" ) ;
1670+ let alice = owned_user_id ! ( "@alice:e.uk" ) ;
1671+
1672+ // Given a room where Alice is a hero.
1673+ let mut room = http:: response:: Room :: new ( ) ;
1674+ room. heroes = Some ( vec ! [ assign!( http:: response:: Hero :: new( alice. clone( ) ) , {
1675+ name: Some ( "Alice" . to_owned( ) ) ,
1676+ } ) ] ) ;
1677+ let response = response_with_room ( room_id, room) ;
1678+ client
1679+ . process_sliding_sync (
1680+ & response,
1681+ & RequestedRequiredStates :: default ( ) ,
1682+ & client. state_store_lock ( ) . lock ( ) . await ,
1683+ )
1684+ . await
1685+ . expect ( "Failed to process sync" ) ;
1686+
1687+ let mut room_info_notable_update = client. room_info_notable_update_receiver ( ) ;
1688+
1689+ // When a subsequent sync carries only a profiles-extension update for Alice.
1690+ let mut response = http:: Response :: new ( "1" . to_owned ( ) ) ;
1691+ response. extensions . profiles . insert (
1692+ alice. clone ( ) ,
1693+ UserProfileUpdate :: from_iter ( [ (
1694+ "org.matrix.msc4426.status" . to_owned ( ) ,
1695+ json ! ( { "text" : "Away" , "emoji" : "🌴" } ) ,
1696+ ) ] ) ,
1697+ ) ;
1698+ client
1699+ . process_sliding_sync (
1700+ & response,
1701+ & RequestedRequiredStates :: default ( ) ,
1702+ & client. state_store_lock ( ) . lock ( ) . await ,
1703+ )
1704+ . await
1705+ . expect ( "Failed to process sync" ) ;
1706+
1707+ // Then a `HEROES` notable update is emitted for the room, so consumers
1708+ // re-read the hero profiles.
1709+ assert_matches ! (
1710+ room_info_notable_update. recv( ) . await ,
1711+ Ok ( RoomInfoNotableUpdate { room_id: received_room_id, reasons } ) => {
1712+ assert_eq!( received_room_id, room_id) ;
1713+ assert!( reasons. contains( RoomInfoNotableUpdateReasons :: HEROES ) ) ;
1714+ }
1715+ ) ;
1716+ assert ! ( room_info_notable_update. is_empty( ) ) ;
1717+ }
1718+
16461719 #[ async_test]
16471720 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response ( ) {
16481721 // Given a logged-in client
0 commit comments