Repository: element-hq/element-x-ios
Pull request #3973: Load single rooms in the notification service extension
Author: login: stefanceriu
Labels: name: pr-bugfix
description: for bug fix
Created: 2025-04-03T12:49:44Z | Merged: 2025-04-03T15:08:34Z

This patch refactors the NSE to create a new UserSession every time a payload comes in and pass the room down to the SDK so it can optimize how many rooms it loads from the store and how much memory it's using.

It will:
- undo creating at most one user session pe process as that shoulnd't be a problem anymore
- rename some methods and improve the logging
- remove the event cache feature flag (as it was synced in the NSE)
- bump the RustSDK to v25.04.02


Diff:
diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj
index deea166083..4b328c3885 100644
--- a/ElementX.xcodeproj/project.pbxproj
+++ b/ElementX.xcodeproj/project.pbxproj
@@ -8549,7 +8549,7 @@
 			repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
 			requirement = {
 				kind = exactVersion;
-				version = 25.03.31;
+				version = 25.04.02;
 			};
 		};
 		701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 203a5bfad8..c1cfff6445 100644
--- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -158,8 +158,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/element-hq/matrix-rust-components-swift",
       "state" : {
-        "revision" : "271f8e34dd754ff6873350576d601442fe18b1d8",
-        "version" : "25.3.31"
+        "revision" : "1bc2c0b021107332e0691e288780e8a3adb87905",
+        "version" : "25.4.2"
       }
     },
     {
diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift
index 13a0e7e97d..5f441d92ac 100644
--- a/ElementX/Sources/Application/AppSettings.swift
+++ b/ElementX/Sources/Application/AppSettings.swift
@@ -19,7 +19,6 @@ protocol CommonSettingsProtocol {
     var enableOnlySignedDeviceIsolationMode: Bool { get }
     var hideInviteAvatars: Bool { get }
     var hideTimelineMedia: Bool { get }
-    var eventCacheEnabled: Bool { get }
 }
 
 /// Store Element specific app settings.
@@ -57,7 +56,6 @@ final class AppSettings {
         case fuzzyRoomListSearchEnabled
         case enableOnlySignedDeviceIsolationMode
         case knockingEnabled
-        case eventCacheEnabledV2
     }
     
     private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
@@ -340,9 +338,6 @@ final class AppSettings {
     
     @UserPreference(key: UserDefaultsKeys.hideTimelineMedia, defaultValue: false, storageType: .userDefaults(store))
     var hideTimelineMedia
-    
-    @UserPreference(key: UserDefaultsKeys.eventCacheEnabledV2, defaultValue: true, storageType: .userDefaults(store))
-    var eventCacheEnabled
 }
 
 extension AppSettings: CommonSettingsProtocol { }
diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift
index 7804f8f32d..64da7f949c 100644
--- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift
+++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift
@@ -2927,6 +2927,52 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable {
         try await restoreSessionSessionClosure?(session)
     }
 
+    //MARK: - restoreSessionWith
+
+    open var restoreSessionWithSessionRoomLoadSettingsThrowableError: Error?
+    var restoreSessionWithSessionRoomLoadSettingsUnderlyingCallsCount = 0
+    open var restoreSessionWithSessionRoomLoadSettingsCallsCount: Int {
+        get {
+            if Thread.isMainThread {
+                return restoreSessionWithSessionRoomLoadSettingsUnderlyingCallsCount
+            } else {
+                var returnValue: Int? = nil
+                DispatchQueue.main.sync {
+                    returnValue = restoreSessionWithSessionRoomLoadSettingsUnderlyingCallsCount
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                restoreSessionWithSessionRoomLoadSettingsUnderlyingCallsCount = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    restoreSessionWithSessionRoomLoadSettingsUnderlyingCallsCount = newValue
+                }
+            }
+        }
+    }
+    open var restoreSessionWithSessionRoomLoadSettingsCalled: Bool {
+        return restoreSessionWithSessionRoomLoadSettingsCallsCount > 0
+    }
+    open var restoreSessionWithSessionRoomLoadSettingsReceivedArguments: (session: Session, roomLoadSettings: RoomLoadSettings)?
+    open var restoreSessionWithSessionRoomLoadSettingsReceivedInvocations: [(session: Session, roomLoadSettings: RoomLoadSettings)] = []
+    open var restoreSessionWithSessionRoomLoadSettingsClosure: ((Session, RoomLoadSettings) async throws -> Void)?
+
+    open override func restoreSessionWith(session: Session, roomLoadSettings: RoomLoadSettings) async throws {
+        if let error = restoreSessionWithSessionRoomLoadSettingsThrowableError {
+            throw error
+        }
+        restoreSessionWithSessionRoomLoadSettingsCallsCount += 1
+        restoreSessionWithSessionRoomLoadSettingsReceivedArguments = (session: session, roomLoadSettings: roomLoadSettings)
+        DispatchQueue.main.async {
+            self.restoreSessionWithSessionRoomLoadSettingsReceivedInvocations.append((session: session, roomLoadSettings: roomLoadSettings))
+        }
+        try await restoreSessionWithSessionRoomLoadSettingsClosure?(session, roomLoadSettings)
+    }
+
     //MARK: - roomAliasExists
 
     open var roomAliasExistsRoomAliasThrowableError: Error?
@@ -5210,77 +5256,6 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder, @unchecked Sendabl
         }
     }
 
