@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,227 +1,114 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { cn } from "../lib/utils.js";
4
- import { Badge } from "../data/Badge.js";
5
4
  import { Icon } from "../data/Icon.js";
6
5
  import { DropdownMenu } from "../overlay/DropdownMenu.js";
7
6
  import { parseTimestamp, formatRelativeTime } from "../data/cells/timestamp-format.js";
8
7
  import { CommentAuthorAvatar } from "./CommentAuthor.js";
8
+ import { StatusChip, RatingChip, FacetBadges, ChecklistChips, RefChips } from "./CommentCardParts.js";
9
9
  import { CommentMarkdown } from "./CommentMarkdown.js";
10
- import { isUnresolved, authorDisplayName, truncatePlain, resolveFacetOption, toneToBadgeTone, resolveStatusConfig } from "./comment-utils.js";
10
+ import { authorDisplayName, truncatePlain, resolveCommentStage, statusForCommentStage } from "./comment-utils.js";
11
11
  import { UiCheck } from "../icons/components/UiCheck.js";
12
- import { UiFullscreen } from "../icons/components/UiFullscreen.js";
12
+ import { UiLock } from "../icons/components/UiLock.js";
13
+ import { UiRestart } from "../icons/components/UiRestart.js";
13
14
  import { UiDotsVertical } from "../icons/components/UiDotsVertical.js";
14
- import { UiThumbsUp } from "../icons/components/UiThumbsUp.js";
15
- import { UiThumbsDown } from "../icons/components/UiThumbsDown.js";
16
- import { UiClose } from "../icons/components/UiClose.js";
17
15
  import { UiTrash } from "../icons/components/UiTrash.js";
