From 0fcd243fc711f50f91bfe53e073655b804e42608 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Jan 2026 08:40:07 +0000 Subject: [PATCH] refactor: open GroupListViewer via chat command with kind 10009 naddr Instead of a separate 'chats' command, open the multi-room group list interface by passing a kind 10009 naddr to the chat command. Changes: - Fix TypeScript error in GroupListViewer (replaceable returns single event) - Add GroupListIdentifier type for kind 10009 addresses - Update chat parser to detect kind 10009 naddr and route to GroupListViewer - Update WindowRenderer to check identifier type and render GroupListViewer - Remove 'chats' command and appId (consolidated into chat command) - Update chat command documentation to include kind 10009 example Usage: chat naddr1...10009... Open multi-room group list interface --- src/components/GroupListViewer.tsx | 12 +++++++----- src/components/WindowRenderer.tsx | 22 +++++++++++---------- src/lib/chat-parser.ts | 31 +++++++++++++++++++++++++++++- src/types/app.ts | 1 - src/types/chat.ts | 19 +++++++++++++++++- src/types/man.ts | 21 +++++--------------- 6 files changed, 72 insertions(+), 34 deletions(-) diff --git a/src/components/GroupListViewer.tsx b/src/components/GroupListViewer.tsx index fcab058..816e2a9 100644 --- a/src/components/GroupListViewer.tsx +++ b/src/components/GroupListViewer.tsx @@ -113,11 +113,13 @@ export function GroupListViewer() { () => activePubkey ? eventStore.replaceable(10009, activePubkey).pipe( - map((events) => { - console.log( - `[GroupListViewer] Loaded ${events.length} group list events`, - ); - return events[0]; // replaceable() returns array, take most recent + map((event) => { + if (event) { + console.log( + `[GroupListViewer] Loaded group list event: ${event.id.slice(0, 8)}...`, + ); + } + return event; }), ) : undefined, diff --git a/src/components/WindowRenderer.tsx b/src/components/WindowRenderer.tsx index cbf73ea..a00275f 100644 --- a/src/components/WindowRenderer.tsx +++ b/src/components/WindowRenderer.tsx @@ -176,16 +176,18 @@ export function WindowRenderer({ window, onClose }: WindowRendererProps) { content = ; break; case "chat": - content = ( - - ); - break; - case "chats": - content = ; + // Check if this is a group list (kind 10009) - render multi-room interface + if (window.props.identifier?.type === "group-list") { + content = ; + } else { + content = ( + + ); + } break; case "spells": content = ; diff --git a/src/lib/chat-parser.ts b/src/lib/chat-parser.ts index 3e91358..8a0778f 100644 --- a/src/lib/chat-parser.ts +++ b/src/lib/chat-parser.ts @@ -1,7 +1,8 @@ -import type { ChatCommandResult } from "@/types/chat"; +import type { ChatCommandResult, GroupListIdentifier } from "@/types/chat"; // import { NipC7Adapter } from "./chat/adapters/nip-c7-adapter"; import { Nip29Adapter } from "./chat/adapters/nip-29-adapter"; import { Nip53Adapter } from "./chat/adapters/nip-53-adapter"; +import { nip19 } from "nostr-tools"; // Import other adapters as they're implemented // import { Nip17Adapter } from "./chat/adapters/nip-17-adapter"; // import { Nip28Adapter } from "./chat/adapters/nip-28-adapter"; @@ -34,6 +35,31 @@ export function parseChatCommand(args: string[]): ChatCommandResult { identifier = `${args[0]}'${args[1]}`; } + // Check for kind 10009 (group list) naddr - open multi-room interface + if (identifier.startsWith("naddr1")) { + try { + const decoded = nip19.decode(identifier); + if (decoded.type === "naddr" && decoded.data.kind === 10009) { + const groupListIdentifier: GroupListIdentifier = { + type: "group-list", + value: { + kind: 10009, + pubkey: decoded.data.pubkey, + identifier: decoded.data.identifier, + }, + relays: decoded.data.relays, + }; + return { + protocol: "nip-29", // Use nip-29 as the protocol designation + identifier: groupListIdentifier, + adapter: null, // No adapter needed for group list view + }; + } + } catch (e) { + // Not a valid naddr, continue to adapter parsing + } + } + // Try each adapter in priority order const adapters = [ // new Nip17Adapter(), // Phase 2 @@ -68,6 +94,9 @@ Currently supported formats: - naddr1... (NIP-53 live activity chat, kind 30311) Example: chat naddr1... (live stream address) + - naddr1... (Multi-room group list, kind 10009) + Example: + chat naddr1... (group list address) More formats coming soon: - npub/nprofile/hex pubkey (NIP-C7/NIP-17 direct messages) diff --git a/src/types/app.ts b/src/types/app.ts index e28e3a7..4b88e97 100644 --- a/src/types/app.ts +++ b/src/types/app.ts @@ -17,7 +17,6 @@ export type AppId = | "debug" | "conn" | "chat" - | "chats" | "spells" | "spellbooks" | "win"; diff --git a/src/types/chat.ts b/src/types/chat.ts index 6d77c7f..51b7b45 100644 --- a/src/types/chat.ts +++ b/src/types/chat.ts @@ -178,6 +178,22 @@ export interface ChannelIdentifier { relays?: string[]; } +/** + * Group list identifier (kind 10009) + * Used to open multi-room chat interface + */ +export interface GroupListIdentifier { + type: "group-list"; + /** Address pointer for the group list (kind 10009) */ + value: { + kind: 10009; + pubkey: string; + identifier: string; + }; + /** Relay hints from naddr encoding */ + relays?: string[]; +} + /** * Protocol-specific identifier - discriminated union * Returned by adapter parseIdentifier() @@ -187,7 +203,8 @@ export type ProtocolIdentifier = | LiveActivityIdentifier | DMIdentifier | NIP05Identifier - | ChannelIdentifier; + | ChannelIdentifier + | GroupListIdentifier; /** * Chat command parsing result diff --git a/src/types/man.ts b/src/types/man.ts index 7fcfc73..a97b2bd 100644 --- a/src/types/man.ts +++ b/src/types/man.ts @@ -350,20 +350,21 @@ export const manPages: Record = { section: "1", synopsis: "chat ", description: - "Join and participate in Nostr chat conversations. Supports NIP-29 relay-based groups and NIP-53 live activity chat. For NIP-29 groups, use format 'relay'group-id' where relay is the WebSocket URL (wss:// prefix optional). For NIP-53 live activities, pass the naddr of a kind 30311 live event to join its chat.", + "Join and participate in Nostr chat conversations. Supports NIP-29 relay-based groups, NIP-53 live activity chat, and multi-room group list interface. For NIP-29 groups, use format 'relay'group-id' where relay is the WebSocket URL (wss:// prefix optional). For NIP-53 live activities, pass the naddr of a kind 30311 live event. For multi-room interface, pass the naddr of a kind 10009 group list event.", options: [ { flag: "", description: - "NIP-29 group (relay'group-id) or NIP-53 live activity (naddr1...)", + "NIP-29 group (relay'group-id), NIP-53 live activity (naddr1... kind 30311), or group list (naddr1... kind 10009)", }, ], examples: [ "chat relay.example.com'bitcoin-dev Join NIP-29 relay group", "chat wss://nos.lol'welcome Join NIP-29 group with explicit protocol", - "chat naddr1... Join NIP-53 live activity chat", + "chat naddr1...30311... Join NIP-53 live activity chat", + "chat naddr1...10009... Open multi-room group list interface", ], - seeAlso: ["chats", "profile", "open", "req", "live"], + seeAlso: ["profile", "open", "req", "live"], appId: "chat", category: "Nostr", argParser: async (args: string[]) => { @@ -374,18 +375,6 @@ export const manPages: Record = { }; }, }, - chats: { - name: "chats", - section: "1", - synopsis: "chats", - description: - "Open a multi-room chat interface showing all your NIP-29 groups. Displays groups from your kind 10009 (User Group List) event, sorted by most recent activity. Click any group to view its chat. Provides an overview of all your group conversations in one place.", - examples: ["chats Open multi-room chat interface"], - seeAlso: ["chat", "profile"], - appId: "chats", - category: "Nostr", - defaultProps: {}, - }, profile: { name: "profile", section: "1",