Inbox misses reaction notifications until the reacted post is visited (relay mismatch) #43

Closed
opened 2026-07-10 10:36:41 +00:00 by laoc · 1 comment
Owner

Symptom

A reaction to one of my comments did not appear in the dashboard inbox (Posteingang). After visiting the post where the comment lives (nevent1qvzqqqqqqypzpjpc8kqa6fzqvazmdpqfheqdvusuxqgzj3jqvl7v2z39mhu3892fqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7qpq3x5puhcd7gj8f748vz5xcf9pxmnf2qd7nyc8nm8m4sa3ka9wsfgs9yucek), the reaction notification ("Sync hat auf ... reagiert", 42m old) suddenly showed up in the inbox.

Root cause

Relay mismatch between where reactions are published and where the inbox loader queries:

  • Reactions (kind 7) are published via the outbox model: reactor's write relays + the target author's NIP-65 read relays (publishReaction in src/lib/helpers/reactions.jspublishEventOptimistic in src/lib/services/publish-service.js). kindToAppRelayCategory(7) returns null, so no app relay is added.
  • The inbox network loader (src/lib/services/inbox-service.svelte.js, getNotificationRelays()) only queries getCommunikeyRelays() + getCalendarRelays() + getEducationalRelays() — it never queries the logged-in user's own NIP-65 read relays.
  • The inbox filter itself ({ kinds: [7], '#p': [pubkey] }) is correct — applesauce's ReactionBlueprint always p-tags the target author, also for kind-1111 comments. The event simply lives on relays the inbox never asks.

Why visiting the post "fixes" it: the post page's ReactionBar mounts reactionsLoader (src/lib/loaders/reactions.js), which additionally queries getReadRelays(commentAuthor) — i.e. exactly the user's read relays where the reaction was published. The event lands in the shared EventStore and the inbox's reactive eventStore.model(...) subscription surfaces it immediately.

Fix direction

Include the logged-in user's NIP-65 read (inbox) relays in getNotificationRelays() in inbox-service.svelte.js, mirroring what reactionsLoader already does.

Reaction notifications render as "hat auf reagiert" with an empty gap: inbox/+page.svelte never computes/passes contentTitle to InboxItem, so m.inbox_action_reaction({ contentTitle }) interpolates an empty string.

## Symptom A reaction to one of my comments did not appear in the dashboard inbox (Posteingang). After visiting the post where the comment lives (nevent1qvzqqqqqqypzpjpc8kqa6fzqvazmdpqfheqdvusuxqgzj3jqvl7v2z39mhu3892fqythwumn8ghj7un9d3shjtnswf5k6ctv9ehx2ap0qy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7qpq3x5puhcd7gj8f748vz5xcf9pxmnf2qd7nyc8nm8m4sa3ka9wsfgs9yucek), the reaction notification ("Sync hat auf ... reagiert", 42m old) suddenly showed up in the inbox. ## Root cause Relay mismatch between where reactions are published and where the inbox loader queries: - Reactions (kind 7) are published via the outbox model: reactor's write relays + the target author's NIP-65 **read** relays (`publishReaction` in `src/lib/helpers/reactions.js` → `publishEventOptimistic` in `src/lib/services/publish-service.js`). `kindToAppRelayCategory(7)` returns `null`, so **no app relay** is added. - The inbox network loader (`src/lib/services/inbox-service.svelte.js`, `getNotificationRelays()`) only queries `getCommunikeyRelays() + getCalendarRelays() + getEducationalRelays()` — it never queries the logged-in user's own NIP-65 read relays. - The inbox filter itself (`{ kinds: [7], '#p': [pubkey] }`) is correct — applesauce's ReactionBlueprint always p-tags the target author, also for kind-1111 comments. The event simply lives on relays the inbox never asks. Why visiting the post "fixes" it: the post page's `ReactionBar` mounts `reactionsLoader` (`src/lib/loaders/reactions.js`), which additionally queries `getReadRelays(commentAuthor)` — i.e. exactly the user's read relays where the reaction was published. The event lands in the shared EventStore and the inbox's reactive `eventStore.model(...)` subscription surfaces it immediately. ## Fix direction Include the logged-in user's NIP-65 read (inbox) relays in `getNotificationRelays()` in `inbox-service.svelte.js`, mirroring what `reactionsLoader` already does. ## Related cosmetic bug (same surface) Reaction notifications render as "hat auf reagiert" with an empty gap: `inbox/+page.svelte` never computes/passes `contentTitle` to `InboxItem`, so `m.inbox_action_reaction({ contentTitle })` interpolates an empty string.
Author
Owner

Fixed in d6139781, merged to dev.

What was verified against live relays before shipping:

  • The missing reaction ("Sync", + by c8383d81…) exists only on wss://nos.lol and wss://relay.primal.net — the reactor's client published to its own write relays, not to the user's NIP-65 read relays (haven.laoc.xyz, theforest.nostr1.com, relay.mostr.pub).
  • With GATED_MODE_DEFAULT=true, the inbox queried only edufeed relays, so nos.lol was never asked.

Fix (two layers):

  1. getNotificationRelays() now always unions the fallback relays via new getNotificationFallbackRelays() — intentionally ungated (decision by @steffen: notifications are p-tagged personal signals, mirroring the WoT social-content exemption).
  2. initializeInbox() additionally spawns a supplemental loader on the user's own NIP-65 read relays once they resolve, for outbox-compliant clients.

Simulating the fixed REQ found the Sync reaction plus two older + reactions (74h, 124h) that were also invisible.

Still open (cosmetic): reaction items render as "hat auf reagiert" with an empty gap because the inbox page never passes contentTitle to InboxItem — left for a follow-up.

Fixed in d6139781, merged to dev. **What was verified against live relays before shipping:** - The missing reaction ("Sync", `+` by `c8383d81…`) exists only on `wss://nos.lol` and `wss://relay.primal.net` — the reactor's client published to its own write relays, not to the user's NIP-65 read relays (`haven.laoc.xyz`, `theforest.nostr1.com`, `relay.mostr.pub`). - With `GATED_MODE_DEFAULT=true`, the inbox queried only edufeed relays, so `nos.lol` was never asked. **Fix (two layers):** 1. `getNotificationRelays()` now always unions the fallback relays via new `getNotificationFallbackRelays()` — intentionally ungated (decision by @steffen: notifications are p-tagged personal signals, mirroring the WoT social-content exemption). 2. `initializeInbox()` additionally spawns a supplemental loader on the user's own NIP-65 read relays once they resolve, for outbox-compliant clients. Simulating the fixed REQ found the Sync reaction plus two older `+` reactions (74h, 124h) that were also invisible. **Still open (cosmetic):** reaction items render as "hat auf reagiert" with an empty gap because the inbox page never passes `contentTitle` to `InboxItem` — left for a follow-up.
laoc closed this issue 2026-07-14 13:37:15 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
edufeed/edufeed-app#43
No description provided.