-    //MARK: - passphrase
-
-    var passphrasePassphraseUnderlyingCallsCount = 0
-    open var passphrasePassphraseCallsCount: Int {
-        get {
-            if Thread.isMainThread {
-                return passphrasePassphraseUnderlyingCallsCount
-            } else {
-                var returnValue: Int? = nil
-                DispatchQueue.main.sync {
-                    returnValue = passphrasePassphraseUnderlyingCallsCount
-                }
-
-                return returnValue!
-            }
-        }
-        set {
-            if Thread.isMainThread {
-                passphrasePassphraseUnderlyingCallsCount = newValue
-            } else {
-                DispatchQueue.main.sync {
-                    passphrasePassphraseUnderlyingCallsCount = newValue
-                }
-            }
-        }
-    }
-    open var passphrasePassphraseCalled: Bool {
-        return passphrasePassphraseCallsCount > 0
-    }
-    open var passphrasePassphraseReceivedPassphrase: String?
-    open var passphrasePassphraseReceivedInvocations: [String?] = []
-
-    var passphrasePassphraseUnderlyingReturnValue: ClientBuilder!
-    open var passphrasePassphraseReturnValue: ClientBuilder! {
-        get {
-            if Thread.isMainThread {
-                return passphrasePassphraseUnderlyingReturnValue
-            } else {
-                var returnValue: ClientBuilder? = nil
-                DispatchQueue.main.sync {
-                    returnValue = passphrasePassphraseUnderlyingReturnValue
-                }
-
-                return returnValue!
-            }
-        }
-        set {
-            if Thread.isMainThread {
-                passphrasePassphraseUnderlyingReturnValue = newValue
-            } else {
-                DispatchQueue.main.sync {
-                    passphrasePassphraseUnderlyingReturnValue = newValue
-                }
-            }
-        }
-    }
-    open var passphrasePassphraseClosure: ((String?) -> ClientBuilder)?
-
-    open override func passphrase(passphrase: String?) -> ClientBuilder {
-        passphrasePassphraseCallsCount += 1
-        passphrasePassphraseReceivedPassphrase = passphrase
-        DispatchQueue.main.async {
-            self.passphrasePassphraseReceivedInvocations.append(passphrase)
-        }
-        if let passphrasePassphraseClosure = passphrasePassphraseClosure {
-            return passphrasePassphraseClosure(passphrase)
-        } else {
-            return passphrasePassphraseReturnValue
-        }
-    }
-
     //MARK: - proxy
 
     var proxyUrlUnderlyingCallsCount = 0
@@ -5707,6 +5682,219 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder, @unchecked Sendabl
         }
     }
 
