From 7b3be7481a17451a14328ea252e6c99dacfa8115 Mon Sep 17 00:00:00 2001 From: Tom Brow Date: Mon, 12 Oct 2026 15:25:54 -0700 Subject: [PATCH] fix(mobile): avoid native snapshot exports for disabled lease retries Signed-off-by: Tom Brow --- .../shared/community/community_provider.dart | 3 +- .../community/community_provider_test.dart | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/mobile/lib/shared/community/community_provider.dart b/mobile/lib/shared/community/community_provider.dart index 7792d55623..e9f470f9e7 100644 --- a/mobile/lib/shared/community/community_provider.dart +++ b/mobile/lib/shared/community/community_provider.dart @@ -553,7 +553,8 @@ class CommunityListNotifier extends AsyncNotifier> { await storage.save(updated); final updatedList = [...current]..[index] = updated; state = AsyncData(updatedList); - unawaited(syncCommunitySnapshot(ref, updatedList)); + // Disabled communities are absent from the native snapshot. Retrying + // changes only the durable generation, so it needs no native export. } pendingCommunity = updated; pendingGeneration = pushState.pendingTombstoneGeneration; diff --git a/mobile/test/shared/community/community_provider_test.dart b/mobile/test/shared/community/community_provider_test.dart index 5ca7da289c..c2b860dbff 100644 --- a/mobile/test/shared/community/community_provider_test.dart +++ b/mobile/test/shared/community/community_provider_test.dart @@ -367,6 +367,60 @@ void main() { } }); + test('failed OFF retries do not queue generation-only snapshots', () async { + final finishExport = Completer(); + var offExports = 0; + final subscription = BuzzPushSubscription( + filter: BuzzPushFilter(kinds: const [9], pTags: ['a' * 64]), + notificationClass: 'default', + ); + final community = + Community.create( + name: 'Test', + relayUrl: 'https://test.example.com', + ).copyWith( + pushNotificationsEnabled: true, + pushSubscriptionState: BuzzPushLeaseSubscriptionState.desired( + desired: [subscription], + ).withAccepted(subscriptions: [subscription], generation: 7), + ); + deactivator = (community, {generation}) async { + throw StateError('offline'); + }; + container = createContainer( + snapshotWriter: (communities) async { + if (communities.isNotEmpty && + !communities.single.pushNotificationsEnabled) { + offExports++; + await finishExport.future; + } + }, + ); + await container.read(communityListProvider.future); + final notifier = container.read(communityListProvider.notifier); + await notifier.addCommunity(community); + try { + await notifier.setPushNotificationsEnabled(community.id, false); + for (var attempt = 0; attempt < 5; attempt++) { + await expectLater( + notifier.retryPendingPushLeaseTombstone( + community.id, + advanceGeneration: true, + ), + throwsStateError, + ); + } + final stored = (await communityStorage.loadAll()).single; + expect(stored.pushNotificationsEnabled, isFalse); + expect(stored.pushSubscriptionState.pendingTombstoneGeneration, 13); + } finally { + finishExport.complete(); + // ON still waits for all earlier exports, making replay observable. + await notifier.setPushNotificationsEnabled(community.id, true); + } + expect(offExports, 1); + }); + test('opt-in still waits for presentation export', () async { final exportStarted = Completer(); final finishExport = Completer();