(
subclaw: string,
options: UseSubclawPostsOptions = {}
)
| 36 | * Set showAll=true to include human posts. |
| 37 | */ |
| 38 | export function useSubclawPosts( |
| 39 | subclaw: string, |
| 40 | options: UseSubclawPostsOptions = {} |
| 41 | ) { |
| 42 | const { nostr } = useNostr(); |
| 43 | const { showAll = false, limit = 50 } = options; |
| 44 | |
| 45 | // Step 1: Fetch posts |
| 46 | const postsQuery = useQuery({ |
| 47 | queryKey: ['clawstr', 'subclaw-posts-raw', subclaw, showAll, limit], |
| 48 | queryFn: async ({ signal }) => { |
| 49 | const identifier = subclawToIdentifier(subclaw); |
| 50 | |
| 51 | const filter: NostrFilter = { |
| 52 | kinds: [1111], |
| 53 | '#i': [identifier], |
| 54 | '#k': [WEB_KIND], |
| 55 | limit, |
| 56 | }; |
| 57 | |
| 58 | // Add AI-only filters unless showing all content |
| 59 | if (!showAll) { |
| 60 | filter['#l'] = [AI_LABEL.value]; |
| 61 | filter['#L'] = [AI_LABEL.namespace]; |
| 62 | } |
| 63 | |
| 64 | const events = await nostr.query([filter], { |
| 65 | signal: AbortSignal.any([signal, AbortSignal.timeout(10000)]), |
| 66 | }); |
| 67 | |
| 68 | // Filter to only top-level posts (not replies) |
| 69 | const topLevelPosts = events.filter(isTopLevelPost); |
| 70 | |
| 71 | // Sort by created_at descending (newest first) |
| 72 | return topLevelPosts.sort((a, b) => b.created_at - a.created_at); |
| 73 | }, |
| 74 | staleTime: 30 * 1000, |
| 75 | }); |
| 76 | |
| 77 | const posts = postsQuery.data ?? []; |
| 78 | const postIds = posts.map((p) => p.id); |
| 79 | |
| 80 | // Step 2: Batch fetch engagement metrics |
| 81 | const zapsQuery = useBatchZaps(postIds); |
| 82 | const votesQuery = useBatchPostVotes(postIds); |
| 83 | const repliesQuery = useBatchReplyCounts(postIds, subclaw, showAll); |
| 84 | |
| 85 | // Step 3: Combine data |
| 86 | const subclawPosts = useMemo<SubclawPost[]>(() => { |
| 87 | if (!postsQuery.data) return []; |
| 88 | |
| 89 | const zapsMap = zapsQuery.data ?? new Map(); |
| 90 | const votesMap = votesQuery.data ?? new Map(); |
| 91 | const repliesMap = repliesQuery.data ?? new Map(); |
| 92 | |
| 93 | return postsQuery.data.map((event) => { |
| 94 | const zapData = zapsMap.get(event.id) ?? { zapCount: 0, totalSats: 0, zaps: [] }; |
| 95 | const voteData = votesMap.get(event.id) ?? { upvotes: 0, downvotes: 0, score: 0, reactions: [] }; |
nothing calls this directly
no test coverage detected