+    //MARK: - sessionCacheSize
+
+    var sessionCacheSizeCacheSizeUnderlyingCallsCount = 0
+    open var sessionCacheSizeCacheSizeCallsCount: Int {
+        get {
+            if Thread.isMainThread {
+                return sessionCacheSizeCacheSizeUnderlyingCallsCount
+            } else {
+                var returnValue: Int? = nil
+                DispatchQueue.main.sync {
+                    returnValue = sessionCacheSizeCacheSizeUnderlyingCallsCount
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                sessionCacheSizeCacheSizeUnderlyingCallsCount = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    sessionCacheSizeCacheSizeUnderlyingCallsCount = newValue
+                }
+            }
+        }
+    }
+    open var sessionCacheSizeCacheSizeCalled: Bool {
+        return sessionCacheSizeCacheSizeCallsCount > 0
+    }
+    open var sessionCacheSizeCacheSizeReceivedCacheSize: UInt32?
+    open var sessionCacheSizeCacheSizeReceivedInvocations: [UInt32?] = []
+
+    var sessionCacheSizeCacheSizeUnderlyingReturnValue: ClientBuilder!
+    open var sessionCacheSizeCacheSizeReturnValue: ClientBuilder! {
+        get {
+            if Thread.isMainThread {
+                return sessionCacheSizeCacheSizeUnderlyingReturnValue
+            } else {
+                var returnValue: ClientBuilder? = nil
+                DispatchQueue.main.sync {
+                    returnValue = sessionCacheSizeCacheSizeUnderlyingReturnValue
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                sessionCacheSizeCacheSizeUnderlyingReturnValue = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    sessionCacheSizeCacheSizeUnderlyingReturnValue = newValue
+                }
+            }
+        }
+    }
+    open var sessionCacheSizeCacheSizeClosure: ((UInt32?) -> ClientBuilder)?
+
+    open override func sessionCacheSize(cacheSize: UInt32?) -> ClientBuilder {
+        sessionCacheSizeCacheSizeCallsCount += 1
+        sessionCacheSizeCacheSizeReceivedCacheSize = cacheSize
+        DispatchQueue.main.async {
+            self.sessionCacheSizeCacheSizeReceivedInvocations.append(cacheSize)
+        }
+        if let sessionCacheSizeCacheSizeClosure = sessionCacheSizeCacheSizeClosure {
+            return sessionCacheSizeCacheSizeClosure(cacheSize)
+        } else {
+            return sessionCacheSizeCacheSizeReturnValue
+        }
+    }
+
+    //MARK: - sessionJournalSizeLimit
+
+    var sessionJournalSizeLimitLimitUnderlyingCallsCount = 0
+    open var sessionJournalSizeLimitLimitCallsCount: Int {
+        get {
+            if Thread.isMainThread {
+                return sessionJournalSizeLimitLimitUnderlyingCallsCount
+            } else {
+                var returnValue: Int? = nil
+                DispatchQueue.main.sync {
+                    returnValue = sessionJournalSizeLimitLimitUnderlyingCallsCount
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                sessionJournalSizeLimitLimitUnderlyingCallsCount = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    sessionJournalSizeLimitLimitUnderlyingCallsCount = newValue
+                }
+            }
+        }
+    }
+    open var sessionJournalSizeLimitLimitCalled: Bool {
+        return sessionJournalSizeLimitLimitCallsCount > 0
+    }
+    open var sessionJournalSizeLimitLimitReceivedLimit: UInt32?
+    open var sessionJournalSizeLimitLimitReceivedInvocations: [UInt32?] = []
+
+    var sessionJournalSizeLimitLimitUnderlyingReturnValue: ClientBuilder!
+    open var sessionJournalSizeLimitLimitReturnValue: ClientBuilder! {
+        get {
+            if Thread.isMainThread {
+                return sessionJournalSizeLimitLimitUnderlyingReturnValue
+            } else {
+                var returnValue: ClientBuilder? = nil
+                DispatchQueue.main.sync {
+                    returnValue = sessionJournalSizeLimitLimitUnderlyingReturnValue
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                sessionJournalSizeLimitLimitUnderlyingReturnValue = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    sessionJournalSizeLimitLimitUnderlyingReturnValue = newValue
+                }
+            }
+        }
+    }
+    open var sessionJournalSizeLimitLimitClosure: ((UInt32?) -> ClientBuilder)?
+
+    open override func sessionJournalSizeLimit(limit: UInt32?) -> ClientBuilder {
+        sessionJournalSizeLimitLimitCallsCount += 1
+        sessionJournalSizeLimitLimitReceivedLimit = limit
+        DispatchQueue.main.async {
+            self.sessionJournalSizeLimitLimitReceivedInvocations.append(limit)
+        }
+        if let sessionJournalSizeLimitLimitClosure = sessionJournalSizeLimitLimitClosure {
+            return sessionJournalSizeLimitLimitClosure(limit)
+        } else {
+            return sessionJournalSizeLimitLimitReturnValue
+        }
+    }
+
+    //MARK: - sessionPassphrase
+
+    var sessionPassphrasePassphraseUnderlyingCallsCount = 0
+    open var sessionPassphrasePassphraseCallsCount: Int {
+        get {
+            if Thread.isMainThread {
+                return sessionPassphrasePassphraseUnderlyingCallsCount
+            } else {
+                var returnValue: Int? = nil
+                DispatchQueue.main.sync {
+                    returnValue = sessionPassphrasePassphraseUnderlyingCallsCount
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                sessionPassphrasePassphraseUnderlyingCallsCount = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    sessionPassphrasePassphraseUnderlyingCallsCount = newValue
+                }
+            }
+        }
+    }
+    open var sessionPassphrasePassphraseCalled: Bool {
+        return sessionPassphrasePassphraseCallsCount > 0
+    }
+    open var sessionPassphrasePassphraseReceivedPassphrase: String?
+    open var sessionPassphrasePassphraseReceivedInvocations: [String?] = []
+
+    var sessionPassphrasePassphraseUnderlyingReturnValue: ClientBuilder!
+    open var sessionPassphrasePassphraseReturnValue: ClientBuilder! {
+        get {
+            if Thread.isMainThread {
+                return sessionPassphrasePassphraseUnderlyingReturnValue
+            } else {
+                var returnValue: ClientBuilder? = nil
+                DispatchQueue.main.sync {
+                    returnValue = sessionPassphrasePassphraseUnderlyingReturnValue
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                sessionPassphrasePassphraseUnderlyingReturnValue = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    sessionPassphrasePassphraseUnderlyingReturnValue = newValue
+                }
+            }
+        }
+    }
+    open var sessionPassphrasePassphraseClosure: ((String?) -> ClientBuilder)?
+
+    open override func sessionPassphrase(passphrase: String?) -> ClientBuilder {
+        sessionPassphrasePassphraseCallsCount += 1
+        sessionPassphrasePassphraseReceivedPassphrase = passphrase
+        DispatchQueue.main.async {
+            self.sessionPassphrasePassphraseReceivedInvocations.append(passphrase)
+        }
+        if let sessionPassphrasePassphraseClosure = sessionPassphrasePassphraseClosure {
+            return sessionPassphrasePassphraseClosure(passphrase)
+        } else {
+            return sessionPassphrasePassphraseReturnValue
+        }
+    }
+
     //MARK: - sessionPaths
 
     var sessionPathsDataPathCachePathUnderlyingCallsCount = 0
@@ -5778,6 +5966,77 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder, @unchecked Sendabl
         }
     }
 
