diff --git a/src/components/ThreadedReplyList.tsx b/src/components/ThreadedReplyList.tsx
new file mode 100644
index 00000000..487a7d88
--- /dev/null
+++ b/src/components/ThreadedReplyList.tsx
@@ -0,0 +1,31 @@
+import type { NostrEvent } from '@nostrify/nostrify';
+import { NoteCard } from '@/components/NoteCard';
+
+export interface ThreadedReply {
+ reply: NostrEvent;
+ firstSubReply?: NostrEvent;
+}
+
+interface ThreadedReplyListProps {
+ replies: ThreadedReply[];
+}
+
+/**
+ * Renders a flat list of replies where each top-level reply is optionally
+ * followed by its first sub-reply, using NoteCard's threaded/threadedLast
+ * connector-line styling for visual continuity.
+ */
+export function ThreadedReplyList({ replies }: ThreadedReplyListProps) {
+ return (
+ <>
+ {replies.map(({ reply, firstSubReply }) => (
+
+
+ {firstSubReply && (
+
+ )}
+
+ ))}
+ >
+ );
+}
diff --git a/src/pages/ExternalContentPage.tsx b/src/pages/ExternalContentPage.tsx
index df4e4341..4304831c 100644
--- a/src/pages/ExternalContentPage.tsx
+++ b/src/pages/ExternalContentPage.tsx
@@ -4,7 +4,7 @@ import { ArrowLeft, Globe, Heart, MessageSquare, Repeat2 } from 'lucide-react';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Skeleton } from '@/components/ui/skeleton';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
-import { NoteCard } from '@/components/NoteCard';
+import { ThreadedReplyList } from '@/components/ThreadedReplyList';
import { ComposeBox } from '@/components/ComposeBox';
import { ReplyComposeModal } from '@/components/ReplyComposeModal';
import { QuickReactMenu } from '@/components/QuickReactMenu';
@@ -326,14 +326,7 @@ export function ExternalContentPage() {
))}
) : orderedReplies.length > 0 ? (
- orderedReplies.map(({ reply, firstSubReply }) => (
-
-
- {firstSubReply && (
-
- )}
-
- ))
+
) : (
diff --git a/src/pages/PostDetailPage.tsx b/src/pages/PostDetailPage.tsx
index d2f132e6..594860fe 100644
--- a/src/pages/PostDetailPage.tsx
+++ b/src/pages/PostDetailPage.tsx
@@ -15,6 +15,7 @@ import { Input } from '@/components/ui/input';
import { NoteContent } from '@/components/NoteContent';
import { VideoPlayer } from '@/components/VideoPlayer';
import { NoteCard } from '@/components/NoteCard';
+import { ThreadedReplyList } from '@/components/ThreadedReplyList';
import { NoteMoreMenu } from '@/components/NoteMoreMenu';
import { ReplyComposeModal } from '@/components/ReplyComposeModal';
import { ReactionButton } from '@/components/ReactionButton';
@@ -1070,14 +1071,7 @@ function PostDetailContent({ event }: { event: NostrEvent }) {
))}
) : orderedReplies.length > 0 ? (
- orderedReplies.map(({ reply, firstSubReply }) => (
-
-
- {firstSubReply && (
-
- )}
-
- ))
+
) : !parentEventId ? (
No replies yet. Be the first to reply!
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx
index af96190c..d938a9da 100644
--- a/src/pages/ProfilePage.tsx
+++ b/src/pages/ProfilePage.tsx
@@ -37,6 +37,8 @@ import type { ProfileTab } from '@/hooks/useProfileFeed';
import { useProfileMedia } from '@/hooks/useProfileMedia';
import { useProfileSupplementary } from '@/hooks/useProfileData';
import { useWallComments } from '@/hooks/useWallComments';
+import { useComments } from '@/hooks/useComments';
+import { ThreadedReplyList } from '@/components/ThreadedReplyList';
import { useNip05Resolve } from '@/hooks/useNip05Resolve';
import { genUserName } from '@/lib/genUserName';
@@ -745,6 +747,9 @@ export function ProfilePage() {
};
}, [pubkey, metadataEvent]);
+ // Fetch all NIP-22 comments on the profile (for sub-reply pairing in the wall tab)
+ const { data: wallCommentsData } = useComments(wallReplyTarget, 500);
+
// Wall compose modal state (for FAB on wall tab)
const [wallComposeOpen, setWallComposeOpen] = useState(false);
@@ -1032,6 +1037,17 @@ export function ProfilePage() {
return items;
}, [wallData?.pages, muteItems]);
+ // Pair each wall comment with its first direct sub-reply (same pattern as PostDetailPage replies)
+ const orderedWallReplies = useMemo(() => {
+ return wallComments.map((comment) => {
+ const subReplies = wallCommentsData?.getDirectReplies(comment.id) ?? [];
+ return {
+ reply: comment,
+ firstSubReply: subReplies[0] as NostrEvent | undefined,
+ };
+ });
+ }, [wallComments, wallCommentsData]);
+
const streak = useMemo(() => {
if (!feedData?.pages) return 0;
const events: NostrEvent[] = [];
@@ -1545,11 +1561,9 @@ export function ProfilePage() {
))}
- ) : wallComments.length > 0 ? (
+ ) : orderedWallReplies.length > 0 ? (
- {wallComments.map((comment) => (
-
- ))}
+
{/* Infinite scroll sentinel */}
{hasNextWallPage && (