From 41549e685dc947648e999a321cfc034a128b176e Mon Sep 17 00:00:00 2001 From: Tom Brow Date: Mon, 26 Oct 2026 18:53:37 -0700 Subject: [PATCH] fix(mobile): push every message in your own Solo chats Solo chats are private streams, not DMs, so the push lease only covered them through explicit mentions. Subscribe channel-wide to the viewer's own joined Solo chats, matching the Solo tab's predicate. Signed-off-by: Tom Brow --- .../push/push_subscription_provider.dart | 13 ++-- .../push/push_subscription_provider_test.dart | 71 +++++++++++++++---- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/mobile/lib/shared/push/push_subscription_provider.dart b/mobile/lib/shared/push/push_subscription_provider.dart index 9d24e7cebc..801f887731 100644 --- a/mobile/lib/shared/push/push_subscription_provider.dart +++ b/mobile/lib/shared/push/push_subscription_provider.dart @@ -50,12 +50,15 @@ List? desiredBuzzPushSubscriptions({ return buildDesiredBuzzPushSubscriptions( myPubkey: pubkey, channelIds: [ - // Activity surfaces channel-wide traffic only for joined DM channels. - // Other message kinds enter the inbox through an exact #p mention or - // participant-thread tag, so subscribing to every joined channel would - // over-notify compared with the product predicate. + // Channel-wide traffic pushes only for joined DMs and the viewer's own + // Solo chats, which are conversations with them. Other channels push + // through an exact #p mention, so subscribing to every joined channel + // would over-notify. for (final channel in channels) - if (channel.isDm && channel.isMember && !channel.isArchived) channel.id, + if ((channel.isDm || channel.isPersonalChat(pubkey, const [])) && + channel.isMember && + !channel.isArchived) + channel.id, ], mutedChannelIds: mutedChannelIds, ); diff --git a/mobile/test/shared/push/push_subscription_provider_test.dart b/mobile/test/shared/push/push_subscription_provider_test.dart index d757106594..360dfecc12 100644 --- a/mobile/test/shared/push/push_subscription_provider_test.dart +++ b/mobile/test/shared/push/push_subscription_provider_test.dart @@ -37,6 +37,43 @@ void main() { }, ); + test('subscribes channel-wide to joined DMs and own Solo chats only', () { + final keys = nostr.Keys.generate(); + const dmID = '123e4567-e89b-42d3-a456-426614174003'; + const soloID = '123e4567-e89b-42d3-a456-426614174004'; + const archivedSoloID = '123e4567-e89b-42d3-a456-426614174005'; + const otherSoloID = '123e4567-e89b-42d3-a456-426614174006'; + const streamID = '123e4567-e89b-42d3-a456-426614174007'; + Channel solo(String id, {String? createdBy, bool archived = false}) => + channel( + id, + channelType: 'stream', + visibility: 'private', + labels: const ['buzz.solo'], + createdBy: createdBy ?? keys.public, + archived: archived, + ); + final subscriptions = desiredBuzzPushSubscriptions( + community: Community.create( + name: 'Team', + relayUrl: 'https://relay.example.com', + nsec: keys.nsec, + ), + channels: [ + channel(dmID), + solo(soloID), + solo(archivedSoloID, archived: true), + solo(otherSoloID, createdBy: 'author'), + channel(streamID, channelType: 'stream', visibility: 'private'), + ], + mutedChannelIds: const [], + ); + + expect(subscriptions, hasLength(2)); + expect(subscriptions!.last.filter.kinds, buzzPushChannelKinds); + expect(subscriptions.last.filter.hTags, [dmID, soloID]); + }); + test('returns no desired subscriptions without a signing identity', () { final subscriptions = desiredBuzzPushSubscriptions( community: Community.create( @@ -51,16 +88,24 @@ void main() { }); } -Channel channel(String id, {bool isMember = true, bool archived = false}) => - Channel( - id: id, - name: id, - channelType: 'dm', - visibility: 'open', - description: '', - createdBy: 'author', - createdAt: DateTime(2026), - memberCount: 1, - isMember: isMember, - archivedAt: archived ? DateTime(2026) : null, - ); +Channel channel( + String id, { + bool isMember = true, + bool archived = false, + String channelType = 'dm', + String visibility = 'open', + List labels = const [], + String createdBy = 'author', +}) => Channel( + id: id, + name: id, + channelType: channelType, + visibility: visibility, + labels: labels, + description: '', + createdBy: createdBy, + createdAt: DateTime(2026), + memberCount: 1, + isMember: isMember, + archivedAt: archived ? DateTime(2026) : null, +);