+    //MARK: - sessionPoolMaxSize
+
+    var sessionPoolMaxSizePoolMaxSizeUnderlyingCallsCount = 0
+    open var sessionPoolMaxSizePoolMaxSizeCallsCount: Int {
+        get {
+            if Thread.isMainThread {
+                return sessionPoolMaxSizePoolMaxSizeUnderlyingCallsCount
+            } else {
+                var returnValue: Int? = nil
+                DispatchQueue.main.sync {
+                    returnValue = sessionPoolMaxSizePoolMaxSizeUnderlyingCallsCount
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                sessionPoolMaxSizePoolMaxSizeUnderlyingCallsCount = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    sessionPoolMaxSizePoolMaxSizeUnderlyingCallsCount = newValue
+                }
+            }
+        }
+    }
+    open var sessionPoolMaxSizePoolMaxSizeCalled: Bool {
+        return sessionPoolMaxSizePoolMaxSizeCallsCount > 0
+    }
+    open var sessionPoolMaxSizePoolMaxSizeReceivedPoolMaxSize: UInt32?
+    open var sessionPoolMaxSizePoolMaxSizeReceivedInvocations: [UInt32?] = []
+
+    var sessionPoolMaxSizePoolMaxSizeUnderlyingReturnValue: ClientBuilder!
+    open var sessionPoolMaxSizePoolMaxSizeReturnValue: ClientBuilder! {
+        get {
+            if Thread.isMainThread {
+                return sessionPoolMaxSizePoolMaxSizeUnderlyingReturnValue
+            } else {
+                var returnValue: ClientBuilder? = nil
+                DispatchQueue.main.sync {
+                    returnValue = sessionPoolMaxSizePoolMaxSizeUnderlyingReturnValue
+                }
+
+                return returnValue!
+            }
+        }
+        set {
+            if Thread.isMainThread {
+                sessionPoolMaxSizePoolMaxSizeUnderlyingReturnValue = newValue
+            } else {
+                DispatchQueue.main.sync {
+                    sessionPoolMaxSizePoolMaxSizeUnderlyingReturnValue = newValue
+                }
+            }
+        }
+    }
+    open var sessionPoolMaxSizePoolMaxSizeClosure: ((UInt32?) -> ClientBuilder)?
+
+    open override func sessionPoolMaxSize(poolMaxSize: UInt32?) -> ClientBuilder {
+        sessionPoolMaxSizePoolMaxSizeCallsCount += 1
+        sessionPoolMaxSizePoolMaxSizeReceivedPoolMaxSize = poolMaxSize
+        DispatchQueue.main.async {
+            self.sessionPoolMaxSizePoolMaxSizeReceivedInvocations.append(poolMaxSize)
+        }
+        if let sessionPoolMaxSizePoolMaxSizeClosure = sessionPoolMaxSizePoolMaxSizeClosure {
+            return sessionPoolMaxSizePoolMaxSizeClosure(poolMaxSize)
+        } else {
+            return sessionPoolMaxSizePoolMaxSizeReturnValue
+        }
+    }
+
     //MARK: - setSessionDelegate
 
     var setSessionDelegateSessionDelegateUnderlyingCallsCount = 0
diff --git a/ElementX/Sources/Other/Extensions/ClientBuilder.swift b/ElementX/Sources/Other/Extensions/ClientBuilder.swift
index 59e8472c03..61c6de3495 100644
--- a/ElementX/Sources/Other/Extensions/ClientBuilder.swift
+++ b/ElementX/Sources/Other/Extensions/ClientBuilder.swift
@@ -15,15 +15,13 @@ extension ClientBuilder {
                             slidingSync: ClientBuilderSlidingSync,
                             sessionDelegate: ClientSessionDelegate,
                             appHooks: AppHooks,
-                            enableOnlySignedDeviceIsolationMode: Bool,
-                            eventCacheEnabled: Bool) -> ClientBuilder {
+                            enableOnlySignedDeviceIsolationMode: Bool) -> ClientBuilder {
         var builder = ClientBuilder()
             .crossProcessStoreLocksHolderName(holderName: InfoPlistReader.main.bundleIdentifier)
             .enableOidcRefreshLock()
             .setSessionDelegate(sessionDelegate: sessionDelegate)
             .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent())
             .requestConfig(config: .init(retryLimit: 0, timeout: 30000, maxConcurrentRequests: nil, retryTimeout: nil))
-            .useEventCachePersistentStorage(value: eventCacheEnabled)
         
         builder = switch slidingSync {
         case .restored: builder
diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift
index ab9d5f79e2..3880277734 100644
--- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift
+++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift
@@ -44,7 +44,6 @@ protocol DeveloperOptionsProtocol: AnyObject {
     var enableOnlySignedDeviceIsolationMode: Bool { get set }
     var elementCallBaseURLOverride: URL? { get set }
     var knockingEnabled: Bool { get set }
-    var eventCacheEnabled: Bool { get set }
 }
 
 extension AppSettings: DeveloperOptionsProtocol { }
diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift
index 83698b5caf..3c17c054d6 100644
--- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift
+++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift
@@ -30,16 +30,7 @@ struct DeveloperOptionsScreen: View {
                     }
                 }
             }
-            
-            Section("General") {
-                Toggle(isOn: $context.eventCacheEnabled) {
-                    Text("Event cache")
-                }
-                .onChange(of: context.eventCacheEnabled) {
-                    context.send(viewAction: .clearCache)
-                }
-            }
-            
+                        
             Section("Room List") {
                 Toggle(isOn: $context.publicSearchEnabled) {
                     Text("Public search")
diff --git a/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift b/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift
index 233ac93877..1d52551f64 100644
--- a/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift
+++ b/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift
@@ -48,10 +48,9 @@ struct AuthenticationClientBuilder: AuthenticationClientBuilderProtocol {
                          slidingSync: .discover,
                          sessionDelegate: clientSessionDelegate,
                          appHooks: appHooks,
-                         enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode,
-                         eventCacheEnabled: appSettings.eventCacheEnabled)
+                         enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode)
             .sessionPaths(dataPath: sessionDirectories.dataPath,
                           cachePath: sessionDirectories.cachePath)
-            .passphrase(passphrase: passphrase)
+            .sessionPassphrase(passphrase: passphrase)
     }
 }
