@flanksource/clicky-ui 0.3.25 → 0.3.26

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 (470) hide show
  1. package/NOTICE.md +1 -0
  2. package/dist/ai.cjs +4 -0
  3. package/dist/ai.cjs.map +1 -1
  4. package/dist/ai.js +6 -2
  5. package/dist/ai.js.map +1 -1
  6. package/dist/chat.cjs +8 -0
  7. package/dist/chat.cjs.map +1 -1
  8. package/dist/chat.js +9 -1
  9. package/dist/chat.js.map +1 -1
  10. package/dist/comments/CommentCard.cjs +89 -14
  11. package/dist/comments/CommentCard.cjs.map +1 -1
  12. package/dist/comments/CommentCard.d.ts.map +1 -1
  13. package/dist/comments/CommentCard.js +90 -15
  14. package/dist/comments/CommentCard.js.map +1 -1
  15. package/dist/comments/CommentComposer.cjs +25 -2
  16. package/dist/comments/CommentComposer.cjs.map +1 -1
  17. package/dist/comments/CommentComposer.d.ts.map +1 -1
  18. package/dist/comments/CommentComposer.js +25 -2
  19. package/dist/comments/CommentComposer.js.map +1 -1
  20. package/dist/comments/CommentProvider.cjs +21 -14
  21. package/dist/comments/CommentProvider.cjs.map +1 -1
  22. package/dist/comments/CommentProvider.d.ts +1 -1
  23. package/dist/comments/CommentProvider.d.ts.map +1 -1
  24. package/dist/comments/CommentProvider.js +21 -14
  25. package/dist/comments/CommentProvider.js.map +1 -1
  26. package/dist/comments/CommentSidePanel.cjs +1 -1
  27. package/dist/comments/CommentSidePanel.cjs.map +1 -1
  28. package/dist/comments/CommentSidePanel.js +1 -1
  29. package/dist/comments/CommentSidePanel.js.map +1 -1
  30. package/dist/comments/comment-types.cjs.map +1 -1
  31. package/dist/comments/comment-types.d.ts +6 -0
  32. package/dist/comments/comment-types.d.ts.map +1 -1
  33. package/dist/comments/comment-types.js.map +1 -1
  34. package/dist/components/DimensionField.cjs +56 -0
  35. package/dist/components/DimensionField.cjs.map +1 -0
  36. package/dist/components/DimensionField.d.ts +17 -0
  37. package/dist/components/DimensionField.d.ts.map +1 -0
  38. package/dist/components/DimensionField.js +56 -0
  39. package/dist/components/DimensionField.js.map +1 -0
  40. package/dist/components.cjs +4 -0
  41. package/dist/components.cjs.map +1 -1
  42. package/dist/components.d.ts +2 -0
  43. package/dist/components.d.ts.map +1 -1
  44. package/dist/components.js +4 -0
  45. package/dist/components.js.map +1 -1
  46. package/dist/data/Clicky.cjs +4 -5
  47. package/dist/data/Clicky.cjs.map +1 -1
  48. package/dist/data/Clicky.d.ts.map +1 -1
  49. package/dist/data/Clicky.js +4 -5
  50. package/dist/data/Clicky.js.map +1 -1
  51. package/dist/data/TaskExecDetails.cjs +8 -3
  52. package/dist/data/TaskExecDetails.cjs.map +1 -1
  53. package/dist/data/TaskExecDetails.d.ts +4 -0
  54. package/dist/data/TaskExecDetails.d.ts.map +1 -1
  55. package/dist/data/TaskExecDetails.js +9 -4
  56. package/dist/data/TaskExecDetails.js.map +1 -1
  57. package/dist/data/TaskManagerButton.cjs +101 -7
  58. package/dist/data/TaskManagerButton.cjs.map +1 -1
  59. package/dist/data/TaskManagerButton.d.ts +6 -1
  60. package/dist/data/TaskManagerButton.d.ts.map +1 -1
  61. package/dist/data/TaskManagerButton.js +103 -9
  62. package/dist/data/TaskManagerButton.js.map +1 -1
  63. package/dist/data/TaskProcessDetails.cjs +2 -0
  64. package/dist/data/TaskProcessDetails.cjs.map +1 -1
  65. package/dist/data/TaskProcessDetails.d.ts.map +1 -1
  66. package/dist/data/TaskProcessDetails.js +2 -0
  67. package/dist/data/TaskProcessDetails.js.map +1 -1
  68. package/dist/data/TaskProgress.cjs +14 -4
  69. package/dist/data/TaskProgress.cjs.map +1 -1
  70. package/dist/data/TaskProgress.js +14 -4
  71. package/dist/data/TaskProgress.js.map +1 -1
  72. package/dist/data/TaskSnapshot.cjs.map +1 -1
  73. package/dist/data/TaskSnapshot.d.ts +1 -0
  74. package/dist/data/TaskSnapshot.d.ts.map +1 -1
  75. package/dist/data/TaskSnapshot.js.map +1 -1
  76. package/dist/data/ai/ChatWindow.cjs +18 -23
  77. package/dist/data/ai/ChatWindow.cjs.map +1 -1
  78. package/dist/data/ai/ChatWindow.d.ts +1 -1
  79. package/dist/data/ai/ChatWindow.d.ts.map +1 -1
  80. package/dist/data/ai/ChatWindow.js +19 -24
  81. package/dist/data/ai/ChatWindow.js.map +1 -1
  82. package/dist/data/ai/ChatWindow.preferences.cjs +12 -21
  83. package/dist/data/ai/ChatWindow.preferences.cjs.map +1 -1
  84. package/dist/data/ai/ChatWindow.preferences.d.ts +3 -2
  85. package/dist/data/ai/ChatWindow.preferences.d.ts.map +1 -1
  86. package/dist/data/ai/ChatWindow.preferences.js +12 -21
  87. package/dist/data/ai/ChatWindow.preferences.js.map +1 -1
  88. package/dist/data/ai/ChatWindow.tool-catalog.cjs +21 -38
  89. package/dist/data/ai/ChatWindow.tool-catalog.cjs.map +1 -1
  90. package/dist/data/ai/ChatWindow.tool-catalog.d.ts +5 -5
  91. package/dist/data/ai/ChatWindow.tool-catalog.d.ts.map +1 -1
  92. package/dist/data/ai/ChatWindow.tool-catalog.js +21 -38
  93. package/dist/data/ai/ChatWindow.tool-catalog.js.map +1 -1
  94. package/dist/data/ai/ChatWindow.types.d.ts +2 -2
  95. package/dist/data/ai/ChatWindow.types.d.ts.map +1 -1
  96. package/dist/data/ai/ChatWindowManager.cjs +2 -2
  97. package/dist/data/ai/ChatWindowManager.cjs.map +1 -1
  98. package/dist/data/ai/ChatWindowManager.js +2 -2
  99. package/dist/data/ai/ChatWindowManager.js.map +1 -1
  100. package/dist/data/ai/ChatWindowRequestBody.cjs +4 -2
  101. package/dist/data/ai/ChatWindowRequestBody.cjs.map +1 -1
  102. package/dist/data/ai/ChatWindowRequestBody.d.ts +15 -3
  103. package/dist/data/ai/ChatWindowRequestBody.d.ts.map +1 -1
  104. package/dist/data/ai/ChatWindowRequestBody.js +4 -2
  105. package/dist/data/ai/ChatWindowRequestBody.js.map +1 -1
  106. package/dist/data/ai/ToolPreferences.cjs +5 -5
  107. package/dist/data/ai/ToolPreferences.cjs.map +1 -1
  108. package/dist/data/ai/ToolPreferences.d.ts +6 -5
  109. package/dist/data/ai/ToolPreferences.d.ts.map +1 -1
  110. package/dist/data/ai/ToolPreferences.js +5 -5
  111. package/dist/data/ai/ToolPreferences.js.map +1 -1
  112. package/dist/data/ai/ToolPreferences.model.cjs +61 -37
  113. package/dist/data/ai/ToolPreferences.model.cjs.map +1 -1
  114. package/dist/data/ai/ToolPreferences.model.d.ts +32 -13
  115. package/dist/data/ai/ToolPreferences.model.d.ts.map +1 -1
  116. package/dist/data/ai/ToolPreferences.model.js +62 -38
  117. package/dist/data/ai/ToolPreferences.model.js.map +1 -1
  118. package/dist/data/ai/ToolPreferencesList.cjs +17 -20
  119. package/dist/data/ai/ToolPreferencesList.cjs.map +1 -1
  120. package/dist/data/ai/ToolPreferencesList.d.ts +16 -7
  121. package/dist/data/ai/ToolPreferencesList.d.ts.map +1 -1
  122. package/dist/data/ai/ToolPreferencesList.js +18 -21
  123. package/dist/data/ai/ToolPreferencesList.js.map +1 -1
  124. package/dist/data/ai/chat-window-context.cjs +1 -1
  125. package/dist/data/ai/chat-window-context.cjs.map +1 -1
  126. package/dist/data/ai/chat-window-context.d.ts +7 -7
  127. package/dist/data/ai/chat-window-context.d.ts.map +1 -1
  128. package/dist/data/ai/chat-window-context.js +1 -1
  129. package/dist/data/ai/chat-window-context.js.map +1 -1
  130. package/dist/data/ai/index.d.ts +3 -1
  131. package/dist/data/ai/index.d.ts.map +1 -1
  132. package/dist/data/chat/clickyOperationsToTools.cjs +2 -1
  133. package/dist/data/chat/clickyOperationsToTools.cjs.map +1 -1
  134. package/dist/data/chat/clickyOperationsToTools.d.ts.map +1 -1
  135. package/dist/data/chat/clickyOperationsToTools.js +2 -1
  136. package/dist/data/chat/clickyOperationsToTools.js.map +1 -1
  137. package/dist/data/chat/index.d.ts +2 -1
  138. package/dist/data/chat/index.d.ts.map +1 -1
  139. package/dist/data/chat/tool-policy.cjs +132 -0
  140. package/dist/data/chat/tool-policy.cjs.map +1 -0
  141. package/dist/data/chat/tool-policy.d.ts +81 -0
  142. package/dist/data/chat/tool-policy.d.ts.map +1 -0
  143. package/dist/data/chat/tool-policy.js +132 -0
  144. package/dist/data/chat/tool-policy.js.map +1 -0
  145. package/dist/data/chat/types.cjs +20 -0
  146. package/dist/data/chat/types.cjs.map +1 -1
  147. package/dist/data/chat/types.d.ts +22 -2
  148. package/dist/data/chat/types.d.ts.map +1 -1
  149. package/dist/data/chat/types.js +20 -0
  150. package/dist/data/chat/types.js.map +1 -1
  151. package/dist/data/debugConsoleSignal.cjs +64 -0
  152. package/dist/data/debugConsoleSignal.cjs.map +1 -0
  153. package/dist/data/debugConsoleSignal.d.ts +83 -0
  154. package/dist/data/debugConsoleSignal.d.ts.map +1 -0
  155. package/dist/data/debugConsoleSignal.js +64 -0
  156. package/dist/data/debugConsoleSignal.js.map +1 -0
  157. package/dist/data/har/HarPanel.cjs +20 -14
  158. package/dist/data/har/HarPanel.cjs.map +1 -1
  159. package/dist/data/har/HarPanel.d.ts.map +1 -1
  160. package/dist/data/har/HarPanel.js +20 -14
  161. package/dist/data/har/HarPanel.js.map +1 -1
  162. package/dist/data/har/types.d.ts +18 -0
  163. package/dist/data/har/types.d.ts.map +1 -1
  164. package/dist/data/query-browser/QueryBrowser.cjs +4 -42
  165. package/dist/data/query-browser/QueryBrowser.cjs.map +1 -1
  166. package/dist/data/query-browser/QueryBrowser.d.ts.map +1 -1
  167. package/dist/data/query-browser/QueryBrowser.js +5 -43
  168. package/dist/data/query-browser/QueryBrowser.js.map +1 -1
  169. package/dist/data/query-browser/QueryBrowser.types.cjs.map +1 -1
  170. package/dist/data/query-browser/QueryBrowser.types.d.ts +0 -1
  171. package/dist/data/query-browser/QueryBrowser.types.d.ts.map +1 -1
  172. package/dist/data/query-browser/QueryBrowser.types.js.map +1 -1
  173. package/dist/data/useDebugAction.cjs +29 -0
  174. package/dist/data/useDebugAction.cjs.map +1 -0
  175. package/dist/data/useDebugAction.d.ts +16 -0
  176. package/dist/data/useDebugAction.d.ts.map +1 -0
  177. package/dist/data/useDebugAction.js +29 -0
  178. package/dist/data/useDebugAction.js.map +1 -0
  179. package/dist/data/version-info.cjs +3 -3
  180. package/dist/data/version-info.js +3 -3
  181. package/dist/data.cjs +16 -8
  182. package/dist/data.cjs.map +1 -1
  183. package/dist/data.d.ts +3 -2
  184. package/dist/data.d.ts.map +1 -1
  185. package/dist/data.js +16 -8
  186. package/dist/devtools/DebugConsole.cjs +138 -0
  187. package/dist/devtools/DebugConsole.cjs.map +1 -0
  188. package/dist/devtools/DebugConsole.d.ts +23 -0
  189. package/dist/devtools/DebugConsole.d.ts.map +1 -0
  190. package/dist/devtools/DebugConsole.js +138 -0
  191. package/dist/devtools/DebugConsole.js.map +1 -0
  192. package/dist/devtools/DebugConsoleButton.cjs +59 -0
  193. package/dist/devtools/DebugConsoleButton.cjs.map +1 -0
  194. package/dist/devtools/DebugConsoleButton.d.ts +20 -0
  195. package/dist/devtools/DebugConsoleButton.d.ts.map +1 -0
  196. package/dist/devtools/DebugConsoleButton.js +59 -0
  197. package/dist/devtools/DebugConsoleButton.js.map +1 -0
  198. package/dist/devtools/DebugConsoleDock.cjs +112 -0
  199. package/dist/devtools/DebugConsoleDock.cjs.map +1 -0
  200. package/dist/devtools/DebugConsoleDock.d.ts +26 -0
  201. package/dist/devtools/DebugConsoleDock.d.ts.map +1 -0
  202. package/dist/devtools/DebugConsoleDock.js +112 -0
  203. package/dist/devtools/DebugConsoleDock.js.map +1 -0
  204. package/dist/devtools/ExecutionDetail.cjs +135 -0
  205. package/dist/devtools/ExecutionDetail.cjs.map +1 -0
  206. package/dist/devtools/ExecutionDetail.d.ts +19 -0
  207. package/dist/devtools/ExecutionDetail.d.ts.map +1 -0
  208. package/dist/devtools/ExecutionDetail.js +135 -0
  209. package/dist/devtools/ExecutionDetail.js.map +1 -0
  210. package/dist/devtools/debugClient.cjs +172 -0
  211. package/dist/devtools/debugClient.cjs.map +1 -0
  212. package/dist/devtools/debugClient.d.ts +98 -0
  213. package/dist/devtools/debugClient.d.ts.map +1 -0
  214. package/dist/devtools/debugClient.js +172 -0
  215. package/dist/devtools/debugClient.js.map +1 -0
  216. package/dist/devtools/debugStore.cjs +115 -0
  217. package/dist/devtools/debugStore.cjs.map +1 -0
  218. package/dist/devtools/debugStore.d.ts +83 -0
  219. package/dist/devtools/debugStore.d.ts.map +1 -0
  220. package/dist/devtools/debugStore.js +115 -0
  221. package/dist/devtools/debugStore.js.map +1 -0
  222. package/dist/devtools/dockState.cjs +51 -0
  223. package/dist/devtools/dockState.cjs.map +1 -0
  224. package/dist/devtools/dockState.d.ts +31 -0
  225. package/dist/devtools/dockState.d.ts.map +1 -0
  226. package/dist/devtools/dockState.js +51 -0
  227. package/dist/devtools/dockState.js.map +1 -0
  228. package/dist/devtools/tabs/ConsoleTab.cjs +36 -0
  229. package/dist/devtools/tabs/ConsoleTab.cjs.map +1 -0
  230. package/dist/devtools/tabs/ConsoleTab.d.ts +19 -0
  231. package/dist/devtools/tabs/ConsoleTab.d.ts.map +1 -0
  232. package/dist/devtools/tabs/ConsoleTab.js +36 -0
  233. package/dist/devtools/tabs/ConsoleTab.js.map +1 -0
  234. package/dist/devtools/tabs/InspectionCaches.cjs +150 -0
  235. package/dist/devtools/tabs/InspectionCaches.cjs.map +1 -0
  236. package/dist/devtools/tabs/InspectionCaches.d.ts +23 -0
  237. package/dist/devtools/tabs/InspectionCaches.d.ts.map +1 -0
  238. package/dist/devtools/tabs/InspectionCaches.js +150 -0
  239. package/dist/devtools/tabs/InspectionCaches.js.map +1 -0
  240. package/dist/devtools/tabs/InspectionTab.cjs +316 -0
  241. package/dist/devtools/tabs/InspectionTab.cjs.map +1 -0
  242. package/dist/devtools/tabs/InspectionTab.d.ts +27 -0
  243. package/dist/devtools/tabs/InspectionTab.d.ts.map +1 -0
  244. package/dist/devtools/tabs/InspectionTab.js +316 -0
  245. package/dist/devtools/tabs/InspectionTab.js.map +1 -0
  246. package/dist/devtools/tabs/NetworkTab.cjs +91 -0
  247. package/dist/devtools/tabs/NetworkTab.cjs.map +1 -0
  248. package/dist/devtools/tabs/NetworkTab.d.ts +20 -0
  249. package/dist/devtools/tabs/NetworkTab.d.ts.map +1 -0
  250. package/dist/devtools/tabs/NetworkTab.js +91 -0
  251. package/dist/devtools/tabs/NetworkTab.js.map +1 -0
  252. package/dist/devtools/tabs/QueriesTab.cjs +85 -0
  253. package/dist/devtools/tabs/QueriesTab.cjs.map +1 -0
  254. package/dist/devtools/tabs/QueriesTab.d.ts +24 -0
  255. package/dist/devtools/tabs/QueriesTab.d.ts.map +1 -0
  256. package/dist/devtools/tabs/QueriesTab.js +85 -0
  257. package/dist/devtools/tabs/QueriesTab.js.map +1 -0
  258. package/dist/devtools/types.cjs +26 -0
  259. package/dist/devtools/types.cjs.map +1 -0
  260. package/dist/devtools/types.d.ts +235 -0
  261. package/dist/devtools/types.d.ts.map +1 -0
  262. package/dist/devtools/types.js +26 -0
  263. package/dist/devtools/types.js.map +1 -0
  264. package/dist/devtools/useDebugStream.cjs +46 -0
  265. package/dist/devtools/useDebugStream.cjs.map +1 -0
  266. package/dist/devtools/useDebugStream.d.ts +23 -0
  267. package/dist/devtools/useDebugStream.d.ts.map +1 -0
  268. package/dist/devtools/useDebugStream.js +46 -0
  269. package/dist/devtools/useDebugStream.js.map +1 -0
  270. package/dist/devtools.cjs +51 -0
  271. package/dist/devtools.cjs.map +1 -0
  272. package/dist/devtools.d.ts +37 -0
  273. package/dist/devtools.d.ts.map +1 -0
  274. package/dist/devtools.js +51 -0
  275. package/dist/devtools.js.map +1 -0
  276. package/dist/icons/components/UiOpenScad.cjs +10 -0
  277. package/dist/icons/components/UiOpenScad.cjs.map +1 -0
  278. package/dist/icons/components/UiOpenScad.d.ts +9 -0
  279. package/dist/icons/components/UiOpenScad.d.ts.map +1 -0
  280. package/dist/icons/components/UiOpenScad.js +10 -0
  281. package/dist/icons/components/UiOpenScad.js.map +1 -0
  282. package/dist/icons/index.d.ts +1 -0
  283. package/dist/icons/index.d.ts.map +1 -1
  284. package/dist/icons/registry.cjs +2 -0
  285. package/dist/icons/registry.cjs.map +1 -1
  286. package/dist/icons/registry.d.ts.map +1 -1
  287. package/dist/icons/registry.js +2 -0
  288. package/dist/icons/registry.js.map +1 -1
  289. package/dist/icons.cjs +2 -0
  290. package/dist/icons.cjs.map +1 -1
  291. package/dist/icons.js +2 -0
  292. package/dist/icons.js.map +1 -1
  293. package/dist/index.cjs +20 -8
  294. package/dist/index.cjs.map +1 -1
  295. package/dist/index.js +20 -8
  296. package/dist/index.js.map +1 -1
  297. package/dist/layout/AppShell.cjs +15 -10
  298. package/dist/layout/AppShell.cjs.map +1 -1
  299. package/dist/layout/AppShell.d.ts +11 -0
  300. package/dist/layout/AppShell.d.ts.map +1 -1
  301. package/dist/layout/AppShell.js +15 -10
  302. package/dist/layout/AppShell.js.map +1 -1
  303. package/dist/layout/AppShell.nav.cjs +170 -49
  304. package/dist/layout/AppShell.nav.cjs.map +1 -1
  305. package/dist/layout/AppShell.nav.d.ts.map +1 -1
  306. package/dist/layout/AppShell.nav.js +170 -49
  307. package/dist/layout/AppShell.nav.js.map +1 -1
  308. package/dist/overlay/CellActions.cjs +18 -124
  309. package/dist/overlay/CellActions.cjs.map +1 -1
  310. package/dist/overlay/CellActions.d.ts +1 -1
  311. package/dist/overlay/CellActions.d.ts.map +1 -1
  312. package/dist/overlay/CellActions.js +18 -124
  313. package/dist/overlay/CellActions.js.map +1 -1
  314. package/dist/overlay/ContextMenu.cjs +144 -0
  315. package/dist/overlay/ContextMenu.cjs.map +1 -0
  316. package/dist/overlay/ContextMenu.d.ts +8 -0
  317. package/dist/overlay/ContextMenu.d.ts.map +1 -0
  318. package/dist/overlay/ContextMenu.js +144 -0
  319. package/dist/overlay/ContextMenu.js.map +1 -0
  320. package/dist/profiles/connections/connectionBrowserModel.cjs +14 -65
  321. package/dist/profiles/connections/connectionBrowserModel.cjs.map +1 -1
  322. package/dist/profiles/connections/connectionBrowserModel.d.ts +35 -35
  323. package/dist/profiles/connections/connectionBrowserModel.d.ts.map +1 -1
  324. package/dist/profiles/connections/connectionBrowserModel.js +14 -65
  325. package/dist/profiles/connections/connectionBrowserModel.js.map +1 -1
  326. package/dist/profiles/connections/connectionQueryWorkspace.cjs +29 -9
  327. package/dist/profiles/connections/connectionQueryWorkspace.cjs.map +1 -1
  328. package/dist/profiles/connections/connectionQueryWorkspace.d.ts +3 -2
  329. package/dist/profiles/connections/connectionQueryWorkspace.d.ts.map +1 -1
  330. package/dist/profiles/connections/connectionQueryWorkspace.js +30 -10
  331. package/dist/profiles/connections/connectionQueryWorkspace.js.map +1 -1
  332. package/dist/profiles/connections/connectionQueryWorkspaceModel.cjs +15 -0
  333. package/dist/profiles/connections/connectionQueryWorkspaceModel.cjs.map +1 -1
  334. package/dist/profiles/connections/connectionQueryWorkspaceModel.d.ts +22 -1
  335. package/dist/profiles/connections/connectionQueryWorkspaceModel.d.ts.map +1 -1
  336. package/dist/profiles/connections/connectionQueryWorkspaceModel.js +15 -0
  337. package/dist/profiles/connections/connectionQueryWorkspaceModel.js.map +1 -1
  338. package/dist/profiles/connections/index.d.ts +1 -0
  339. package/dist/profiles/connections/index.d.ts.map +1 -1
  340. package/dist/profiles/connections/inspectionStatus.cjs +44 -0
  341. package/dist/profiles/connections/inspectionStatus.cjs.map +1 -0
  342. package/dist/profiles/connections/inspectionStatus.d.ts +5 -0
  343. package/dist/profiles/connections/inspectionStatus.d.ts.map +1 -0
  344. package/dist/profiles/connections/inspectionStatus.js +44 -0
  345. package/dist/profiles/connections/inspectionStatus.js.map +1 -0
  346. package/dist/profiles/connections/useInspection.cjs +123 -0
  347. package/dist/profiles/connections/useInspection.cjs.map +1 -0
  348. package/dist/profiles/connections/useInspection.d.ts +35 -0
  349. package/dist/profiles/connections/useInspection.d.ts.map +1 -0
  350. package/dist/profiles/connections/useInspection.js +123 -0
  351. package/dist/profiles/connections/useInspection.js.map +1 -0
  352. package/dist/profiles/editor/profileBuilderWorkspace.cjs +55 -23
  353. package/dist/profiles/editor/profileBuilderWorkspace.cjs.map +1 -1
  354. package/dist/profiles/editor/profileBuilderWorkspace.d.ts.map +1 -1
  355. package/dist/profiles/editor/profileBuilderWorkspace.js +56 -24
  356. package/dist/profiles/editor/profileBuilderWorkspace.js.map +1 -1
  357. package/dist/profiles/elasticsearch/esQueryBuilder.cjs +12 -3
  358. package/dist/profiles/elasticsearch/esQueryBuilder.cjs.map +1 -1
  359. package/dist/profiles/elasticsearch/esQueryBuilder.d.ts +2 -2
  360. package/dist/profiles/elasticsearch/esQueryBuilder.d.ts.map +1 -1
  361. package/dist/profiles/elasticsearch/esQueryBuilder.js +12 -3
  362. package/dist/profiles/elasticsearch/esQueryBuilder.js.map +1 -1
  363. package/dist/profiles/fields/profileFieldEditor.cjs +137 -22
  364. package/dist/profiles/fields/profileFieldEditor.cjs.map +1 -1
  365. package/dist/profiles/fields/profileFieldEditor.d.ts.map +1 -1
  366. package/dist/profiles/fields/profileFieldEditor.js +137 -22
  367. package/dist/profiles/fields/profileFieldEditor.js.map +1 -1
  368. package/dist/profiles/fields/profileFieldFilterEditor.cjs +171 -82
  369. package/dist/profiles/fields/profileFieldFilterEditor.cjs.map +1 -1
  370. package/dist/profiles/fields/profileFieldFilterEditor.d.ts +0 -9
  371. package/dist/profiles/fields/profileFieldFilterEditor.d.ts.map +1 -1
  372. package/dist/profiles/fields/profileFieldFilterEditor.js +174 -85
  373. package/dist/profiles/fields/profileFieldFilterEditor.js.map +1 -1
  374. package/dist/profiles/fields/profileFieldIcons.cjs +6 -3
  375. package/dist/profiles/fields/profileFieldIcons.cjs.map +1 -1
  376. package/dist/profiles/fields/profileFieldIcons.js +6 -3
  377. package/dist/profiles/fields/profileFieldIcons.js.map +1 -1
  378. package/dist/profiles/query/profileSampleFilterLookup.cjs +34 -0
  379. package/dist/profiles/query/profileSampleFilterLookup.cjs.map +1 -0
  380. package/dist/profiles/query/profileSampleFilterLookup.d.ts +12 -0
  381. package/dist/profiles/query/profileSampleFilterLookup.d.ts.map +1 -0
  382. package/dist/profiles/query/profileSampleFilterLookup.js +34 -0
  383. package/dist/profiles/query/profileSampleFilterLookup.js.map +1 -0
  384. package/dist/profiles/query/profileSamplePayload.cjs +6 -4
  385. package/dist/profiles/query/profileSamplePayload.cjs.map +1 -1
  386. package/dist/profiles/query/profileSamplePayload.d.ts +11 -4
  387. package/dist/profiles/query/profileSamplePayload.d.ts.map +1 -1
  388. package/dist/profiles/query/profileSamplePayload.js +6 -4
  389. package/dist/profiles/query/profileSamplePayload.js.map +1 -1
  390. package/dist/profiles/query/profileSampleResult.cjs +26 -0
  391. package/dist/profiles/query/profileSampleResult.cjs.map +1 -0
  392. package/dist/profiles/query/profileSampleResult.d.ts +11 -0
  393. package/dist/profiles/query/profileSampleResult.d.ts.map +1 -0
  394. package/dist/profiles/query/profileSampleResult.js +26 -0
  395. package/dist/profiles/query/profileSampleResult.js.map +1 -0
  396. package/dist/profiles/query/queryTargetPicker.cjs +5 -3
  397. package/dist/profiles/query/queryTargetPicker.cjs.map +1 -1
  398. package/dist/profiles/query/queryTargetPicker.d.ts +3 -2
  399. package/dist/profiles/query/queryTargetPicker.d.ts.map +1 -1
  400. package/dist/profiles/query/queryTargetPicker.js +5 -3
  401. package/dist/profiles/query/queryTargetPicker.js.map +1 -1
  402. package/dist/profiles/wizard/profileWizard.cjs +14 -5
  403. package/dist/profiles/wizard/profileWizard.cjs.map +1 -1
  404. package/dist/profiles/wizard/profileWizard.d.ts.map +1 -1
  405. package/dist/profiles/wizard/profileWizard.js +15 -6
  406. package/dist/profiles/wizard/profileWizard.js.map +1 -1
  407. package/dist/profiles/wizard/profileWizardModel.cjs +35 -6
  408. package/dist/profiles/wizard/profileWizardModel.cjs.map +1 -1
  409. package/dist/profiles/wizard/profileWizardModel.d.ts +29 -4
  410. package/dist/profiles/wizard/profileWizardModel.d.ts.map +1 -1
  411. package/dist/profiles/wizard/profileWizardModel.js +35 -6
  412. package/dist/profiles/wizard/profileWizardModel.js.map +1 -1
  413. package/dist/profiles/wizard/profileWizardQueryStep.cjs +44 -11
  414. package/dist/profiles/wizard/profileWizardQueryStep.cjs.map +1 -1
  415. package/dist/profiles/wizard/profileWizardQueryStep.d.ts.map +1 -1
  416. package/dist/profiles/wizard/profileWizardQueryStep.js +46 -13
  417. package/dist/profiles/wizard/profileWizardQueryStep.js.map +1 -1
  418. package/dist/profiles/wizard/profileWizardSteps.cjs +33 -2
  419. package/dist/profiles/wizard/profileWizardSteps.cjs.map +1 -1
  420. package/dist/profiles/wizard/profileWizardSteps.d.ts +11 -2
  421. package/dist/profiles/wizard/profileWizardSteps.d.ts.map +1 -1
  422. package/dist/profiles/wizard/profileWizardSteps.js +33 -2
  423. package/dist/profiles/wizard/profileWizardSteps.js.map +1 -1
  424. package/dist/profiles.cjs +8 -2
  425. package/dist/profiles.cjs.map +1 -1
  426. package/dist/profiles.js +10 -4
  427. package/dist/profiles.js.map +1 -1
  428. package/dist/rpc/FilterForm.cjs +21 -237
  429. package/dist/rpc/FilterForm.cjs.map +1 -1
  430. package/dist/rpc/FilterForm.d.ts +3 -1
  431. package/dist/rpc/FilterForm.d.ts.map +1 -1
  432. package/dist/rpc/FilterForm.js +21 -237
  433. package/dist/rpc/FilterForm.js.map +1 -1
  434. package/dist/rpc/FilterFormFields.cjs +240 -0
  435. package/dist/rpc/FilterFormFields.cjs.map +1 -0
  436. package/dist/rpc/FilterFormFields.d.ts +12 -0
  437. package/dist/rpc/FilterFormFields.d.ts.map +1 -0
  438. package/dist/rpc/FilterFormFields.js +240 -0
  439. package/dist/rpc/FilterFormFields.js.map +1 -0
  440. package/dist/rpc/router.cjs +3 -2
  441. package/dist/rpc/router.cjs.map +1 -1
  442. package/dist/rpc/router.d.ts.map +1 -1
  443. package/dist/rpc/router.js +3 -2
  444. package/dist/rpc/router.js.map +1 -1
  445. package/dist/rpc/types.cjs.map +1 -1
  446. package/dist/rpc/types.d.ts +3 -1
  447. package/dist/rpc/types.d.ts.map +1 -1
  448. package/dist/rpc/types.js.map +1 -1
  449. package/dist/styles.css +1 -1
  450. package/package.json +6 -1
  451. package/dist/data/query-info/QueryInfoDialog.cjs +0 -106
  452. package/dist/data/query-info/QueryInfoDialog.cjs.map +0 -1
  453. package/dist/data/query-info/QueryInfoDialog.d.ts +0 -16
  454. package/dist/data/query-info/QueryInfoDialog.d.ts.map +0 -1
  455. package/dist/data/query-info/QueryInfoDialog.js +0 -106
  456. package/dist/data/query-info/QueryInfoDialog.js.map +0 -1
  457. package/dist/data/query-info/index.d.ts +0 -4
  458. package/dist/data/query-info/index.d.ts.map +0 -1
  459. package/dist/data/query-info/queryInfo.cjs +0 -42
  460. package/dist/data/query-info/queryInfo.cjs.map +0 -1
  461. package/dist/data/query-info/queryInfo.d.ts +0 -45
  462. package/dist/data/query-info/queryInfo.d.ts.map +0 -1
  463. package/dist/data/query-info/queryInfo.js +0 -42
  464. package/dist/data/query-info/queryInfo.js.map +0 -1
  465. package/dist/data/query-info/useQueryInfo.cjs +0 -48
  466. package/dist/data/query-info/useQueryInfo.cjs.map +0 -1
  467. package/dist/data/query-info/useQueryInfo.d.ts +0 -26
  468. package/dist/data/query-info/useQueryInfo.d.ts.map +0 -1
  469. package/dist/data/query-info/useQueryInfo.js +0 -48
  470. package/dist/data/query-info/useQueryInfo.js.map +0 -1
