diff --git a/src/modules/events/components/BookmarkButton.vue b/src/modules/events/components/BookmarkButton.vue
index a99d9c2..fc01311 100644
--- a/src/modules/events/components/BookmarkButton.vue
+++ b/src/modules/events/components/BookmarkButton.vue
@@ -1,12 +1,11 @@
diff --git a/src/modules/events/composables/useBookmarks.ts b/src/modules/events/composables/useBookmarks.ts
index 57281a9..8af2f36 100644
--- a/src/modules/events/composables/useBookmarks.ts
+++ b/src/modules/events/composables/useBookmarks.ts
@@ -88,20 +88,9 @@ export function useBookmarks() {
/**
* Toggle bookmark for an event. Publishes updated NIP-51 bookmark list.
- *
- * Updates local state OPTIMISTICALLY so the UI (heart fill) responds
- * instantly, then signs + publishes in the background. Signing routes
- * through the remote LNbits signer and publishing hits relays, so
- * awaiting both before flipping state made the heart lag ~1s. On
- * failure the optimistic change is rolled back. Resolves to whether
- * the change was persisted.
*/
- async function toggleBookmark(
- eventKind: number,
- pubkey: string,
- dTag: string,
- ): Promise {
- if (!isAuthenticated.value || !currentUser.value?.pubkey) return false
+ async function toggleBookmark(eventKind: number, pubkey: string, dTag: string) {
+ if (!isAuthenticated.value || !currentUser.value?.pubkey) return
const coord = `${eventKind}:${pubkey}:${dTag}`
const newCoords = new Set(state.value.bookmarkedCoords)
@@ -112,17 +101,6 @@ export function useBookmarks() {
newCoords.add(coord)
}
- // Optimistic flip — preserve the prior state so we can roll back if
- // signing or publishing fails. Keep lastEventId/lastCreatedAt until
- // the real event is confirmed.
- const prevState = state.value
- state.value = { bookmarkedCoords: newCoords, lastEventId: prevState.lastEventId }
- ;(state.value as any).lastCreatedAt = (prevState as any).lastCreatedAt
-
- function rollback() {
- state.value = prevState
- }
-
// Build and publish updated bookmark list
const tags: string[][] = Array.from(newCoords).map(c => ['a', c])
@@ -138,25 +116,19 @@ export function useBookmarks() {
signedEvent = await signEventViaLnbits(template)
} catch (err) {
console.error('[useBookmarks] signEventViaLnbits failed:', err)
- rollback()
- return false
+ return
}
const relayHub = tryInjectService(SERVICE_TOKENS.RELAY_HUB)
- if (!relayHub) {
- rollback()
- return false
- }
+ if (!relayHub) return
const result = await relayHub.publishEvent(signedEvent)
if (result.success > 0) {
- state.value = { bookmarkedCoords: newCoords, lastEventId: signedEvent.id }
- ;(state.value as any).lastCreatedAt = template.created_at
- return true
+ state.value = {
+ bookmarkedCoords: newCoords,
+ lastEventId: signedEvent.id,
+ }
}
-
- rollback()
- return false
}
onMounted(() => {
diff --git a/src/modules/events/composables/useEventLikes.ts b/src/modules/events/composables/useEventLikes.ts
deleted file mode 100644
index 9bca9fd..0000000
--- a/src/modules/events/composables/useEventLikes.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { reactive } from 'vue'
-import type { Event as NostrEvent } from 'nostr-tools'
-import { tryInjectService, SERVICE_TOKENS } from '@/core/di-container'
-
-/**
- * Live "like" counts for events. A like == the event appearing in a
- * user's NIP-51 bookmark list (kind 10003) — the same action the heart
- * performs (and what the Favorites page reads).
- *
- * One batched subscription covers every event coordinate that a mounted
- * heart has registered, filtered by `#a`. It stays open after EOSE, so
- * when anyone publishes/updates a bookmark list referencing a tracked
- * event the relay pushes it live and the count increments for everyone
- * in real time (Alice likes → Bob's count ticks up).
- *
- * Caveats (inherent to counting replaceable bookmark lists via `#a`):
- * - An un-like by ANOTHER user only reflects on next load: their new
- * list no longer contains the coord, so it no longer matches the
- * filter and we never receive the update. Counts are correct on a
- * fresh load (the un-liker is simply absent from the results).
- * - The current user's own like/un-like updates instantly via setSelf(),
- * driven by the optimistic heart state.
- */
-
-const BOOKMARK_KIND = 10003
-
-// coord ("kind:pubkey:dTag") -> set of pubkeys who bookmarked it.
-const authorsByCoord = new Map>() // plain map, for dedup
-const counts = reactive(new Map()) // reactive mirror for the UI
-const tracked = new Set()
-
-let unsubscribe: (() => void) | null = null
-let resubTimer: ReturnType | null = null
-
-function setCount(coord: string, pubkey: string, present: boolean) {
- let set = authorsByCoord.get(coord)
- if (!set) {
- set = new Set()
- authorsByCoord.set(coord, set)
- }
- const had = set.has(pubkey)
- if (present && !had) {
- set.add(pubkey)
- counts.set(coord, set.size)
- } else if (!present && had) {
- set.delete(pubkey)
- counts.set(coord, set.size)
- }
-}
-
-function ingest(event: NostrEvent) {
- // A bookmark list references many events via 'a' tags; credit the
- // author to every coord we're tracking.
- for (const tag of event.tags) {
- if (tag[0] === 'a' && tracked.has(tag[1])) setCount(tag[1], event.pubkey, true)
- }
-}
-
-function resubscribe() {
- const relayHub = tryInjectService(SERVICE_TOKENS.RELAY_HUB)
- if (!relayHub) {
- scheduleResubscribe() // relay hub not registered yet — retry shortly
- return
- }
- if (unsubscribe) {
- unsubscribe()
- unsubscribe = null
- }
- const coords = [...tracked]
- if (coords.length === 0) return
- unsubscribe = relayHub.subscribe({
- id: 'event-likes-aggregate',
- filters: [{ kinds: [BOOKMARK_KIND], '#a': coords }],
- onEvent: (event: NostrEvent) => ingest(event),
- })
-}
-
-function scheduleResubscribe() {
- if (resubTimer) clearTimeout(resubTimer)
- // Debounced so a burst of mounting hearts results in one (re)subscribe.
- resubTimer = setTimeout(resubscribe, 250)
-}
-
-export function useEventLikes() {
- /** Register an event coordinate so its like count is fetched + kept live. */
- function track(coord: string) {
- if (!coord || tracked.has(coord)) return
- tracked.add(coord)
- scheduleResubscribe()
- }
-
- /** Reactive like count for a coordinate (0 when none/unknown). */
- function likeCount(coord: string): number {
- return counts.get(coord) ?? 0
- }
-
- /**
- * Reflect the current user's own like state immediately (optimistic),
- * so their count matches the instant heart fill and their un-like
- * decrements right away.
- */
- function setSelf(coord: string, pubkey: string, liked: boolean) {
- if (!coord || !pubkey) return
- setCount(coord, pubkey, liked)
- }
-
- return { track, likeCount, setSelf }
-}
diff --git a/src/modules/events/composables/useTicketPurchase.ts b/src/modules/events/composables/useTicketPurchase.ts
index 5c3df37..4d4dc45 100644
--- a/src/modules/events/composables/useTicketPurchase.ts
+++ b/src/modules/events/composables/useTicketPurchase.ts
@@ -5,19 +5,12 @@ import { useAsyncOperation } from '@/core/composables/useAsyncOperation'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { PaymentService } from '@/core/services/PaymentService'
import type { TicketApiService } from '../services/TicketApiService'
-import { useOwnedTickets } from './useOwnedTickets'
export function useTicketPurchase() {
const { isAuthenticated, currentUser } = useAuth()
const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as PaymentService
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
- // Refresh the shared owned-tickets singleton after a purchase so the
- // feed/calendar "My tickets" filter and EventCard owned badges update
- // without a reload — purchase is exactly the "consumer that mutates
- // the ticket set" useOwnedTickets's docs anticipate.
- const { refresh: refreshOwnedTickets } = useOwnedTickets()
-
// Async operations
const purchaseOperation = useAsyncOperation()
@@ -185,12 +178,6 @@ export function useTicketPurchase() {
clearInterval(checkInterval)
}
- // Ticket row(s) now exist — refresh the shared owned-tickets
- // state so the feed/calendar My-tickets filter and owned
- // badges reflect the purchase immediately (no reload). Runs in
- // parallel with QR generation below.
- void refreshOwnedTickets()
-
// Multi-ticket purchases come back with `ticketIds` (N rows
// sharing one invoice). Single-ticket purchases include
// `ticketId` only. Render one QR per row so each attendee