From 217ac46e7a95be6dd05b5d8b07029bf2c4c9261c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 21:18:46 +0000 Subject: [PATCH] feat(chat): improve NIP-10/NIP-22 UI and relay selection ChatViewer improvements: - Make NIP-22 protocol badge clickable to open NIP specification - Hide "load older messages" button for NIP-10 and NIP-22 (thread-based protocols) - Show custom title with kind icon, author, and event title for thread-based chats NIP-22 relay selection improvements: - Extract relay hints from comment event tags (A/E tags with relay URLs) - Fetch author's outbox relays (kind 10002) for better event discovery - Merge relay hints from multiple sources: pointer, comment tags, author outbox - Should significantly reduce "Comment root not found" errors All 1114 tests passing. --- src/components/ChatViewer.tsx | 68 ++++++++++++-- src/lib/chat/adapters/nip-22-adapter.ts | 119 ++++++++++++++++++++++-- 2 files changed, 171 insertions(+), 16 deletions(-) diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index ff2a343..e5158e3 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -69,6 +69,8 @@ import { TooltipTrigger, } from "./ui/tooltip"; import { useBlossomUpload } from "@/hooks/useBlossomUpload"; +import { getKindIcon } from "@/constants/kinds"; +import { getEventDisplayTitle } from "@/lib/event-title"; interface ChatViewerProps { protocol: ChatProtocol; @@ -831,6 +833,8 @@ export function ChatViewer({ const handleNipClick = useCallback(() => { if (conversation?.protocol === "nip-10") { addWindow("nip", { number: 10 }); + } else if (conversation?.protocol === "nip-22") { + addWindow("nip", { number: 22 }); } else if (conversation?.protocol === "nip-29") { addWindow("nip", { number: 29 }); } else if (conversation?.protocol === "nip-53") { @@ -906,6 +910,30 @@ export function ChatViewer({ liveActivity?.hostPubkey, ]); + // Get root event for NIP-10 and NIP-22 to build custom title + // Must be called before any early returns (React Hooks rules) + const rootEventId = + (protocol === "nip-10" || protocol === "nip-22") && conversation + ? conversation.metadata?.rootEventId || + conversation.metadata?.providedEventId + : undefined; + + const rootEvent = use$( + () => (rootEventId ? eventStore.event(rootEventId) : undefined), + [rootEventId], + ); + + // Compute custom title for thread-based protocols (NIP-10, NIP-22) + const threadTitle = useMemo(() => { + if ((protocol !== "nip-10" && protocol !== "nip-22") || !rootEvent) { + return null; + } + + const kindIcon = getKindIcon(rootEvent.kind); + const eventTitle = getEventDisplayTitle(rootEvent); + return { kindIcon, author: rootEvent.pubkey, eventTitle }; + }, [protocol, rootEvent]); + // Handle loading state if (!conversationResult || conversationResult.status === "loading") { return ( @@ -951,10 +979,28 @@ export function ChatViewer({ - {(conversation.type === "group" || + {(conversation.protocol === "nip-10" || + conversation.protocol === "nip-22" || + conversation.type === "group" || conversation.type === "live-chat") && ( )} - {(conversation.type === "group" || + {(conversation.protocol === "nip-10" || + conversation.protocol === "nip-22" || + conversation.type === "group" || conversation.type === "live-chat") && ( )} - {conversation.protocol === "nip-10" ? ( + {conversation.protocol === "nip-10" || + conversation.protocol === "nip-22" ? ( Thread @@ -1057,7 +1108,9 @@ export function ChatViewer({
- {(conversation.type === "group" || + {(conversation.protocol === "nip-10" || + conversation.protocol === "nip-22" || + conversation.type === "group" || conversation.type === "live-chat") && (