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
@@ -1,7 +1,13 @@
1
1
  import { z } from 'zod';
2
2
  import { CommentStore } from '../comment-store.js';
3
+ import { lookupUserProfiles } from '../auth/user-metadata-lookup.js';
4
+ import { getErrorMessage } from '../utils/error.js';
3
5
  import { isReviewer } from '../authorization/index.js';
6
+ import { parseLogicalPath } from '../paths/validation.js';
7
+ import { normalizeFilesystemPath } from '../paths/normalize.js';
4
8
  import { defineEndpoint } from './route-builder.js';
9
+ import { isItemKey } from '../item-keys.js';
10
+ import { parseCanopyPath } from '../editor/canopy-path.js';
5
11
  import { branchParamSchema, branchNameSchema, logicalPathSchema } from './validators.js';
6
12
  // ============================================================================
7
13
  // Zod Schemas for Validation
@@ -16,14 +22,76 @@ const addCommentBodySchema = z.object({
16
22
  type: z.enum(['field', 'entry', 'branch']),
17
23
  entryPath: logicalPathSchema.optional(),
18
24
  canopyPath: z.string().optional(), // Canopy field path, not a file path
25
+ itemKeys: z.array(z.string().refine(isItemKey, 'not an item key')).max(32).optional(),
19
26
  });
