@flanksource/clicky-ui 0.3.30 → 0.3.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/comments/CommentCard.cjs +156 -273
  2. package/dist/comments/CommentCard.cjs.map +1 -1
  3. package/dist/comments/CommentCard.d.ts +5 -1
  4. package/dist/comments/CommentCard.d.ts.map +1 -1
  5. package/dist/comments/CommentCard.js +151 -268
  6. package/dist/comments/CommentCard.js.map +1 -1
  7. package/dist/comments/CommentCardParts.cjs +115 -0
  8. package/dist/comments/CommentCardParts.cjs.map +1 -0
  9. package/dist/comments/CommentCardParts.d.ts +21 -0
  10. package/dist/comments/CommentCardParts.d.ts.map +1 -0
  11. package/dist/comments/CommentCardParts.js +115 -0
  12. package/dist/comments/CommentCardParts.js.map +1 -0
  13. package/dist/comments/CommentProvider.cjs +3 -0
  14. package/dist/comments/CommentProvider.cjs.map +1 -1
  15. package/dist/comments/CommentProvider.d.ts +1 -1
  16. package/dist/comments/CommentProvider.d.ts.map +1 -1
  17. package/dist/comments/CommentProvider.js +3 -0
  18. package/dist/comments/CommentProvider.js.map +1 -1
  19. package/dist/comments/CommentSidePanel.cjs +55 -22
  20. package/dist/comments/CommentSidePanel.cjs.map +1 -1
  21. package/dist/comments/CommentSidePanel.d.ts +3 -1
  22. package/dist/comments/CommentSidePanel.d.ts.map +1 -1
  23. package/dist/comments/CommentSidePanel.js +56 -23
  24. package/dist/comments/CommentSidePanel.js.map +1 -1
  25. package/dist/comments/CommentThread.cjs +2 -0
  26. package/dist/comments/CommentThread.cjs.map +1 -1
  27. package/dist/comments/CommentThread.d.ts +2 -1
  28. package/dist/comments/CommentThread.d.ts.map +1 -1
  29. package/dist/comments/CommentThread.js +2 -0
  30. package/dist/comments/CommentThread.js.map +1 -1
  31. package/dist/comments/CommentThreadList.cjs +52 -34
  32. package/dist/comments/CommentThreadList.cjs.map +1 -1
  33. package/dist/comments/CommentThreadList.d.ts +5 -2
  34. package/dist/comments/CommentThreadList.d.ts.map +1 -1
  35. package/dist/comments/CommentThreadList.js +52 -34
  36. package/dist/comments/CommentThreadList.js.map +1 -1
  37. package/dist/comments/comment-types.cjs +16 -4
  38. package/dist/comments/comment-types.cjs.map +1 -1
  39. package/dist/comments/comment-types.d.ts +11 -1
  40. package/dist/comments/comment-types.d.ts.map +1 -1
  41. package/dist/comments/comment-types.js +16 -4
  42. package/dist/comments/comment-types.js.map +1 -1
  43. package/dist/comments/comment-utils.cjs +31 -1
  44. package/dist/comments/comment-utils.cjs.map +1 -1
  45. package/dist/comments/comment-utils.d.ts +9 -2
  46. package/dist/comments/comment-utils.d.ts.map +1 -1
  47. package/dist/comments/comment-utils.js +31 -1
  48. package/dist/comments/comment-utils.js.map +1 -1
  49. package/dist/comments.cjs +3 -0
  50. package/dist/comments.cjs.map +1 -1
  51. package/dist/comments.js +4 -1
  52. package/dist/components/ComboboxMenu.cjs +4 -1
  53. package/dist/components/ComboboxMenu.cjs.map +1 -1
  54. package/dist/components/ComboboxMenu.d.ts.map +1 -1
  55. package/dist/components/ComboboxMenu.js +5 -2
  56. package/dist/components/ComboboxMenu.js.map +1 -1
  57. package/dist/components/ComboboxTags.cjs +5 -1
  58. package/dist/components/ComboboxTags.cjs.map +1 -1
  59. package/dist/components/ComboboxTags.d.ts.map +1 -1
  60. package/dist/components/ComboboxTags.js +6 -2
  61. package/dist/components/ComboboxTags.js.map +1 -1
  62. package/dist/components/ConnectionLoggingPolicy.cjs +24 -35
  63. package/dist/components/ConnectionLoggingPolicy.cjs.map +1 -1
  64. package/dist/components/ConnectionLoggingPolicy.d.ts.map +1 -1
  65. package/dist/components/ConnectionLoggingPolicy.js +24 -35
  66. package/dist/components/ConnectionLoggingPolicy.js.map +1 -1
  67. package/dist/components/MultiSelect.cjs.map +1 -1
  68. package/dist/components/MultiSelect.d.ts +7 -0
  69. package/dist/components/MultiSelect.d.ts.map +1 -1
  70. package/dist/components/MultiSelect.js.map +1 -1
  71. package/dist/components/filter-bar-multi.cjs +10 -1
  72. package/dist/components/filter-bar-multi.cjs.map +1 -1
  73. package/dist/components/filter-bar-multi.d.ts.map +1 -1
  74. package/dist/components/filter-bar-multi.js +11 -2
  75. package/dist/components/filter-bar-multi.js.map +1 -1
  76. package/dist/data/ai/SessionViewer.model.cjs +23 -8
  77. package/dist/data/ai/SessionViewer.model.cjs.map +1 -1
  78. package/dist/data/ai/SessionViewer.model.d.ts +8 -0
  79. package/dist/data/ai/SessionViewer.model.d.ts.map +1 -1
  80. package/dist/data/ai/SessionViewer.model.js +23 -8
  81. package/dist/data/ai/SessionViewer.model.js.map +1 -1
  82. package/dist/data/ai/SessionViewer.rows.cjs +9 -1
  83. package/dist/data/ai/SessionViewer.rows.cjs.map +1 -1
  84. package/dist/data/ai/SessionViewer.rows.js +9 -1
  85. package/dist/data/ai/SessionViewer.rows.js.map +1 -1
  86. package/dist/data/ai/SpecRuntimeEditor/VerifySection.cjs +24 -2
  87. package/dist/data/ai/SpecRuntimeEditor/VerifySection.cjs.map +1 -1
  88. package/dist/data/ai/SpecRuntimeEditor/VerifySection.d.ts.map +1 -1
  89. package/dist/data/ai/SpecRuntimeEditor/VerifySection.js +25 -3
  90. package/dist/data/ai/SpecRuntimeEditor/VerifySection.js.map +1 -1
  91. package/dist/data/ai/SpecRuntimeEditor/summaries.cjs.map +1 -1
  92. package/dist/data/ai/SpecRuntimeEditor/summaries.d.ts.map +1 -1
  93. package/dist/data/ai/SpecRuntimeEditor/summaries.js.map +1 -1
  94. package/dist/data/ai/SpecRuntimeEditor.model.cjs +4 -0
  95. package/dist/data/ai/SpecRuntimeEditor.model.cjs.map +1 -1
  96. package/dist/data/ai/SpecRuntimeEditor.model.d.ts +4 -0
  97. package/dist/data/ai/SpecRuntimeEditor.model.d.ts.map +1 -1
  98. package/dist/data/ai/SpecRuntimeEditor.model.js +4 -0
  99. package/dist/data/ai/SpecRuntimeEditor.model.js.map +1 -1
  100. package/dist/data/test-runner/TestRunner.cjs +6 -4
  101. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  102. package/dist/data/test-runner/TestRunner.d.ts +10 -1
  103. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  104. package/dist/data/test-runner/TestRunner.js +6 -4
  105. package/dist/data/test-runner/TestRunner.js.map +1 -1
  106. package/dist/data/test-runner/status.cjs +1 -1
  107. package/dist/data/test-runner/status.cjs.map +1 -1
  108. package/dist/data/test-runner/status.js +1 -1
  109. package/dist/data/test-runner/status.js.map +1 -1
  110. package/dist/data/test-runner/types.d.ts +6 -0
  111. package/dist/data/test-runner/types.d.ts.map +1 -1
  112. package/dist/data/verification/VerificationResults.cjs +99 -0
  113. package/dist/data/verification/VerificationResults.cjs.map +1 -0
  114. package/dist/data/verification/VerificationResults.d.ts +27 -0
  115. package/dist/data/verification/VerificationResults.d.ts.map +1 -0
  116. package/dist/data/verification/VerificationResults.js +99 -0
  117. package/dist/data/verification/VerificationResults.js.map +1 -0
  118. package/dist/data/verification/adapterViews.cjs +116 -0
  119. package/dist/data/verification/adapterViews.cjs.map +1 -0
  120. package/dist/data/verification/adapterViews.d.ts +13 -0
  121. package/dist/data/verification/adapterViews.d.ts.map +1 -0
  122. package/dist/data/verification/adapterViews.js +116 -0
  123. package/dist/data/verification/adapterViews.js.map +1 -0
  124. package/dist/data/verification/adapters.cjs +26 -0
  125. package/dist/data/verification/adapters.cjs.map +1 -0
  126. package/dist/data/verification/adapters.d.ts +6 -0
  127. package/dist/data/verification/adapters.d.ts.map +1 -0
  128. package/dist/data/verification/adapters.js +26 -0
  129. package/dist/data/verification/adapters.js.map +1 -0
  130. package/dist/data/verification/index.d.ts +5 -0
  131. package/dist/data/verification/index.d.ts.map +1 -0
  132. package/dist/data/verification/verify-report.cjs +58 -0
  133. package/dist/data/verification/verify-report.cjs.map +1 -0
  134. package/dist/data/verification/verify-report.d.ts +96 -0
  135. package/dist/data/verification/verify-report.d.ts.map +1 -0
  136. package/dist/data/verification/verify-report.js +58 -0
  137. package/dist/data/verification/verify-report.js.map +1 -0
  138. package/dist/data/verification/verifyReportTests.cjs +84 -0
  139. package/dist/data/verification/verifyReportTests.cjs.map +1 -0
  140. package/dist/data/verification/verifyReportTests.d.ts +13 -0
  141. package/dist/data/verification/verifyReportTests.d.ts.map +1 -0
  142. package/dist/data/verification/verifyReportTests.js +84 -0
  143. package/dist/data/verification/verifyReportTests.js.map +1 -0
  144. package/dist/data/version-info.cjs +3 -3
  145. package/dist/data/version-info.js +3 -3
  146. package/dist/data.cjs +10 -0
  147. package/dist/data.cjs.map +1 -1
  148. package/dist/data.d.ts +1 -0
  149. package/dist/data.d.ts.map +1 -1
  150. package/dist/data.js +10 -0
  151. package/dist/data.js.map +1 -1
  152. package/dist/index.cjs +13 -0
  153. package/dist/index.cjs.map +1 -1
  154. package/dist/index.js +14 -1
  155. package/dist/index.js.map +1 -1
  156. package/dist/styles.css +1 -1
  157. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CommentSidePanel.cjs","sources":["../../src/comments/CommentSidePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type MouseEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiComment } from \"../icons\";\nimport { CommentThread } from \"./CommentThread\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport {\n useCommentContextOptional,\n type CommentContextValue,\n} from \"./comment-context\";\nimport {\n buildThreadListHandlers,\n getRoots,\n selectAnchorThreads,\n selectUnresolvedThreads,\n sortReplies,\n buildReplyMap,\n} from \"./comment-utils\";\nimport {\n DOCUMENT_ANCHOR,\n type Comment,\n type CommentAnchor,\n} from \"./comment-types\";\n\nexport type CommentSidePanelProps = {\n /** Explicit label per anchor key. */\n anchorLabels?: Record<CommentAnchor, string>;\n /** Fallback label formatter for anchors without an explicit label. */\n formatAnchorLabel?: (anchor: CommentAnchor) => string;\n compact?: boolean;\n /** Position the focused thread beside its registered content anchor. */\n focusedAlignment?: \"flow\" | \"anchor\";\n className?: string;\n};\n\nfunction defaultAnchorLabel(anchor: CommentAnchor): string {\n if (anchor === DOCUMENT_ANCHOR) return \"General\";\n return anchor\n .replace(/\\[\\d+\\]/g, \"\")\n .replaceAll(\".\", \" › \")\n .replaceAll(\"_\", \" \")\n .trim();\n}\n\nfunction useAnchorLabel(props: CommentSidePanelProps) {\n return (anchor: CommentAnchor): string =>\n props.anchorLabels?.[anchor] ??\n props.formatAnchorLabel?.(anchor) ??\n defaultAnchorLabel(anchor);\n}\n\nfunction LocationMeta({ label }: { label: string }) {\n return (\n <span\n data-testid=\"comment-location-meta\"\n className=\"inline-flex items-center rounded-full bg-muted px-2 py-1 text-[10px] font-medium text-muted-foreground\"\n >\n {label}\n </span>\n );\n}\n\nfunction RailToggle({\n active,\n onClick,\n children,\n testId,\n}: {\n active?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n testId?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n data-testid={testId}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium shadow-sm transition-colors\",\n active\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n <Icon icon={UiComment} className=\"text-xs\" />\n {children}\n </button>\n );\n}\n\nfunction orderedAnchors(ctx: CommentContextValue): string[] {\n return Object.keys(ctx.commentMeta).sort((a, b) => {\n if (a === DOCUMENT_ANCHOR) return -1;\n if (b === DOCUMENT_ANCHOR) return 1;\n const at = ctx.getAnchorTop(a);\n const bt = ctx.getAnchorTop(b);\n if (at == null && bt == null) return a.localeCompare(b);\n if (at == null) return 1;\n if (bt == null) return -1;\n if (at !== bt) return at - bt;\n return a.localeCompare(b);\n });\n}\n\nfunction orderComments(\n comments: Comment[],\n anchorOrder: Map<string, number>,\n): Comment[] {\n const replyMap = buildReplyMap(comments);\n const roots = getRoots(comments).sort((a, b) => {\n const ai =\n anchorOrder.get(a.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n const bi =\n anchorOrder.get(b.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n if (ai !== bi) return ai - bi;\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n return roots.flatMap((root) => [\n root,\n ...sortReplies(replyMap.get(root.id) ?? []),\n ]);\n}\n\nfunction AllComments({\n ctx,\n comments,\n label,\n emptyLabel,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n label: (a: CommentAnchor) => string;\n emptyLabel: string;\n}) {\n const ordered = useMemo(() => {\n const anchorOrder = new Map(orderedAnchors(ctx).map((a, i) => [a, i]));\n return orderComments(comments, anchorOrder);\n }, [ctx, comments]);\n const handlers = buildThreadListHandlers(ordered, ctx.config, ctx.callbacks);\n\n function activateThread(\n event: MouseEvent<HTMLDivElement>,\n anchor: CommentAnchor,\n ) {\n const target = event.target as Element;\n if (\n target.closest(\n 'button, a, input, textarea, select, [contenteditable=\"true\"]',\n )\n )\n return;\n const roleButton = target.closest('[role=\"button\"]');\n if (roleButton && roleButton !== target.closest(\"[data-comment-kind]\"))\n return;\n if (anchor !== DOCUMENT_ANCHOR) {\n const found = ctx.scrollToAnchor(anchor, {\n behavior: \"smooth\",\n block: \"start\",\n offset: 12,\n });\n if (!found) return;\n }\n ctx.focusAnchor(anchor);\n }\n\n if (ordered.length === 0) {\n return (\n <p className=\"rounded-lg bg-muted/40 px-3 py-2 text-xs text-muted-foreground\">\n {emptyLabel}\n </p>\n );\n }\n\n return (\n <div\n data-testid=\"comment-all-rail\"\n className=\"space-y-3 overflow-y-auto pr-1\"\n >\n <CommentThreadList\n comments={ordered}\n config={ctx.config}\n compact\n renderRootMeta={(c) => {\n const anchor = c.anchor ?? DOCUMENT_ANCHOR;\n const available =\n anchor === DOCUMENT_ANCHOR || ctx.getAnchorTop(anchor) != null;\n return (\n <LocationMeta\n label={`${label(anchor)}${available ? \"\" : \" · Unavailable\"}`}\n />\n );\n }}\n getThreadProps={(c) => ({\n \"data-testid\": \"comment-feed-item\",\n onMouseEnter: () =>\n ctx.setHighlightAnchor(c.anchor ?? DOCUMENT_ANCHOR),\n onMouseLeave: () => ctx.setHighlightAnchor(null),\n onClick: (event) =>\n activateThread(event, c.anchor ?? DOCUMENT_ANCHOR),\n })}\n {...handlers}\n />\n </div>\n );\n}\n\nfunction FocusedComments({\n ctx,\n comments: visible,\n anchor,\n label,\n compact,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n anchor: CommentAnchor;\n label: string;\n compact?: boolean;\n}) {\n const comments = selectAnchorThreads(visible, anchor);\n const hasComments = comments.length > 0;\n return (\n <div className=\"space-y-3\" data-comment-anchor={anchor}>\n <LocationMeta\n label={\n anchor === DOCUMENT_ANCHOR\n ? \"Whole-page comment\"\n : `Attached to ${label}`\n }\n />\n <CommentThread\n comments={comments}\n config={ctx.config}\n anchor={anchor}\n compact={compact ?? false}\n autoFocusComposer={!hasComments}\n defaultExpanded={hasComments}\n composerPlaceholder={\n hasComments ? \"Add another top-level comment…\" : \"Add a comment…\"\n }\n {...ctx.callbacks}\n />\n </div>\n );\n}\n\n/**\n * A controlled comment rail driven by {@link CommentProvider}. Shows a focused\n * thread for the active anchor, the full document feed in anchor order, or a\n * toggle when collapsed. Renders nothing outside a provider or when empty.\n */\nexport function CommentSidePanel(props: CommentSidePanelProps) {\n const ctx = useCommentContextOptional();\n const label = useAnchorLabel(props);\n const railRef = useRef<HTMLElement>(null);\n const focusedRef = useRef<HTMLDivElement>(null);\n const [focusedOffset, setFocusedOffset] = useState<number | null>(null);\n const [showResolved, setShowResolved] = useState(false);\n const focusedAnchor = ctx?.railMode === \"focused\" ? ctx.focusedAnchor : null;\n\n useEffect(() => {\n if (!ctx || props.focusedAlignment !== \"anchor\" || !focusedAnchor) {\n setFocusedOffset(null);\n return;\n }\n const content = ctx.contentRef.current;\n const rail = railRef.current;\n const focused = focusedRef.current;\n if (!content || !rail || !focused) return;\n\n const update = () => {\n const anchorTop = ctx.getAnchorTop(focusedAnchor);\n if (anchorTop == null) {\n setFocusedOffset(null);\n return;\n }\n const desiredTop = anchorTop + content.getBoundingClientRect().top;\n const currentOffset = Number.parseFloat(focused.style.top) || 0;\n const nextOffset =\n currentOffset + desiredTop - focused.getBoundingClientRect().top;\n setFocusedOffset(Math.max(0, nextOffset));\n };\n update();\n content.addEventListener(\"scroll\", update, { passive: true });\n window.addEventListener(\"resize\", update);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n observer?.observe(content);\n observer?.observe(rail);\n return () => {\n content.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n observer?.disconnect();\n };\n }, [ctx, focusedAnchor, props.focusedAlignment]);\n\n if (!ctx) return null;\n\n // Threads, not cards: a reply is part of its root, never a separate entry.\n const unresolved = selectUnresolvedThreads(ctx.comments, ctx.config);\n const visible = showResolved ? ctx.comments : unresolved;\n const total = getRoots(visible).length;\n const resolvedCount = getRoots(ctx.comments).length - getRoots(unresolved).length;\n if (ctx.railMode === \"closed\" && total === 0 && resolvedCount === 0) return null;\n\n return (\n <aside\n ref={railRef}\n data-testid=\"comment-side-panel\"\n className={cn(\"relative w-[320px] space-y-3\", props.className)}\n >\n <div\n data-testid=\"comment-rail-header\"\n className=\"sticky top-0 z-20 flex items-center gap-2 bg-background py-2\"\n >\n {(total > 0 || ctx.railMode === \"all\") && (\n <RailToggle\n active={ctx.railMode === \"all\"}\n onClick={ctx.openCommentList}\n testId=\"comment-open-all\"\n >\n {ctx.railMode === \"closed\"\n ? `Open comments (${total})`\n : `All comments (${total})`}\n </RailToggle>\n )}\n {resolvedCount > 0 && (\n <button\n type=\"button\"\n aria-pressed={showResolved}\n onClick={() => setShowResolved((shown) => !shown)}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n showResolved\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Resolved ({resolvedCount})\n </button>\n )}\n {ctx.railMode !== \"closed\" && (\n <button\n type=\"button\"\n onClick={ctx.closeRail}\n className=\"ml-auto rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n {ctx.railMode === \"all\" ? \"Close\" : \"Hide\"}\n </button>\n )}\n </div>\n {ctx.railMode === \"all\" ? (\n <AllComments\n ctx={ctx}\n comments={visible}\n label={label}\n emptyLabel={resolvedCount > 0 ? \"No open comments.\" : \"No comments yet.\"}\n />\n ) : ctx.railMode === \"focused\" && ctx.focusedAnchor ? (\n <div\n ref={focusedRef}\n data-testid=\"comment-focused-rail\"\n className=\"relative space-y-3\"\n style={focusedOffset == null ? undefined : { top: focusedOffset }}\n >\n <FocusedComments\n ctx={ctx}\n comments={visible}\n anchor={ctx.focusedAnchor}\n label={label(ctx.focusedAnchor)}\n {...(props.compact !== undefined ? { compact: props.compact } : {})}\n />\n </div>\n ) : null}\n </aside>\n );\n}\n"],"names":["DOCUMENT_ANCHOR","jsx","jsxs","cn","Icon","UiComment","buildReplyMap","getRoots","sortReplies","useMemo","buildThreadListHandlers","CommentThreadList","selectAnchorThreads","CommentThread","useCommentContextOptional","useRef","useState","useEffect","selectUnresolvedThreads"],"mappings":"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"}
