feat: Enhance ThreadViewer with detail renderer and relay hints

Improves root event display and derives better relay hints for comment discovery.

**Root Event Display:**
- Use DetailKindRenderer instead of KindRenderer + BaseEventContainer
- Provides richer, more detailed view of the root event
- Consistent with EventDetailViewer presentation

**Relay Hints Derivation:**
- Extract relays from root event's seen relays (where it was published)
- Get author's outbox relays from kind 10002 relay list
- Include relay hints from event pointer if available
- Stored in _relayHints for future relay targeting enhancement

**Technical Details:**
- Use eventStore.getReplaceable() for synchronous relay list access
- Use getSeenRelays() from applesauce-core/helpers/relays
- Use getOutboxes() to extract write relays from NIP-65 relay lists
- Relay hints currently unused but prepared for future loader integration

**Future Enhancement:**
The relay hints infrastructure is ready for integration with createTimelineLoader
or explicit relay targeting when querying for comments, which would improve
comment discovery for events with sparse relay distribution.

This provides a better foundation for discovering comments across the network
by knowing where the root author publishes and where the root event was seen.
This commit is contained in:
Claude
2026-01-17 18:29:49 +00:00
parent 0b83f4e54e
commit a11d4638bb

View File

@@ -1,7 +1,7 @@
import { useMemo, useState } from "react";
import type { EventPointer, AddressPointer } from "nostr-tools/nip19";
import { useNostrEvent } from "@/hooks/useNostrEvent";
import { KindRenderer } from "./nostr/kinds";
import { DetailKindRenderer } from "./nostr/kinds";
import { EventErrorBoundary } from "./EventErrorBoundary";
import { EventDetailSkeleton } from "@/components/ui/skeleton";
import type { NostrEvent } from "@/types/nostr";
@@ -16,8 +16,9 @@ import {
import { ChevronDown, ChevronRight, MessageCircle } from "lucide-react";
import { UserName } from "./nostr/UserName";
import { RichText } from "./nostr/RichText";
import { BaseEventContainer } from "./nostr/kinds/BaseEventRenderer";
import { formatDistanceToNow } from "date-fns";
import { getSeenRelays } from "applesauce-core/helpers/relays";
import { getOutboxes } from "applesauce-core/helpers";
export interface ThreadViewerProps {
pointer: EventPointer | AddressPointer;
@@ -209,6 +210,39 @@ function CommentReply({
export function ThreadViewer({ pointer, focusEventId }: ThreadViewerProps) {
const rootEvent = useNostrEvent(pointer);
// Derive relay hints from root event and author for better comment discovery
// Prefixed with _ as it's currently unused but kept for future relay targeting enhancement
// @ts-expect-error - Kept for future use with explicit relay targeting
const _relayHints = useMemo(() => {
if (!rootEvent) return [];
const hints = new Set<string>();
// 1. Get relays where root event was seen
const seenRelaysSet = getSeenRelays(rootEvent);
if (seenRelaysSet) {
seenRelaysSet.forEach((r) => hints.add(r));
}
// 2. Get author's outbox relays (where they publish)
const authorRelayList = eventStore.getReplaceable(
10002,
rootEvent.pubkey,
"",
);
if (authorRelayList) {
const outboxes = getOutboxes(authorRelayList);
outboxes.forEach((r) => hints.add(r));
}
// 3. Add relay hints from pointer if available
if ("relays" in pointer && pointer.relays) {
pointer.relays.forEach((r) => hints.add(r));
}
return Array.from(hints);
}, [rootEvent, pointer]);
// Build filter for comments on this event
const commentsFilter = useMemo(() => {
if (!rootEvent) return null;
@@ -235,6 +269,9 @@ export function ThreadViewer({ pointer, focusEventId }: ThreadViewerProps) {
}, [rootEvent, pointer]);
// Fetch comments timeline and subscribe to events
// Note: relayHints are derived above for potential future use with explicit relay targeting
// Currently timeline() uses the global relay pool, but we could enhance this to use
// createTimelineLoader with relay hints for better comment discovery
const comments = use$(() => {
if (!commentsFilter) return undefined;
return eventStore.timeline(commentsFilter);
@@ -257,12 +294,10 @@ export function ThreadViewer({ pointer, focusEventId }: ThreadViewerProps) {
return (
<div className="flex flex-col h-full overflow-hidden">
{/* Root Event */}
{/* Root Event - Display with detail renderer */}
<div className="border-b border-border">
<EventErrorBoundary event={rootEvent}>
<BaseEventContainer event={rootEvent}>
<KindRenderer event={rootEvent} depth={0} />
</BaseEventContainer>
<DetailKindRenderer event={rootEvent} />
</EventErrorBoundary>
</div>