@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
@@ -4,6 +4,9 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
5
  const utils = require("../lib/utils.cjs");
6
6
  const button = require("../components/button.cjs");
7
+ const clipboard = require("../components/clipboard.cjs");
8
+ const Markdown = require("../data/Markdown.cjs");
9
+ const Tabs = require("../layout/Tabs.cjs");
7
10
  const Modal = require("../overlay/Modal.cjs");
8
11
  const CommentCard = require("./CommentCard.cjs");
9
12
  const commentUtils = require("./comment-utils.cjs");
@@ -66,25 +69,33 @@ function CommentThreadList({
66
69
  renderRootMeta,
67
70
  getThreadProps,
68
71
  renderBody,
72
+ threadToMarkdown,
69
73
  onUpdateStatus,
74
+ onClose,
70
75
  onChecklistToggle,
71
76
  onDelete,
72
77
  onReply
73
78
  }) {
74
79
  const [replyingTo, setReplyingTo] = react.useState(null);
75
80
  const [maximizedRootId, setMaximizedRootId] = react.useState(null);
81
+ const [maximizedTab, setMaximizedTab] = react.useState(
82
+ "thread"
83
+ );
76
84
  const [modalReplyTo, setModalReplyTo] = react.useState(null);
77
85
  const roots = commentUtils.getRoots(comments);
78
86
  const replyMap = commentUtils.buildReplyMap(comments);
79
87
  if (roots.length === 0) return null;
80
88
  const maximizedRoot = roots.find((root) => root.id === maximizedRootId);
89
+ const maximizedThread = maximizedRoot ? [maximizedRoot, ...commentUtils.sortReplies(replyMap.get(maximizedRoot.id) ?? [])] : [];
81
90
  function closeMaximized() {
82
91
  setMaximizedRootId(null);
83
92
  setModalReplyTo(null);
93
+ setMaximizedTab("thread");
84
94
  }
85
95
  function cardHandlers(id) {
86
96
  return {
87
97
  ...onUpdateStatus ? { onUpdateStatus: (status) => onUpdateStatus(id, status) } : {},
98
+ ...onClose ? { onClose: () => onClose(id) } : {},
88
99
  ...onChecklistToggle ? { onChecklistToggle: (index) => onChecklistToggle(id, index) } : {},
89
100
  ...onDelete ? { onDelete: () => onDelete(id) } : {}
90
101
  };
@@ -92,6 +103,7 @@ function CommentThreadList({
92
103
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
93
104
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: roots.map((root) => {
94
105
  const replies = commentUtils.sortReplies(replyMap.get(root.id) ?? []);
106
+ const thread = [root, ...replies];
95
107
  const threadProps = (getThreadProps == null ? void 0 : getThreadProps(root)) ?? {};
96
108
  return /* @__PURE__ */ jsxRuntime.jsxs(
97
109
  "div",
@@ -110,6 +122,7 @@ function CommentThreadList({
110
122
  compact,
111
123
  defaultExpanded,
112
124
  onMaximize: () => setMaximizedRootId(root.id),
125
+ ...threadToMarkdown ? { onCopy: () => clipboard.copyText(threadToMarkdown(thread)) } : {},
113
126
  ...renderBody ? { renderBody } : {},
114
127
  ...cardHandlers(root.id),
115
128
  ...onReply ? { onReply: () => setReplyingTo(root.id) } : {}
@@ -123,6 +136,7 @@ function CommentThreadList({
123
136
  compact,
124
137
  defaultExpanded,
125
138
  onMaximize: () => setMaximizedRootId(root.id),
139
+ ...threadToMarkdown ? { onCopy: () => clipboard.copyText(threadToMarkdown(thread)) } : {},
126
140
  ...renderBody ? { renderBody } : {},
127
141
  ...cardHandlers(reply.id)
128
142
  },
@@ -143,41 +157,45 @@ function CommentThreadList({
143
157
  root.id
144
158
  );
145
159
  }) }),
146
- maximizedRoot && /* @__PURE__ */ jsxRuntime.jsx(
147
- Modal.Modal,
148
- {
149
- open: true,
150
- onClose: closeMaximized,
151
- title: "Comment thread",
152
- size: "lg",
153
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", "data-testid": "comment-thread-modal", children: [
154
- maximizedRoot,
155
- ...commentUtils.sortReplies(replyMap.get(maximizedRoot.id) ?? [])
156
- ].map((message) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
157
- /* @__PURE__ */ jsxRuntime.jsx(
158
- CommentCard.CommentCard,
159
- {
160
- comment: message,
161
- config,
162
- defaultExpanded: true,
163
- ...renderBody ? { renderBody } : {},
164
- ...cardHandlers(message.id),
165
- ...onReply ? { onReply: () => setModalReplyTo(message.id) } : {}
166
- }
167
- ),
168
- onReply && modalReplyTo === message.id && /* @__PURE__ */ jsxRuntime.jsx(
169
- ReplyInput,
170
- {
171
- onCancel: () => setModalReplyTo(null),
172
- onSubmit: async (body) => {
173
- await onReply(maximizedRoot, body);
174
- setModalReplyTo(null);
175
- }
160
+ maximizedRoot && /* @__PURE__ */ jsxRuntime.jsx(Modal.Modal, { open: true, onClose: closeMaximized, title: "Comment thread", size: "lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", "data-testid": "comment-thread-modal", children: [
161
+ threadToMarkdown && /* @__PURE__ */ jsxRuntime.jsx(
162
+ Tabs.Tabs,
163
+ {
164
+ tabs: [
165
+ { id: "thread", label: "Thread" },
166
+ { id: "markdown", label: "Markdown" }
167
+ ],
168
+ value: maximizedTab,
169
+ onChange: (tab) => setMaximizedTab(tab === "markdown" ? "markdown" : "thread")
170
+ }
171
+ ),
172
+ maximizedTab === "markdown" && threadToMarkdown ? /* @__PURE__ */ jsxRuntime.jsx(Markdown.Markdown, { text: threadToMarkdown(maximizedThread) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: maximizedThread.map((message) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
173
+ /* @__PURE__ */ jsxRuntime.jsx(
174
+ CommentCard.CommentCard,
175
+ {
176
+ comment: message,
177
+ config,
178
+ defaultExpanded: true,
179
+ ...threadToMarkdown ? {
180
+ onCopy: () => clipboard.copyText(threadToMarkdown(maximizedThread))
181
+ } : {},
182
+ ...renderBody ? { renderBody } : {},
183
+ ...cardHandlers(message.id),
184
+ ...onReply ? { onReply: () => setModalReplyTo(message.id) } : {}
185
+ }
186
+ ),
187
+ onReply && modalReplyTo === message.id && /* @__PURE__ */ jsxRuntime.jsx(
188
+ ReplyInput,
189
+ {
190
+ onCancel: () => setModalReplyTo(null),
191
+ onSubmit: async (body) => {
192
+ await onReply(maximizedRoot, body);
193
+ setModalReplyTo(null);
176
194
  }
177
- )
178
- ] }, message.id)) })
179
- }
180
- )
195
+ }
196
+ )
197
+ ] }, message.id)) })
198
+ ] }) })
181
199
  ] });