@@ -8,12 +8,40 @@ import { DropdownMenu } from "../overlay/DropdownMenu.js";
8
8
  import { parseTimestamp, formatRelativeTime } from "../data/cells/timestamp-format.js";
9
9
  import { CommentAuthorAvatar } from "./CommentAuthor.js";
10
10
  import { CommentMarkdown } from "./CommentMarkdown.js";
11
- import { authorDisplayName, truncatePlain, resolveFacetOption, toneToBadgeTone, resolveStatusConfig } from "./comment-utils.js";
11
+ import { isUnresolved, authorDisplayName, truncatePlain, resolveFacetOption, toneToBadgeTone, resolveStatusConfig } from "./comment-utils.js";
12
+ import { UiCheck } from "../icons/components/UiCheck.js";
12
13
  import { UiFullscreen } from "../icons/components/UiFullscreen.js";
13
14
  import { UiDotsVertical } from "../icons/components/UiDotsVertical.js";
14
- import { UiCheck } from "../icons/components/UiCheck.js";
15
+ import { UiThumbsUp } from "../icons/components/UiThumbsUp.js";
16
+ import { UiThumbsDown } from "../icons/components/UiThumbsDown.js";
15
17
  import { UiClose } from "../icons/components/UiClose.js";
16
18
  import { UiTrash } from "../icons/components/UiTrash.js";
