canopycms 0.0.68-int.98 → 0.1.0

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 (688) hide show
  1. package/CHANGELOG.md +2131 -0
  2. package/README.md +47 -63
  3. package/dist/ai/generate.d.ts +5 -0
  4. package/dist/ai/generate.js +244 -115
  5. package/dist/ai/index.d.ts +1 -1
  6. package/dist/ai/json-to-markdown.d.ts +21 -1
  7. package/dist/ai/json-to-markdown.js +120 -57
  8. package/dist/ai/references.d.ts +72 -0
  9. package/dist/ai/references.js +223 -0
  10. package/dist/ai/resolve-branch.js +9 -2
  11. package/dist/ai/to-plain-text.d.ts +17 -8
  12. package/dist/ai/to-plain-text.js +18 -15
  13. package/dist/ai/types.d.ts +84 -3
  14. package/dist/api/admin-branch-health.d.ts +24 -1
  15. package/dist/api/admin-branch-health.js +52 -20
  16. package/dist/api/admin.d.ts +23 -5
  17. package/dist/api/admin.js +36 -1
  18. package/dist/api/assets.js +88 -78
  19. package/dist/api/branch-create-window.d.ts +12 -0
  20. package/dist/api/branch-create-window.js +13 -0
  21. package/dist/api/branch-merge.d.ts +1 -1
  22. package/dist/api/branch-merge.js +1 -1
  23. package/dist/api/branch-review.d.ts +3 -3
  24. package/dist/api/branch-review.js +16 -7
  25. package/dist/api/branch-status.d.ts +16 -6
  26. package/dist/api/branch-status.js +57 -12
  27. package/dist/api/branch-withdraw.d.ts +2 -2
  28. package/dist/api/branch-withdraw.js +8 -3
  29. package/dist/api/branch.d.ts +25 -12
  30. package/dist/api/branch.js +164 -103
  31. package/dist/api/client.d.ts +78 -58
  32. package/dist/api/client.js +150 -117
  33. package/dist/api/comments.d.ts +12 -3
  34. package/dist/api/comments.js +118 -9
  35. package/dist/api/content.d.ts +9 -9
  36. package/dist/api/content.js +131 -22
  37. package/dist/api/entries-constants.d.ts +14 -3
  38. package/dist/api/entries-constants.js +14 -3
  39. package/dist/api/entries.d.ts +40 -2
  40. package/dist/api/entries.js +102 -6
  41. package/dist/api/github-sync.d.ts +19 -0
  42. package/dist/api/github-sync.js +50 -3
  43. package/dist/api/groups.d.ts +4 -4
  44. package/dist/api/groups.js +2 -1
  45. package/dist/api/guards.js +23 -3
  46. package/dist/api/index.d.ts +3 -1
  47. package/dist/api/index.js +1 -0
  48. package/dist/api/permissions.d.ts +21 -2
  49. package/dist/api/permissions.js +50 -3
  50. package/dist/api/request-deadline.d.ts +59 -0
  51. package/dist/api/request-deadline.js +73 -0
  52. package/dist/api/resolve-references.js +8 -7
  53. package/dist/api/route-builder.d.ts +14 -0
  54. package/dist/api/route-builder.js +14 -1
  55. package/dist/api/schema.d.ts +11 -9
  56. package/dist/api/schema.js +2 -0
  57. package/dist/api/settings-helpers.d.ts +5 -1
  58. package/dist/api/settings-helpers.js +10 -2
  59. package/dist/api/types.d.ts +18 -0
  60. package/dist/api/users-constants.d.ts +6 -0
  61. package/dist/api/users-constants.js +6 -0
  62. package/dist/assets/asset-url.d.ts +28 -16
  63. package/dist/assets/asset-url.js +51 -25
  64. package/dist/assets/finalize.js +3 -0
  65. package/dist/assets/index.d.ts +1 -1
  66. package/dist/assets/materialize.d.ts +153 -0
  67. package/dist/assets/materialize.js +402 -0
  68. package/dist/assets/store-local.d.ts +19 -5
  69. package/dist/assets/store-local.js +69 -10
  70. package/dist/assets/store-s3.d.ts +38 -7
  71. package/dist/assets/store-s3.js +128 -22
  72. package/dist/assets/transform-directives.d.ts +52 -13
  73. package/dist/assets/transform-directives.js +87 -27
  74. package/dist/assets/transform.d.ts +3 -3
  75. package/dist/assets/transform.js +13 -8
  76. package/dist/assets/types.d.ts +31 -4
  77. package/dist/auth/cache.d.ts +1 -1
  78. package/dist/auth/cache.js +1 -1
  79. package/dist/auth/caching-auth-plugin.d.ts +2 -0
  80. package/dist/auth/caching-auth-plugin.js +12 -2
  81. package/dist/auth/file-based-auth-cache.d.ts +8 -0
  82. package/dist/auth/file-based-auth-cache.js +18 -8
  83. package/dist/auth/person-name.d.ts +5 -0
  84. package/dist/auth/person-name.js +8 -0
  85. package/dist/auth/plugin.d.ts +10 -1
  86. package/dist/auth/types.d.ts +2 -0
  87. package/dist/auth/user-metadata-lookup.d.ts +21 -0
  88. package/dist/auth/user-metadata-lookup.js +116 -0
  89. package/dist/authorization/branch.d.ts +1 -1
  90. package/dist/authorization/branch.js +12 -13
  91. package/dist/authorization/groups/schema.d.ts +4 -4
  92. package/dist/authorization/helpers.d.ts +8 -0
  93. package/dist/authorization/helpers.js +8 -0
  94. package/dist/authorization/index.d.ts +1 -1
  95. package/dist/authorization/index.js +1 -1
  96. package/dist/authorization/protected-branch.d.ts +5 -4
  97. package/dist/authorization/protected-branch.js +7 -5
  98. package/dist/branch-health.d.ts +35 -8
  99. package/dist/branch-health.js +54 -18
  100. package/dist/branch-metadata-error.d.ts +27 -0
  101. package/dist/branch-metadata-error.js +23 -0
  102. package/dist/branch-metadata-file.d.ts +205 -24
  103. package/dist/branch-metadata-file.js +126 -25
  104. package/dist/branch-metadata.d.ts +45 -6
  105. package/dist/branch-metadata.js +142 -50
  106. package/dist/branch-provisioning.d.ts +167 -0
  107. package/dist/branch-provisioning.js +501 -0
  108. package/dist/branch-registry.d.ts +8 -0
  109. package/dist/branch-registry.js +61 -9
  110. package/dist/branch-schema-cache.d.ts +29 -12
  111. package/dist/branch-schema-cache.js +64 -21
  112. package/dist/branch-sparse.d.ts +23 -0
  113. package/dist/branch-sparse.js +118 -0
  114. package/dist/branch-workspace.d.ts +42 -3
  115. package/dist/branch-workspace.js +239 -100
  116. package/dist/build/asset-refs.d.ts +94 -0
  117. package/dist/build/asset-refs.js +267 -0
  118. package/dist/build/generate-ai-content.js +5 -0
  119. package/dist/build/inline-flight.d.ts +37 -0
  120. package/dist/build/inline-flight.js +142 -0
  121. package/dist/cli/asset-refs.d.ts +52 -0
  122. package/dist/cli/asset-refs.js +194 -0
  123. package/dist/cli/cli.d.ts +15 -1
  124. package/dist/cli/cli.js +8543 -3193
  125. package/dist/cli/configured-asset-store.d.ts +12 -0
  126. package/dist/cli/configured-asset-store.js +46 -0
  127. package/dist/cli/generate-ai-content.js +4244 -1072
  128. package/dist/cli/init.js +217 -126
  129. package/dist/cli/project-detect.d.ts +2 -0
  130. package/dist/cli/project-detect.js +2 -0
  131. package/dist/cli/template-files/Dockerfile.cms.template +13 -5
  132. package/dist/cli/template-files/cms-stack.ts.template +16 -13
  133. package/dist/client.d.ts +1 -2
  134. package/dist/client.js +1 -2
  135. package/dist/comment-store.d.ts +25 -0
  136. package/dist/comment-store.js +43 -8
  137. package/dist/config/flatten.js +2 -0
  138. package/dist/config/helpers.js +2 -2
  139. package/dist/config/index.d.ts +1 -1
  140. package/dist/config/schemas/config.d.ts +12 -29
  141. package/dist/config/schemas/config.js +9 -12
  142. package/dist/config/schemas/field.d.ts +3 -0
  143. package/dist/config/schemas/field.js +58 -4
  144. package/dist/config/schemas/media.d.ts +4 -13
  145. package/dist/config/schemas/media.js +5 -8
  146. package/dist/config/schemas/url.d.ts +2 -20
  147. package/dist/config/schemas/url.js +2 -25
  148. package/dist/config/types.d.ts +116 -22
  149. package/dist/config/types.js +3 -9
  150. package/dist/config/validation.d.ts +25 -4
  151. package/dist/config/validation.js +116 -3
  152. package/dist/content-id-index.js +2 -2
  153. package/dist/content-index-generation.d.ts +1 -1
  154. package/dist/content-index-generation.js +1 -1
  155. package/dist/content-listing.d.ts +10 -1
  156. package/dist/content-listing.js +22 -5
  157. package/dist/content-reader.d.ts +9 -0
  158. package/dist/content-reader.js +16 -11
  159. package/dist/content-store.d.ts +75 -12
  160. package/dist/content-store.js +249 -86
  161. package/dist/content-tree.js +2 -2
  162. package/dist/context.js +5 -2
  163. package/dist/dangling-reference-log.d.ts +17 -0
  164. package/dist/dangling-reference-log.js +29 -0
  165. package/dist/dev-content-watcher.d.ts +1 -1
  166. package/dist/dev-content-watcher.js +1 -1
  167. package/dist/editor/BranchManager.d.ts +23 -7
  168. package/dist/editor/BranchManager.js +119 -54
  169. package/dist/editor/CanopyEditor.d.ts +1 -1
  170. package/dist/editor/CanopyEditor.js +1 -2
  171. package/dist/editor/CommentsPanel.d.ts +14 -0
  172. package/dist/editor/CommentsPanel.js +68 -43
  173. package/dist/editor/Editor.d.ts +11 -5
  174. package/dist/editor/Editor.js +891 -218
  175. package/dist/editor/EditorAuthGate.d.ts +7 -1
  176. package/dist/editor/EditorAuthGate.js +41 -9
  177. package/dist/editor/EntryNavigator.d.ts +22 -0
  178. package/dist/editor/EntryNavigator.js +353 -105
  179. package/dist/editor/FormHeader.d.ts +25 -0
  180. package/dist/editor/FormHeader.js +82 -0
  181. package/dist/editor/FormRenderer.d.ts +27 -3
  182. package/dist/editor/FormRenderer.js +171 -76
  183. package/dist/editor/PreviewFrame.d.ts +40 -0
  184. package/dist/editor/PreviewFrame.js +215 -0
  185. package/dist/editor/PreviewPane.d.ts +47 -0
  186. package/dist/editor/PreviewPane.js +147 -0
  187. package/dist/editor/PreviewToolbarTools.d.ts +22 -0
  188. package/dist/editor/PreviewToolbarTools.js +73 -0
  189. package/dist/editor/admin/SystemHealthPanel.d.ts +2 -1
  190. package/dist/editor/admin/SystemHealthPanel.js +163 -33
  191. package/dist/editor/admin/useSystemHealth.d.ts +18 -0
  192. package/dist/editor/admin/useSystemHealth.js +70 -1
  193. package/dist/editor/branch-status.d.ts +17 -0
  194. package/dist/editor/branch-status.js +29 -0
  195. package/dist/editor/canopy-path.d.ts +9 -6
  196. package/dist/editor/canopy-path.js +8 -11
  197. package/dist/editor/client-reference-resolver.d.ts +43 -16
  198. package/dist/editor/client-reference-resolver.js +155 -64
  199. package/dist/editor/comments/BranchComments.d.ts +1 -1
  200. package/dist/editor/comments/BranchComments.js +2 -2
  201. package/dist/editor/comments/CloseRefusedNotice.d.ts +4 -0
  202. package/dist/editor/comments/CloseRefusedNotice.js +7 -0
  203. package/dist/editor/comments/CommentThreadsPopover.d.ts +47 -0
  204. package/dist/editor/comments/CommentThreadsPopover.js +274 -0
  205. package/dist/editor/comments/CommentWriteAlert.d.ts +11 -0
  206. package/dist/editor/comments/CommentWriteAlert.js +13 -0
  207. package/dist/editor/comments/EntryComments.d.ts +10 -4
  208. package/dist/editor/comments/EntryComments.js +14 -17
  209. package/dist/editor/comments/FieldWrapper.d.ts +18 -4
  210. package/dist/editor/comments/FieldWrapper.js +62 -28
  211. package/dist/editor/comments/InlineCommentThread.d.ts +9 -2
  212. package/dist/editor/comments/InlineCommentThread.js +11 -47
  213. package/dist/editor/comments/ThreadCarousel.d.ts +1 -1
  214. package/dist/editor/comments/ThreadCarousel.js +18 -21
  215. package/dist/editor/comments/ThreadReplyControls.d.ts +28 -0
  216. package/dist/editor/comments/ThreadReplyControls.js +92 -0
  217. package/dist/editor/comments/field-comment-context.d.ts +14 -0
  218. package/dist/editor/comments/field-comment-context.js +14 -0
  219. package/dist/editor/comments/form-comments.d.ts +48 -0
  220. package/dist/editor/comments/form-comments.js +178 -0
  221. package/dist/editor/comments/item-anchor-copy.d.ts +6 -0
  222. package/dist/editor/comments/item-anchor-copy.js +6 -0
  223. package/dist/editor/comments/move-focus-if-unmoved.d.ts +6 -0
  224. package/dist/editor/comments/move-focus-if-unmoved.js +11 -0
  225. package/dist/editor/comments/popover-placement.d.ts +18 -0
  226. package/dist/editor/comments/popover-placement.js +58 -0
  227. package/dist/editor/comments/use-tracked-writes.d.ts +16 -0
  228. package/dist/editor/comments/use-tracked-writes.js +53 -0
  229. package/dist/editor/components/BranchAccessList.d.ts +15 -0
  230. package/dist/editor/components/BranchAccessList.js +52 -0
  231. package/dist/editor/components/BranchLockBanner.d.ts +22 -0
  232. package/dist/editor/components/BranchLockBanner.js +39 -0
  233. package/dist/editor/components/BranchPill.d.ts +32 -0
  234. package/dist/editor/components/BranchPill.js +33 -0
  235. package/dist/editor/components/BranchesDrawer.d.ts +11 -0
  236. package/dist/editor/components/BranchesDrawer.js +5 -0
  237. package/dist/editor/components/ConfirmDeleteModal.d.ts +6 -1
  238. package/dist/editor/components/ConfirmDeleteModal.js +3 -2
  239. package/dist/editor/components/DestructiveMenuItem.d.ts +7 -0
  240. package/dist/editor/components/DestructiveMenuItem.js +8 -0
  241. package/dist/editor/components/EditorCrashScreen.d.ts +17 -0
  242. package/dist/editor/components/EditorCrashScreen.js +22 -0
  243. package/dist/editor/components/EditorErrorBoundary.d.ts +38 -0
  244. package/dist/editor/components/EditorErrorBoundary.js +40 -0
  245. package/dist/editor/components/EditorHeader.d.ts +56 -106
  246. package/dist/editor/components/EditorHeader.js +130 -131
  247. package/dist/editor/components/EditorRail.d.ts +49 -0
  248. package/dist/editor/components/EditorRail.js +65 -0
  249. package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
  250. package/dist/editor/components/EntryBreadcrumb.js +56 -0
  251. package/dist/editor/components/EntryCreateModal.d.ts +12 -7
  252. package/dist/editor/components/EntryCreateModal.js +51 -57
  253. package/dist/editor/components/NoEditPermissionNotice.d.ts +6 -5
  254. package/dist/editor/components/NoEditPermissionNotice.js +5 -4
  255. package/dist/editor/components/QuickOpenPalette.d.ts +22 -0
  256. package/dist/editor/components/QuickOpenPalette.js +66 -0
  257. package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
  258. package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
  259. package/dist/editor/components/ReasonedMenuItem.d.ts +15 -0
  260. package/dist/editor/components/ReasonedMenuItem.js +15 -0
  261. package/dist/editor/components/ReferencedByList.d.ts +11 -0
  262. package/dist/editor/components/ReferencedByList.js +28 -0
  263. package/dist/editor/components/RelativeTime.d.ts +10 -0
  264. package/dist/editor/components/RelativeTime.js +10 -0
  265. package/dist/editor/components/RenameEntryModal.d.ts +9 -1
  266. package/dist/editor/components/RenameEntryModal.js +19 -31
  267. package/dist/editor/components/RetryToastMessage.d.ts +9 -0
  268. package/dist/editor/components/RetryToastMessage.js +6 -0
  269. package/dist/editor/components/SaveAllResult.d.ts +25 -0
  270. package/dist/editor/components/SaveAllResult.js +86 -0
  271. package/dist/editor/components/SaveSplitButton.d.ts +30 -0
  272. package/dist/editor/components/SaveSplitButton.js +108 -0
  273. package/dist/editor/components/StagedChangesDrawer.d.ts +39 -0
  274. package/dist/editor/components/StagedChangesDrawer.js +92 -0
  275. package/dist/editor/components/UnavailableEntryNotice.d.ts +6 -0
  276. package/dist/editor/components/UnavailableEntryNotice.js +9 -0
  277. package/dist/editor/components/UnavailableTypeMessage.d.ts +6 -0
  278. package/dist/editor/components/UnavailableTypeMessage.js +9 -0
  279. package/dist/editor/components/UndoToastMessage.d.ts +18 -0
  280. package/dist/editor/components/UndoToastMessage.js +10 -0
  281. package/dist/editor/components/UserBadge.d.ts +2 -5
  282. package/dist/editor/components/UserBadge.js +62 -33
  283. package/dist/editor/components/ValidationSummary.d.ts +14 -0
  284. package/dist/editor/components/ValidationSummary.js +24 -0
  285. package/dist/editor/components/index.d.ts +2 -2
  286. package/dist/editor/components/index.js +2 -2
  287. package/dist/editor/context/ApiClientContext.d.ts +8 -2
  288. package/dist/editor/context/ApiClientContext.js +19 -8
  289. package/dist/editor/context/AssetContext.d.ts +13 -35
  290. package/dist/editor/context/AssetContext.js +12 -31
  291. package/dist/editor/context/EditorStateContext.d.ts +4 -4
  292. package/dist/editor/context/EditorStateContext.js +4 -4
  293. package/dist/editor/context/SWRProvider.d.ts +3 -3
  294. package/dist/editor/context/SiteMdxAllowContext.d.ts +4 -0
  295. package/dist/editor/context/SiteMdxAllowContext.js +7 -0
  296. package/dist/editor/context/index.d.ts +2 -1
  297. package/dist/editor/context/index.js +2 -1
  298. package/dist/editor/contrast.d.ts +19 -0
  299. package/dist/editor/contrast.js +41 -0
  300. package/dist/editor/copy.d.ts +18 -0
  301. package/dist/editor/copy.js +18 -0
  302. package/dist/editor/editor-config.js +16 -0
  303. package/dist/editor/editor-utils.d.ts +34 -8
  304. package/dist/editor/editor-utils.js +42 -20
  305. package/dist/editor/entry-search.d.ts +20 -0
  306. package/dist/editor/entry-search.js +34 -0
  307. package/dist/editor/field-breadcrumb.d.ts +12 -0
  308. package/dist/editor/field-breadcrumb.js +85 -0
  309. package/dist/editor/field-props.d.ts +86 -0
  310. package/dist/editor/field-props.js +27 -0
  311. package/dist/editor/field-target.d.ts +30 -0
  312. package/dist/editor/field-target.js +77 -0
  313. package/dist/editor/fields/BlockField.d.ts +12 -0
  314. package/dist/editor/fields/BlockField.js +89 -73
  315. package/dist/editor/fields/CodeField.d.ts +2 -0
  316. package/dist/editor/fields/CodeField.js +6 -2
  317. package/dist/editor/fields/DateTimeField.d.ts +4 -29
  318. package/dist/editor/fields/DateTimeField.js +79 -41
  319. package/dist/editor/fields/ExpandAllButton.d.ts +6 -0
  320. package/dist/editor/fields/ExpandAllButton.js +4 -0
  321. package/dist/editor/fields/FieldCrashFallback.d.ts +17 -0
  322. package/dist/editor/fields/FieldCrashFallback.js +30 -0
  323. package/dist/editor/fields/FieldLabel.d.ts +37 -0
  324. package/dist/editor/fields/FieldLabel.js +19 -0
  325. package/dist/editor/fields/ImageField.d.ts +4 -0
  326. package/dist/editor/fields/ImageField.js +29 -14
  327. package/dist/editor/fields/InlineGroupField.js +4 -3
  328. package/dist/editor/fields/ItemList.d.ts +42 -0
  329. package/dist/editor/fields/ItemList.js +130 -0
  330. package/dist/editor/fields/MarkdownField.d.ts +32 -0
  331. package/dist/editor/fields/MarkdownField.js +223 -46
  332. package/dist/editor/fields/MdxImageDialog.js +1 -1
  333. package/dist/editor/fields/NumberField.d.ts +2 -0
  334. package/dist/editor/fields/NumberField.js +8 -5
  335. package/dist/editor/fields/NumberListField.d.ts +2 -0
  336. package/dist/editor/fields/NumberListField.js +19 -16
  337. package/dist/editor/fields/ObjectField.d.ts +2 -7
  338. package/dist/editor/fields/ObjectField.js +15 -5
  339. package/dist/editor/fields/ObjectListField.d.ts +29 -0
  340. package/dist/editor/fields/ObjectListField.js +57 -0
  341. package/dist/editor/fields/ReferenceField.d.ts +2 -0
  342. package/dist/editor/fields/ReferenceField.js +34 -9
  343. package/dist/editor/fields/SelectField.d.ts +2 -0
  344. package/dist/editor/fields/SelectField.js +11 -2
  345. package/dist/editor/fields/StringListField.d.ts +4 -1
  346. package/dist/editor/fields/StringListField.js +22 -8
  347. package/dist/editor/fields/TextField.d.ts +2 -0
  348. package/dist/editor/fields/TextField.js +6 -2
  349. package/dist/editor/fields/ToggleField.d.ts +2 -0
  350. package/dist/editor/fields/ToggleField.js +8 -3
  351. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
  352. package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
  353. package/dist/editor/fields/form-expansion.d.ts +9 -0
  354. package/dist/editor/fields/form-expansion.js +2 -0
  355. package/dist/editor/fields/item-expansion.d.ts +15 -0
  356. package/dist/editor/fields/item-expansion.js +60 -0
  357. package/dist/editor/fields/list-item-keys.d.ts +8 -0
  358. package/dist/editor/fields/list-item-keys.js +50 -0
  359. package/dist/editor/fields/list-item-title.d.ts +20 -0
  360. package/dist/editor/fields/list-item-title.js +85 -0
  361. package/dist/editor/fields/markdown-export-options.d.ts +2 -0
  362. package/dist/editor/fields/markdown-export-options.js +8 -0
  363. package/dist/editor/fields/markdown-fidelity-visitors.d.ts +11 -0
  364. package/dist/editor/fields/markdown-fidelity-visitors.js +160 -0
  365. package/dist/editor/fields/mdx-jsx-support.d.ts +5 -1
  366. package/dist/editor/fields/mdx-jsx-support.js +157 -69
  367. package/dist/editor/fields/rich-text-failures.d.ts +5 -0
  368. package/dist/editor/fields/rich-text-failures.js +26 -0
  369. package/dist/editor/fields/toolbar-wrap.d.ts +7 -0
  370. package/dist/editor/fields/toolbar-wrap.js +35 -0
  371. package/dist/editor/form-outline.d.ts +13 -0
  372. package/dist/editor/form-outline.js +70 -0
  373. package/dist/editor/group-manager/GroupCard.d.ts +4 -1
  374. package/dist/editor/group-manager/GroupCard.js +12 -3
  375. package/dist/editor/group-manager/GroupForm.js +1 -1
  376. package/dist/editor/group-manager/InternalGroupsTab.d.ts +2 -1
  377. package/dist/editor/group-manager/InternalGroupsTab.js +4 -4
  378. package/dist/editor/group-manager/MemberList.d.ts +2 -1
  379. package/dist/editor/group-manager/MemberList.js +15 -4
  380. package/dist/editor/group-manager/hooks/useGroupState.d.ts +5 -0
  381. package/dist/editor/group-manager/hooks/useGroupState.js +30 -7
  382. package/dist/editor/group-manager/index.js +34 -5
  383. package/dist/editor/group-manager/types.d.ts +5 -2
  384. package/dist/editor/hooks/create-branch-request.d.ts +20 -0
  385. package/dist/editor/hooks/create-branch-request.js +54 -0
  386. package/dist/editor/hooks/save-all.d.ts +68 -0
  387. package/dist/editor/hooks/save-all.js +61 -0
  388. package/dist/editor/hooks/useBranchActions.d.ts +11 -1
  389. package/dist/editor/hooks/useBranchActions.js +42 -19
  390. package/dist/editor/hooks/useBranchManager.d.ts +21 -0
  391. package/dist/editor/hooks/useBranchManager.js +321 -125
  392. package/dist/editor/hooks/useBranchesData.d.ts +8 -4
  393. package/dist/editor/hooks/useBranchesData.js +9 -5
  394. package/dist/editor/hooks/useCommentSystem.d.ts +14 -2
  395. package/dist/editor/hooks/useCommentSystem.js +79 -55
  396. package/dist/editor/hooks/useCommentsData.d.ts +11 -7
  397. package/dist/editor/hooks/useCommentsData.js +20 -19
  398. package/dist/editor/hooks/useDraftManager.d.ts +77 -3
  399. package/dist/editor/hooks/useDraftManager.js +887 -94
  400. package/dist/editor/hooks/useEditorLayout.d.ts +53 -7
  401. package/dist/editor/hooks/useEditorLayout.js +98 -20
  402. package/dist/editor/hooks/useEntriesData.d.ts +2 -1
  403. package/dist/editor/hooks/useEntriesData.js +18 -4
  404. package/dist/editor/hooks/useEntryManager.d.ts +41 -2
  405. package/dist/editor/hooks/useEntryManager.js +227 -41
  406. package/dist/editor/hooks/useGroupManager.js +13 -16
  407. package/dist/editor/hooks/usePermissionManager.js +14 -6
  408. package/dist/editor/hooks/usePreviewMarks.d.ts +21 -0
  409. package/dist/editor/hooks/usePreviewMarks.js +33 -0
  410. package/dist/editor/hooks/useReferenceResolution.d.ts +10 -3
  411. package/dist/editor/hooks/useReferenceResolution.js +34 -177
  412. package/dist/editor/hooks/useSchemaManager.d.ts +25 -6
  413. package/dist/editor/hooks/useSchemaManager.js +97 -39
  414. package/dist/editor/hooks/useUserMetadata.d.ts +14 -4
  415. package/dist/editor/hooks/useUserMetadata.js +76 -47
  416. package/dist/editor/hooks/user-metadata-batcher.d.ts +8 -0
  417. package/dist/editor/hooks/user-metadata-batcher.js +33 -0
  418. package/dist/editor/media/AssetCard.d.ts +6 -7
  419. package/dist/editor/media/AssetCard.js +24 -31
  420. package/dist/editor/media/AssetListRow.d.ts +16 -0
  421. package/dist/editor/media/AssetListRow.js +41 -0
  422. package/dist/editor/media/CropStep.js +6 -1
  423. package/dist/editor/media/MediaLibrary.d.ts +4 -3
  424. package/dist/editor/media/MediaLibrary.js +11 -4
  425. package/dist/editor/media/MediaLibraryBody.d.ts +3 -3
  426. package/dist/editor/media/MediaLibraryBody.js +195 -80
  427. package/dist/editor/media/MediaLibrarySkeleton.d.ts +6 -0
  428. package/dist/editor/media/MediaLibrarySkeleton.js +14 -0
  429. package/dist/editor/media/crop-math.d.ts +3 -4
  430. package/dist/editor/media/crop-math.js +11 -22
  431. package/dist/editor/media/editor-image-src.d.ts +8 -0
  432. package/dist/editor/media/editor-image-src.js +10 -0
  433. package/dist/editor/media/format-bytes.d.ts +2 -0
  434. package/dist/editor/media/format-bytes.js +23 -0
  435. package/dist/editor/media/media-library-prefs.d.ts +23 -0
  436. package/dist/editor/media/media-library-prefs.js +49 -0
  437. package/dist/editor/media/media-library-view.d.ts +34 -0
  438. package/dist/editor/media/media-library-view.js +40 -0
  439. package/dist/editor/media/useAssetLibrary.d.ts +30 -0
  440. package/dist/editor/media/useAssetLibrary.js +126 -0
  441. package/dist/editor/media/useStableAssetOrder.d.ts +11 -0
  442. package/dist/editor/media/useStableAssetOrder.js +37 -0
  443. package/dist/editor/media/useThumbnailFailure.d.ts +6 -0
  444. package/dist/editor/media/useThumbnailFailure.js +16 -0
  445. package/dist/editor/permission-manager/GroupSelector.js +2 -2
  446. package/dist/editor/permission-manager/PermissionEditor.d.ts +1 -1
  447. package/dist/editor/permission-manager/PermissionEditor.js +15 -4
  448. package/dist/editor/permission-manager/PermissionLevelBadge.js +1 -1
  449. package/dist/editor/permission-manager/PermissionTree.d.ts +1 -1
  450. package/dist/editor/permission-manager/UserSelector.d.ts +1 -1
  451. package/dist/editor/permission-manager/UserSelector.js +3 -3
  452. package/dist/editor/permission-manager/index.js +12 -3
  453. package/dist/editor/permission-manager/types.d.ts +5 -2
  454. package/dist/editor/platform.d.ts +3 -0
  455. package/dist/editor/platform.js +9 -0
  456. package/dist/editor/preview-asset-base.d.ts +9 -0
  457. package/dist/editor/preview-asset-base.js +17 -0
  458. package/dist/editor/preview-bridge.d.ts +51 -27
  459. package/dist/editor/preview-bridge.js +103 -144
  460. package/dist/editor/preview-marks.d.ts +15 -0
  461. package/dist/editor/preview-marks.js +74 -0
  462. package/dist/editor/preview-path.d.ts +12 -0
  463. package/dist/editor/preview-path.js +45 -1
  464. package/dist/editor/preview-scale.d.ts +27 -0
  465. package/dist/editor/preview-scale.js +34 -0
  466. package/dist/editor/raw-asset-base.d.ts +5 -0
  467. package/dist/editor/raw-asset-base.js +14 -0
  468. package/dist/editor/relative-time.d.ts +4 -4
  469. package/dist/editor/relative-time.js +16 -8
  470. package/dist/editor/schema-editor/CollectionEditor.d.ts +20 -3
  471. package/dist/editor/schema-editor/CollectionEditor.js +361 -62
  472. package/dist/editor/schema-editor/EntryTypeEditor.d.ts +5 -1
  473. package/dist/editor/schema-editor/EntryTypeEditor.js +11 -6
  474. package/dist/editor/schema-editor/collection-draft.d.ts +22 -0
  475. package/dist/editor/schema-editor/collection-draft.js +40 -0
  476. package/dist/editor/schema-editor/index.d.ts +1 -1
  477. package/dist/editor/schema-label.d.ts +10 -0
  478. package/dist/editor/schema-label.js +17 -0
  479. package/dist/editor/settings-conflict.d.ts +5 -0
  480. package/dist/editor/settings-conflict.js +8 -0
  481. package/dist/editor/shell/ActivityRail.d.ts +37 -0
  482. package/dist/editor/shell/ActivityRail.js +91 -0
  483. package/dist/editor/shell/EditorShell.d.ts +17 -0
  484. package/dist/editor/shell/EditorShell.js +31 -0
  485. package/dist/editor/shell/PaneLayout.d.ts +28 -0
  486. package/dist/editor/shell/PaneLayout.js +116 -0
  487. package/dist/editor/shell/SidePanelHost.d.ts +35 -0
  488. package/dist/editor/shell/SidePanelHost.js +184 -0
  489. package/dist/editor/shell/TopBar.d.ts +12 -0
  490. package/dist/editor/shell/TopBar.js +14 -0
  491. package/dist/editor/shell/host-chrome.d.ts +16 -0
  492. package/dist/editor/shell/host-chrome.js +69 -0
  493. package/dist/editor/slug.d.ts +16 -0
  494. package/dist/editor/slug.js +43 -0
  495. package/dist/editor/theme.d.ts +2 -1
  496. package/dist/editor/theme.js +314 -13
  497. package/dist/editor/unanswered.d.ts +17 -0
  498. package/dist/editor/unanswered.js +26 -0
  499. package/dist/editor/unavailable-entry-type.d.ts +20 -0
  500. package/dist/editor/unavailable-entry-type.js +28 -0
  501. package/dist/editor/undo-removal.d.ts +57 -0
  502. package/dist/editor/undo-removal.js +163 -0
  503. package/dist/editor/user-display.d.ts +4 -0
  504. package/dist/editor/user-display.js +5 -0
  505. package/dist/editor/utils/confirm-modal.d.ts +8 -0
  506. package/dist/editor/utils/confirm-modal.js +12 -0
  507. package/dist/editor/utils/editor-errors.d.ts +19 -0
  508. package/dist/editor/utils/editor-errors.js +32 -0
  509. package/dist/editor/utils/focus-return.d.ts +56 -0
  510. package/dist/editor/utils/focus-return.js +106 -0
  511. package/dist/entry-link-resolver.d.ts +11 -1
  512. package/dist/entry-link-resolver.js +65 -5
  513. package/dist/entry-schema-registry.d.ts +3 -3
  514. package/dist/entry-schema-registry.js +7 -4
  515. package/dist/entry-schema.d.ts +49 -3
  516. package/dist/entry-schema.js +18 -0
  517. package/dist/git-manager.d.ts +172 -10
  518. package/dist/git-manager.js +449 -107
  519. package/dist/github-service.d.ts +39 -2
  520. package/dist/github-service.js +101 -7
  521. package/dist/http/handler.js +74 -9
  522. package/dist/http/types.d.ts +2 -0
  523. package/dist/http/worker-not-ready.d.ts +9 -4
  524. package/dist/http/worker-not-ready.js +59 -6
  525. package/dist/index.d.ts +2 -0
  526. package/dist/index.js +3 -0
  527. package/dist/item-keys.d.ts +46 -0
  528. package/dist/item-keys.js +235 -0
  529. package/dist/operating-mode/editor-mode-check.d.ts +15 -0
  530. package/dist/operating-mode/editor-mode-check.js +24 -0
  531. package/dist/operating-mode/mode-env.d.ts +13 -14
  532. package/dist/operating-mode/mode-env.js +13 -14
  533. package/dist/operating-mode/types.d.ts +2 -2
  534. package/dist/paths/branch.d.ts +0 -1
  535. package/dist/paths/branch.js +1 -8
  536. package/dist/paths/index.d.ts +1 -1
  537. package/dist/paths/index.js +1 -1
  538. package/dist/paths/validation.js +0 -2
  539. package/dist/preview.d.ts +8 -0
  540. package/dist/preview.js +7 -0
  541. package/dist/reference-resolver.js +1 -0
  542. package/dist/resolve-canopy-user.d.ts +1 -1
  543. package/dist/resolve-canopy-user.js +1 -1
  544. package/dist/schema/meta-loader.d.ts +18 -8
  545. package/dist/schema/meta-loader.js +42 -9
  546. package/dist/schema/registry-fingerprint.d.ts +14 -0
  547. package/dist/schema/registry-fingerprint.js +63 -0
  548. package/dist/schema/resolver.d.ts +7 -4
  549. package/dist/schema/resolver.js +7 -6
  550. package/dist/schema/schema-store.d.ts +7 -10
  551. package/dist/schema/schema-store.js +13 -11
  552. package/dist/schema/schema-unavailable-error.d.ts +11 -0
  553. package/dist/schema/schema-unavailable-error.js +15 -0
  554. package/dist/schema/types.d.ts +22 -0
  555. package/dist/schema-registry-record.d.ts +35 -0
  556. package/dist/schema-registry-record.js +134 -0
  557. package/dist/server.d.ts +79 -9
  558. package/dist/server.js +62 -8
  559. package/dist/services.d.ts +33 -5
  560. package/dist/services.js +155 -37
  561. package/dist/settings-workspace.d.ts +23 -3
  562. package/dist/settings-workspace.js +159 -45
  563. package/dist/stamp-on-publish.d.ts +36 -0
  564. package/dist/stamp-on-publish.js +71 -0
  565. package/dist/static/index.d.ts +58 -5
  566. package/dist/static/index.js +133 -3
  567. package/dist/static/seo.d.ts +5 -5
  568. package/dist/static/seo.js +5 -5
  569. package/dist/submission-attribution.d.ts +8 -0
  570. package/dist/submission-attribution.js +22 -0
  571. package/dist/system-users.d.ts +12 -0
  572. package/dist/system-users.js +12 -0
  573. package/dist/task-queue/cms-task-queue.d.ts +1 -1
  574. package/dist/task-queue/cms-task-queue.js +1 -1
  575. package/dist/task-queue/index.d.ts +1 -1
  576. package/dist/task-queue/index.js +1 -1
  577. package/dist/task-queue/task-queue.d.ts +6 -0
  578. package/dist/task-queue/task-queue.js +29 -0
  579. package/dist/task-queue/worker-status.d.ts +35 -9
  580. package/dist/task-queue/worker-status.js +84 -17
  581. package/dist/types.d.ts +90 -9
  582. package/dist/url-collision.js +3 -3
  583. package/dist/url-exclusivity-fixtures.d.ts +8 -1
  584. package/dist/url-exclusivity-fixtures.js +11 -1
  585. package/dist/user.js +2 -1
  586. package/dist/utils/atomic-write.js +2 -1
  587. package/dist/utils/base-branch.d.ts +13 -0
  588. package/dist/utils/base-branch.js +23 -0
  589. package/dist/utils/content-serialize.d.ts +15 -7
  590. package/dist/utils/content-serialize.js +353 -51
  591. package/dist/utils/content-write-lock.d.ts +6 -1
  592. package/dist/utils/content-write-lock.js +21 -6
  593. package/dist/utils/debug.d.ts +1 -1
  594. package/dist/utils/debug.js +5 -3
  595. package/dist/utils/error.js +13 -2
  596. package/dist/utils/escape-text.d.ts +7 -0
  597. package/dist/utils/escape-text.js +27 -0
  598. package/dist/utils/fs.d.ts +12 -4
  599. package/dist/utils/fs.js +18 -5
  600. package/dist/utils/git.d.ts +21 -1
  601. package/dist/utils/git.js +63 -8
  602. package/dist/utils/json-source-splice.d.ts +14 -0
  603. package/dist/utils/json-source-splice.js +120 -0
  604. package/dist/utils/markdown-body-splice.d.ts +39 -0
  605. package/dist/utils/markdown-body-splice.js +421 -0
  606. package/dist/utils/occ-json-write.js +18 -12
  607. package/dist/utils/provision-log.d.ts +25 -0
  608. package/dist/utils/provision-log.js +49 -0
  609. package/dist/utils/provisioning-lock.d.ts +32 -1
  610. package/dist/utils/provisioning-lock.js +78 -32
  611. package/dist/utils/sanitize-href.d.ts +9 -23
  612. package/dist/utils/sanitize-href.js +14 -28
  613. package/dist/utils/title-field.d.ts +10 -0
  614. package/dist/utils/title-field.js +47 -0
  615. package/dist/utils/yaml-source-splice.d.ts +58 -0
  616. package/dist/utils/yaml-source-splice.js +515 -0
  617. package/dist/validation/block-structural-keys.d.ts +1 -1
  618. package/dist/validation/block-structural-keys.js +1 -1
  619. package/dist/validation/deletion-checker.d.ts +21 -29
  620. package/dist/validation/deletion-checker.js +41 -117
  621. package/dist/validation/entry-validator.d.ts +6 -10
  622. package/dist/validation/entry-validator.js +19 -26
  623. package/dist/validation/field-traversal.d.ts +22 -0
  624. package/dist/validation/field-traversal.js +39 -8
  625. package/dist/validation/markdown-safety.d.ts +68 -0
  626. package/dist/validation/markdown-safety.js +596 -0
  627. package/dist/validation/mdx-allowlist.d.ts +55 -0
  628. package/dist/validation/mdx-allowlist.js +183 -0
  629. package/dist/validation/reference-validator.d.ts +2 -0
  630. package/dist/validation/reference-validator.js +3 -0
  631. package/dist/version.d.ts +1 -1
  632. package/dist/version.js +1 -1
  633. package/dist/worker/branch-policy.d.ts +124 -0
  634. package/dist/worker/branch-policy.js +243 -0
  635. package/dist/worker/bundle-header.d.ts +117 -0
  636. package/dist/worker/bundle-header.js +253 -0
  637. package/dist/worker/cms-worker.d.ts +236 -142
  638. package/dist/worker/cms-worker.js +772 -285
  639. package/dist/worker/gateway-egress.d.ts +43 -0
  640. package/dist/worker/gateway-egress.js +209 -0
  641. package/dist/worker/gateway-instance.d.ts +42 -0
  642. package/dist/worker/gateway-instance.js +129 -0
  643. package/dist/worker/git-process.d.ts +81 -0
  644. package/dist/worker/git-process.js +234 -0
  645. package/dist/worker/git-sync.d.ts +34 -17
  646. package/dist/worker/git-sync.js +394 -131
  647. package/dist/worker/github-auth.d.ts +9 -9
  648. package/dist/worker/github-auth.js +13 -15
  649. package/dist/worker/github-gateway-http.d.ts +62 -0
  650. package/dist/worker/github-gateway-http.js +977 -0
  651. package/dist/worker/github-gateway-wire.d.ts +590 -0
  652. package/dist/worker/github-gateway-wire.js +488 -0
  653. package/dist/worker/github-gateway.d.ts +238 -0
  654. package/dist/worker/github-gateway.js +726 -0
  655. package/dist/worker/github-mirror-upkeep.d.ts +75 -0
  656. package/dist/worker/github-mirror-upkeep.js +266 -0
  657. package/dist/worker/github-mirror.d.ts +364 -0
  658. package/dist/worker/github-mirror.js +1030 -0
  659. package/dist/worker/github-transfer.d.ts +56 -0
  660. package/dist/worker/github-transfer.js +444 -0
  661. package/dist/worker/history-rewrite.d.ts +4 -5
  662. package/dist/worker/history-rewrite.js +12 -17
  663. package/dist/worker/provisioned-workspace.d.ts +5 -5
  664. package/dist/worker/provisioned-workspace.js +5 -5
  665. package/dist/worker/rebase.d.ts +1 -1
  666. package/dist/worker/rebase.js +58 -54
  667. package/dist/worker/remote-git-maintenance.d.ts +3 -0
  668. package/dist/worker/remote-git-maintenance.js +12 -0
  669. package/dist/worker/schema-gate.d.ts +49 -0
  670. package/dist/worker/schema-gate.js +226 -0
  671. package/dist/worker/self-checks.d.ts +107 -0
  672. package/dist/worker/self-checks.js +358 -0
  673. package/dist/worker/shared-repo-git.d.ts +96 -0
  674. package/dist/worker/shared-repo-git.js +459 -0
  675. package/dist/worker/sparse-cone.d.ts +20 -0
  676. package/dist/worker/sparse-cone.js +136 -0
  677. package/dist/worker/stale-mirror.d.ts +82 -0
  678. package/dist/worker/stale-mirror.js +130 -0
  679. package/dist/worker/task-runner.d.ts +5 -3
  680. package/dist/worker/task-runner.js +287 -186
  681. package/dist/worker/worker-context.d.ts +36 -37
  682. package/package.json +35 -7
  683. package/dist/editor/EditorPanes.d.ts +0 -10
  684. package/dist/editor/EditorPanes.js +0 -98
  685. package/dist/editor/components/EditorFooter.d.ts +0 -11
  686. package/dist/editor/components/EditorFooter.js +0 -9
  687. package/dist/editor/components/EditorSidebar.d.ts +0 -67
  688. package/dist/editor/components/EditorSidebar.js +0 -22
