From cb6e1351fb6299de46b5bf8b622d72ae0dd4dd5c Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 30 May 2026 08:03:55 +0200 Subject: [PATCH] fix(activities): scope detail-page query by NIP-52 d-tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `useActivityDetail.load()` previously asked every relay for every kind-31922/31923 event and raced a 5s timeout to find the one matching the route param. On a cold refresh of the detail page, the race was often lost — the store starts empty (no feed subscription to populate it), the relay sprays the whole calendar, and the matching event may arrive after the timeout, leaving the user with "Activity not found" on a valid URL. Add a `dTags` field to `CalendarEventFilters` and emit it as the nostr `#d` tag filter. Detail-page subscribe + query both scope to the single activity, so the relay resolves a parameterized-replaceable lookup in milliseconds instead of streaming the whole calendar. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../activities/composables/useActivityDetail.ts | 14 ++++++++++---- .../activities/services/ActivitiesNostrService.ts | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/modules/activities/composables/useActivityDetail.ts b/src/modules/activities/composables/useActivityDetail.ts index e29a272..9fc3ce4 100644 --- a/src/modules/activities/composables/useActivityDetail.ts +++ b/src/modules/activities/composables/useActivityDetail.ts @@ -32,18 +32,24 @@ export function useActivityDetail(activityId: string) { isLoading.value = true error.value = null - // Subscribe and wait for this specific event + // Scope both the subscription and the one-shot query to this + // activity's d-tag. Without this scope, the query asks every + // relay for every kind-31922/31923 event and races a 5s timeout + // to find ours — on a cold page refresh that race is often lost + // even when the activity is reachable. + const detailFilters = { dTags: [activityId] } + unsubscribe = nostrService.subscribeToCalendarEvents( (incoming) => { store.upsertActivity(incoming) if (incoming.id === activityId) { isLoading.value = false } - } + }, + detailFilters ) - // Also do a one-shot query - const results = await nostrService.queryCalendarEvents() + const results = await nostrService.queryCalendarEvents(detailFilters) store.upsertActivities(results) // If we still don't have it after query, stop loading diff --git a/src/modules/activities/services/ActivitiesNostrService.ts b/src/modules/activities/services/ActivitiesNostrService.ts index f098728..da5ed05 100644 --- a/src/modules/activities/services/ActivitiesNostrService.ts +++ b/src/modules/activities/services/ActivitiesNostrService.ts @@ -25,6 +25,8 @@ export interface CalendarEventFilters { hashtags?: string[] /** Filter by geohash prefix (NIP-52 'g' tag) */ geohash?: string + /** Filter by NIP-52 'd' tag — scopes the query to specific parameterized-replaceable events */ + dTags?: string[] } /** @@ -168,6 +170,7 @@ export class ActivitiesNostrService extends BaseService { if (filters?.authors?.length) filter.authors = filters.authors if (filters?.hashtags?.length) filter['#t'] = filters.hashtags if (filters?.geohash) filter['#g'] = [filters.geohash] + if (filters?.dTags?.length) filter['#d'] = filters.dTags return [filter] }