+ {/* Issue Header */}
+
+ {/* Title */}
+ {title || "Untitled Issue"}
+
+ {/* Repository Link */}
+ {repoAddress && (
+
+ Repository:
+
+
+ )}
+
+ {/* Metadata */}
+
+
+ By
+
+
+
•
+
+
+
+ {/* Labels */}
+ {labels.length > 0 && (
+
+
+ {labels.map((label, idx) => (
+
+ {label}
+
+ ))}
+
+ )}
+
+
+ {/* Issue Body - Markdown */}
+ {event.content ? (
+
+ {
+ if (url.startsWith("nostr:")) return url;
+ return defaultUrlTransform(url);
+ }}
+ components={{
+ // Enable images with zoom
+ img: ({ src, alt }) =>
+ src ? (
+
+ ) : null,
+ // Handle nostr: links
+ a: ({ href, children, ...props }) => {
+ if (!href) return null;
+
+ // Render nostr: mentions inline
+ if (href.startsWith("nostr:")) {
+ return ;
+ }
+
+ // Regular links
+ return (
+
+ {children}
+
+ );
+ },
+ // Style adjustments for dark theme
+ h1: ({ ...props }) => (
+
+ ),
+ h2: ({ ...props }) => (
+
+ ),
+ h3: ({ ...props }) => (
+
+ ),
+ p: ({ ...props }) => (
+
+ ),
+ code: ({ ...props }: any) => (
+
+ ),
+ blockquote: ({ ...props }) => (
+
+ ),
+ ul: ({ ...props }) => (
+
+ ),
+ ol: ({ ...props }) => (
+
+ ),
+ hr: () =>
,
+ }}
+ >
+ {event.content}
+
+
+ ) : (
+
+ (No description provided)
+
+ )}
+
+ );
+}
diff --git a/src/components/nostr/kinds/Kind1621Renderer.tsx b/src/components/nostr/kinds/Kind1621Renderer.tsx
new file mode 100644
index 0000000..ede873d
--- /dev/null
+++ b/src/components/nostr/kinds/Kind1621Renderer.tsx
@@ -0,0 +1,110 @@
+import { BaseEventContainer, type BaseEventProps } from "./BaseEventRenderer";
+import { GitBranch } from "lucide-react";
+import { useGrimoire } from "@/core/state";
+import { useNostrEvent } from "@/hooks/useNostrEvent";
+import {
+ getIssueTitle,
+ getIssueLabels,
+ getIssueRepositoryAddress,
+} from "@/lib/nip34-helpers";
+import {
+ getRepositoryName,
+ getRepositoryIdentifier,
+} from "@/lib/nip34-helpers";
+import { UserName } from "../UserName";
+
+/**
+ * Renderer for Kind 1621 - Issue
+ * Displays as a compact issue card in feed view
+ */
+export function Kind1621Renderer({ event }: BaseEventProps) {
+ const { addWindow } = useGrimoire();
+ const title = getIssueTitle(event);
+ const labels = getIssueLabels(event);
+ const repoAddress = getIssueRepositoryAddress(event);
+
+ // Parse repository address to get the pointer
+ const repoPointer = repoAddress
+ ? (() => {
+ try {
+ // Address format: "kind:pubkey:identifier"
+ const [kindStr, pubkey, identifier] = repoAddress.split(":");
+ return {
+ kind: parseInt(kindStr),
+ pubkey,
+ identifier,
+ };
+ } catch {
+ return null;
+ }
+ })()
+ : null;
+
+ // Fetch the repository event to get its name
+ const repoEvent = useNostrEvent(
+ repoPointer
+ ? {
+ kind: repoPointer.kind,
+ pubkey: repoPointer.pubkey,
+ identifier: repoPointer.identifier,
+ }
+ : undefined,
+ );
+
+ // Get repository display name
+ const repoName = repoEvent
+ ? getRepositoryName(repoEvent) ||
+ getRepositoryIdentifier(repoEvent) ||
+ "Repository"
+ : repoAddress?.split(":")[2] || "Unknown Repository";
+
+ const handleRepoClick = () => {
+ addWindow("open", { pointer: repoPointer }, `Repository: ${repoName}`);
+ };
+
+ return (
+
+ {/* Repository Header */}
+
+ {/* Name */}
+ {displayName}
+
+ {/* Identifier */}
+ {identifier && (
+
+ /{identifier}
+
+ )}
+
+ {/* Description */}
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {/* URLs Section */}
+ {(webUrls.length > 0 || cloneUrls.length > 0) && (
+
+
+
+ URLs
+
+
+ {/* Web URLs */}
+ {webUrls.length > 0 && (
+
+
+ Website
+
+
+ {webUrls.map((url, idx) => (
+
+ ))}
+
+
+ )}
+
+ {/* Clone URLs */}
+ {cloneUrls.length > 0 && (
+
+
+ git URLs
+
+
+ {cloneUrls.map((url, idx) => (
+
+ ))}
+
+
+ )}
+
+ )}
+
+ {/* Maintainers Section */}
+ {maintainers.length > 0 && (
+
+
+
+ Maintainers
+
+
+ {maintainers.map((pubkey) => (
+
+ ))}
+
+
+ )}
+
+ {/* Relay Hints Section */}
+ {relays.length > 0 && (
+
+
+
+ Relays
+
+
+ {relays.map((url) => (
+ -
+
+
+ ))}
+
+
+ )}
+
+ );
+}
+
+/**
+ * Component to display a web URL with copy button
+ */
+function UrlItem({ url }: { url: string }) {
+ const { copy, copied } = useCopy();
+
+ return (
+