18
- function completionStatus(config) {
19
- for (const completionName of ["resolved", "closed"]) {
20
- const status = config.statuses.find(
21
- (candidate) => candidate.value.toLowerCase() === completionName || candidate.label.toLowerCase() === completionName
22
- );
23
- if (status) return status;
24
- }
25
- return void 0;
26
- }
27
- function completionActionLabel(status) {
28
- return status.value.toLowerCase() === "closed" || status.label.toLowerCase() === "closed" ? "Close comment" : "Resolve comment";
29
- }
30
- function RatingChip({ rating }) {
31
- if (!rating) return null;
32
- const positive = rating === "positive";
33
- return /* @__PURE__ */ jsx(
34
- Badge,
35
- {
36
- variant: "soft",
37
- tone: positive ? "success" : "danger",
38
- size: "xs",
39
- icon: positive ? UiThumbsUp : UiThumbsDown,
40
- children: positive ? "Positive rating" : "Negative rating"
41
- }
42
- );
43
- }
44
- function StatusChip({
45
- status,
46
- config,
47
- showLabel
48
- }) {
49
- const cfg = resolveStatusConfig(config, status);
50
- if (!cfg) return null;
51
- return /* @__PURE__ */ jsx(
52
- Badge,
53
- {
54
- variant: "soft",
55
- tone: toneToBadgeTone(cfg.tone),
56
- size: "xs",
57
- ...cfg.icon ? { icon: cfg.icon } : {},
58
- children: showLabel ? cfg.label : null
59
- }
60
- );
61
- }
62
- function FacetBadges({
63
- comment,
64
- config,
65
- compact
66
- }) {
67
- const facets = config.facets ?? [];
68
- const chips = facets.flatMap((facet) => {
69
- var _a;
70
- const value = (_a = comment.facets) == null ? void 0 : _a[facet.key];
71
- const option = resolveFacetOption(facet, value);
72
- if (!option) return [];
73
- return [
74
- /* @__PURE__ */ jsx(
75
- Badge,
76
- {
77
- variant: "soft",
78
- tone: toneToBadgeTone(option.tone),
79
- size: "xs",
80
- children: compact ? option.short ?? option.label : option.label
81
- },
82
- facet.key
83
- )
84
- ];
85
- });
86
- return chips.length > 0 ? /* @__PURE__ */ jsx(Fragment, { children: chips }) : null;
87
- }
88
- function ChecklistChips({
89
- comment,
90
- onChecklistToggle
91
- }) {
92
- if (!comment.checklist || comment.checklist.length === 0) return null;
93
- return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: comment.checklist.map((item, i) => {
94
- const done = /done|complete|resolved/i.test(item.status);
95
- const active = /progress|doing/i.test(item.status);
96
- return /* @__PURE__ */ jsx(
97
- "button",
98
- {
99
- type: "button",
100
- onClick: () => onChecklistToggle == null ? void 0 : onChecklistToggle(i),
101
- disabled: !onChecklistToggle,
102
- className: cn(
103
- "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] transition-opacity",
104
- onChecklistToggle && "cursor-pointer hover:opacity-80",
105
- done ? "bg-green-100 text-green-800 line-through dark:bg-green-500/20 dark:text-green-300" : active ? "bg-yellow-100 text-yellow-800 dark:bg-yellow-500/20 dark:text-yellow-300" : "bg-muted text-muted-foreground"
106
- ),
107
- children: item.label
108
- },
109
- i
110
- );
111
- }) });
112
- }
113
- function RefChips({ comment }) {
114
- if (!comment.refs || comment.refs.length === 0) return null;
115
- return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: comment.refs.map((group, i) => /* @__PURE__ */ jsxs(
116
- "span",
117
- {
118
- className: "inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground",
119
- children: [
120
- /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
121
- group.label,
122
- ":"
123
- ] }),
124
- /* @__PURE__ */ jsx("span", { className: group.mono ? "font-mono" : void 0, children: group.items.join(", ") })
125
- ]
126
- },
127
- i
128
- )) });
129
- }
130
- function statusMenuItems(comment, config, isReply, opts) {
16
+ import { UiCopy } from "../icons/components/UiCopy.js";
17
+ import { UiFullscreen } from "../icons/components/UiFullscreen.js";
18
+ function actionMenuItems(isReply, opts) {
131
19
  const items = [];
20
+ if (opts.onDelete) {
21
+ items.push({ label: "Delete", icon: UiTrash, onSelect: opts.onDelete });
22
+ }
23
+ if (!isReply) items.push(...opts.statusActions ?? []);
24
+ if (opts.onCopy) {
25
+ items.push({ label: "Copy", icon: UiCopy, onSelect: opts.onCopy });
26
+ }
132
27
  if (opts.onMaximize) {
133
28
  items.push({
134
- label: "Maximize",
29
+ label: "Maximise",
135
30
  icon: UiFullscreen,
136
31
  onSelect: opts.onMaximize
137
32
  });
138
33
  }
139
- if (opts.onCollapse) {
140
- items.push({ label: "Collapse", icon: UiClose, onSelect: opts.onCollapse });
141
- }
142
- if (!isReply && opts.onUpdateStatus) {
143
- for (const status of config.statuses) {
144
- if (status.value === opts.completionStatus) continue;
145
- const current = status.value === comment.status;
146
- items.push({
147
- label: /* @__PURE__ */ jsxs(
148
- "span",
149
- {
150
- className: cn(
151
- "inline-flex items-center gap-1.5",
152
- current && "font-semibold"
153
- ),
154
- children: [
155
- /* @__PURE__ */ jsx(StatusChip, { status: status.value, config }),
156
- status.label
157
- ]
158
- }
159
- ),
160
- onSelect: () => {
161
- var _a;
162
- return (_a = opts.onUpdateStatus) == null ? void 0 : _a.call(opts, status.value);
163
- }
164
- });
165
- }
166
- }
167
- if (opts.onDelete) {
168
- items.push({ label: "Delete", icon: UiTrash, onSelect: opts.onDelete });
169
- }
170
34
  return items;
171
35
  }
172
- function IconAction({
173
- icon,
174
- label,
175
- onClick,
176
- className
177
- }) {
178
- return /* @__PURE__ */ jsx(
179
- "button",
180
- {
181
- type: "button",
182
- onClick,
183
- "aria-label": label,
184
- title: label,
185
- className: cn(
186
- "rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
187
- className
188
- ),
189
- children: /* @__PURE__ */ jsx(Icon, { icon, className: "text-sm" })
190
- }
191
- );
192
- }
193
36
  function CommentBody({
194
37
  comment,
195
38
  config,
196
39
  renderBody,
197
40
  onUpdateStatus,
41
+ onClose,
198
42
  onDelete,
43
+ onCopy,
199
44
  onReply,
200
45
  onChecklistToggle,
201
- onCollapse,
202
46
  onMaximize
203
47
  }) {
204
- const [statusError, setStatusError] = useState("");
48
+ const [actionError, setActionError] = useState("");
205
49
  const isReply = Boolean(comment.parentId);
206
50
  const date = parseTimestamp(comment.createdAt);
207
- const completion = completionStatus(config);
208
- const canComplete = !isReply && onUpdateStatus != null && completion != null && isUnresolved(config, comment.status);
51
+ const resolved = statusForCommentStage(config, "resolved");
52
+ const active = statusForCommentStage(config, "active");
53
+ const closed = statusForCommentStage(config, "closed");
54
+ const stage = resolveCommentStage(config, comment.status);
209
55
  const updateStatus = async (status) => {
210
- setStatusError("");
56
+ setActionError("");
211
57
  try {
212
58
  await (onUpdateStatus == null ? void 0 : onUpdateStatus(status));
213
59
  } catch (error) {
214
- setStatusError(
215
- error instanceof Error ? error.message : "Unexpected error"
60
+ setActionError(
61
+ `Couldn't update comment: ${error instanceof Error ? error.message : "Unexpected error"}`
216
62
  );
217
63
  }
218
64
  };
219
- const menuItems = statusMenuItems(comment, config, isReply, {
220
- ...onUpdateStatus ? { onUpdateStatus: (status) => void updateStatus(status) } : {},
65
+ const close = async () => {
66
+ setActionError("");
67
+ try {
68
+ await (onClose == null ? void 0 : onClose());
69
+ } catch (error) {
70
+ setActionError(
71
+ `Couldn't close comment: ${error instanceof Error ? error.message : "Unexpected error"}`
72
+ );
73
+ }
74
+ };
75
+ const copy = async () => {
76
+ setActionError("");
77
+ try {
78
+ await (onCopy == null ? void 0 : onCopy());
79
+ } catch (error) {
80
+ setActionError(
81
+ `Couldn't copy comment: ${error instanceof Error ? error.message : "Unexpected error"}`
82
+ );
83
+ }
84
+ };
85
+ const menuItems = actionMenuItems(isReply, {
221
86
  ...onDelete ? { onDelete } : {},
222
- ...onCollapse ? { onCollapse } : {},
223
- ...onMaximize ? { onMaximize } : {},
224
- ...completion ? { completionStatus: completion.value } : {}
87
+ statusActions: [
88
+ ...stage === "active" && onUpdateStatus && resolved ? [
89
+ {
90
+ label: "Resolve",
91
+ icon: UiCheck,
92
+ onSelect: () => void updateStatus(resolved.value)
93
+ }
94
+ ] : [],
95
+ ...stage === "resolved" && onClose && closed ? [
96
+ {
97
+ label: "Close",
98
+ icon: UiLock,
99
+ onSelect: () => void close()
100
+ }
101
+ ] : [],
102
+ ...(stage === "resolved" || stage === "closed") && onUpdateStatus && active ? [
103
+ {
104
+ label: "Reopen",
105
+ icon: UiRestart,
106
+ onSelect: () => void updateStatus(active.value)
107
+ }
108
+ ] : []
109
+ ],
110
+ ...onCopy ? { onCopy: () => void copy() } : {},
111
+ ...onMaximize ? { onMaximize } : {}
225
112
  });
226
113
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
227
114
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5", children: [
@@ -239,50 +126,37 @@ function CommentBody({
239
126
  )
240
127
  ] }),
241
128
  !isReply && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap gap-1", children: [
129
+ /* @__PURE__ */ jsx(StatusChip, { comment, config }),
242
130
  /* @__PURE__ */ jsx(RatingChip, { rating: comment.rating }),
243
131
  /* @__PURE__ */ jsx(FacetBadges, { comment, config })
244
132
  ] })
245
133
  ] }),
246
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
247
- canComplete && /* @__PURE__ */ jsx(
248
- IconAction,
249
- {
250
- icon: UiCheck,
251
- label: completionActionLabel(completion),
252
- onClick: () => void updateStatus(completion.value),
253
- className: "hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300"
254
- }
255
- ),
256
- menuItems.length > 0 && /* @__PURE__ */ jsx(
257
- DropdownMenu,
258
- {
259
- align: "right",
260
- menuClassName: "min-w-[150px]",
261
- trigger: /* @__PURE__ */ jsx(
262
- "span",
263
- {
264
- role: "button",
265
- tabIndex: 0,
266
- "aria-label": "Comment actions",
267
- className: "inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
268
- children: /* @__PURE__ */ jsx(Icon, { icon: UiDotsVertical, className: "text-sm" })
269
- }
270
- ),
271
- items: menuItems
272
- }
273
- )
274
- ] })
134
+ /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-0.5", children: menuItems.length > 0 && /* @__PURE__ */ jsx(
135
+ DropdownMenu,
136
+ {
137
+ align: "right",
138
+ menuClassName: "min-w-[150px]",
139
+ trigger: /* @__PURE__ */ jsx(
140
+ "span",
141
+ {
142
+ role: "button",
143
+ tabIndex: 0,
144
+ "aria-label": "Comment actions",
145
+ className: "inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
146
+ children: /* @__PURE__ */ jsx(Icon, { icon: UiDotsVertical, className: "text-sm" })
147
+ }
148
+ ),
149
+ items: menuItems
150
+ }
151
+ ) })
275
152
  ] }),
276
153
  comment.body.trim() && /* @__PURE__ */ jsx("div", { className: "text-[13px] leading-5 text-foreground", children: renderBody ? renderBody(comment.body) : /* @__PURE__ */ jsx(CommentMarkdown, { text: comment.body }) }),
277
- statusError && /* @__PURE__ */ jsxs(
154
+ actionError && /* @__PURE__ */ jsx(
278
155
  "div",
279
156
  {
280
157
  role: "alert",
281
158
  className: "rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive",
282
- children: [
283
- "Couldn't update comment: ",
284
- statusError
285
- ]
159
+ children: actionError
286
160
  }
287
161
  ),
288
162
  !isReply && /* @__PURE__ */ jsx(
@@ -305,31 +179,41 @@ function CommentBody({
305
179
  ] });
306
180
  }