@@ -0,0 +1,274 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { Badge, Button, Group, Popover, ScrollArea, Stack, Text, Textarea, Tooltip, } from '@mantine/core';
6
+ import { useClickOutside, useDidUpdate, useId, useIsomorphicEffect, useMediaQuery, useWindowEvent, } from '@mantine/hooks';
7
+ import { IconMessage, IconMessagePlus, IconPlus } from '@tabler/icons-react';
8
+ import { InlineCommentThread } from './InlineCommentThread.js';
9
+ import { useTrackedWrites } from './use-tracked-writes.js';
10
+ import { CloseRefusedNotice } from './CloseRefusedNotice.js';
11
+ import { useRefocusAfterSubmit } from './ThreadReplyControls.js';
12
+ import { commentsAriaKeyShortcuts, commentsShortcutLabel } from './form-comments.js';
13
+ import { choosePopoverSide } from './popover-placement.js';
14
+ import { CommentWriteAlert, toCommentWriteFailure, } from './CommentWriteAlert.js';
15
+ const HIGHLIGHT_MS = 2000;
16
+ const sortThreads = (threads) => [...threads].sort((a, b) => {
17
+ if (a.resolved !== b.resolved)
18
+ return a.resolved ? 1 : -1;
19
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
20
+ });
21
+ const openThreadsLabel = (count) => count === 1 ? '1 open thread' : `${count} open threads`;
22
+ /**
23
+ * A field's or entry's comment control and the popover it opens: the threads (open first), each
24
+ * with reply and Resolve, and a box for a new thread. Comments stay open on a read-only form,
25
+ * since reviewers comment on submitted branches.
26
+ */
27
+ export const CommentThreadsPopover = ({ threads, subject, variant, revealed = false, anchors, openRequest, currentUserId, canResolve, onAddComment, onResolveThread, onGetUserMetadata, highlightThreadId, threadFieldLabel, composeBlockedReason, testId, }) => {
28
+ const [opened, setOpened] = useState(false);
29
+ const [composing, setComposing] = useState(false);
30
+ // Drafts live here, not in the dropdown, which unmounts whenever the popover closes.
31
+ const [newThreadDraft, setNewThreadDraft] = useState('');
32
+ const [replyDrafts, setReplyDrafts] = useState({});
33
+ const headingRef = useRef(null);
34
+ const [flashThreadId, setFlashThreadId] = useState(undefined);
35
+ const threadRefs = useRef(new Map());
36
+ // Set by a post; the "New thread" button takes focus when it next mounts, as the composer goes.
37
+ const focusNewThreadRef = useRef(false);
38
+ // Touch screens have no hover, so the field icon stays visible there.
39
+ const noHover = useMediaQuery('(hover: none)');
40
+ const popoverId = useId();
41
+ const [controlNode, setControlNode] = useState(null);
42
+ // Read when focus returns: a popover that mounts open has no `controlNode` state as it opens.
43
+ const controlRef = useRef(null);
44
+ const setControl = useCallback((el) => {
45
+ controlRef.current = el;
46
+ setControlNode(el);
47
+ }, []);
48
+ const [dropdownNode, setDropdownNode] = useState(null);
49
+ // Set by a menu item's open, whose item is gone by close: its menu's trigger stands in.
50
+ const [returnFocusTo, setReturnFocusTo] = useState(undefined);
51
+ // Where focus returns on close, recorded as the popover opens, before its focus trap runs.
52
+ const openerRef = useRef(null);
53
+ const [side, setSide] = useState('below');
54
+ // A highlight's open always scrolls its field into place, even when it fits, and so overrides
55
+ // the smooth scroll `handleJumpToField` (`hooks/useCommentSystem.ts`) starts.
56
+ const [scrollOnOpen, setScrollOnOpen] = useState(false);
57
+ const placedRef = useRef(false);
58
+ // A click chose where focus goes, so a stand-in never takes it.
59
+ const pointerClosedRef = useRef(false);
60
+ const open = ({ scroll = false } = {}) => {
61
+ setReturnFocusTo(undefined);
62
+ setScrollOnOpen(scroll);
63
+ setOpened(true);
64
+ };
65
+ const sortedThreads = useMemo(() => sortThreads(threads), [threads]);
66
+ const openCount = useMemo(() => threads.filter((t) => !t.resolved).length, [threads]);
67
+ // A newly highlighted thread here opens the popover on it; adjusted during render, not in an
68
+ // effect, so the popover opens in the same commit.
69
+ const [seenHighlight, setSeenHighlight] = useState(undefined);
70
+ // It stays unseen until its thread arrives, so a thread list that catches up still opens it.
71
+ const highlightHere = !!highlightThreadId && threads.some((t) => t.id === highlightThreadId);
72
+ if (highlightThreadId !== seenHighlight && (!highlightThreadId || highlightHere)) {
73
+ setSeenHighlight(highlightThreadId);
74
+ if (highlightHere) {
75
+ open({ scroll: true });
76
+ setFlashThreadId(highlightThreadId);
77
+ }
78
+ }
79
+ const [seenOpenRequest, setSeenOpenRequest] = useState(openRequest);
80
+ if (openRequest !== seenOpenRequest) {
81
+ setSeenOpenRequest(openRequest);
82
+ if (openRequest) {
83
+ setOpened(true);
84
+ setScrollOnOpen(false);
85
+ setReturnFocusTo(() => openRequest.returnFocusTo);
86
+ }
87
+ }
88
+ // Once per open, before paint, as soon as both anchors exist: a field that mounts already
89
+ // highlighted gets them a commit after the popover opens.
90
+ useIsomorphicEffect(() => {
91
+ if (!opened) {
92
+ placedRef.current = false;
93
+ return;
94
+ }
95
+ if (placedRef.current || !anchors?.above || !anchors.below)
96
+ return;
97
+ placedRef.current = true;
98
+ setSide(choosePopoverSide(anchors, { scroll: scrollOnOpen }));
99
+ }, [opened, anchors]);
100
+ useIsomorphicEffect(() => {
101
+ if (!opened)
102
+ return;
103
+ pointerClosedRef.current = false;
104
+ const active = document.activeElement;
105
+ openerRef.current = returnFocusTo
106
+ ? { target: returnFocusTo, standIn: true }
107
+ : {
108
+ // A popover that opened with nothing focused, as on a highlight, returns to its control.
109
+ target: () => active instanceof HTMLElement && active !== document.body && active.isConnected
110
+ ? active
111
+ : controlRef.current,
112
+ standIn: false,
113
+ };
114
+ }, [opened]);
115
+ // Focus returns here rather than through Mantine, which refocuses the opener even after a click
116
+ // has put focus on another control.
117
+ useDidUpdate(() => {
118
+ const opener = openerRef.current;
119
+ if (opened || !opener)
120
+ return;
121
+ if (pointerClosedRef.current && opener.standIn)
122
+ return;
123
+ const closing = dropdownNode;
124
+ const timer = window.setTimeout(() => {
125
+ const active = document.activeElement;
126
+ // Focus left in the fading dropdown, on a detached or closing menu item, or nowhere, hasn't
127
+ // been moved on by the user.
128
+ const unmoved = !active ||
129
+ active === document.body ||
130
+ !active.isConnected ||
131
+ closing?.contains(active) ||
132
+ active.closest('[data-menu-dropdown]');
133
+ if (!unmoved)
134
+ return;
135
+ const target = opener.target();
136
+ target?.focus({ preventScroll: true });
137
+ if (opener.standIn)
138
+ target?.scrollIntoView?.({ block: 'nearest' });
139
+ }, 20);
140
+ return () => window.clearTimeout(timer);
141
+ }, [opened]);
142
+ useEffect(() => {
143
+ if (!flashThreadId)
144
+ return;
145
+ const frame = window.requestAnimationFrame(() => {
146
+ threadRefs.current.get(flashThreadId)?.scrollIntoView({ block: 'nearest' });
147
+ });
148
+ const timer = window.setTimeout(() => setFlashThreadId(undefined), HIGHLIGHT_MS);
149
+ return () => {
150
+ window.cancelAnimationFrame(frame);
151
+ window.clearTimeout(timer);
152
+ };
153
+ }, [flashThreadId]);
154
+ // While a post or Resolve is in flight the popover stays open, so its busy state and any error
155
+ // stay in view and a reopened composer can't send the same text twice.
156
+ const { inFlight, track, holdOpen, closeRefused } = useTrackedWrites();
157
+ const addComment = track(onAddComment);
158
+ const resolveThread = track(onResolveThread);
159
+ const close = () => {
160
+ if (holdOpen())
161
+ return;
162
+ setOpened(false);
163
+ setComposing(false);
164
+ focusNewThreadRef.current = false;
165
+ };
166
+ const controlLabel = openCount > 0
167
+ ? `Comment on ${subject} (${openThreadsLabel(openCount)})`
168
+ : `Comment on ${subject}`;
169
+ // Open threads keep the control in view as a count; resolved ones are listed by the form's
170
+ // "Field comments" menu.
171
+ const shown = revealed || opened || noHover || openCount > 0;
172
+ // Idle, the control leaves the tab order: the form's menu and shortcut reach comments instead.
173
+ const idle = !opened && openCount === 0;
174
+ const anchored = variant === 'field' && anchors !== undefined;
175
+ const controlTooltip = openCount > 0
176
+ ? `Comment on ${subject} (${openThreadsLabel(openCount)}, ${commentsShortcutLabel()})`
177
+ : `Comment on ${subject} (${commentsShortcutLabel()})`;
178
+ // Mantine's Escape is off in flight; on the window, as a disabled composer drops focus.
179
+ useWindowEvent('keydown', (event) => {
180
+ if (event.key === 'Escape' && opened)
181
+ holdOpen();
182
+ });
183
+ const closeByPointer = () => {
184
+ if (holdOpen())
185
+ return;
186
+ pointerClosedRef.current = true;
187
+ close();
188
+ };
189
+ // Anchored, the control is outside the popover's target, so clicks on it count as inside here.
190
+ useClickOutside(() => anchored && opened && closeByPointer(), null, [controlNode, dropdownNode]);
191
+ const target = variant === 'entry' ? (_jsx(Button, { variant: "subtle", color: "gray", c: "var(--mantine-color-text)", size: "compact-sm", leftSection: _jsx(IconMessage, { size: 16, "aria-hidden": true }), rightSection: openCount > 0 ? (_jsx(Badge, { size: "sm", variant: "light", color: "orange", c: "var(--mantine-color-text)", "aria-hidden": true, children: openCount })) : undefined, ref: setControl, "aria-label": controlLabel, "aria-expanded": opened, "aria-haspopup": "dialog", onClick: (event) => (!opened ? open() : event.detail > 0 ? closeByPointer() : close()), "data-testid": testId, children: "Comment on this entry" })) : (
192
+ // One element for every state, so a count change never remounts the control focus returns to.
193
+ _jsx(Button, { variant: openCount > 0 ? 'light' : 'subtle', color: openCount > 0 ? 'orange' : 'gray', c: openCount > 0 ? 'var(--mantine-color-text)' : undefined, size: "compact-xs", px: 4, ref: setControl, tabIndex: idle ? -1 : undefined, "aria-label": controlLabel, "aria-expanded": opened, "aria-haspopup": "dialog", "aria-controls": anchored && opened ? `${popoverId}-dropdown` : undefined, "aria-keyshortcuts": commentsAriaKeyShortcuts(), onClick: (event) => (!opened ? open() : event.detail > 0 ? closeByPointer() : close()), "data-testid": testId, "data-comment-control": true, "data-revealed": shown ? '' : undefined, style: {
194
+ // Hidden by opacity, not display, so a pointer or focus on the field shows it in place.
195
+ opacity: shown ? 1 : 0,
196
+ transition: 'opacity 120ms ease',
197
+ }, children: _jsxs(Group, { gap: 4, wrap: "nowrap", children: [threads.length === 0 ? (_jsx(IconMessagePlus, { size: 16, "aria-hidden": true })) : (_jsx(IconMessage, { size: 14, color: openCount > 0 ? 'var(--mantine-color-orange-text)' : undefined, "aria-hidden": true })), openCount > 0 && _jsx("span", { children: openCount })] }) }));
198
+ const tooltipTarget = variant === 'entry' ? (target) : (_jsx(Tooltip, { label: controlTooltip, withinPortal: true, disabled: opened, children: target }));
199
+ const popover = (_jsxs(Popover, { id: popoverId, opened: opened, onChange: (next) => (next ? open() : close()), onClose: close,
200
+ // Never over the field's input; the entry's button sits above the form.
201
+ position: anchored && side === 'below' ? 'bottom-start' : 'top-start',
202
+ // Clamped to the room on its side, scrolling inside, since it never flips over the input.
203
+ middlewares: anchored ? { flip: false, shift: true, size: { padding: 8 } } : undefined, width: 380, shadow: "md", withArrow: !anchored, trapFocus: true, returnFocus: false,
204
+ // Off while a write is in flight, when `close` refuses anyway, so Mantine's Escape handler
205
+ // doesn't run its focus return on a popover that stays open.
206
+ closeOnEscape: inFlight === 0, closeOnClickOutside: !anchored,
207
+ // Anchored, the target is an empty element, so the control and dropdown carry the roles.
208
+ withRoles: !anchored, children: [_jsx(Popover.Target, { children: anchored ? _jsx(AnchorTarget, { node: anchors?.[side] }) : tooltipTarget }), _jsx(Popover.Dropdown, { ref: setDropdownNode, "aria-label": `Comments on ${subject}`, ...(anchored ? { role: 'dialog', id: `${popoverId}-dropdown`, tabIndex: -1 } : {}), style: anchored ? { overflowY: 'auto' } : undefined, "data-testid": `${testId}-popover`, p: "sm", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Text, { ref: headingRef, size: "sm", fw: 600, tabIndex: -1, "data-autofocus": sortedThreads.length > 0 || undefined, children: ["Comments on ", subject] }), sortedThreads.length > 0 && (_jsx(ScrollArea.Autosize, { mah: 360, offsetScrollbars: true, type: "auto", children: _jsx(Stack, { gap: "sm", children: sortedThreads.map((thread) => (_jsx("div", { ref: (el) => {
209
+ if (el)
210
+ threadRefs.current.set(thread.id, el);
211
+ else
212
+ threadRefs.current.delete(thread.id);
213
+ }, "data-highlighted": flashThreadId === thread.id ? '' : undefined, style: {
214
+ borderRadius: 4,
215
+ outline: flashThreadId === thread.id
216
+ ? '2px solid var(--mantine-color-blue-5)'
217
+ : undefined,
218
+ outlineOffset: -2,
219
+ }, children: _jsx(InlineCommentThread, { thread: thread, onAddReply: (text) => addComment(text, thread.id), onResolve: () => resolveThread(thread.id),
220
+ // Resolve and the reply box go with the open state, so focus stays inside.
221
+ focusAfterResolve: () => headingRef.current, replyText: replyDrafts[thread.id] ?? '', onReplyTextChange: (text) => setReplyDrafts((prev) => ({ ...prev, [thread.id]: text })), currentUserId: currentUserId, canResolve: canResolve, onGetUserMetadata: onGetUserMetadata, fieldLabel: threadFieldLabel?.(thread) }) }, thread.id))) }) })), sortedThreads.length === 0 || composing ? (_jsx(NewThreadComposer, { blockedReason: composeBlockedReason, text: newThreadDraft, onTextChange: setNewThreadDraft, onSubmit: async (text) => {
222
+ // Set first: the first thread's arrival mounts the button before this resolves.
223
+ focusNewThreadRef.current = true;
224
+ try {
225
+ await addComment(text);
226
+ }
227
+ catch (err) {
228
+ focusNewThreadRef.current = false;
229
+ throw err;
230
+ }
231
+ setComposing(false);
232
+ }, onCancel: sortedThreads.length > 0 ? () => setComposing(false) : close })) : (_jsx(Group, { children: _jsx(Button, { ref: (el) => {
233
+ if (el && focusNewThreadRef.current) {
234
+ focusNewThreadRef.current = false;
235
+ el.focus();
236
+ }
237
+ }, variant: "subtle", size: "compact-sm", leftSection: _jsx(IconPlus, { size: 14, "aria-hidden": true }), onClick: () => setComposing(true), "data-testid": "new-thread-button", children: "New thread" }) })), _jsx(CloseRefusedNotice, { refused: closeRefused })] }) })] }));
238
+ return anchored ? (_jsxs(_Fragment, { children: [tooltipTarget, popover] })) : (popover);
239
+ };
240
+ /** An empty element placed in `node`, as the popover's position reference. */
241
+ const AnchorTarget = React.forwardRef(function AnchorTarget({ node }, ref) {
242
+ return node ? createPortal(_jsx("div", { ref: ref, "aria-hidden": true }), node) : null;
243
+ });
244
+ const NewThreadComposer = ({ text, onTextChange: setText, onSubmit, onCancel, blockedReason }) => {
245
+ const [submitting, setSubmitting] = useState(false);
246
+ const [error, setError] = useState(null);
247
+ const boxRef = useRef(null);
248
+ // Shown on request ("New thread"), so it takes focus.
249
+ useEffect(() => {
250
+ boxRef.current?.focus();
251
+ }, []);
252
+ useRefocusAfterSubmit(submitting, boxRef);
253
+ const submit = async () => {
254
+ if (!text.trim() || blockedReason)
255
+ return;
256
+ setSubmitting(true);
257
+ setError(null);
258
+ try {
259
+ await onSubmit(text);
260
+ setText('');
261
+ }
262
+ catch (err) {
263
+ setError(toCommentWriteFailure(err));
264
+ }
265
+ finally {
266
+ setSubmitting(false);
267
+ }
268
+ };
269
+ const button = (_jsx(Button, { size: "compact-sm", onClick: () => void submit(), loading: submitting, disabled: !text.trim(), "data-disabled": blockedReason ? true : undefined, "aria-disabled": blockedReason ? true : undefined, "data-testid": "create-thread-button", children: "Comment" }));
270
+ // `aria-disabled` keeps it focusable for its reason. Unblocked it isn't wrapped: around a focused
271
+ // button the Tooltip keeps Escape from the listener that refuses a close mid-post.
272
+ const commentButton = blockedReason ? (_jsx(Tooltip, { label: blockedReason, multiline: true, maw: 280, children: button })) : (button);
273
+ return (_jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { ref: boxRef, "aria-label": "New comment", placeholder: "Add a comment\u2026", value: text, onChange: (e) => setText(e.currentTarget.value), minRows: 2, autosize: true, disabled: submitting, description: blockedReason, error: error && !error.mayHaveLanded ? error.message : undefined, "data-autofocus": true, "data-testid": "new-thread-textarea" }), error?.mayHaveLanded && _jsx(CommentWriteAlert, { failure: error, onClose: () => setError(null) }), _jsxs(Group, { gap: "xs", children: [commentButton, _jsx(Button, { size: "compact-sm", variant: "subtle", color: "gray", onClick: onCancel, disabled: submitting, children: "Cancel" })] })] }));
274
+ };
@@ -0,0 +1,11 @@
1
+ /** A comment write's failure, and whether the write may have landed anyway. */
2
+ export interface CommentWriteFailure {
3
+ message: string;
4
+ mayHaveLanded: boolean;
5
+ }
6
+ export declare const toCommentWriteFailure: (err: unknown) => CommentWriteFailure;
7
+ /** A failed comment write: red, or yellow when it may have been posted. */
8
+ export declare function CommentWriteAlert({ failure, onClose, }: {
9
+ failure: CommentWriteFailure;
10
+ onClose: () => void;
11
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Alert } from '@mantine/core';
3
+ import { IconAlertCircle, IconAlertTriangle } from '@tabler/icons-react';
4
+ import { getErrorMessage } from '../../utils/error.js';
5
+ import { MayHaveLandedError } from '../unanswered.js';
6
+ export const toCommentWriteFailure = (err) => ({
7
+ message: getErrorMessage(err),
8
+ mayHaveLanded: err instanceof MayHaveLandedError,
9
+ });
10
+ /** A failed comment write: red, or yellow when it may have been posted. */
11
+ export function CommentWriteAlert({ failure, onClose, }) {
12
+ return (_jsx(Alert, { icon: failure.mayHaveLanded ? _jsx(IconAlertTriangle, { size: 16 }) : _jsx(IconAlertCircle, { size: 16 }), color: failure.mayHaveLanded ? 'yellow' : 'red', variant: "light", onClose: onClose, withCloseButton: true, children: failure.message }));
13
+ }
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { CommentThread } from '../../comment-store.js';
3
+ import type { UserSearchResult } from '../../auth/types.js';
3
4
  export interface EntryCommentsProps {
4
5
  /** All comments (will be filtered for entry-level) */
5
6
  comments: CommentThread[];
@@ -13,13 +14,18 @@ export interface EntryCommentsProps {
13
14
  onAddComment: (text: string, type: 'field' | 'entry' | 'branch', entryPath?: string, canopyPath?: string, threadId?: string) => Promise<void>;
14
15
  /** Handler to resolve a thread */
15
16
  onResolveThread: (threadId: string) => Promise<void>;
16
- /** Auto-focus and expand */
17
- autoFocus?: boolean;
18
17
  /** Thread ID to highlight and scroll to */
19
18
  highlightThreadId?: string;
19
+ /** Resolves author ids to names; without it authors show as ids. */
20
+ onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
21
+ /** Changes with the entry or branch, which resets the popover, so a draft never posts elsewhere. */
22
+ resetKey: string;
23
+ /** Names the field of a thread on a removed item. */
24
+ removedItemFieldLabel?: (thread: CommentThread) => string;
20
25
  }
21
26
  /**
22
- * Entry-level comments section displayed at the top of the form.
23
- * Uses ThreadCarousel for navigation.
27
+ * The top of the form's comment actions: the "Field comments" menu, the keyboard route to
28
+ * field comments, and "Comment on this entry" with the entry's threads. Those include the field
29
+ * threads whose list item or block was removed, which stay on the entry, marked.
24
30
  */
25
31
  export declare const EntryComments: React.FC<EntryCommentsProps>;
@@ -1,21 +1,18 @@
1
1
  'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useMemo, useState } from 'react';
4
- import { Box, Button } from '@mantine/core';
5
- import { ThreadCarousel } from './ThreadCarousel.js';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useMemo } from 'react';
4
+ import { Group } from '@mantine/core';
5
+ import { CommentThreadsPopover } from './CommentThreadsPopover.js';
6
+ import { FormCommentsMenu } from './form-comments.js';
6
7
  /**
7
- * Entry-level comments section displayed at the top of the form.
8
- * Uses ThreadCarousel for navigation.
8
+ * The top of the form's comment actions: the "Field comments" menu, the keyboard route to
9
+ * field comments, and "Comment on this entry" with the entry's threads. Those include the field
10
+ * threads whose list item or block was removed, which stay on the entry, marked.
9
11
  */
10
- export const EntryComments = ({ comments, entryPath, currentUserId, canResolve, onAddComment, onResolveThread, autoFocus, highlightThreadId, }) => {
11
- const [showCarousel, setShowCarousel] = useState(false);
12
- const entryThreads = useMemo(() => comments.filter((t) => t.type === 'entry' && t.entryPath === entryPath), [comments, entryPath]);
13
- const shouldShowCarousel = entryThreads.length > 0 || showCarousel || autoFocus;
14
- const handleAddComment = async (text, threadId) => {
15
- await onAddComment(text, 'entry', entryPath, undefined, threadId);
16
- };
17
- if (!shouldShowCarousel) {
18
- return (_jsx(Box, { children: _jsx(Button, { size: "xs", variant: "subtle", color: "gray", onClick: () => setShowCarousel(true), style: { fontSize: '0.75rem', height: '1.5rem' }, children: "New file comment" }) }));
19
- }
20
- return (_jsx(ThreadCarousel, { threads: entryThreads, label: "Entry Comments", contextType: "entry", currentUserId: currentUserId, canResolve: canResolve, onAddComment: handleAddComment, onResolveThread: onResolveThread, autoFocus: autoFocus, autoOpenNewThread: showCarousel && entryThreads.length === 0, highlightThreadId: highlightThreadId }));
12
+ export const EntryComments = ({ comments, entryPath, currentUserId, canResolve, onAddComment, onResolveThread, highlightThreadId, onGetUserMetadata, resetKey, removedItemFieldLabel, }) => {
13
+ const entryThreads = useMemo(() => comments.filter((t) => t.entryPath === entryPath &&
14
+ (t.type === 'entry' || (t.type === 'field' && t.onRemovedItem))), [comments, entryPath]);
15
+ return (_jsxs(Group, { justify: "flex-end", gap: "xs", children: [_jsx(FormCommentsMenu, { comments: comments, entryPath: entryPath }), _jsx(CommentThreadsPopover, { threads: entryThreads, subject: "this entry", variant: "entry", currentUserId: currentUserId, canResolve: canResolve, onAddComment: (text, threadId) => onAddComment(text, 'entry', entryPath, undefined, threadId), onResolveThread: onResolveThread, onGetUserMetadata: onGetUserMetadata, highlightThreadId: highlightThreadId, threadFieldLabel: (thread) => thread.onRemovedItem && thread.canopyPath
16
+ ? (removedItemFieldLabel?.(thread) ?? thread.canopyPath)
17
+ : undefined, testId: "entry-comment-button" }, resetKey)] }));
21
18
  };
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { CommentThread } from '../../comment-store.js';
3
+ import type { UserSearchResult } from '../../auth/types.js';
3
4
  export interface FieldWrapperProps {
4
5
  /** Child form field element */
5
6
  children: React.ReactNode;
@@ -19,13 +20,26 @@ export interface FieldWrapperProps {
19
20
  onAddComment: (text: string, type: 'field' | 'entry' | 'branch', entryPath?: string, canopyPath?: string, threadId?: string) => Promise<void>;
20
21
  /** Handler to resolve a thread */
21
22
  onResolveThread: (threadId: string) => Promise<void>;
22
- /** Field label for positioning the new comment button */
23
- fieldLabel?: string;
23
+ /** The field's breadcrumb ("Links › /a › Href"), which names the control: "Comment on <breadcrumb>". */
24
+ fieldLabel: string;
24
25
  /** Thread ID to highlight and scroll to */
25
26
  highlightThreadId?: string;
27
+ /** Resolves author ids to names; without it authors show as ids. */
28
+ onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
29
+ /**
30
+ * `label`: the field puts the control in its `FieldLabel` via `useFieldCommentControl`.
31
+ * `corner`: the wrapper places it at the top right, for custom renderers, whose labels are
32
+ * their own.
33
+ */
34
+ controlPlacement?: 'label' | 'corner';
35
+ /** Changes with the entry or branch, which resets the popover, so a draft never posts elsewhere. */
36
+ resetKey: string;
37
+ /** Why a new thread can't be posted on this field yet (`CommentThreadsPopover`). */
38
+ commentBlockedReason?: string;
26
39
  }
27
40
  /**
28
- * Wraps a form field with inline comment functionality.
29
- * Shows ThreadCarousel when comments exist or when user clicks "New comment".
41
+ * Gives a form field its comment control. The control shows while the pointer or focus is on
42
+ * this field and not on a field nested inside it, so a group never shows a second icon for its
43
+ * children; a field with open threads always shows it.
30
44
  */
31
45
  export declare const FieldWrapper: React.FC<FieldWrapperProps>;
@@ -1,36 +1,70 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useRef, useState } from 'react';
4
- import { Box, Button } from '@mantine/core';
5
- import { ThreadCarousel } from './ThreadCarousel.js';
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { Box } from '@mantine/core';
5
+ import { CommentThreadsPopover } from './CommentThreadsPopover.js';
6
+ import { FieldCommentContext } from './field-comment-context.js';
7
+ import { useFormComments } from './form-comments.js';
8
+ import { whenRevealed } from '../field-target.js';
9
+ const FIELD_SCOPE_ATTR = 'data-field-comments';
6
10
  /**
7
- * Wraps a form field with inline comment functionality.
8
- * Shows ThreadCarousel when comments exist or when user clicks "New comment".
11
+ * Gives a form field its comment control. The control shows while the pointer or focus is on
12
+ * this field and not on a field nested inside it, so a group never shows a second icon for its
13
+ * children; a field with open threads always shows it.
9
14
  */
10
- export const FieldWrapper = ({ children, canopyPath, entryPath, threads, autoFocus, currentUserId, canResolve, onAddComment, onResolveThread, highlightThreadId, }) => {
15
+ export const FieldWrapper = ({ children, canopyPath, entryPath, threads, autoFocus, currentUserId, canResolve, onAddComment, onResolveThread, fieldLabel, highlightThreadId, onGetUserMetadata, controlPlacement = 'label', resetKey, commentBlockedReason, }) => {
11
16
  const wrapperRef = useRef(null);
12
- const [showCarousel, setShowCarousel] = useState(false);
13
- const shouldShowCarousel = threads.length > 0 || showCarousel;
17
+ const [pointerOn, setPointerOn] = useState(false);
18
+ const [focusOn, setFocusOn] = useState(false);
19
+ const [above, setAbove] = useState(null);
20
+ const [below, setBelow] = useState(null);
21
+ const anchors = useMemo(() => ({ above, below }), [above, below]);
22
+ // A highlighted thread here opens the popover, which scrolls the field itself.
23
+ const highlightHere = !!highlightThreadId && threads.some((t) => t.id === highlightThreadId);
24
+ const [openRequest, setOpenRequest] = useState(undefined);
25
+ const formComments = useFormComments();
26
+ useEffect(() => formComments?.register(canopyPath, {
27
+ subject: fieldLabel,
28
+ blockedReason: commentBlockedReason,
29
+ // A new object per request, so asking again while it is open still counts.
30
+ open: (returnFocusTo) => {
31
+ const field = wrapperRef.current;
32
+ if (field)
33
+ whenRevealed(field, () => setOpenRequest({ returnFocusTo }));
34
+ else
35
+ setOpenRequest({ returnFocusTo });
36
+ },
37
+ element: () => wrapperRef.current,
38
+ }), [formComments, canopyPath, fieldLabel, commentBlockedReason]);
14
39
  useEffect(() => {
15
- if (autoFocus) {
16
- wrapperRef.current?.scrollIntoView({
17
- behavior: 'smooth',
18
- block: 'center',
19
- });
40
+ const field = wrapperRef.current;
41
+ if (!field)
42
+ return;
43
+ if (highlightHere)
44
+ whenRevealed(field, () => { });
45
+ else if (autoFocus) {
46
+ whenRevealed(field, () => field.scrollIntoView({ behavior: 'smooth', block: 'center' }));
20
47
  }
21
- }, [autoFocus]);
22
- const handleAddComment = async (text, threadId) => {
23
- await onAddComment(text, 'field', entryPath, canopyPath, threadId);
24
- };
25
- return (_jsxs(Box, { ref: wrapperRef, "data-canopy-field": canopyPath, pos: "relative", style: { width: '100%' }, children: [_jsx(Box, { style: {
26
- outline: autoFocus ? '2px solid var(--mantine-color-blue-5)' : undefined,
27
- outlineOffset: autoFocus ? 2 : undefined,
28
- borderRadius: 4,
29
- transition: 'outline 0.2s ease',
30
- }, children: children }), !shouldShowCarousel && (_jsx(Box, { style: {
31
- position: 'absolute',
32
- top: 4,
33
- right: 8,
34
- zIndex: 1,
35
- }, children: _jsx(Button, { size: "xs", variant: "subtle", color: "gray", onClick: () => setShowCarousel(true), style: { fontSize: '0.75rem', height: '1.5rem' }, "data-testid": `field-new-comment-${canopyPath}`, children: "New comment" }) })), shouldShowCarousel && (_jsx(ThreadCarousel, { threads: threads, contextType: "field", currentUserId: currentUserId, canResolve: canResolve, onAddComment: handleAddComment, onResolveThread: onResolveThread, autoFocus: autoFocus, autoOpenNewThread: showCarousel && threads.length === 0, highlightThreadId: highlightThreadId, onCancelNewThread: () => setShowCarousel(false) }))] }));
48
+ }, [autoFocus, highlightHere]);
49
+ // Events from a nested field, or from a popover portalled out of the DOM, have another
50
+ // closest scope (or none), so they leave this field's icon hidden.
51
+ const ownsTarget = (target) => target instanceof Element &&
52
+ wrapperRef.current !== null &&
53
+ target.closest(`[${FIELD_SCOPE_ATTR}]`) === wrapperRef.current;
54
+ const control = (_jsx(CommentThreadsPopover, { threads: threads, subject: fieldLabel, variant: "field", revealed: pointerOn || focusOn, anchors: anchors, openRequest: openRequest, currentUserId: currentUserId, canResolve: canResolve, onAddComment: (text, threadId) => onAddComment(text, 'field', entryPath, canopyPath, threadId), onResolveThread: onResolveThread, onGetUserMetadata: onGetUserMetadata, highlightThreadId: highlightThreadId, composeBlockedReason: commentBlockedReason, testId: `field-comment-${canopyPath}` }, resetKey));
55
+ return (_jsxs(Box, { ref: wrapperRef, "data-canopy-field": canopyPath, [FIELD_SCOPE_ATTR]: '', pos: "relative", style: { width: '100%' }, onMouseOver: (event) => setPointerOn(ownsTarget(event.target)), onMouseLeave: () => setPointerOn(false), onFocus: (event) => {
56
+ const owned = ownsTarget(event.target);
57
+ setFocusOn(owned);
58
+ if (owned)
59
+ formComments?.noteFocus(canopyPath);
60
+ }, onBlur: (event) => {
61
+ const next = event.relatedTarget;
62
+ if (!(next instanceof Node && event.currentTarget.contains(next)))
63
+ setFocusOn(false);
64
+ }, children: [_jsx("div", { ref: setAbove, "data-comment-anchor": "above" }), _jsx(FieldCommentContext.Provider, { value: { canopyPath, control }, children: _jsx(Box, { style: {
65
+ outline: autoFocus ? '2px solid var(--mantine-color-blue-5)' : undefined,
66
+ outlineOffset: autoFocus ? 2 : undefined,
67
+ borderRadius: 4,
68
+ transition: 'outline 0.2s ease',
69
+ }, children: children }) }), _jsx("div", { ref: setBelow, "data-comment-anchor": "below" }), controlPlacement === 'corner' && (_jsx(Box, { pos: "absolute", top: 4, right: 4, style: { zIndex: 1 }, children: control }))] }));
36
70
  };
@@ -8,15 +8,22 @@ export interface InlineCommentThreadProps {
8
8
  onAddReply: (text: string) => Promise<void>;
9
9
  /** Handler to resolve this thread */
10
10
  onResolve: () => Promise<void>;
11
+ /** Where focus goes after a resolve; see `ThreadReplyControls`. */
12
+ focusAfterResolve?: () => HTMLElement | null | undefined;
11
13
  /** Current user ID for permission checking */
12
14
  currentUserId: string;
13
- /** Whether user can resolve threads */
15
+ /** Whether the user may resolve any thread; a thread's own author always may resolve it. */
14
16
  canResolve: boolean;
15
17
  /** Optional function to fetch user metadata for displaying user badges */
16
18
  onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
19
+ /** The reply draft, when the caller keeps it, so it outlives this component; with `onReplyTextChange`. */
20
+ replyText?: string;
21
+ onReplyTextChange?: (text: string) => void;
22
+ /** The field a thread shown away from it is on, such as one on a removed item. */
23
+ fieldLabel?: string;
17
24
  }
18
25
  /**
19
- * Individual comment thread display within the carousel.
26
+ * One comment thread, as the comment popover and ThreadCarousel list them.
20
27
  * Always shows full thread view with per-thread scrolling.
21
28
  */
22
29
  export declare const InlineCommentThread: React.FC<InlineCommentThreadProps>;
@@ -1,60 +1,24 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState } from 'react';
4
- import { Alert, Badge, Button, Group, Paper, Stack, Text, Textarea } from '@mantine/core';
5
- import { IconAlertCircle } from '@tabler/icons-react';
3
+ import { Badge, Group, Paper, Stack, Text } from '@mantine/core';
6
4
  import { UserBadge } from '../components/UserBadge.js';
7
- import { formatRelativeTime } from '../relative-time.js';
5
+ import { userIdLabel } from '../user-display.js';
6
+ import { RelativeTime } from '../components/RelativeTime.js';
7
+ import { ThreadReplyControls } from './ThreadReplyControls.js';
8
+ import { ON_REMOVED_ITEM } from './item-anchor-copy.js';
8
9
  /**
9
- * Individual comment thread display within the carousel.
10
+ * One comment thread, as the comment popover and ThreadCarousel list them.
10
11
  * Always shows full thread view with per-thread scrolling.
11
12
  */
12
- export const InlineCommentThread = ({ thread, onAddReply, onResolve, currentUserId, canResolve, onGetUserMetadata, }) => {
13
- const [replyText, setReplyText] = useState('');
14
- const [isSubmitting, setIsSubmitting] = useState(false);
15
- const [error, setError] = useState(null);
16
- const handleReply = async () => {
17
- if (!replyText.trim())
18
- return;
19
- setIsSubmitting(true);
20
- setError(null);
21
- try {
22
- await onAddReply(replyText);
23
- setReplyText('');
24
- }
25
- catch (err) {
26
- setError(err instanceof Error ? err.message : 'Failed to add reply');
27
- }
28
- finally {
29
- setIsSubmitting(false);
30
- }
31
- };
32
- const handleResolve = async () => {
33
- setIsSubmitting(true);
34
- setError(null);
35
- try {
36
- await onResolve();
37
- }
38
- catch (err) {
39
- setError(err instanceof Error ? err.message : 'Failed to resolve thread');
40
- }
41
- finally {
42
- setIsSubmitting(false);
43
- }
44
- };
45
- const canUserResolve = () => {
46
- if (!canResolve)
47
- return false;
48
- return thread.authorId === currentUserId || canResolve;
49
- };
50
- return (_jsx(Paper, { withBorder: true, p: "md", bg: "white", "data-testid": "inline-comment-thread", style: {
13
+ export const InlineCommentThread = ({ thread, onAddReply, onResolve, focusAfterResolve, currentUserId, canResolve, onGetUserMetadata, replyText, onReplyTextChange, fieldLabel, }) => {
14
+ return (_jsx(Paper, { withBorder: true, p: "md", bg: "var(--mantine-color-body)", "data-testid": "inline-comment-thread", style: {
51
15
  width: '100%',
52
16
  overflowY: 'auto',
53
17
  display: 'flex',
54
18
  flexDirection: 'column',
55
- }, children: _jsxs(Stack, { gap: "md", children: [_jsxs(Group, { gap: "xs", children: [!thread.resolved && (_jsx(Badge, { size: "xs", color: "orange", variant: "light", children: "Unresolved" })), thread.resolved && (_jsx(Badge, { size: "xs", color: "green", variant: "light", children: "Resolved" }))] }), _jsx(Stack, { gap: "md", children: thread.comments.map((comment, idx) => (_jsxs("div", { children: [idx > 0 && (_jsx("div", { style: {
56
- borderTop: '1px solid var(--mantine-color-gray-3)',
19
+ }, children: _jsxs(Stack, { gap: "md", children: [_jsxs(Group, { gap: "xs", children: [!thread.resolved && (_jsx(Badge, { size: "xs", color: "orange", variant: "light", children: "Unresolved" })), thread.resolved && (_jsx(Badge, { size: "xs", color: "green", variant: "light", children: "Resolved" })), thread.onRemovedItem && (_jsx(Badge, { size: "xs", color: "gray", variant: "light", c: "var(--mantine-color-text)", children: ON_REMOVED_ITEM }))] }), fieldLabel && (_jsx(Text, { size: "xs", c: "dimmed", "data-testid": "inline-comment-thread-field", children: fieldLabel })), _jsx(Stack, { gap: "md", children: thread.comments.map((comment, idx) => (_jsxs("div", { children: [idx > 0 && (_jsx("div", { style: {
20
+ borderTop: '1px solid var(--mantine-color-default-border)',
57
21
  marginTop: 8,
58
22
  marginBottom: 8,
59
- } })), _jsxs(Stack, { gap: 4, children: [_jsxs(Group, { gap: "xs", justify: "space-between", children: [onGetUserMetadata ? (_jsx(UserBadge, { userId: comment.userId, getUserMetadata: onGetUserMetadata, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })) : (_jsx(Text, { size: "xs", fw: 600, children: comment.userId })), _jsx(Text, { size: "xs", c: "dimmed", children: formatRelativeTime(comment.timestamp) })] }), _jsx(Text, { size: "sm", style: { whiteSpace: 'pre-wrap' }, children: comment.text })] })] }, comment.id))) }), error && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", variant: "light", onClose: () => setError(null), withCloseButton: true, children: error })), !thread.resolved && (_jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { placeholder: "Write a reply...", value: replyText, onChange: (e) => setReplyText(e.target.value), minRows: 2, disabled: isSubmitting }), _jsxs(Group, { gap: "xs", children: [_jsx(Button, { size: "xs", onClick: handleReply, loading: isSubmitting, disabled: !replyText.trim(), children: "Reply" }), canUserResolve() && (_jsx(Button, { size: "xs", variant: "light", color: "green", onClick: handleResolve, loading: isSubmitting, "data-testid": "resolve-thread-button", children: "Resolve" }))] })] })), thread.resolved && thread.resolvedBy && (_jsxs(Group, { gap: 4, children: [_jsx(Text, { size: "xs", c: "dimmed", fs: "italic", children: "Resolved by" }), onGetUserMetadata ? (_jsx(UserBadge, { userId: thread.resolvedBy, getUserMetadata: onGetUserMetadata, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })) : (_jsx(Text, { size: "xs", c: "dimmed", fs: "italic", children: thread.resolvedBy })), thread.resolvedAt && (_jsx(Text, { size: "xs", c: "dimmed", fs: "italic", children: formatRelativeTime(thread.resolvedAt) }))] }))] }) }));
23
+ } })), _jsxs(Stack, { gap: 4, children: [_jsxs(Group, { gap: "xs", justify: "space-between", children: [onGetUserMetadata ? (_jsx(UserBadge, { userId: comment.userId, getUserMetadata: onGetUserMetadata, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })) : (_jsx(Text, { size: "xs", fw: 600, children: userIdLabel(comment.userId) })), _jsx(RelativeTime, { iso: comment.timestamp, size: "xs", c: "dimmed" })] }), _jsx(Text, { size: "sm", style: { whiteSpace: 'pre-wrap' }, children: comment.text })] })] }, comment.id))) }), !thread.resolved && (_jsx(ThreadReplyControls, { thread: thread, onAddReply: onAddReply, onResolve: onResolve, focusAfterResolve: focusAfterResolve, currentUserId: currentUserId, canResolve: canResolve, replyText: replyText, onReplyTextChange: onReplyTextChange })), thread.resolved && thread.resolvedBy && (_jsxs(Group, { gap: 4, children: [_jsx(Text, { size: "xs", c: "dimmed", fs: "italic", children: "Resolved by" }), onGetUserMetadata ? (_jsx(UserBadge, { userId: thread.resolvedBy, getUserMetadata: onGetUserMetadata, variant: "avatar-name", size: "xs", showEmailTooltip: true, showBadge: true, badgeVariant: "light", color: "gray" })) : (_jsx(Text, { size: "xs", c: "dimmed", fs: "italic", children: userIdLabel(thread.resolvedBy) })), thread.resolvedAt && (_jsx(RelativeTime, { iso: thread.resolvedAt, size: "xs", c: "dimmed", fs: "italic" }))] }))] }) }));
60
24
  };