diff --git a/ElementX/Sources/Services/Timeline/TimelineProxy.swift b/ElementX/Sources/Services/Timeline/TimelineProxy.swift
index bd821baf2f..9f4a5003e7 100644
--- a/ElementX/Sources/Services/Timeline/TimelineProxy.swift
+++ b/ElementX/Sources/Services/Timeline/TimelineProxy.swift
@@ -236,6 +236,7 @@ final class TimelineProxy: TimelineProxyProtocol {
                                                               caption: caption,
                                                               formattedCaption: nil, // Rust will build this from the caption's markdown.
                                                               mentions: nil,
+                                                              replyParams: nil,
                                                               useSendQueue: true),
                                                 audioInfo: audioInfo,
                                                 progressWatcher: nil)
@@ -263,6 +264,7 @@ final class TimelineProxy: TimelineProxyProtocol {
                                                              caption: caption,
                                                              formattedCaption: nil, // Rust will build this from the caption's markdown.
                                                              mentions: nil,
+                                                             replyParams: nil,
                                                              useSendQueue: true),
                                                fileInfo: fileInfo,
                                                progressWatcher: nil)
@@ -291,6 +293,7 @@ final class TimelineProxy: TimelineProxyProtocol {
                                                               caption: caption,
                                                               formattedCaption: nil, // Rust will build this from the caption's markdown.
                                                               mentions: nil,
+                                                              replyParams: nil,
                                                               useSendQueue: true),
                                                 thumbnailPath: thumbnailURL.path(percentEncoded: false),
                                                 imageInfo: imageInfo,
@@ -338,6 +341,7 @@ final class TimelineProxy: TimelineProxyProtocol {
                                                               caption: caption,
                                                               formattedCaption: nil,
                                                               mentions: nil,
+                                                              replyParams: nil,
                                                               useSendQueue: true),
                                                 thumbnailPath: thumbnailURL.path(percentEncoded: false),
                                                 videoInfo: videoInfo,
@@ -366,6 +370,7 @@ final class TimelineProxy: TimelineProxyProtocol {
                                                                      caption: nil,
                                                                      formattedCaption: nil,
                                                                      mentions: nil,
+                                                                     replyParams: nil,
                                                                      useSendQueue: true),
                                                        audioInfo: audioInfo,
                                                        waveform: waveform,
diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift
index 2e39c7da7f..883969b0d9 100644
--- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift
+++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift
@@ -123,13 +123,12 @@ class UserSessionStore: UserSessionStoreProtocol {
                          slidingSync: .restored,
                          sessionDelegate: keychainController,
                          appHooks: appHooks,
-                         enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode,
-                         eventCacheEnabled: appSettings.eventCacheEnabled)
+                         enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode)
             .sessionPaths(dataPath: credentials.restorationToken.sessionDirectories.dataPath,
                           cachePath: credentials.restorationToken.sessionDirectories.cachePath)
             .username(username: credentials.userID)
             .homeserverUrl(url: homeserverURL)
