Desktop catch-up escalates @channel markers from before the reader joined (badge-without-row)
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
> 🤖 Filed by kray's AI agent on his behalf.
Deferred finding from the adversarial review of the #3197 late-joiner backfill fix. Depends on #3197.
## Problem
The server-side mentions feed now bounds `@channel` rows to members who had already joined when the relay admitted the event (`cm.joined_at <= e.received_at`, cf5fd184e). The desktop **catch-up** path has no equivalent bound:
- `useUnreadChannels.ts` fetches per-channel history with `since = readAt + 1`, and a channel with no read state — which is exactly the just-joined case — uses `since: 0`, pulling the full retained timeline.
- Classification runs each fetched event through `shouldNotifyForEvent` / `isHighPriorityEventForUser`, whose `channelNotifyEscalates` treats **every** `["notify","channel"]` marker as mention-tier. It has no join-time input.
Net effect: a member who joins a channel gets mention-tier sidebar/app badges for `@channel` events sent **before they joined** — events the (now-correct) server feed deliberately excludes. Badge with no corresponding Inbox row.
`@here` is only marginally exposed (its 120s observation-time freshness window bounds retroactivity), but the same join-time question applies to an `@here` sent moments before the reader joined.
## Fix directions
1. **Join-time bound in the pure predicate** (mirrors the server): extend `ChannelNotifyContext` (`desktop/src/features/notifications/lib/channelNotifyEscalation.ts`) with an optional `joinedAtSeconds`, and return `false` when `event.created_at < joinedAtSeconds`. Callers in the catch-up path pass the reader's own `joinedAt` (available on `ChannelMember`, `desktop/src/shared/api/types.ts`) — the cost is plumbing per-channel self-membership into `useUnreadChannels`, which today only holds membership *sets*. Note the client only has the client-authored `created_at`, not the relay's `received_at`, so the bound is skew-tolerant rather than exact — same trade-off class the server fix documents.
2. **Feed-driven badges**: treat the bounded mentions feed (which desktop now requests — see #3197) as the authority for `@channel` mention-tier in catch-up, and stop escalating on the raw marker for historical fetches. Live-subscription escalation stays as-is (a live event is by definition post-join).
Option 1 is the smaller change; option 2 removes the client/server divergence class entirely.
## Related
- #3197 — `@channel`/`@here` (NIP-CM); server-side join bound in cf5fd184e
- #3235 — sibling finding: inactive-community rail counts mentions by `#p` only
Contributor guide
Assessment
This issue has not been assessed yet.