182
200
  }
183
201
  exports.CommentThreadList = CommentThreadList;
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n onUpdateStatus,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal\n open\n onClose={closeMaximized}\n title=\"Comment thread\"\n size=\"lg\"\n >\n {/* No onMaximize inside: the thread is already maximized. */}\n <div className=\"space-y-2\" data-testid=\"comment-thread-modal\">\n {[\n maximizedRoot,\n ...sortReplies(replyMap.get(maximizedRoot.id) ?? []),\n ].map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n // Always the root: the store nests messages under a\n // comment, never under another message.\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","Fragment","sortReplies","cn","CommentCard","Modal"],"mappings":"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"}
1
+ {"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","sortReplies","Fragment","cn","CommentCard","copyText","Modal","Tabs","Markdown"],"mappings":"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"}
@@ -12,7 +12,10 @@ export type CommentThreadListProps = {
12
12
  getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;
13
13
  /** Custom body renderer forwarded to each card. */
14
14
  renderBody?: (body: string) => ReactNode;
15
- onUpdateStatus?: (id: string, status: string) => void;
15
+ /** Serialize a root and its replies for Copy and the Markdown preview. */
16
+ threadToMarkdown?: (thread: readonly Comment[]) => string;
17
+ onUpdateStatus?: (id: string, status: string) => void | Promise<void>;
18
+ onClose?: (id: string) => void | Promise<void>;
16
19
  onChecklistToggle?: (id: string, index: number) => void;
17
20
  onDelete?: (id: string) => void;
18
21
  /** Submit a reply to `parent`. Presence enables the inline reply affordance. */
@@ -23,5 +26,5 @@ export type CommentThreadListProps = {
23
26
  * optional inline reply box. Fully controlled — mutations route through the
24
27
  * id-keyed callbacks.
25
28
  */
26
- export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, getThreadProps, renderBody, onUpdateStatus, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
29
+ export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, getThreadProps, renderBody, threadToMarkdown, onUpdateStatus, onClose, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
27
30
  //# sourceMappingURL=CommentThreadList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAsDF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,OAAe,EACf,eAAuB,EACvB,cAAc,EACd,cAAc,EACd,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,EAAE,sBAAsB,sCAiIxB"}
1
+ {"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAsDF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,OAAe,EACf,eAAuB,EACvB,cAAc,EACd,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,EAAE,sBAAsB,sCA8JxB"}
@@ -2,6 +2,9 @@ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
2
  import { useState, useRef } from "react";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { Button } from "../components/button.js";
5
+ import { copyText } from "../components/clipboard.js";
6
+ import { Markdown } from "../data/Markdown.js";
7
+ import { Tabs } from "../layout/Tabs.js";
5
8
  import { Modal } from "../overlay/Modal.js";
6
9
  import { CommentCard } from "./CommentCard.js";
7
10
  import { getRoots, buildReplyMap, sortReplies } from "./comment-utils.js";
@@ -64,25 +67,33 @@ function CommentThreadList({
64
67
  renderRootMeta,
65
68
  getThreadProps,
66
69
  renderBody,
70
+ threadToMarkdown,
67
71
  onUpdateStatus,
72
+ onClose,
68
73
  onChecklistToggle,
69
74
  onDelete,
70
75
  onReply
71
76
  }) {
72
77
  const [replyingTo, setReplyingTo] = useState(null);
73
78
  const [maximizedRootId, setMaximizedRootId] = useState(null);
79
+ const [maximizedTab, setMaximizedTab] = useState(
80
+ "thread"
81
+ );
74
82
  const [modalReplyTo, setModalReplyTo] = useState(null);
75
83
  const roots = getRoots(comments);
76
84
  const replyMap = buildReplyMap(comments);
77
85
  if (roots.length === 0) return null;
78
86
  const maximizedRoot = roots.find((root) => root.id === maximizedRootId);
87
+ const maximizedThread = maximizedRoot ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])] : [];
79
88
  function closeMaximized() {
80
89
  setMaximizedRootId(null);
81
90
  setModalReplyTo(null);
91
+ setMaximizedTab("thread");
82
92
  }
