diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx
index 2808c46..b62d872 100644
--- a/src/components/ChatViewer.tsx
+++ b/src/components/ChatViewer.tsx
@@ -11,6 +11,7 @@ import {
Paperclip,
Copy,
CopyCheck,
+ FileText,
} from "lucide-react";
import { nip19 } from "nostr-tools";
import { getZapRequest } from "applesauce-common/helpers/zap";
@@ -24,6 +25,7 @@ import type {
} from "@/types/chat";
import { CHAT_KINDS } from "@/types/chat";
// import { NipC7Adapter } from "@/lib/chat/adapters/nip-c7-adapter"; // Coming soon
+import { Nip10Adapter } from "@/lib/chat/adapters/nip-10-adapter";
import { Nip29Adapter } from "@/lib/chat/adapters/nip-29-adapter";
import { Nip53Adapter } from "@/lib/chat/adapters/nip-53-adapter";
import type { ChatProtocolAdapter } from "@/lib/chat/adapters/base-adapter";
@@ -41,6 +43,7 @@ import { StatusBadge } from "./live/StatusBadge";
import { ChatMessageContextMenu } from "./chat/ChatMessageContextMenu";
import { useGrimoire } from "@/core/state";
import { Button } from "./ui/button";
+import LoginDialog from "./nostr/LoginDialog";
import {
MentionEditor,
type MentionEditorHandle,
@@ -589,6 +592,9 @@ export function ChatViewer({
// State for tooltip open (for mobile tap support)
const [tooltipOpen, setTooltipOpen] = useState(false);
+ // State for login dialog
+ const [showLogin, setShowLogin] = useState(false);
+
// Handle sending messages with error handling
const handleSend = async (
content: string,
@@ -731,7 +737,9 @@ export function ChatViewer({
// Handle NIP badge click
const handleNipClick = useCallback(() => {
- if (conversation?.protocol === "nip-29") {
+ if (conversation?.protocol === "nip-10") {
+ addWindow("nip", { number: 10 });
+ } else if (conversation?.protocol === "nip-29") {
addWindow("nip", { number: 29 });
} else if (conversation?.protocol === "nip-53") {
addWindow("nip", { number: 53 });
@@ -745,33 +753,63 @@ export function ChatViewer({
? conversation?.metadata?.liveActivity
: undefined;
- // Derive participants from messages for live activities (unique pubkeys who have chatted)
+ // Derive participants from messages for live activities and NIP-10 threads
const derivedParticipants = useMemo(() => {
- if (conversation?.type !== "live-chat" || !messages) {
- return conversation?.participants || [];
- }
+ // NIP-10 threads: derive from messages with OP first
+ if (protocol === "nip-10" && messages && conversation) {
+ const rootAuthor = conversation.metadata?.rootEventId
+ ? messages.find((m) => m.id === conversation.metadata?.rootEventId)
+ ?.author
+ : undefined;
- const hostPubkey = liveActivity?.hostPubkey;
- const participants: { pubkey: string; role: "host" | "member" }[] = [];
+ const participants: { pubkey: string; role: "op" | "member" }[] = [];
- // Host always first
- if (hostPubkey) {
- participants.push({ pubkey: hostPubkey, role: "host" });
- }
-
- // Add other participants from messages (excluding host)
- const seen = new Set(hostPubkey ? [hostPubkey] : []);
- for (const msg of messages) {
- if (msg.type !== "system" && !seen.has(msg.author)) {
- seen.add(msg.author);
- participants.push({ pubkey: msg.author, role: "member" });
+ // OP (root author) always first
+ if (rootAuthor) {
+ participants.push({ pubkey: rootAuthor, role: "op" });
}
+
+ // Add other participants from messages (excluding OP)
+ const seen = new Set(rootAuthor ? [rootAuthor] : []);
+ for (const msg of messages) {
+ if (msg.type !== "system" && !seen.has(msg.author)) {
+ seen.add(msg.author);
+ participants.push({ pubkey: msg.author, role: "member" });
+ }
+ }
+
+ return participants;
}
- return participants;
+ // Live activities: derive from messages with host first
+ if (conversation?.type === "live-chat" && messages) {
+ const hostPubkey = liveActivity?.hostPubkey;
+ const participants: { pubkey: string; role: "host" | "member" }[] = [];
+
+ // Host always first
+ if (hostPubkey) {
+ participants.push({ pubkey: hostPubkey, role: "host" });
+ }
+
+ // Add other participants from messages (excluding host)
+ const seen = new Set(hostPubkey ? [hostPubkey] : []);
+ for (const msg of messages) {
+ if (msg.type !== "system" && !seen.has(msg.author)) {
+ seen.add(msg.author);
+ participants.push({ pubkey: msg.author, role: "member" });
+ }
+ }
+
+ return participants;
+ }
+
+ // Other protocols: use static participants from conversation
+ return conversation?.participants || [];
}, [
+ protocol,
conversation?.type,
conversation?.participants,
+ conversation?.metadata?.rootEventId,
messages,
liveActivity?.hostPubkey,
]);
@@ -874,9 +912,16 @@ export function ChatViewer({
conversation.type === "live-chat") && (
•
)}
-
- {conversation.type}
-
+ {conversation.protocol === "nip-10" ? (
+
+
+ Thread
+
+ ) : (
+
+ {conversation.type}
+
+ )}
{/* Live Activity Status */}
{liveActivity?.status && (
@@ -946,7 +991,9 @@ export function ChatViewer({
alignToBottom
components={{
Header: () =>
- hasMore && conversationResult.status === "success" ? (
+ hasMore &&
+ conversationResult.status === "success" &&
+ protocol !== "nip-10" ? (