diff --git a/bitchat/App/AppRuntime.swift b/bitchat/App/AppRuntime.swift index e37eb8c1..98379cc6 100644 --- a/bitchat/App/AppRuntime.swift +++ b/bitchat/App/AppRuntime.swift @@ -185,7 +185,7 @@ final class AppRuntime: ObservableObject { favorites: FavoritesPersistenceService ) -> Bool { isDirectChatRestorable(peerID, isPeerFavorited: { - favorites.getFavoriteStatus(forPeerID: $0.toShort()) != nil + favorites.getFavoriteStatus(forPeerID: $0.toShort())?.isFavorite ?? false }) } diff --git a/bitchat/Views/ContentSheetViews.swift b/bitchat/Views/ContentSheetViews.swift index dd72d8b6..ac0c5b61 100644 --- a/bitchat/Views/ContentSheetViews.swift +++ b/bitchat/Views/ContentSheetViews.swift @@ -85,7 +85,7 @@ struct ContentPeopleSheetView: View { } } .navigationDestination(isPresented: Binding( - get: { appChromeModel.showingFingerprintFor != nil && (showSidebar || privateConversationModel.selectedPeerID != nil) }, + get: { appChromeModel.showingFingerprintFor != nil && (showSidebar || privateConversationModel.selectedPeerID != nil || appChromeModel.presentsConversationListOnLaunch) }, set: { isPresented in if !isPresented { appChromeModel.clearFingerprint() @@ -105,7 +105,7 @@ struct ContentPeopleSheetView: View { #endif #if os(iOS) .fullScreenCover(isPresented: Binding( - get: { showImagePicker && (showSidebar || privateConversationModel.selectedPeerID != nil) }, + get: { showImagePicker && (showSidebar || privateConversationModel.selectedPeerID != nil || appChromeModel.presentsConversationListOnLaunch) }, set: { newValue in if !newValue { showImagePicker = false diff --git a/bitchatTests/ConversationStoreLastActiveTests.swift b/bitchatTests/ConversationStoreLastActiveTests.swift index 292947fd..dbd3ebaa 100644 --- a/bitchatTests/ConversationStoreLastActiveTests.swift +++ b/bitchatTests/ConversationStoreLastActiveTests.swift @@ -183,6 +183,27 @@ final class ConversationStoreLastActiveTests: XCTestCase { ) } + func test_production_unfavoritedPeerWhoStillFavoritesUsIsNotRestorable() { + // removeFavorite RETAINS a record (isFavorite: false, theyFavoritedUs: + // true) when the peer still favorites us. The resolver must key on + // isFavorite, not mere record existence — otherwise a DM to a peer we + // deliberately unfavorited reopens on restart, contradicting the + // "is a persisted favorite" contract. Regression for Codex P2 review. + let favorites = FavoritesPersistenceService(keychain: MockKeychain()) + let noiseKey = Data((0..<32).map(UInt8.init)) + favorites.addFavorite(peerNoisePublicKey: noiseKey, peerNickname: "Alice") + favorites.updatePeerFavoritedUs(peerNoisePublicKey: noiseKey, favorited: true) + favorites.removeFavorite(peerNoisePublicKey: noiseKey) + + let fullHexPeer = PeerID(str: noiseKey.hexEncodedString()) + // The record survives (they still favorite us) but isFavorite is false. + XCTAssertNotNil(favorites.getFavoriteStatus(forPeerID: fullHexPeer.toShort())) + XCTAssertFalse(favorites.getFavoriteStatus(forPeerID: fullHexPeer.toShort())!.isFavorite) + XCTAssertFalse( + AppRuntime.isDirectChatRestorable(fullHexPeer, favorites: favorites) + ) + } + // MARK: - Helpers private func makeStorage() -> UserDefaults {