From 719dd2c4a897f6a379d1ca128a2940ec8ef4374a Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Mon, 7 Sep 2026 22:56:20 +0700 Subject: [PATCH] Route event notifications to the event, not the actor (1.2.35) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audited the RSVP / event-update notification flow end to end. The data layer was already solid and secure — RSVPs p-tag the host, updates p-tag every RSVPer, and useActivityNotifications guards event-update spoofing by requiring the author to be the event's host (from the `a` coordinate) or an admin. The gap was on the rendering side. Fixes: - ActivityNotificationCard hardwired every notification to link to the ACTOR's profile. For an RSVP ("Alice is going to your event") the host wants the event + attendee list, and for an event update ("moved to Sunday") the attendee wants to READ the update — not visit a profile. Both events carry the event's `a` coordinate (31923:pubkey:identifier), so these two types now route to /event/:pubkey/:identifier. Everything else still lands on the actor's profile. d-tags containing ':' are reconstructed correctly. - useRsvp only invalidated the single-event RSVP query, so the grid / calendar "going" counts lagged behind by up to the 30s staleTime after RSVPing. Now also invalidates the batch-event-rsvps query so counts update promptly. - Social-tab empty-state copy omitted RSVPs and host updates even though they land there; made it honest. Co-authored-by: shakespeare.diy --- package.json | 2 +- src/components/ActivityNotificationCard.tsx | 25 ++++++++++++++++++--- src/components/NotificationBell.tsx | 2 +- src/hooks/useEventRsvps.ts | 4 ++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ee60e76..592b2fd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.2.34", + "version": "1.2.35", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/components/ActivityNotificationCard.tsx b/src/components/ActivityNotificationCard.tsx index be5bdea..8816af9 100644 --- a/src/components/ActivityNotificationCard.tsx +++ b/src/components/ActivityNotificationCard.tsx @@ -58,6 +58,23 @@ export function ActivityNotificationCard({ addSuffix: true, }); + // Type-aware destination. For event RSVPs (a host is notified) and event + // updates (an attendee is notified), the useful place to land is the EVENT — + // not the actor's profile. Both carry the event's addressable coordinate in + // an `a` tag ("31923:pubkey:identifier"), which maps to /event/:pubkey/:id. + const eventCoordTag = + notification.type === 'rsvp' || notification.type === 'event-update' + ? notification.event.tags.find(([t]) => t === 'a')?.[1] + : undefined; + + const eventPath = (() => { + if (!eventCoordTag) return null; + const [kind, pubkey, ...idParts] = eventCoordTag.split(':'); + const identifier = idParts.join(':'); // d-tags may contain ':' + if (kind !== '31923' || !pubkey || !identifier) return null; + return `/event/${pubkey}/${identifier}`; + })(); + const snippet = (notification.type === 'reply' || notification.type === 'mention' || @@ -110,11 +127,13 @@ export function ActivityNotificationCard({ ); - // Link to the actor's profile (the always-valid in-app destination). - if (!npub) return body; + // Prefer landing on the event for RSVP / event-update notifications; fall back + // to the actor's profile for everything else (the always-valid destination). + const destination = eventPath ?? (npub ? `/${npub}` : null); + if (!destination) return body; return ( - + {body} ); diff --git a/src/components/NotificationBell.tsx b/src/components/NotificationBell.tsx index 079c248..45bf115 100644 --- a/src/components/NotificationBell.tsx +++ b/src/components/NotificationBell.tsx @@ -413,7 +413,7 @@ export function NotificationBell({ className }: { className?: string }) { )} {!activityLoading && activity.length === 0 && ( - + )}
{activity.map((n) => ( diff --git a/src/hooks/useEventRsvps.ts b/src/hooks/useEventRsvps.ts index 2f2afd0..eb4c583 100644 --- a/src/hooks/useEventRsvps.ts +++ b/src/hooks/useEventRsvps.ts @@ -199,6 +199,10 @@ export function useRsvp() { onSuccess: (_data, variables) => { const coord = eventCoord(variables.event); queryClient.invalidateQueries({ queryKey: ['event-rsvps', coord] }); + // Refresh the grid/calendar "going" counts too (batch query keyed by the + // full coordinate set), so numbers update right after RSVPing rather than + // waiting out the 30s staleTime. + queryClient.invalidateQueries({ queryKey: ['batch-event-rsvps'] }); }, });