19
+ function completionStatus(config) {
20
+ for (const completionName of ["resolved", "closed"]) {
21
+ const status = config.statuses.find(
22
+ (candidate) => candidate.value.toLowerCase() === completionName || candidate.label.toLowerCase() === completionName
23
+ );
24
+ if (status) return status;
25
+ }
26
+ return void 0;
27
+ }
28
+ function completionActionLabel(status) {
29
+ return status.value.toLowerCase() === "closed" || status.label.toLowerCase() === "closed" ? "Close comment" : "Resolve comment";
30
+ }
31
+ function RatingChip({ rating }) {
32
+ if (!rating) return null;
33
+ const positive = rating === "positive";
34
+ return /* @__PURE__ */ jsx(
35
+ Badge,
36
+ {
37
+ variant: "soft",
38
+ tone: positive ? "success" : "danger",
39
+ size: "xs",
40
+ icon: positive ? UiThumbsUp : UiThumbsDown,
41
+ children: positive ? "Positive rating" : "Negative rating"
42
+ }
43
+ );
44
+ }
17
45
  function StatusChip({
18
46
  status,
19
47
  config,
@@ -114,7 +142,7 @@ function statusMenuItems(comment, config, isReply, opts) {
114
142
  }
115
143
  if (!isReply && opts.onUpdateStatus) {
116
144
  for (const status of config.statuses) {
117
- if (status.value === opts.resolvedStatus) continue;
145
+ if (status.value === opts.completionStatus) continue;
118
146
  const current = status.value === comment.status;
119
147
  items.push({
120
148
  label: /* @__PURE__ */ jsxs(
@@ -177,10 +205,8 @@ function CommentBody({
177
205
  const [statusError, setStatusError] = useState("");
178
206
  const isReply = Boolean(comment.parentId);
179
207
  const date = parseTimestamp(comment.createdAt);
180
- const resolvedStatus = config.statuses.find(
181
- (status) => status.value.toLowerCase() === "resolved" || status.label.toLowerCase() === "resolved"
182
- );
183
- const canResolve = !isReply && onUpdateStatus != null && resolvedStatus != null && comment.status !== resolvedStatus.value;
208
+ const completion = completionStatus(config);
209
+ const canComplete = !isReply && onUpdateStatus != null && completion != null && isUnresolved(config, comment.status);
184
210
  const updateStatus = async (status) => {
185
211
  setStatusError("");
186
212
  try {
@@ -196,7 +222,7 @@ function CommentBody({
196
222
  ...onDelete ? { onDelete } : {},
197
223
  ...onCollapse ? { onCollapse } : {},
198
224
  ...onMaximize ? { onMaximize } : {},
199
- ...resolvedStatus ? { resolvedStatus: resolvedStatus.value } : {}
225
+ ...completion ? { completionStatus: completion.value } : {}
200
226
  });
201
227
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
202
228
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5", children: [
@@ -213,15 +239,18 @@ function CommentBody({
213
239
  }
214
240
  )
215
241
  ] }),
216
- !isReply && /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: /* @__PURE__ */ jsx(FacetBadges, { comment, config }) })
242
+ !isReply && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap gap-1", children: [
243
+ /* @__PURE__ */ jsx(RatingChip, { rating: comment.rating }),
244
+ /* @__PURE__ */ jsx(FacetBadges, { comment, config })
245
+ ] })
217
246
  ] }),
