diff --git a/src/pages/PostDetailPage.tsx b/src/pages/PostDetailPage.tsx index fd8e27d2..58508e63 100644 --- a/src/pages/PostDetailPage.tsx +++ b/src/pages/PostDetailPage.tsx @@ -650,15 +650,11 @@ function PostDetailContent({ event }: { event: NostrEvent }) { return rawReplies.filter((r) => !isEventMuted(r, muteItems)); }, [rawReplies, muteItems]); - // Organize replies into "conversation groups": - // - Direct replies to this event are the top-level items - // - If a direct reply starts a linear chain (each node has exactly 1 child), - // group the chain together so it renders connected - // - If a reply has multiple children (branching), it stays standalone - const replyGroups = useMemo(() => { + // Build a reply tree and flatten depth-first so sub-replies appear + // directly after their parent — natural conversation order. + const orderedReplies = useMemo(() => { if (!replies || replies.length === 0) return []; - // Map of parentId -> children const childrenMap = new Map(); const directReplies: NostrEvent[] = []; @@ -673,32 +669,17 @@ function PostDetailContent({ event }: { event: NostrEvent }) { } } - // For each direct reply, follow the linear chain as far as it goes - // A chain continues as long as the current node has exactly 1 child - type ReplyGroup = { chain: NostrEvent[]; branches: NostrEvent[] }; - const groups: ReplyGroup[] = []; - - for (const root of directReplies) { - const chain: NostrEvent[] = [root]; - let current = root; - - // Walk down single-child paths - while (true) { - const kids = childrenMap.get(current.id); - if (kids && kids.length === 1) { - chain.push(kids[0]); - current = kids[0]; - } else { - break; - } + // Depth-first walk + const result: NostrEvent[] = []; + function walk(events: NostrEvent[]) { + for (const ev of events) { + result.push(ev); + const children = childrenMap.get(ev.id); + if (children) walk(children); } - - // Whatever children the last node in the chain has (0 or 2+) are branches - const lastKids = childrenMap.get(current.id) || []; - groups.push({ chain, branches: lastKids }); } - - return groups; + walk(directReplies); + return result; }, [replies, event.id]); const [moreMenuOpen, setMoreMenuOpen] = useState(false); const [replyOpen, setReplyOpen] = useState(false); @@ -952,7 +933,7 @@ function PostDetailContent({ event }: { event: NostrEvent }) { /> - {/* Replies — linear chains rendered connected, branches rendered flat */} + {/* Replies */}
{repliesLoading ? (
@@ -960,9 +941,9 @@ function PostDetailContent({ event }: { event: NostrEvent }) { ))}
- ) : replyGroups.length > 0 ? ( - replyGroups.map((group) => ( - + ) : orderedReplies.length > 0 ? ( + orderedReplies.map((reply) => ( + )) ) : (
@@ -1136,40 +1117,6 @@ function AncestorNote({ event }: { event: NostrEvent }) { ); } -/** - * Renders a conversation group: a linear chain of replies shown connected - * (avatar + connector line), followed by any branching replies as flat NoteCards. - * - * - Single-reply chain (no continuation): renders as a normal NoteCard - * - Multi-reply chain: all but the last get the connected treatment (like ancestors), - * the last one gets a full NoteCard so it has action buttons - * - Branches off the end of the chain: rendered as standalone NoteCards below - */ -function ReplyChainGroup({ chain, branches }: { chain: NostrEvent[]; branches: NostrEvent[] }) { - if (chain.length === 1 && branches.length === 0) { - // Single reply, no continuation — just a normal card - return ; - } - - // Chain of 2+: show the leading items connected, last item as a full card - const connected = chain.length > 1 ? chain.slice(0, -1) : []; - const lastInChain = chain[chain.length - 1]; - - return ( -
- {/* Connected chain items — compact, with connector lines */} - {connected.map((ev) => ( - - ))} - {/* Last item in chain — full NoteCard with actions */} - - {/* Branching replies — flat NoteCards */} - {branches.map((ev) => ( - - ))} -
- ); -} function PostDetailSkeleton() { return (