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,82 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useRef, useState } from 'react';
4
+ import { ActionIcon, Badge, Box, Button, Group, Popover, ScrollArea, Text, Tooltip, UnstyledButton, VisuallyHidden, } from '@mantine/core';
5
+ import { useIsomorphicEffect } from '@mantine/hooks';
6
+ import { IconChevronsDown, IconChevronsUp, IconListTree } from '@tabler/icons-react';
7
+ /** The padding of the scroller the form sits in, which `shell/PaneLayout.tsx` sets; 0 where unset. */
8
+ const PANE_PADDING = 'var(--canopy-form-pane-padding, 0px)';
9
+ /** Below this header width "Outline" shows its icon alone, so the controls never clip. */
10
+ const COMPACT_WIDTH = 280;
11
+ /** Space kept between the header and a field scrolled up to it. */
12
+ const SCROLL_GAP = 8;
13
+ /** The nearest ancestor that scrolls vertically, which the header sticks within. */
14
+ const scrollerOf = (element) => {
15
+ for (let node = element.parentElement; node; node = node.parentElement) {
16
+ const { overflowY } = getComputedStyle(node);
17
+ if (overflowY === 'auto' || overflowY === 'scroll')
18
+ return node;
19
+ }
20
+ return undefined;
21
+ };
22
+ /**
23
+ * One level of the outline as a list. A row's name leads with its field's label ("Page sections ›
24
+ * Hero, Welcome"), so rows of two lists are told apart by ear.
25
+ */
26
+ const OutlineList = ({ nodes, field, nested = false, onChoose }) => (_jsx("ul", { style: { listStyle: 'none', margin: 0, paddingLeft: nested ? 16 : 0 }, children: nodes.map((node, idx) => {
27
+ const name = node.secondary ? `${node.label}, ${node.secondary}` : node.label;
28
+ return (_jsxs("li", { children: [node.path === undefined ? (_jsx(Text, { size: "xs", fw: 600, c: "dimmed", px: "xs", pt: 6, pb: 2, children: node.label })) : (_jsx(UnstyledButton, { onClick: () => onChoose(node.path), "aria-label": field ? `${field} › ${name}` : undefined, display: "block", w: "100%", py: 4, px: "xs", style: { borderRadius: 'var(--mantine-radius-sm)' }, className: "canopy-outline-item", "data-testid": "form-outline-item", children: _jsxs(Text, { size: "sm", truncate: "end", fw: field ? undefined : 500, children: [node.label, node.secondary && (_jsxs(Text, { span: true, c: "dimmed", size: "sm", children: [' ', node.secondary] }))] }) })), node.children && node.children.length > 0 && (_jsx(OutlineList, { nodes: node.children, field: node.path === undefined ? undefined : node.label, nested: true, onChoose: onChoose }))] }, node.path ?? `group-${idx}`));
29
+ }) }));
30
+ /**
31
+ * The form's sticky header: which entry this is, an outline of its fields to jump to, and
32
+ * Expand all / Collapse all for every list and block field in it.
33
+ */
34
+ export const FormHeader = ({ title, typeLabel, outline, onSelect, onExpandAll, }) => {
35
+ const [outlineOpen, setOutlineOpen] = useState(false);
36
+ const [compact, setCompact] = useState(false);
37
+ const headerRef = useRef(null);
38
+ // Its scroller pads its scroll edge by the header's height and a gap, so a field scrolled to the
39
+ // top, as `scrollIntoView` does, lands below the header rather than under it.
40
+ useIsomorphicEffect(() => {
41
+ const header = headerRef.current;
42
+ if (!header)
43
+ return;
44
+ const scroller = scrollerOf(header);
45
+ const previous = scroller?.style.scrollPaddingTop ?? '';
46
+ const measure = () => {
47
+ if (scroller)
48
+ scroller.style.scrollPaddingTop = `${header.offsetHeight + SCROLL_GAP}px`;
49
+ setCompact(header.clientWidth > 0 && header.clientWidth < COMPACT_WIDTH);
50
+ };
51
+ measure();
52
+ const observer = typeof ResizeObserver === 'function' ? new ResizeObserver(measure) : undefined;
53
+ observer?.observe(header);
54
+ return () => {
55
+ observer?.disconnect();
56
+ if (scroller)
57
+ scroller.style.scrollPaddingTop = previous;
58
+ };
59
+ }, []);
60
+ return (_jsxs(Box, { ref: headerRef, "data-testid": "form-header", style: {
61
+ // Bleeds over the form pane's padding, so it spans the pane and sticks to its very top.
62
+ position: 'sticky',
63
+ top: `calc(-1 * ${PANE_PADDING})`,
64
+ zIndex: 3,
65
+ margin: `calc(-1 * ${PANE_PADDING}) calc(-1 * ${PANE_PADDING}) 0`,
66
+ padding: `8px ${PANE_PADDING}`,
67
+ backgroundColor: 'var(--canopy-surface-muted)',
68
+ borderBottom: '1px solid var(--mantine-color-default-border)',
69
+ }, children: [_jsxs(Group, { gap: "xs", wrap: "nowrap", children: [_jsx(Text, { fw: 600, size: "sm", truncate: "end", style: { minWidth: 0 }, children: title }), typeLabel && (_jsx(Badge, { variant: "light", color: "gray", size: "sm", tt: "none",
70
+ // Gives way before the title in a narrow pane.
71
+ style: { flexShrink: 2, minWidth: 0 }, children: typeLabel })), _jsxs(Group, { gap: 4, wrap: "nowrap", ml: "auto", style: { flexShrink: 0 }, children: [_jsxs(Popover, { opened: outlineOpen, onChange: setOutlineOpen, position: "bottom-end", width: 300, shadow: "md", withinPortal: true,
72
+ // Focus enters the list; Escape hands it back to "Outline" (`closeOnEscape`). A chosen
73
+ // field takes it instead, so closing returns it nowhere.
74
+ trapFocus: true, returnFocus: false, children: [_jsx(Tooltip, { label: "Outline", disabled: !compact, opened: compact ? undefined : false, children: _jsx(Popover.Target, { children: _jsx(Button, { variant: "subtle", color: "gray", c: "dimmed", size: "compact-sm", px: compact ? 6 : undefined, leftSection: _jsx(IconListTree, { size: 14, "aria-hidden": true }), styles: compact ? { section: { marginInlineEnd: 0 } } : undefined, onClick: () => setOutlineOpen((open) => !open), "data-testid": "form-outline-button", children: compact ? _jsx(VisuallyHidden, { children: "Outline" }) : 'Outline' }) }) }), _jsx(Popover.Dropdown, { p: 4, children: _jsx(ScrollArea.Autosize, { mah: 400, type: "auto", children: _jsx("nav", { "aria-label": "Outline", "data-testid": "form-outline", children: _jsx(OutlineList, { nodes: outline, onChoose: (path) => {
75
+ setOutlineOpen(false);
76
+ onSelect(path);
77
+ } }) }) }) })] }), onExpandAll && (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: "Expand all lists", children: _jsx(ActionIcon, { variant: "subtle", color: "gray", "aria-label": "Expand all lists", onClick: () => onExpandAll(true), "data-testid": "form-expand-all", children: _jsx(IconChevronsDown, { size: 16, "aria-hidden": true }) }) }), _jsx(Tooltip, { label: "Collapse all lists", children: _jsx(ActionIcon, { variant: "subtle", color: "gray", "aria-label": "Collapse all lists", onClick: () => onExpandAll(false), "data-testid": "form-collapse-all", children: _jsx(IconChevronsUp, { size: 16, "aria-hidden": true }) }) })] }))] })] }), _jsx("style", { children: `
78
+ .canopy-outline-item:hover, .canopy-outline-item:focus-visible {
79
+ background-color: var(--canopy-surface-muted);
80
+ }
81
+ ` })] }));
82
+ };
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
- import type { EntrySchema, FieldConfig } from '../config/index.js';
2
+ import type { ContentFormat, EntrySchema, FieldConfig } from '../config/index.js';
3
3
  import type { CommentThread } from '../comment-store.js';
4
+ import type { UserSearchResult } from '../auth/types.js';
5
+ import type { FieldRemoval } from './undo-removal.js';
4
6
  export type FormValue = Record<string, unknown>;
5
7
  export interface CustomFieldRenderProps {
6
8
  field: FieldConfig;
@@ -8,6 +10,8 @@ export interface CustomFieldRenderProps {
8
10
  onChange: (v: unknown) => void;
9
11
  path: Array<string | number>;
10
12
  id: string;
13
+ /** The branch is locked (protected, or in review): show the value; `onChange` is ignored. */
14
+ readOnly: boolean;
11
15
  }
12
16
  export type CustomFieldRenderers = Record<string, (props: CustomFieldRenderProps) => React.ReactNode>;
13
17
  export interface FormRendererProps {
@@ -24,8 +28,6 @@ export interface FormRendererProps {
24
28
  highlightThreadId?: string;
25
29
  onAddComment?: (text: string, type: 'field' | 'entry' | 'branch', entryPath?: string, canopyPath?: string, threadId?: string) => Promise<void>;
26
30
  onResolveThread?: (threadId: string) => Promise<void>;
27
- onResolvedValueChange?: (resolved: FormValue) => void;
28
- onLoadingStateChange?: (loadingState: FormValue) => void;
29
31
  /** True when this entry's content conflicts with a recent change on the base branch */
30
32
  conflictNotice?: boolean;
31
33
  /**
@@ -35,5 +37,27 @@ export interface FormRendererProps {
35
37
  * in useDraftManager and by server 422 rejections.
36
38
  */
37
39
  fieldErrors?: Record<string, string>;
40
+ /** The entry's format, which decides how its body field is checked. */
41
+ format?: ContentFormat;
42
+ /** Shows the value without accepting edits. Comments stay open. */
43
+ readOnly?: boolean;
44
+ /** Told of each list item, block or image Remove takes out, or object Clear resets, for Undo. */
45
+ onRemoved?: (removal: FieldRemoval) => void;
46
+ /** Resolves comment authors to names in field and entry threads; without it they show as ids. */
47
+ onGetUserMetadata?: (userId: string) => Promise<UserSearchResult | null>;
48
+ /**
49
+ * Shows a sticky header naming the entry, with an outline of its fields and, when it has list
50
+ * or block fields, Expand all and Collapse all.
51
+ */
52
+ header?: {
53
+ title: string;
54
+ typeLabel?: string;
55
+ };
56
+ /** An entry-wide notice, shown first under the header. */
57
+ notice?: React.ReactNode;
58
+ /** Why a field can't take a new comment thread yet, such as one inside an unsaved item. */
59
+ commentBlockedReason?: (canopyPath: string) => string | undefined;
60
+ /** Names a removed item's thread's field; else the schema alone does, without the items. */
61
+ removedItemFieldLabel?: (thread: CommentThread) => string | undefined;
38
62
  }
39
63
  export declare const FormRenderer: React.FC<FormRendererProps>;
@@ -1,8 +1,12 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Alert, Button, Group, Paper, Stack, Text } from '@mantine/core';
4
- import { IconAlertCircle, IconInfoCircle } from '@tabler/icons-react';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Alert, Stack, Text } from '@mantine/core';
5
+ import { IconInfoCircle } from '@tabler/icons-react';
5
6
  import { MarkdownField } from './fields/MarkdownField.js';
7
+ import { useSiteMdxAllow } from './context/index.js';
8
+ import { markdownPolicyOf } from '../validation/mdx-allowlist.js';
9
+ import { findBodyFieldName } from '../utils/body-field.js';
6
10
  import { StringListField } from './fields/StringListField.js';
7
11
  import { TextField } from './fields/TextField.js';
8
12
  import { ToggleField } from './fields/ToggleField.js';
@@ -15,23 +19,31 @@ import { ReferenceField } from './fields/ReferenceField.js';
15
19
  import { CodeField } from './fields/CodeField.js';
16
20
  import { ImageField } from './fields/ImageField.js';
17
21
  import { ObjectField } from './fields/ObjectField.js';
18
- import { FieldDescription, groupDescriptionProps } from './fields/FieldDescription.js';
22
+ import { ObjectListField } from './fields/ObjectListField.js';
23
+ import { listItemTitle } from './fields/list-item-title.js';
19
24
  import { InlineGroupField } from './fields/InlineGroupField.js';
20
25
  import { formatCanopyPath, normalizeCanopyPath } from './canopy-path.js';
21
26
  import { FieldWrapper } from './comments/FieldWrapper.js';
27
+ import { FormCommentsContext, createFormComments } from './comments/form-comments.js';
22
28
  import { EntryComments } from './comments/EntryComments.js';
23
- import { useReferenceResolution } from './hooks/useReferenceResolution.js';
29
+ import { EditorErrorBoundary } from './components/EditorErrorBoundary.js';
30
+ import { ValidationSummary } from './components/ValidationSummary.js';
31
+ import { fieldBreadcrumb } from './field-breadcrumb.js';
32
+ import { FieldCrashFallback } from './fields/FieldCrashFallback.js';
33
+ import { useListItemKeys } from './fields/list-item-keys.js';
34
+ import { FormExpansionContext } from './fields/form-expansion.js';
35
+ import { FormHeader } from './FormHeader.js';
36
+ import { buildFormOutline, hasCollapsibleList } from './form-outline.js';
37
+ import { findFieldTarget, focusFieldTarget } from './field-target.js';
38
+ import { schemaLabel } from './schema-label.js';
24
39
  /**
25
- * Card heading for an object-list item: the value of its `itemTitleField` when that is a
26
- * non-blank string or a number, otherwise `fallback`.
40
+ * How an Undo toast names a removed object-list item: by its title field or first non-empty
41
+ * text field, since a positional "#2" title passes to the next item once this one is gone.
27
42
  */