20
- const listCommentsHandler = async (gc, _ctx, _req, _params) => {
27
+ /**
28
+ * Whether `entryPath` is spelled the way path rules expect an entry's logical path: rooted at
29
+ * the content root, with no empty or `.` segment. Rules are globs over that spelling, so
30
+ * `secret/plan` (which the content API reads as `content/secret/plan`) and
31
+ * `content/./secret/plan` both miss a rule written for `content/secret/**`.
32
+ */
33
+ const isCanonicalEntryPath = (entryPath, contentRoot) => entryPath.startsWith(`${normalizeFilesystemPath(contentRoot)}/`) &&
34
+ entryPath.split('/').every((segment) => segment !== '' && segment !== '.');
35
+ /**
36
+ * Whether the user may see a thread: its entry's path rules at `read`, the level a content
37
+ * read needs, so a thread discloses nothing about an entry its reader could not open. A
38
+ * field thread is governed by its entry. A thread with no entry path (a branch thread) is
39
+ * governed by branch access alone, which the `branchAccess` guard has already passed. An
40
+ * entry path that is not a canonical logical path is denied: comments.json is read without
41
+ * schema validation.
42
+ */
43
+ const canReadThreadEntry = (checkAccess, contentRoot, entryPath) => {
44
+ if (entryPath === undefined)
45
+ return true;
46
+ const parsed = parseLogicalPath(entryPath);
47
+ return (parsed.ok &&
48
+ isCanonicalEntryPath(parsed.path, contentRoot) &&
49
+ checkAccess(parsed.path, 'read').allowed);
50
+ };
51
+ /** One checker per request, bound to the content root its paths are canonical against. */
52
+ const createThreadReadCheck = async (ctx, req, branchContext) => {
53
+ const checkAccess = await ctx.services.createContentAccessChecker(branchContext, branchContext.branchRoot, req.user);
54
+ const contentRoot = ctx.services.config.contentRoot || 'content';
55
+ return (entryPath) => canReadThreadEntry(checkAccess, contentRoot, entryPath);
56
+ };
57
+ /**
58
+ * The profile of everyone who wrote or resolved one of `threads`, looked up only for threads
59
+ * already filtered to what the requester may read, so the list names no one the requester could
60
+ * not see in a thread. A failed lookup costs the names, never the threads.
61
+ */
62
+ const lookupThreadUsers = async (ctx, threads) => {
63
+ const authPlugin = ctx.authPlugin;
64
+ if (!authPlugin)
65
+ return [];
66
+ try {
67
+ const ids = [];
68
+ for (const thread of threads) {
69
+ ids.push(thread.authorId, thread.resolvedBy);
70
+ // comments.json is parsed without validation, so a thread may lack its comment list.
71
+ if (Array.isArray(thread.comments)) {
72
+ for (const comment of thread.comments)
73
+ ids.push(comment?.userId);
74
+ }
75
+ }
76
+ return await lookupUserProfiles(authPlugin, ids);
77
+ }
78
+ catch (error) {
79
+ console.error('CanopyCMS: Failed to look up comment authors:', getErrorMessage(error));
80
+ return [];
81
+ }
82
+ };
83
+ const listCommentsHandler = async (gc, ctx, req, _params) => {
21
84
  const { branchContext } = gc;
22
85
  const commentStore = new CommentStore(branchContext.branchRoot);
23
- const threads = await commentStore.listThreads({ includeResolved: true });
24
- return { ok: true, status: 200, data: { threads } };
86
+ const [allThreads, canRead] = await Promise.all([
87
+ commentStore.listThreads({ includeResolved: true }),
88
+ createThreadReadCheck(ctx, req, branchContext),
89
+ ]);
90
+ const threads = allThreads.filter((thread) => canRead(thread.entryPath));
91
+ const users = await lookupThreadUsers(ctx, threads);
92
+ return { ok: true, status: 200, data: { threads, users } };
25
93
  };
26
- const addCommentHandler = async (gc, _ctx, req, _params, body) => {
94
+ const addCommentHandler = async (gc, ctx, req, _params, body) => {
27
95
  const { branchContext } = gc;
28
96
  // Validate required fields based on type
29
97
  if (body.type === 'field' && !body.canopyPath) {
@@ -33,6 +101,18 @@ const addCommentHandler = async (gc, _ctx, req, _params, body) => {
33
101
  error: 'canopyPath required for field comments',
34
102
  };
35
103
  }
104
+ // A new field thread inside list items names each item on its path by key, one per list index,
105
+ // so it follows the item rather than the index. A reply lands on its thread and needs none.
106
+ const listIndexes = body.type === 'field' && body.canopyPath && !body.threadId
107
+ ? parseCanopyPath(body.canopyPath).filter((s) => typeof s === 'number').length
108
+ : 0;
109
+ if ((body.itemKeys?.length ?? 0) !== listIndexes) {
110
+ return {
111
+ ok: false,
112
+ status: 400,
113
+ error: 'itemKeys must name one item per list index in a new field comment’s canopyPath',
114
+ };
115
+ }
36
116
  if ((body.type === 'field' || body.type === 'entry') && !body.entryPath) {
37
117
  return {
38
118
  ok: false,
@@ -40,22 +120,51 @@ const addCommentHandler = async (gc, _ctx, req, _params, body) => {
40
120
  error: 'entryPath required for field/entry comments',
41
121
  };
42
122
  }
123
+ // Commenting needs read access to the entry the comment lands on. A reply lands on the
124
+ // stored thread, so it is checked against that thread's entry path, which never changes
125
+ // once the thread exists; a reply to a thread the user cannot read answers as missing, as
126
+ // resolve does.
43
127
  const commentStore = new CommentStore(branchContext.branchRoot);
128
+ const [existing, canRead] = await Promise.all([
129
+ body.threadId ? commentStore.getThread(body.threadId) : Promise.resolve(null),
130
+ createThreadReadCheck(ctx, req, branchContext),
131
+ ]);
132
+ if (body.threadId) {
133
+ if (!existing || !canRead(existing.entryPath)) {
134
+ return { ok: false, status: 404, error: 'Thread not found' };
135
+ }
136
+ const reply = await commentStore.addReply({
137
+ userId: req.user.userId,
138
+ text: body.text,
139
+ threadId: body.threadId,
140
+ });
141
+ if (!reply) {
142
+ return { ok: false, status: 404, error: 'Thread not found' };
143
+ }
144
+ return { ok: true, status: 201, data: reply };
145
+ }
146
+ if (!canRead(body.entryPath)) {
147
+ return { ok: false, status: 403, error: 'Forbidden' };
148
+ }
44
149
  const result = await commentStore.addComment({
45
150
  userId: req.user.userId,
46
151
  text: body.text,
47
- threadId: body.threadId,
48
152
  type: body.type,
49
153
  entryPath: body.entryPath,
50
154
  canopyPath: body.canopyPath,
155
+ itemKeys: body.itemKeys,
51
156
  });
52
157
  return { ok: true, status: 201, data: result };
53
158
  };
54
- const resolveCommentHandler = async (gc, _ctx, req, params) => {
159
+ const resolveCommentHandler = async (gc, ctx, req, params) => {
55
160
  const { branchContext } = gc;
56
161
  const commentStore = new CommentStore(branchContext.branchRoot);
57
- const thread = await commentStore.getThread(params.threadId);
58
- if (!thread) {
162
+ const [thread, canRead] = await Promise.all([
163
+ commentStore.getThread(params.threadId),
164
+ createThreadReadCheck(ctx, req, branchContext),
165
+ ]);
166
+ // A thread the user cannot read answers as missing, so its id does not confirm it exists.
167
+ if (!thread || !canRead(thread.entryPath)) {
59
168
  return { ok: false, status: 404, error: 'Thread not found' };
60
169
  }
61
170
  // Check permissions: thread author, reviewer, or admin
@@ -85,7 +194,7 @@ const listComments = defineEndpoint({
85
194
  params: branchParamSchema,
86
195
  responseType: 'CommentsResponse',
87
196
  response: {},
88
- defaultMockData: { threads: [] },
197
+ defaultMockData: { threads: [], users: [] },
89
198
  guards: ['branchAccess'],
90
199
  handler: listCommentsHandler,
91
200
  });
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import type { ApiResponse } from './types.js';
3
3
  import type { EntryValidationIssue } from '../config/index.js';
4
- import { type Slug } from '../paths/index.js';
4
+ import { type LogicalPath, type Slug } from '../paths/index.js';
5
5
  export type ContentReadResponse = ApiResponse<{
6
6
  format: string;
7
7
  data: Record<string, unknown>;
@@ -41,8 +41,8 @@ export interface WriteContentBody {
41
41
  body?: string;
42
42
  /**
43
43
  * OCC token. A number from a prior read/write response makes this an update,
44
- * rejected with 409 if the file's mtime no longer matches (a file deleted
45
- * since is written anew). `null` or omitted makes it
44
+ * rejected with 409 if the file's mtime no longer matches, or with 409 `ENTRY_GONE` if the
45
+ * file is gone (deleted or moved since the read). `null` or omitted makes it
46
46
  * a create, rejected with 409 if the entry already exists. There is no blind
47
47
  * write: without a token, "no conflict detection" would be indistinguishable
48
48
  * from "lost the token", so an update has to prove which version it read.
@@ -61,7 +61,7 @@ export interface RenameEntryBody {
61
61
  export declare const CONTENT_ROUTES: {
62
62
  readonly read: import("./route-builder.js").RouteDefinition<z.ZodObject<{
63
63
  branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
64
- path: z.ZodType<import("../paths/index.js").LogicalPath, z.ZodTypeDef, import("../paths/index.js").LogicalPath>;
64
+ path: z.ZodType<LogicalPath, z.ZodTypeDef, LogicalPath>;
65
65
  }, "strip", z.ZodTypeAny, {
66
66
  path: string & {
67
67
  readonly __brand: "LogicalPath";
@@ -79,7 +79,7 @@ export declare const CONTENT_ROUTES: {
79
79
  }>, undefined, ContentReadResponse>;
80
80
  readonly write: import("./route-builder.js").RouteDefinition<z.ZodObject<{
81
81
  branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
82
- path: z.ZodType<import("../paths/index.js").LogicalPath, z.ZodTypeDef, import("../paths/index.js").LogicalPath>;
82
+ path: z.ZodType<LogicalPath, z.ZodTypeDef, LogicalPath>;
83
83
  entryType: z.ZodOptional<z.ZodString>;
84
84
  }, "strip", z.ZodTypeAny, {
85
85
  path: string & {
@@ -104,18 +104,18 @@ export declare const CONTENT_ROUTES: {
104
104
  expectedVersion: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
105
105
  }, "strip", z.ZodTypeAny, {
106
106
  format: "mdx" | "md" | "json" | "yaml";
107
+ data?: Record<string, unknown> | undefined;
107
108
  expectedVersion?: number | null | undefined;
108
109
  body?: string | undefined;
109
- data?: Record<string, unknown> | undefined;
110
110
  }, {
111
111
  format: "mdx" | "md" | "json" | "yaml";
112
+ data?: Record<string, unknown> | undefined;
112
113
  expectedVersion?: number | null | undefined;
113
114
  body?: string | undefined;
114
- data?: Record<string, unknown> | undefined;
115
115
  }>, ContentWriteResponse>;
116
116
  readonly validateReferences: import("./route-builder.js").RouteDefinition<z.ZodObject<{
117
117
  branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
118
- path: z.ZodType<import("../paths/index.js").LogicalPath, z.ZodTypeDef, import("../paths/index.js").LogicalPath>;
118
+ path: z.ZodType<LogicalPath, z.ZodTypeDef, LogicalPath>;
119
119
  entryType: z.ZodOptional<z.ZodString>;
120
120
  }, "strip", z.ZodTypeAny, {
121
121
  path: string & {
@@ -142,7 +142,7 @@ export declare const CONTENT_ROUTES: {
142
142
  }>, ReferenceValidationResponse>;
143
143
  readonly renameEntry: import("./route-builder.js").RouteDefinition<z.ZodObject<{
144
144
  branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
145
- path: z.ZodType<import("../paths/index.js").LogicalPath, z.ZodTypeDef, import("../paths/index.js").LogicalPath>;
145
+ path: z.ZodType<LogicalPath, z.ZodTypeDef, LogicalPath>;
146
146
  }, "strip", z.ZodTypeAny, {
147
147
  path: string & {
148
148
  readonly __brand: "LogicalPath";
@@ -1,13 +1,19 @@
1
1
  import { z } from 'zod';
2
- import { BranchSyncingError, ContentStore, ContentStoreError, ContentConflictError, DuplicateContentIdError, UrlPathConflictError, getDefaultEntryType, } from '../content-store.js';
2
+ import { ENTRY_CHANGED_MESSAGE, ENTRY_GONE_MESSAGE } from './entries-constants.js';
3
+ import { BranchSyncingError, ContentStore, ContentStoreError, ContentConflictError, DuplicateContentIdError, EntryGoneError, UrlPathConflictError, getDefaultEntryType, } from '../content-store.js';
3
4
  import { defineEndpoint } from './route-builder.js';
5
+ import { SchemaUnavailableError } from '../schema/schema-unavailable-error.js';
4
6
  import { ReferenceValidator } from '../validation/reference-validator.js';
5
7
  import { findUnknownKeys, mergeBodyIntoData, normalizeReferenceValues, validateEntryData, } from '../validation/entry-validator.js';
6
8
  import { validateEntryLinks } from '../validation/entry-link-validator.js';
9
+ import { findMarkdownSafetyIssues, splitByStored } from '../validation/markdown-safety.js';
10
+ import { collectReferenceIds } from '../validation/field-traversal.js';
7
11
  import { branchNameSchema, logicalPathSchema, slugSchema } from './validators.js';
8
12
  import { entryLogicalPath, parseSlug } from '../paths/index.js';
9
13
  import { getErrorMessage, isNotFoundError, sanitizeErrorMessage } from '../utils/error.js';
10
14
  import { isDataOnlyFormat } from '../utils/format.js';
15
+ import { withItemKeys } from '../item-keys.js';
16
+ import { findBodyFieldName } from '../utils/body-field.js';
11
17
  /**
12
18
  * Parse an API path into logical path segments, prepending the content root if needed.
13
19
  * Shared by all content API handlers.
@@ -110,6 +116,40 @@ const readContentHandler = async (gc, ctx, req, params) => {
110
116
  throw err;
111
117
  }
112
118
  };
119
+ /**
120
+ * A reference's field and id, positions dropped (`blocks[2].author` → `blocks.author`), so
121
+ * reordering blocks or list items keeps a match while moving an id to a field of another name
122
+ * does not.
123
+ */
124
+ const referenceSite = (fieldPath, id) => `${fieldPath.replace(/\[\d+\]/g, '')}\0${id}`;
125
+ /** Every reference site the stored entry holds, read raw so a missing target still yields its id. */
126
+ const storedReferenceSites = async (store, collectionPath, slug, fields) => {
127
+ const doc = await store.read(collectionPath, slug, { resolveReferences: false });
128
+ return new Set(collectReferenceIds(fields, doc.data).map((ref) => referenceSite(ref.path, ref.id)));
129
+ };
130
+ function withoutKey(record, key) {
131
+ if (!(key in record))
132
+ return record;
133
+ const { [key]: _dropped, ...rest } = record;
134
+ return rest;
135
+ }
136
+ /** The policy's issues in the stored entry, which a save may keep. */
137
+ const storedMarkdownSafetyIssues = async (store, collectionPath, slug, fields, siteAllow) => {
138
+ // A stored file that does not read keeps nothing, so the save's code is refused, not a 500.
139
+ const doc = await store
140
+ .read(collectionPath, slug, { resolveReferences: false })
141
+ .catch(() => undefined);
142
+ if (doc === undefined)
143
+ return [];
144
+ const data = 'body' in doc ? mergeBodyIntoData(fields, doc.data, doc.body) : doc.data;
145
+ return findMarkdownSafetyIssues(fields, doc.format, data, siteAllow);
146
+ };
147
+ const entryGoneResponse = () => ({
148
+ ok: false,
149
+ status: 409,
150
+ error: ENTRY_GONE_MESSAGE,
151
+ code: 'ENTRY_GONE',
152
+ });
113
153
  const writeContentHandler = async (gc, ctx, req, params, body) => {
114
154
  const { branchContext } = gc;
115
155
  const flatSchema = branchContext.flatSchema;
@@ -135,15 +175,22 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
135
175
  if (!access.allowed) {
136
176
  return { ok: false, status: 403, error: 'Forbidden' };
137
177
  }
178
+ // Update guard: an update whose entry is gone is refused before anything below judges it,
179
+ // since its entry type can no longer be read off the file and every check that follows would
180
+ // treat it as a create. store.write() re-enforces it in-lock (EntryGoneError).
181
+ const exists = await store.documentExists(schemaItem.logicalPath, slug);
182
+ if (body.expectedVersion != null && !exists)
183
+ return entryGoneResponse();
138
184
  // Authoritative schema validation at the write boundary (COMPOUND-2): the server re-checks
139
185
  // required fields, type/format correctness, and non-empty required references (rules shared
140
186
  // with the editor via validation/entry-validator), plus reference EXISTENCE and
141
187
  // EntryTypeConfig.maxItems (SCH-H3) — so a direct API call can't bypass what the editor's form
142
188
  // enforces client-side.
143
189
  //
144
- // Carve-out: a create scaffold (target doesn't exist yet, payload is entirely empty `{}`/no
145
- // body) skips field validation, since the editor's create flow writes an empty scaffold before
146
- // the user fills the form.
190
+ // Carve-outs for a create (the target doesn't exist yet), because the editor creates an entry
191
+ // before the user fills its form: an entirely empty payload (`{}`, no body) skips field
192
+ // validation, and any other create skips only the required-field checks, so a create carrying
193
+ // just a title lands. Every save after that is checked in full.
147
194
  // Resolve the entry-type config the store will write with (mirrors ContentStore.write)
148
195
  let entryTypeConfig;
149
196
  let fields = [];
@@ -198,20 +245,30 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
198
245
  fields = entryTypeConfig?.schema ?? [];
199
246
  maxItems = entryTypeConfig?.maxItems;
200
247
  }
201
- const data = body.data ?? {};
248
+ // Refused before validation, which an empty `fields` would wave through; mapped to a 503.
249
+ const resolvedEntryType = schemaItem.type === 'entry-type' ? schemaItem : entryTypeConfig;
250
+ if (resolvedEntryType?.unavailable) {
251
+ throw new SchemaUnavailableError(resolvedEntryType.name, resolvedEntryType.unavailable);
252
+ }
202
253
  const isDataOnly = isDataOnlyFormat(body.format);
254
+ // An md/mdx file's body field is its body, so a frontmatter key of that name is dropped: no
255
+ // check would see it, and a read or a resolved reference would serve it as the body.
256
+ const requestData = isDataOnly || body.data === undefined
257
+ ? body.data
258
+ : withoutKey(body.data, findBodyFieldName(fields));
259
+ const data = requestData ?? {};
203
260
  // The store's own `undefined` (blind write) is never reachable from here: an omitted token is
204
261
  // a create, so an update that lost its token 409s instead of overwriting unchecked.
205
262
  const expectedVersion = body.expectedVersion ?? null;
206
263
  const createConflictError = body.expectedVersion === null
207
264
  ? `An entry with slug "${slug}" already exists`
208
265
  : `An entry with slug "${slug}" already exists; an update must send the expectedVersion from its last read`;
266
+ const keptWarnings = [];
209
267
  try {
210
- const exists = await store.documentExists(schemaItem.logicalPath, slug);
211
268
  // Create guard: a create against a slug that already has content must never silently
212
269
  // overwrite it — short-circuit with 409 before field validation runs, so the error names the
213
270
  // real problem instead of "field is required" or a bare conflict. store.write() re-enforces
214
- // this itself inside its per-entry lock against a fresh stat (the race-safe authoritative
271
+ // this itself inside its per-entry lock against a fresh open (the race-safe authoritative
215
272
  // check); this is just a cheaper fast path for the common case.
216
273
  if (expectedVersion === null && exists) {
217
274
  return { ok: false, status: 409, error: createConflictError };
@@ -256,7 +313,27 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
256
313
  // Pure rules (shared with the editor). For md/mdx the body is validated
257
314
  // as the schema's isBody field.
258
315
  const dataForValidation = isDataOnly ? data : mergeBodyIntoData(fields, data, body.body ?? '');
259
- const fieldErrors = validateEntryData(fields, dataForValidation);
316
+ const fieldErrors = validateEntryData(fields, dataForValidation, '', {
317
+ required: exists,
318
+ });
319
+ // Code in markdown or MDX (validation/markdown-safety.ts). A field the stored entry held
320
+ // with code is kept, with a warning, when saved unchanged; any other code is refused.
321
+ const { mdxAllow } = ctx.services.config;
322
+ const unsafe = findMarkdownSafetyIssues(fields, body.format, dataForValidation, mdxAllow);
323
+ if (unsafe.length > 0) {
324
+ const stored = exists
325
+ ? await storedMarkdownSafetyIssues(store, schemaItem.logicalPath, slug, fields, mdxAllow)
326
+ : [];
327
+ const { refused, kept } = splitByStored(unsafe, stored);
328
+ fieldErrors.push(...refused);
329
+ for (const e of kept) {
330
+ keptWarnings.push({
331
+ level: 'warning',
332
+ fieldPath: e.fieldPath,
333
+ message: `holds content this field refuses, kept because the saved entry already had it: ${e.message}. Ask a developer to move it into a component, or to allow it.`,
334
+ });
335
+ }
336
+ }
260
337
  // Reference existence (server-only: reads the content ID index). Editor
261
338
  // payloads may still carry resolved `{ id, ... }` objects from a prior
262
339
  // read, so collapse them to id strings before checking.
@@ -264,7 +341,25 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
264
341
  const idIndex = await store.idIndex();
265
342
  const refValidator = new ReferenceValidator(idIndex, fields, (name) => store.resolveCollectionItem(name)?.logicalPath);
266
343
  const refResult = await refValidator.validate(normalizeReferenceValues(fields, data));
267
- fieldErrors.push(...refResult.errors.map((e) => ({ fieldPath: e.fieldPath, message: e.error })));
344
+ // A dangling id the file already holds in that field is kept with a warning, so an entry
345
+ // whose target was deleted stays saveable and the id survives; the production build is
346
+ // what fails on it. A dangling id this save introduces, or moves to a field of another name, is
347
+ // refused.
348
+ const storedSites = exists && refResult.errors.some((e) => e.dangling)
349
+ ? await storedReferenceSites(store, schemaItem.logicalPath, slug, fields)
350
+ : new Set();
351
+ for (const e of refResult.errors) {
352
+ if (e.dangling && storedSites.has(referenceSite(e.fieldPath, e.id))) {
353
+ keptWarnings.push({
354
+ level: 'warning',
355
+ fieldPath: e.fieldPath,
356
+ message: `references a missing entry (${e.id}). It is kept as it was; repoint or clear it.`,
357
+ });
358
+ }
359
+ else {
360
+ fieldErrors.push({ fieldPath: e.fieldPath, message: e.error });
361
+ }
362
+ }
268
363
  }
269
364
  if (fieldErrors.length > 0) {
270
365
  return {
@@ -286,7 +381,7 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
286
381
  // Adopter save-time validation, run BEFORE the file is written: 'error' issues
287
382
  // refuse the save (e.g. a body that would break the site's production build),
288
383
  // 'warning' issues are returned alongside the successful write.
289
- let validationWarnings;
384
+ let validationWarnings = keptWarnings.length > 0 ? keptWarnings : undefined;
290
385
  const validateEntry = ctx.services.config.validateEntry;
291
386
  // Collapse resolved reference objects back to bare ID strings before persisting (the reference
292
387
  // validator above gets its own copy). The editor's GET resolves references by default, so form
@@ -294,7 +389,13 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
294
389
  // left unnormalized, a save would freeze that snapshot into the file (resolution only
295
390
  // re-resolves plain ID strings), permanently severing the reference from its target. Idempotent:
296
391
  // a payload that already holds ID strings is unchanged.
297
- const normalizedData = body.data === undefined ? undefined : normalizeReferenceValues(fields, body.data);
392
+ //
393
+ // Every list item and block leaves a save keyed (`item-keys.ts`). A key sent is kept, so a
394
+ // thread naming it still finds its item; one missing is filled from the item's content, as a
395
+ // read fills one.
396
+ const normalizedData = requestData === undefined
397
+ ? undefined
398
+ : withItemKeys(fields, normalizeReferenceValues(fields, requestData), 'content');
298
399
  // Computed before the validateEntry hook and the entry-link scan too, not just the write, so
299
400
  // every consumer agrees on the same bytes — otherwise an adopter's hook could see a resolved
300
401
  // object while the file got an ID string, depending on whether the post came from the editor.
@@ -313,6 +414,7 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
313
414
  const overflow = unknownKeys.length - shown.length;
314
415
  const list = overflow > 0 ? `${shown.join(', ')} (and ${overflow} more)` : shown.join(', ');
315
416
  validationWarnings = [
417
+ ...(validationWarnings ?? []),
316
418
  {
317
419
  level: 'warning',
318
420
  message: `Saved. ${unknownKeys.length === 1 ? 'One field is' : `${unknownKeys.length} fields are`} ` +
@@ -328,7 +430,9 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
328
430
  issues = await validateEntry({
329
431
  entryPath: logicalPathSegments.join('/'),
330
432
  branch: params.branch,
331
- ...(params.entryType ? { entryType: params.entryType } : {}),
433
+ // The type the store writes with, so a request that omits `entryType` can't skip a
434
+ // type-gated rule.
435
+ ...(entryTypeName ? { entryType: entryTypeName } : {}),
332
436
  format: body.format,
333
437
  data: normalizedData ?? {},
334
438
  body: body.body,
@@ -352,8 +456,8 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
352
456
  .join('; '),
353
457
  };
354
458
  }
355
- // Appended, not assigned: the unknown-key scan above may already have found some, and the
356
- // editor shows the channel as one notification.
459
+ // Appended, not assigned: the scans above may already have found some, and the editor shows
460
+ // the channel as one notification.
357
461
  const warnings = issues.filter((issue) => issue.level === 'warning');
358
462
  if (warnings.length > 0)
359
463
  validationWarnings = [...(validationWarnings ?? []), ...warnings];
@@ -398,7 +502,12 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
398
502
  // refused outright, but a lock compromised mid-write can still land it — each case carries
399
503
  // its own message, so pass `err.message` through rather than the generic conflict below.
400
504
  if (err instanceof BranchSyncingError) {
401
- return { ok: false, status: 409, error: err.message };
505
+ return {
506
+ ok: false,
507
+ status: 409,
508
+ error: err.message,
509
+ code: err.outcome === 'unknown' ? 'WRITE_OUTCOME_UNKNOWN' : 'BRANCH_SYNCING',
510
+ };
402
511
  }
403
512
  // [F1] Also not an editor collision: this content ID is quarantined on two files
404
513
  // (ContentIdIndex's duplicate-ID detection). The generic message below would send the
@@ -413,16 +522,15 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
413
522
  if (err instanceof UrlPathConflictError) {
414
523
  return { ok: false, status: 409, error: err.message };
415
524
  }
525
+ // Gone since the `exists` check above: deleted or moved between it and the in-lock open.
526
+ if (err instanceof EntryGoneError)
527
+ return entryGoneResponse();
416
528
  // Race-safe fallback: the early `exists` check above catches the common case; this covers a
417
- // collision that lands between that check and store.write()'s in-lock stat.
529
+ // collision that lands between that check and store.write()'s in-lock open.
418
530
  if (expectedVersion === null) {
419
531
  return { ok: false, status: 409, error: createConflictError };
420
532
  }
421
- return {
422
- ok: false,
423
- status: 409,
424
- error: 'Content conflict: entry was modified by another editor',
425
- };
533
+ return { ok: false, status: 409, error: ENTRY_CHANGED_MESSAGE };
426
534
  }
427
535
  // C2: a ContentStoreError is an expected client fault (bad slug, validation, etc.) and keeps
428
536
  // its 400. Anything else — ENOSPC, EACCES, a bug — is a genuine server fault and must not be
@@ -542,7 +650,7 @@ const renameEntryHandler = async (gc, ctx, req, params, body) => {
542
650
  return {
543
651
  ok: false,
544
652
  status: 409,
545
- error: passThrough ? err.message : 'Content conflict: entry was modified by another editor',
653
+ error: passThrough ? err.message : ENTRY_CHANGED_MESSAGE,
546
654
  };
547
655
  }
548
656
  // C2: same rule as writeContentHandler's catch above (ContentStoreError -> 400, everything
@@ -598,6 +706,7 @@ const validateReferences = defineEndpoint({
598
706
  namespace: 'content',
599
707
  name: 'validateReferences',
600
708
  method: 'POST',
709
+ readOnly: true,
601
710
  path: '/:branch/validate-references/...path',
602
711
  params: validateReferencesParamsSchema,
603
712
  body: validateReferencesBodySchema,
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Pagination constants for the entries list endpoint. Kept dependency-free (no server-only
3
- * imports) so the editor's browser bundle can import the page-size cap without pulling in
4
- * `entries.ts` and its `node:fs`-backed deps; `entries.ts` re-exports these for API discoverability.
2
+ * Constants the entries and content endpoints share with the editor. Kept dependency-free (no
3
+ * server-only imports) so the editor's browser bundle can import them without pulling in
4
+ * `entries.ts` and its `node:fs`-backed deps.
5
5
  */
6
6
  /**
7
7
  * Maximum entries the list endpoint returns per request; larger `limit` values are clamped to
@@ -10,3 +10,14 @@
10
10
  export declare const MAX_ENTRIES_PER_PAGE = 200;
11
11
  /** Default page size when a request omits `limit`. */
12
12
  export declare const DEFAULT_ENTRIES_LIMIT = 50;
13
+ /**
14
+ * The generic 409 for a conflicting change to an entry, usually its version moving on since the
15
+ * caller read it. Shared with the editor, whose own pre-save staleness check reports the same.
16
+ */
17
+ export declare const ENTRY_CHANGED_MESSAGE = "This entry changed since you opened it. Choose \"Refresh from server\" to see the latest version (your unsaved edits will be lost).";
18
+ /**
19
+ * The 409 for an update whose entry is gone: deleted or moved since the caller read it (API code
20
+ * `ENTRY_GONE`). The save is refused rather than re-creating the entry; re-creating it is a create
21
+ * the caller makes deliberately.
22
+ */
23
+ export declare const ENTRY_GONE_MESSAGE = "This entry was deleted or moved since you opened it, so your changes were not saved.";
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Pagination constants for the entries list endpoint. Kept dependency-free (no server-only
3
- * imports) so the editor's browser bundle can import the page-size cap without pulling in
4
- * `entries.ts` and its `node:fs`-backed deps; `entries.ts` re-exports these for API discoverability.
2
+ * Constants the entries and content endpoints share with the editor. Kept dependency-free (no
3
+ * server-only imports) so the editor's browser bundle can import them without pulling in
4
+ * `entries.ts` and its `node:fs`-backed deps.
5
5
  */
6
6
  /**
7
7
  * Maximum entries the list endpoint returns per request; larger `limit` values are clamped to
@@ -10,3 +10,14 @@
10
10
  export const MAX_ENTRIES_PER_PAGE = 200;
11
11
  /** Default page size when a request omits `limit`. */
12
12
  export const DEFAULT_ENTRIES_LIMIT = 50;
13
+ /**
14
+ * The generic 409 for a conflicting change to an entry, usually its version moving on since the
15
+ * caller read it. Shared with the editor, whose own pre-save staleness check reports the same.
16
+ */
17
+ export const ENTRY_CHANGED_MESSAGE = 'This entry changed since you opened it. Choose "Refresh from server" to see the latest version (your unsaved edits will be lost).';
18
+ /**
19
+ * The 409 for an update whose entry is gone: deleted or moved since the caller read it (API code
20
+ * `ENTRY_GONE`). The save is refused rather than re-creating the entry; re-creating it is a create
21
+ * the caller makes deliberately.
22
+ */
23
+ export const ENTRY_GONE_MESSAGE = 'This entry was deleted or moved since you opened it, so your changes were not saved.';