From b7e932d514841d35974c294999605fb6529adf6a Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Sun, 16 Aug 2026 17:44:52 +0700 Subject: [PATCH] Fix crew detail page 404 with robust d-tag fallback lookup useCrew previously relied on a single combined filter (kind + #d + authors, limit 1). Some relays don't reliably match combined author + d-tag filters, causing the crew detail page to render NotFound even when the crew exists. Now, if the precise addressable lookup returns nothing and the author pubkey is known, we fall back to fetching that author's crews (kind + authors) and matching the identifier client-side. This survives relays with quirky tag indexing while still preferring the exact match when available. Co-authored-by: shakespeare.diy --- src/hooks/useCrews.ts | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/hooks/useCrews.ts b/src/hooks/useCrews.ts index 324fd27..295e478 100644 --- a/src/hooks/useCrews.ts +++ b/src/hooks/useCrews.ts @@ -90,6 +90,14 @@ export function useCrew(identifier: string, pubkey?: string) { queryFn: async () => { const signal = AbortSignal.timeout(10000); + const pickMostRecent = (events: { created_at: number }[]) => + events.length + ? events.reduce((latest, current) => + current.created_at > latest.created_at ? current : latest + ) + : undefined; + + // 1) Precise addressable lookup: kind + d-tag (+ author when known). const filter: { kinds: number[]; '#d': string[]; @@ -102,14 +110,25 @@ export function useCrew(identifier: string, pubkey?: string) { }; if (pubkey) filter.authors = [pubkey]; - const events = await querySkateContent([filter], { signal }); - if (events.length === 0) return null; + let events = await querySkateContent([filter], { signal }); - const mostRecent = events.reduce((latest, current) => - current.created_at > latest.created_at ? current : latest - ); + // 2) Fallback: some relays don't index the `d` tag reliably (or choke on + // combined author + tag filters). If nothing came back and we know the + // author, fetch that author's crews and match the identifier locally. + if (events.length === 0 && pubkey) { + const byAuthor = await querySkateContent( + [{ kinds: [SKATE_CREW_KIND], authors: [pubkey], limit: 200 }], + { signal } + ); + events = byAuthor.filter((e) => + e.tags.some(([name, value]) => name === 'd' && value === identifier) + ); + } - return parseSkateCrew(mostRecent); + const mostRecent = pickMostRecent(events); + if (!mostRecent) return null; + + return parseSkateCrew(mostRecent as typeof events[number]); }, enabled: !!identifier, staleTime: 5 * 60 * 1000,