Skip to content

Push wakes the phone for messages the app itself refuses to mark unread — the push lease subscribes to every channel message, the unread rule excludes untouched thread replies #7825

Description

@mewc

Describe the bug

Buzz has two different, independently-written answers to "is this event worth telling the user about", and they disagree. The push lease is broad; the in-app unread rule is narrow. The result on a phone is the worst of both: it buzzes for a thread reply in a thread you have never touched, you open Buzz, and nothing is badged — because the client's own rule says that event was never notification-worthy.

Verified against main at 312cf67.

The two rules

Push — mobile/lib/shared/push/push_subscription.dart:492-503, buildDesiredBuzzPushSubscriptions:

final channels = normalizedChannelIds.difference(normalizedMuted).toList()..sort();
for (final chunk in _chunks(channels, buzzPushChannelChunkSize)) {
  subscriptions.add(
    BuzzPushSubscription(
      filter: BuzzPushFilter(kinds: buzzPushChannelKinds, hTags: chunk),
      notificationClass: 'default',
      ignore: ignores,
      suppress: suppression,

buzzPushChannelKinds is [9] (:9). The filter is kind + #h only. The only narrowing is ignore (self-authored, muted channels) and suppress (pTagsMax: 20, the hellthread guard). Per NIP-CW a thread reply is a kind:9 carrying the channel's h tag — depth is derived from e reply markers, which this filter does not look at. So every reply in every non-muted channel you are a member of matches.

In-app unread — mobile/lib/features/channels/unread_badge/should_notify_for_event.dart:39-49:

if (ref.parentId == null) return true;

final rootId = ref.rootId;
if (rootId != null && mutedRootIds.contains(rootId)) return false;

return rootId != null &&
    (participatedRootIds.contains(rootId) ||
        followedRootIds.contains(rootId) ||
        authoredRootIds.contains(rootId));

A reply only counts if you're p-tagged, it's broadcast, or you participated in / follow / authored the root. This is the right rule — it's what stops a busy channel's threads from lighting up the whole sidebar. It is also strictly narrower than the push filter.

Steps to reproduce

  1. iOS build with BUZZ_PUSH_GATEWAY_URL baked in, against a relay that advertises nip-pl (see Hosted communities: enable iPhone push (NIP-PL) for sunnn.communities.buzz.xyz + is the public App Store build covered? #7415 — this is currently the hard part).
  2. Join a channel with active threads. Do not post in, follow, or get mentioned in thread T.
  3. Lock the phone.
  4. Have someone reply in thread T, with no p tag for you and no ["broadcast","1"].

Expected behavior

Either the phone stays quiet, or the app badges the thread when opened. The two surfaces agree.

Actual behavior

The phone wakes and shows a banner. Opening Buzz shows no unread badge for that channel or thread, because shouldNotifyForEvent returns false for the same event.

Why this matters more than it sounds

It's the mechanism behind "notifications are noisy and I still miss things" — the most common shape of notification complaint, and it's why muting-by-hand doesn't converge. It also makes every other notification issue harder to reproduce, because the reporter's phone and the reporter's app are describing different event sets.

Suggested direction

shouldNotifyForEvent is the product decision and it's already written down. The push lease should express the same predicate as far as a relay-side filter can: the self-directed #p subscription already covers mentions; the broad per-channel #h subscription is what over-fires. Options, roughly in order of how much protocol work they need:

  1. Narrow the channel subscription to depth-0 (no e reply marker) plus broadcast — needs a NIP-PL filter primitive that can express "no reply marker", which today it can't.
  2. Add followed/participated root ids as an #e subscription and drop the blanket #h one. Bounded by buzzPushMaxSubscriptions = 16 and the 50-per-chunk size, so it won't hold for a heavy user — but it's closer to correct than today.
  3. Keep the broad lease and resolve on-device in the NSE: it already fetches relay data to rewrite title/body (mobile/ios/NotificationService/NotificationService.swift:76-79). Apply shouldNotifyForEvent there and suppress rather than present. Costs a wake per event but makes the two surfaces agree, and needs no protocol change.

Whichever, the predicate should live in one place and both surfaces should import it. Right now should_notify_for_event.dart has no test file (grep -rl shouldNotifyForEvent mobile/test → nothing), which is probably why the divergence went unnoticed.

Version and platform

  • Buzz version: 0.5.23
  • Mobile: iOS and Android
  • Relay: hosted twelfth-ai.communities.buzz.xyz, relay software 0.2.1
  • Verified at block/buzz@312cf67

Related: #7447, #7779, #7186, #6092, #4657

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions