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
@@ -33,7 +33,7 @@ export interface ThreadCarouselProps {
33
33
  contextType: 'field' | 'entry' | 'branch';
34
34
  /** Current user ID */
35
35
  currentUserId: string;
36
- /** Whether user can resolve threads */
36
+ /** Whether the user may resolve any thread; a thread's own author always may resolve it. */
37
37
  canResolve: boolean;
38
38
  /** Handler to add a new comment (creates new thread or adds to existing) */
39
39
  onAddComment: (text: string, threadId?: string) => Promise<void>;
@@ -1,9 +1,11 @@
1
1
  'use client';
2
2
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useMemo, useEffect, useRef } from 'react';
4
- import { ActionIcon, Alert, Button, Group, Paper, Stack, Text, Textarea } from '@mantine/core';
5
- import { IconChevronLeft, IconChevronRight, IconAlertCircle } from '@tabler/icons-react';
4
+ import { ActionIcon, Button, Group, Paper, Stack, Text, Textarea, Tooltip } from '@mantine/core';
5
+ import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
6
6
  import { InlineCommentThread } from './InlineCommentThread.js';
7
+ import { useTrackedWrites } from './use-tracked-writes.js';
8
+ import { CommentWriteAlert, toCommentWriteFailure, } from './CommentWriteAlert.js';
7
9
  export const ThreadCarousel = ({ threads, label = 'Comments', contextType: _, currentUserId, canResolve, onAddComment, onResolveThread, autoFocus, autoOpenNewThread, highlightThreadId, onGetUserMetadata, onCancelNewThread, }) => {
8
10
  const [currentIndex, setCurrentIndex] = useState(0);
9
11
  const [showNewThreadBox, setShowNewThreadBox] = useState(false);
@@ -112,39 +114,34 @@ export const ThreadCarousel = ({ threads, label = 'Comments', contextType: _, cu
112
114
  scrollToIndex(newIndex);
113
115
  }
114
116
  };
115
- const handleAddReply = async (threadId, text) => {
116
- try {
117
- setError(null);
118
- await onAddComment(text, threadId);
119
- }
120
- catch (err) {
121
- setError(err instanceof Error ? err.message : 'Failed to add reply');
122
- throw err; // Re-throw so InlineCommentThread can handle it
123
- }
124
- };
117
+ const { track } = useTrackedWrites();
118
+ const addComment = track(onAddComment);
119
+ const resolveThread = track(onResolveThread);
120
+ // A resolved thread's controls unmount with the focused button, so focus lands here.
121
+ const labelRef = useRef(null);
125
122
  const handleCreateNewThread = async () => {
126
123
  if (!newThreadText.trim())
127
124
  return;
128
125
  setIsSubmitting(true);
129
126
  setError(null);
130
127
  try {
131
- await onAddComment(newThreadText);
128
+ await addComment(newThreadText);
132
129
  setNewThreadText('');
133
130
  setShowNewThreadBox(false);
134
131
  }
135
132
  catch (err) {
136
- setError(err instanceof Error ? err.message : 'Failed to create comment');
133
+ setError(toCommentWriteFailure(err));
137
134
  }
138
135
  finally {
139
136
  setIsSubmitting(false);
140
137
  }
141
138
  };
142
139
  const unresolvedCount = sortedThreads.filter((t) => !t.resolved).length;
143
- return (_jsx(Paper, { p: "sm", bg: "gray.0", style: { marginTop: 8 }, children: _jsxs(Stack, { gap: "xs", children: [_jsxs(Group, { justify: "space-between", align: "center", children: [_jsxs(Group, { gap: "xs", children: [_jsxs(Text, { size: "sm", fw: 600, children: [label, sortedThreads.length > 0 && ` (${sortedThreads.length})`] }), unresolvedCount > 0 && (_jsxs(Text, { size: "xs", c: "orange", fw: 600, children: [unresolvedCount, " unresolved"] }))] }), _jsxs(Group, { gap: "xs", children: [sortedThreads.length > 1 && (_jsxs(_Fragment, { children: [_jsx(ActionIcon, { size: "sm", variant: "subtle", onClick: handlePrevious, disabled: currentIndex === 0, children: _jsx(IconChevronLeft, { size: 16 }) }), _jsxs(Text, { size: "xs", fw: 500, children: [currentIndex + 1, "/", sortedThreads.length] }), _jsx(ActionIcon, { size: "sm", variant: "subtle", onClick: handleNext, disabled: currentIndex === sortedThreads.length - 1, children: _jsx(IconChevronRight, { size: 16 }) }), _jsx("div", { style: {
140
+ return (_jsx(Paper, { p: "sm", bg: "var(--mantine-color-default-hover)", style: { marginTop: 8 }, children: _jsxs(Stack, { gap: "xs", children: [_jsxs(Group, { justify: "space-between", align: "center", children: [_jsxs(Group, { gap: "xs", children: [_jsxs(Text, { ref: labelRef, size: "sm", fw: 600, tabIndex: -1, children: [label, sortedThreads.length > 0 && ` (${sortedThreads.length})`] }), unresolvedCount > 0 && (_jsxs(Text, { size: "xs", c: "orange", fw: 600, children: [unresolvedCount, " unresolved"] }))] }), _jsxs(Group, { gap: "xs", children: [sortedThreads.length > 1 && (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: "Previous thread", children: _jsx(ActionIcon, { variant: "subtle", onClick: handlePrevious, disabled: currentIndex === 0, "aria-label": "Previous thread", children: _jsx(IconChevronLeft, { size: 16, "aria-hidden": true }) }) }), _jsxs(Text, { size: "xs", fw: 500, children: [currentIndex + 1, "/", sortedThreads.length] }), _jsx(Tooltip, { label: "Next thread", children: _jsx(ActionIcon, { variant: "subtle", onClick: handleNext, disabled: currentIndex === sortedThreads.length - 1, "aria-label": "Next thread", children: _jsx(IconChevronRight, { size: 16, "aria-hidden": true }) }) }), _jsx("div", { style: {
144
141
  width: 1,
145
142
  height: 16,
146
- background: 'var(--mantine-color-gray-4)',
147
- } })] })), _jsx(Button, { size: "xs", variant: "light", onClick: () => setShowNewThreadBox(!showNewThreadBox), children: "+ New" })] })] }), error && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", variant: "light", onClose: () => setError(null), withCloseButton: true, children: error })), showNewThreadBox && (_jsx(Paper, { withBorder: true, p: "sm", bg: "white", children: _jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { placeholder: "Start a new thread...", value: newThreadText, onChange: (e) => setNewThreadText(e.target.value), minRows: 2, disabled: isSubmitting, autoFocus: true, "data-testid": "new-thread-textarea" }), _jsxs(Group, { gap: "xs", children: [_jsx(Button, { size: "xs", onClick: handleCreateNewThread, loading: isSubmitting, disabled: !newThreadText.trim(), "data-testid": "create-thread-button", children: "Create Thread" }), _jsx(Button, { size: "xs", variant: "subtle", onClick: () => {
143
+ background: 'var(--mantine-color-default-border)',
144
+ } })] })), _jsx(Button, { variant: "light", onClick: () => setShowNewThreadBox(!showNewThreadBox), children: "+ New" })] })] }), error && _jsx(CommentWriteAlert, { failure: error, onClose: () => setError(null) }), showNewThreadBox && (_jsx(Paper, { withBorder: true, p: "sm", bg: "var(--mantine-color-body)", children: _jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { placeholder: "Start a new thread...", value: newThreadText, onChange: (e) => setNewThreadText(e.target.value), minRows: 2, disabled: isSubmitting, autoFocus: true, "data-testid": "new-thread-textarea" }), _jsxs(Group, { gap: "xs", children: [_jsx(Button, { onClick: handleCreateNewThread, loading: isSubmitting, disabled: !newThreadText.trim(), "data-testid": "create-thread-button", children: "Create Thread" }), _jsx(Button, { variant: "subtle", onClick: () => {
148
145
  setShowNewThreadBox(false);
149
146
  onCancelNewThread?.();
150
147
  }, children: "Cancel" })] })] }) })), sortedThreads.length > 0 && (_jsxs("div", { style: {
@@ -173,7 +170,7 @@ export const ThreadCarousel = ({ threads, label = 'Comments', contextType: _, cu
173
170
  outlineOffset: isHighlighted ? 2 : undefined,
174
171
  borderRadius: isHighlighted ? 8 : undefined,
175
172
  transition: 'outline 0.3s ease, outline-offset 0.3s ease',
176
- }, children: _jsx(InlineCommentThread, { thread: thread, onAddReply: (text) => handleAddReply(thread.id, text), onResolve: () => onResolveThread(thread.id), currentUserId: currentUserId, canResolve: canResolve, onGetUserMetadata: onGetUserMetadata }) }, thread.id));
173
+ }, children: _jsx(InlineCommentThread, { thread: thread, onAddReply: (text) => addComment(text, thread.id), onResolve: () => resolveThread(thread.id), focusAfterResolve: () => labelRef.current, currentUserId: currentUserId, canResolve: canResolve, onGetUserMetadata: onGetUserMetadata }) }, thread.id));
177
174
  }), sortedThreads.length > 1 && (_jsx("div", { style: {
178
175
  flexShrink: 0,
179
176
  width: 60, // Match peekaboo size
@@ -184,7 +181,7 @@ export const ThreadCarousel = ({ threads, label = 'Comments', contextType: _, cu
184
181
  right: 0,
185
182
  width: 60,
186
183
  height: '100%',
187
- background: 'linear-gradient(to left, var(--mantine-color-gray-0) 0%, transparent 100%)',
184
+ background: 'linear-gradient(to left, var(--mantine-color-default-hover) 0%, transparent 100%)',
188
185
  pointerEvents: 'none',
189
186
  } })), _jsx("div", { onMouseDown: handleResizeStart, style: {
190
187
  position: 'absolute',
@@ -199,7 +196,7 @@ export const ThreadCarousel = ({ threads, label = 'Comments', contextType: _, cu
199
196
  alignItems: 'center',
200
197
  justifyContent: 'center',
201
198
  }, onMouseEnter: (e) => {
202
- e.currentTarget.style.background = 'var(--mantine-color-gray-3)';
199
+ e.currentTarget.style.background = 'var(--mantine-color-default-hover)';
203
200
  }, onMouseLeave: (e) => {
204
201
  if (!isResizing) {
205
202
  e.currentTarget.style.background = 'transparent';
@@ -208,6 +205,6 @@ export const ThreadCarousel = ({ threads, label = 'Comments', contextType: _, cu
208
205
  width: 40,
209
206
  height: 3,
210
207
  borderRadius: 2,
211
- background: 'var(--mantine-color-gray-5)',
208
+ background: 'var(--mantine-color-dimmed)',
212
209
  } }) })] })), sortedThreads.length === 0 && !showNewThreadBox && (_jsx(Text, { size: "xs", c: "dimmed", ta: "center", py: "xs", children: "No comments yet. Click \"+ New\" to start a thread." }))] }) }));
213
210
  };
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import type { CommentThread } from '../../comment-store.js';
3
+ export interface ThreadReplyControlsProps {
4
+ /** An unresolved thread; a resolved one has nothing to reply to. */
5
+ thread: CommentThread;
6
+ /** Adds a reply to this thread; rejecting keeps the box open with the text. */
7
+ onAddReply: (text: string) => Promise<void>;
8
+ onResolve: () => Promise<void>;
9
+ /**
10
+ * Where focus goes once the thread is resolved and these controls are gone, unless the user put
11
+ * it somewhere else after clicking Resolve.
12
+ */
13
+ focusAfterResolve?: () => HTMLElement | null | undefined;
14
+ currentUserId: string;
15
+ /** Whether the user may resolve any thread; a thread's own author always may resolve it. */
16
+ canResolve: boolean;
17
+ /** The reply draft, when the caller keeps it, so it outlives this component; with `onReplyTextChange`. */
18
+ replyText?: string;
19
+ onReplyTextChange?: (text: string) => void;
20
+ }
21
+ /** Refocuses `box` when a submit settles: the busy button disabled itself and dropped focus. */
22
+ export declare const useRefocusAfterSubmit: (submitting: boolean, box: React.RefObject<HTMLTextAreaElement | null>) => void;
23
+ /**
24
+ * A thread's Reply and Resolve controls. Reply opens the reply box in place, so a list of open
25
+ * threads shows buttons rather than one box each; Cancel closes it and keeps the draft. Reply and
26
+ * Resolve each show only their own progress, and the one error here belongs to this thread.
27
+ */
28
+ export declare const ThreadReplyControls: React.FC<ThreadReplyControlsProps>;
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Button, Group, Stack, Textarea } from '@mantine/core';
5
+ import { moveFocusIfUnmoved } from './move-focus-if-unmoved.js';
6
+ import { CommentWriteAlert, toCommentWriteFailure, } from './CommentWriteAlert.js';
7
+ /** Refocuses `box` when a submit settles: the busy button disabled itself and dropped focus. */
8
+ export const useRefocusAfterSubmit = (submitting, box) => {
9
+ const wasSubmitting = useRef(false);
10
+ useEffect(() => {
11
+ if (wasSubmitting.current && !submitting)
12
+ box.current?.focus();
13
+ wasSubmitting.current = submitting;
14
+ }, [submitting, box]);
15
+ };
16
+ /**
17
+ * A thread's Reply and Resolve controls. Reply opens the reply box in place, so a list of open
18
+ * threads shows buttons rather than one box each; Cancel closes it and keeps the draft. Reply and
19
+ * Resolve each show only their own progress, and the one error here belongs to this thread.
20
+ */
21
+ export const ThreadReplyControls = ({ thread, onAddReply, onResolve, focusAfterResolve, currentUserId, canResolve, replyText: controlledReply, onReplyTextChange, }) => {
22
+ const [ownReply, setOwnReply] = useState('');
23
+ const replyText = controlledReply ?? ownReply;
24
+ const setReplyText = onReplyTextChange ?? setOwnReply;
25
+ const [expanded, setExpanded] = useState(replyText.length > 0);
26
+ const [replying, setReplying] = useState(false);
27
+ const [resolving, setResolving] = useState(false);
28
+ const [error, setError] = useState(null);
29
+ const boxRef = useRef(null);
30
+ const replyButtonRef = useRef(null);
31
+ // Where focus goes once the next expand or collapse has rendered.
32
+ const focusNext = useRef(null);
33
+ useRefocusAfterSubmit(replying, boxRef);
34
+ useEffect(() => {
35
+ if (focusNext.current === (expanded ? 'box' : 'button')) {
36
+ ;
37
+ (expanded ? boxRef : replyButtonRef).current?.focus();
38
+ focusNext.current = null;
39
+ }
40
+ }, [expanded]);
41
+ const expand = () => {
42
+ focusNext.current = 'box';
43
+ setExpanded(true);
44
+ };
45
+ const collapse = () => {
46
+ focusNext.current = 'button';
47
+ setExpanded(false);
48
+ };
49
+ const handleReply = async () => {
50
+ if (!replyText.trim())
51
+ return;
52
+ setReplying(true);
53
+ setError(null);
54
+ try {
55
+ await onAddReply(replyText);
56
+ setReplyText('');
57
+ collapse();
58
+ }
59
+ catch (err) {
60
+ setError(toCommentWriteFailure(err));
61
+ }
62
+ finally {
63
+ setReplying(false);
64
+ }
65
+ };
66
+ // Resolving closes the thread and unmounts this box, so a draft in it is posted first, never lost.
67
+ const hasDraft = replyText.trim().length > 0;
68
+ const handleResolve = async () => {
69
+ // Taken at the click: a draft posts first, and focus moved during that post is the user's.
70
+ const origin = document.activeElement;
71
+ setResolving(true);
72
+ setError(null);
73
+ try {
74
+ if (hasDraft) {
75
+ await onAddReply(replyText);
76
+ setReplyText('');
77
+ }
78
+ await onResolve();
79
+ if (focusAfterResolve)
80
+ moveFocusIfUnmoved(origin, focusAfterResolve());
81
+ }
82
+ catch (err) {
83
+ setError(toCommentWriteFailure(err));
84
+ }
85
+ finally {
86
+ setResolving(false);
87
+ }
88
+ };
89
+ const mayResolve = canResolve || thread.authorId === currentUserId;
90
+ const resolveButton = mayResolve && (_jsx(Button, { variant: "light", color: "green", onClick: handleResolve, loading: resolving, disabled: replying, "data-testid": "resolve-thread-button", children: hasDraft ? 'Reply and resolve' : 'Resolve' }));
91
+ return (_jsxs(Stack, { gap: "xs", children: [error && _jsx(CommentWriteAlert, { failure: error, onClose: () => setError(null) }), expanded ? (_jsxs(_Fragment, { children: [_jsx(Textarea, { ref: boxRef, placeholder: "Write a reply\u2026", "aria-label": "Reply to this thread", value: replyText, onChange: (e) => setReplyText(e.target.value), minRows: 2, disabled: replying || resolving }), _jsxs(Group, { gap: "xs", children: [_jsx(Button, { onClick: handleReply, loading: replying, disabled: !replyText.trim() || resolving, "data-testid": "submit-reply-button", children: "Reply" }), _jsx(Button, { variant: "subtle", color: "gray", onClick: collapse, disabled: replying, children: "Cancel" }), resolveButton] })] })) : (_jsxs(Group, { gap: "xs", children: [_jsx(Button, { ref: replyButtonRef, variant: "light", onClick: expand, disabled: resolving, "data-testid": "reply-thread-button", children: "Reply\u2026" }), resolveButton] }))] }));
92
+ };
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export interface FieldCommentContextValue {
3
+ /** The canonical canopy path of the field whose `FieldWrapper` provides this. */
4
+ canopyPath: string;
5
+ /** That field's comment control, for its label row. */
6
+ control: React.ReactNode;
7
+ }
8
+ export declare const FieldCommentContext: React.Context<FieldCommentContextValue | null>;
9
+ /**
10
+ * The comment control for the field at `canopyPath`, for its `FieldLabel`'s `commentControl`
11
+ * slot; null outside a `FieldWrapper` for that same path. Matching the path keeps a parent's
12
+ * control out of a nested field's label, and out of list item titles, so each field shows one.
13
+ */
14
+ export declare const useFieldCommentControl: (canopyPath: string | undefined) => React.ReactNode;
@@ -0,0 +1,14 @@
1
+ 'use client';
2
+ import { createContext, useContext } from 'react';
3
+ export const FieldCommentContext = createContext(null);
4
+ /**
5
+ * The comment control for the field at `canopyPath`, for its `FieldLabel`'s `commentControl`
6
+ * slot; null outside a `FieldWrapper` for that same path. Matching the path keeps a parent's
7
+ * control out of a nested field's label, and out of list item titles, so each field shows one.
8
+ */
9
+ export const useFieldCommentControl = (canopyPath) => {
10
+ const context = useContext(FieldCommentContext);
11
+ if (!context || canopyPath === undefined || context.canopyPath !== canopyPath)
12
+ return null;
13
+ return context.control;
14
+ };
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import type { CommentThread } from '../../comment-store.js';
3
+ /** A field's comment popover, as the form-level keyboard route reaches it. */
4
+ interface FieldCommentOpener {
5
+ /** The field's breadcrumb, as its control names it. */
6
+ subject: string;
7
+ /** Why a new thread can't be posted on the field yet; the menu shows it beside the field. */
8
+ blockedReason?: string;
9
+ /** Opens the popover; `returnFocusTo` stands in for its opener unless a click closes it. */
10
+ open: (returnFocusTo?: () => HTMLElement | null) => void;
11
+ /** The field's wrapper, which orders the menu as the form reads. */
12
+ element: () => HTMLElement | null;
13
+ }
14
+ export interface FormComments {
15
+ /** Registers a field's popover under its canopy path; returns the unregister. */
16
+ register: (canopyPath: string, opener: FieldCommentOpener) => () => void;
17
+ get: (canopyPath: string) => FieldCommentOpener | undefined;
18
+ /** Every registered field, in the form's order. */
19
+ fields: () => Array<FieldCommentOpener & {
20
+ canopyPath: string;
21
+ }>;
22
+ /** Records the field that last took focus, which "Comment on …" names; `undefined` forgets it. */
23
+ noteFocus: (canopyPath: string | undefined) => void;
24
+ lastFocused: () => string | undefined;
25
+ /** Bumped whenever the fields or the field last focused change, for `useSyncExternalStore`. */
26
+ version: () => number;
27
+ subscribe: (listener: () => void) => () => void;
28
+ }
29
+ /** One form's field comment popovers, for its "Field comments" menu and shortcut. */
30
+ export declare const createFormComments: () => FormComments;
31
+ export declare const FormCommentsContext: React.Context<FormComments | null>;
32
+ export declare const useFormComments: () => FormComments | null;
33
+ /** The shortcut's label for tooltips, in the platform's modifier. */
34
+ export declare const commentsShortcutLabel: () => string;
35
+ /** The shortcut for `aria-keyshortcuts`. */
36
+ export declare const commentsAriaKeyShortcuts: () => string;
37
+ export interface FormCommentsMenuProps {
38
+ /** Every thread on the branch; the menu counts this entry's field threads. */
39
+ comments: CommentThread[];
40
+ entryPath: string;
41
+ }
42
+ /**
43
+ * The form's one keyboard route to field comments: a menu listing the fields with threads, the
44
+ * field last focused, and every field, each opening that field's popover. Mod+Alt+M opens
45
+ * the focused field's popover, or this menu when focus is elsewhere in the editor.
46
+ */
47
+ export declare const FormCommentsMenu: React.FC<FormCommentsMenuProps>;
48
+ export {};
@@ -0,0 +1,178 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
4
+ import { Badge, Button, Menu, Text, Tooltip } from '@mantine/core';
5
+ import { useHotkeys } from '@mantine/hooks';
6
+ import { IconChevronDown, IconMessages } from '@tabler/icons-react';
7
+ import { modKeyLabel } from '../platform.js';
8
+ const byFormOrder = (a, b) => {
9
+ if (!a || !b || a === b)
10
+ return 0;
11
+ return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
12
+ };
13
+ /** One form's field comment popovers, for its "Field comments" menu and shortcut. */
14
+ export const createFormComments = () => {
15
+ const openers = new Map();
16
+ const listeners = new Set();
17
+ let lastFocused;
18
+ let version = 0;
19
+ const notify = () => {
20
+ version += 1;
21
+ listeners.forEach((listener) => listener());
22
+ };
23
+ return {
24
+ register: (canopyPath, opener) => {
25
+ openers.set(canopyPath, opener);
26
+ notify();
27
+ return () => {
28
+ if (openers.get(canopyPath) !== opener)
29
+ return;
30
+ openers.delete(canopyPath);
31
+ notify();
32
+ };
33
+ },
34
+ get: (canopyPath) => openers.get(canopyPath),
35
+ fields: () => [...openers]
36
+ .map(([canopyPath, opener]) => ({ ...opener, canopyPath }))
37
+ .sort((a, b) => byFormOrder(a.element(), b.element())),
38
+ noteFocus: (canopyPath) => {
39
+ if (lastFocused === canopyPath)
40
+ return;
41
+ lastFocused = canopyPath;
42
+ notify();
43
+ },
44
+ lastFocused: () => lastFocused,
45
+ version: () => version,
46
+ subscribe: (listener) => {
47
+ listeners.add(listener);
48
+ return () => listeners.delete(listener);
49
+ },
50
+ };
51
+ };
52
+ export const FormCommentsContext = createContext(null);
53
+ export const useFormComments = () => useContext(FormCommentsContext);
54
+ /** The shortcut's label for tooltips, in the platform's modifier. */
55
+ export const commentsShortcutLabel = () => (modKeyLabel() === '⌘' ? '⌘⌥M' : 'Ctrl+Alt+M');
56
+ /** The shortcut for `aria-keyshortcuts`. */
57
+ export const commentsAriaKeyShortcuts = () => modKeyLabel() === '⌘' ? 'Meta+Alt+M' : 'Control+Alt+M';
58
+ const FIELD_SCOPE_SELECTOR = '[data-field-comments]';
59
+ /** One letter of a script other than Latin; AltGr symbols such as "µ" are Common, not letters of one. */
60
+ const NON_LATIN_LETTER = /^(?![\p{Script=Latin}\p{Script=Common}])\p{L}$/u;
61
+ /** Focus is in another overlay, or a modal not holding this form is open, so the shortcut leaves it be. */
62
+ const inOtherOverlay = (active, form) => {
63
+ const dialog = active?.closest('[role="dialog"]');
64
+ if (dialog && !dialog.contains(form))
65
+ return true;
66
+ return Array.from(document.querySelectorAll('[role="dialog"][aria-modal="true"]')).some((modal) => !modal.contains(form));
67
+ };
68
+ const openThreadsText = (count) => count === 1 ? '1 open thread' : `${count} open threads`;
69
+ /**
70
+ * The form's one keyboard route to field comments: a menu listing the fields with threads, the
71
+ * field last focused, and every field, each opening that field's popover. Mod+Alt+M opens
72
+ * the focused field's popover, or this menu when focus is elsewhere in the editor.
73
+ */
74
+ export const FormCommentsMenu = ({ comments, entryPath }) => {
75
+ const formComments = useFormComments();
76
+ const [opened, setOpened] = useState(false);
77
+ const triggerRef = useRef(null);
78
+ // Re-renders on every registration, so counts and lists follow the fields mounted now.
79
+ useSyncExternalStore(formComments?.subscribe ?? noopSubscribe, formComments?.version ?? noVersion, noVersion);
80
+ const onRemovedItems = useMemo(() => comments.filter((t) => t.type === 'field' && t.entryPath === entryPath && t.onRemovedItem && !t.resolved).length, [comments, entryPath]);
81
+ const counts = useMemo(() => {
82
+ const byPath = new Map();
83
+ for (const thread of comments) {
84
+ if (thread.type !== 'field' ||
85
+ thread.entryPath !== entryPath ||
86
+ !thread.canopyPath ||
87
+ thread.onRemovedItem) {
88
+ continue;
89
+ }
90
+ const count = byPath.get(thread.canopyPath) ?? { open: 0, total: 0 };
91
+ count.total += 1;
92
+ if (!thread.resolved)
93
+ count.open += 1;
94
+ byPath.set(thread.canopyPath, count);
95
+ }
96
+ return byPath;
97
+ }, [comments, entryPath]);
98
+ const onShortcut = (event) => {
99
+ const form = triggerRef.current?.closest('[data-form-renderer]');
100
+ if (!formComments || !form)
101
+ return;
102
+ const active = document.activeElement;
103
+ // An inert form is covered by the content panel's overlay.
104
+ if (inOtherOverlay(active, form) || form.closest('[inert]'))
105
+ return;
106
+ event.preventDefault();
107
+ const field = active && form.contains(active) ? active.closest(FIELD_SCOPE_SELECTOR) : null;
108
+ const path = field?.getAttribute('data-canopy-field');
109
+ const opener = path ? formComments.get(path) : undefined;
110
+ if (opener)
111
+ opener.open();
112
+ else {
113
+ triggerRef.current?.scrollIntoView?.({ block: 'nearest' });
114
+ setOpened(true);
115
+ }
116
+ };
117
+ useHotkeys([
118
+ [
119
+ 'mod+alt+M',
120
+ (event) => {
121
+ // ⌥ turns M into another character on macOS, so there the key matches by position.
122
+ if (modKeyLabel() === '⌘' && event.metaKey)
123
+ onShortcut(event);
124
+ },
125
+ { preventDefault: false, usePhysicalKeys: true },
126
+ ],
127
+ [
128
+ 'mod+alt+M',
129
+ (event) => {
130
+ // Elsewhere by character: AltGr reads as Ctrl+Alt, and AltGr+M types a character
131
+ // on some layouts, which this leaves alone.
132
+ if (modKeyLabel() !== '⌘')
133
+ onShortcut(event);
134
+ },
135
+ { preventDefault: false },
136
+ ],
137
+ [
138
+ 'mod+alt+M',
139
+ (event) => {
140
+ // A layout whose letters aren't Latin, such as Cyrillic or Greek, matches by position.
141
+ if (modKeyLabel() !== '⌘' &&
142
+ NON_LATIN_LETTER.test(event.key) &&
143
+ !event.getModifierState?.('AltGraph'))
144
+ onShortcut(event);
145
+ },
146
+ { preventDefault: false, usePhysicalKeys: true },
147
+ ],
148
+ ],
149
+ // `[]` ignores no tags and `true` fires from contenteditable, so it works while typing.
150
+ [], true);
151
+ if (!formComments)
152
+ return null;
153
+ // Counts only fields mounted now, which the menu can open.
154
+ const openCount = [...counts].reduce((sum, [path, { open }]) => (formComments.get(path) ? sum + open : sum), 0);
155
+ // The menu item is gone once the popover opens, so its trigger stands in as the opener.
156
+ const openField = (opener) => opener.open(() => triggerRef.current);
157
+ return (_jsxs(Menu, { opened: opened, onChange: setOpened, position: "bottom-end", withinPortal: true, shadow: "sm", children: [_jsx(Tooltip, { label: `Field comments (${commentsShortcutLabel()})`, withinPortal: true, disabled: opened, children: _jsx(Menu.Target, { children: _jsx(Button, { ref: triggerRef, variant: "subtle", color: "gray",
158
+ // The theme's text colour, not a palette shade: a fixed shade is unreadable on one scheme.
159
+ c: "var(--mantine-color-text)", size: "compact-sm", leftSection: _jsx(IconMessages, { size: 16, "aria-hidden": true }), rightSection: _jsxs(_Fragment, { children: [openCount > 0 && (_jsx(Badge, { size: "sm", variant: "light", color: "orange", c: "var(--mantine-color-text)", "aria-hidden": true, mr: 4, children: openCount })), _jsx(IconChevronDown, { size: 14, "aria-hidden": true })] }), "aria-label": openCount > 0 ? `Field comments (${openThreadsText(openCount)})` : 'Field comments', "aria-keyshortcuts": commentsAriaKeyShortcuts(), "data-testid": "form-comments-menu-button", children: "Field comments" }) }) }), _jsx(Menu.Dropdown, { "data-testid": "form-comments-menu", maw: 420, mah: "min(480px, 70vh)", style: { overflowY: 'auto' }, children: _jsx(MenuItems, { formComments: formComments, counts: counts, onRemovedItems: onRemovedItems, openField: openField }) })] }));
160
+ };
161
+ /** The menu's items, built only while its dropdown is mounted: every field, in form order. */
162
+ const MenuItems = ({ formComments, counts, onRemovedItems, openField }) => {
163
+ const allFields = formComments.fields();
164
+ const withThreads = allFields.flatMap((field) => {
165
+ const count = counts.get(field.canopyPath);
166
+ return count ? [{ field, ...count }] : [];
167
+ });
168
+ const lastFocusedPath = formComments.lastFocused();
169
+ const lastFocused = lastFocusedPath ? formComments.get(lastFocusedPath) : undefined;
170
+ const otherFields = allFields.filter((field) => field.canopyPath !== lastFocusedPath);
171
+ return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: "Fields with comments" }), withThreads.length === 0 ? (_jsx(Text, { size: "xs", c: "dimmed", px: "sm", pb: "xs", children: onRemovedItems > 0
172
+ ? `${onRemovedItems === 1 ? '1 comment thread is' : `${onRemovedItems} comment threads are`} on a removed item, under "Comment on this entry".`
173
+ : 'No field has comments yet.' })) : (withThreads.map(({ field, open }) => (_jsxs(Menu.Item, { onClick: () => openField(field), "data-testid": `form-comments-field-${field.canopyPath}`, children: [_jsx(Text, { size: "sm", lineClamp: 2, children: field.subject }), _jsx(Text, { size: "xs", c: "dimmed", children: open > 0 ? openThreadsText(open) : 'Resolved' })] }, field.canopyPath)))), _jsx(Menu.Divider, {}), lastFocused && (_jsxs(Menu.Item, { onClick: () => openField(lastFocused), "data-testid": "form-comments-comment-on-focused", children: [_jsxs(Text, { size: "sm", lineClamp: 2, children: ["Comment on ", lastFocused.subject] }), _jsx(BlockedReason, { opener: lastFocused })] })), _jsx(Menu.Label, { children: lastFocused ? 'Comment on another field' : 'Comment on a field' }), otherFields.map((field) => (_jsxs(Menu.Item, { onClick: () => openField(field), "data-testid": `form-comments-any-field-${field.canopyPath}`, children: [_jsx(Text, { size: "sm", lineClamp: 2, children: field.subject }), _jsx(BlockedReason, { opener: field })] }, field.canopyPath)))] }));
174
+ };
175
+ /** A field's reason it can't take a new thread yet; its popover still opens, to draft one. */
176
+ const BlockedReason = ({ opener }) => opener.blockedReason ? (_jsx(Text, { size: "xs", c: "dimmed", children: opener.blockedReason })) : null;
177
+ const noopSubscribe = () => () => { };
178
+ const noVersion = () => 0;
@@ -0,0 +1,6 @@
1
+ /** Refuses a new thread on a field inside an item the draft added, which has no saved place yet. */
2
+ export declare const NEW_ITEM_COMMENT_MESSAGE = "Save the entry before commenting on this new item.";
3
+ /** Refuses a new thread inside a list before the entry's saved value has loaded. */
4
+ export declare const ENTRY_NOT_LOADED_COMMENT_MESSAGE = "This entry hasn't finished loading, so a comment here can't be placed yet. Reload it and try again.";
5
+ /** Names a field thread whose list item or block was removed; the field's labels follow it. */
6
+ export declare const ON_REMOVED_ITEM = "On a removed item";
@@ -0,0 +1,6 @@
1
+ /** Refuses a new thread on a field inside an item the draft added, which has no saved place yet. */
2
+ export const NEW_ITEM_COMMENT_MESSAGE = 'Save the entry before commenting on this new item.';
3
+ /** Refuses a new thread inside a list before the entry's saved value has loaded. */
4
+ export const ENTRY_NOT_LOADED_COMMENT_MESSAGE = "This entry hasn't finished loading, so a comment here can't be placed yet. Reload it and try again.";
5
+ /** Names a field thread whose list item or block was removed; the field's labels follow it. */
6
+ export const ON_REMOVED_ITEM = 'On a removed item';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Focuses `target` unless the user has moved focus since `origin` had it. Focus that stayed on
3
+ * `origin`, or fell to the body with an unmounted element, is the user's to get back; focus they
4
+ * put somewhere else while a write was in flight stays where they put it.
5
+ */
6
+ export declare const moveFocusIfUnmoved: (origin: Element | null, target: HTMLElement | null | undefined) => void;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Focuses `target` unless the user has moved focus since `origin` had it. Focus that stayed on
3
+ * `origin`, or fell to the body with an unmounted element, is the user's to get back; focus they
4
+ * put somewhere else while a write was in flight stays where they put it.
5
+ */
6
+ export const moveFocusIfUnmoved = (origin, target) => {
7
+ const active = document.activeElement;
8
+ if (!active || active === origin || active === document.body || !active.isConnected) {
9
+ target?.focus();
10
+ }
11
+ };
@@ -0,0 +1,18 @@
1
+ /** The two places a field's comment popover may open, neither over the field's input. */
2
+ export interface FieldAnchors {
3
+ /** At the top of the field, above its label row. */
4
+ above: HTMLElement | null;
5
+ /** At the bottom of the field, below its input. */
6
+ below: HTMLElement | null;
7
+ }
8
+ export type PopoverSide = 'above' | 'below';
9
+ /**
10
+ * Picks the side a field's popover opens on. A short field prefers below its input; a group or a
11
+ * field taller than 40% of its scroll area prefers above its label row, since below is far from
12
+ * the control. Either takes the other side when only that one has room. When neither does, or
13
+ * when `scroll` asks, the preferred side scrolls to the middle first. Room is measured in the
14
+ * field's scroll container.
15
+ */
16
+ export declare const choosePopoverSide: ({ above, below }: FieldAnchors, { scroll }?: {
17
+ scroll?: boolean;
18
+ }) => PopoverSide;
@@ -0,0 +1,58 @@
1
+ /** Room the popover needs on its side, for its heading and a composer. */
2
+ const POPOVER_ROOM = 280;
3
+ const scrollBounds = (el) => {
4
+ for (let p = el.parentElement; p; p = p.parentElement) {
5
+ const { overflowY } = getComputedStyle(p);
6
+ if ((overflowY === 'auto' || overflowY === 'scroll') && p.scrollHeight > p.clientHeight) {
7
+ const { top, bottom } = p.getBoundingClientRect();
8
+ return { top, bottom };
9
+ }
10
+ }
11
+ return { top: 0, bottom: window.innerHeight };
12
+ };
13
+ /**
14
+ * Picks the side a field's popover opens on. A short field prefers below its input; a group or a
15
+ * field taller than 40% of its scroll area prefers above its label row, since below is far from
16
+ * the control. Either takes the other side when only that one has room. When neither does, or
17
+ * when `scroll` asks, the preferred side scrolls to the middle first. Room is measured in the
18
+ * field's scroll container.
19
+ */
20
+ export const choosePopoverSide = ({ above, below }, { scroll = false } = {}) => {
21
+ if (!above || !below)
22
+ return 'below';
23
+ const measure = () => {
24
+ const bounds = scrollBounds(above);
25
+ const top = above.getBoundingClientRect().top;
26
+ const bottom = below.getBoundingClientRect().bottom;
27
+ const topInView = top >= bounds.top && top < bounds.bottom;
28
+ return {
29
+ tall: bottom - top > (bounds.bottom - bounds.top) * 0.4,
30
+ above: topInView && top - bounds.top >= POPOVER_ROOM,
31
+ below: topInView && bottom <= bounds.bottom && bounds.bottom - bottom >= POPOVER_ROOM,
32
+ roomAbove: top - bounds.top,
33
+ roomBelow: bounds.bottom - bottom,
34
+ };
35
+ };
36
+ const group = below.parentElement?.querySelector('[data-field-comments]') != null;
37
+ let room = measure();
38
+ const preferred = group || room.tall ? 'above' : 'below';
39
+ const other = preferred === 'above' ? 'below' : 'above';
40
+ if (!scroll) {
41
+ if (room[preferred])
42
+ return preferred;
43
+ if (room[other])
44
+ return other;
45
+ }
46
+ // Instant, so the second measure reads the new geometry even under `scroll-behavior: smooth`.
47
+ ;
48
+ (preferred === 'above' ? above : below).scrollIntoView?.({
49
+ block: 'center',
50
+ behavior: 'instant',
51
+ });
52
+ room = measure();
53
+ if (room[preferred])
54
+ return preferred;
55
+ if (room[other])
56
+ return other;
57
+ return room.roomBelow >= room.roomAbove ? 'below' : 'above';
58
+ };
@@ -0,0 +1,16 @@
1
+ /** Shown while a surface refuses to close because a comment write is still in flight. */
2
+ export declare const CLOSE_WAITS_FOR_WRITE = "Still saving\u2026 You can close this once it finishes.";
3
+ /**
4
+ * Wraps a surface's comment writes: `inFlight` counts the unsettled ones, and a write that fails
5
+ * after the surface unmounted (the Branches drawer closed under the branch discussion, another
6
+ * entry or branch opened) still reports, as a toast, since nothing is left on screen to show the
7
+ * error. A wrapped write still rejects for its caller. `holdOpen()` is true while a write is in
8
+ * flight, and then sets `closeRefused` until the writes settle, which the API client's request
9
+ * deadline bounds, so a surface that refuses to close can say why.
10
+ */
11
+ export declare function useTrackedWrites(): {
12
+ inFlight: number;
13
+ track: <A extends unknown[]>(write: (...args: A) => Promise<void>) => (...args: A) => Promise<void>;
14
+ holdOpen: () => boolean;
15
+ closeRefused: boolean;
16
+ };