mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
### What changed? Adds optimistic rendering for newly locally posted messages in channels and threads. - Messages appear immediately - Relay echoes and history are deduplicated by event ID - Rejected or timed-out publishes erase the optimistic rendering. The implementation covers reconnect and hydration races, channel-window and legacy WebSocket paths, thread-local overlays, and rapid concurrent sends. ### Why? This is a valuable partial mitigation for [BOT-1449](https://linear.app/squareup/issue/BOT-1449/buzz-mobile-posted-messages-dont-appear-until-leavingre-entering-the): senders no longer depend on receiving a relay echo before seeing their own post. It does not address the likely primary cause of stale channels. Mobile currently does not recover live subscriptions after a rate-limited relay `CLOSED`; that recovery is being handled separately. ### How is it tested? Full mobile suite: 676 passed, 1 skipped. Added regression coverage for optimistic insertion, authoritative deduplication, rollback, reconnect and hydration, thread replies, rapid and equal-time sends, never-echoed successful sends, and legacy WebSocket retirement. --------- Signed-off-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz> Co-authored-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
43 lines
1.1 KiB
Dart
43 lines
1.1 KiB
Dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import '../../shared/relay/relay.dart';
|
|
|
|
/// Signed local messages whose publish has not yet been corroborated by an
|
|
/// authoritative relay EVENT or query result.
|
|
class PendingLocalMessagesNotifier extends Notifier<Map<String, NostrEvent>> {
|
|
final String channelId;
|
|
|
|
PendingLocalMessagesNotifier(this.channelId);
|
|
|
|
@override
|
|
Map<String, NostrEvent> build() => const {};
|
|
|
|
void add(NostrEvent event) {
|
|
state = {...state, event.id: event};
|
|
}
|
|
|
|
NostrEvent? take(String eventId) {
|
|
final event = state[eventId];
|
|
if (event == null) return null;
|
|
final next = {...state}..remove(eventId);
|
|
state = next;
|
|
return event;
|
|
}
|
|
|
|
void confirm(Iterable<String> eventIds) {
|
|
final confirmed = eventIds.toSet();
|
|
if (!state.keys.any(confirmed.contains)) return;
|
|
state = {
|
|
for (final entry in state.entries)
|
|
if (!confirmed.contains(entry.key)) entry.key: entry.value,
|
|
};
|
|
}
|
|
}
|
|
|
|
final pendingLocalMessagesProvider =
|
|
NotifierProvider.family<
|
|
PendingLocalMessagesNotifier,
|
|
Map<String, NostrEvent>,
|
|
String
|
|
>(PendingLocalMessagesNotifier.new);
|