From 6853a2f7c9128fc9c111db7a80021b345c151104 Mon Sep 17 00:00:00 2001
From: DanConwayDev
Date: Mon, 22 Dec 2025 20:58:58 +0000
Subject: [PATCH] feat: add NIP-34 user grasp list rendering
because you wanted to know what it feels like:
---
src/components/nostr/kinds/GraspListDetailRenderer.tsx | 34 ++++++++++++++++++++++++++++++++++
src/components/nostr/kinds/GraspListRenderer.tsx | 39 +++++++++++++++++++++++++++++++++++++++
src/components/nostr/kinds/index.tsx | 4 ++++
src/constants/kinds.ts | 7 +++++++
src/data/nostr-kinds-schema.yaml | 11 +++++++++++
5 files changed, 95 insertions(+)
create mode 100644 src/components/nostr/kinds/GraspListDetailRenderer.tsx
create mode 100644 src/components/nostr/kinds/GraspListRenderer.tsx
diff --git a/src/components/nostr/kinds/GraspListDetailRenderer.tsx b/src/components/nostr/kinds/GraspListDetailRenderer.tsx
new file mode 100644
index 0000000..b3c9475
--- /dev/null
+++ b/src/components/nostr/kinds/GraspListDetailRenderer.tsx
@@ -0,0 +1,34 @@
+import { NostrEvent } from "@/types/nostr";
+import { RelayLink } from "../RelayLink";
+
+/**
+ * Kind 10317 Detail Renderer - User Grasp List (Detail View)
+ * Shows full list of grasp service relays
+ */
+export function Kind10317DetailRenderer({ event }: { event: NostrEvent }) {
+ // Extract grasp relay URLs from g tags
+ const graspRelays = event.tags
+ .filter((tag) => tag[0] === "g" && tag[1])
+ .map((tag) => tag[1]);
+
+ if (graspRelays.length === 0) {
+ return (
+
+ No grasp relays configured
+
+ );
+ }
+
+ return (
+
+ {graspRelays.map((url, index) => (
+ + key={`${url}-${index}`} + url={url} + urlClassname="text-md underline decoration-dotted" + iconClassname="size-4" + /> + ))} +
+ );
+}
diff --git a/src/components/nostr/kinds/GraspListRenderer.tsx b/src/components/nostr/kinds/GraspListRenderer.tsx
new file mode 100644
index 0000000..ff1b2b8
--- /dev/null
+++ b/src/components/nostr/kinds/GraspListRenderer.tsx
@@ -0,0 +1,39 @@
+import { BaseEventProps, BaseEventContainer } from "./BaseEventRenderer";
+import { RelayLink } from "../RelayLink";
+
+/**
+ * Kind 10317 Renderer - User Grasp List (Feed View)
+ * Shows list of grasp service relays
+ */
+export function Kind10317Renderer({ event }: BaseEventProps) {
+ // Extract grasp relay URLs from g tags
+ const graspRelays = event.tags
+ .filter((tag) => tag[0] === "g" && tag[1])
+ .map((tag) => tag[1]);
+
+ if (graspRelays.length === 0) {
+ return (
+
+
+ No grasp relays configured
+
+
+ );
+ }
+
+ return (
+
+
+ {graspRelays.map((url, index) => (
+ + key={`${url}-${index}`} + url={url} + className="py-0.5 hover:bg-none" + iconClassname="size-4" + urlClassname="underline decoration-dotted" + /> + ))} +
+
+ );
+}
diff --git a/src/components/nostr/kinds/index.tsx b/src/components/nostr/kinds/index.tsx
index b251e88..989d4de 100644
--- a/src/components/nostr/kinds/index.tsx
+++ b/src/components/nostr/kinds/index.tsx
@@ -25,6 +25,8 @@ import { Kind9802Renderer } from "./HighlightRenderer";
import { Kind9802DetailRenderer } from "./HighlightDetailRenderer";
import { Kind10002Renderer } from "./RelayListRenderer";
import { Kind10002DetailRenderer } from "./RelayListDetailRenderer";
+import { Kind10317Renderer } from "./GraspListRenderer";
+import { Kind10317DetailRenderer } from "./GraspListDetailRenderer";
import { Kind30023Renderer } from "./ArticleRenderer";
import { Kind30023DetailRenderer } from "./ArticleDetailRenderer";
import { CommunityNIPRenderer } from "./CommunityNIPRenderer";
@@ -74,6 +76,7 @@ const kindRenderers: Record
9802: Kind9802Renderer, // Highlight
777: SpellRenderer, // Spell (Grimoire)
10002: Kind10002Renderer, // Relay List Metadata (NIP-65)
+ 10317: Kind10317Renderer, // User Grasp List (NIP-34)
10006: GenericRelayListRenderer, // Blocked Relays (NIP-51)
10007: GenericRelayListRenderer, // Search Relays (NIP-51)
10012: GenericRelayListRenderer, // Favorite Relays (NIP-51)
@@ -137,6 +140,7 @@ const detailRenderers: Record<
1621: IssueDetailRenderer, // Issue Detail (NIP-34)
9802: Kind9802DetailRenderer, // Highlight Detail
10002: Kind10002DetailRenderer, // Relay List Detail (NIP-65)
+ 10317: Kind10317DetailRenderer, // User Grasp List Detail (NIP-34)
777: SpellDetailRenderer, // Spell Detail
30023: Kind30023DetailRenderer, // Long-form Article Detail
30311: LiveActivityDetailRenderer, // Live Streaming Event Detail (NIP-53)
diff --git a/src/constants/kinds.ts b/src/constants/kinds.ts
index 6d51b1f..284595a 100644
--- a/src/constants/kinds.ts
+++ b/src/constants/kinds.ts
@@ -847,6 +847,13 @@ export const EVENT_KINDS: Record
nip: "66",
icon: Activity,
},
+ 10317: {
+ kind: 10317,
+ name: "Grasp List",
+ description: "User grasp list",
+ nip: "34",
+ icon: FolderGit2,
+ },
// 10312: {
// kind: 10312,
// name: "Room Presence",
diff --git a/src/data/nostr-kinds-schema.yaml b/src/data/nostr-kinds-schema.yaml
index 7f172c2..fb10587 100644
--- a/src/data/nostr-kinds-schema.yaml
+++ b/src/data/nostr-kinds-schema.yaml
@@ -1318,6 +1318,17 @@ kinds:
type: empty
tags: []
+ 10317:
+ description: User grasp list
+ content:
+ type: empty
+ tags:
+ -
+ name: g
+ next:
+ type: relay
+ required: true
+
10377:
description: Proxy Announcement
content:
--
libgit2 1.9.1