diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx
index 686d9bd..7f5d614 100644
--- a/src/components/ChatViewer.tsx
+++ b/src/components/ChatViewer.tsx
@@ -12,6 +12,7 @@ import {
Copy,
CopyCheck,
FileText,
+ MessageSquare,
} from "lucide-react";
import { nip19 } from "nostr-tools";
import { getZapRequest } from "applesauce-common/helpers/zap";
@@ -35,6 +36,7 @@ import type { ChatAction } from "@/types/chat-actions";
import { parseSlashCommand } from "@/lib/chat/slash-command-parser";
import { UserName } from "./nostr/UserName";
import { RichText } from "./nostr/RichText";
+import { KindRenderer } from "./nostr/kinds";
import Timestamp from "./Timestamp";
import { ReplyPreview } from "./chat/ReplyPreview";
import { MembersDropdown } from "./chat/MembersDropdown";
@@ -263,6 +265,7 @@ const MessageItem = memo(function MessageItem({
onReply,
canReply,
onScrollToMessage,
+ isRootPost = false,
}: {
message: Message;
adapter: ChatProtocolAdapter;
@@ -270,6 +273,7 @@ const MessageItem = memo(function MessageItem({
onReply?: (messageId: string) => void;
canReply: boolean;
onScrollToMessage?: (messageId: string) => void;
+ isRootPost?: boolean;
}) {
// Get relays for this conversation (memoized to prevent unnecessary re-subscriptions)
const relays = useMemo(
@@ -277,6 +281,22 @@ const MessageItem = memo(function MessageItem({
[conversation],
);
+ // Root post: render using KindRenderer with bare mode (no header/footer)
+ if (isRootPost && message.event) {
+ return (
+
+ );
+ }
+
// System messages (join/leave) have special styling
if (message.type === "system") {
return (
@@ -918,6 +938,11 @@ export function ChatViewer({
Thread
+ ) : conversation.protocol === "nip-22" ? (
+
+
+ Comments
+
) : (
{conversation.type}
@@ -1028,6 +1053,10 @@ export function ChatViewer({
);
}
+ // Check if this is the root post (for NIP-10/NIP-22)
+ const rootEventId = conversation.metadata?.rootEventId;
+ const isRootPost = rootEventId === item.data.id;
+
return (
);
}}
diff --git a/src/components/nostr/kinds/BaseEventRenderer.tsx b/src/components/nostr/kinds/BaseEventRenderer.tsx
index b10f633..562c3ca 100644
--- a/src/components/nostr/kinds/BaseEventRenderer.tsx
+++ b/src/components/nostr/kinds/BaseEventRenderer.tsx
@@ -48,6 +48,11 @@ export interface BaseEventProps {
pubkey: string;
label?: string; // e.g., "Host", "Sender", "Zapper", "From"
};
+ /**
+ * If true, render content without header/footer wrapper
+ * Used in chat views where the container provides its own context
+ */
+ bare?: boolean;
}
/**
@@ -363,6 +368,8 @@ export function ClickableEventTitle({
/**
* Base event container with universal header
* Kind-specific renderers can wrap their content with this
+ *
+ * @param bare - If true, render children without header/footer wrapper
*/
/**
* Format relative time (e.g., "2m ago", "3h ago", "5d ago")
@@ -372,6 +379,7 @@ export function BaseEventContainer({
event,
children,
authorOverride,
+ bare = false,
}: {
event: NostrEvent;
children: React.ReactNode;
@@ -379,9 +387,15 @@ export function BaseEventContainer({
pubkey: string;
label?: string;
};
+ bare?: boolean;
}) {
const { locale } = useGrimoire();
+ // If bare mode, just render children without wrapper
+ if (bare) {
+ return <>{children}>;
+ }
+
// Format relative time for display
const relativeTime = formatTimestamp(
event.created_at,
diff --git a/src/components/nostr/kinds/NoteRenderer.tsx b/src/components/nostr/kinds/NoteRenderer.tsx
index 9614de7..e815fd9 100644
--- a/src/components/nostr/kinds/NoteRenderer.tsx
+++ b/src/components/nostr/kinds/NoteRenderer.tsx
@@ -71,7 +71,11 @@ function ParentEventCard({
* Renderer for Kind 1 - Short Text Note (NIP-10 threading)
* Shows immediate parent (reply) only for cleaner display
*/
-export function Kind1Renderer({ event, depth = 0 }: BaseEventProps) {
+export function Kind1Renderer({
+ event,
+ depth = 0,
+ bare = false,
+}: BaseEventProps) {
const { addWindow } = useGrimoire();
// Use NIP-10 threading helpers
@@ -93,14 +97,14 @@ export function Kind1Renderer({ event, depth = 0 }: BaseEventProps) {
};
return (
-
+
- {/* Show reply event (immediate parent) */}
- {replyPointer && !replyEvent && (
+ {/* Show reply event (immediate parent) - hide in bare mode */}
+ {!bare && replyPointer && !replyEvent && (
} />
)}
- {replyPointer && replyEvent && (
+ {!bare && replyPointer && replyEvent && (
> = {
* Default renderer for kinds without custom implementations
* Shows basic event info with raw content
*/
-function DefaultKindRenderer({ event }: BaseEventProps) {
+function DefaultKindRenderer({ event, bare = false }: BaseEventProps) {
return (
-
+
{event.content || "(empty content)"}
@@ -256,12 +256,14 @@ function DefaultKindRenderer({ event }: BaseEventProps) {
export function KindRenderer({
event,
depth = 0,
+ bare = false,
}: {
event: NostrEvent;
depth?: number;
+ bare?: boolean;
}) {
const Renderer = kindRenderers[event.kind] || DefaultKindRenderer;
- return ;
+ return ;
}
/**