1
+ {"version":3,"file":"CommentSidePanel.cjs","sources":["../../src/comments/CommentSidePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type MouseEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiComment } from \"../icons\";\nimport { CommentThread } from \"./CommentThread\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport {\n useCommentContextOptional,\n type CommentContextValue,\n} from \"./comment-context\";\nimport {\n buildThreadListHandlers,\n getRoots,\n resolveCommentStage,\n selectAnchorThreads,\n selectCommentThreadsByStage,\n sortReplies,\n buildReplyMap,\n} from \"./comment-utils\";\nimport {\n DOCUMENT_ANCHOR,\n type Comment,\n type CommentAnchor,\n type CommentStatusStage,\n} from \"./comment-types\";\n\nexport type CommentSidePanelProps = {\n /** Explicit label per anchor key. */\n anchorLabels?: Record<CommentAnchor, string>;\n /** Fallback label formatter for anchors without an explicit label. */\n formatAnchorLabel?: (anchor: CommentAnchor) => string;\n compact?: boolean;\n /** Position the focused thread beside its registered content anchor. */\n focusedAlignment?: \"flow\" | \"anchor\";\n className?: string;\n /** Serialize one whole thread for Copy and its maximized Markdown tab. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n};\n\nfunction defaultAnchorLabel(anchor: CommentAnchor): string {\n if (anchor === DOCUMENT_ANCHOR) return \"General\";\n return anchor\n .replace(/\\[\\d+\\]/g, \"\")\n .replaceAll(\".\", \" › \")\n .replaceAll(\"_\", \" \")\n .trim();\n}\n\nfunction useAnchorLabel(props: CommentSidePanelProps) {\n return (anchor: CommentAnchor): string =>\n props.anchorLabels?.[anchor] ??\n props.formatAnchorLabel?.(anchor) ??\n defaultAnchorLabel(anchor);\n}\n\nfunction LocationMeta({ label }: { label: string }) {\n return (\n <span\n data-testid=\"comment-location-meta\"\n className=\"inline-flex items-center rounded-full bg-muted px-2 py-1 text-[10px] font-medium text-muted-foreground\"\n >\n {label}\n </span>\n );\n}\n\nfunction RailToggle({\n active,\n onClick,\n children,\n testId,\n}: {\n active?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n testId?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n data-testid={testId}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium shadow-sm transition-colors\",\n active\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n <Icon icon={UiComment} className=\"text-xs\" />\n {children}\n </button>\n );\n}\n\nfunction orderedAnchors(ctx: CommentContextValue): string[] {\n return Object.keys(ctx.commentMeta).sort((a, b) => {\n if (a === DOCUMENT_ANCHOR) return -1;\n if (b === DOCUMENT_ANCHOR) return 1;\n const at = ctx.getAnchorTop(a);\n const bt = ctx.getAnchorTop(b);\n if (at == null && bt == null) return a.localeCompare(b);\n if (at == null) return 1;\n if (bt == null) return -1;\n if (at !== bt) return at - bt;\n return a.localeCompare(b);\n });\n}\n\nfunction orderComments(\n comments: Comment[],\n anchorOrder: Map<string, number>,\n): Comment[] {\n const replyMap = buildReplyMap(comments);\n const roots = getRoots(comments).sort((a, b) => {\n const ai =\n anchorOrder.get(a.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n const bi =\n anchorOrder.get(b.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n if (ai !== bi) return ai - bi;\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n return roots.flatMap((root) => [\n root,\n ...sortReplies(replyMap.get(root.id) ?? []),\n ]);\n}\n\nfunction AllComments({\n ctx,\n comments,\n label,\n emptyLabel,\n threadToMarkdown,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n label: (a: CommentAnchor) => string;\n emptyLabel: string;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n}) {\n const ordered = useMemo(() => {\n const anchorOrder = new Map(orderedAnchors(ctx).map((a, i) => [a, i]));\n return orderComments(comments, anchorOrder);\n }, [ctx, comments]);\n const handlers = buildThreadListHandlers(ordered, ctx.config, ctx.callbacks);\n\n function activateThread(\n event: MouseEvent<HTMLDivElement>,\n anchor: CommentAnchor,\n ) {\n const target = event.target as Element;\n if (\n target.closest(\n 'button, a, input, textarea, select, [contenteditable=\"true\"]',\n )\n )\n return;\n const roleButton = target.closest('[role=\"button\"]');\n if (roleButton && roleButton !== target.closest(\"[data-comment-kind]\"))\n return;\n if (anchor !== DOCUMENT_ANCHOR) {\n const found = ctx.scrollToAnchor(anchor, {\n behavior: \"smooth\",\n block: \"start\",\n offset: 12,\n });\n if (!found) return;\n }\n ctx.focusAnchor(anchor);\n }\n\n if (ordered.length === 0) {\n return (\n <p className=\"rounded-lg bg-muted/40 px-3 py-2 text-xs text-muted-foreground\">\n {emptyLabel}\n </p>\n );\n }\n\n return (\n <div\n data-testid=\"comment-all-rail\"\n className=\"space-y-3 overflow-y-auto pr-1\"\n >\n <CommentThreadList\n comments={ordered}\n config={ctx.config}\n compact\n renderRootMeta={(c) => {\n const anchor = c.anchor ?? DOCUMENT_ANCHOR;\n const available =\n anchor === DOCUMENT_ANCHOR || ctx.getAnchorTop(anchor) != null;\n return (\n <LocationMeta\n label={`${label(anchor)}${available ? \"\" : \" · Unavailable\"}`}\n />\n );\n }}\n getThreadProps={(c) => ({\n \"data-testid\": \"comment-feed-item\",\n onMouseEnter: () =>\n ctx.setHighlightAnchor(c.anchor ?? DOCUMENT_ANCHOR),\n onMouseLeave: () => ctx.setHighlightAnchor(null),\n onClick: (event) =>\n activateThread(event, c.anchor ?? DOCUMENT_ANCHOR),\n })}\n {...handlers}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n />\n </div>\n );\n}\n\nfunction FocusedComments({\n ctx,\n comments: visible,\n anchor,\n label,\n compact,\n threadToMarkdown,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n anchor: CommentAnchor;\n label: string;\n compact?: boolean;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n}) {\n const comments = selectAnchorThreads(visible, anchor);\n const hasComments = comments.length > 0;\n return (\n <div className=\"space-y-3\" data-comment-anchor={anchor}>\n <LocationMeta\n label={\n anchor === DOCUMENT_ANCHOR\n ? \"Whole-page comment\"\n : `Attached to ${label}`\n }\n />\n <CommentThread\n comments={comments}\n config={ctx.config}\n anchor={anchor}\n compact={compact ?? false}\n autoFocusComposer={!hasComments}\n defaultExpanded={hasComments}\n composerPlaceholder={\n hasComments ? \"Add another top-level comment…\" : \"Add a comment…\"\n }\n {...ctx.callbacks}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n />\n </div>\n );\n}\n\n/**\n * A controlled comment rail driven by {@link CommentProvider}. Shows a focused\n * thread for the active anchor, the full document feed in anchor order, or a\n * toggle when collapsed. Renders nothing outside a provider or when empty.\n */\nexport function CommentSidePanel(props: CommentSidePanelProps) {\n const ctx = useCommentContextOptional();\n const label = useAnchorLabel(props);\n const railRef = useRef<HTMLElement>(null);\n const focusedRef = useRef<HTMLDivElement>(null);\n const [focusedOffset, setFocusedOffset] = useState<number | null>(null);\n const [stage, setStage] = useState<CommentStatusStage>(\"active\");\n const focusedAnchor = ctx?.railMode === \"focused\" ? ctx.focusedAnchor : null;\n\n useEffect(() => {\n if (!ctx || props.focusedAlignment !== \"anchor\" || !focusedAnchor) {\n setFocusedOffset(null);\n return;\n }\n const content = ctx.contentRef.current;\n const rail = railRef.current;\n const focused = focusedRef.current;\n if (!content || !rail || !focused) return;\n\n const update = () => {\n const anchorTop = ctx.getAnchorTop(focusedAnchor);\n if (anchorTop == null) {\n setFocusedOffset(null);\n return;\n }\n const desiredTop = anchorTop + content.getBoundingClientRect().top;\n const currentOffset = Number.parseFloat(focused.style.top) || 0;\n const nextOffset =\n currentOffset + desiredTop - focused.getBoundingClientRect().top;\n setFocusedOffset(Math.max(0, nextOffset));\n };\n update();\n content.addEventListener(\"scroll\", update, { passive: true });\n window.addEventListener(\"resize\", update);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n observer?.observe(content);\n observer?.observe(rail);\n return () => {\n content.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n observer?.disconnect();\n };\n }, [ctx, focusedAnchor, props.focusedAlignment]);\n\n if (!ctx) return null;\n\n // Threads, not cards: a reply is part of its root, never a separate entry.\n const counts = getRoots(ctx.comments).reduce(\n (result, root) => {\n const rootStage = resolveCommentStage(ctx.config, root.status);\n if (rootStage) result[rootStage] += 1;\n return result;\n },\n { active: 0, resolved: 0, closed: 0 },\n );\n const visible = selectCommentThreadsByStage(ctx.comments, ctx.config, stage);\n if (\n ctx.railMode === \"closed\" &&\n counts.active + counts.resolved + counts.closed === 0\n )\n return null;\n\n function selectStage(next: CommentStatusStage) {\n setStage(next);\n if (ctx?.railMode !== \"all\") ctx?.openCommentList();\n }\n\n return (\n <aside\n ref={railRef}\n data-testid=\"comment-side-panel\"\n className={cn(\"relative w-[320px] space-y-3\", props.className)}\n >\n <div\n data-testid=\"comment-rail-header\"\n className=\"sticky top-0 z-20 flex items-center gap-2 bg-background py-2\"\n >\n {(counts.active > 0 || ctx.railMode === \"all\") && (\n <RailToggle\n active={ctx.railMode === \"all\" && stage === \"active\"}\n onClick={() => selectStage(\"active\")}\n testId=\"comment-open-all\"\n >\n {ctx.railMode === \"closed\"\n ? `Open comments (${counts.active})`\n : ctx.railMode === \"focused\"\n ? `All comments (${counts.active})`\n : `Open (${counts.active})`}\n </RailToggle>\n )}\n {counts.resolved > 0 && (\n <button\n type=\"button\"\n aria-pressed={ctx.railMode === \"all\" && stage === \"resolved\"}\n onClick={() => selectStage(\"resolved\")}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n ctx.railMode === \"all\" && stage === \"resolved\"\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Resolved ({counts.resolved})\n </button>\n )}\n {counts.closed > 0 && (\n <button\n type=\"button\"\n aria-pressed={ctx.railMode === \"all\" && stage === \"closed\"}\n onClick={() => selectStage(\"closed\")}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n ctx.railMode === \"all\" && stage === \"closed\"\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Closed ({counts.closed})\n </button>\n )}\n {ctx.railMode !== \"closed\" && (\n <button\n type=\"button\"\n onClick={ctx.closeRail}\n className=\"ml-auto rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n {ctx.railMode === \"all\" ? \"Close\" : \"Hide\"}\n </button>\n )}\n </div>\n {ctx.railMode === \"all\" ? (\n <AllComments\n ctx={ctx}\n comments={visible}\n label={label}\n emptyLabel={\n stage === \"active\"\n ? \"No open comments.\"\n : stage === \"resolved\"\n ? \"No resolved comments.\"\n : \"No closed comments.\"\n }\n {...(props.threadToMarkdown\n ? { threadToMarkdown: props.threadToMarkdown }\n : {})}\n />\n ) : ctx.railMode === \"focused\" && ctx.focusedAnchor ? (\n <div\n ref={focusedRef}\n data-testid=\"comment-focused-rail\"\n className=\"relative space-y-3\"\n style={focusedOffset == null ? undefined : { top: focusedOffset }}\n >\n <FocusedComments\n ctx={ctx}\n comments={ctx.comments}\n anchor={ctx.focusedAnchor}\n label={label(ctx.focusedAnchor)}\n {...(props.compact !== undefined ? { compact: props.compact } : {})}\n {...(props.threadToMarkdown\n ? { threadToMarkdown: props.threadToMarkdown }\n : {})}\n />\n </div>\n ) : null}\n </aside>\n );\n}\n"],"names":["DOCUMENT_ANCHOR","jsx","jsxs","cn","Icon","UiComment","buildReplyMap","getRoots","sortReplies","useMemo","buildThreadListHandlers","CommentThreadList","selectAnchorThreads","CommentThread","useCommentContextOptional","useRef","useState","useEffect","resolveCommentStage","selectCommentThreadsByStage"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { CommentAnchor } from './comment-types';
1
+ import { Comment, CommentAnchor } from './comment-types';
2
2
  export type CommentSidePanelProps = {
3
3
  /** Explicit label per anchor key. */
4
4
  anchorLabels?: Record<CommentAnchor, string>;
@@ -8,6 +8,8 @@ export type CommentSidePanelProps = {
8
8
  /** Position the focused thread beside its registered content anchor. */
9
9
  focusedAlignment?: "flow" | "anchor";
10
10
  className?: string;
11
+ /** Serialize one whole thread for Copy and its maximized Markdown tab. */
12
+ threadToMarkdown?: (thread: readonly Comment[]) => string;
11
13
  };
12
14
  /**
13
15
  * A controlled comment rail driven by {@link CommentProvider}. Shows a focused
@@ -1 +1 @@
1
- {"version":3,"file":"CommentSidePanel.d.ts","sourceRoot":"","sources":["../../src/comments/CommentSidePanel.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAC7C,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAwNF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,sCA6H5D"}
1
+ {"version":3,"file":"CommentSidePanel.d.ts","sourceRoot":"","sources":["../../src/comments/CommentSidePanel.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,aAAa,EAEnB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAC7C,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,MAAM,CAAC;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;CAC3D,CAAC;AA8NF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,sCAwK5D"}
@@ -5,7 +5,7 @@ import { Icon } from "../data/Icon.js";
5
5
  import { CommentThread } from "./CommentThread.js";
6
6
  import { CommentThreadList } from "./CommentThreadList.js";
7
7
  import { useCommentContextOptional } from "./comment-context.js";
8
- import { selectUnresolvedThreads, getRoots, buildThreadListHandlers, selectAnchorThreads, buildReplyMap, sortReplies } from "./comment-utils.js";
8
+ import { getRoots, resolveCommentStage, selectCommentThreadsByStage, buildThreadListHandlers, selectAnchorThreads, buildReplyMap, sortReplies } from "./comment-utils.js";
9
9
  import { DOCUMENT_ANCHOR } from "./comment-types.js";
10
10
  import { UiComment } from "../icons/components/UiComment.js";
11
11
  function defaultAnchorLabel(anchor) {
@@ -83,7 +83,8 @@ function AllComments({
83
83
  ctx,
84
84
  comments,
85
85
  label,
86
- emptyLabel
86
+ emptyLabel,
87
+ threadToMarkdown
87
88
  }) {
88
89
  const ordered = useMemo(() => {
89
90
  const anchorOrder = new Map(orderedAnchors(ctx).map((a, i) => [a, i]));
@@ -139,7 +140,8 @@ function AllComments({
139
140
  onMouseLeave: () => ctx.setHighlightAnchor(null),
140
141
  onClick: (event) => activateThread(event, c.anchor ?? DOCUMENT_ANCHOR)
141
142
  }),
142
- ...handlers
143
+ ...handlers,
144
+ ...threadToMarkdown ? { threadToMarkdown } : {}
143
145
  }
144
146
  )
145
147
  }
@@ -150,7 +152,8 @@ function FocusedComments({
150
152
  comments: visible,
151
153
  anchor,
152
154
  label,
153
- compact
155
+ compact,
156
+ threadToMarkdown
154
157
  }) {
155
158
  const comments = selectAnchorThreads(visible, anchor);
156
159
  const hasComments = comments.length > 0;
@@ -171,7 +174,8 @@ function FocusedComments({
171
174
  autoFocusComposer: !hasComments,
172
175
  defaultExpanded: hasComments,
173
176
  composerPlaceholder: hasComments ? "Add another top-level comment…" : "Add a comment…",
174
- ...ctx.callbacks
177
+ ...ctx.callbacks,
178
+ ...threadToMarkdown ? { threadToMarkdown } : {}
175
179
  }
176
180
  )
177
181
  ] });
@@ -182,7 +186,7 @@ function CommentSidePanel(props) {
182
186
  const railRef = useRef(null);
183
187
  const focusedRef = useRef(null);
184
188
  const [focusedOffset, setFocusedOffset] = useState(null);
185
- const [showResolved, setShowResolved] = useState(false);
189
+ const [stage, setStage] = useState("active");
186
190
  const focusedAnchor = (ctx == null ? void 0 : ctx.railMode) === "focused" ? ctx.focusedAnchor : null;
187
191
  useEffect(() => {
188
192
  if (!ctx || props.focusedAlignment !== "anchor" || !focusedAnchor) {
@@ -217,11 +221,21 @@ function CommentSidePanel(props) {
217
221
  };
218
222
  }, [ctx, focusedAnchor, props.focusedAlignment]);
219
223
  if (!ctx) return null;
220
- const unresolved = selectUnresolvedThreads(ctx.comments, ctx.config);
221
- const visible = showResolved ? ctx.comments : unresolved;
222
- const total = getRoots(visible).length;
223
- const resolvedCount = getRoots(ctx.comments).length - getRoots(unresolved).length;
224
- if (ctx.railMode === "closed" && total === 0 && resolvedCount === 0) return null;
224
+ const counts = getRoots(ctx.comments).reduce(
225
+ (result, root) => {
226
+ const rootStage = resolveCommentStage(ctx.config, root.status);
227
+ if (rootStage) result[rootStage] += 1;
228
+ return result;
229
+ },
230
+ { active: 0, resolved: 0, closed: 0 }
231
+ );
232
+ const visible = selectCommentThreadsByStage(ctx.comments, ctx.config, stage);
233
+ if (ctx.railMode === "closed" && counts.active + counts.resolved + counts.closed === 0)
234
+ return null;
235
+ function selectStage(next) {
236
+ setStage(next);
237
+ if ((ctx == null ? void 0 : ctx.railMode) !== "all") ctx == null ? void 0 : ctx.openCommentList();
238
+ }
225
239
  return /* @__PURE__ */ jsxs(
226
240
  "aside",
227
241
  {
@@ -235,28 +249,45 @@ function CommentSidePanel(props) {
235
249
  "data-testid": "comment-rail-header",
236
250
  className: "sticky top-0 z-20 flex items-center gap-2 bg-background py-2",
237
251
  children: [
238
- (total > 0 || ctx.railMode === "all") && /* @__PURE__ */ jsx(
252
+ (counts.active > 0 || ctx.railMode === "all") && /* @__PURE__ */ jsx(
239
253
  RailToggle,
240
254
  {
241
- active: ctx.railMode === "all",
242
- onClick: ctx.openCommentList,
255
+ active: ctx.railMode === "all" && stage === "active",
256
+ onClick: () => selectStage("active"),
243
257
  testId: "comment-open-all",
244
- children: ctx.railMode === "closed" ? `Open comments (${total})` : `All comments (${total})`
258
+ children: ctx.railMode === "closed" ? `Open comments (${counts.active})` : ctx.railMode === "focused" ? `All comments (${counts.active})` : `Open (${counts.active})`
245
259
  }
246
260
  ),
247
- resolvedCount > 0 && /* @__PURE__ */ jsxs(
261
+ counts.resolved > 0 && /* @__PURE__ */ jsxs(
248
262
  "button",
249
263
  {
250
264
  type: "button",
251
- "aria-pressed": showResolved,
252
- onClick: () => setShowResolved((shown) => !shown),
265
+ "aria-pressed": ctx.railMode === "all" && stage === "resolved",
266
+ onClick: () => selectStage("resolved"),
253
267
  className: cn(
254
268
  "rounded-full border px-2 py-1 text-xs font-medium transition-colors",
255
- showResolved ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-muted-foreground hover:text-foreground"
269
+ ctx.railMode === "all" && stage === "resolved" ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-muted-foreground hover:text-foreground"
256
270
  ),
257
271
  children: [
258
272
  "Resolved (",
259
- resolvedCount,
273
+ counts.resolved,
274
+ ")"
275
+ ]
276
+ }
277
+ ),
278
+ counts.closed > 0 && /* @__PURE__ */ jsxs(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ "aria-pressed": ctx.railMode === "all" && stage === "closed",
283
+ onClick: () => selectStage("closed"),
284
+ className: cn(
285
+ "rounded-full border px-2 py-1 text-xs font-medium transition-colors",
286
+ ctx.railMode === "all" && stage === "closed" ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-muted-foreground hover:text-foreground"
287
+ ),
288
+ children: [
289
+ "Closed (",
290
+ counts.closed,
260
291
  ")"
261
292
  ]
262
293
  }
@@ -279,7 +310,8 @@ function CommentSidePanel(props) {
279
310
  ctx,
280
311
  comments: visible,
281
312
  label,
282
- emptyLabel: resolvedCount > 0 ? "No open comments." : "No comments yet."
313
+ emptyLabel: stage === "active" ? "No open comments." : stage === "resolved" ? "No resolved comments." : "No closed comments.",
314
+ ...props.threadToMarkdown ? { threadToMarkdown: props.threadToMarkdown } : {}
283
315
  }
284
316
  ) : ctx.railMode === "focused" && ctx.focusedAnchor ? /* @__PURE__ */ jsx(
285
317
  "div",
@@ -292,10 +324,11 @@ function CommentSidePanel(props) {
292
324
  FocusedComments,
293
325
  {
294
326
  ctx,
295
- comments: visible,
327
+ comments: ctx.comments,
296
328
  anchor: ctx.focusedAnchor,
297
329
  label: label(ctx.focusedAnchor),
298
- ...props.compact !== void 0 ? { compact: props.compact } : {}
330
+ ...props.compact !== void 0 ? { compact: props.compact } : {},
331
+ ...props.threadToMarkdown ? { threadToMarkdown: props.threadToMarkdown } : {}
299
332
  }
300
333
  )
301
334
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CommentSidePanel.js","sources":["../../src/comments/CommentSidePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type MouseEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiComment } from \"../icons\";\nimport { CommentThread } from \"./CommentThread\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport {\n useCommentContextOptional,\n type CommentContextValue,\n} from \"./comment-context\";\nimport {\n buildThreadListHandlers,\n getRoots,\n selectAnchorThreads,\n selectUnresolvedThreads,\n sortReplies,\n buildReplyMap,\n} from \"./comment-utils\";\nimport {\n DOCUMENT_ANCHOR,\n type Comment,\n type CommentAnchor,\n} from \"./comment-types\";\n\nexport type CommentSidePanelProps = {\n /** Explicit label per anchor key. */\n anchorLabels?: Record<CommentAnchor, string>;\n /** Fallback label formatter for anchors without an explicit label. */\n formatAnchorLabel?: (anchor: CommentAnchor) => string;\n compact?: boolean;\n /** Position the focused thread beside its registered content anchor. */\n focusedAlignment?: \"flow\" | \"anchor\";\n className?: string;\n};\n\nfunction defaultAnchorLabel(anchor: CommentAnchor): string {\n if (anchor === DOCUMENT_ANCHOR) return \"General\";\n return anchor\n .replace(/\\[\\d+\\]/g, \"\")\n .replaceAll(\".\", \" › \")\n .replaceAll(\"_\", \" \")\n .trim();\n}\n\nfunction useAnchorLabel(props: CommentSidePanelProps) {\n return (anchor: CommentAnchor): string =>\n props.anchorLabels?.[anchor] ??\n props.formatAnchorLabel?.(anchor) ??\n defaultAnchorLabel(anchor);\n}\n\nfunction LocationMeta({ label }: { label: string }) {\n return (\n <span\n data-testid=\"comment-location-meta\"\n className=\"inline-flex items-center rounded-full bg-muted px-2 py-1 text-[10px] font-medium text-muted-foreground\"\n >\n {label}\n </span>\n );\n}\n\nfunction RailToggle({\n active,\n onClick,\n children,\n testId,\n}: {\n active?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n testId?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n data-testid={testId}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium shadow-sm transition-colors\",\n active\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n <Icon icon={UiComment} className=\"text-xs\" />\n {children}\n </button>\n );\n}\n\nfunction orderedAnchors(ctx: CommentContextValue): string[] {\n return Object.keys(ctx.commentMeta).sort((a, b) => {\n if (a === DOCUMENT_ANCHOR) return -1;\n if (b === DOCUMENT_ANCHOR) return 1;\n const at = ctx.getAnchorTop(a);\n const bt = ctx.getAnchorTop(b);\n if (at == null && bt == null) return a.localeCompare(b);\n if (at == null) return 1;\n if (bt == null) return -1;\n if (at !== bt) return at - bt;\n return a.localeCompare(b);\n });\n}\n\nfunction orderComments(\n comments: Comment[],\n anchorOrder: Map<string, number>,\n): Comment[] {\n const replyMap = buildReplyMap(comments);\n const roots = getRoots(comments).sort((a, b) => {\n const ai =\n anchorOrder.get(a.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n const bi =\n anchorOrder.get(b.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n if (ai !== bi) return ai - bi;\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n return roots.flatMap((root) => [\n root,\n ...sortReplies(replyMap.get(root.id) ?? []),\n ]);\n}\n\nfunction AllComments({\n ctx,\n comments,\n label,\n emptyLabel,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n label: (a: CommentAnchor) => string;\n emptyLabel: string;\n}) {\n const ordered = useMemo(() => {\n const anchorOrder = new Map(orderedAnchors(ctx).map((a, i) => [a, i]));\n return orderComments(comments, anchorOrder);\n }, [ctx, comments]);\n const handlers = buildThreadListHandlers(ordered, ctx.config, ctx.callbacks);\n\n function activateThread(\n event: MouseEvent<HTMLDivElement>,\n anchor: CommentAnchor,\n ) {\n const target = event.target as Element;\n if (\n target.closest(\n 'button, a, input, textarea, select, [contenteditable=\"true\"]',\n )\n )\n return;\n const roleButton = target.closest('[role=\"button\"]');\n if (roleButton && roleButton !== target.closest(\"[data-comment-kind]\"))\n return;\n if (anchor !== DOCUMENT_ANCHOR) {\n const found = ctx.scrollToAnchor(anchor, {\n behavior: \"smooth\",\n block: \"start\",\n offset: 12,\n });\n if (!found) return;\n }\n ctx.focusAnchor(anchor);\n }\n\n if (ordered.length === 0) {\n return (\n <p className=\"rounded-lg bg-muted/40 px-3 py-2 text-xs text-muted-foreground\">\n {emptyLabel}\n </p>\n );\n }\n\n return (\n <div\n data-testid=\"comment-all-rail\"\n className=\"space-y-3 overflow-y-auto pr-1\"\n >\n <CommentThreadList\n comments={ordered}\n config={ctx.config}\n compact\n renderRootMeta={(c) => {\n const anchor = c.anchor ?? DOCUMENT_ANCHOR;\n const available =\n anchor === DOCUMENT_ANCHOR || ctx.getAnchorTop(anchor) != null;\n return (\n <LocationMeta\n label={`${label(anchor)}${available ? \"\" : \" · Unavailable\"}`}\n />\n );\n }}\n getThreadProps={(c) => ({\n \"data-testid\": \"comment-feed-item\",\n onMouseEnter: () =>\n ctx.setHighlightAnchor(c.anchor ?? DOCUMENT_ANCHOR),\n onMouseLeave: () => ctx.setHighlightAnchor(null),\n onClick: (event) =>\n activateThread(event, c.anchor ?? DOCUMENT_ANCHOR),\n })}\n {...handlers}\n />\n </div>\n );\n}\n\nfunction FocusedComments({\n ctx,\n comments: visible,\n anchor,\n label,\n compact,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n anchor: CommentAnchor;\n label: string;\n compact?: boolean;\n}) {\n const comments = selectAnchorThreads(visible, anchor);\n const hasComments = comments.length > 0;\n return (\n <div className=\"space-y-3\" data-comment-anchor={anchor}>\n <LocationMeta\n label={\n anchor === DOCUMENT_ANCHOR\n ? \"Whole-page comment\"\n : `Attached to ${label}`\n }\n />\n <CommentThread\n comments={comments}\n config={ctx.config}\n anchor={anchor}\n compact={compact ?? false}\n autoFocusComposer={!hasComments}\n defaultExpanded={hasComments}\n composerPlaceholder={\n hasComments ? \"Add another top-level comment…\" : \"Add a comment…\"\n }\n {...ctx.callbacks}\n />\n </div>\n );\n}\n\n/**\n * A controlled comment rail driven by {@link CommentProvider}. Shows a focused\n * thread for the active anchor, the full document feed in anchor order, or a\n * toggle when collapsed. Renders nothing outside a provider or when empty.\n */\nexport function CommentSidePanel(props: CommentSidePanelProps) {\n const ctx = useCommentContextOptional();\n const label = useAnchorLabel(props);\n const railRef = useRef<HTMLElement>(null);\n const focusedRef = useRef<HTMLDivElement>(null);\n const [focusedOffset, setFocusedOffset] = useState<number | null>(null);\n const [showResolved, setShowResolved] = useState(false);\n const focusedAnchor = ctx?.railMode === \"focused\" ? ctx.focusedAnchor : null;\n\n useEffect(() => {\n if (!ctx || props.focusedAlignment !== \"anchor\" || !focusedAnchor) {\n setFocusedOffset(null);\n return;\n }\n const content = ctx.contentRef.current;\n const rail = railRef.current;\n const focused = focusedRef.current;\n if (!content || !rail || !focused) return;\n\n const update = () => {\n const anchorTop = ctx.getAnchorTop(focusedAnchor);\n if (anchorTop == null) {\n setFocusedOffset(null);\n return;\n }\n const desiredTop = anchorTop + content.getBoundingClientRect().top;\n const currentOffset = Number.parseFloat(focused.style.top) || 0;\n const nextOffset =\n currentOffset + desiredTop - focused.getBoundingClientRect().top;\n setFocusedOffset(Math.max(0, nextOffset));\n };\n update();\n content.addEventListener(\"scroll\", update, { passive: true });\n window.addEventListener(\"resize\", update);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n observer?.observe(content);\n observer?.observe(rail);\n return () => {\n content.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n observer?.disconnect();\n };\n }, [ctx, focusedAnchor, props.focusedAlignment]);\n\n if (!ctx) return null;\n\n // Threads, not cards: a reply is part of its root, never a separate entry.\n const unresolved = selectUnresolvedThreads(ctx.comments, ctx.config);\n const visible = showResolved ? ctx.comments : unresolved;\n const total = getRoots(visible).length;\n const resolvedCount = getRoots(ctx.comments).length - getRoots(unresolved).length;\n if (ctx.railMode === \"closed\" && total === 0 && resolvedCount === 0) return null;\n\n return (\n <aside\n ref={railRef}\n data-testid=\"comment-side-panel\"\n className={cn(\"relative w-[320px] space-y-3\", props.className)}\n >\n <div\n data-testid=\"comment-rail-header\"\n className=\"sticky top-0 z-20 flex items-center gap-2 bg-background py-2\"\n >\n {(total > 0 || ctx.railMode === \"all\") && (\n <RailToggle\n active={ctx.railMode === \"all\"}\n onClick={ctx.openCommentList}\n testId=\"comment-open-all\"\n >\n {ctx.railMode === \"closed\"\n ? `Open comments (${total})`\n : `All comments (${total})`}\n </RailToggle>\n )}\n {resolvedCount > 0 && (\n <button\n type=\"button\"\n aria-pressed={showResolved}\n onClick={() => setShowResolved((shown) => !shown)}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n showResolved\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Resolved ({resolvedCount})\n </button>\n )}\n {ctx.railMode !== \"closed\" && (\n <button\n type=\"button\"\n onClick={ctx.closeRail}\n className=\"ml-auto rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n {ctx.railMode === \"all\" ? \"Close\" : \"Hide\"}\n </button>\n )}\n </div>\n {ctx.railMode === \"all\" ? (\n <AllComments\n ctx={ctx}\n comments={visible}\n label={label}\n emptyLabel={resolvedCount > 0 ? \"No open comments.\" : \"No comments yet.\"}\n />\n ) : ctx.railMode === \"focused\" && ctx.focusedAnchor ? (\n <div\n ref={focusedRef}\n data-testid=\"comment-focused-rail\"\n className=\"relative space-y-3\"\n style={focusedOffset == null ? undefined : { top: focusedOffset }}\n >\n <FocusedComments\n ctx={ctx}\n comments={visible}\n anchor={ctx.focusedAnchor}\n label={label(ctx.focusedAnchor)}\n {...(props.compact !== undefined ? { compact: props.compact } : {})}\n />\n </div>\n ) : null}\n </aside>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CommentSidePanel.js","sources":["../../src/comments/CommentSidePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type MouseEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiComment } from \"../icons\";\nimport { CommentThread } from \"./CommentThread\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport {\n useCommentContextOptional,\n type CommentContextValue,\n} from \"./comment-context\";\nimport {\n buildThreadListHandlers,\n getRoots,\n resolveCommentStage,\n selectAnchorThreads,\n selectCommentThreadsByStage,\n sortReplies,\n buildReplyMap,\n} from \"./comment-utils\";\nimport {\n DOCUMENT_ANCHOR,\n type Comment,\n type CommentAnchor,\n type CommentStatusStage,\n} from \"./comment-types\";\n\nexport type CommentSidePanelProps = {\n /** Explicit label per anchor key. */\n anchorLabels?: Record<CommentAnchor, string>;\n /** Fallback label formatter for anchors without an explicit label. */\n formatAnchorLabel?: (anchor: CommentAnchor) => string;\n compact?: boolean;\n /** Position the focused thread beside its registered content anchor. */\n focusedAlignment?: \"flow\" | \"anchor\";\n className?: string;\n /** Serialize one whole thread for Copy and its maximized Markdown tab. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n};\n\nfunction defaultAnchorLabel(anchor: CommentAnchor): string {\n if (anchor === DOCUMENT_ANCHOR) return \"General\";\n return anchor\n .replace(/\\[\\d+\\]/g, \"\")\n .replaceAll(\".\", \" › \")\n .replaceAll(\"_\", \" \")\n .trim();\n}\n\nfunction useAnchorLabel(props: CommentSidePanelProps) {\n return (anchor: CommentAnchor): string =>\n props.anchorLabels?.[anchor] ??\n props.formatAnchorLabel?.(anchor) ??\n defaultAnchorLabel(anchor);\n}\n\nfunction LocationMeta({ label }: { label: string }) {\n return (\n <span\n data-testid=\"comment-location-meta\"\n className=\"inline-flex items-center rounded-full bg-muted px-2 py-1 text-[10px] font-medium text-muted-foreground\"\n >\n {label}\n </span>\n );\n}\n\nfunction RailToggle({\n active,\n onClick,\n children,\n testId,\n}: {\n active?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n testId?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n data-testid={testId}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium shadow-sm transition-colors\",\n active\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n <Icon icon={UiComment} className=\"text-xs\" />\n {children}\n </button>\n );\n}\n\nfunction orderedAnchors(ctx: CommentContextValue): string[] {\n return Object.keys(ctx.commentMeta).sort((a, b) => {\n if (a === DOCUMENT_ANCHOR) return -1;\n if (b === DOCUMENT_ANCHOR) return 1;\n const at = ctx.getAnchorTop(a);\n const bt = ctx.getAnchorTop(b);\n if (at == null && bt == null) return a.localeCompare(b);\n if (at == null) return 1;\n if (bt == null) return -1;\n if (at !== bt) return at - bt;\n return a.localeCompare(b);\n });\n}\n\nfunction orderComments(\n comments: Comment[],\n anchorOrder: Map<string, number>,\n): Comment[] {\n const replyMap = buildReplyMap(comments);\n const roots = getRoots(comments).sort((a, b) => {\n const ai =\n anchorOrder.get(a.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n const bi =\n anchorOrder.get(b.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n if (ai !== bi) return ai - bi;\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n return roots.flatMap((root) => [\n root,\n ...sortReplies(replyMap.get(root.id) ?? []),\n ]);\n}\n\nfunction AllComments({\n ctx,\n comments,\n label,\n emptyLabel,\n threadToMarkdown,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n label: (a: CommentAnchor) => string;\n emptyLabel: string;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n}) {\n const ordered = useMemo(() => {\n const anchorOrder = new Map(orderedAnchors(ctx).map((a, i) => [a, i]));\n return orderComments(comments, anchorOrder);\n }, [ctx, comments]);\n const handlers = buildThreadListHandlers(ordered, ctx.config, ctx.callbacks);\n\n function activateThread(\n event: MouseEvent<HTMLDivElement>,\n anchor: CommentAnchor,\n ) {\n const target = event.target as Element;\n if (\n target.closest(\n 'button, a, input, textarea, select, [contenteditable=\"true\"]',\n )\n )\n return;\n const roleButton = target.closest('[role=\"button\"]');\n if (roleButton && roleButton !== target.closest(\"[data-comment-kind]\"))\n return;\n if (anchor !== DOCUMENT_ANCHOR) {\n const found = ctx.scrollToAnchor(anchor, {\n behavior: \"smooth\",\n block: \"start\",\n offset: 12,\n });\n if (!found) return;\n }\n ctx.focusAnchor(anchor);\n }\n\n if (ordered.length === 0) {\n return (\n <p className=\"rounded-lg bg-muted/40 px-3 py-2 text-xs text-muted-foreground\">\n {emptyLabel}\n </p>\n );\n }\n\n return (\n <div\n data-testid=\"comment-all-rail\"\n className=\"space-y-3 overflow-y-auto pr-1\"\n >\n <CommentThreadList\n comments={ordered}\n config={ctx.config}\n compact\n renderRootMeta={(c) => {\n const anchor = c.anchor ?? DOCUMENT_ANCHOR;\n const available =\n anchor === DOCUMENT_ANCHOR || ctx.getAnchorTop(anchor) != null;\n return (\n <LocationMeta\n label={`${label(anchor)}${available ? \"\" : \" · Unavailable\"}`}\n />\n );\n }}\n getThreadProps={(c) => ({\n \"data-testid\": \"comment-feed-item\",\n onMouseEnter: () =>\n ctx.setHighlightAnchor(c.anchor ?? DOCUMENT_ANCHOR),\n onMouseLeave: () => ctx.setHighlightAnchor(null),\n onClick: (event) =>\n activateThread(event, c.anchor ?? DOCUMENT_ANCHOR),\n })}\n {...handlers}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n />\n </div>\n );\n}\n\nfunction FocusedComments({\n ctx,\n comments: visible,\n anchor,\n label,\n compact,\n threadToMarkdown,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n anchor: CommentAnchor;\n label: string;\n compact?: boolean;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n}) {\n const comments = selectAnchorThreads(visible, anchor);\n const hasComments = comments.length > 0;\n return (\n <div className=\"space-y-3\" data-comment-anchor={anchor}>\n <LocationMeta\n label={\n anchor === DOCUMENT_ANCHOR\n ? \"Whole-page comment\"\n : `Attached to ${label}`\n }\n />\n <CommentThread\n comments={comments}\n config={ctx.config}\n anchor={anchor}\n compact={compact ?? false}\n autoFocusComposer={!hasComments}\n defaultExpanded={hasComments}\n composerPlaceholder={\n hasComments ? \"Add another top-level comment…\" : \"Add a comment…\"\n }\n {...ctx.callbacks}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n />\n </div>\n );\n}\n\n/**\n * A controlled comment rail driven by {@link CommentProvider}. Shows a focused\n * thread for the active anchor, the full document feed in anchor order, or a\n * toggle when collapsed. Renders nothing outside a provider or when empty.\n */\nexport function CommentSidePanel(props: CommentSidePanelProps) {\n const ctx = useCommentContextOptional();\n const label = useAnchorLabel(props);\n const railRef = useRef<HTMLElement>(null);\n const focusedRef = useRef<HTMLDivElement>(null);\n const [focusedOffset, setFocusedOffset] = useState<number | null>(null);\n const [stage, setStage] = useState<CommentStatusStage>(\"active\");\n const focusedAnchor = ctx?.railMode === \"focused\" ? ctx.focusedAnchor : null;\n\n useEffect(() => {\n if (!ctx || props.focusedAlignment !== \"anchor\" || !focusedAnchor) {\n setFocusedOffset(null);\n return;\n }\n const content = ctx.contentRef.current;\n const rail = railRef.current;\n const focused = focusedRef.current;\n if (!content || !rail || !focused) return;\n\n const update = () => {\n const anchorTop = ctx.getAnchorTop(focusedAnchor);\n if (anchorTop == null) {\n setFocusedOffset(null);\n return;\n }\n const desiredTop = anchorTop + content.getBoundingClientRect().top;\n const currentOffset = Number.parseFloat(focused.style.top) || 0;\n const nextOffset =\n currentOffset + desiredTop - focused.getBoundingClientRect().top;\n setFocusedOffset(Math.max(0, nextOffset));\n };\n update();\n content.addEventListener(\"scroll\", update, { passive: true });\n window.addEventListener(\"resize\", update);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n observer?.observe(content);\n observer?.observe(rail);\n return () => {\n content.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n observer?.disconnect();\n };\n }, [ctx, focusedAnchor, props.focusedAlignment]);\n\n if (!ctx) return null;\n\n // Threads, not cards: a reply is part of its root, never a separate entry.\n const counts = getRoots(ctx.comments).reduce(\n (result, root) => {\n const rootStage = resolveCommentStage(ctx.config, root.status);\n if (rootStage) result[rootStage] += 1;\n return result;\n },\n { active: 0, resolved: 0, closed: 0 },\n );\n const visible = selectCommentThreadsByStage(ctx.comments, ctx.config, stage);\n if (\n ctx.railMode === \"closed\" &&\n counts.active + counts.resolved + counts.closed === 0\n )\n return null;\n\n function selectStage(next: CommentStatusStage) {\n setStage(next);\n if (ctx?.railMode !== \"all\") ctx?.openCommentList();\n }\n\n return (\n <aside\n ref={railRef}\n data-testid=\"comment-side-panel\"\n className={cn(\"relative w-[320px] space-y-3\", props.className)}\n >\n <div\n data-testid=\"comment-rail-header\"\n className=\"sticky top-0 z-20 flex items-center gap-2 bg-background py-2\"\n >\n {(counts.active > 0 || ctx.railMode === \"all\") && (\n <RailToggle\n active={ctx.railMode === \"all\" && stage === \"active\"}\n onClick={() => selectStage(\"active\")}\n testId=\"comment-open-all\"\n >\n {ctx.railMode === \"closed\"\n ? `Open comments (${counts.active})`\n : ctx.railMode === \"focused\"\n ? `All comments (${counts.active})`\n : `Open (${counts.active})`}\n </RailToggle>\n )}\n {counts.resolved > 0 && (\n <button\n type=\"button\"\n aria-pressed={ctx.railMode === \"all\" && stage === \"resolved\"}\n onClick={() => selectStage(\"resolved\")}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n ctx.railMode === \"all\" && stage === \"resolved\"\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Resolved ({counts.resolved})\n </button>\n )}\n {counts.closed > 0 && (\n <button\n type=\"button\"\n aria-pressed={ctx.railMode === \"all\" && stage === \"closed\"}\n onClick={() => selectStage(\"closed\")}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n ctx.railMode === \"all\" && stage === \"closed\"\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Closed ({counts.closed})\n </button>\n )}\n {ctx.railMode !== \"closed\" && (\n <button\n type=\"button\"\n onClick={ctx.closeRail}\n className=\"ml-auto rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n {ctx.railMode === \"all\" ? \"Close\" : \"Hide\"}\n </button>\n )}\n </div>\n {ctx.railMode === \"all\" ? (\n <AllComments\n ctx={ctx}\n comments={visible}\n label={label}\n emptyLabel={\n stage === \"active\"\n ? \"No open comments.\"\n : stage === \"resolved\"\n ? \"No resolved comments.\"\n : \"No closed comments.\"\n }\n {...(props.threadToMarkdown\n ? { threadToMarkdown: props.threadToMarkdown }\n : {})}\n />\n ) : ctx.railMode === \"focused\" && ctx.focusedAnchor ? (\n <div\n ref={focusedRef}\n data-testid=\"comment-focused-rail\"\n className=\"relative space-y-3\"\n style={focusedOffset == null ? undefined : { top: focusedOffset }}\n >\n <FocusedComments\n ctx={ctx}\n comments={ctx.comments}\n anchor={ctx.focusedAnchor}\n label={label(ctx.focusedAnchor)}\n {...(props.compact !== undefined ? { compact: props.compact } : {})}\n {...(props.threadToMarkdown\n ? { threadToMarkdown: props.threadToMarkdown }\n : {})}\n />\n </div>\n ) : null}\n </aside>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -17,6 +17,7 @@ function CommentThread({
17
17
  hideComposer = false,
18
18
  renderRootMeta,
19
19
  renderBody,
20
+ threadToMarkdown,
20
21
  onCreate,
21
22
  onReply,
22
23
  onUpdateStatus,
@@ -41,6 +42,7 @@ function CommentThread({
41
42
  defaultExpanded,
42
43
  ...renderRootMeta ? { renderRootMeta } : {},
43
44
  ...renderBody ? { renderBody } : {},
45
+ ...threadToMarkdown ? { threadToMarkdown } : {},
44
46
  ...listHandlers
45
47
  }
46
48
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThread.cjs","sources":["../../src/comments/CommentThread.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { CommentComposer } from \"./CommentComposer\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport { buildThreadListHandlers } from \"./comment-utils\";\nimport type {\n Comment,\n CommentAnchor,\n CommentCallbacks,\n CommentConfig,\n} from \"./comment-types\";\n\nexport type CommentThreadProps = CommentCallbacks & {\n /** Comments for this thread (typically pre-filtered to one anchor). */\n comments: Comment[];\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n compact?: boolean;\n /** Auto-open and focus the composer on mount. */\n autoFocusComposer?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Label for creating a separate root comment. */\n composerPlaceholder?: string;\n /** Hide the composer (read-only thread). */\n hideComposer?: boolean;\n renderRootMeta?: (comment: Comment) => ReactNode;\n renderBody?: (body: string) => ReactNode;\n};\n\n/**\n * Top-level controlled comment thread: a threaded list plus a mention-aware\n * composer. Owns no data — it renders `comments` and forwards every mutation to\n * the supplied callbacks, translating list-level events (checklist index, reply\n * target) into the callback contract.\n */\nexport function CommentThread({\n comments,\n config,\n anchor,\n compact = false,\n autoFocusComposer = false,\n defaultExpanded = false,\n composerPlaceholder,\n hideComposer = false,\n renderRootMeta,\n renderBody,\n onCreate,\n onReply,\n onUpdateStatus,\n onDelete,\n onChecklistToggle,\n onMention,\n}: CommentThreadProps) {\n const listHandlers = buildThreadListHandlers(comments, config, {\n ...(onUpdateStatus ? { onUpdateStatus } : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onChecklistToggle ? { onChecklistToggle } : {}),\n ...(onReply ? { onReply } : {}),\n ...(onMention ? { onMention } : {}),\n });\n\n return (\n <div className={cn(compact && \"p-2\")}>\n <CommentThreadList\n comments={comments}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n {...(renderRootMeta ? { renderRootMeta } : {})}\n {...(renderBody ? { renderBody } : {})}\n {...listHandlers}\n />\n {!hideComposer && onCreate && (\n <div className=\"pt-1\">\n <CommentComposer\n config={config}\n anchor={anchor ?? null}\n autoFocus={autoFocusComposer}\n {...(composerPlaceholder ? { placeholder: composerPlaceholder } : {})}\n onCreate={onCreate}\n {...(onMention ? { onMention } : {})}\n />\n </div>\n )}\n </div>\n );\n}\n"],"names":["buildThreadListHandlers","cn","jsx","CommentThreadList","CommentComposer"],"mappings":";;;;;;;;AAqCO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,eAAeA,aAAAA,wBAAwB,UAAU,QAAQ;AAAA,IAC7D,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,IAC1C,GAAI,WAAW,EAAE,SAAA,IAAa,CAAA;AAAA,IAC9B,GAAI,oBAAoB,EAAE,kBAAA,IAAsB,CAAA;AAAA,IAChD,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA;AAAA,IAC5B,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,EAAC,CAClC;AAED,yCACG,OAAA,EAAI,WAAWC,MAAAA,GAAG,WAAW,KAAK,GACjC,UAAA;AAAA,IAAAC,2BAAAA;AAAAA,MAACC,kBAAAA;AAAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,QAC1C,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,QAClC,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,CAAC,gBAAgB,YAChBD,+BAAC,OAAA,EAAI,WAAU,QACb,UAAAA,2BAAAA;AAAAA,MAACE,gBAAAA;AAAAA,MAAA;AAAA,QACC;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,WAAW;AAAA,QACV,GAAI,sBAAsB,EAAE,aAAa,oBAAA,IAAwB,CAAA;AAAA,QAClE;AAAA,QACC,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,MAAC;AAAA,IAAA,EACpC,CACF;AAAA,EAAA,GAEJ;AAEJ;;"}
1
+ {"version":3,"file":"CommentThread.cjs","sources":["../../src/comments/CommentThread.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { CommentComposer } from \"./CommentComposer\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport { buildThreadListHandlers } from \"./comment-utils\";\nimport type {\n Comment,\n CommentAnchor,\n CommentCallbacks,\n CommentConfig,\n} from \"./comment-types\";\n\nexport type CommentThreadProps = CommentCallbacks & {\n /** Comments for this thread (typically pre-filtered to one anchor). */\n comments: Comment[];\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n compact?: boolean;\n /** Auto-open and focus the composer on mount. */\n autoFocusComposer?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Label for creating a separate root comment. */\n composerPlaceholder?: string;\n /** Hide the composer (read-only thread). */\n hideComposer?: boolean;\n renderRootMeta?: (comment: Comment) => ReactNode;\n renderBody?: (body: string) => ReactNode;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n};\n\n/**\n * Top-level controlled comment thread: a threaded list plus a mention-aware\n * composer. Owns no data — it renders `comments` and forwards every mutation to\n * the supplied callbacks, translating list-level events (checklist index, reply\n * target) into the callback contract.\n */\nexport function CommentThread({\n comments,\n config,\n anchor,\n compact = false,\n autoFocusComposer = false,\n defaultExpanded = false,\n composerPlaceholder,\n hideComposer = false,\n renderRootMeta,\n renderBody,\n threadToMarkdown,\n onCreate,\n onReply,\n onUpdateStatus,\n onDelete,\n onChecklistToggle,\n onMention,\n}: CommentThreadProps) {\n const listHandlers = buildThreadListHandlers(comments, config, {\n ...(onUpdateStatus ? { onUpdateStatus } : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onChecklistToggle ? { onChecklistToggle } : {}),\n ...(onReply ? { onReply } : {}),\n ...(onMention ? { onMention } : {}),\n });\n\n return (\n <div className={cn(compact && \"p-2\")}>\n <CommentThreadList\n comments={comments}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n {...(renderRootMeta ? { renderRootMeta } : {})}\n {...(renderBody ? { renderBody } : {})}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n {...listHandlers}\n />\n {!hideComposer && onCreate && (\n <div className=\"pt-1\">\n <CommentComposer\n config={config}\n anchor={anchor ?? null}\n autoFocus={autoFocusComposer}\n {...(composerPlaceholder\n ? { placeholder: composerPlaceholder }\n : {})}\n onCreate={onCreate}\n {...(onMention ? { onMention } : {})}\n />\n </div>\n )}\n </div>\n );\n}\n"],"names":["buildThreadListHandlers","cn","jsx","CommentThreadList","CommentComposer"],"mappings":";;;;;;;;AAsCO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,eAAeA,aAAAA,wBAAwB,UAAU,QAAQ;AAAA,IAC7D,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,IAC1C,GAAI,WAAW,EAAE,SAAA,IAAa,CAAA;AAAA,IAC9B,GAAI,oBAAoB,EAAE,kBAAA,IAAsB,CAAA;AAAA,IAChD,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA;AAAA,IAC5B,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,EAAC,CAClC;AAED,yCACG,OAAA,EAAI,WAAWC,MAAAA,GAAG,WAAW,KAAK,GACjC,UAAA;AAAA,IAAAC,2BAAAA;AAAAA,MAACC,kBAAAA;AAAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,QAC1C,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,QAClC,GAAI,mBAAmB,EAAE,iBAAA,IAAqB,CAAA;AAAA,QAC9C,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,CAAC,gBAAgB,YAChBD,+BAAC,OAAA,EAAI,WAAU,QACb,UAAAA,2BAAAA;AAAAA,MAACE,gBAAAA;AAAAA,MAAA;AAAA,QACC;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,WAAW;AAAA,QACV,GAAI,sBACD,EAAE,aAAa,oBAAA,IACf,CAAA;AAAA,QACJ;AAAA,QACC,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,MAAC;AAAA,IAAA,EACpC,CACF;AAAA,EAAA,GAEJ;AAEJ;;"}
@@ -17,6 +17,7 @@ export type CommentThreadProps = CommentCallbacks & {
17
17
  hideComposer?: boolean;
18
18
  renderRootMeta?: (comment: Comment) => ReactNode;
19
19
  renderBody?: (body: string) => ReactNode;
20
+ threadToMarkdown?: (thread: readonly Comment[]) => string;
20
21
  };
21
22
  /**
22
23
  * Top-level controlled comment thread: a threaded list plus a mention-aware
@@ -24,5 +25,5 @@ export type CommentThreadProps = CommentCallbacks & {
24
25
  * the supplied callbacks, translating list-level events (checklist index, reply
25
26
  * target) into the callback contract.
26
27
  */
27
- export declare function CommentThread({ comments, config, anchor, compact, autoFocusComposer, defaultExpanded, composerPlaceholder, hideComposer, renderRootMeta, renderBody, onCreate, onReply, onUpdateStatus, onDelete, onChecklistToggle, onMention, }: CommentThreadProps): import("react").JSX.Element;
28
+ export declare function CommentThread({ comments, config, anchor, compact, autoFocusComposer, defaultExpanded, composerPlaceholder, hideComposer, renderRootMeta, renderBody, threadToMarkdown, onCreate, onReply, onUpdateStatus, onDelete, onChecklistToggle, onMention, }: CommentThreadProps): import("react").JSX.Element;
28
29
  //# sourceMappingURL=CommentThread.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThread.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThread.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,gBAAgB,EAChB,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;IAClD,uEAAuE;IACvE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kDAAkD;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;CAC1C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAe,EACf,iBAAyB,EACzB,eAAuB,EACvB,mBAAmB,EACnB,YAAoB,EACpB,cAAc,EACd,UAAU,EACV,QAAQ,EACR,OAAO,EACP,cAAc,EACd,QAAQ,EACR,iBAAiB,EACjB,SAAS,GACV,EAAE,kBAAkB,+BAkCpB"}
1
+ {"version":3,"file":"CommentThread.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThread.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,gBAAgB,EAChB,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;IAClD,uEAAuE;IACvE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kDAAkD;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;CAC3D,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAe,EACf,iBAAyB,EACzB,eAAuB,EACvB,mBAAmB,EACnB,YAAoB,EACpB,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,QAAQ,EACR,OAAO,EACP,cAAc,EACd,QAAQ,EACR,iBAAiB,EACjB,SAAS,GACV,EAAE,kBAAkB,+BAqCpB"}
@@ -15,6 +15,7 @@ function CommentThread({
15
15
  hideComposer = false,
16
16
  renderRootMeta,
17
17
  renderBody,
18
+ threadToMarkdown,
18
19
  onCreate,
19
20
  onReply,
20
21
  onUpdateStatus,
@@ -39,6 +40,7 @@ function CommentThread({
39
40
  defaultExpanded,
40
41
  ...renderRootMeta ? { renderRootMeta } : {},
41
42
  ...renderBody ? { renderBody } : {},
43
+ ...threadToMarkdown ? { threadToMarkdown } : {},
42
44
  ...listHandlers
43
45
  }
44
46
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThread.js","sources":["../../src/comments/CommentThread.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { CommentComposer } from \"./CommentComposer\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport { buildThreadListHandlers } from \"./comment-utils\";\nimport type {\n Comment,\n CommentAnchor,\n CommentCallbacks,\n CommentConfig,\n} from \"./comment-types\";\n\nexport type CommentThreadProps = CommentCallbacks & {\n /** Comments for this thread (typically pre-filtered to one anchor). */\n comments: Comment[];\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n compact?: boolean;\n /** Auto-open and focus the composer on mount. */\n autoFocusComposer?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Label for creating a separate root comment. */\n composerPlaceholder?: string;\n /** Hide the composer (read-only thread). */\n hideComposer?: boolean;\n renderRootMeta?: (comment: Comment) => ReactNode;\n renderBody?: (body: string) => ReactNode;\n};\n\n/**\n * Top-level controlled comment thread: a threaded list plus a mention-aware\n * composer. Owns no data — it renders `comments` and forwards every mutation to\n * the supplied callbacks, translating list-level events (checklist index, reply\n * target) into the callback contract.\n */\nexport function CommentThread({\n comments,\n config,\n anchor,\n compact = false,\n autoFocusComposer = false,\n defaultExpanded = false,\n composerPlaceholder,\n hideComposer = false,\n renderRootMeta,\n renderBody,\n onCreate,\n onReply,\n onUpdateStatus,\n onDelete,\n onChecklistToggle,\n onMention,\n}: CommentThreadProps) {\n const listHandlers = buildThreadListHandlers(comments, config, {\n ...(onUpdateStatus ? { onUpdateStatus } : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onChecklistToggle ? { onChecklistToggle } : {}),\n ...(onReply ? { onReply } : {}),\n ...(onMention ? { onMention } : {}),\n });\n\n return (\n <div className={cn(compact && \"p-2\")}>\n <CommentThreadList\n comments={comments}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n {...(renderRootMeta ? { renderRootMeta } : {})}\n {...(renderBody ? { renderBody } : {})}\n {...listHandlers}\n />\n {!hideComposer && onCreate && (\n <div className=\"pt-1\">\n <CommentComposer\n config={config}\n anchor={anchor ?? null}\n autoFocus={autoFocusComposer}\n {...(composerPlaceholder ? { placeholder: composerPlaceholder } : {})}\n onCreate={onCreate}\n {...(onMention ? { onMention } : {})}\n />\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AAqCO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,eAAe,wBAAwB,UAAU,QAAQ;AAAA,IAC7D,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,IAC1C,GAAI,WAAW,EAAE,SAAA,IAAa,CAAA;AAAA,IAC9B,GAAI,oBAAoB,EAAE,kBAAA,IAAsB,CAAA;AAAA,IAChD,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA;AAAA,IAC5B,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,EAAC,CAClC;AAED,8BACG,OAAA,EAAI,WAAW,GAAG,WAAW,KAAK,GACjC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,QAC1C,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,QAClC,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,CAAC,gBAAgB,YAChB,oBAAC,OAAA,EAAI,WAAU,QACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,WAAW;AAAA,QACV,GAAI,sBAAsB,EAAE,aAAa,oBAAA,IAAwB,CAAA;AAAA,QAClE;AAAA,QACC,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,MAAC;AAAA,IAAA,EACpC,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
1
+ {"version":3,"file":"CommentThread.js","sources":["../../src/comments/CommentThread.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { CommentComposer } from \"./CommentComposer\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport { buildThreadListHandlers } from \"./comment-utils\";\nimport type {\n Comment,\n CommentAnchor,\n CommentCallbacks,\n CommentConfig,\n} from \"./comment-types\";\n\nexport type CommentThreadProps = CommentCallbacks & {\n /** Comments for this thread (typically pre-filtered to one anchor). */\n comments: Comment[];\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n compact?: boolean;\n /** Auto-open and focus the composer on mount. */\n autoFocusComposer?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Label for creating a separate root comment. */\n composerPlaceholder?: string;\n /** Hide the composer (read-only thread). */\n hideComposer?: boolean;\n renderRootMeta?: (comment: Comment) => ReactNode;\n renderBody?: (body: string) => ReactNode;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n};\n\n/**\n * Top-level controlled comment thread: a threaded list plus a mention-aware\n * composer. Owns no data — it renders `comments` and forwards every mutation to\n * the supplied callbacks, translating list-level events (checklist index, reply\n * target) into the callback contract.\n */\nexport function CommentThread({\n comments,\n config,\n anchor,\n compact = false,\n autoFocusComposer = false,\n defaultExpanded = false,\n composerPlaceholder,\n hideComposer = false,\n renderRootMeta,\n renderBody,\n threadToMarkdown,\n onCreate,\n onReply,\n onUpdateStatus,\n onDelete,\n onChecklistToggle,\n onMention,\n}: CommentThreadProps) {\n const listHandlers = buildThreadListHandlers(comments, config, {\n ...(onUpdateStatus ? { onUpdateStatus } : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onChecklistToggle ? { onChecklistToggle } : {}),\n ...(onReply ? { onReply } : {}),\n ...(onMention ? { onMention } : {}),\n });\n\n return (\n <div className={cn(compact && \"p-2\")}>\n <CommentThreadList\n comments={comments}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n {...(renderRootMeta ? { renderRootMeta } : {})}\n {...(renderBody ? { renderBody } : {})}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n {...listHandlers}\n />\n {!hideComposer && onCreate && (\n <div className=\"pt-1\">\n <CommentComposer\n config={config}\n anchor={anchor ?? null}\n autoFocus={autoFocusComposer}\n {...(composerPlaceholder\n ? { placeholder: composerPlaceholder }\n : {})}\n onCreate={onCreate}\n {...(onMention ? { onMention } : {})}\n />\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AAsCO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,eAAe,wBAAwB,UAAU,QAAQ;AAAA,IAC7D,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,IAC1C,GAAI,WAAW,EAAE,SAAA,IAAa,CAAA;AAAA,IAC9B,GAAI,oBAAoB,EAAE,kBAAA,IAAsB,CAAA;AAAA,IAChD,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA;AAAA,IAC5B,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,EAAC,CAClC;AAED,8BACG,OAAA,EAAI,WAAW,GAAG,WAAW,KAAK,GACjC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,QAC1C,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,QAClC,GAAI,mBAAmB,EAAE,iBAAA,IAAqB,CAAA;AAAA,QAC9C,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,CAAC,gBAAgB,YAChB,oBAAC,OAAA,EAAI,WAAU,QACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,WAAW;AAAA,QACV,GAAI,sBACD,EAAE,aAAa,oBAAA,IACf,CAAA;AAAA,QACJ;AAAA,QACC,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,MAAC;AAAA,IAAA,EACpC,CACF;AAAA,EAAA,GAEJ;AAEJ;"}