218
247
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
219
- canResolve && /* @__PURE__ */ jsx(
248
+ canComplete && /* @__PURE__ */ jsx(
220
249
  IconAction,
221
250
  {
222
251
  icon: UiCheck,
223
- label: "Resolve comment",
224
- onClick: () => void updateStatus(resolvedStatus.value),
252
+ label: completionActionLabel(completion),
253
+ onClick: () => void updateStatus(completion.value),
225
254
  className: "hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300"
226
255
  }
227
256
  ),
@@ -245,7 +274,7 @@ function CommentBody({
245
274
  )
246
275
  ] })
247
276
  ] }),
248
- /* @__PURE__ */ jsx("div", { className: "text-[13px] leading-5 text-foreground", children: renderBody ? renderBody(comment.body) : /* @__PURE__ */ jsx(CommentMarkdown, { text: comment.body }) }),
277
+ comment.body.trim() && /* @__PURE__ */ jsx("div", { className: "text-[13px] leading-5 text-foreground", children: renderBody ? renderBody(comment.body) : /* @__PURE__ */ jsx(CommentMarkdown, { text: comment.body }) }),
249
278
  statusError && /* @__PURE__ */ jsxs(
250
279
  "div",
251
280
  {
@@ -280,7 +309,22 @@ function CommentCard(props) {
280
309
  const { comment, config, compact, defaultExpanded } = props;
281
310
  const [expanded, setExpanded] = useState(defaultExpanded ?? false);
282
311
  const [maximized, setMaximized] = useState(false);
312
+ const [collapsedStatusError, setCollapsedStatusError] = useState("");
283
313
  const isReply = Boolean(comment.parentId);
314
+ const completion = completionStatus(config);
315
+ const canComplete = !isReply && props.onUpdateStatus != null && completion != null && isUnresolved(config, comment.status);
316
+ const completeCollapsed = async () => {
317
+ var _a;
318
+ if (!completion) return;
319
+ setCollapsedStatusError("");
320
+ try {
321
+ await ((_a = props.onUpdateStatus) == null ? void 0 : _a.call(props, completion.value));
322
+ } catch (error) {
323
+ setCollapsedStatusError(
324
+ error instanceof Error ? error.message : "Unexpected error"
325
+ );
326
+ }
327
+ };
284
328
  const body = /* @__PURE__ */ jsx(
285
329
  CommentBody,
286
330
  {
@@ -324,8 +368,28 @@ function CommentCard(props) {
324
368
  children: [
325
369
  /* @__PURE__ */ jsx(CommentAuthorAvatar, { author: comment.author, bare: true }),
326
370
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] font-semibold text-foreground", children: authorDisplayName(comment.author) }),
327
- !isReply && /* @__PURE__ */ jsx(FacetBadges, { comment, config, compact: true }),
328
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[12px] text-foreground/80", children: truncatePlain(comment.body) }),
371
+ !isReply && /* @__PURE__ */ jsxs(Fragment, { children: [
372
+ /* @__PURE__ */ jsx(RatingChip, { rating: comment.rating }),
373
+ /* @__PURE__ */ jsx(FacetBadges, { comment, config, compact: true })
374
+ ] }),
375
+ /* @__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" }),
376
+ canComplete && /* @__PURE__ */ jsx(
377
+ "span",
378
+ {
379
+ role: "presentation",
380
+ onClick: (e) => e.stopPropagation(),
381
+ onKeyDown: (e) => e.stopPropagation(),
382
+ children: /* @__PURE__ */ jsx(
383
+ IconAction,
384
+ {
385
+ icon: UiCheck,
386
+ label: completionActionLabel(completion),
387
+ onClick: () => void completeCollapsed(),
388
+ className: "hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300"
389
+ }
390
+ )
391
+ }
392
+ ),
329
393
  /* @__PURE__ */ jsx(
330
394
  "span",
331
395
  {
@@ -361,6 +425,17 @@ function CommentCard(props) {
361
425
  ]
362
426
  }
363
427
  ),
428
+ collapsedStatusError && /* @__PURE__ */ jsxs(
429
+ "div",
430
+ {
431
+ role: "alert",
432
+ className: "rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive",
433
+ children: [
434
+ "Couldn't update comment: ",
435
+ collapsedStatusError
436
+ ]
437
+ }
438
+ ),
364
439
  modal
365
440
  ] });
366
441
  }
@@ -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 { Modal } from \"../overlay/Modal\";\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 UiTrash,\n} from \"../icons\";\nimport { CommentAuthorAvatar } from \"./CommentAuthor\";\nimport { CommentMarkdown } from \"./CommentMarkdown\";\nimport {\n authorDisplayName,\n resolveFacetOption,\n resolveStatusConfig,\n toneToBadgeTone,\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 /** 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\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 resolvedStatus?: 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.resolvedStatus) 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; onMaximize?: () => void }) {\n const [statusError, setStatusError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const date = parseTimestamp(comment.createdAt);\n const resolvedStatus = config.statuses.find(\n (status) =>\n status.value.toLowerCase() === \"resolved\" ||\n status.label.toLowerCase() === \"resolved\",\n );\n const canResolve =\n !isReply &&\n onUpdateStatus != null &&\n resolvedStatus != null &&\n comment.status !== resolvedStatus.value;\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 ...(resolvedStatus ? { resolvedStatus: resolvedStatus.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 <FacetBadges comment={comment} config={config} />\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {canResolve && (\n <IconAction\n icon={UiCheck}\n label=\"Resolve comment\"\n onClick={() => void updateStatus(resolvedStatus.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 <div className=\"text-[13px] leading-5 text-foreground\">\n {renderBody ? (\n renderBody(comment.body)\n ) : (\n <CommentMarkdown text={comment.body} />\n )}\n </div>\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. The full view can be maximized into a modal. Fully controlled — all\n * mutations are delegated to 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 [maximized, setMaximized] = useState(false);\n const isReply = Boolean(comment.parentId);\n\n const body = (\n <CommentBody\n {...props}\n onMaximize={() => setMaximized(true)}\n {...(expanded ? { onCollapse: () => setExpanded(false) } : {})}\n />\n );\n\n const modal = (\n <Modal\n open={maximized}\n onClose={() => setMaximized(false)}\n title=\"Comment\"\n size=\"lg\"\n >\n <CommentBody {...props} />\n </Modal>\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 <FacetBadges comment={comment} config={config} compact />\n )}\n <span className=\"min-w-0 flex-1 truncate text-[12px] text-foreground/80\">\n {truncatePlain(comment.body)}\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={[\n { label: \"Expand\", onSelect: () => setExpanded(true) },\n {\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: () => setMaximized(true),\n },\n ]}\n />\n </span>\n </div>\n {modal}\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 {modal}\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 { Badge } from \"../data/Badge\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { Modal } from \"../overlay/Modal\";\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\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; onMaximize?: () => 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. The full view can be maximized into a modal. Fully controlled — all\n * mutations are delegated to 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 [maximized, setMaximized] = useState(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 onMaximize={() => setMaximized(true)}\n {...(expanded ? { onCollapse: () => setExpanded(false) } : {})}\n />\n );\n\n const modal = (\n <Modal\n open={maximized}\n onClose={() => setMaximized(false)}\n title=\"Comment\"\n size=\"lg\"\n >\n <CommentBody {...props} />\n </Modal>\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 {\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: () => setMaximized(true),\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 {modal}\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 {modal}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -8,6 +8,8 @@ const DropdownMenu = require("../overlay/DropdownMenu.cjs");
8
8
  const MentionTextarea = require("./MentionTextarea.cjs");
9
9
  const commentUtils = require("./comment-utils.cjs");
10
10
  const UiClose = require("../icons/components/UiClose.cjs");
11
+ const UiThumbsUp = require("../icons/components/UiThumbsUp.cjs");
12
+ const UiThumbsDown = require("../icons/components/UiThumbsDown.cjs");
11
13
  const UiArrowUp = require("../icons/components/UiArrowUp.cjs");
12
14
  const FACET_DOT_CLASS = {
13
15
  neutral: "bg-muted-foreground",
@@ -95,6 +97,7 @@ function CommentComposer({
95
97
  const [open, setOpen] = react.useState(!collapsible || autoFocus);
96
98
  const [body, setBody] = react.useState("");
97
99
  const [facets, setFacets] = react.useState({});
100
+ const [rating, setRating] = react.useState();
98
101
  const [submitting, setSubmitting] = react.useState(false);
99
102
  const submittingRef = react.useRef(false);
100
103
  const mentionsRef = react.useRef(/* @__PURE__ */ new Map());
@@ -102,6 +105,7 @@ function CommentComposer({
102
105
  function reset() {
103
106
  setBody("");
104
107
  setFacets({});
108
+ setRating(void 0);
105
109
  mentionsRef.current.clear();
106
110
  }
107
111
  function setFacet(key, value) {
@@ -115,7 +119,7 @@ function CommentComposer({
115
119
  }
116
120
  async function submit() {
117
121
  const text = body.trim();
118
- if (!text || submittingRef.current) return;
122
+ if (!text && !rating || submittingRef.current) return;
119
123
  submittingRef.current = true;
120
124
  setSubmitting(true);
121
125
  const mentions = [...mentionsRef.current.values()].filter(
@@ -125,6 +129,7 @@ function CommentComposer({
125
129
  await (onCreate == null ? void 0 : onCreate({
126
130
  body: text,
127
131
  anchor: anchor ?? null,
132
+ ...rating ? { rating } : {},
128
133
  ...Object.keys(facets).length > 0 ? { facets } : {},
129
134
  ...mentions.length > 0 ? { mentions } : {}
130
135
  }));
@@ -187,6 +192,24 @@ function CommentComposer({
187
192
  }
188
193
  ),
189
194
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-x-3 bottom-2.5 flex items-center gap-1.5", children: [
195
+ [
196
+ ["positive", "Positive rating", UiThumbsUp.UiThumbsUp],
197
+ ["negative", "Negative rating", UiThumbsDown.UiThumbsDown]
198
+ ].map(([value, label, icon]) => /* @__PURE__ */ jsxRuntime.jsx(
199
+ "button",
200
+ {
201
+ type: "button",
202
+ "aria-label": label,
203
+ "aria-pressed": rating === value,
204
+ onClick: () => setRating((current) => current === value ? void 0 : value),
205
+ className: utils.cn(
206
+ "inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
207
+ rating === value && (value === "positive" ? "bg-green-100 text-green-700 [[data-theme=dark]_&]:bg-green-500/20 [[data-theme=dark]_&]:text-green-300" : "bg-red-100 text-red-700 [[data-theme=dark]_&]:bg-red-500/20 [[data-theme=dark]_&]:text-red-300")
208
+ ),
209
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon, className: "text-sm" })
210
+ },
211
+ value
212
+ )),
190
213
  (config.facets ?? []).map((facet) => /* @__PURE__ */ jsxRuntime.jsx(
191
214
  FacetPicker,
192
215
  {
@@ -203,7 +226,7 @@ function CommentComposer({
203
226
  "data-testid": "comment-compose-send",
204
227
  "aria-label": "Post comment",
205
228
  onClick: () => void submit(),
206
- disabled: submitting || !body.trim(),
229
+ disabled: submitting || !body.trim() && !rating,
207
230
  className: "ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground",
208
231
  children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiArrowUp.UiArrowUp, className: "text-xs" })
209
232
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CommentComposer.cjs","sources":["../../src/comments/CommentComposer.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu } from \"../overlay/DropdownMenu\";\nimport { UiArrowUp, UiClose } from \"../icons\";\nimport type { BadgeTone } from \"../data/Badge\";\nimport { MentionTextarea } from \"./MentionTextarea\";\nimport { toneToBadgeTone } from \"./comment-utils\";\nimport type {\n CommentAnchor,\n CommentConfig,\n CommentCreateInput,\n CommentFacet,\n CommentMention,\n} from \"./comment-types\";\n\n// Static literals so Tailwind's source scanner emits these utilities.\nconst FACET_DOT_CLASS: Record<BadgeTone, string> = {\n neutral: \"bg-muted-foreground\",\n success: \"bg-green-500\",\n danger: \"bg-red-500\",\n warning: \"bg-yellow-500\",\n info: \"bg-blue-500\",\n};\n\nexport type CommentComposerProps = {\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n /** Start collapsed as an \"Add a comment…\" pill until clicked. */\n collapsible?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n /** Create a new root comment. */\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n /** Fired once per mention referenced by a posted comment. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\nfunction FacetPicker({\n facet,\n value,\n onChange,\n}: {\n facet: CommentFacet;\n value: string | undefined;\n onChange: (value: string | undefined) => void;\n}) {\n const selected = facet.options.find((o) => o.value === value);\n return (\n <DropdownMenu\n align=\"left\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium transition-colors\",\n selected\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60\",\n )}\n >\n {selected ? selected.label : facet.label}\n {selected && (\n <span\n role=\"presentation\"\n className=\"ml-0.5 cursor-pointer hover:text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onChange(undefined);\n }}\n >\n ×\n </span>\n )}\n </span>\n }\n >\n {(close) => (\n <div className=\"py-1\">\n {facet.options.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => {\n onChange(option.value === value ? undefined : option.value);\n close();\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-popover-foreground hover:bg-accent hover:text-accent-foreground\",\n option.value === value && \"bg-muted font-semibold\",\n )}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n FACET_DOT_CLASS[toneToBadgeTone(option.tone)],\n )}\n />\n {option.label}\n </button>\n ))}\n </div>\n )}\n </DropdownMenu>\n );\n}\n\n/**\n * The new-comment composer: a mention-aware textarea, one picker per configured\n * facet, and a send button. Starts as a pill when `collapsible`. Owns only its\n * own draft state; posting is delegated to `onCreate`.\n */\nexport function CommentComposer({\n config,\n anchor,\n collapsible = true,\n autoFocus = false,\n placeholder = \"Add a comment…\",\n onCreate,\n onMention,\n}: CommentComposerProps) {\n const [open, setOpen] = useState(!collapsible || autoFocus);\n const [body, setBody] = useState(\"\");\n const [facets, setFacets] = useState<Record<string, string>>({});\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n const mentionsRef = useRef<Map<string, CommentMention>>(new Map());\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n function reset() {\n setBody(\"\");\n setFacets({});\n mentionsRef.current.clear();\n }\n\n function setFacet(key: string, value: string | undefined) {\n setFacets((prev) => {\n if (value === undefined) {\n const { [key]: _drop, ...rest } = prev;\n return rest;\n }\n return { ...prev, [key]: value };\n });\n }\n\n async function submit() {\n const text = body.trim();\n if (!text || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n const mentions = [...mentionsRef.current.values()].filter((m) =>\n body.toLowerCase().includes(`@${m.name.toLowerCase()}`),\n );\n try {\n await onCreate?.({\n body: text,\n anchor: anchor ?? null,\n ...(Object.keys(facets).length > 0 ? { facets } : {}),\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body: text });\n reset();\n if (collapsible) setOpen(false);\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n if (!open && body.trim().length === 0) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"w-full rounded-full border border-border bg-muted/25 px-4 py-2 text-left text-sm text-muted-foreground/70 shadow-sm transition-colors hover:border-primary/30 hover:bg-muted/40\"\n >\n {placeholder}\n </button>\n );\n }\n\n return (\n <div\n data-testid=\"comment-compose-box\"\n aria-busy={submitting}\n className=\"relative rounded-2xl border border-border bg-muted/25 px-3 pt-3 pb-10 shadow-sm transition-[border-color,box-shadow] focus-within:border-primary/40 focus-within:shadow-md\"\n >\n <MentionTextarea\n ref={inputRef}\n value={body}\n onChange={setBody}\n mentionables={config.mentionables ?? []}\n onMentionSelect={(m) => mentionsRef.current.set(m.id, m)}\n onSubmit={() => void submit()}\n onCancel={() => {\n reset();\n if (collapsible) setOpen(false);\n }}\n placeholder={placeholder}\n autoFocus={autoFocus || open}\n data-testid=\"comment-compose-input\"\n className=\"min-h-[48px] pr-10\"\n />\n {collapsible && (\n <button\n type=\"button\"\n onClick={() => {\n reset();\n setOpen(false);\n }}\n aria-label=\"Close composer\"\n className=\"absolute right-3 top-3 rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n <div className=\"absolute inset-x-3 bottom-2.5 flex items-center gap-1.5\">\n {(config.facets ?? []).map((facet) => (\n <FacetPicker\n key={facet.key}\n facet={facet}\n value={facets[facet.key]}\n onChange={(value) => setFacet(facet.key, value)}\n />\n ))}\n <button\n type=\"button\"\n data-testid=\"comment-compose-send\"\n aria-label=\"Post comment\"\n onClick={() => void submit()}\n disabled={submitting || !body.trim()}\n className=\"ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\"\n >\n <Icon icon={UiArrowUp} className=\"text-xs\" />\n </button>\n </div>\n </div>\n );\n}\n"],"names":["jsx","DropdownMenu","jsxs","cn","toneToBadgeTone","useState","useRef","MentionTextarea","Icon","UiClose","UiArrowUp"],"mappings":"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"}
1
+ {"version":3,"file":"CommentComposer.cjs","sources":["../../src/comments/CommentComposer.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu } from \"../overlay/DropdownMenu\";\nimport { UiArrowUp, UiClose, UiThumbsDown, UiThumbsUp } from \"../icons\";\nimport type { BadgeTone } from \"../data/Badge\";\nimport { MentionTextarea } from \"./MentionTextarea\";\nimport { toneToBadgeTone } from \"./comment-utils\";\nimport type {\n CommentAnchor,\n CommentConfig,\n CommentCreateInput,\n CommentFacet,\n CommentMention,\n CommentRating,\n} from \"./comment-types\";\n\n// Static literals so Tailwind's source scanner emits these utilities.\nconst FACET_DOT_CLASS: Record<BadgeTone, string> = {\n neutral: \"bg-muted-foreground\",\n success: \"bg-green-500\",\n danger: \"bg-red-500\",\n warning: \"bg-yellow-500\",\n info: \"bg-blue-500\",\n};\n\nexport type CommentComposerProps = {\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n /** Start collapsed as an \"Add a comment…\" pill until clicked. */\n collapsible?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n /** Create a new root comment. */\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n /** Fired once per mention referenced by a posted comment. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\nfunction FacetPicker({\n facet,\n value,\n onChange,\n}: {\n facet: CommentFacet;\n value: string | undefined;\n onChange: (value: string | undefined) => void;\n}) {\n const selected = facet.options.find((o) => o.value === value);\n return (\n <DropdownMenu\n align=\"left\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium transition-colors\",\n selected\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60\",\n )}\n >\n {selected ? selected.label : facet.label}\n {selected && (\n <span\n role=\"presentation\"\n className=\"ml-0.5 cursor-pointer hover:text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onChange(undefined);\n }}\n >\n ×\n </span>\n )}\n </span>\n }\n >\n {(close) => (\n <div className=\"py-1\">\n {facet.options.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => {\n onChange(option.value === value ? undefined : option.value);\n close();\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-popover-foreground hover:bg-accent hover:text-accent-foreground\",\n option.value === value && \"bg-muted font-semibold\",\n )}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n FACET_DOT_CLASS[toneToBadgeTone(option.tone)],\n )}\n />\n {option.label}\n </button>\n ))}\n </div>\n )}\n </DropdownMenu>\n );\n}\n\n/**\n * The new-comment composer: a mention-aware textarea, one picker per configured\n * facet, and a send button. Starts as a pill when `collapsible`. Owns only its\n * own draft state; posting is delegated to `onCreate`.\n */\nexport function CommentComposer({\n config,\n anchor,\n collapsible = true,\n autoFocus = false,\n placeholder = \"Add a comment…\",\n onCreate,\n onMention,\n}: CommentComposerProps) {\n const [open, setOpen] = useState(!collapsible || autoFocus);\n const [body, setBody] = useState(\"\");\n const [facets, setFacets] = useState<Record<string, string>>({});\n const [rating, setRating] = useState<CommentRating | undefined>();\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n const mentionsRef = useRef<Map<string, CommentMention>>(new Map());\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n function reset() {\n setBody(\"\");\n setFacets({});\n setRating(undefined);\n mentionsRef.current.clear();\n }\n\n function setFacet(key: string, value: string | undefined) {\n setFacets((prev) => {\n if (value === undefined) {\n const { [key]: _drop, ...rest } = prev;\n return rest;\n }\n return { ...prev, [key]: value };\n });\n }\n\n async function submit() {\n const text = body.trim();\n if ((!text && !rating) || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n const mentions = [...mentionsRef.current.values()].filter((m) =>\n body.toLowerCase().includes(`@${m.name.toLowerCase()}`),\n );\n try {\n await onCreate?.({\n body: text,\n anchor: anchor ?? null,\n ...(rating ? { rating } : {}),\n ...(Object.keys(facets).length > 0 ? { facets } : {}),\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body: text });\n reset();\n if (collapsible) setOpen(false);\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n if (!open && body.trim().length === 0) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"w-full rounded-full border border-border bg-muted/25 px-4 py-2 text-left text-sm text-muted-foreground/70 shadow-sm transition-colors hover:border-primary/30 hover:bg-muted/40\"\n >\n {placeholder}\n </button>\n );\n }\n\n return (\n <div\n data-testid=\"comment-compose-box\"\n aria-busy={submitting}\n className=\"relative rounded-2xl border border-border bg-muted/25 px-3 pt-3 pb-10 shadow-sm transition-[border-color,box-shadow] focus-within:border-primary/40 focus-within:shadow-md\"\n >\n <MentionTextarea\n ref={inputRef}\n value={body}\n onChange={setBody}\n mentionables={config.mentionables ?? []}\n onMentionSelect={(m) => mentionsRef.current.set(m.id, m)}\n onSubmit={() => void submit()}\n onCancel={() => {\n reset();\n if (collapsible) setOpen(false);\n }}\n placeholder={placeholder}\n autoFocus={autoFocus || open}\n data-testid=\"comment-compose-input\"\n className=\"min-h-[48px] pr-10\"\n />\n {collapsible && (\n <button\n type=\"button\"\n onClick={() => {\n reset();\n setOpen(false);\n }}\n aria-label=\"Close composer\"\n className=\"absolute right-3 top-3 rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n <div className=\"absolute inset-x-3 bottom-2.5 flex items-center gap-1.5\">\n {(\n [\n [\"positive\", \"Positive rating\", UiThumbsUp],\n [\"negative\", \"Negative rating\", UiThumbsDown],\n ] as const\n ).map(([value, label, icon]) => (\n <button\n key={value}\n type=\"button\"\n aria-label={label}\n aria-pressed={rating === value}\n onClick={() =>\n setRating((current) => (current === value ? undefined : value))\n }\n className={cn(\n \"inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n rating === value &&\n (value === \"positive\"\n ? \"bg-green-100 text-green-700 [[data-theme=dark]_&]:bg-green-500/20 [[data-theme=dark]_&]:text-green-300\"\n : \"bg-red-100 text-red-700 [[data-theme=dark]_&]:bg-red-500/20 [[data-theme=dark]_&]:text-red-300\"),\n )}\n >\n <Icon icon={icon} className=\"text-sm\" />\n </button>\n ))}\n {(config.facets ?? []).map((facet) => (\n <FacetPicker\n key={facet.key}\n facet={facet}\n value={facets[facet.key]}\n onChange={(value) => setFacet(facet.key, value)}\n />\n ))}\n <button\n type=\"button\"\n data-testid=\"comment-compose-send\"\n aria-label=\"Post comment\"\n onClick={() => void submit()}\n disabled={submitting || (!body.trim() && !rating)}\n className=\"ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\"\n >\n <Icon icon={UiArrowUp} className=\"text-xs\" />\n </button>\n </div>\n </div>\n );\n}\n"],"names":["jsx","DropdownMenu","jsxs","cn","toneToBadgeTone","useState","useRef","MentionTextarea","Icon","UiClose","UiThumbsUp","UiThumbsDown","UiArrowUp"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/comments/CommentComposer.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,kBAAkB,EAElB,cAAc,EACf,MAAM,iBAAiB,CAAC;AAWzB,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,6DAA6D;IAC7D,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1E,CAAC;AAyEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,MAAM,EACN,WAAkB,EAClB,SAAiB,EACjB,WAA8B,EAC9B,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,2CAsHtB"}
1
+ {"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/comments/CommentComposer.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,kBAAkB,EAElB,cAAc,EAEf,MAAM,iBAAiB,CAAC;AAWzB,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,6DAA6D;IAC7D,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1E,CAAC;AAyEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,MAAM,EACN,WAAkB,EAClB,SAAiB,EACjB,WAA8B,EAC9B,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,2CAkJtB"}
@@ -6,6 +6,8 @@ import { DropdownMenu } from "../overlay/DropdownMenu.js";
6
6
  import { MentionTextarea } from "./MentionTextarea.js";
7
7
  import { toneToBadgeTone } from "./comment-utils.js";
8
8
  import { UiClose } from "../icons/components/UiClose.js";
9
+ import { UiThumbsUp } from "../icons/components/UiThumbsUp.js";
10
+ import { UiThumbsDown } from "../icons/components/UiThumbsDown.js";
9
11
  import { UiArrowUp } from "../icons/components/UiArrowUp.js";
10
12
  const FACET_DOT_CLASS = {
11
13
  neutral: "bg-muted-foreground",
@@ -93,6 +95,7 @@ function CommentComposer({
93
95
  const [open, setOpen] = useState(!collapsible || autoFocus);
94
96
  const [body, setBody] = useState("");
95
97
  const [facets, setFacets] = useState({});
98
+ const [rating, setRating] = useState();
96
99
  const [submitting, setSubmitting] = useState(false);
97
100
  const submittingRef = useRef(false);
98
101
  const mentionsRef = useRef(/* @__PURE__ */ new Map());
@@ -100,6 +103,7 @@ function CommentComposer({
100
103
  function reset() {
101
104
  setBody("");
102
105
  setFacets({});
106
+ setRating(void 0);
103
107
  mentionsRef.current.clear();
104
108
  }
105
109
  function setFacet(key, value) {
@@ -113,7 +117,7 @@ function CommentComposer({
113
117
  }
114
118
  async function submit() {
115
119
  const text = body.trim();
116
- if (!text || submittingRef.current) return;
120
+ if (!text && !rating || submittingRef.current) return;
117
121
  submittingRef.current = true;
118
122
  setSubmitting(true);
119
123
  const mentions = [...mentionsRef.current.values()].filter(
@@ -123,6 +127,7 @@ function CommentComposer({
123
127
  await (onCreate == null ? void 0 : onCreate({
124
128
  body: text,
125
129
  anchor: anchor ?? null,
130
+ ...rating ? { rating } : {},
126
131
  ...Object.keys(facets).length > 0 ? { facets } : {},
127
132
  ...mentions.length > 0 ? { mentions } : {}
128
133
  }));
@@ -185,6 +190,24 @@ function CommentComposer({
185
190
  }
186
191
  ),
187
192
  /* @__PURE__ */ jsxs("div", { className: "absolute inset-x-3 bottom-2.5 flex items-center gap-1.5", children: [
193
+ [
194
+ ["positive", "Positive rating", UiThumbsUp],
195
+ ["negative", "Negative rating", UiThumbsDown]
196
+ ].map(([value, label, icon]) => /* @__PURE__ */ jsx(
197
+ "button",
198
+ {
199
+ type: "button",
200
+ "aria-label": label,
201
+ "aria-pressed": rating === value,
202
+ onClick: () => setRating((current) => current === value ? void 0 : value),
203
+ className: cn(
204
+ "inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
205
+ rating === value && (value === "positive" ? "bg-green-100 text-green-700 [[data-theme=dark]_&]:bg-green-500/20 [[data-theme=dark]_&]:text-green-300" : "bg-red-100 text-red-700 [[data-theme=dark]_&]:bg-red-500/20 [[data-theme=dark]_&]:text-red-300")
206
+ ),
207
+ children: /* @__PURE__ */ jsx(Icon, { icon, className: "text-sm" })
208
+ },
209
+ value
210
+ )),
188
211
  (config.facets ?? []).map((facet) => /* @__PURE__ */ jsx(
189
212
  FacetPicker,
190
213
  {
@@ -201,7 +224,7 @@ function CommentComposer({
201
224
  "data-testid": "comment-compose-send",
202
225
  "aria-label": "Post comment",
203
226
  onClick: () => void submit(),
204
- disabled: submitting || !body.trim(),
227
+ disabled: submitting || !body.trim() && !rating,
205
228
  className: "ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground",
206
229
  children: /* @__PURE__ */ jsx(Icon, { icon: UiArrowUp, className: "text-xs" })
207
230
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CommentComposer.js","sources":["../../src/comments/CommentComposer.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu } from \"../overlay/DropdownMenu\";\nimport { UiArrowUp, UiClose } from \"../icons\";\nimport type { BadgeTone } from \"../data/Badge\";\nimport { MentionTextarea } from \"./MentionTextarea\";\nimport { toneToBadgeTone } from \"./comment-utils\";\nimport type {\n CommentAnchor,\n CommentConfig,\n CommentCreateInput,\n CommentFacet,\n CommentMention,\n} from \"./comment-types\";\n\n// Static literals so Tailwind's source scanner emits these utilities.\nconst FACET_DOT_CLASS: Record<BadgeTone, string> = {\n neutral: \"bg-muted-foreground\",\n success: \"bg-green-500\",\n danger: \"bg-red-500\",\n warning: \"bg-yellow-500\",\n info: \"bg-blue-500\",\n};\n\nexport type CommentComposerProps = {\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n /** Start collapsed as an \"Add a comment…\" pill until clicked. */\n collapsible?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n /** Create a new root comment. */\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n /** Fired once per mention referenced by a posted comment. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\nfunction FacetPicker({\n facet,\n value,\n onChange,\n}: {\n facet: CommentFacet;\n value: string | undefined;\n onChange: (value: string | undefined) => void;\n}) {\n const selected = facet.options.find((o) => o.value === value);\n return (\n <DropdownMenu\n align=\"left\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium transition-colors\",\n selected\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60\",\n )}\n >\n {selected ? selected.label : facet.label}\n {selected && (\n <span\n role=\"presentation\"\n className=\"ml-0.5 cursor-pointer hover:text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onChange(undefined);\n }}\n >\n ×\n </span>\n )}\n </span>\n }\n >\n {(close) => (\n <div className=\"py-1\">\n {facet.options.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => {\n onChange(option.value === value ? undefined : option.value);\n close();\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-popover-foreground hover:bg-accent hover:text-accent-foreground\",\n option.value === value && \"bg-muted font-semibold\",\n )}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n FACET_DOT_CLASS[toneToBadgeTone(option.tone)],\n )}\n />\n {option.label}\n </button>\n ))}\n </div>\n )}\n </DropdownMenu>\n );\n}\n\n/**\n * The new-comment composer: a mention-aware textarea, one picker per configured\n * facet, and a send button. Starts as a pill when `collapsible`. Owns only its\n * own draft state; posting is delegated to `onCreate`.\n */\nexport function CommentComposer({\n config,\n anchor,\n collapsible = true,\n autoFocus = false,\n placeholder = \"Add a comment…\",\n onCreate,\n onMention,\n}: CommentComposerProps) {\n const [open, setOpen] = useState(!collapsible || autoFocus);\n const [body, setBody] = useState(\"\");\n const [facets, setFacets] = useState<Record<string, string>>({});\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n const mentionsRef = useRef<Map<string, CommentMention>>(new Map());\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n function reset() {\n setBody(\"\");\n setFacets({});\n mentionsRef.current.clear();\n }\n\n function setFacet(key: string, value: string | undefined) {\n setFacets((prev) => {\n if (value === undefined) {\n const { [key]: _drop, ...rest } = prev;\n return rest;\n }\n return { ...prev, [key]: value };\n });\n }\n\n async function submit() {\n const text = body.trim();\n if (!text || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n const mentions = [...mentionsRef.current.values()].filter((m) =>\n body.toLowerCase().includes(`@${m.name.toLowerCase()}`),\n );\n try {\n await onCreate?.({\n body: text,\n anchor: anchor ?? null,\n ...(Object.keys(facets).length > 0 ? { facets } : {}),\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body: text });\n reset();\n if (collapsible) setOpen(false);\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n if (!open && body.trim().length === 0) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"w-full rounded-full border border-border bg-muted/25 px-4 py-2 text-left text-sm text-muted-foreground/70 shadow-sm transition-colors hover:border-primary/30 hover:bg-muted/40\"\n >\n {placeholder}\n </button>\n );\n }\n\n return (\n <div\n data-testid=\"comment-compose-box\"\n aria-busy={submitting}\n className=\"relative rounded-2xl border border-border bg-muted/25 px-3 pt-3 pb-10 shadow-sm transition-[border-color,box-shadow] focus-within:border-primary/40 focus-within:shadow-md\"\n >\n <MentionTextarea\n ref={inputRef}\n value={body}\n onChange={setBody}\n mentionables={config.mentionables ?? []}\n onMentionSelect={(m) => mentionsRef.current.set(m.id, m)}\n onSubmit={() => void submit()}\n onCancel={() => {\n reset();\n if (collapsible) setOpen(false);\n }}\n placeholder={placeholder}\n autoFocus={autoFocus || open}\n data-testid=\"comment-compose-input\"\n className=\"min-h-[48px] pr-10\"\n />\n {collapsible && (\n <button\n type=\"button\"\n onClick={() => {\n reset();\n setOpen(false);\n }}\n aria-label=\"Close composer\"\n className=\"absolute right-3 top-3 rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n <div className=\"absolute inset-x-3 bottom-2.5 flex items-center gap-1.5\">\n {(config.facets ?? []).map((facet) => (\n <FacetPicker\n key={facet.key}\n facet={facet}\n value={facets[facet.key]}\n onChange={(value) => setFacet(facet.key, value)}\n />\n ))}\n <button\n type=\"button\"\n data-testid=\"comment-compose-send\"\n aria-label=\"Post comment\"\n onClick={() => void submit()}\n disabled={submitting || !body.trim()}\n className=\"ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\"\n >\n <Icon icon={UiArrowUp} className=\"text-xs\" />\n </button>\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,kBAA6C;AAAA,EACjD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AACR;AAgBA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,WAAW,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AAC5D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,OAAM;AAAA,MACN,eAAc;AAAA,MACd,SACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,UAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA,WACI,6BACA;AAAA,UAAA;AAAA,UAGL,UAAA;AAAA,YAAA,WAAW,SAAS,QAAQ,MAAM;AAAA,YAClC,YACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS,CAAC,MAAM;AACd,oBAAE,gBAAA;AACF,2BAAS,MAAS;AAAA,gBACpB;AAAA,gBACD,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAED;AAAA,QAAA;AAAA,MAAA;AAAA,MAKL,UAAA,CAAC,UACA,oBAAC,OAAA,EAAI,WAAU,QACZ,UAAA,MAAM,QAAQ,IAAI,CAAC,WAClB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,qBAAS,OAAO,UAAU,QAAQ,SAAY,OAAO,KAAK;AAC1D,kBAAA;AAAA,UACF;AAAA,UACA,WAAW;AAAA,YACT;AAAA,YACA,OAAO,UAAU,SAAS;AAAA,UAAA;AAAA,UAG5B,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAW;AAAA,kBACT;AAAA,kBACA,gBAAgB,gBAAgB,OAAO,IAAI,CAAC;AAAA,gBAAA;AAAA,cAC9C;AAAA,YAAA;AAAA,YAED,OAAO;AAAA,UAAA;AAAA,QAAA;AAAA,QAjBH,OAAO;AAAA,MAAA,CAmBf,EAAA,CACH;AAAA,IAAA;AAAA,EAAA;AAIR;AAOO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,CAAC,eAAe,SAAS;AAC1D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE;AACnC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiC,CAAA,CAAE;AAC/D,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,gBAAgB,OAAO,KAAK;AAClC,QAAM,cAAc,OAAoC,oBAAI,KAAK;AACjE,QAAM,WAAW,OAA4B,IAAI;AAEjD,WAAS,QAAQ;AACf,YAAQ,EAAE;AACV,cAAU,CAAA,CAAE;AACZ,gBAAY,QAAQ,MAAA;AAAA,EACtB;AAEA,WAAS,SAAS,KAAa,OAA2B;AACxD,cAAU,CAAC,SAAS;AAClB,UAAI,UAAU,QAAW;AACvB,cAAM,EAAE,CAAC,GAAG,GAAG,OAAO,GAAG,SAAS;AAClC,eAAO;AAAA,MACT;AACA,aAAO,EAAE,GAAG,MAAM,CAAC,GAAG,GAAG,MAAA;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,iBAAe,SAAS;AACtB,UAAM,OAAO,KAAK,KAAA;AAClB,QAAI,CAAC,QAAQ,cAAc,QAAS;AACpC,kBAAc,UAAU;AACxB,kBAAc,IAAI;AAClB,UAAM,WAAW,CAAC,GAAG,YAAY,QAAQ,OAAA,CAAQ,EAAE;AAAA,MAAO,CAAC,MACzD,KAAK,YAAA,EAAc,SAAS,IAAI,EAAE,KAAK,YAAA,CAAa,EAAE;AAAA,IAAA;AAExD,QAAI;AACF,aAAM,qCAAW;AAAA,QACf,MAAM;AAAA,QACN,QAAQ,UAAU;AAAA,QAClB,GAAI,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,EAAE,OAAA,IAAW,CAAA;AAAA,QAClD,GAAI,SAAS,SAAS,IAAI,EAAE,SAAA,IAAa,CAAA;AAAA,MAAC;AAE5C,iBAAW,WAAW,SAAU,wCAAY,SAAS,EAAE,MAAM;AAC7D,YAAA;AACA,UAAI,qBAAqB,KAAK;AAAA,IAChC,UAAA;AACE,oBAAc,UAAU;AACxB,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ,KAAK,KAAA,EAAO,WAAW,GAAG;AACrC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,QAAQ,IAAI;AAAA,QAC3B,WAAU;AAAA,QAET,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAY;AAAA,MACZ,aAAW;AAAA,MACX,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU;AAAA,YACV,cAAc,OAAO,gBAAgB,CAAA;AAAA,YACrC,iBAAiB,CAAC,MAAM,YAAY,QAAQ,IAAI,EAAE,IAAI,CAAC;AAAA,YACvD,UAAU,MAAM,KAAK,OAAA;AAAA,YACrB,UAAU,MAAM;AACd,oBAAA;AACA,kBAAI,qBAAqB,KAAK;AAAA,YAChC;AAAA,YACA;AAAA,YACA,WAAW,aAAa;AAAA,YACxB,eAAY;AAAA,YACZ,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEX,eACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM;AACb,oBAAA;AACA,sBAAQ,KAAK;AAAA,YACf;AAAA,YACA,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,SAAS,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAG7C,qBAAC,OAAA,EAAI,WAAU,2DACX,UAAA;AAAA,WAAA,OAAO,UAAU,CAAA,GAAI,IAAI,CAAC,UAC1B;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC;AAAA,cACA,OAAO,OAAO,MAAM,GAAG;AAAA,cACvB,UAAU,CAAC,UAAU,SAAS,MAAM,KAAK,KAAK;AAAA,YAAA;AAAA,YAHzC,MAAM;AAAA,UAAA,CAKd;AAAA,UACD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,eAAY;AAAA,cACZ,cAAW;AAAA,cACX,SAAS,MAAM,KAAK,OAAA;AAAA,cACpB,UAAU,cAAc,CAAC,KAAK,KAAA;AAAA,cAC9B,WAAU;AAAA,cAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,WAAW,WAAU,UAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QAC7C,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"CommentComposer.js","sources":["../../src/comments/CommentComposer.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu } from \"../overlay/DropdownMenu\";\nimport { UiArrowUp, UiClose, UiThumbsDown, UiThumbsUp } from \"../icons\";\nimport type { BadgeTone } from \"../data/Badge\";\nimport { MentionTextarea } from \"./MentionTextarea\";\nimport { toneToBadgeTone } from \"./comment-utils\";\nimport type {\n CommentAnchor,\n CommentConfig,\n CommentCreateInput,\n CommentFacet,\n CommentMention,\n CommentRating,\n} from \"./comment-types\";\n\n// Static literals so Tailwind's source scanner emits these utilities.\nconst FACET_DOT_CLASS: Record<BadgeTone, string> = {\n neutral: \"bg-muted-foreground\",\n success: \"bg-green-500\",\n danger: \"bg-red-500\",\n warning: \"bg-yellow-500\",\n info: \"bg-blue-500\",\n};\n\nexport type CommentComposerProps = {\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n /** Start collapsed as an \"Add a comment…\" pill until clicked. */\n collapsible?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n /** Create a new root comment. */\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n /** Fired once per mention referenced by a posted comment. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\nfunction FacetPicker({\n facet,\n value,\n onChange,\n}: {\n facet: CommentFacet;\n value: string | undefined;\n onChange: (value: string | undefined) => void;\n}) {\n const selected = facet.options.find((o) => o.value === value);\n return (\n <DropdownMenu\n align=\"left\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium transition-colors\",\n selected\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60\",\n )}\n >\n {selected ? selected.label : facet.label}\n {selected && (\n <span\n role=\"presentation\"\n className=\"ml-0.5 cursor-pointer hover:text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onChange(undefined);\n }}\n >\n ×\n </span>\n )}\n </span>\n }\n >\n {(close) => (\n <div className=\"py-1\">\n {facet.options.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => {\n onChange(option.value === value ? undefined : option.value);\n close();\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-popover-foreground hover:bg-accent hover:text-accent-foreground\",\n option.value === value && \"bg-muted font-semibold\",\n )}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n FACET_DOT_CLASS[toneToBadgeTone(option.tone)],\n )}\n />\n {option.label}\n </button>\n ))}\n </div>\n )}\n </DropdownMenu>\n );\n}\n\n/**\n * The new-comment composer: a mention-aware textarea, one picker per configured\n * facet, and a send button. Starts as a pill when `collapsible`. Owns only its\n * own draft state; posting is delegated to `onCreate`.\n */\nexport function CommentComposer({\n config,\n anchor,\n collapsible = true,\n autoFocus = false,\n placeholder = \"Add a comment…\",\n onCreate,\n onMention,\n}: CommentComposerProps) {\n const [open, setOpen] = useState(!collapsible || autoFocus);\n const [body, setBody] = useState(\"\");\n const [facets, setFacets] = useState<Record<string, string>>({});\n const [rating, setRating] = useState<CommentRating | undefined>();\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n const mentionsRef = useRef<Map<string, CommentMention>>(new Map());\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n function reset() {\n setBody(\"\");\n setFacets({});\n setRating(undefined);\n mentionsRef.current.clear();\n }\n\n function setFacet(key: string, value: string | undefined) {\n setFacets((prev) => {\n if (value === undefined) {\n const { [key]: _drop, ...rest } = prev;\n return rest;\n }\n return { ...prev, [key]: value };\n });\n }\n\n async function submit() {\n const text = body.trim();\n if ((!text && !rating) || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n const mentions = [...mentionsRef.current.values()].filter((m) =>\n body.toLowerCase().includes(`@${m.name.toLowerCase()}`),\n );\n try {\n await onCreate?.({\n body: text,\n anchor: anchor ?? null,\n ...(rating ? { rating } : {}),\n ...(Object.keys(facets).length > 0 ? { facets } : {}),\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body: text });\n reset();\n if (collapsible) setOpen(false);\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n if (!open && body.trim().length === 0) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"w-full rounded-full border border-border bg-muted/25 px-4 py-2 text-left text-sm text-muted-foreground/70 shadow-sm transition-colors hover:border-primary/30 hover:bg-muted/40\"\n >\n {placeholder}\n </button>\n );\n }\n\n return (\n <div\n data-testid=\"comment-compose-box\"\n aria-busy={submitting}\n className=\"relative rounded-2xl border border-border bg-muted/25 px-3 pt-3 pb-10 shadow-sm transition-[border-color,box-shadow] focus-within:border-primary/40 focus-within:shadow-md\"\n >\n <MentionTextarea\n ref={inputRef}\n value={body}\n onChange={setBody}\n mentionables={config.mentionables ?? []}\n onMentionSelect={(m) => mentionsRef.current.set(m.id, m)}\n onSubmit={() => void submit()}\n onCancel={() => {\n reset();\n if (collapsible) setOpen(false);\n }}\n placeholder={placeholder}\n autoFocus={autoFocus || open}\n data-testid=\"comment-compose-input\"\n className=\"min-h-[48px] pr-10\"\n />\n {collapsible && (\n <button\n type=\"button\"\n onClick={() => {\n reset();\n setOpen(false);\n }}\n aria-label=\"Close composer\"\n className=\"absolute right-3 top-3 rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n <div className=\"absolute inset-x-3 bottom-2.5 flex items-center gap-1.5\">\n {(\n [\n [\"positive\", \"Positive rating\", UiThumbsUp],\n [\"negative\", \"Negative rating\", UiThumbsDown],\n ] as const\n ).map(([value, label, icon]) => (\n <button\n key={value}\n type=\"button\"\n aria-label={label}\n aria-pressed={rating === value}\n onClick={() =>\n setRating((current) => (current === value ? undefined : value))\n }\n className={cn(\n \"inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n rating === value &&\n (value === \"positive\"\n ? \"bg-green-100 text-green-700 [[data-theme=dark]_&]:bg-green-500/20 [[data-theme=dark]_&]:text-green-300\"\n : \"bg-red-100 text-red-700 [[data-theme=dark]_&]:bg-red-500/20 [[data-theme=dark]_&]:text-red-300\"),\n )}\n >\n <Icon icon={icon} className=\"text-sm\" />\n </button>\n ))}\n {(config.facets ?? []).map((facet) => (\n <FacetPicker\n key={facet.key}\n facet={facet}\n value={facets[facet.key]}\n onChange={(value) => setFacet(facet.key, value)}\n />\n ))}\n <button\n type=\"button\"\n data-testid=\"comment-compose-send\"\n aria-label=\"Post comment\"\n onClick={() => void submit()}\n disabled={submitting || (!body.trim() && !rating)}\n className=\"ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\"\n >\n <Icon icon={UiArrowUp} className=\"text-xs\" />\n </button>\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}