-            .passphrase(passphrase: credentials.restorationToken.passphrase)
+            .sessionPassphrase(passphrase: credentials.restorationToken.passphrase)
         
         do {
             let client = try await builder.build()
diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift
index 614dec6680..4c211046cb 100644
--- a/NSE/Sources/NotificationServiceExtension.swift
+++ b/NSE/Sources/NotificationServiceExtension.swift
@@ -28,36 +28,24 @@ import UserNotifications
 // but it will always be called on different threads. It may or may not be
 // called on the same instance of `NotificationService` as a previous
 // notification.
-//
-// We keep a global `environment` singleton to ensure that our app context,
-// database, logging, etc. are only ever setup once per *process*
 
 private let settings: CommonSettingsProtocol = AppSettings()
-private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(mentionBuilder: PlainMentionBuilder()), destination: .notification),
-                                                                    settings: settings)
+
 private let keychainController = KeychainController(service: .sessions,
                                                     accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
 
+private let eventStringBuilder = RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(mentionBuilder: PlainMentionBuilder()),
+                                                               destination: .notification)
+
+private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: eventStringBuilder,
+                                                                    settings: settings)
+
 class NotificationServiceExtension: UNNotificationServiceExtension {
     private var handler: ((UNNotificationContent) -> Void)?
     private var modifiedContent: UNMutableNotificationContent?
     
     private let appHooks = AppHooks()
-    
-    // Used to create one single UserSession across process/instances/runs
-    private static let serialQueue = DispatchQueue(label: "io.element.elementx.nse")
-    
-    // Temporary. We need to make sure the NSE and the main app pass in the same value.
-    // The NSE has a tendency of staying alive for longer so use this to manually kill it
-    // when the feature flag doesn't match.
-    private static var eventCacheEnabled = false
-    
-    private static var userSession: NSEUserSession? {
-        didSet {
-            eventCacheEnabled = settings.eventCacheEnabled
-        }
-    }
-    
+            
     deinit {
         cleanUp()
         ExtensionLogger.logMemory(with: tag)
@@ -85,68 +73,49 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
         modifiedContent = request.content.mutableCopy() as? UNMutableNotificationContent
         
         MXLog.info("\(tag) #########################################")
-        ExtensionLogger.logMemory(with: tag)
-        MXLog.info("\(tag) Payload came: \(request.content.userInfo)")
         
-        Self.serialQueue.sync {
-            // If the session directories have changed, the user has logged out and back in (even if they entered the same user ID).
-            // We can't do this comparison with the access token of the existing session here due to token refresh when using OIDC.
-            if Self.userSession == nil || Self.userSession?.sessionDirectories != credentials.restorationToken.sessionDirectories {
-                // This function might be run concurrently and from different processes
-                // It's imperative that we create **at most** one UserSession/Client per process
-                Task.synchronous { [appHooks] in
-                    do {
-                        Self.userSession = try await NSEUserSession(credentials: credentials,
-                                                                    clientSessionDelegate: keychainController,
-                                                                    appHooks: appHooks,
-                                                                    appSettings: settings)
-                    } catch {
-                        MXLog.error("Failed creating user session with error: \(error)")
-                    }
-                }
-            }
-            
-            if Self.userSession == nil {
-                return discard(unreadCount: request.unreadCount)
-            }
-        }
+        ExtensionLogger.logMemory(with: tag)
+        MXLog.info("\(tag) Received payload: \(request.content.userInfo)")
         
-        guard Self.eventCacheEnabled == settings.eventCacheEnabled else {
-            MXLog.error("Found missmatch `eventCacheEnabled` feature flag missmatch, restarting the NSE.")
-            exit(0)
-        }
-
         Task {
-            await run(with: credentials,
-                      roomID: roomID,
-                      eventID: eventID,
-                      unreadCount: request.unreadCount)
+            do {
+                let userSession = try await NSEUserSession(credentials: credentials,
+                                                           roomID: roomID,
+                                                           clientSessionDelegate: keychainController,
+                                                           appHooks: appHooks,
+                                                           appSettings: settings)
+                
+                ExtensionLogger.logMemory(with: tag)
+                MXLog.info("\(tag) Configured user session")
+                
+                await processEvent(eventID,
+                                   roomID: roomID,
+                                   unreadCount: request.unreadCount,
+                                   userSession: userSession)
+            } catch {
+                MXLog.error("Failed creating user session with error: \(error)")
+            }
         }
     }
     
     override func serviceExtensionTimeWillExpire() {
         // Called just before the extension will be terminated by the system.
         // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
-        MXLog.warning("\(tag) serviceExtensionTimeWillExpire")
+        MXLog.warning("\(tag) Extension time will expire")
         notify(unreadCount: nil)
     }
     
     // MARK: - Private
 
-    private func run(with credentials: KeychainCredentials,
-                     roomID: String,
-                     eventID: String,
-                     unreadCount: Int?) async {
-        MXLog.info("\(tag) run with roomId: \(roomID), eventId: \(eventID)")
+    private func processEvent(_ eventID: String,
+                              roomID: String,
+                              unreadCount: Int?,
+                              userSession: NSEUserSession) async {
+        MXLog.info("\(tag) Processing event: \(eventID) in room: \(roomID)")
         
-        guard let userSession = Self.userSession else {
-            MXLog.error("Invalid NSE User Session, discarding.")
-            return discard(unreadCount: unreadCount)
-        }
-
         do {
             guard let itemProxy = await userSession.notificationItemProxy(roomID: roomID, eventID: eventID) else {
-                MXLog.info("\(tag) no notification for the event, discard")
+                MXLog.error("\(tag) Failed retrieving notification item")
                 return discard(unreadCount: unreadCount)
             }
                   
@@ -157,40 +126,35 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
                 break
             }
             
-            // After the first processing, update the modified content
             modifiedContent = try await notificationContentBuilder.content(for: itemProxy, mediaProvider: nil)
             
             guard itemProxy.hasMedia else {
-                MXLog.info("\(tag) no media needed")
-
-                // We've processed the item and no media operations needed, so no need to go further
+                MXLog.info("\(tag) Notification item doesn't contain media")
                 return notify(unreadCount: unreadCount)
             }
 
-            MXLog.info("\(tag) process with media")
-
-            // There is some media to load, process it again
+            MXLog.info("\(tag) Processing media")
             if let latestContent = try? await notificationContentBuilder.content(for: itemProxy, mediaProvider: userSession.mediaProvider) {
-                // Processing finished, hopefully with some media
                 modifiedContent = latestContent
+            } else {
+                MXLog.error("\(tag) Failed processing notification media")
             }
-            // We still notify, but without the media attachment if it fails to load
             
             return notify(unreadCount: unreadCount)
         } catch {
-            MXLog.error("NSE run error: \(error)")
+            MXLog.error("Failed processing with error: \(error)")
             return discard(unreadCount: unreadCount)
         }
     }
     
     private func notify(unreadCount: Int?) {
-        MXLog.info("\(tag) notify")
-
         guard let modifiedContent else {
-            MXLog.info("\(tag) notify: no modified content")
+            MXLog.error("\(tag) Notification modified content invalid")
             return discard(unreadCount: unreadCount)
         }
         
+        MXLog.info("\(tag) Displaying notification")
+        
         if let unreadCount {
             modifiedContent.badge = NSNumber(value: unreadCount)
         }
@@ -200,7 +164,7 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
     }
 
     private func discard(unreadCount: Int?) {
-        MXLog.info("\(tag) discard")
+        MXLog.info("\(tag) Discarding notification")
         
         let content = UNMutableNotificationContent()
         
@@ -242,7 +206,7 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
                 }
             case .roomRedaction(let redactedEventID, _):
                 guard let redactedEventID else {
-                    MXLog.error("Unable to handle redact notification due to missing event ID.")
+                    MXLog.error("Unable to handle redact notification due to missing event ID")
                     return .processedShouldDiscard
                 }
                 
diff --git a/NSE/Sources/Other/NSEUserSession.swift b/NSE/Sources/Other/NSEUserSession.swift
index 7fddb8fe39..9cd0ec7f02 100644
--- a/NSE/Sources/Other/NSEUserSession.swift
+++ b/NSE/Sources/Other/NSEUserSession.swift
@@ -19,7 +19,11 @@ final class NSEUserSession {
                                                                                networkMonitor: nil)
     private let delegateHandle: TaskHandle?
 
-    init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate, appHooks: AppHooks, appSettings: CommonSettingsProtocol) async throws {
+    init(credentials: KeychainCredentials,
+         roomID: String,
+         clientSessionDelegate: ClientSessionDelegate,
+         appHooks: AppHooks,
+         appSettings: CommonSettingsProtocol) async throws {
         sessionDirectories = credentials.restorationToken.sessionDirectories
         
         userID = credentials.userID
@@ -34,17 +38,18 @@ final class NSEUserSession {
                          slidingSync: .restored,
                          sessionDelegate: clientSessionDelegate,
                          appHooks: appHooks,
-                         enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode,
-                         eventCacheEnabled: appSettings.eventCacheEnabled)
+                         enableOnlySignedDeviceIsolationMode: appSettings.enableOnlySignedDeviceIsolationMode)
             .sessionPaths(dataPath: credentials.restorationToken.sessionDirectories.dataPath,
                           cachePath: credentials.restorationToken.sessionDirectories.cachePath)
             .username(username: credentials.userID)
             .homeserverUrl(url: homeserverURL)
-            .passphrase(passphrase: credentials.restorationToken.passphrase)
+            .sessionPassphrase(passphrase: credentials.restorationToken.passphrase)
         
         baseClient = try await clientBuilder.build()
         delegateHandle = baseClient.setDelegate(delegate: ClientDelegateWrapper())
-        try await baseClient.restoreSession(session: credentials.restorationToken.session)
+        
+        try await baseClient.restoreSessionWith(session: credentials.restorationToken.session,
+                                                roomLoadSettings: .one(roomId: roomID))
         
         notificationClient = try await baseClient.notificationClient(processSetup: .multipleProcesses)
     }
