From 772e1b140481139481fb0817b3d51a83664dc2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Tue, 3 Mar 2026 21:57:49 +0100 Subject: [PATCH] refactor: extract useRepositoryRelays hook from NIP-34 renderers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes ~45 lines of identical relay resolution boilerplate duplicated across 6 renderers (Issue, Patch, PR - feed and detail views). The hook encapsulates the 3-tier fallback: repo relays → owner outbox → aggregators, and also returns the repository event needed for getValidStatusAuthors. Co-Authored-By: Claude Sonnet 4.6 --- .../nostr/kinds/IssueDetailRenderer.tsx | 54 ++----------------- src/components/nostr/kinds/IssueRenderer.tsx | 54 ++----------------- .../nostr/kinds/PatchDetailRenderer.tsx | 46 ++-------------- src/components/nostr/kinds/PatchRenderer.tsx | 54 ++----------------- .../nostr/kinds/PullRequestDetailRenderer.tsx | 46 ++-------------- .../nostr/kinds/PullRequestRenderer.tsx | 54 ++----------------- src/hooks/useRepositoryRelays.ts | 53 ++++++++++++++++++ 7 files changed, 71 insertions(+), 290 deletions(-) create mode 100644 src/hooks/useRepositoryRelays.ts diff --git a/src/components/nostr/kinds/IssueDetailRenderer.tsx b/src/components/nostr/kinds/IssueDetailRenderer.tsx index 6aed758..8041829 100644 --- a/src/components/nostr/kinds/IssueDetailRenderer.tsx +++ b/src/components/nostr/kinds/IssueDetailRenderer.tsx @@ -7,20 +7,16 @@ import { getIssueTitle, getIssueLabels, getIssueRepositoryAddress, - getRepositoryRelays, getStatusType, getValidStatusAuthors, findCurrentStatus, } from "@/lib/nip34-helpers"; -import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; -import { getOutboxes } from "applesauce-core/helpers"; import { Label } from "@/components/ui/label"; import { RepositoryLink } from "../RepositoryLink"; import { StatusIndicator } from "../StatusIndicator"; import { useTimeline } from "@/hooks/useTimeline"; -import { useNostrEvent } from "@/hooks/useNostrEvent"; import { formatTimestamp } from "@/hooks/useLocale"; -import { AGGREGATOR_RELAYS } from "@/services/loaders"; +import { useRepositoryRelays } from "@/hooks/useRepositoryRelays"; /** * Detail renderer for Kind 1621 - Issue (NIP-34) @@ -31,52 +27,8 @@ export function IssueDetailRenderer({ event }: { event: NostrEvent }) { const labels = getIssueLabels(event); const repoAddress = getIssueRepositoryAddress(event); - // Parse repository address for fetching repo event - const parsedRepo = useMemo( - () => (repoAddress ? parseReplaceableAddress(repoAddress) : null), - [repoAddress], - ); - - // Fetch repository event to get maintainers list - const repoPointer = useMemo(() => { - if (!parsedRepo) return undefined; - return { - kind: parsedRepo.kind, - pubkey: parsedRepo.pubkey, - identifier: parsedRepo.identifier, - }; - }, [parsedRepo]); - - const repositoryEvent = useNostrEvent(repoPointer); - - // Fetch repo author's relay list for fallback - const repoAuthorRelayListPointer = useMemo(() => { - if (!parsedRepo?.pubkey) return undefined; - return { kind: 10002, pubkey: parsedRepo.pubkey, identifier: "" }; - }, [parsedRepo?.pubkey]); - - const repoAuthorRelayList = useNostrEvent(repoAuthorRelayListPointer); - - // Build relay list with fallbacks: - // 1. Repository configured relays - // 2. Repo author's outbox (write) relays - // 3. AGGREGATOR_RELAYS as final fallback - const statusRelays = useMemo(() => { - // Try repository relays first - if (repositoryEvent) { - const repoRelays = getRepositoryRelays(repositoryEvent); - if (repoRelays.length > 0) return repoRelays; - } - - // Try repo author's outbox relays - if (repoAuthorRelayList) { - const authorOutbox = getOutboxes(repoAuthorRelayList); - if (authorOutbox.length > 0) return authorOutbox; - } - - // Fallback to aggregator relays - return AGGREGATOR_RELAYS; - }, [repositoryEvent, repoAuthorRelayList]); + const { relays: statusRelays, repositoryEvent } = + useRepositoryRelays(repoAddress); // Fetch status events that reference this issue // Status events use e tag with root marker to reference the issue diff --git a/src/components/nostr/kinds/IssueRenderer.tsx b/src/components/nostr/kinds/IssueRenderer.tsx index 6bbeee0..775b594 100644 --- a/src/components/nostr/kinds/IssueRenderer.tsx +++ b/src/components/nostr/kinds/IssueRenderer.tsx @@ -8,18 +8,14 @@ import { getIssueTitle, getIssueLabels, getIssueRepositoryAddress, - getRepositoryRelays, getValidStatusAuthors, findCurrentStatus, } from "@/lib/nip34-helpers"; -import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; -import { getOutboxes } from "applesauce-core/helpers"; import { Label } from "@/components/ui/label"; import { RepositoryLink } from "../RepositoryLink"; import { StatusIndicator } from "../StatusIndicator"; import { useTimeline } from "@/hooks/useTimeline"; -import { useNostrEvent } from "@/hooks/useNostrEvent"; -import { AGGREGATOR_RELAYS } from "@/services/loaders"; +import { useRepositoryRelays } from "@/hooks/useRepositoryRelays"; /** * Renderer for Kind 1621 - Issue @@ -30,52 +26,8 @@ export function IssueRenderer({ event }: BaseEventProps) { const labels = getIssueLabels(event); const repoAddress = getIssueRepositoryAddress(event); - // Parse repository address for fetching repo event - const parsedRepo = useMemo( - () => (repoAddress ? parseReplaceableAddress(repoAddress) : null), - [repoAddress], - ); - - // Fetch repository event to get maintainers list - const repoPointer = useMemo(() => { - if (!parsedRepo) return undefined; - return { - kind: parsedRepo.kind, - pubkey: parsedRepo.pubkey, - identifier: parsedRepo.identifier, - }; - }, [parsedRepo]); - - const repositoryEvent = useNostrEvent(repoPointer); - - // Fetch repo author's relay list for fallback - const repoAuthorRelayListPointer = useMemo(() => { - if (!parsedRepo?.pubkey) return undefined; - return { kind: 10002, pubkey: parsedRepo.pubkey, identifier: "" }; - }, [parsedRepo?.pubkey]); - - const repoAuthorRelayList = useNostrEvent(repoAuthorRelayListPointer); - - // Build relay list with fallbacks: - // 1. Repository configured relays - // 2. Repo author's outbox (write) relays - // 3. AGGREGATOR_RELAYS as final fallback - const statusRelays = useMemo(() => { - // Try repository relays first - if (repositoryEvent) { - const repoRelays = getRepositoryRelays(repositoryEvent); - if (repoRelays.length > 0) return repoRelays; - } - - // Try repo author's outbox relays - if (repoAuthorRelayList) { - const authorOutbox = getOutboxes(repoAuthorRelayList); - if (authorOutbox.length > 0) return authorOutbox; - } - - // Fallback to aggregator relays - return AGGREGATOR_RELAYS; - }, [repositoryEvent, repoAuthorRelayList]); + const { relays: statusRelays, repositoryEvent } = + useRepositoryRelays(repoAddress); // Fetch status events that reference this issue const statusFilter = useMemo( diff --git a/src/components/nostr/kinds/PatchDetailRenderer.tsx b/src/components/nostr/kinds/PatchDetailRenderer.tsx index de5f914..170586a 100644 --- a/src/components/nostr/kinds/PatchDetailRenderer.tsx +++ b/src/components/nostr/kinds/PatchDetailRenderer.tsx @@ -15,18 +15,14 @@ import { getPatchRepositoryAddress, isPatchRoot, isPatchRootRevision, - getRepositoryRelays, getStatusType, getValidStatusAuthors, findCurrentStatus, } from "@/lib/nip34-helpers"; -import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; -import { getOutboxes } from "applesauce-core/helpers"; import { RepositoryLink } from "../RepositoryLink"; import { StatusIndicator } from "../StatusIndicator"; import { useTimeline } from "@/hooks/useTimeline"; -import { useNostrEvent } from "@/hooks/useNostrEvent"; -import { AGGREGATOR_RELAYS } from "@/services/loaders"; +import { useRepositoryRelays } from "@/hooks/useRepositoryRelays"; /** * Detail renderer for Kind 1617 - Patch @@ -43,44 +39,8 @@ export function PatchDetailRenderer({ event }: { event: NostrEvent }) { const isRoot = isPatchRoot(event); const isRootRevision = isPatchRootRevision(event); - // Parse repository address for fetching repo event - const parsedRepo = useMemo( - () => (repoAddress ? parseReplaceableAddress(repoAddress) : null), - [repoAddress], - ); - - // Fetch repository event to get maintainers list - const repoPointer = useMemo(() => { - if (!parsedRepo) return undefined; - return { - kind: parsedRepo.kind, - pubkey: parsedRepo.pubkey, - identifier: parsedRepo.identifier, - }; - }, [parsedRepo]); - - const repositoryEvent = useNostrEvent(repoPointer); - - // Fetch repo author's relay list for fallback - const repoAuthorRelayListPointer = useMemo(() => { - if (!parsedRepo?.pubkey) return undefined; - return { kind: 10002, pubkey: parsedRepo.pubkey, identifier: "" }; - }, [parsedRepo?.pubkey]); - - const repoAuthorRelayList = useNostrEvent(repoAuthorRelayListPointer); - - // Build relay list with fallbacks - const statusRelays = useMemo(() => { - if (repositoryEvent) { - const repoRelays = getRepositoryRelays(repositoryEvent); - if (repoRelays.length > 0) return repoRelays; - } - if (repoAuthorRelayList) { - const authorOutbox = getOutboxes(repoAuthorRelayList); - if (authorOutbox.length > 0) return authorOutbox; - } - return AGGREGATOR_RELAYS; - }, [repositoryEvent, repoAuthorRelayList]); + const { relays: statusRelays, repositoryEvent } = + useRepositoryRelays(repoAddress); // Fetch status events const statusFilter = useMemo( diff --git a/src/components/nostr/kinds/PatchRenderer.tsx b/src/components/nostr/kinds/PatchRenderer.tsx index 60649a1..bf9eb01 100644 --- a/src/components/nostr/kinds/PatchRenderer.tsx +++ b/src/components/nostr/kinds/PatchRenderer.tsx @@ -8,17 +8,13 @@ import { getPatchSubject, getPatchCommitId, getPatchRepositoryAddress, - getRepositoryRelays, getValidStatusAuthors, findCurrentStatus, } from "@/lib/nip34-helpers"; -import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; -import { getOutboxes } from "applesauce-core/helpers"; import { RepositoryLink } from "../RepositoryLink"; import { StatusIndicator } from "../StatusIndicator"; import { useTimeline } from "@/hooks/useTimeline"; -import { useNostrEvent } from "@/hooks/useNostrEvent"; -import { AGGREGATOR_RELAYS } from "@/services/loaders"; +import { useRepositoryRelays } from "@/hooks/useRepositoryRelays"; /** * Renderer for Kind 1617 - Patch @@ -32,52 +28,8 @@ export function PatchRenderer({ event }: BaseEventProps) { // Shorten commit ID for display const shortCommitId = commitId ? commitId.slice(0, 7) : undefined; - // Parse repository address for fetching repo event - const parsedRepo = useMemo( - () => (repoAddress ? parseReplaceableAddress(repoAddress) : null), - [repoAddress], - ); - - // Fetch repository event to get maintainers list - const repoPointer = useMemo(() => { - if (!parsedRepo) return undefined; - return { - kind: parsedRepo.kind, - pubkey: parsedRepo.pubkey, - identifier: parsedRepo.identifier, - }; - }, [parsedRepo]); - - const repositoryEvent = useNostrEvent(repoPointer); - - // Fetch repo author's relay list for fallback - const repoAuthorRelayListPointer = useMemo(() => { - if (!parsedRepo?.pubkey) return undefined; - return { kind: 10002, pubkey: parsedRepo.pubkey, identifier: "" }; - }, [parsedRepo?.pubkey]); - - const repoAuthorRelayList = useNostrEvent(repoAuthorRelayListPointer); - - // Build relay list with fallbacks: - // 1. Repository configured relays - // 2. Repo author's outbox (write) relays - // 3. AGGREGATOR_RELAYS as final fallback - const statusRelays = useMemo(() => { - // Try repository relays first - if (repositoryEvent) { - const repoRelays = getRepositoryRelays(repositoryEvent); - if (repoRelays.length > 0) return repoRelays; - } - - // Try repo author's outbox relays - if (repoAuthorRelayList) { - const authorOutbox = getOutboxes(repoAuthorRelayList); - if (authorOutbox.length > 0) return authorOutbox; - } - - // Fallback to aggregator relays - return AGGREGATOR_RELAYS; - }, [repositoryEvent, repoAuthorRelayList]); + const { relays: statusRelays, repositoryEvent } = + useRepositoryRelays(repoAddress); // Fetch status events that reference this patch const statusFilter = useMemo( diff --git a/src/components/nostr/kinds/PullRequestDetailRenderer.tsx b/src/components/nostr/kinds/PullRequestDetailRenderer.tsx index c523ea4..8d618b4 100644 --- a/src/components/nostr/kinds/PullRequestDetailRenderer.tsx +++ b/src/components/nostr/kinds/PullRequestDetailRenderer.tsx @@ -13,19 +13,15 @@ import { getPullRequestCloneUrls, getPullRequestMergeBase, getPullRequestRepositoryAddress, - getRepositoryRelays, getStatusType, getValidStatusAuthors, findCurrentStatus, } from "@/lib/nip34-helpers"; -import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; -import { getOutboxes } from "applesauce-core/helpers"; import { Label } from "@/components/ui/label"; import { RepositoryLink } from "../RepositoryLink"; import { StatusIndicator } from "../StatusIndicator"; import { useTimeline } from "@/hooks/useTimeline"; -import { useNostrEvent } from "@/hooks/useNostrEvent"; -import { AGGREGATOR_RELAYS } from "@/services/loaders"; +import { useRepositoryRelays } from "@/hooks/useRepositoryRelays"; /** * Detail renderer for Kind 1618 - Pull Request @@ -42,44 +38,8 @@ export function PullRequestDetailRenderer({ event }: { event: NostrEvent }) { const mergeBase = getPullRequestMergeBase(event); const repoAddress = getPullRequestRepositoryAddress(event); - // Parse repository address for fetching repo event - const parsedRepo = useMemo( - () => (repoAddress ? parseReplaceableAddress(repoAddress) : null), - [repoAddress], - ); - - // Fetch repository event to get maintainers list - const repoPointer = useMemo(() => { - if (!parsedRepo) return undefined; - return { - kind: parsedRepo.kind, - pubkey: parsedRepo.pubkey, - identifier: parsedRepo.identifier, - }; - }, [parsedRepo]); - - const repositoryEvent = useNostrEvent(repoPointer); - - // Fetch repo author's relay list for fallback - const repoAuthorRelayListPointer = useMemo(() => { - if (!parsedRepo?.pubkey) return undefined; - return { kind: 10002, pubkey: parsedRepo.pubkey, identifier: "" }; - }, [parsedRepo?.pubkey]); - - const repoAuthorRelayList = useNostrEvent(repoAuthorRelayListPointer); - - // Build relay list with fallbacks - const statusRelays = useMemo(() => { - if (repositoryEvent) { - const repoRelays = getRepositoryRelays(repositoryEvent); - if (repoRelays.length > 0) return repoRelays; - } - if (repoAuthorRelayList) { - const authorOutbox = getOutboxes(repoAuthorRelayList); - if (authorOutbox.length > 0) return authorOutbox; - } - return AGGREGATOR_RELAYS; - }, [repositoryEvent, repoAuthorRelayList]); + const { relays: statusRelays, repositoryEvent } = + useRepositoryRelays(repoAddress); // Fetch status events const statusFilter = useMemo( diff --git a/src/components/nostr/kinds/PullRequestRenderer.tsx b/src/components/nostr/kinds/PullRequestRenderer.tsx index 6466a7d..f3a5ab3 100644 --- a/src/components/nostr/kinds/PullRequestRenderer.tsx +++ b/src/components/nostr/kinds/PullRequestRenderer.tsx @@ -10,18 +10,14 @@ import { getPullRequestLabels, getPullRequestBranchName, getPullRequestRepositoryAddress, - getRepositoryRelays, getValidStatusAuthors, findCurrentStatus, } from "@/lib/nip34-helpers"; -import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; -import { getOutboxes } from "applesauce-core/helpers"; import { Label } from "@/components/ui/label"; import { RepositoryLink } from "../RepositoryLink"; import { StatusIndicator } from "../StatusIndicator"; import { useTimeline } from "@/hooks/useTimeline"; -import { useNostrEvent } from "@/hooks/useNostrEvent"; -import { AGGREGATOR_RELAYS } from "@/services/loaders"; +import { useRepositoryRelays } from "@/hooks/useRepositoryRelays"; /** * Renderer for Kind 1618 - Pull Request @@ -33,52 +29,8 @@ export function PullRequestRenderer({ event }: BaseEventProps) { const branchName = getPullRequestBranchName(event); const repoAddress = getPullRequestRepositoryAddress(event); - // Parse repository address for fetching repo event - const parsedRepo = useMemo( - () => (repoAddress ? parseReplaceableAddress(repoAddress) : null), - [repoAddress], - ); - - // Fetch repository event to get maintainers list - const repoPointer = useMemo(() => { - if (!parsedRepo) return undefined; - return { - kind: parsedRepo.kind, - pubkey: parsedRepo.pubkey, - identifier: parsedRepo.identifier, - }; - }, [parsedRepo]); - - const repositoryEvent = useNostrEvent(repoPointer); - - // Fetch repo author's relay list for fallback - const repoAuthorRelayListPointer = useMemo(() => { - if (!parsedRepo?.pubkey) return undefined; - return { kind: 10002, pubkey: parsedRepo.pubkey, identifier: "" }; - }, [parsedRepo?.pubkey]); - - const repoAuthorRelayList = useNostrEvent(repoAuthorRelayListPointer); - - // Build relay list with fallbacks: - // 1. Repository configured relays - // 2. Repo author's outbox (write) relays - // 3. AGGREGATOR_RELAYS as final fallback - const statusRelays = useMemo(() => { - // Try repository relays first - if (repositoryEvent) { - const repoRelays = getRepositoryRelays(repositoryEvent); - if (repoRelays.length > 0) return repoRelays; - } - - // Try repo author's outbox relays - if (repoAuthorRelayList) { - const authorOutbox = getOutboxes(repoAuthorRelayList); - if (authorOutbox.length > 0) return authorOutbox; - } - - // Fallback to aggregator relays - return AGGREGATOR_RELAYS; - }, [repositoryEvent, repoAuthorRelayList]); + const { relays: statusRelays, repositoryEvent } = + useRepositoryRelays(repoAddress); // Fetch status events that reference this PR const statusFilter = useMemo( diff --git a/src/hooks/useRepositoryRelays.ts b/src/hooks/useRepositoryRelays.ts new file mode 100644 index 0000000..34a348c --- /dev/null +++ b/src/hooks/useRepositoryRelays.ts @@ -0,0 +1,53 @@ +import { useMemo } from "react"; +import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; +import { getOutboxes } from "applesauce-core/helpers"; +import { getRepositoryRelays } from "@/lib/nip34-helpers"; +import { useNostrEvent } from "@/hooks/useNostrEvent"; +import { AGGREGATOR_RELAYS } from "@/services/loaders"; +import type { NostrEvent } from "@/types/nostr"; + +/** + * Resolves the relay list for a NIP-34 git repository address. + * + * Fallback chain: + * 1. Relays from the repository event's `relays` tag (kind 30617) + * 2. Repository owner's outbox relays (kind 10002) + * 3. Well-known aggregator relays + * + * Also returns the repository event, needed by callers for getValidStatusAuthors. + */ +export function useRepositoryRelays(repoAddress: string | undefined): { + relays: string[]; + repositoryEvent: NostrEvent | undefined; +} { + const repoPointer = useMemo( + () => (repoAddress ? parseReplaceableAddress(repoAddress) : undefined), + [repoAddress], + ); + + const repositoryEvent = useNostrEvent(repoPointer); + + const authorRelayListPointer = useMemo( + () => + repoPointer + ? { kind: 10002, pubkey: repoPointer.pubkey, identifier: "" } + : undefined, + [repoPointer], + ); + + const authorRelayList = useNostrEvent(authorRelayListPointer); + + const relays = useMemo(() => { + if (repositoryEvent) { + const repoRelays = getRepositoryRelays(repositoryEvent); + if (repoRelays.length > 0) return repoRelays; + } + if (authorRelayList) { + const outbox = getOutboxes(authorRelayList); + if (outbox.length > 0) return outbox; + } + return AGGREGATOR_RELAYS; + }, [repositoryEvent, authorRelayList]); + + return { relays, repositoryEvent }; +}