28
- const listItemTitle = (item, itemTitleField, fallback) => {
29
- const raw = itemTitleField ? item?.[itemTitleField] : undefined;
30
- if (typeof raw === 'number' && Number.isFinite(raw))
31
- return String(raw);
32
- if (typeof raw === 'string' && raw.trim() !== '')
33
- return raw.trim();
34
- return fallback;
43
+ const removedItemLabel = (item, objectField, listLabel, index) => {
44
+ const text = listItemTitle(item, objectField);
45
+ const byPosition = `${objectField.itemLabel ?? 'item'} ${index + 1}`;
46
+ return text ? `"${text}" from ${listLabel}` : `${byPosition} from ${listLabel}`;
35
47
  };
36
48
  const normalizeOptions = (options) => {
37
49
  if (!options)
@@ -39,22 +51,74 @@ const normalizeOptions = (options) => {
39
51
  return options.map((opt) => (typeof opt === 'string' ? { label: opt, value: opt } : opt));
40
52
  };
41
53
  const fieldKey = (path) => formatCanopyPath(path);
42
- export const FormRenderer = ({ fields, value, onChange, customRenderers, branch = 'main', comments = [], currentEntryPath, currentUserId, canResolve = false, focusedFieldPath, highlightThreadId, onAddComment, onResolveThread, onResolvedValueChange, onLoadingStateChange, conflictNotice = false, fieldErrors, }) => {
43
- useReferenceResolution({
44
- value,
45
- fields,
46
- branch,
47
- onResolvedValueChange,
48
- onLoadingStateChange,
49
- });
50
- // Wraps the rendered control with an inline validation message when this
51
- // field has an active error (keyed by canonical canopy path, so errors land
52
- // on nested object/block fields too). Passed down to BlockField/ObjectField
53
- // so nested fields get the same decoration.
54
+ const noop = () => { };
55
+ const FENCE_STYLE = { border: 0, padding: 0, margin: 0, minWidth: 0 };
56
+ const READ_ONLY_FORM_CLASS = 'canopy-read-only-form';
57
+ /**
58
+ * Read-only inputs look disabled but stay focusable, so a reviewer can select and copy them. A
59
+ * picker's trigger is a button, which Mantine marks `data-read-only` instead.
60
+ */
61
+ const ReadOnlyFormStyles = () => (_jsx("style", { children: `
62
+ .${READ_ONLY_FORM_CLASS} .mantine-Input-input:is([readonly], [data-read-only], :has(input[readonly])) {
63
+ background-color: var(--input-disabled-bg);
64
+ }
65
+ .${READ_ONLY_FORM_CLASS} .canopy-mdx-content {
66
+ background-color: var(--canopy-surface-muted);
67
+ }
68
+ .${READ_ONLY_FORM_CLASS} .mantine-Switch-label {
69
+ color: var(--mantine-color-text);
70
+ }
71
+ ` }));
72
+ /**
73
+ * Disables a custom renderer's native controls on a read-only form, whether or not it honours
74
+ * `readOnly`. It wraps the control, never its `FieldWrapper`, so review comments stay open.
75
+ */
76
+ const ReadOnlyFence = ({ readOnly, children, }) => (_jsx("fieldset", { disabled: readOnly, role: "presentation", style: FENCE_STYLE, children: children }));
77
+ /** Builds the control inside the field's boundary: a custom renderer is called, not mounted. */
78
+ const FieldControl = ({ build }) => _jsx(_Fragment, { children: build() });
79
+ /**
80
+ * One field's error boundary. Every edit callback a field was rendered with before it crashed
81
+ * is dead for good, so nothing it scheduled can land later, even on another entry's draft.
82
+ * Callbacks from renders after the crash, which happen once the boundary resets on an entry or
83
+ * branch change, work.
84
+ */
85
+ const FieldBoundary = ({ canopyPath, resetKey, update, build, fallback }) => {
86
+ const [generation, setGeneration] = useState(0);
87
+ const liveGeneration = useRef(0);
88
+ const guardedUpdate = (next) => {
89
+ if (liveGeneration.current === generation)
90
+ update(next);
91
+ };
92
+ return (_jsx(EditorErrorBoundary, { context: { boundary: 'field', fieldPath: canopyPath }, resetKey: resetKey, onCaught: () => {
93
+ liveGeneration.current += 1;
94
+ setGeneration(liveGeneration.current);
95
+ }, fallback: fallback, children: _jsx(FieldControl, { build: () => build(guardedUpdate) }) }));
96
+ };
97
+ export const FormRenderer = ({ fields, value, onChange, customRenderers, branch = 'main', comments = [], currentEntryPath, currentUserId, canResolve = false, focusedFieldPath, highlightThreadId, onAddComment, onResolveThread, conflictNotice = false, fieldErrors, format, readOnly = false, onRemoved, onGetUserMetadata, header, notice, commentBlockedReason, removedItemFieldLabel, }) => {
98
+ const emit = readOnly ? noop : onChange;
99
+ const formRef = useRef(null);
100
+ const customTypes = new Set(Object.keys(customRenderers ?? {}));
101
+ const [expansionCommand, setExpansionCommand] = useState();
102
+ const boundaryResetKey = `${branch}\n${currentEntryPath ?? ''}`;
103
+ const siteMdxAllow = useSiteMdxAllow();
104
+ const bodyName = format === 'md' || format === 'mdx' ? findBodyFieldName(fields) : undefined;
105
+ const keysForList = useListItemKeys();
106
+ // Whether a list row holds an error, so a collapsed row can say so.
107
+ const rowHasErrors = (listPath) => (index) => {
108
+ const row = normalizeCanopyPath([...listPath, index]);
109
+ return Object.keys(fieldErrors ?? {}).some((key) => key === row || key.startsWith(`${row}.`) || key.startsWith(`${row}[`));
110
+ };
111
+ const [formComments] = useState(createFormComments);
112
+ // The field last focused on one entry is never offered on the next.
113
+ useEffect(() => formComments.noteFocus(undefined), [formComments, boundaryResetKey]);
114
+ // Wraps the rendered control in an error boundary, and with an inline validation message
115
+ // when this field has an active error (keyed by canonical canopy path, so errors land on
116
+ // nested object/block fields too). Passed down to BlockField/ObjectField so nested fields
117
+ // get the same decoration.
54
118
  const renderField = (field, currentValue, update, path) => {
55
- const control = renderFieldControl(field, currentValue, update, path);
56
- const error = fieldErrors?.[normalizeCanopyPath(path)];
57
- return (_jsxs(Stack, { gap: 4, children: [control, error && (_jsx(Text, { size: "xs", c: "red", "data-testid": `field-error-${fieldKey(path)}`, children: error }))] }, fieldKey(path)));
119
+ const canopyPath = normalizeCanopyPath(path);
120
+ const error = fieldErrors?.[canopyPath];
121
+ return (_jsxs(Stack, { gap: 4, children: [_jsx(FieldBoundary, { canopyPath: canopyPath, resetKey: boundaryResetKey, update: update, build: (guardedUpdate) => renderFieldControl(field, currentValue, guardedUpdate, path), fallback: (caught) => (_jsx(FieldCrashFallback, { label: schemaLabel(field), fieldType: field.type, value: currentValue, onChange: readOnly ? undefined : update, caught: caught, dataCanopyField: canopyPath })) }), error && (_jsx(Text, { size: "xs", c: "var(--canopy-danger-text)", "data-testid": `field-error-${fieldKey(path)}`, children: error }))] }, fieldKey(path)));
58
122
  };
59
123
  const renderFieldControl = (field, currentValue, update, path) => {
60
124
  const fieldId = `field-${fieldKey(path).replace(/[^a-zA-Z0-9_-]/g, '-')}`;
@@ -62,49 +126,56 @@ export const FormRenderer = ({ fields, value, onChange, customRenderers, branch
62
126
  const fieldThreads = currentEntryPath && onAddComment
63
127
  ? comments.filter((thread) => thread.type === 'field' &&
64
128
  thread.entryPath === currentEntryPath &&
65
- thread.canopyPath === canopyPath)
129
+ thread.canopyPath === canopyPath &&
130
+ !thread.onRemovedItem)
66
131
  : [];
132
+ // The full breadcrumb, so a list item's or block's fields are told apart by name.
133
+ const commentSubject = fieldBreadcrumb(fields, value, canopyPath).join(' › ');
67
134
  const custom = customRenderers?.[field.type];
68
135
  if (custom) {
69
- const renderedField = (_jsx("div", { children: custom({
136
+ const renderedField = (_jsx(ReadOnlyFence, { readOnly: readOnly, children: custom({
70
137
  field,
71
138
  value: currentValue,
72
- onChange: update,
139
+ onChange: readOnly ? noop : update,
73
140
  path,
74
141
  id: fieldId,
142
+ readOnly,
75
143
  }) }, fieldKey(path)));
76
144
  if (currentEntryPath && currentUserId && onAddComment && onResolveThread) {
77
- return (_jsx(FieldWrapper, { canopyPath: canopyPath, entryPath: currentEntryPath, threads: fieldThreads, autoFocus: focusedFieldPath === canopyPath, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, children: renderedField }, fieldKey(path)));
145
+ return (_jsx(FieldWrapper, { canopyPath: canopyPath, entryPath: currentEntryPath, threads: fieldThreads, commentBlockedReason: commentBlockedReason?.(canopyPath), autoFocus: focusedFieldPath === canopyPath, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, onGetUserMetadata: onGetUserMetadata, fieldLabel: commentSubject, resetKey: boundaryResetKey, controlPlacement: "corner", children: renderedField }, fieldKey(path)));
78
146
  }
79
147
  return renderedField;
80
148
  }
81
- const label = field.label ?? field.name;
149
+ const label = schemaLabel(field);
82
150
  const wrapWithComments = (renderedField) => {
83
151
  if (currentEntryPath && currentUserId && onAddComment && onResolveThread) {
84
- return (_jsx(FieldWrapper, { canopyPath: canopyPath, entryPath: currentEntryPath, threads: fieldThreads, autoFocus: focusedFieldPath === canopyPath, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, children: renderedField }));
152
+ return (_jsx(FieldWrapper, { canopyPath: canopyPath, entryPath: currentEntryPath, threads: fieldThreads, commentBlockedReason: commentBlockedReason?.(canopyPath), autoFocus: focusedFieldPath === canopyPath, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, onGetUserMetadata: onGetUserMetadata, fieldLabel: commentSubject, resetKey: boundaryResetKey, children: renderedField }));
85
153
  }
86
154
  return renderedField;
87
155
  };
88
156
  switch (field.type) {
89
157
  case 'string':
90
158
  if (field.list) {
91
- return wrapWithComments(_jsx(StringListField, { id: fieldId, label: label, description: field.description, value: Array.isArray(currentValue) ? currentValue : [], onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
159
+ return wrapWithComments(_jsx(StringListField, { id: fieldId, label: label, description: field.description, value: Array.isArray(currentValue) ? currentValue : [], onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly }, fieldKey(path)));
92
160
  }
93
- return wrapWithComments(_jsx(TextField, { id: fieldId, label: label, description: field.description, value: currentValue ?? '', onChange: update, dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
161
+ return wrapWithComments(_jsx(TextField, { id: fieldId, label: label, description: field.description, value: currentValue ?? '', onChange: update, dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly }, fieldKey(path)));
94
162
  case 'boolean':
95
- return wrapWithComments(_jsx(ToggleField, { id: fieldId, label: label, description: field.description, value: Boolean(currentValue), onChange: (v) => update(Boolean(v)), dataCanopyField: normalizeCanopyPath(path), testId: `field-toggle-${field.name}` }, fieldKey(path)));
163
+ return wrapWithComments(_jsx(ToggleField, { id: fieldId, label: label, description: field.description, value: Boolean(currentValue), onChange: (v) => update(Boolean(v)), dataCanopyField: normalizeCanopyPath(path), testId: `field-toggle-${field.name}`, readOnly: readOnly }, fieldKey(path)));
96
164
  case 'number':
97
165
  if (field.list) {
98
166
  return wrapWithComments(_jsx(NumberListField, { id: fieldId, label: label, description: field.description, value: Array.isArray(currentValue)
99
167
  ? currentValue.filter((v) => typeof v === 'number')
100
- : [], onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
168
+ : [], onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly }, fieldKey(path)));
101
169
  }
102
- return wrapWithComments(_jsx(NumberField, { id: fieldId, label: label, description: field.description, value: typeof currentValue === 'number' ? currentValue : undefined, onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
170
+ return wrapWithComments(_jsx(NumberField, { id: fieldId, label: label, description: field.description, value: typeof currentValue === 'number' ? currentValue : undefined, onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly }, fieldKey(path)));
103
171
  case 'datetime':
104
- return wrapWithComments(_jsx(DateTimeField, { id: fieldId, label: label, description: field.description, value: typeof currentValue === 'string' ? currentValue : '', onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
172
+ return wrapWithComments(_jsx(DateTimeField, { id: fieldId, label: label, description: field.description, value: typeof currentValue === 'string' ? currentValue : '', onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly, stampOnPublish: 'stampOnPublish' in field && field.stampOnPublish === true }, fieldKey(path)));
105
173
  case 'markdown':
106
- case 'mdx':
107
- return wrapWithComments(_jsx(MarkdownField, { id: fieldId, label: label, description: field.description, value: currentValue ?? '', onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
174
+ case 'mdx': {
175
+ const isBody = path.length === 1 && path[0] === bodyName;
176
+ const policy = markdownPolicyOf(field, siteMdxAllow, isBody ? format : undefined);
177
+ return wrapWithComments(_jsx(MarkdownField, { id: fieldId, label: label, description: field.description, value: currentValue ?? '', onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path), htmlTags: policy?.dialect === 'mdx' ? policy.allow.htmlTags : undefined, readOnly: readOnly }, fieldKey(path)));
178
+ }
108
179
  case 'select': {
109
180
  const selectField = field;
110
181
  const options = normalizeOptions(selectField.options);
@@ -113,7 +184,7 @@ export const FormRenderer = ({ fields, value, onChange, customRenderers, branch
113
184
  ? Array.isArray(currentValue)
114
185
  ? currentValue
115
186
  : []
116
- : (currentValue ?? ''), multiple: isMulti, onChange: (next) => update(next), dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
187
+ : (currentValue ?? ''), multiple: isMulti, onChange: (next) => update(next), dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly }, fieldKey(path)));
117
188
  }
118
189
  case 'reference': {
119
190
  const referenceField = field;
@@ -128,7 +199,7 @@ export const FormRenderer = ({ fields, value, onChange, customRenderers, branch
128
199
  ? Array.isArray(currentValue)
129
200
  ? currentValue
130
201
  : []
131
- : (currentValue ?? ''), multiple: isMulti, onChange: (next) => update(next), dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
202
+ : (currentValue ?? ''), multiple: isMulti, onChange: (next) => update(next), dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly }, fieldKey(path)));
132
203
  }
133
204
  case 'image': {
134
205
  const imageField = field;
@@ -136,11 +207,20 @@ export const FormRenderer = ({ fields, value, onChange, customRenderers, branch
136
207
  src: fieldErrors?.[`${canopyPath}.src`],
137
208
  alt: fieldErrors?.[`${canopyPath}.alt`],
138
209
  crop: fieldErrors?.[`${canopyPath}.crop`],
139
- } }, fieldKey(path)));
210
+ }, readOnly: readOnly, onRemoved: (removed) => onRemoved?.({ kind: 'value', path, value: removed, label: `the image from ${label}` }) }, fieldKey(path)));
140
211
  }
141
212
  case 'block': {
142
213
  const blockField = field;
143
- return wrapWithComments(_jsx(BlockField, { label: label, description: field.description, templates: blockField.templates, value: (Array.isArray(currentValue) ? currentValue : []), onChange: (next) => update(next), renderField: renderField, path: path, dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
214
+ return wrapWithComments(_jsx(BlockField, { label: label, required: field.required, description: field.description, templates: blockField.templates, value: (Array.isArray(currentValue) ? currentValue : []), onChange: (next) => update(next), renderField: renderField, path: path, dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly, resetKey: boundaryResetKey, hasErrors: rowHasErrors(path), onRemoved: (index, block) => {
215
+ const template = blockField.templates.find((t) => t.name === block.template);
216
+ onRemoved?.({
217
+ kind: 'list-item',
218
+ listPath: path,
219
+ index,
220
+ item: block,
221
+ label: `"${template ? schemaLabel(template) : block.template}" from ${label}`,
222
+ });
223
+ } }, fieldKey(path)));
144
224
  }
145
225
  case 'object': {
146
226
  const objectField = field;
@@ -148,41 +228,56 @@ export const FormRenderer = ({ fields, value, onChange, customRenderers, branch
148
228
  const items = Array.isArray(currentValue)
149
229
  ? currentValue
150
230
  : [];
151
- return wrapWithComments(_jsx(Paper, { withBorder: true, radius: "md", p: "md", shadow: "xs", ...groupDescriptionProps(fieldId, field.description), children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Group, { justify: "space-between", children: [_jsx(Text, { size: "sm", fw: 600, children: label }), _jsx(Button, { size: "xs", variant: "light", onClick: () => update([...items, {}]), children: "Add item" })] }), _jsx(FieldDescription, { baseId: fieldId, description: field.description }), _jsxs(Stack, { gap: "sm", children: [items.map((item, idx) => {
152
- const itemTitle = listItemTitle(item, objectField.itemTitleField, `${label} #${idx + 1}`);
153
- return (_jsx(Paper, { role: "group", "aria-label": itemTitle, withBorder: true, radius: "md", p: "sm", shadow: "xs", children: _jsxs(Stack, { gap: "xs", children: [_jsxs(Group, { justify: "space-between", children: [_jsx(Text, { size: "xs", fw: 700, children: itemTitle }), _jsx(Button, { size: "xs", variant: "subtle", color: "red", onClick: () => update(items.filter((_, i) => i !== idx)), children: "Remove" })] }), _jsx(ObjectField, { fields: objectField.fields, value: item, onChange: (next) => {
154
- const nextItems = [...items];
155
- nextItems[idx] = next;
156
- update(nextItems);
157
- }, renderField: renderField, path: [...path, idx], dataCanopyField: normalizeCanopyPath([...path, idx]) })] }) }, fieldKey([...path, idx])));
158
- }), items.length === 0 && (_jsx(Text, { size: "xs", c: "dimmed", children: "No items yet. Add one to get started." }))] })] }) }, fieldKey(path)));
231
+ return wrapWithComments(_jsx(ObjectListField, { id: fieldId, label: label, field: objectField, items: items, itemKeys: keysForList(items, fieldKey(path)), onChange: (next) => update(next), renderField: renderField, path: path, canopyPath: canopyPath, readOnly: readOnly, resetKey: boundaryResetKey, hasErrors: rowHasErrors(path), onRemoved: (index, item) => onRemoved?.({
232
+ kind: 'list-item',
233
+ listPath: path,
234
+ index,
235
+ item,
236
+ label: removedItemLabel(item, objectField, label, index),
237
+ }) }, fieldKey(path)));
159
238
  }
160
- // Unlike the list branch above (each item has its own Remove button), a
161
- // non-list object has no other way back to `undefined`: a required child
162
- // filled and cleared to `''` reads as empty via `isEmptyForRequired`
163
- // (entry-validator.ts), but the object itself only reads "absent" when
164
- // `undefined`/`null`. The Clear button (ObjectField's `onRemove`) resets to
165
- // `undefined`, never `{}` -- shown only when the field is optional and
166
- // already has a value, and labeled "Clear" rather than "Remove" so it
167
- // doesn't read as deleting the field from the schema.
239
+ // A non-list object has no list item to remove, so its only way back to `undefined` is
240
+ // the Clear button (`onRemove`): shown only for an optional field that has a value.
241
+ // Reset to `undefined`, never `{}`, which `isEmptyForRequired` (entry-validator.ts) reads
242
+ // as present. The label is "Clear" (docs/ux-guidelines.md, Vocabulary), and it offers Undo.
168
243
  const hasValue = currentValue !== undefined && currentValue !== null;
169
- const canClear = objectField.required !== true && hasValue;
170
- return wrapWithComments(_jsx(ObjectField, { label: label, description: field.description, fields: objectField.fields, value: currentValue, onChange: (next) => update(next), onRemove: canClear ? () => update(undefined) : undefined, renderField: renderField, path: path, dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
244
+ const canClear = !readOnly && objectField.required !== true && hasValue;
245
+ return wrapWithComments(_jsx(ObjectField, { label: label, required: objectField.required, description: field.description, fields: objectField.fields, value: currentValue, onChange: (next) => update(next), onRemove: canClear
246
+ ? () => {
247
+ update(undefined);
248
+ onRemoved?.({
249
+ kind: 'value',
250
+ path,
251
+ value: currentValue,
252
+ label: `"${label}"`,
253
+ action: 'clear',
254
+ });
255
+ }
256
+ : undefined, renderField: renderField, path: path, dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
171
257
  }
172
258
  case 'code':
173
- return wrapWithComments(_jsx(CodeField, { id: fieldId, label: label, description: field.description, value: typeof currentValue === 'string' ? currentValue : '', onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path) }, fieldKey(path)));
259
+ return wrapWithComments(_jsx(CodeField, { id: fieldId, label: label, description: field.description, value: typeof currentValue === 'string' ? currentValue : '', onChange: (v) => update(v), dataCanopyField: normalizeCanopyPath(path), readOnly: readOnly }, fieldKey(path)));
174
260
  default:
175
261
  return (_jsxs(Text, { size: "xs", c: "dimmed", children: ["Unsupported field: ", field.type] }, fieldKey(path)));
176
262
  }
177
263
  };
178
- return (_jsxs(Stack, { gap: "md", "data-form-renderer": true, children: [currentEntryPath && currentUserId && onAddComment && onResolveThread && (_jsx(EntryComments, { comments: comments, entryPath: currentEntryPath, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId })), conflictNotice && (_jsx(Alert, { icon: _jsx(IconInfoCircle, { size: 16 }), color: "orange", variant: "light", title: "Page updated since your draft started", "data-testid": "conflict-alert", children: "Someone else has recently changed this page. You can keep editing \u2014 a reviewer will reconcile your changes when you submit." })), fieldErrors && Object.keys(fieldErrors).length > 0 && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", variant: "light", title: "Fix these issues before saving", "data-testid": "validation-alert", children: _jsx(Stack, { gap: 2, children: Object.entries(fieldErrors).map(([path, message]) => (_jsxs(Text, { size: "xs", children: [path, ": ", message] }, path))) }) })), fields.map((field) => {
179
- // Inline groups are transparent to the value — render them directly with parent value/onChange
180
- if (field.type === 'group') {
181
- const groupField = field;
182
- return (_jsx("div", { children: _jsx(InlineGroupField, { label: groupField.label, description: groupField.description, fields: groupField.fields, value: value, onChange: onChange, renderField: renderField, path: [] }) }, `group-${groupField.name}`));
183
- }
184
- const val = value[field.name];
185
- const path = [field.name];
186
- return (_jsx("div", { children: renderField(field, val, (next) => onChange({ ...value, [field.name]: next }), path) }, fieldKey(path)));
187
- })] }));
264
+ return (_jsx(FormCommentsContext.Provider, { value: formComments, children: _jsx(FormExpansionContext.Provider, { value: expansionCommand, children: _jsxs(Stack, { ref: formRef, gap: "md", "data-form-renderer": true, className: readOnly ? READ_ONLY_FORM_CLASS : undefined, children: [header && (_jsx(FormHeader, { title: header.title, typeLabel: header.typeLabel, outline: buildFormOutline(fields, value, customTypes), onSelect: (path) => {
265
+ const target = formRef.current ? findFieldTarget(path, formRef.current) : undefined;
266
+ if (target)
267
+ focusFieldTarget(target.element);
268
+ }, onExpandAll: hasCollapsibleList(fields, customTypes)
269
+ ? (expanded) => setExpansionCommand((prev) => ({ seq: (prev?.seq ?? 0) + 1, expanded }))
270
+ : undefined })), notice, readOnly && _jsx(ReadOnlyFormStyles, {}), currentEntryPath && currentUserId && onAddComment && onResolveThread && (_jsx(EntryComments, { comments: comments, entryPath: currentEntryPath, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, onGetUserMetadata: onGetUserMetadata, resetKey: boundaryResetKey, removedItemFieldLabel: (thread) => removedItemFieldLabel?.(thread) ??
271
+ fieldBreadcrumb(fields, undefined, thread.canopyPath ?? '', { items: false }).join(' › ') })), conflictNotice && (_jsx(Alert, { icon: _jsx(IconInfoCircle, { size: 16 }), color: "orange", variant: "light", title: "Page updated since your draft started", "data-testid": "conflict-alert", children: readOnly
272
+ ? 'Someone else has recently changed this page.'
273
+ : 'Someone else has recently changed this page. You can keep editing — a reviewer will reconcile your changes when you submit.' })), fieldErrors && _jsx(ValidationSummary, { fields: fields, value: value, errors: fieldErrors }), fields.map((field) => {
274
+ // Inline groups are transparent to the value — render them directly with parent value/onChange
275
+ if (field.type === 'group') {
276
+ const groupField = field;
277
+ return (_jsx("div", { children: _jsx(InlineGroupField, { label: groupField.label, description: groupField.description, fields: groupField.fields, value: value, onChange: emit, renderField: renderField, path: [] }) }, `group-${groupField.name}`));
278
+ }
279
+ const val = value[field.name];
280
+ const path = [field.name];
281
+ return (_jsx("div", { children: renderField(field, val, (next) => emit({ ...value, [field.name]: next }), path) }, fieldKey(path)));
282
+ })] }) }) }));
188
283
  };
@@ -0,0 +1,40 @@
1
+ import type { CSSProperties } from 'react';
2
+ /** What the preview reports of its marks; see `PreviewMarksMessage`. */
3
+ export interface PreviewMarks {
4
+ count: number;
5
+ paths?: string[];
6
+ }
7
+ /**
8
+ * Lightweight iframe wrapper to keep the preview in sync with form state.
9
+ * It posts the latest draft data to the iframe after load and when data changes. A progress bar
10
+ * runs until the preview's ready message arrives; when none does, a "Live updates off" chip offers
11
+ * to reload the preview.
12
+ */
13
+ export declare const PreviewFrame: ({ src, path, data, isLoading, className, style, highlightEnabled, onPreviewError, onMarks, assetBase, iframeStyle, onReload, }: {
14
+ src: string;
15
+ path: string;
16
+ data?: unknown;
17
+ isLoading?: unknown;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ highlightEnabled?: boolean;
21
+ /**
22
+ * The editor's authenticated asset route, a same-origin path, so it is sent with drafts only to
23
+ * a same-origin preview: another origin would resolve it against itself.
24
+ */
25
+ assetBase?: string;
26
+ /** Called when the preview reports a draft compile/render error; null clears it. */
27
+ onPreviewError?: (error: {
28
+ message: string;
29
+ fieldPath?: string;
30
+ } | null) => void;
31
+ /**
32
+ * Called with how many elements the preview marks, while highlighting is on, and their
33
+ * distinct paths when the preview sends them within `MARK_REPORT_LIMITS`.
34
+ */
35
+ onMarks?: (marks: PreviewMarks) => void;
36
+ /** Overrides the iframe's own layout, such as a fixed width and scale; the bar and chip stay unscaled. */
37
+ iframeStyle?: CSSProperties;
38
+ /** Replaces the chip's Retry, for a host whose own reload control remounts the frame. */
39
+ onReload?: () => void;
40
+ }) => import("react/jsx-runtime").JSX.Element;