diff --git a/project.yml b/project.yml
index 1ee8581058..5510c7ba79 100644
--- a/project.yml
+++ b/project.yml
@@ -59,7 +59,7 @@ packages:
   # Element/Matrix dependencies
   MatrixRustSDK:
     url: https://github.com/element-hq/matrix-rust-components-swift
-    exactVersion: 25.03.31
+    exactVersion: 25.04.02
     # path: ../matrix-rust-sdk
   Compound:
     url: https://github.com/element-hq/compound-ios


Reviews:
user: login: Velin92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T13:39:17Z
user: login: Velin92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T13:41:42Z
user: login: Velin92
body: LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-03T13:42:53Z
user: login: stefanceriu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T13:43:56Z

Review comments:
diff_hunk: @@ -157,40 +126,35 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
                 break
             }
             
-            // After the first processing, update the modified content
             modifiedContent = try await notificationContentBuilder.content(for: itemProxy, mediaProvider: nil)
             
             guard itemProxy.hasMedia else {
-                MXLog.info("\(tag) no media needed")
-
-                // We've processed the item and no media operations needed, so no need to go further
+                MXLog.info("\(tag) Notification item doesn't contain media")
                 return notify(unreadCount: unreadCount)
             }
 
-            MXLog.info("\(tag) process with media")
-
-            // There is some media to load, process it again
+            MXLog.info("\(tag) Processing media")
             if let latestContent = try? await notificationContentBuilder.content(for: itemProxy, mediaProvider: userSession.mediaProvider) {
-                // Processing finished, hopefully with some media
                 modifiedContent = latestContent
+            } else {
+                MXLog.error("\(tag) Failed processnig notification media")
path: NSE/Sources/NotificationServiceExtension.swift
user: login: Velin92
body: ```suggestion
                MXLog.error("\(tag) Failed processing notification media")
```
created_at: 2025-04-03T13:39:17Z
author_association: MEMBER
diff_hunk: @@ -85,68 +73,49 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
         modifiedContent = request.content.mutableCopy() as? UNMutableNotificationContent
         
         MXLog.info("\(tag) #########################################")
-        ExtensionLogger.logMemory(with: tag)
-        MXLog.info("\(tag) Payload came: \(request.content.userInfo)")
         
-        Self.serialQueue.sync {
-            // If the session directories have changed, the user has logged out and back in (even if they entered the same user ID).
-            // We can't do this comparison with the access token of the existing session here due to token refresh when using OIDC.
-            if Self.userSession == nil || Self.userSession?.sessionDirectories != credentials.restorationToken.sessionDirectories {
-                // This function might be run concurrently and from different processes
-                // It's imperative that we create **at most** one UserSession/Client per process
-                Task.synchronous { [appHooks] in
-                    do {
-                        Self.userSession = try await NSEUserSession(credentials: credentials,
-                                                                    clientSessionDelegate: keychainController,
-                                                                    appHooks: appHooks,
-                                                                    appSettings: settings)
-                    } catch {
-                        MXLog.error("Failed creating user session with error: \(error)")
-                    }
-                }
-            }
-            
-            if Self.userSession == nil {
-                return discard(unreadCount: request.unreadCount)
-            }
-        }
path: NSE/Sources/NotificationServiceExtension.swift
user: login: Velin92
body: Oh so this has been finally fixed?
created_at: 2025-04-03T13:41:42Z
author_association: MEMBER
diff_hunk: @@ -85,68 +73,49 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
         modifiedContent = request.content.mutableCopy() as? UNMutableNotificationContent
         
         MXLog.info("\(tag) #########################################")
-        ExtensionLogger.logMemory(with: tag)
-        MXLog.info("\(tag) Payload came: \(request.content.userInfo)")
         
-        Self.serialQueue.sync {
-            // If the session directories have changed, the user has logged out and back in (even if they entered the same user ID).
-            // We can't do this comparison with the access token of the existing session here due to token refresh when using OIDC.
-            if Self.userSession == nil || Self.userSession?.sessionDirectories != credentials.restorationToken.sessionDirectories {
-                // This function might be run concurrently and from different processes
-                // It's imperative that we create **at most** one UserSession/Client per process
-                Task.synchronous { [appHooks] in
-                    do {
-                        Self.userSession = try await NSEUserSession(credentials: credentials,
-                                                                    clientSessionDelegate: keychainController,
-                                                                    appHooks: appHooks,
-                                                                    appSettings: settings)
-                    } catch {
-                        MXLog.error("Failed creating user session with error: \(error)")
-                    }
-                }
-            }
-            
-            if Self.userSession == nil {
-                return discard(unreadCount: request.unreadCount)
-            }
-        }
path: NSE/Sources/NotificationServiceExtension.swift
user: login: stefanceriu
body: That's what I'm lead to believe but I think we'll need to have to wait and see.
created_at: 2025-04-03T13:43:56Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-03T12:51:23Z
body: 
<!--
  0 failure: 
  1 warning:  You seem to have ...
  
  
  DangerID: danger-id-Danger;
-->


<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true">Warnings</th>
    </tr>
  </thead>
  <tbody><tr>
      <td>:warning:</td>
      <td>You seem to have made changes to views. Please consider adding screenshots.</td>
    </tr>
  </tbody>
</table>



<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/swift">Danger Swift</a> against 9b6f09c1eed14d3a0b3d6564096e838ebdc4084c
</p>

author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-03T13:05:42Z
body: ## [Codecov](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) Report
Attention: Patch coverage is `0%` with `45 lines` in your changes missing coverage. Please review.
> Project coverage is 79.19%. Comparing base [(`27070f7`)](https://app.codecov.io/gh/element-hq/element-x-ios/commit/27070f77fde5755c4e1305da9dd26fadabccc044?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) to head [(`9b6f09c`)](https://app.codecov.io/gh/element-hq/element-x-ios/commit/9b6f09c1eed14d3a0b3d6564096e838ebdc4084c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq).
> Report is 4 commits behind head on develop.

:white_check_mark: All tests successful. No failed tests found.

| [Files with missing lines](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) | Patch % | Lines |
|---|---|---|
| [NSE/Sources/NotificationServiceExtension.swift](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&filepath=NSE%2FSources%2FNotificationServiceExtension.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq#diff-TlNFL1NvdXJjZXMvTm90aWZpY2F0aW9uU2VydmljZUV4dGVuc2lvbi5zd2lmdA==) | 0.00% | [33 Missing :warning: ](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) |
| [NSE/Sources/Other/NSEUserSession.swift](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&filepath=NSE%2FSources%2FOther%2FNSEUserSession.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq#diff-TlNFL1NvdXJjZXMvT3RoZXIvTlNFVXNlclNlc3Npb24uc3dpZnQ=) | 0.00% | [6 Missing :warning: ](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) |
| [...s/Authentication/AuthenticationClientBuilder.swift](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&filepath=ElementX%2FSources%2FServices%2FAuthentication%2FAuthenticationClientBuilder.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq#diff-RWxlbWVudFgvU291cmNlcy9TZXJ2aWNlcy9BdXRoZW50aWNhdGlvbi9BdXRoZW50aWNhdGlvbkNsaWVudEJ1aWxkZXIuc3dpZnQ=) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) |
| [...ources/Services/UserSession/UserSessionStore.swift](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&filepath=ElementX%2FSources%2FServices%2FUserSession%2FUserSessionStore.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq#diff-RWxlbWVudFgvU291cmNlcy9TZXJ2aWNlcy9Vc2VyU2Vzc2lvbi9Vc2VyU2Vzc2lvblN0b3JlLnN3aWZ0) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) |
| [...mentX/Sources/Other/Extensions/ClientBuilder.swift](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&filepath=ElementX%2FSources%2FOther%2FExtensions%2FClientBuilder.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq#diff-RWxlbWVudFgvU291cmNlcy9PdGhlci9FeHRlbnNpb25zL0NsaWVudEJ1aWxkZXIuc3dpZnQ=) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) |
| [...perOptionsScreen/View/DeveloperOptionsScreen.swift](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&filepath=ElementX%2FSources%2FScreens%2FSettings%2FDeveloperOptionsScreen%2FView%2FDeveloperOptionsScreen.swift&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq#diff-RWxlbWVudFgvU291cmNlcy9TY3JlZW5zL1NldHRpbmdzL0RldmVsb3Blck9wdGlvbnNTY3JlZW4vVmlldy9EZXZlbG9wZXJPcHRpb25zU2NyZWVuLnN3aWZ0) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) |

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop    #3973      +/-   ##
===========================================
- Coverage    79.19%   79.19%   -0.01%     
===========================================
  Files          793      793              
  Lines        71690    71683       -7     
===========================================
- Hits         56773    56767       -6     
+ Misses       14917    14916       -1     
```

| [Flag](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq) | `71.20% <0.00%> (+0.03%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/element-hq/element-x-ios/pull/3973?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=element-hq).

author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-03T13:49:21Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=element-x-ios&pullRequest=3973) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=element-x-ios&pullRequest=3973&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=element-x-ios&pullRequest=3973&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=element-x-ios&pullRequest=3973&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=element-x-ios&pullRequest=3973&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=element-x-ios&pullRequest=3973&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=element-x-ios&pullRequest=3973)


author_association: NONE