From 530f5b4d0d5cbc6a2bd5185d3d03ffb42e2fb2ff Mon Sep 17 00:00:00 2001 From: Chad Curtis Date: Tue, 3 Mar 2026 00:36:55 -0600 Subject: [PATCH] fix vine reactions and kind 16 repost for addressable events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - VineHeartButton: add optimistic cache updates and query invalidation on reaction so the heart fills and count increments immediately without a page refresh - RepostMenu: include NIP-18 'a' tag (kind:pubkey:d-tag) when reposting addressable events (30000–39999) so kind 16 events are properly linked back to the original vine --- src/components/RepostMenu.tsx | 5 +++++ src/pages/VinesFeedPage.tsx | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/components/RepostMenu.tsx b/src/components/RepostMenu.tsx index 790baa35..f3a80bd3 100644 --- a/src/components/RepostMenu.tsx +++ b/src/components/RepostMenu.tsx @@ -58,6 +58,11 @@ export function RepostMenu({ event, children }: RepostMenuProps) { // Kind 16 generic reposts require a 'k' tag with the original event's kind if (repostKind === 16) { tags.push(['k', String(event.kind)]); + // Addressable events (30000–39999) should include an 'a' tag per NIP-18 + if (event.kind >= 30000 && event.kind < 40000) { + const dTag = event.tags.find(([name]) => name === 'd')?.[1] ?? ''; + tags.push(['a', `${event.kind}:${event.pubkey}:${dTag}`]); + } } publishEvent( diff --git a/src/pages/VinesFeedPage.tsx b/src/pages/VinesFeedPage.tsx index 8e14af6f..9ccae2de 100644 --- a/src/pages/VinesFeedPage.tsx +++ b/src/pages/VinesFeedPage.tsx @@ -5,6 +5,7 @@ import { useCallback, useMemo, } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { useSeoMeta } from '@unhead/react'; import { @@ -24,7 +25,7 @@ import { useQuery } from '@tanstack/react-query'; import { useStreamKind } from '@/hooks/useStreamKind'; import { useAuthor } from '@/hooks/useAuthor'; import { useCurrentUser } from '@/hooks/useCurrentUser'; -import { useEventStats } from '@/hooks/useTrending'; +import { useEventStats, type EventStats } from '@/hooks/useTrending'; import { useAppContext } from '@/hooks/useAppContext'; import { useBlossomFallback } from '@/hooks/useBlossomFallback'; import { useLayoutOptions } from '@/contexts/LayoutContext'; @@ -128,12 +129,42 @@ export function VineHeartButton({ event, label, noBackground }: { event: NostrEv const { user } = useCurrentUser(); const userReaction = useUserReaction(event.id); const { mutate: publishEvent } = useNostrPublish(); + const queryClient = useQueryClient(); const hasReacted = !!userReaction; const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); if (!user || hasReacted) return; - publishEvent({ kind: 7, content: '+', tags: [['e', event.id], ['p', event.pubkey], ['k', String(event.kind)]] }); + + // Optimistically update stats cache + const prevStats = queryClient.getQueryData(['event-stats', event.id]); + if (prevStats) { + queryClient.setQueryData(['event-stats', event.id], { + ...prevStats, + reactions: prevStats.reactions + 1, + }); + } + // Optimistically mark user as having reacted + queryClient.setQueryData(['user-reaction', event.id], { content: '👍' }); + + publishEvent( + { kind: 7, content: '+', tags: [['e', event.id], ['p', event.pubkey], ['k', String(event.kind)]] }, + { + onSuccess: () => { + setTimeout(() => { + queryClient.invalidateQueries({ queryKey: ['event-stats', event.id] }); + queryClient.invalidateQueries({ queryKey: ['event-interactions', event.id] }); + }, 3000); + }, + onError: () => { + // Revert optimistic updates + if (prevStats) { + queryClient.setQueryData(['event-stats', event.id], prevStats); + } + queryClient.removeQueries({ queryKey: ['user-reaction', event.id] }); + }, + }, + ); }; return (