// client-side comment threading, ported from NewCommentList in appview/models/comment.go. // bobbin returns comments flat; we group replies under the top-level comment their // replyTo strongRef points at, and sort everything oldest-first. export interface CommentView { uri: string; // content hash, needed to strongRef this comment when replying to it cid?: string; rkey: string; authorDid: string; authorHandle: string; createdAt: string; body: string; bodyHtml: string | null; } export interface CommentThread { self: CommentView; replies: CommentView[]; } // pairs a rendered view with the parent uri it replies to (null for top-level). export interface ThreadInput { comment: CommentView; replyTo: string | null; } const byCreatedAt = (a: CommentView, b: CommentView) => a.createdAt.localeCompare(b.createdAt); export function buildCommentThreads(inputs: ThreadInput[]): CommentThread[] { const threads = new Map(); const orphanReplies: ThreadInput[] = []; for (const input of inputs) { if (input.replyTo === null) { threads.set(input.comment.uri, { self: input.comment, replies: [] }); } } for (const input of inputs) { if (input.replyTo === null) continue; const parent = threads.get(input.replyTo); if (parent) { parent.replies.push(input.comment); } else { // parent not found (e.g. legacy/cross-collection ref): surface as top-level // so nothing is dropped. orphanReplies.push(input); } } for (const orphan of orphanReplies) { threads.set(orphan.comment.uri, { self: orphan.comment, replies: [] }); } const list = [...threads.values()]; list.sort((a, b) => byCreatedAt(a.self, b.self)); for (const thread of list) thread.replies.sort(byCreatedAt); return list; }