From 8ade942c32b70e8dd5f267d374207b95cfa3afa5 Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 17 Jun 2026 19:16:19 +0200 Subject: [PATCH] fix(events): keep event detail's ticket counts live (subscribe even when cached) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useEventDetail.load() early-returned when the event was already in the store, so arriving from the feed (cached) set up no live subscription. NIP-52 calendar events are replaceable and the events extension republishes them when a ticket sells (updating tickets_sold/available), but with no subscription the detail page never received the update — counts went stale until a manual reload. Always open the dTag-scoped subscription (only the one-shot query + loading state are skipped on a cache hit), and unsubscribe a prior sub before re-subscribing so reload() can't leak one. The reactive `event` computed then reflects republished counts without a reload. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../events/composables/useEventDetail.ts | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/modules/events/composables/useEventDetail.ts b/src/modules/events/composables/useEventDetail.ts index 56d104c..738a760 100644 --- a/src/modules/events/composables/useEventDetail.ts +++ b/src/modules/events/composables/useEventDetail.ts @@ -19,36 +19,46 @@ export function useEventDetail(eventId: string) { ) async function load() { - // Already in cache - if (event.value) return - const nostrService = tryInjectService(SERVICE_TOKENS.EVENTS_NOSTR_SERVICE) if (!nostrService) { error.value = 'Events service not available' return } + // Scope both the subscription and the one-shot query to this + // event'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 event is reachable. + const detailFilters = { dTags: [eventId] } + + // Subscribe for LIVE updates regardless of cache state. NIP-52 + // calendar events are replaceable, so when the events extension + // republishes after a ticket sells (updating tickets_sold / + // tickets_available — see events services.py), the new version + // arrives here and the reactive `event` (and its ticket counts) + // updates without a reload. Subscribing only on a cache miss meant + // arriving from the feed (event already cached) left the detail + // page with no live subscription, so counts went stale until reload. + if (unsubscribe) unsubscribe() // avoid leaking a sub if load() re-runs + unsubscribe = nostrService.subscribeToCalendarEvents( + (incoming) => { + store.upsertEvent(incoming) + if (incoming.id === eventId) { + isLoading.value = false + } + }, + detailFilters + ) + + // Already cached — the subscription above keeps it fresh; skip the + // one-shot query + loading state. + if (event.value) return + try { isLoading.value = true error.value = null - // Scope both the subscription and the one-shot query to this - // event'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 event is reachable. - const detailFilters = { dTags: [eventId] } - - unsubscribe = nostrService.subscribeToCalendarEvents( - (incoming) => { - store.upsertEvent(incoming) - if (incoming.id === eventId) { - isLoading.value = false - } - }, - detailFilters - ) - const results = await nostrService.queryCalendarEvents(detailFilters) store.upsertEvents(results) -- 2.53.0