83
93
  function cardHandlers(id) {
84
94
  return {
85
95
  ...onUpdateStatus ? { onUpdateStatus: (status) => onUpdateStatus(id, status) } : {},
96
+ ...onClose ? { onClose: () => onClose(id) } : {},
86
97
  ...onChecklistToggle ? { onChecklistToggle: (index) => onChecklistToggle(id, index) } : {},
87
98
  ...onDelete ? { onDelete: () => onDelete(id) } : {}
88
99
  };
@@ -90,6 +101,7 @@ function CommentThreadList({
90
101
  return /* @__PURE__ */ jsxs(Fragment, { children: [
91
102
  /* @__PURE__ */ jsx("div", { className: "space-y-2", children: roots.map((root) => {
92
103
  const replies = sortReplies(replyMap.get(root.id) ?? []);
104
+ const thread = [root, ...replies];
93
105
  const threadProps = (getThreadProps == null ? void 0 : getThreadProps(root)) ?? {};
94
106
  return /* @__PURE__ */ jsxs(
95
107
  "div",
@@ -108,6 +120,7 @@ function CommentThreadList({
108
120
  compact,
109
121
  defaultExpanded,
110
122
  onMaximize: () => setMaximizedRootId(root.id),
123
+ ...threadToMarkdown ? { onCopy: () => copyText(threadToMarkdown(thread)) } : {},
111
124
  ...renderBody ? { renderBody } : {},
112
125
  ...cardHandlers(root.id),
113
126
  ...onReply ? { onReply: () => setReplyingTo(root.id) } : {}
@@ -121,6 +134,7 @@ function CommentThreadList({
121
134
  compact,
122
135
  defaultExpanded,
123
136
  onMaximize: () => setMaximizedRootId(root.id),
137
+ ...threadToMarkdown ? { onCopy: () => copyText(threadToMarkdown(thread)) } : {},
124
138
  ...renderBody ? { renderBody } : {},
125
139
  ...cardHandlers(reply.id)
126
140
  },
@@ -141,41 +155,45 @@ function CommentThreadList({
141
155
  root.id
142
156
  );
143
157
  }) }),
144
- maximizedRoot && /* @__PURE__ */ jsx(
145
- Modal,
146
- {
147
- open: true,
148
- onClose: closeMaximized,
149
- title: "Comment thread",
150
- size: "lg",
151
- children: /* @__PURE__ */ jsx("div", { className: "space-y-2", "data-testid": "comment-thread-modal", children: [
152
- maximizedRoot,
153
- ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])
154
- ].map((message) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
155
- /* @__PURE__ */ jsx(
156
- CommentCard,
157
- {
158
- comment: message,
159
- config,
160
- defaultExpanded: true,
161
- ...renderBody ? { renderBody } : {},
162
- ...cardHandlers(message.id),
163
- ...onReply ? { onReply: () => setModalReplyTo(message.id) } : {}
164
- }
165
- ),
166
- onReply && modalReplyTo === message.id && /* @__PURE__ */ jsx(
167
- ReplyInput,
168
- {
169
- onCancel: () => setModalReplyTo(null),
170
- onSubmit: async (body) => {
171
- await onReply(maximizedRoot, body);
172
- setModalReplyTo(null);
173
- }
158
+ maximizedRoot && /* @__PURE__ */ jsx(Modal, { open: true, onClose: closeMaximized, title: "Comment thread", size: "lg", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", "data-testid": "comment-thread-modal", children: [
159
+ threadToMarkdown && /* @__PURE__ */ jsx(
160
+ Tabs,
161
+ {
162
+ tabs: [
163
+ { id: "thread", label: "Thread" },
164
+ { id: "markdown", label: "Markdown" }
165
+ ],
166
+ value: maximizedTab,
167
+ onChange: (tab) => setMaximizedTab(tab === "markdown" ? "markdown" : "thread")
168
+ }
169
+ ),
170
+ maximizedTab === "markdown" && threadToMarkdown ? /* @__PURE__ */ jsx(Markdown, { text: threadToMarkdown(maximizedThread) }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: maximizedThread.map((message) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
171
+ /* @__PURE__ */ jsx(
172
+ CommentCard,
173
+ {
174
+ comment: message,
175
+ config,
176
+ defaultExpanded: true,
177
+ ...threadToMarkdown ? {
178
+ onCopy: () => copyText(threadToMarkdown(maximizedThread))
179
+ } : {},
180
+ ...renderBody ? { renderBody } : {},
181
+ ...cardHandlers(message.id),
182
+ ...onReply ? { onReply: () => setModalReplyTo(message.id) } : {}
183
+ }
184
+ ),
185
+ onReply && modalReplyTo === message.id && /* @__PURE__ */ jsx(
186
+ ReplyInput,
187
+ {
188
+ onCancel: () => setModalReplyTo(null),
189
+ onSubmit: async (body) => {
190
+ await onReply(maximizedRoot, body);
191
+ setModalReplyTo(null);
174
192
  }
175
- )
176
- ] }, message.id)) })
177
- }
178
- )
193
+ }
194
+ )
195
+ ] }, message.id)) })
196
+ ] }) })
179
197
  ] });
