- {conversation.metadata?.icon && (
-

- )}
{customTitle || conversation.title}
diff --git a/src/components/nostr/GroupLink.tsx b/src/components/nostr/GroupLink.tsx
index 810b962..35036af 100644
--- a/src/components/nostr/GroupLink.tsx
+++ b/src/components/nostr/GroupLink.tsx
@@ -1,22 +1,21 @@
import { MessageSquare } from "lucide-react";
import { useGrimoire } from "@/core/state";
import { cn } from "@/lib/utils";
-import { use$ } from "applesauce-react/hooks";
-import { map } from "rxjs/operators";
-import eventStore from "@/services/event-store";
import { getTagValue } from "applesauce-core/helpers";
+import type { NostrEvent } from "@/types/nostr";
/**
- * Format group identifier for display
- * Shows just the group-id part without the relay URL
+ * Format relay URL for display
+ * Removes protocol and trailing slash
*/
-function formatGroupIdForDisplay(groupId: string): string {
- return groupId;
+function formatRelayForDisplay(url: string): string {
+ return url.replace(/^wss?:\/\//, "").replace(/\/$/, "");
}
export interface GroupLinkProps {
groupId: string;
relayUrl: string;
+ metadata?: NostrEvent; // Optional pre-loaded metadata
className?: string;
iconClassname?: string;
}
@@ -25,35 +24,36 @@ export interface GroupLinkProps {
* GroupLink - Clickable NIP-29 group component
* Displays group name (from kind 39000 metadata) or group ID
* Opens chat window on click
+ *
+ * Special case: "_" group ID represents the unmanaged relay top-level group
*/
export function GroupLink({
groupId,
relayUrl,
+ metadata,
className,
iconClassname,
}: GroupLinkProps) {
const { addWindow } = useGrimoire();
- // Try to fetch group metadata (kind 39000) from EventStore
- // NIP-29 metadata events use #d tag with group ID
- const groupMetadata = use$(
- () =>
- eventStore
- .timeline([{ kinds: [39000], "#d": [groupId], limit: 1 }])
- .pipe(map((events) => events[0])),
- [groupId],
- );
+ // Handle special case: "_" is the unmanaged relay top-level group
+ const isUnmanagedGroup = groupId === "_";
// Extract group name from metadata if available
- const groupName =
- groupMetadata && groupMetadata.kind === 39000
- ? getTagValue(groupMetadata, "name") || groupId
- : groupId;
+ let groupName: string;
+ if (isUnmanagedGroup) {
+ // For "_" groups, show the relay name
+ groupName = formatRelayForDisplay(relayUrl);
+ } else if (metadata && metadata.kind === 39000) {
+ groupName = getTagValue(metadata, "name") || groupId;
+ } else {
+ groupName = groupId;
+ }
- // Extract group icon if available
+ // Extract group icon if available (not applicable for "_" groups)
const groupIcon =
- groupMetadata && groupMetadata.kind === 39000
- ? getTagValue(groupMetadata, "picture")
+ !isUnmanagedGroup && metadata && metadata.kind === 39000
+ ? getTagValue(metadata, "picture")
: undefined;
const handleClick = () => {
@@ -68,8 +68,6 @@ export function GroupLink({
});
};
- const displayName = formatGroupIdForDisplay(groupName);
-
return (
)}
- {displayName}
+ {groupName}
);
diff --git a/src/components/nostr/kinds/PublicChatsRenderer.tsx b/src/components/nostr/kinds/PublicChatsRenderer.tsx
index 03d5f20..f2bf46d 100644
--- a/src/components/nostr/kinds/PublicChatsRenderer.tsx
+++ b/src/components/nostr/kinds/PublicChatsRenderer.tsx
@@ -1,5 +1,9 @@
+import { use$ } from "applesauce-react/hooks";
+import { map } from "rxjs/operators";
import { BaseEventProps, BaseEventContainer } from "./BaseEventRenderer";
import { GroupLink } from "../GroupLink";
+import eventStore from "@/services/event-store";
+import type { NostrEvent } from "@/types/nostr";
/**
* Extract group references from a kind 10009 event
@@ -27,10 +31,35 @@ function extractGroups(event: { tags: string[][] }): Array<{
* Public Chats Renderer (Kind 10009)
* NIP-51 list of NIP-29 groups
* Displays each group as a clickable link with icon and name
+ * Batch-loads metadata for all groups to show their names
*/
export function PublicChatsRenderer({ event }: BaseEventProps) {
const groups = extractGroups(event);
+ // Batch-load metadata for all groups at once
+ // Filter out "_" which is the unmanaged relay group (doesn't have metadata)
+ const groupIds = groups.map((g) => g.groupId).filter((id) => id !== "_");
+
+ const groupMetadataMap = use$(
+ () =>
+ groupIds.length > 0
+ ? eventStore.timeline([{ kinds: [39000], "#d": groupIds }]).pipe(
+ map((events) => {
+ const metadataMap = new Map
();
+ for (const evt of events) {
+ // Extract group ID from #d tag
+ const dTag = evt.tags.find((t) => t[0] === "d");
+ if (dTag && dTag[1]) {
+ metadataMap.set(dTag[1], evt);
+ }
+ }
+ return metadataMap;
+ }),
+ )
+ : undefined,
+ [groupIds.join(",")],
+ );
+
if (groups.length === 0) {
return (
@@ -49,6 +78,7 @@ export function PublicChatsRenderer({ event }: BaseEventProps) {
key={`${group.relayUrl}'${group.groupId}`}
groupId={group.groupId}
relayUrl={group.relayUrl}
+ metadata={groupMetadataMap?.get(group.groupId)}
/>
))}