From 0f7fec6caa06d04fd4d7e50327f0facaee2c10de Mon Sep 17 00:00:00 2001 From: Sam Trouy Date: Tue, 28 Jul 2026 10:15:52 -0400 Subject: [PATCH 1/2] fix(mobile): retain live thread replies in channel window Co-authored-by: Sam Trouy Signed-off-by: Sam Trouy --- .../channels/channel_messages_provider.dart | 7 -- .../channel_messages_provider_test.dart | 74 +++++++++++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/mobile/lib/features/channels/channel_messages_provider.dart b/mobile/lib/features/channels/channel_messages_provider.dart index 7f657f4b7c..0e442d88d5 100644 --- a/mobile/lib/features/channels/channel_messages_provider.dart +++ b/mobile/lib/features/channels/channel_messages_provider.dart @@ -220,7 +220,6 @@ class ChannelMessagesNotifier extends Notifier>> { ), ); } - if (!_isBroadcastReply(event)) return false; } if (!isTimelineRow && !EventKind.channelAuxEventKinds.contains(event.kind)) { @@ -461,12 +460,6 @@ class ChannelMessagesNotifier extends Notifier>> { } } -bool _isBroadcastReply(NostrEvent event) { - return event.tags.any( - (tag) => tag.length >= 2 && tag[0] == 'broadcast' && tag[1] == '1', - ); -} - int _currentUnixSeconds() => DateTime.now().millisecondsSinceEpoch ~/ 1000; final channelMessagesProvider = diff --git a/mobile/test/features/channels/channel_messages_provider_test.dart b/mobile/test/features/channels/channel_messages_provider_test.dart index 6620c8c506..c9596bb3f0 100644 --- a/mobile/test/features/channels/channel_messages_provider_test.dart +++ b/mobile/test/features/channels/channel_messages_provider_test.dart @@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:buzz/features/channels/channel_messages_provider.dart'; import 'package:buzz/features/channels/pending_local_messages_provider.dart'; import 'package:buzz/features/channels/thread_replies_provider.dart'; +import 'package:buzz/features/channels/timeline_message.dart'; import 'package:buzz/shared/relay/relay.dart'; void main() { @@ -242,6 +243,79 @@ void main() { ); }); + test('live non-broadcast reply updates its parent thread summary', () async { + final relaySession = _RecordingRelaySessionNotifier( + queryResults: [ + [_event(id: 'root', createdAt: 10), _bounds()], + ], + ); + final container = _buildContainer(relaySession); + addTearDown(container.dispose); + + container.read(channelMessagesProvider(_channelId)); + await relaySession.subscribed; + await _pumpEventQueue(); + + relaySession.emit( + _event( + id: 'reply', + createdAt: 20, + extraTags: const [ + ['e', 'root', '', 'reply'], + ], + ), + ); + await _pumpEventQueue(); + + final notifier = container.read( + channelMessagesProvider(_channelId).notifier, + ); + final events = container.read(channelMessagesProvider(_channelId)).value!; + final entries = buildMainTimelineEntries( + formatTimeline(events), + relaySummaries: notifier.threadSummaries, + ); + + expect(entries.single.summary?.replyCount, 1); + }); + + test( + 'live non-broadcast reply stays out of the top-level timeline', + () async { + final relaySession = _RecordingRelaySessionNotifier( + queryResults: [ + [_event(id: 'root', createdAt: 10), _bounds()], + ], + ); + final container = _buildContainer(relaySession); + addTearDown(container.dispose); + + container.read(channelMessagesProvider(_channelId)); + await relaySession.subscribed; + await _pumpEventQueue(); + + relaySession.emit( + _event( + id: 'reply', + createdAt: 20, + extraTags: const [ + ['e', 'root', '', 'reply'], + ], + ), + ); + await _pumpEventQueue(); + + final events = container.read(channelMessagesProvider(_channelId)).value!; + expect(events.map((event) => event.id), ['root', 'reply']); + expect( + buildMainTimelineEntries( + formatTimeline(events), + ).map((entry) => entry.message.id), + ['root'], + ); + }, + ); + test('reconnect hydration cannot retain a rolled-back local row', () async { final relaySession = _RecordingRelaySessionNotifier( queryResults: [ From a1b7c004876c7c5ce16a0b0c8d13084eefde7823 Mon Sep 17 00:00:00 2001 From: Daniel Aguilera Date: Fri, 31 Jul 2026 16:18:39 -0400 Subject: [PATCH 2/2] test(mobile): move live reply coverage past hydration Keep the live-reply regression tests away from main's newly added local-row summary test so both changes merge without conflict. Co-authored-by: Daniel Aguilera Signed-off-by: Daniel Aguilera --- .../channel_messages_provider_test.dart | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/mobile/test/features/channels/channel_messages_provider_test.dart b/mobile/test/features/channels/channel_messages_provider_test.dart index c9596bb3f0..89b6b597e8 100644 --- a/mobile/test/features/channels/channel_messages_provider_test.dart +++ b/mobile/test/features/channels/channel_messages_provider_test.dart @@ -243,6 +243,46 @@ void main() { ); }); + test('reconnect hydration cannot retain a rolled-back local row', () async { + final relaySession = _RecordingRelaySessionNotifier( + queryResults: [ + [_event(id: 'history', createdAt: 10), _bounds()], + [_event(id: 'history', createdAt: 10), _bounds()], + ], + ); + final container = _buildContainer(relaySession); + addTearDown(container.dispose); + + container.read(channelMessagesProvider(_channelId)); + await relaySession.subscribed; + await _pumpEventQueue(); + final notifier = container.read( + channelMessagesProvider(_channelId).notifier, + ); + notifier.addLocalMessage(_event(id: 'local', createdAt: 20)); + + relaySession.setConnected(false); + await _pumpEventQueue(); + relaySession.setConnected(true); + await _pumpEventQueue(); + expect( + container + .read(channelMessagesProvider(_channelId)) + .value + ?.map((event) => event.id), + ['history', 'local'], + ); + + notifier.removeLocalMessage('local'); + expect( + container + .read(channelMessagesProvider(_channelId)) + .value + ?.map((event) => event.id), + ['history'], + ); + }); + test('live non-broadcast reply updates its parent thread summary', () async { final relaySession = _RecordingRelaySessionNotifier( queryResults: [ @@ -316,46 +356,6 @@ void main() { }, ); - test('reconnect hydration cannot retain a rolled-back local row', () async { - final relaySession = _RecordingRelaySessionNotifier( - queryResults: [ - [_event(id: 'history', createdAt: 10), _bounds()], - [_event(id: 'history', createdAt: 10), _bounds()], - ], - ); - final container = _buildContainer(relaySession); - addTearDown(container.dispose); - - container.read(channelMessagesProvider(_channelId)); - await relaySession.subscribed; - await _pumpEventQueue(); - final notifier = container.read( - channelMessagesProvider(_channelId).notifier, - ); - notifier.addLocalMessage(_event(id: 'local', createdAt: 20)); - - relaySession.setConnected(false); - await _pumpEventQueue(); - relaySession.setConnected(true); - await _pumpEventQueue(); - expect( - container - .read(channelMessagesProvider(_channelId)) - .value - ?.map((event) => event.id), - ['history', 'local'], - ); - - notifier.removeLocalMessage('local'); - expect( - container - .read(channelMessagesProvider(_channelId)) - .value - ?.map((event) => event.id), - ['history'], - ); - }); - test( 'thread replies are inserted, deduped, and rolled back locally', () async {