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
This commit is contained in:
Claude
2026-01-13 08:40:07 +00:00
parent 51fd0afe45
commit 0fcd243fc7
6 changed files with 72 additions and 34 deletions

View File

@@ -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,

View File

@@ -176,16 +176,18 @@ export function WindowRenderer({ window, onClose }: WindowRendererProps) {
content = <ConnViewer />;
break;
case "chat":
content = (
<ChatViewer
protocol={window.props.protocol}
identifier={window.props.identifier}
customTitle={window.customTitle}
/>
);
break;
case "chats":
content = <GroupListViewer />;
// Check if this is a group list (kind 10009) - render multi-room interface
if (window.props.identifier?.type === "group-list") {
content = <GroupListViewer />;
} else {
content = (
<ChatViewer
protocol={window.props.protocol}
identifier={window.props.identifier}
customTitle={window.customTitle}
/>
);
}
break;
case "spells":
content = <SpellsViewer />;

View File

@@ -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)

View File

@@ -17,7 +17,6 @@ export type AppId =
| "debug"
| "conn"
| "chat"
| "chats"
| "spells"
| "spellbooks"
| "win";

View File

@@ -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

View File

@@ -350,20 +350,21 @@ export const manPages: Record<string, ManPageEntry> = {
section: "1",
synopsis: "chat <identifier>",
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: "<identifier>",
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<string, ManPageEntry> = {
};
},
},
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",