180
198
  }
181
199
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n onUpdateStatus,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal\n open\n onClose={closeMaximized}\n title=\"Comment thread\"\n size=\"lg\"\n >\n {/* No onMaximize inside: the thread is already maximized. */}\n <div className=\"space-y-2\" data-testid=\"comment-thread-modal\">\n {[\n maximizedRoot,\n ...sortReplies(replyMap.get(maximizedRoot.id) ?? []),\n ].map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n // Always the root: the store nests messages under a\n // comment, never under another message.\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -2,15 +2,27 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const DOCUMENT_ANCHOR = "__document__";
4
4
  const DEFAULT_COMMENT_STATUSES = [
5
- { value: "open", label: "Open", tone: "info", unresolved: true },
5
+ {
6
+ value: "open",
7
+ label: "Open",
8
+ tone: "info",
9
+ unresolved: true,
10
+ stage: "active"
11
+ },
6
12
  {
7
13
  value: "in_progress",
8
14
  label: "In progress",
9
15
  tone: "warning",
10
- unresolved: true
16
+ unresolved: true,
17
+ stage: "active"
18
+ },
19
+ {
20
+ value: "resolved",
21
+ label: "Resolved",
22
+ tone: "success",
23
+ stage: "resolved"
11
24
  },
12
- { value: "resolved", label: "Resolved", tone: "success" },
13
- { value: "closed", label: "Closed", tone: "neutral" }
25
+ { value: "closed", label: "Closed", tone: "neutral", stage: "closed" }
14
26
  ];
15
27
  const DEFAULT_CHECKLIST_CYCLE = ["open", "in_progress", "done"];
16
28
  exports.DEFAULT_CHECKLIST_CYCLE = DEFAULT_CHECKLIST_CYCLE;