307
181
  function CommentCard(props) {
308
- const { comment, config, compact, defaultExpanded, onMaximize } = props;
182
+ const { comment, config, compact, defaultExpanded } = props;
309
183
  const [expanded, setExpanded] = useState(defaultExpanded ?? false);
310
- const [collapsedStatusError, setCollapsedStatusError] = useState("");
184
+ const [collapsedActionError, setCollapsedActionError] = useState("");
311
185
  const isReply = Boolean(comment.parentId);
312
- const completion = completionStatus(config);
313
- const canComplete = !isReply && props.onUpdateStatus != null && completion != null && isUnresolved(config, comment.status);
314
- const completeCollapsed = async () => {
315
- var _a;
316
- if (!completion) return;
317
- setCollapsedStatusError("");
186
+ const body = /* @__PURE__ */ jsx(CommentBody, { ...props });
187
+ async function runCollapsedAction(action) {
188
+ var _a, _b;
189
+ setCollapsedActionError("");
318
190
  try {
319
- await ((_a = props.onUpdateStatus) == null ? void 0 : _a.call(props, completion.value));
191
+ if (action === "close") {
192
+ await ((_a = props.onClose) == null ? void 0 : _a.call(props));
193
+ return;
194
+ }
195
+ const next = statusForCommentStage(
196
+ config,
197
+ action === "resolve" ? "resolved" : "active"
198
+ );
199
+ if (next) await ((_b = props.onUpdateStatus) == null ? void 0 : _b.call(props, next.value));
320
200
  } catch (error) {
321
- setCollapsedStatusError(
322
- error instanceof Error ? error.message : "Unexpected error"
201
+ setCollapsedActionError(
202
+ `${action === "close" ? "Couldn't close comment" : "Couldn't update comment"}: ${error instanceof Error ? error.message : "Unexpected error"}`
323
203
  );
324
204
  }
325
- };
326
- const body = /* @__PURE__ */ jsx(
327
- CommentBody,
328
- {
329
- ...props,
330
- ...expanded ? { onCollapse: () => setExpanded(false) } : {}
205
+ }
206
+ async function copyCollapsed() {
207
+ var _a;
208
+ setCollapsedActionError("");
209
+ try {
210
+ await ((_a = props.onCopy) == null ? void 0 : _a.call(props));
211
+ } catch (error) {
212
+ setCollapsedActionError(
213
+ `Couldn't copy comment: ${error instanceof Error ? error.message : "Unexpected error"}`
214
+ );
331
215
  }
332
- );
216
+ }
333
217
  if (!expanded) {
334
218
  return /* @__PURE__ */ jsxs(Fragment, { children: [
335
219
  /* @__PURE__ */ jsxs(
@@ -356,27 +240,11 @@ function CommentCard(props) {
356
240
  /* @__PURE__ */ jsx(CommentAuthorAvatar, { author: comment.author, bare: true }),
357
241
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] font-semibold text-foreground", children: authorDisplayName(comment.author) }),
358
242
  !isReply && /* @__PURE__ */ jsxs(Fragment, { children: [
243
+ /* @__PURE__ */ jsx(StatusChip, { comment, config }),
359
244
  /* @__PURE__ */ jsx(RatingChip, { rating: comment.rating }),
360
245
  /* @__PURE__ */ jsx(FacetBadges, { comment, config, compact: true })
361
246
  ] }),
362
247
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[12px] text-foreground/80", children: comment.body.trim() ? truncatePlain(comment.body) : comment.rating === "positive" ? "Positive rating" : comment.rating === "negative" ? "Negative rating" : "Empty comment" }),
363
- canComplete && /* @__PURE__ */ jsx(
364
- "span",
365
- {
366
- role: "presentation",
367
- onClick: (e) => e.stopPropagation(),
368
- onKeyDown: (e) => e.stopPropagation(),
369
- children: /* @__PURE__ */ jsx(
370
- IconAction,
371
- {
372
- icon: UiCheck,
373
- label: completionActionLabel(completion),
374
- onClick: () => void completeCollapsed(),
375
- className: "hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300"
376
- }
377
- )
378
- }
379
- ),
380
248
  /* @__PURE__ */ jsx(
381
249
  "span",
382
250
  {
@@ -397,16 +265,34 @@ function CommentCard(props) {
397
265
  children: /* @__PURE__ */ jsx(Icon, { icon: UiDotsVertical, className: "text-sm" })
398
266
  }
399
267
  ),
400
- items: [
401
- { label: "Expand", onSelect: () => setExpanded(true) },
402
- ...onMaximize ? [
403
- {
404
- label: "Maximize",
405
- icon: UiFullscreen,
406
- onSelect: onMaximize
407
- }
408
- ] : []
409
- ]
268
+ items: actionMenuItems(isReply, {
269
+ ...props.onDelete ? { onDelete: props.onDelete } : {},
270
+ statusActions: [
271
+ ...resolveCommentStage(config, comment.status) === "active" && props.onUpdateStatus && statusForCommentStage(config, "resolved") ? [
272
+ {
273
+ label: "Resolve",
274
+ icon: UiCheck,
275
+ onSelect: () => void runCollapsedAction("resolve")
276
+ }
277
+ ] : [],
278
+ ...resolveCommentStage(config, comment.status) === "resolved" && props.onClose && statusForCommentStage(config, "closed") ? [
279
+ {
280
+ label: "Close",
281
+ icon: UiLock,
282
+ onSelect: () => void runCollapsedAction("close")
283
+ }
284
+ ] : [],
285
+ ...(resolveCommentStage(config, comment.status) === "resolved" || resolveCommentStage(config, comment.status) === "closed") && props.onUpdateStatus && statusForCommentStage(config, "active") ? [
286
+ {
287
+ label: "Reopen",
288
+ icon: UiRestart,
289
+ onSelect: () => void runCollapsedAction("reopen")
290
+ }
291
+ ] : []
292
+ ],
293
+ ...props.onCopy ? { onCopy: () => void copyCollapsed() } : {},
294
+ ...props.onMaximize ? { onMaximize: props.onMaximize } : {}
295
+ })
410
296
  }
411
297
  )
412
298
  }
@@ -414,15 +300,12 @@ function CommentCard(props) {
414
300
  ]
415
301
  }
416
302
  ),
417
- collapsedStatusError && /* @__PURE__ */ jsxs(
303
+ collapsedActionError && /* @__PURE__ */ jsx(
418
304
  "div",
419
305
  {
420
306
  role: "alert",
421
307
  className: "rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive",
422
- children: [
423
- "Couldn't update comment: ",
424
- collapsedStatusError
425
- ]
308
+ children: collapsedActionError
426
309
  }
427
310
  )
428
311
  ] });
@@ -1 +1 @@
1
- {"version":3,"file":"CommentCard.js","sources":["../../src/comments/CommentCard.tsx"],"sourcesContent":["import { useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Badge } from \"../data/Badge\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { DropdownMenu, type DropdownMenuItem } from \"../overlay/DropdownMenu\";\nimport {\n parseTimestamp,\n formatRelativeTime,\n} from \"../data/cells/timestamp-format\";\nimport {\n UiCheck,\n UiClose,\n UiDotsVertical,\n UiFullscreen,\n UiThumbsDown,\n UiThumbsUp,\n UiTrash,\n} from \"../icons\";\nimport { CommentAuthorAvatar } from \"./CommentAuthor\";\nimport { CommentMarkdown } from \"./CommentMarkdown\";\nimport {\n authorDisplayName,\n isUnresolved,\n resolveFacetOption,\n resolveStatusConfig,\n toneToBadgeTone,\n truncatePlain,\n} from \"./comment-utils\";\nimport type {\n Comment,\n CommentConfig,\n CommentRating,\n CommentStatusConfig,\n} from \"./comment-types\";\n\nfunction completionStatus(config: CommentConfig) {\n for (const completionName of [\"resolved\", \"closed\"]) {\n const status = config.statuses.find(\n (candidate) =>\n candidate.value.toLowerCase() === completionName ||\n candidate.label.toLowerCase() === completionName,\n );\n if (status) return status;\n }\n return undefined;\n}\n\nfunction completionActionLabel(status: CommentStatusConfig) {\n return status.value.toLowerCase() === \"closed\" ||\n status.label.toLowerCase() === \"closed\"\n ? \"Close comment\"\n : \"Resolve comment\";\n}\n\nexport type CommentCardProps = {\n comment: Comment;\n config: CommentConfig;\n /** Tighter padding for dense rails. */\n compact?: boolean;\n /** Render expanded on first mount. */\n defaultExpanded?: boolean;\n /** Custom body renderer; defaults to the lightweight CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n /** Change this comment's status (roots only). */\n onUpdateStatus?: (status: string) => void | Promise<void>;\n /** Delete this comment. */\n onDelete?: () => void;\n /** Begin a reply to this comment. */\n onReply?: () => void;\n /** Advance the checklist item at `index` to its next status. */\n onChecklistToggle?: (index: number) => void;\n /**\n * Open this card's thread in a larger view. Owned by the caller because a\n * card knows only itself, while maximizing shows the root and its replies.\n * Omit to drop the action (e.g. inside an already-maximized thread).\n */\n onMaximize?: () => void;\n};\n\nfunction RatingChip({ rating }: { rating: CommentRating | undefined }) {\n if (!rating) return null;\n const positive = rating === \"positive\";\n return (\n <Badge\n variant=\"soft\"\n tone={positive ? \"success\" : \"danger\"}\n size=\"xs\"\n icon={positive ? UiThumbsUp : UiThumbsDown}\n >\n {positive ? \"Positive rating\" : \"Negative rating\"}\n </Badge>\n );\n}\n\nfunction StatusChip({\n status,\n config,\n showLabel,\n}: {\n status: string | undefined;\n config: CommentConfig;\n showLabel?: boolean;\n}) {\n const cfg = resolveStatusConfig(config, status);\n if (!cfg) return null;\n return (\n <Badge\n variant=\"soft\"\n tone={toneToBadgeTone(cfg.tone)}\n size=\"xs\"\n {...(cfg.icon ? { icon: cfg.icon } : {})}\n >\n {showLabel ? cfg.label : null}\n </Badge>\n );\n}\n\nfunction FacetBadges({\n comment,\n config,\n compact,\n}: {\n comment: Comment;\n config: CommentConfig;\n compact?: boolean;\n}) {\n const facets = config.facets ?? [];\n const chips = facets.flatMap((facet) => {\n const value = comment.facets?.[facet.key];\n const option = resolveFacetOption(facet, value);\n if (!option) return [];\n return [\n <Badge\n key={facet.key}\n variant=\"soft\"\n tone={toneToBadgeTone(option.tone)}\n size=\"xs\"\n >\n {compact ? (option.short ?? option.label) : option.label}\n </Badge>,\n ];\n });\n return chips.length > 0 ? <>{chips}</> : null;\n}\n\nfunction ChecklistChips({\n comment,\n onChecklistToggle,\n}: {\n comment: Comment;\n onChecklistToggle?: (index: number) => void;\n}) {\n if (!comment.checklist || comment.checklist.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1\">\n {comment.checklist.map((item, i) => {\n const done = /done|complete|resolved/i.test(item.status);\n const active = /progress|doing/i.test(item.status);\n return (\n <button\n key={i}\n type=\"button\"\n onClick={() => onChecklistToggle?.(i)}\n disabled={!onChecklistToggle}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] transition-opacity\",\n onChecklistToggle && \"cursor-pointer hover:opacity-80\",\n done\n ? \"bg-green-100 text-green-800 line-through dark:bg-green-500/20 dark:text-green-300\"\n : active\n ? \"bg-yellow-100 text-yellow-800 dark:bg-yellow-500/20 dark:text-yellow-300\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n\nfunction RefChips({ comment }: { comment: Comment }) {\n if (!comment.refs || comment.refs.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {comment.refs.map((group, i) => (\n <span\n key={i}\n className=\"inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground\"\n >\n <span className=\"font-medium\">{group.label}:</span>\n <span className={group.mono ? \"font-mono\" : undefined}>\n {group.items.join(\", \")}\n </span>\n </span>\n ))}\n </div>\n );\n}\n\nfunction statusMenuItems(\n comment: Comment,\n config: CommentConfig,\n isReply: boolean,\n opts: {\n onUpdateStatus?: (status: string) => void;\n onDelete?: () => void;\n onCollapse?: () => void;\n onMaximize?: () => void;\n completionStatus?: string;\n },\n): DropdownMenuItem[] {\n const items: DropdownMenuItem[] = [];\n if (opts.onMaximize) {\n items.push({\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: opts.onMaximize,\n });\n }\n if (opts.onCollapse) {\n items.push({ label: \"Collapse\", icon: UiClose, onSelect: opts.onCollapse });\n }\n if (!isReply && opts.onUpdateStatus) {\n for (const status of config.statuses) {\n if (status.value === opts.completionStatus) continue;\n const current = status.value === comment.status;\n items.push({\n label: (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5\",\n current && \"font-semibold\",\n )}\n >\n <StatusChip status={status.value} config={config} />\n {status.label}\n </span>\n ),\n onSelect: () => opts.onUpdateStatus?.(status.value),\n });\n }\n }\n if (opts.onDelete) {\n items.push({ label: \"Delete\", icon: UiTrash, onSelect: opts.onDelete });\n }\n return items;\n}\n\nfunction IconAction({\n icon,\n label,\n onClick,\n className,\n}: {\n icon: StaticIconComponent;\n label: string;\n onClick: () => void;\n className?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n \"rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n className,\n )}\n >\n <Icon icon={icon} className=\"text-sm\" />\n </button>\n );\n}\n\nfunction CommentBody({\n comment,\n config,\n renderBody,\n onUpdateStatus,\n onDelete,\n onReply,\n onChecklistToggle,\n onCollapse,\n onMaximize,\n}: CommentCardProps & { onCollapse?: () => void }) {\n const [statusError, setStatusError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const date = parseTimestamp(comment.createdAt);\n const completion = completionStatus(config);\n const canComplete =\n !isReply &&\n onUpdateStatus != null &&\n completion != null &&\n isUnresolved(config, comment.status);\n const updateStatus = async (status: string) => {\n setStatusError(\"\");\n try {\n await onUpdateStatus?.(status);\n } catch (error) {\n setStatusError(\n error instanceof Error ? error.message : \"Unexpected error\",\n );\n }\n };\n const menuItems = statusMenuItems(comment, config, isReply, {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status) => void updateStatus(status) }\n : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onCollapse ? { onCollapse } : {}),\n ...(onMaximize ? { onMaximize } : {}),\n ...(completion ? { completionStatus: completion.value } : {}),\n });\n\n return (\n <div className=\"space-y-2.5\">\n <div className=\"flex items-start gap-2.5\">\n <CommentAuthorAvatar author={comment.author} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"truncate text-xs font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {date && (\n <span\n title={date.toLocaleString()}\n className=\"shrink-0 text-[10px] text-muted-foreground\"\n >\n {formatRelativeTime(date)}\n </span>\n )}\n </div>\n {!isReply && (\n <div className=\"mt-1 flex flex-wrap gap-1\">\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} />\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {canComplete && (\n <IconAction\n icon={UiCheck}\n label={completionActionLabel(completion)}\n onClick={() => void updateStatus(completion.value)}\n className=\"hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300\"\n />\n )}\n {menuItems.length > 0 && (\n <DropdownMenu\n align=\"right\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={menuItems}\n />\n )}\n </div>\n </div>\n {comment.body.trim() && (\n <div className=\"text-[13px] leading-5 text-foreground\">\n {renderBody ? (\n renderBody(comment.body)\n ) : (\n <CommentMarkdown text={comment.body} />\n )}\n </div>\n )}\n {statusError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n Couldn't update comment: {statusError}\n </div>\n )}\n {!isReply && (\n <ChecklistChips\n comment={comment}\n {...(onChecklistToggle ? { onChecklistToggle } : {})}\n />\n )}\n {!isReply && <RefChips comment={comment} />}\n {onReply && (\n <button\n type=\"button\"\n onClick={onReply}\n className=\"-ml-2 rounded px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary/10\"\n >\n Reply\n </button>\n )}\n </div>\n );\n}\n\n/**\n * A single comment, collapsible between a compact one-line preview and a full\n * card. Offers a Maximize action when `onMaximize` is supplied — the owner\n * enlarges the whole thread. Fully controlled — all mutations are delegated to\n * the supplied callbacks.\n */\nexport function CommentCard(props: CommentCardProps) {\n const { comment, config, compact, defaultExpanded, onMaximize } = props;\n const [expanded, setExpanded] = useState(defaultExpanded ?? false);\n const [collapsedStatusError, setCollapsedStatusError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const completion = completionStatus(config);\n const canComplete =\n !isReply &&\n props.onUpdateStatus != null &&\n completion != null &&\n isUnresolved(config, comment.status);\n const completeCollapsed = async () => {\n if (!completion) return;\n setCollapsedStatusError(\"\");\n try {\n await props.onUpdateStatus?.(completion.value);\n } catch (error) {\n setCollapsedStatusError(\n error instanceof Error ? error.message : \"Unexpected error\",\n );\n }\n };\n\n const body = (\n <CommentBody\n {...props}\n {...(expanded ? { onCollapse: () => setExpanded(false) } : {})}\n />\n );\n\n if (!expanded) {\n return (\n <>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpanded(true)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setExpanded(true);\n }\n }}\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs transition-colors hover:bg-muted/50\",\n !isReply && \"border border-border bg-background shadow-sm\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/30\",\n )}\n >\n <CommentAuthorAvatar author={comment.author} bare />\n <span className=\"shrink-0 text-[11px] font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {!isReply && (\n <>\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} compact />\n </>\n )}\n <span className=\"min-w-0 flex-1 truncate text-[12px] text-foreground/80\">\n {comment.body.trim()\n ? truncatePlain(comment.body)\n : comment.rating === \"positive\"\n ? \"Positive rating\"\n : comment.rating === \"negative\"\n ? \"Negative rating\"\n : \"Empty comment\"}\n </span>\n {canComplete && (\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <IconAction\n icon={UiCheck}\n label={completionActionLabel(completion)}\n onClick={() => void completeCollapsed()}\n className=\"hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300\"\n />\n </span>\n )}\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <DropdownMenu\n align=\"right\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:opacity-100 group-hover/comment:opacity-70\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={[\n { label: \"Expand\", onSelect: () => setExpanded(true) },\n ...(onMaximize\n ? [\n {\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: onMaximize,\n },\n ]\n : []),\n ]}\n />\n </span>\n </div>\n {collapsedStatusError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n Couldn't update comment: {collapsedStatusError}\n </div>\n )}\n </>\n );\n }\n\n return (\n <>\n <div\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment rounded-xl bg-background px-3.5 py-3 text-xs\",\n !isReply &&\n \"border border-border/80 shadow-sm transition-shadow hover:shadow-md\",\n compact && \"px-2.5 py-2.5\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/20 shadow-none\",\n )}\n >\n {body}\n </div>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CommentCard.js","sources":["../../src/comments/CommentCard.tsx"],"sourcesContent":["import { useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu, type DropdownMenuItem } from \"../overlay/DropdownMenu\";\nimport {\n parseTimestamp,\n formatRelativeTime,\n} from \"../data/cells/timestamp-format\";\nimport {\n UiCheck,\n UiCopy,\n UiDotsVertical,\n UiFullscreen,\n UiLock,\n UiRestart,\n UiTrash,\n} from \"../icons\";\nimport { CommentAuthorAvatar } from \"./CommentAuthor\";\nimport {\n ChecklistChips,\n FacetBadges,\n RatingChip,\n RefChips,\n StatusChip,\n} from \"./CommentCardParts\";\nimport { CommentMarkdown } from \"./CommentMarkdown\";\nimport {\n authorDisplayName,\n resolveCommentStage,\n statusForCommentStage,\n truncatePlain,\n} from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentCardProps = {\n comment: Comment;\n config: CommentConfig;\n /** Tighter padding for dense rails. */\n compact?: boolean;\n /** Render expanded on first mount. */\n defaultExpanded?: boolean;\n /** Custom body renderer; defaults to the lightweight CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n /** Change this comment's status (roots only). */\n onUpdateStatus?: (status: string) => void | Promise<void>;\n /** Close a resolved root comment. */\n onClose?: () => void | Promise<void>;\n /** Delete this comment. */\n onDelete?: () => void;\n /** Copy this comment's whole thread in the caller's canonical format. */\n onCopy?: () => void | Promise<void>;\n /** Begin a reply to this comment. */\n onReply?: () => void;\n /** Advance the checklist item at `index` to its next status. */\n onChecklistToggle?: (index: number) => void;\n /**\n * Open this card's thread in a larger view. Owned by the caller because a\n * card knows only itself, while maximizing shows the root and its replies.\n * Omit to drop the action (e.g. inside an already-maximized thread).\n */\n onMaximize?: () => void;\n};\n\nfunction actionMenuItems(\n isReply: boolean,\n opts: {\n onDelete?: () => void;\n statusActions?: DropdownMenuItem[];\n onCopy?: () => void;\n onMaximize?: () => void;\n },\n): DropdownMenuItem[] {\n const items: DropdownMenuItem[] = [];\n if (opts.onDelete) {\n items.push({ label: \"Delete\", icon: UiTrash, onSelect: opts.onDelete });\n }\n if (!isReply) items.push(...(opts.statusActions ?? []));\n if (opts.onCopy) {\n items.push({ label: \"Copy\", icon: UiCopy, onSelect: opts.onCopy });\n }\n if (opts.onMaximize) {\n items.push({\n label: \"Maximise\",\n icon: UiFullscreen,\n onSelect: opts.onMaximize,\n });\n }\n return items;\n}\n\nfunction CommentBody({\n comment,\n config,\n renderBody,\n onUpdateStatus,\n onClose,\n onDelete,\n onCopy,\n onReply,\n onChecklistToggle,\n onMaximize,\n}: CommentCardProps) {\n const [actionError, setActionError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const date = parseTimestamp(comment.createdAt);\n const resolved = statusForCommentStage(config, \"resolved\");\n const active = statusForCommentStage(config, \"active\");\n const closed = statusForCommentStage(config, \"closed\");\n const stage = resolveCommentStage(config, comment.status);\n const updateStatus = async (status: string) => {\n setActionError(\"\");\n try {\n await onUpdateStatus?.(status);\n } catch (error) {\n setActionError(\n `Couldn't update comment: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n };\n const close = async () => {\n setActionError(\"\");\n try {\n await onClose?.();\n } catch (error) {\n setActionError(\n `Couldn't close comment: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n };\n const copy = async () => {\n setActionError(\"\");\n try {\n await onCopy?.();\n } catch (error) {\n setActionError(\n `Couldn't copy comment: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n };\n const menuItems = actionMenuItems(isReply, {\n ...(onDelete ? { onDelete } : {}),\n statusActions: [\n ...(stage === \"active\" && onUpdateStatus && resolved\n ? [\n {\n label: \"Resolve\",\n icon: UiCheck,\n onSelect: () => void updateStatus(resolved.value),\n },\n ]\n : []),\n ...(stage === \"resolved\" && onClose && closed\n ? [\n {\n label: \"Close\",\n icon: UiLock,\n onSelect: () => void close(),\n },\n ]\n : []),\n ...((stage === \"resolved\" || stage === \"closed\") &&\n onUpdateStatus &&\n active\n ? [\n {\n label: \"Reopen\",\n icon: UiRestart,\n onSelect: () => void updateStatus(active.value),\n },\n ]\n : []),\n ],\n ...(onCopy ? { onCopy: () => void copy() } : {}),\n ...(onMaximize ? { onMaximize } : {}),\n });\n\n return (\n <div className=\"space-y-2.5\">\n <div className=\"flex items-start gap-2.5\">\n <CommentAuthorAvatar author={comment.author} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"truncate text-xs font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {date && (\n <span\n title={date.toLocaleString()}\n className=\"shrink-0 text-[10px] text-muted-foreground\"\n >\n {formatRelativeTime(date)}\n </span>\n )}\n </div>\n {!isReply && (\n <div className=\"mt-1 flex flex-wrap gap-1\">\n <StatusChip comment={comment} config={config} />\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} />\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {menuItems.length > 0 && (\n <DropdownMenu\n align=\"right\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={menuItems}\n />\n )}\n </div>\n </div>\n {comment.body.trim() && (\n <div className=\"text-[13px] leading-5 text-foreground\">\n {renderBody ? (\n renderBody(comment.body)\n ) : (\n <CommentMarkdown text={comment.body} />\n )}\n </div>\n )}\n {actionError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n {actionError}\n </div>\n )}\n {!isReply && (\n <ChecklistChips\n comment={comment}\n {...(onChecklistToggle ? { onChecklistToggle } : {})}\n />\n )}\n {!isReply && <RefChips comment={comment} />}\n {onReply && (\n <button\n type=\"button\"\n onClick={onReply}\n className=\"-ml-2 rounded px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary/10\"\n >\n Reply\n </button>\n )}\n </div>\n );\n}\n\n/**\n * A single comment, collapsible between a compact one-line preview and a full\n * card. Offers a Maximise action when `onMaximize` is supplied — the owner\n * enlarges the whole thread. Fully controlled — all mutations are delegated to\n * the supplied callbacks.\n */\nexport function CommentCard(props: CommentCardProps) {\n const { comment, config, compact, defaultExpanded } = props;\n const [expanded, setExpanded] = useState(defaultExpanded ?? false);\n const [collapsedActionError, setCollapsedActionError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const body = <CommentBody {...props} />;\n\n async function runCollapsedAction(action: \"resolve\" | \"close\" | \"reopen\") {\n setCollapsedActionError(\"\");\n try {\n if (action === \"close\") {\n await props.onClose?.();\n return;\n }\n const next = statusForCommentStage(\n config,\n action === \"resolve\" ? \"resolved\" : \"active\",\n );\n if (next) await props.onUpdateStatus?.(next.value);\n } catch (error) {\n setCollapsedActionError(\n `${action === \"close\" ? \"Couldn't close comment\" : \"Couldn't update comment\"}: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n }\n\n async function copyCollapsed() {\n setCollapsedActionError(\"\");\n try {\n await props.onCopy?.();\n } catch (error) {\n setCollapsedActionError(\n `Couldn't copy comment: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n }\n\n if (!expanded) {\n return (\n <>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpanded(true)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setExpanded(true);\n }\n }}\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs transition-colors hover:bg-muted/50\",\n !isReply && \"border border-border bg-background shadow-sm\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/30\",\n )}\n >\n <CommentAuthorAvatar author={comment.author} bare />\n <span className=\"shrink-0 text-[11px] font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {!isReply && (\n <>\n <StatusChip comment={comment} config={config} />\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} compact />\n </>\n )}\n <span className=\"min-w-0 flex-1 truncate text-[12px] text-foreground/80\">\n {comment.body.trim()\n ? truncatePlain(comment.body)\n : comment.rating === \"positive\"\n ? \"Positive rating\"\n : comment.rating === \"negative\"\n ? \"Negative rating\"\n : \"Empty comment\"}\n </span>\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <DropdownMenu\n align=\"right\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:opacity-100 group-hover/comment:opacity-70\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={actionMenuItems(isReply, {\n ...(props.onDelete ? { onDelete: props.onDelete } : {}),\n statusActions: [\n ...(resolveCommentStage(config, comment.status) ===\n \"active\" &&\n props.onUpdateStatus &&\n statusForCommentStage(config, \"resolved\")\n ? [\n {\n label: \"Resolve\",\n icon: UiCheck,\n onSelect: () => void runCollapsedAction(\"resolve\"),\n },\n ]\n : []),\n ...(resolveCommentStage(config, comment.status) ===\n \"resolved\" &&\n props.onClose &&\n statusForCommentStage(config, \"closed\")\n ? [\n {\n label: \"Close\",\n icon: UiLock,\n onSelect: () => void runCollapsedAction(\"close\"),\n },\n ]\n : []),\n ...((resolveCommentStage(config, comment.status) ===\n \"resolved\" ||\n resolveCommentStage(config, comment.status) === \"closed\") &&\n props.onUpdateStatus &&\n statusForCommentStage(config, \"active\")\n ? [\n {\n label: \"Reopen\",\n icon: UiRestart,\n onSelect: () => void runCollapsedAction(\"reopen\"),\n },\n ]\n : []),\n ],\n ...(props.onCopy ? { onCopy: () => void copyCollapsed() } : {}),\n ...(props.onMaximize ? { onMaximize: props.onMaximize } : {}),\n })}\n />\n </span>\n </div>\n {collapsedActionError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n {collapsedActionError}\n </div>\n )}\n </>\n );\n }\n\n return (\n <>\n <div\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment rounded-xl bg-background px-3.5 py-3 text-xs\",\n !isReply &&\n \"border border-border/80 shadow-sm transition-shadow hover:shadow-md\",\n compact && \"px-2.5 py-2.5\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/20 shadow-none\",\n )}\n >\n {body}\n </div>\n </>\n );\n}\n"],"names":[],"mappings":"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"}