From 9b72f849665f1ecf514d9d226f78c6119a6e14a1 Mon Sep 17 00:00:00 2001 From: jack Date: Tue, 11 Aug 2026 10:16:53 +0200 Subject: [PATCH] Review fixes: consent on every add-favorite path; correct empty-star tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Codex P1: the context-menu and VoiceOver "add favorite" actions called onToggleFavorite directly, bypassing the one-time consent dialog and disclosing the nostr key without asking. All add-favorite entry points now route through requestFavoriteToggle (consent gate); removals stay immediate. - Codex P2: the empty star showed the "favorited — offline messaging starts when they favorite you back" tooltip on peers that weren't favorited at all. The pending/mutual copy now applies only once isFavorite; an unfavorited star reads "add favorite". Co-Authored-By: Claude Fable 5 --- bitchat/Views/MeshPeerList.swift | 34 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/bitchat/Views/MeshPeerList.swift b/bitchat/Views/MeshPeerList.swift index 16b67d42..ca79801d 100644 --- a/bitchat/Views/MeshPeerList.swift +++ b/bitchat/Views/MeshPeerList.swift @@ -180,15 +180,7 @@ struct MeshPeerList: View { } if !isMe { - Button(action: { - // The first favorite ever asks once: the star - // notifies the peer and shares the nostr key. - if !peer.isFavorite, !FavoriteConsent.isAcknowledged { - pendingFavorite = peer - } else { - onToggleFavorite(peer.peerID) - } - }) { + Button(action: { requestFavoriteToggle(peer) }) { // Mutuality is the load-bearing state (one-sided // favorites don't enable offline delivery), so it // shows at the point of decision: half star until @@ -205,7 +197,7 @@ struct MeshPeerList: View { .contentShape(Rectangle()) } .buttonStyle(.plain) - .help(peer.isMutualFavorite ? Strings.favoriteMutualTooltip : Strings.favoritePendingTooltip) + .help(favoriteTooltip(for: peer)) } } .padding(.horizontal) @@ -221,7 +213,7 @@ struct MeshPeerList: View { onTapPeer(peer.peerID) } Button(peer.isFavorite ? Strings.removeFavorite : Strings.addFavorite) { - onToggleFavorite(peer.peerID) + requestFavoriteToggle(peer) } Button(Strings.showFingerprint) { onShowFingerprint(peer.peerID) @@ -246,7 +238,7 @@ struct MeshPeerList: View { .accessibilityActions { if !isMe { Button(peer.isFavorite ? Strings.removeFavorite : Strings.addFavorite) { - onToggleFavorite(peer.peerID) + requestFavoriteToggle(peer) } Button(Strings.showFingerprint) { onShowFingerprint(peer.peerID) @@ -291,6 +283,24 @@ struct MeshPeerList: View { } } + /// Routes every add-favorite entry point (star, context menu, VoiceOver) + /// through the one-time consent dialog; removals stay immediate. + private func requestFavoriteToggle(_ peer: MeshPeerRow) { + if !peer.isFavorite, !FavoriteConsent.isAcknowledged { + pendingFavorite = peer + } else { + onToggleFavorite(peer.peerID) + } + } + + /// Tooltip for the star: the mutual/pending copy applies only once the + /// peer is actually favorited — on an empty star it would claim a + /// favorite that doesn't exist. + private func favoriteTooltip(for peer: MeshPeerRow) -> String { + guard peer.isFavorite else { return Strings.addFavorite } + return peer.isMutualFavorite ? Strings.favoriteMutualTooltip : Strings.favoritePendingTooltip + } + /// One spoken sentence per row: name, how they're reachable, and any /// state badges — the visual row is icon soup for VoiceOver otherwise. private func accessibilityDescription(for peer: MeshPeerRow) -> String {