Files
buzz/mobile/test/features/activity/dm_resurface_test.dart
klopez4212 4d77175c78 fix(client): resurface hidden DMs from live activity (#6885)
## Summary

Hiding a DM row and then receiving a new message from that person never
brought the row back — the reader was left with only an Inbox
notification. This restores the row from live activity, client-side,
with no relay or database changes.

- Subscribe to hidden-DM activity **channel-scoped** over the hidden-DM
id set (`#h`), re-registering whenever the set changes. The relay's
`fan_out_scoped` only routes channel-scoped events (every kind:9 DM
carries a `channel_id`) to channel-scoped subscriptions, so a
community-global `#p` filter receives nothing. Hiding a DM never drops
membership, so `#h` authorization holds. Because every delivered event
is already for a hidden DM the reader belongs to, eligibility no longer
needs a self `p` tag — untagged CLI/agent DMs resurface too — and no
per-event visibility fetch is required.
- On qualifying activity, invoke the existing open-DM action (`41010`)
to reopen the hidden one-to-one or group DM, validating that the relay
reopened the same channel.
- Preserve the existing relay-synced hide snapshot (`41012` → `30622`);
hide state still syncs across devices.
- Let Inbox reopen a hidden DM before navigating to its message, with a
busy state on the row and context-menu "Open in channel" action while a
reopen is pending.
- Coalesce resurface attempts per channel: a concurrent message that
arrives while a reopen is in flight triggers a retry on failure instead
of being marked handled and dropped.
- Write exact dm-visibility query keys on open/hide mutations.

This is the client-only replacement agreed in the Buzz design thread and
supersedes #6713. The accepted limitation is that resurface only fires
while a client is running — a message that arrives while all clients are
closed does not restore the row on next launch (the `30622` snapshot has
no watermark); Inbox click-through is the safety net for that case.

Addresses #6316.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
2026-08-27 10:29:26 -04:00

62 lines
1.7 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:buzz/features/activity/dm_resurface.dart';
import 'package:buzz/shared/relay/relay.dart';
void main() {
const self =
'1111111111111111111111111111111111111111111111111111111111111111';
const alice =
'2222222222222222222222222222222222222222222222222222222222222222';
const bob =
'3333333333333333333333333333333333333333333333333333333333333333';
test('derives group DM peers from authoritative membership', () {
expect(dmPeerPubkeysFromMembers([self, alice, bob], self), {alice, bob});
expect(dmPeerPubkeysFromMembers([alice, bob], self), isEmpty);
});
test('accepts external channel messages regardless of p tags', () {
NostrEvent event({
int kind = EventKind.streamMessage,
String? author,
List<List<String>>? tags,
}) => NostrEvent(
id: 'event-1',
pubkey: author ?? alice,
createdAt: 1,
kind: kind,
tags:
tags ??
const [
['h', 'dm-1'],
['p', self],
],
content: 'hello',
sig: 'sig',
);
expect(isIncomingChannelMessageFromOther(event(), self), isTrue);
expect(
isIncomingChannelMessageFromOther(event(kind: EventKind.reaction), self),
isFalse,
);
expect(
isIncomingChannelMessageFromOther(event(author: self), self),
isFalse,
);
// #h-scoped delivery already guarantees relevance: an untagged DM from
// another sender still qualifies.
expect(
isIncomingChannelMessageFromOther(
event(
tags: const [
['h', 'dm-1'],
],
),
self,
),
isTrue,
);
});
}