@@ -1 +1 @@
1
- {"version":3,"file":"comment-types.cjs","sources":["../../src/comments/comment-types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { StaticIconComponent } from \"../data/Icon\";\n\n/** Semantic tone driving a status/facet color (maps onto Badge tones). */\nexport type CommentTone =\n | \"default\"\n | \"info\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"neutral\";\n\n/** A single selectable status (e.g. \"open\", \"resolved\"). Consumer-defined. */\nexport type CommentStatusConfig = {\n /** Stable value stored on a comment's `status`. */\n value: string;\n /** Human label shown in menus and badges. */\n label: string;\n /** Color tone for the status chip. */\n tone?: CommentTone;\n /** Optional leading glyph. */\n icon?: string | StaticIconComponent;\n /**\n * Whether this status counts as \"unresolved/open\" — drives progress tallies\n * and which groups start expanded. Defaults to false.\n */\n unresolved?: boolean;\n};\n\n/** One option of a generic facet (e.g. a \"category\" value). */\nexport type CommentFacetOption = {\n value: string;\n label: string;\n tone?: CommentTone;\n /** Compact glyph for the collapsed card (e.g. \"BS\" for a category). */\n short?: string;\n};\n\n/**\n * A generic, consumer-supplied classification axis. Replaces the financial\n * category/severity vocabulary — any app declares its own facets.\n */\nexport type CommentFacet = {\n /** Key into `Comment.facets`. */\n key: string;\n label: string;\n options: CommentFacetOption[];\n /** Allow several values to be selected at once in the filter bar. */\n multiple?: boolean;\n};\n\nexport type CommentMentionKind = \"user\" | \"agent\";\n\n/** A person or agent that can be @-mentioned in the composer. */\nexport type CommentMentionable = {\n id: string;\n name: string;\n /** Image URL for `kind: \"user\"`. */\n avatar?: string;\n kind: CommentMentionKind;\n /** Glyph override for `kind: \"agent\"`. */\n icon?: string | StaticIconComponent;\n};\n\n/** A resolved mention emitted when a comment referencing a mentionable is posted. */\nexport type CommentMention = {\n id: string;\n name: string;\n kind: CommentMentionKind;\n};\n\n/** A labelled group of reference chips (e.g. linked ids). */\nexport type CommentRefGroup = {\n label: string;\n items: string[];\n /** Render items in a monospace font. */\n mono?: boolean;\n};\n\n/** One item of a generic checklist; `status` cycles through `checklistStatusCycle`. */\nexport type CommentChecklistItem = {\n label: string;\n status: string;\n};\n\nexport type CommentAuthor = {\n id?: string;\n name: string;\n kind?: CommentMentionKind;\n avatar?: string;\n /** Glyph override for `kind: \"agent\"` authors. */\n icon?: string | StaticIconComponent;\n};\n\n/**\n * An opaque anchor key locating a comment within a document. Not a JSON path —\n * any stable string a consumer registers via the provider. `null`/absent means\n * document-level.\n */\nexport type CommentAnchor = string;\n\n/** Binary review signal attached to a root comment. */\nexport type CommentRating = \"positive\" | \"negative\";\n\n/** Anchor key reserved for document-level (un-anchored) comments. */\nexport const DOCUMENT_ANCHOR = \"__document__\";\n\n/** A single comment. Replies set `parentId`; roots leave it null. */\nexport type Comment = {\n id: string;\n body: string;\n /** ISO-8601 creation time. */\n createdAt: string;\n /** ISO-8601 last-update time. */\n updatedAt?: string;\n author: CommentAuthor | null;\n /** Key into the configured statuses; roots only. */\n status?: string;\n /** Parent comment id when this is a reply. */\n parentId?: string | null;\n /** Where the comment is anchored; null/absent = document-level. */\n anchor?: CommentAnchor | null;\n /** Optional positive/negative review signal; roots only. */\n rating?: CommentRating;\n /** facet.key → selected option value. */\n facets?: Record<string, string>;\n refs?: CommentRefGroup[];\n checklist?: CommentChecklistItem[];\n};\n\n/** Payload for creating a new root comment. */\nexport type CommentCreateInput = {\n body: string;\n anchor?: CommentAnchor | null;\n rating?: CommentRating;\n facets?: Record<string, string>;\n mentions?: CommentMention[];\n};\n\n/** Payload for replying to a comment. */\nexport type CommentReplyInput = {\n parentId: string;\n body: string;\n anchor?: CommentAnchor | null;\n mentions?: CommentMention[];\n};\n\n/**\n * Controlled callbacks. The components own no data fetching; the consumer\n * performs the mutation and feeds the updated `comments` back in.\n */\nexport type CommentCallbacks = {\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n onReply?: (input: CommentReplyInput) => void | Promise<void>;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;\n onDelete?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (\n id: string,\n index: number,\n next: string,\n ) => void | Promise<void>;\n /** Fired when a posted comment references a mentionable. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\n/** Static configuration shared across the comment components. */\nexport type CommentConfig = {\n /** Selectable statuses; the first `unresolved` one is the default for new comments. */\n statuses: CommentStatusConfig[];\n /** Classification axes (category/severity/etc.). */\n facets?: CommentFacet[];\n /** People/agents available for @-mention. */\n mentionables?: CommentMentionable[];\n /** Status values a checklist item cycles through on click. */\n checklistStatusCycle?: string[];\n};\n\n/** Aggregate metadata for one anchor, derived from the comment list. */\nexport type CommentAnchorMeta = {\n count: number;\n authors: string[];\n latestStatus?: string;\n};\n\n/** Selected filter state for the filter bar / `applyCommentFilters`. */\nexport type CommentFilters = {\n /** Selected status values; empty = all statuses. */\n statuses: Set<string>;\n /** facet.key → selected option values; empty set = all values for that facet. */\n facets: Record<string, Set<string>>;\n /** Restrict to an author kind, or \"all\". */\n authorKind?: CommentMentionKind | \"all\";\n};\n\n/** Optional renderers passed down to a card. */\nexport type CommentRenderOptions = {\n /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n};\n\n/** A domain-free default status set: open / in progress / resolved. */\nexport const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[] = [\n { value: \"open\", label: \"Open\", tone: \"info\", unresolved: true },\n {\n value: \"in_progress\",\n label: \"In progress\",\n tone: \"warning\",\n unresolved: true,\n },\n { value: \"resolved\", label: \"Resolved\", tone: \"success\" },\n { value: \"closed\", label: \"Closed\", tone: \"neutral\" },\n];\n\n/** Default checklist status cycle used when a consumer omits one. */\nexport const DEFAULT_CHECKLIST_CYCLE = [\"open\", \"in_progress\", \"done\"];\n"],"names":[],"mappings":";;AAyGO,MAAM,kBAAkB;AAiGxB,MAAM,2BAAkD;AAAA,EAC7D,EAAE,OAAO,QAAQ,OAAO,QAAQ,MAAM,QAAQ,YAAY,KAAA;AAAA,EAC1D;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,EAAA;AAAA,EAEd,EAAE,OAAO,YAAY,OAAO,YAAY,MAAM,UAAA;AAAA,EAC9C,EAAE,OAAO,UAAU,OAAO,UAAU,MAAM,UAAA;AAC5C;AAGO,MAAM,0BAA0B,CAAC,QAAQ,eAAe,MAAM;;;;"}
1
+ {"version":3,"file":"comment-types.cjs","sources":["../../src/comments/comment-types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { StaticIconComponent } from \"../data/Icon\";\n\n/** Semantic tone driving a status/facet color (maps onto Badge tones). */\nexport type CommentTone =\n | \"default\"\n | \"info\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"neutral\";\n\n/** Product lifecycle stage represented by one or more stored statuses. */\nexport type CommentStatusStage = \"active\" | \"resolved\" | \"closed\";\n\n/** A single selectable status (e.g. \"open\", \"resolved\"). Consumer-defined. */\nexport type CommentStatusConfig = {\n /** Stable value stored on a comment's `status`. */\n value: string;\n /** Human label shown in menus and badges. */\n label: string;\n /** Color tone for the status chip. */\n tone?: CommentTone;\n /** Optional leading glyph. */\n icon?: string | StaticIconComponent;\n /** Lifecycle stage used by review queues and status actions. */\n stage?: CommentStatusStage;\n /**\n * Whether this status counts as \"unresolved/open\" — drives progress tallies\n * and which groups start expanded. Defaults to false.\n */\n unresolved?: boolean;\n};\n\n/** One option of a generic facet (e.g. a \"category\" value). */\nexport type CommentFacetOption = {\n value: string;\n label: string;\n tone?: CommentTone;\n /** Compact glyph for the collapsed card (e.g. \"BS\" for a category). */\n short?: string;\n};\n\n/**\n * A generic, consumer-supplied classification axis. Replaces the financial\n * category/severity vocabulary — any app declares its own facets.\n */\nexport type CommentFacet = {\n /** Key into `Comment.facets`. */\n key: string;\n label: string;\n options: CommentFacetOption[];\n /** Allow several values to be selected at once in the filter bar. */\n multiple?: boolean;\n};\n\nexport type CommentMentionKind = \"user\" | \"agent\";\n\n/** A person or agent that can be @-mentioned in the composer. */\nexport type CommentMentionable = {\n id: string;\n name: string;\n /** Image URL for `kind: \"user\"`. */\n avatar?: string;\n kind: CommentMentionKind;\n /** Glyph override for `kind: \"agent\"`. */\n icon?: string | StaticIconComponent;\n};\n\n/** A resolved mention emitted when a comment referencing a mentionable is posted. */\nexport type CommentMention = {\n id: string;\n name: string;\n kind: CommentMentionKind;\n};\n\n/** A labelled group of reference chips (e.g. linked ids). */\nexport type CommentRefGroup = {\n label: string;\n items: string[];\n /** Render items in a monospace font. */\n mono?: boolean;\n};\n\n/** One item of a generic checklist; `status` cycles through `checklistStatusCycle`. */\nexport type CommentChecklistItem = {\n label: string;\n status: string;\n};\n\nexport type CommentAuthor = {\n id?: string;\n name: string;\n kind?: CommentMentionKind;\n avatar?: string;\n /** Glyph override for `kind: \"agent\"` authors. */\n icon?: string | StaticIconComponent;\n};\n\n/**\n * An opaque anchor key locating a comment within a document. Not a JSON path —\n * any stable string a consumer registers via the provider. `null`/absent means\n * document-level.\n */\nexport type CommentAnchor = string;\n\n/** Binary review signal attached to a root comment. */\nexport type CommentRating = \"positive\" | \"negative\";\n\n/** Anchor key reserved for document-level (un-anchored) comments. */\nexport const DOCUMENT_ANCHOR = \"__document__\";\n\n/** A single comment. Replies set `parentId`; roots leave it null. */\nexport type Comment = {\n id: string;\n body: string;\n /** ISO-8601 creation time. */\n createdAt: string;\n /** ISO-8601 last-update time. */\n updatedAt?: string;\n author: CommentAuthor | null;\n /** Key into the configured statuses; roots only. */\n status?: string;\n /** Parent comment id when this is a reply. */\n parentId?: string | null;\n /** Where the comment is anchored; null/absent = document-level. */\n anchor?: CommentAnchor | null;\n /** Optional positive/negative review signal; roots only. */\n rating?: CommentRating;\n /** ISO-8601 time when a human closed the resolved comment. */\n closedAt?: string;\n /** Human who closed the comment. */\n closedBy?: CommentAuthor;\n /** facet.key → selected option value. */\n facets?: Record<string, string>;\n refs?: CommentRefGroup[];\n checklist?: CommentChecklistItem[];\n};\n\n/** Payload for creating a new root comment. */\nexport type CommentCreateInput = {\n body: string;\n anchor?: CommentAnchor | null;\n rating?: CommentRating;\n facets?: Record<string, string>;\n mentions?: CommentMention[];\n};\n\n/** Payload for replying to a comment. */\nexport type CommentReplyInput = {\n parentId: string;\n body: string;\n anchor?: CommentAnchor | null;\n mentions?: CommentMention[];\n};\n\n/**\n * Controlled callbacks. The components own no data fetching; the consumer\n * performs the mutation and feeds the updated `comments` back in.\n */\nexport type CommentCallbacks = {\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n onReply?: (input: CommentReplyInput) => void | Promise<void>;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n /** Human-only transition from resolved to closed. */\n onClose?: (id: string) => void | Promise<void>;\n onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;\n onDelete?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (\n id: string,\n index: number,\n next: string,\n ) => void | Promise<void>;\n /** Fired when a posted comment references a mentionable. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\n/** Static configuration shared across the comment components. */\nexport type CommentConfig = {\n /** Selectable statuses; the first `unresolved` one is the default for new comments. */\n statuses: CommentStatusConfig[];\n /** Classification axes (category/severity/etc.). */\n facets?: CommentFacet[];\n /** People/agents available for @-mention. */\n mentionables?: CommentMentionable[];\n /** Status values a checklist item cycles through on click. */\n checklistStatusCycle?: string[];\n};\n\n/** Aggregate metadata for one anchor, derived from the comment list. */\nexport type CommentAnchorMeta = {\n count: number;\n authors: string[];\n latestStatus?: string;\n};\n\n/** Selected filter state for the filter bar / `applyCommentFilters`. */\nexport type CommentFilters = {\n /** Selected status values; empty = all statuses. */\n statuses: Set<string>;\n /** facet.key → selected option values; empty set = all values for that facet. */\n facets: Record<string, Set<string>>;\n /** Restrict to an author kind, or \"all\". */\n authorKind?: CommentMentionKind | \"all\";\n};\n\n/** Optional renderers passed down to a card. */\nexport type CommentRenderOptions = {\n /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n};\n\n/** A domain-free default status set: open / in progress / resolved / closed. */\nexport const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[] = [\n {\n value: \"open\",\n label: \"Open\",\n tone: \"info\",\n unresolved: true,\n stage: \"active\",\n },\n {\n value: \"in_progress\",\n label: \"In progress\",\n tone: \"warning\",\n unresolved: true,\n stage: \"active\",\n },\n {\n value: \"resolved\",\n label: \"Resolved\",\n tone: \"success\",\n stage: \"resolved\",\n },\n { value: \"closed\", label: \"Closed\", tone: \"neutral\", stage: \"closed\" },\n];\n\n/** Default checklist status cycle used when a consumer omits one. */\nexport const DEFAULT_CHECKLIST_CYCLE = [\"open\", \"in_progress\", \"done\"];\n"],"names":[],"mappings":";;AA8GO,MAAM,kBAAkB;AAuGxB,MAAM,2BAAkD;AAAA,EAC7D;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAET;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAET;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,EAAA;AAAA,EAET,EAAE,OAAO,UAAU,OAAO,UAAU,MAAM,WAAW,OAAO,SAAA;AAC9D;AAGO,MAAM,0BAA0B,CAAC,QAAQ,eAAe,MAAM;;;;"}
@@ -2,6 +2,8 @@ import { ReactNode } from '../../../../node_modules/@types/react/index.d.ts';
2
2
  import { StaticIconComponent } from '../data/Icon';
