From c01be3b35995b414bc15ec84966047cb95a209e3 Mon Sep 17 00:00:00 2001 From: ecgang Date: Sun, 5 Jul 2026 20:51:23 -0700 Subject: [PATCH] fix: gate in-sheet actions on launch presentation; require isFavorite for DM restore Addresses two Codex P2 review comments on #1364: - ContentSheetViews fingerprint drill-down and image-picker gates missed the launch-presentation flag, so on the launch-fallback path (only presentsConversationListOnLaunch true) tapping a peer fingerprint was a silent no-op. Add || appChromeModel.presentsConversationListOnLaunch to both in-sheet gate conditions to match the ContentView call site. - isDirectChatRestorable treated any non-nil favorite record as restorable, but removeFavorite retains a record (isFavorite: false, theyFavoritedUs: true) when the peer still favorites us, so a DM to an unfavorited peer reopened on restart. Key the resolver on isFavorite, not record existence. Adds a regression test for the unfavorited-but-still-favorited-by-them peer. --- bitchat/App/AppRuntime.swift | 2 +- bitchat/Views/ContentSheetViews.swift | 4 ++-- .../ConversationStoreLastActiveTests.swift | 21 +++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) 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 {