3
3
  /** Semantic tone driving a status/facet color (maps onto Badge tones). */
4
4
  export type CommentTone = "default" | "info" | "success" | "warning" | "danger" | "neutral";
5
+ /** Product lifecycle stage represented by one or more stored statuses. */
6
+ export type CommentStatusStage = "active" | "resolved" | "closed";
5
7
  /** A single selectable status (e.g. "open", "resolved"). Consumer-defined. */
6
8
  export type CommentStatusConfig = {
7
9
  /** Stable value stored on a comment's `status`. */
@@ -12,6 +14,8 @@ export type CommentStatusConfig = {
12
14
  tone?: CommentTone;
13
15
  /** Optional leading glyph. */
14
16
  icon?: string | StaticIconComponent;
17
+ /** Lifecycle stage used by review queues and status actions. */
18
+ stage?: CommentStatusStage;
15
19
  /**
16
20
  * Whether this status counts as "unresolved/open" — drives progress tallies
17
21
  * and which groups start expanded. Defaults to false.
@@ -102,6 +106,10 @@ export type Comment = {
102
106
  anchor?: CommentAnchor | null;
103
107
  /** Optional positive/negative review signal; roots only. */
104
108
  rating?: CommentRating;
109
+ /** ISO-8601 time when a human closed the resolved comment. */
110
+ closedAt?: string;
111
+ /** Human who closed the comment. */
112
+ closedBy?: CommentAuthor;
105
113
  /** facet.key → selected option value. */
106
114
  facets?: Record<string, string>;
107
115
  refs?: CommentRefGroup[];
@@ -130,6 +138,8 @@ export type CommentCallbacks = {
130
138
  onCreate?: (input: CommentCreateInput) => void | Promise<void>;
131
139
  onReply?: (input: CommentReplyInput) => void | Promise<void>;
132
140
  onUpdateStatus?: (id: string, status: string) => void | Promise<void>;
141
+ /** Human-only transition from resolved to closed. */
142
+ onClose?: (id: string) => void | Promise<void>;
133
143
  onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;
134
144
  onDelete?: (id: string) => void | Promise<void>;
135
145
  onChecklistToggle?: (id: string, index: number, next: string) => void | Promise<void>;
@@ -169,7 +179,7 @@ export type CommentRenderOptions = {
169
179
  /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */
170
180
  renderBody?: (body: string) => ReactNode;
171
181
  };
172
- /** A domain-free default status set: open / in progress / resolved. */
182
+ /** A domain-free default status set: open / in progress / resolved / closed. */
173
183
  export declare const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[];
174
184
  /** Default checklist status cycle used when a consumer omits one. */
175
185
  export declare const DEFAULT_CHECKLIST_CYCLE: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"comment-types.d.ts","sourceRoot":"","sources":["../../src/comments/comment-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"comment-types.d.ts","sourceRoot":"","sources":["../../src/comments/comment-types.ts"],"names":[],"mappings":"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"}