convex-cms 0.0.6 → 0.0.7

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 (329) hide show
  1. package/README.md +68 -101
  2. package/admin/src/components/BreakingChangesWarningDialog.tsx +5 -5
  3. package/admin/src/components/BulkOperationModal.tsx +14 -14
  4. package/admin/src/components/ContentEntryEditor.tsx +8 -8
  5. package/admin/src/components/ContentTypeFormModal.tsx +122 -82
  6. package/admin/src/components/Header.tsx +5 -2
  7. package/admin/src/components/SchemaDriftWarning.tsx +126 -0
  8. package/admin/src/components/TaxonomyEditor.tsx +2 -2
  9. package/admin/src/components/TermTree.tsx +3 -3
  10. package/admin/src/components/UploadDropzone.tsx +7 -7
  11. package/admin/src/components/VersionCompare.tsx +13 -13
  12. package/admin/src/components/VersionHistory.tsx +2 -2
  13. package/admin/src/components/VersionRollbackModal.tsx +5 -5
  14. package/admin/src/components/cmsds/CmsButton.tsx +2 -2
  15. package/admin/src/components/cmsds/CmsDialog.tsx +4 -1
  16. package/admin/src/components/cmsds/CmsStatusBadge.tsx +5 -5
  17. package/admin/src/components/fields/JsonField.tsx +1 -1
  18. package/admin/src/components/fields/ReferenceField.tsx +9 -9
  19. package/admin/src/components/fields/TagField.tsx +1 -1
  20. package/admin/src/contexts/SettingsConfigContext.tsx +10 -3
  21. package/admin/src/embed/index.tsx +29 -9
  22. package/admin/src/embed/pages/ContentTypeEntries.tsx +25 -0
  23. package/admin/src/embed/pages/Entry.tsx +114 -0
  24. package/admin/src/embed/pages/Media.tsx +3 -1
  25. package/admin/src/embed/pages/NewEntry.tsx +83 -0
  26. package/admin/src/embed/pages/index.ts +3 -0
  27. package/admin/src/pages/ContentPage.tsx +27 -20
  28. package/admin/src/pages/ContentTypeEntriesPage.tsx +466 -0
  29. package/admin/src/pages/ContentTypesPage.tsx +65 -19
  30. package/admin/src/pages/DashboardPage.tsx +3 -0
  31. package/admin/src/pages/SettingsPage.tsx +4 -4
  32. package/admin/src/pages/index.ts +1 -0
  33. package/admin/src/routes/__root.tsx +10 -10
  34. package/admin/src/routes/entries/$entryId.tsx +1 -1
  35. package/admin/src/routes/entries/type/$contentTypeId.tsx +1 -1
  36. package/admin/src/styles/globals.css +31 -5
  37. package/admin/src/styles/tailwind-config.css +25 -0
  38. package/admin/src/styles/theme.css +50 -0
  39. package/admin-dist/nitro.json +1 -1
  40. package/admin-dist/public/assets/CmsEmptyState-6-PLaXtD.js +1 -0
  41. package/admin-dist/public/assets/CmsPageHeader-SoF4Epu9.js +1 -0
  42. package/admin-dist/public/assets/CmsStatusBadge-D7kYaohx.js +1 -0
  43. package/admin-dist/public/assets/{CmsSurface-DBy5Lumx.js → CmsSurface-BvksBm6W.js} +1 -1
  44. package/admin-dist/public/assets/CmsToolbar-DlZPMe2B.js +1 -0
  45. package/admin-dist/public/assets/ContentEntryEditor-C6n9xLS9.js +4 -0
  46. package/admin-dist/public/assets/TaxonomyFilter-CFX1_g8s.js +1 -0
  47. package/admin-dist/public/assets/_contentTypeId-DTv8UoTp.js +1 -0
  48. package/admin-dist/public/assets/_entryId-D3lr5Dvy.js +1 -0
  49. package/admin-dist/public/assets/alert-BAHTL6ao.js +1 -0
  50. package/admin-dist/public/assets/badge-oJv4Eai8.js +1 -0
  51. package/admin-dist/public/assets/{circle-check-big-CpLxAvEj.js → circle-check-big-3OHxNDhO.js} +1 -1
  52. package/admin-dist/public/assets/command-DwgQs69u.js +1 -0
  53. package/admin-dist/public/assets/content-CKQ4QwW2.js +1 -0
  54. package/admin-dist/public/assets/content-types-BrttaLpc.js +1 -0
  55. package/admin-dist/public/assets/globals-CoCRjt0K.css +1 -0
  56. package/admin-dist/public/assets/index-DOkgTSx0.js +1 -0
  57. package/admin-dist/public/assets/main-DV6oxWnU.js +102 -0
  58. package/admin-dist/public/assets/media-B2i-mCbx.js +1 -0
  59. package/admin-dist/public/assets/new._contentTypeId-VF63rpic.js +1 -0
  60. package/admin-dist/public/assets/pencil-CX1CiTDD.js +1 -0
  61. package/admin-dist/public/assets/refresh-cw-Cm-YOeFI.js +1 -0
  62. package/admin-dist/public/assets/{rotate-ccw-BZpZtw0N.js → rotate-ccw-B45JsL5f.js} +1 -1
  63. package/admin-dist/public/assets/scroll-area-b3A1HHR7.js +1 -0
  64. package/admin-dist/public/assets/{search-BvgYr-c9.js → search-DKKh_DdH.js} +1 -1
  65. package/admin-dist/public/assets/settings-CGVDEV1r.js +1 -0
  66. package/admin-dist/public/assets/switch-BTMY8Qnk.js +1 -0
  67. package/admin-dist/public/assets/tabs-DUQwUoIb.js +1 -0
  68. package/admin-dist/public/assets/tanstack-adapter-f7AHmQ5L.js +1 -0
  69. package/admin-dist/public/assets/taxonomies-DvMppdiD.js +1 -0
  70. package/admin-dist/public/assets/trash-D7e0uKd9.js +1 -0
  71. package/admin-dist/public/assets/{useBreadcrumbLabel-D00rvqjw.js → useBreadcrumbLabel-CF2KYwsw.js} +1 -1
  72. package/admin-dist/public/assets/usePermissions-DWBImEOW.js +1 -0
  73. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +2 -2
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +1 -1
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +1 -1
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +2 -2
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +2 -2
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +1 -1
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +1 -1
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +1 -1
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +6 -72
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1 -1
  83. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +1 -1
  84. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +20 -435
  85. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +30 -3
  86. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +0 -376
  87. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +168 -383
  88. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +451 -1195
  89. package/admin-dist/server/_chunks/_libs/react-dom.mjs +5 -5
  90. package/admin-dist/server/_chunks/_libs/react.mjs +2 -2
  91. package/admin-dist/server/_libs/cmdk.mjs +1 -1
  92. package/admin-dist/server/_libs/convex.mjs +3 -3
  93. package/admin-dist/server/_libs/cookie-es.mjs +1 -58
  94. package/admin-dist/server/_libs/date-fns.mjs +1 -1
  95. package/admin-dist/server/_libs/lucide-react.mjs +117 -103
  96. package/admin-dist/server/_libs/seroval-plugins.mjs +1 -58
  97. package/admin-dist/server/_libs/seroval.mjs +1 -1765
  98. package/admin-dist/server/_libs/zod.mjs +1 -1
  99. package/admin-dist/server/_ssr/CmsEmptyState-BM8DghTl.mjs +38 -0
  100. package/admin-dist/server/_ssr/{CmsPageHeader-ClNPU7Up.mjs → CmsPageHeader-BHUmrIWD.mjs} +1 -1
  101. package/admin-dist/server/_ssr/{CmsStatusBadge-CojMbrY7.mjs → CmsStatusBadge-D0Zb0oRl.mjs} +7 -7
  102. package/admin-dist/server/_ssr/{CmsSurface-Dcv440rp.mjs → CmsSurface-B2eBr-47.mjs} +1 -1
  103. package/admin-dist/server/_ssr/{CmsToolbar-BKv1nL6u.mjs → CmsToolbar-BCrwg7OL.mjs} +2 -3
  104. package/admin-dist/server/_ssr/{ContentEntryEditor-weiXSBdZ.mjs → ContentEntryEditor-Cjfm0uhr.mjs} +46 -49
  105. package/admin-dist/server/_ssr/{TaxonomyFilter-BPQ57Mwk.mjs → TaxonomyFilter-C4pD0kfM.mjs} +4 -5
  106. package/admin-dist/server/_ssr/{_contentTypeId-DyyauLOs.mjs → _contentTypeId-CiDiX-p7.mjs} +34 -43
  107. package/admin-dist/server/_ssr/{_entryId-9Cafwxmw.mjs → _entryId-9GxatOkL.mjs} +35 -47
  108. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-CC7UrHKE.mjs +4 -0
  109. package/admin-dist/server/_ssr/badge-EI998zba.mjs +39 -0
  110. package/admin-dist/server/_ssr/{command-CEf8YBxY.mjs → command-BLAWQhUw.mjs} +2 -2
  111. package/admin-dist/server/_ssr/{config.server-D7JHDcDv.mjs → config.server-BOr7Jxr4.mjs} +5 -14
  112. package/admin-dist/server/_ssr/{content-ZFWVzO25.mjs → content-BHX39L4D.mjs} +63 -63
  113. package/admin-dist/server/_ssr/content-types-DCzrBhTH.mjs +459 -0
  114. package/admin-dist/server/_ssr/{index-BlSIlH4Z.mjs → index-DwM_5VNP.mjs} +114 -30
  115. package/admin-dist/server/_ssr/index.mjs +3459 -62
  116. package/admin-dist/server/_ssr/{media-CD2_NUMw.mjs → media-CbzgTRRQ.mjs} +31 -43
  117. package/admin-dist/server/_ssr/{new._contentTypeId-dmZy6PBX.mjs → new._contentTypeId-6Ph-Gtlw.mjs} +33 -45
  118. package/admin-dist/server/_ssr/router-vd1nySeP.mjs +3041 -0
  119. package/admin-dist/server/_ssr/{scroll-area-BH_1K-WT.mjs → scroll-area--B9snFTJ.mjs} +1 -1
  120. package/admin-dist/server/_ssr/{settings-DVdsoWoh.mjs → settings-DlTO2JSj.mjs} +34 -43
  121. package/admin-dist/server/_ssr/{switch-DX_X8vZl.mjs → switch-C05NgNW0.mjs} +1 -1
  122. package/admin-dist/server/_ssr/{tabs-4FWM0sn8.mjs → tabs-DAk2J5xy.mjs} +9 -10
  123. package/admin-dist/server/_ssr/{tanstack-adapter-D3ZcKtbY.mjs → tanstack-adapter-DWbaPByn.mjs} +15 -1
  124. package/admin-dist/server/_ssr/{taxonomies-BHFfO9Yr.mjs → taxonomies-B8nqce6u.mjs} +35 -44
  125. package/admin-dist/server/_ssr/{trash-9tUB2KwI.mjs → trash-zdlZgpTo.mjs} +30 -39
  126. package/admin-dist/server/_ssr/{useBreadcrumbLabel-DVme3DSb.mjs → useBreadcrumbLabel-DpEKyG1h.mjs} +1 -1
  127. package/admin-dist/server/_ssr/{usePermissions-zAQj-ruE.mjs → usePermissions-olYRd9S9.mjs} +1 -1
  128. package/admin-dist/server/index.mjs +168 -203
  129. package/dist/cli/templates/cmsClient.d.ts +1 -1
  130. package/dist/cli/templates/cmsClient.d.ts.map +1 -1
  131. package/dist/cli/templates/cmsClient.js +30 -11
  132. package/dist/cli/templates/cmsClient.js.map +1 -1
  133. package/dist/cli/templates/cmsConfig.d.ts +1 -1
  134. package/dist/cli/templates/cmsConfig.d.ts.map +1 -1
  135. package/dist/cli/templates/cmsConfig.js +3 -3
  136. package/dist/client/admin/contentLock.d.ts +4 -4
  137. package/dist/client/admin/contentLock.d.ts.map +1 -1
  138. package/dist/client/admin/contentLock.js +1 -1
  139. package/dist/client/admin/contentLock.js.map +1 -1
  140. package/dist/client/admin/contentTypes.d.ts +328 -290
  141. package/dist/client/admin/contentTypes.d.ts.map +1 -1
  142. package/dist/client/admin/contentTypes.js +307 -9
  143. package/dist/client/admin/contentTypes.js.map +1 -1
  144. package/dist/client/admin/entries.d.ts +18 -19
  145. package/dist/client/admin/entries.d.ts.map +1 -1
  146. package/dist/client/admin/entries.js +33 -8
  147. package/dist/client/admin/entries.js.map +1 -1
  148. package/dist/client/admin/index.d.ts +694 -642
  149. package/dist/client/admin/index.d.ts.map +1 -1
  150. package/dist/client/admin/index.js +45 -5
  151. package/dist/client/admin/index.js.map +1 -1
  152. package/dist/client/admin/media.d.ts.map +1 -1
  153. package/dist/client/admin/taxonomies.d.ts.map +1 -1
  154. package/dist/client/admin/trash.d.ts +3 -4
  155. package/dist/client/admin/trash.d.ts.map +1 -1
  156. package/dist/client/admin/types.d.ts +185 -4
  157. package/dist/client/admin/types.d.ts.map +1 -1
  158. package/dist/client/admin/validators.d.ts +2009 -25
  159. package/dist/client/admin/validators.d.ts.map +1 -1
  160. package/dist/client/admin/validators.js +15 -4
  161. package/dist/client/admin/validators.js.map +1 -1
  162. package/dist/client/admin/versions.d.ts +1 -1
  163. package/dist/client/admin/versions.d.ts.map +1 -1
  164. package/dist/client/agentTools.d.ts +1 -4
  165. package/dist/client/agentTools.d.ts.map +1 -1
  166. package/dist/client/agentTools.js +9 -21
  167. package/dist/client/agentTools.js.map +1 -1
  168. package/dist/client/config.d.ts +10 -0
  169. package/dist/client/config.d.ts.map +1 -1
  170. package/dist/client/config.js +1 -0
  171. package/dist/client/config.js.map +1 -1
  172. package/dist/client/defineContent.d.ts +338 -0
  173. package/dist/client/defineContent.d.ts.map +1 -0
  174. package/dist/client/defineContent.js +368 -0
  175. package/dist/client/defineContent.js.map +1 -0
  176. package/dist/client/index.d.ts +2 -0
  177. package/dist/client/index.d.ts.map +1 -1
  178. package/dist/client/index.js +2 -0
  179. package/dist/client/index.js.map +1 -1
  180. package/dist/client/queryBuilder.d.ts +0 -15
  181. package/dist/client/queryBuilder.d.ts.map +1 -1
  182. package/dist/client/queryBuilder.js +0 -23
  183. package/dist/client/queryBuilder.js.map +1 -1
  184. package/dist/client/registry.d.ts +77 -0
  185. package/dist/client/registry.d.ts.map +1 -0
  186. package/dist/client/registry.js +95 -0
  187. package/dist/client/registry.js.map +1 -0
  188. package/dist/client/schema/defineContentType.d.ts +36 -24
  189. package/dist/client/schema/defineContentType.d.ts.map +1 -1
  190. package/dist/client/schema/defineContentType.js +176 -128
  191. package/dist/client/schema/defineContentType.js.map +1 -1
  192. package/dist/client/schema/typedClient.d.ts.map +1 -1
  193. package/dist/client/schema/typedClient.js +2 -10
  194. package/dist/client/schema/typedClient.js.map +1 -1
  195. package/dist/client/schema/types.d.ts +16 -9
  196. package/dist/client/schema/types.d.ts.map +1 -1
  197. package/dist/client/schema/types.js.map +1 -1
  198. package/dist/client/utils/toSlug.d.ts +60 -0
  199. package/dist/client/utils/toSlug.d.ts.map +1 -0
  200. package/dist/client/utils/toSlug.js +31 -0
  201. package/dist/client/utils/toSlug.js.map +1 -0
  202. package/dist/client/wrapper.d.ts +2 -2
  203. package/dist/client/wrapper.d.ts.map +1 -1
  204. package/dist/client/wrapper.js +22 -30
  205. package/dist/client/wrapper.js.map +1 -1
  206. package/dist/component/_generated/component.d.ts +24 -28
  207. package/dist/component/_generated/component.d.ts.map +1 -1
  208. package/dist/component/authorizationHooks.d.ts +1 -1
  209. package/dist/component/authorizationHooks.d.ts.map +1 -1
  210. package/dist/component/authorizationHooks.js +2 -2
  211. package/dist/component/authorizationHooks.js.map +1 -1
  212. package/dist/component/bulkOperations.d.ts.map +1 -1
  213. package/dist/component/bulkOperations.js +7 -4
  214. package/dist/component/bulkOperations.js.map +1 -1
  215. package/dist/component/contentEntries.d.ts +18 -56
  216. package/dist/component/contentEntries.d.ts.map +1 -1
  217. package/dist/component/contentEntries.js +45 -137
  218. package/dist/component/contentEntries.js.map +1 -1
  219. package/dist/component/contentEntryMutations.d.ts +14 -14
  220. package/dist/component/contentEntryMutations.d.ts.map +1 -1
  221. package/dist/component/contentEntryMutations.js +40 -43
  222. package/dist/component/contentEntryMutations.js.map +1 -1
  223. package/dist/component/contentEntryValidation.d.ts +3 -3
  224. package/dist/component/contentEntryValidation.js +6 -9
  225. package/dist/component/contentEntryValidation.js.map +1 -1
  226. package/dist/component/contentLock.d.ts +7 -7
  227. package/dist/component/contentLock.js +3 -3
  228. package/dist/component/contentLock.js.map +1 -1
  229. package/dist/component/contentTypeMigration.d.ts +1 -1
  230. package/dist/component/contentTypeMigration.js +2 -2
  231. package/dist/component/contentTypeMigration.js.map +1 -1
  232. package/dist/component/contentTypeMutations.d.ts.map +1 -1
  233. package/dist/component/contentTypeMutations.js +7 -6
  234. package/dist/component/contentTypeMutations.js.map +1 -1
  235. package/dist/component/convex.config.d.ts.map +1 -1
  236. package/dist/component/convex.config.js +1 -1
  237. package/dist/component/convex.config.js.map +1 -1
  238. package/dist/component/eventEmitter.d.ts +0 -1
  239. package/dist/component/eventEmitter.d.ts.map +1 -1
  240. package/dist/component/eventEmitter.js.map +1 -1
  241. package/dist/component/exportImport.d.ts +37 -37
  242. package/dist/component/exportImport.d.ts.map +1 -1
  243. package/dist/component/exportImport.js +34 -34
  244. package/dist/component/exportImport.js.map +1 -1
  245. package/dist/component/lib/deepReferenceResolver.d.ts +2 -2
  246. package/dist/component/lib/deepReferenceResolver.d.ts.map +1 -1
  247. package/dist/component/lib/deepReferenceResolver.js +13 -8
  248. package/dist/component/lib/deepReferenceResolver.js.map +1 -1
  249. package/dist/component/lib/ragContentChunker.d.ts +3 -3
  250. package/dist/component/lib/ragContentChunker.d.ts.map +1 -1
  251. package/dist/component/lib/ragContentChunker.js +4 -4
  252. package/dist/component/lib/ragContentChunker.js.map +1 -1
  253. package/dist/component/lib/referenceResolver.d.ts.map +1 -1
  254. package/dist/component/lib/referenceResolver.js +10 -17
  255. package/dist/component/lib/referenceResolver.js.map +1 -1
  256. package/dist/component/mediaAssetMutations.js +4 -4
  257. package/dist/component/mediaAssetMutations.js.map +1 -1
  258. package/dist/component/ragContentIndexer.d.ts +2 -2
  259. package/dist/component/ragContentIndexer.d.ts.map +1 -1
  260. package/dist/component/ragContentIndexer.js +44 -48
  261. package/dist/component/ragContentIndexer.js.map +1 -1
  262. package/dist/component/roles.d.ts +3 -3
  263. package/dist/component/scheduledPublish.d.ts +4 -4
  264. package/dist/component/scheduledPublish.js +3 -3
  265. package/dist/component/scheduledPublish.js.map +1 -1
  266. package/dist/component/schema.d.ts +18 -18
  267. package/dist/component/schema.js +5 -5
  268. package/dist/component/schema.js.map +1 -1
  269. package/dist/component/trash.d.ts +6 -9
  270. package/dist/component/trash.d.ts.map +1 -1
  271. package/dist/component/trash.js +12 -36
  272. package/dist/component/trash.js.map +1 -1
  273. package/dist/component/userContext.d.ts +1 -2
  274. package/dist/component/userContext.d.ts.map +1 -1
  275. package/dist/component/userContext.js +1 -2
  276. package/dist/component/userContext.js.map +1 -1
  277. package/dist/component/validators.d.ts +27 -33
  278. package/dist/component/validators.d.ts.map +1 -1
  279. package/dist/component/validators.js +3 -5
  280. package/dist/component/validators.js.map +1 -1
  281. package/dist/component/versionMutations.d.ts +1 -1
  282. package/dist/component/webhookTrigger.d.ts +14 -14
  283. package/dist/test.d.ts +30 -30
  284. package/dist/test.d.ts.map +1 -1
  285. package/dist/test.js +24 -24
  286. package/dist/test.js.map +1 -1
  287. package/package.json +1 -1
  288. package/admin-dist/public/assets/CmsEmptyState-Do_erIgn.js +0 -5
  289. package/admin-dist/public/assets/CmsPageHeader-qDwPGi48.js +0 -1
  290. package/admin-dist/public/assets/CmsStatusBadge-Dd9uToHE.js +0 -1
  291. package/admin-dist/public/assets/CmsToolbar-D1-Y-7SK.js +0 -1
  292. package/admin-dist/public/assets/ContentEntryEditor-CWBiIx52.js +0 -4
  293. package/admin-dist/public/assets/TaxonomyFilter-CdYQawxb.js +0 -1
  294. package/admin-dist/public/assets/_contentTypeId-D9VMP6Gs.js +0 -1
  295. package/admin-dist/public/assets/_entryId-2FlCfqE7.js +0 -1
  296. package/admin-dist/public/assets/alert-GxZx0y5c.js +0 -1
  297. package/admin-dist/public/assets/badge-BAlGIjop.js +0 -1
  298. package/admin-dist/public/assets/command-di7XCqcv.js +0 -1
  299. package/admin-dist/public/assets/content-D8zELsDG.js +0 -1
  300. package/admin-dist/public/assets/content-types-BmzD0krT.js +0 -2
  301. package/admin-dist/public/assets/globals-BvFfH-v9.css +0 -1
  302. package/admin-dist/public/assets/index-zqfj4T_v.js +0 -1
  303. package/admin-dist/public/assets/label-B6PPtKR5.js +0 -1
  304. package/admin-dist/public/assets/link-2-W2fVnVOf.js +0 -1
  305. package/admin-dist/public/assets/list-F8O0lZXC.js +0 -1
  306. package/admin-dist/public/assets/main-dZT72bAG.js +0 -97
  307. package/admin-dist/public/assets/media-CETueFbV.js +0 -1
  308. package/admin-dist/public/assets/new._contentTypeId-BV2-TyyR.js +0 -1
  309. package/admin-dist/public/assets/plus-AABQIF0N.js +0 -1
  310. package/admin-dist/public/assets/scroll-area-CDfk-zrz.js +0 -1
  311. package/admin-dist/public/assets/select-BuiHcMzS.js +0 -1
  312. package/admin-dist/public/assets/settings-DBxbYDvn.js +0 -1
  313. package/admin-dist/public/assets/switch-DiJvolcs.js +0 -1
  314. package/admin-dist/public/assets/tabs-Cgz6G_Xy.js +0 -1
  315. package/admin-dist/public/assets/tanstack-adapter-BknsSgra.js +0 -1
  316. package/admin-dist/public/assets/taxonomies-DOErsLl5.js +0 -1
  317. package/admin-dist/public/assets/textarea-CgggMxUX.js +0 -1
  318. package/admin-dist/public/assets/trash-BU4ANuaW.js +0 -1
  319. package/admin-dist/public/assets/triangle-alert-lvCbwp0s.js +0 -1
  320. package/admin-dist/public/assets/usePermissions-D7tQowaF.js +0 -1
  321. package/admin-dist/server/_libs/h3-v2.mjs +0 -277
  322. package/admin-dist/server/_ssr/CmsButton-DbzfJru_.mjs +0 -125
  323. package/admin-dist/server/_ssr/CmsEmptyState-CuvcXr3Z.mjs +0 -290
  324. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-Dk-FIYPN.mjs +0 -4
  325. package/admin-dist/server/_ssr/content-types-D25lUE-j.mjs +0 -1312
  326. package/admin-dist/server/_ssr/label-PblVvdRv.mjs +0 -22
  327. package/admin-dist/server/_ssr/router-x6Ab8T4s.mjs +0 -1622
  328. package/admin-dist/server/_ssr/select-CrfEkFJw.mjs +0 -142
  329. package/admin-dist/server/_ssr/textarea-CZVaroMc.mjs +0 -18
package/README.md CHANGED
@@ -1,86 +1,11 @@
1
1
  # Convex CMS
2
2
 
3
- > **Alpha Status (v0.0.5)** — Actively developed. APIs may change. [Report issues](https://github.com/obkaro/convex-cms/issues).
3
+ [![npm version](https://badge.fury.io/js/convex-cms.svg)](https://www.npmjs.com/package/convex-cms)
4
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
5
 
5
- A headless CMS built as a [Convex Component](https://docs.convex.dev/components) — content management that runs inside your Convex app.
6
+ > **Alpha (v0.0.7)** Actively developed. APIs may change. [Report issues](https://github.com/obkaro/convex-cms/issues).
6
7
 
7
- ## Why Convex CMS?
8
-
9
- If you're building on Convex and need content management, this is the most integrated option:
10
-
11
- - **Zero infrastructure** — Runs entirely within your Convex deployment
12
- - **True real-time** — Content updates via Convex subscriptions, not polling
13
- - **Type-safe** — Code-first schemas with full TypeScript inference
14
- - **Component isolation** — Separate database tables, versioned independently
15
- - **Agent-native** — 23 pre-built tools for AI agent integration via `@convex-dev/agent`
16
-
17
- ## Choose Your Path
18
-
19
- ### Need an Admin Interface?
20
-
21
- Use **`defineAdminAPI`** — one line creates all the backend functions for a working admin UI.
22
-
23
- ```
24
- Your App Admin UI
25
- │ │
26
- └── convex/admin.ts ────────────┘
27
- defineAdminAPI()
28
-
29
- ├── listContentTypes
30
- ├── getEntry
31
- ├── publishEntry
32
- └── ... (60+ functions across 11 domains)
33
-
34
-
35
- CMS Component
36
- ```
37
-
38
- → **[Admin UI Setup Guide](./docs/guides/admin-ui-setup.md)**
39
-
40
- ### Building Custom Content Logic?
41
-
42
- Use **`createCmsClient`** — full programmatic control with typed methods in your Convex functions.
43
-
44
- ```
45
- Your Convex Functions
46
-
47
- └── cms.contentEntries.list(ctx, { status: "published" })
48
- cms.contentTypes.create(ctx, { name: "blog", ... })
49
- cms.mediaAssets.upload(ctx, { ... })
50
-
51
-
52
- CMS Component
53
- ```
54
-
55
- → **[Getting Started Guide](./docs/guides/getting-started.md)**
56
-
57
- ### Want Full Type Safety?
58
-
59
- Use **code-first schemas** — define content types with Convex validators, get TypeScript inference.
60
-
61
- ```typescript
62
- const blogPost = defineContentType({
63
- name: "blog_post",
64
- validator: v.object({
65
- title: v.string(),
66
- content: v.string(),
67
- }),
68
- });
69
-
70
- // TypeScript knows entry.data.title is string
71
- const entry = await cms.typedContentEntries.get<"blog_post">(ctx, id);
72
- ```
73
-
74
- → **[Code-First Schema Reference](./docs/api/code-first-schema.md)**
75
-
76
- ### Need Both?
77
-
78
- **Most apps use both.** This is the typical setup:
79
-
80
- - `defineAdminAPI` powers the admin interface for content editors
81
- - `createCmsClient` gives you typed methods for custom queries on your frontend
82
-
83
- They work together through the same CMS component.
8
+ A headless CMS built as a [Convex Component](https://docs.convex.dev/components). Content management that runs inside your Convex app.
84
9
 
85
10
  ## Quick Start
86
11
 
@@ -95,14 +20,14 @@ pnpm add convex-cms
95
20
  ```typescript
96
21
  // convex/convex.config.ts
97
22
  import { defineApp } from "convex/server";
98
- import convexCms from "convex-cms/convex.config";
23
+ import cms from "convex-cms/convex.config";
99
24
 
100
25
  const app = defineApp();
101
- app.use(convexCms);
26
+ app.use(cms);
102
27
  export default app;
103
28
  ```
104
29
 
105
- ### 3. Choose Your Setup
30
+ ### 3. Initialize
106
31
 
107
32
  **For Admin UI:** Run `pnpm convex-cms init` then `pnpm convex-cms admin`
108
33
  → [Full Admin UI Setup](./docs/guides/admin-ui-setup.md)
@@ -110,35 +35,77 @@ export default app;
110
35
  **For Custom Functions:** Create a CMS client and use it in your functions
111
36
  → [Full Getting Started Guide](./docs/guides/getting-started.md)
112
37
 
113
- ## What's Included
38
+ ## Why Convex CMS?
39
+
40
+ If you're building on Convex and need content management without the overhead of all the CMS plumbing, this is the most integrated option.
41
+
42
+ ### What you get:
43
+
44
+ - **Typesafe admin API** Admin APIs exported directly from your backend for use in your React queries and mutations
45
+ - **Built in admin UI** A well designed admin UI that you can view and edit content from
46
+ - **Embeddable content manager** Ability to embed and serve the prebuilt UI as part of you React application
47
+ - **Data independence** CMS that lives in your own convex deployment, extendable and customizable with your convex functions
48
+ - **Agent-native.** Pre-built tools useful for AI agent integration with `@convex-dev/agent`
49
+
50
+ ## Features
51
+
52
+ | Feature | What it does |
53
+ |---------|--------------|
54
+ | **Code-first config** | Define content types in TypeScript with full type inference |
55
+ | **UI-defined config** | Create and modify content types through the admin interface |
56
+ | **CMS Client** | Programmatic access for custom queries and mutations |
57
+ | **Admin API** | Pre-built functions that power the admin UI |
58
+ | **CLI Admin UI** | Run for local development, content entry, and management |
59
+ | **Embedded Admin UI** | Ship the admin interface as part of your React app |
60
+
61
+ ## In Practice
62
+
63
+ **Full control over the editorial experience?**
64
+ Code-first config + CMS Client. You define the schema in TypeScript and build exactly the UI you want.
65
+
66
+ **Ship fast with a ready-made admin?**
67
+ Code-first config + Admin API + Embedded Admin UI. Type-safe schemas with a working admin interface out of the box.
68
+
69
+ **Content team needs to modify schemas without deploys?**
70
+ UI-defined config + Admin API + Embedded Admin UI. Non-developers can add fields and content types.
71
+
72
+ **Automated content pipelines?**
73
+ CMS Client + agent tools. Pre-built tools for AI-driven workflows.
74
+
75
+ *Any combination of these features works together seamlessly. Pick what fits your workflow.*
76
+
77
+
78
+ ## Batteries Included
79
+
80
+ Leverage included features or extend and customize within your own convex functions to your desire.
114
81
 
115
82
  ### Core Content
116
- - **13 field types** text, richText, number, boolean, date, datetime, select, multiSelect, reference, media, json, tags, category
117
- - **Publishing workflows** draft → scheduled → published with version history
118
- - **Content versioning** Snapshots, comparison, and rollback
119
- - **Scheduled publishing** Convex scheduler integration for future publish dates
83
+ - **13 field types.** text, richText, number, boolean, date, datetime, select, multiSelect, reference, media, json, tags, category
84
+ - **Publishing workflows.** Draft → scheduled → published with version history
85
+ - **Content versioning.** Snapshots, comparison, and rollback
86
+ - **Scheduled publishing.** Convex scheduler integration for future publish dates
120
87
 
121
88
  ### Media Management
122
- - **File uploads** Direct to Convex storage with folder organization
123
- - **Image variants** Automatic resizing and format conversion
124
- - **Metadata & tagging** Alt text, descriptions, taxonomy support
89
+ - **File uploads.** Direct to Convex storage with folder organization
90
+ - **Image variants.** Automatic resizing and format conversion
91
+ - **Metadata & tagging.** Alt text, descriptions, taxonomy support
125
92
 
126
93
  ### Organization
127
- - **Taxonomies** Hierarchical categories and flat tags
128
- - **Content locking** Prevent concurrent edit conflicts
129
- - **Soft delete & trash** Configurable retention with restore
94
+ - **Taxonomies.** Hierarchical categories and flat tags
95
+ - **Content locking.** Prevent concurrent edit conflicts
96
+ - **Soft delete & trash.** Configurable retention with restore
130
97
 
131
98
  ### Integration
132
- - **RBAC** 4 built-in roles + custom roles with fine-grained permissions
133
- - **Multi-locale** Content localization with fallback chains
134
- - **Webhooks** Event-driven integration with external systems
135
- - **Event system** All mutations emit events for async processing
136
- - **Agent tools** 23 pre-built tools with Zod schemas for AI integration
137
- - **Query builder** Fluent API for complex content queries
99
+ - **RBAC.** 4 built-in roles + custom roles with fine-grained permissions
100
+ - **Multi-locale.** Content localization with fallback chains
101
+ - **Webhooks.** Event-driven integration with external systems
102
+ - **Event system.** All mutations emit events for async processing
103
+ - **Agent tools.** 23 pre-built tools with Zod schemas for AI integration
104
+ - **Query builder.** Fluent API for complex content queries
138
105
 
139
106
  ### Admin UI
140
- - **Pre-built React interface** CLI mode for development, embeddable for production
141
- - **Visual content editing** Rich text, media picker, reference selector
107
+ - **Pre-built React interface.** CLI mode for development, embeddable for production
108
+ - **Visual content editing.** Rich text, media picker, reference selector
142
109
 
143
110
  ## Admin UI Modes
144
111
 
@@ -42,13 +42,13 @@ export function BreakingChangesWarningDialog({
42
42
  }
43
43
  >
44
44
  <div className="space-y-4">
45
- <div className="flex items-start gap-3 rounded-lg border border-amber-200 bg-amber-50 p-3">
46
- <AlertTriangle className="mt-0.5 size-5 shrink-0 text-amber-600" />
45
+ <div className="diff-modified flex items-start gap-3 rounded-lg border p-3">
46
+ <AlertTriangle className="mt-0.5 size-5 shrink-0 text-diff-modified" />
47
47
  <div className="space-y-1">
48
- <p className="text-sm font-medium text-amber-800">
48
+ <p className="text-sm font-medium text-diff-modified">
49
49
  These changes may affect existing content
50
50
  </p>
51
- <p className="text-sm text-amber-700">
51
+ <p className="text-sm text-diff-modified-foreground">
52
52
  The following changes could cause data loss or validation errors for existing entries.
53
53
  Review carefully before proceeding.
54
54
  </p>
@@ -65,7 +65,7 @@ export function BreakingChangesWarningDialog({
65
65
  key={index}
66
66
  className="flex items-start gap-2 rounded-md border bg-muted/30 px-3 py-2 text-sm"
67
67
  >
68
- <span className="mt-0.5 size-1.5 shrink-0 rounded-full bg-amber-500" />
68
+ <span className="mt-0.5 size-1.5 shrink-0 rounded-full bg-warning" />
69
69
  <span className="text-muted-foreground">{change}</span>
70
70
  </li>
71
71
  ))}
@@ -102,7 +102,7 @@ export function BulkOperationModal({
102
102
  <div className="space-y-4">
103
103
  {result.failed === 0 ? (
104
104
  <div className="flex flex-col items-center gap-3 py-4 text-center">
105
- <div className="flex size-12 items-center justify-center rounded-full bg-emerald-100 text-emerald-600">
105
+ <div className="flex size-12 items-center justify-center rounded-full bg-diff-added-bg text-diff-added">
106
106
  <CheckCircle className="size-6" />
107
107
  </div>
108
108
  <p className="text-sm text-muted-foreground">
@@ -116,33 +116,33 @@ export function BulkOperationModal({
116
116
  ) : (
117
117
  <div className="space-y-4">
118
118
  <div className="grid grid-cols-2 gap-4">
119
- <div className="rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-center">
120
- <p className="text-2xl font-semibold text-emerald-700">
119
+ <div className="diff-added rounded-lg border p-3 text-center">
120
+ <p className="text-2xl font-semibold text-diff-added">
121
121
  {result.succeeded}
122
122
  </p>
123
- <p className="text-xs text-emerald-600">Succeeded</p>
123
+ <p className="text-xs text-diff-added-foreground">Succeeded</p>
124
124
  </div>
125
- <div className="rounded-lg border border-red-200 bg-red-50 p-3 text-center">
126
- <p className="text-2xl font-semibold text-red-700">
125
+ <div className="diff-removed rounded-lg border p-3 text-center">
126
+ <p className="text-2xl font-semibold text-diff-removed">
127
127
  {result.failed}
128
128
  </p>
129
- <p className="text-xs text-red-600">Failed</p>
129
+ <p className="text-xs text-diff-removed-foreground">Failed</p>
130
130
  </div>
131
131
  </div>
132
132
 
133
133
  {result.errors && result.errors.length > 0 && (
134
- <div className="rounded-lg border border-amber-200 bg-amber-50 p-3">
135
- <p className="mb-2 text-sm font-medium text-amber-800">
134
+ <div className="diff-modified rounded-lg border p-3">
135
+ <p className="mb-2 text-sm font-medium text-diff-modified">
136
136
  Errors:
137
137
  </p>
138
- <ul className="space-y-1 text-xs text-amber-700">
138
+ <ul className="space-y-1 text-xs text-diff-modified-foreground">
139
139
  {result.errors.slice(0, 5).map((error, index) => (
140
140
  <li key={index} className="truncate">
141
141
  • {error}
142
142
  </li>
143
143
  ))}
144
144
  {result.errors.length > 5 && (
145
- <li className="text-amber-600">
145
+ <li className="text-diff-modified-foreground/80">
146
146
  ...and {result.errors.length - 5} more errors
147
147
  </li>
148
148
  )}
@@ -163,9 +163,9 @@ export function BulkOperationModal({
163
163
  </p>
164
164
  <p className="text-sm text-muted-foreground">{config.description}</p>
165
165
  {config.warning && (
166
- <div className="flex items-start gap-2 rounded-lg border border-amber-200 bg-amber-50 p-3">
167
- <AlertTriangle className="mt-0.5 size-4 shrink-0 text-amber-600" />
168
- <p className="text-sm text-amber-800">
166
+ <div className="diff-modified flex items-start gap-2 rounded-lg border p-3">
167
+ <AlertTriangle className="mt-0.5 size-4 shrink-0 text-diff-modified" />
168
+ <p className="text-sm">
169
169
  <span className="font-medium">Note:</span> {config.warning}
170
170
  </p>
171
171
  </div>
@@ -95,7 +95,7 @@ export interface ContentType {
95
95
 
96
96
  export interface ContentEntry {
97
97
  _id: string
98
- contentTypeId: string
98
+ contentTypeName: string
99
99
  slug: string
100
100
  status: 'draft' | 'published' | 'scheduled' | 'archived'
101
101
  data: Record<string, unknown>
@@ -645,7 +645,7 @@ export function ContentEntryEditor({
645
645
  })) as ContentEntry
646
646
  } else {
647
647
  savedEntry = (await createEntry({
648
- contentTypeId: contentType._id,
648
+ contentTypeName: contentType.name,
649
649
  data: dataForBackend,
650
650
  })) as ContentEntry
651
651
  }
@@ -725,8 +725,8 @@ export function ContentEntryEditor({
725
725
  className={cn(
726
726
  'flex items-center gap-1.5 text-sm',
727
727
  autosaveStatus === 'saving' && 'text-muted-foreground',
728
- autosaveStatus === 'saved' && 'text-emerald-600',
729
- autosaveStatus === 'error' && 'text-red-600'
728
+ autosaveStatus === 'saved' && 'text-success',
729
+ autosaveStatus === 'error' && 'text-destructive'
730
730
  )}
731
731
  data-testid="autosave-status"
732
732
  >
@@ -755,7 +755,7 @@ export function ContentEntryEditor({
755
755
  )}
756
756
 
757
757
  {isDirty && (
758
- <span className="text-sm text-amber-600">Unsaved changes</span>
758
+ <span className="text-sm text-warning">Unsaved changes</span>
759
759
  )}
760
760
  </div>
761
761
  </div>
@@ -763,7 +763,7 @@ export function ContentEntryEditor({
763
763
  {/* Success/Error Messages */}
764
764
  {saveSuccess && (
765
765
  <div
766
- className="flex items-center gap-2 rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800"
766
+ className="diff-added flex items-center gap-2 rounded-lg border px-4 py-3 text-sm"
767
767
  role="status"
768
768
  >
769
769
  <CheckCircle className="size-4" />
@@ -773,7 +773,7 @@ export function ContentEntryEditor({
773
773
 
774
774
  {(submitError || publishError) && (
775
775
  <div
776
- className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800"
776
+ className="diff-removed rounded-lg border px-4 py-3 text-sm"
777
777
  role="alert"
778
778
  >
779
779
  <span className="font-medium">Error:</span> {submitError || publishError}
@@ -886,7 +886,7 @@ export function ContentEntryEditor({
886
886
  entry.status === 'scheduled' &&
887
887
  entry.scheduledPublishAt && (
888
888
  <span
889
- className="flex items-center gap-1 text-xs text-blue-600"
889
+ className="flex items-center gap-1 text-xs text-info"
890
890
  data-testid="scheduled-time"
891
891
  >
892
892
  <Clock className="size-3" />
@@ -32,6 +32,7 @@ import {
32
32
  List,
33
33
  Tag,
34
34
  FolderOpen,
35
+ Code2,
35
36
  } from "lucide-react";
36
37
  import { cn } from "~/lib/cn";
37
38
  import { BreakingChangesWarningDialog } from "./BreakingChangesWarningDialog";
@@ -139,7 +140,7 @@ interface ContentTypeFormModalProps {
139
140
  onClose: () => void;
140
141
  onCreated?: (contentType: unknown) => void;
141
142
  onUpdated?: (contentType: unknown) => void;
142
- contentType?: ContentType | null;
143
+ contentType?: (ContentType & { source?: "code" | "database" }) | null;
143
144
  }
144
145
 
145
146
  function generateMachineName(displayName: string): string {
@@ -164,6 +165,8 @@ export function ContentTypeFormModal({
164
165
  contentType,
165
166
  }: ContentTypeFormModalProps) {
166
167
  const isEditing = !!contentType;
168
+ const isCodeDefined = contentType?.source === "code";
169
+ const isReadOnly = isCodeDefined;
167
170
 
168
171
  const [displayName, setDisplayName] = useState("");
169
172
  const [machineName, setMachineName] = useState("");
@@ -512,34 +515,62 @@ export function ContentTypeFormModal({
512
515
  <CmsDialog
513
516
  open={isOpen}
514
517
  onOpenChange={(open) => !open && handleClose()}
515
- title={isEditing ? "Edit Content Type" : "Create Content Type"}
518
+ title={
519
+ isCodeDefined
520
+ ? "View Content Type"
521
+ : isEditing
522
+ ? "Edit Content Type"
523
+ : "Create Content Type"
524
+ }
516
525
  size="2xl"
517
526
  footer={
518
- <>
519
- <CmsButton
520
- variant="outline"
521
- onClick={handleClose}
522
- disabled={isSubmitting}
523
- >
524
- Cancel
525
- </CmsButton>
526
- <CmsButton
527
- variant="primary"
528
- onClick={handleSubmit}
529
- disabled={validationErrors.length > 0}
530
- loading={isSubmitting}
531
- data-testid={
532
- isEditing
533
- ? "update-content-type-submit"
534
- : "create-content-type-submit"
535
- }
536
- >
537
- {isEditing ? "Save Changes" : "Create Content Type"}
527
+ isReadOnly ? (
528
+ <CmsButton variant="outline" onClick={handleClose}>
529
+ Close
538
530
  </CmsButton>
539
- </>
531
+ ) : (
532
+ <>
533
+ <CmsButton
534
+ variant="outline"
535
+ onClick={handleClose}
536
+ disabled={isSubmitting}
537
+ >
538
+ Cancel
539
+ </CmsButton>
540
+ <CmsButton
541
+ variant="primary"
542
+ onClick={handleSubmit}
543
+ disabled={validationErrors.length > 0}
544
+ loading={isSubmitting}
545
+ data-testid={
546
+ isEditing
547
+ ? "update-content-type-submit"
548
+ : "create-content-type-submit"
549
+ }
550
+ >
551
+ {isEditing ? "Save Changes" : "Create Content Type"}
552
+ </CmsButton>
553
+ </>
554
+ )
540
555
  }
541
556
  >
542
557
  <form onSubmit={handleSubmit} className="space-y-6">
558
+ {isCodeDefined && (
559
+ <div className="flex items-start gap-3 rounded-lg border border-violet-200 bg-violet-50 p-3">
560
+ <Code2 className="mt-0.5 size-5 shrink-0 text-violet-600" />
561
+ <div className="space-y-1">
562
+ <p className="text-sm font-medium text-violet-900">
563
+ Managed by Code
564
+ </p>
565
+ <p className="text-sm text-violet-700">
566
+ This content type is defined in your codebase and cannot be
567
+ edited through the admin interface. To make changes, update
568
+ the definition in your code.
569
+ </p>
570
+ </div>
571
+ </div>
572
+ )}
573
+
543
574
  {/* Basic Info Section */}
544
575
  <div className="space-y-4">
545
576
  <h4 className="text-sm font-semibold text-foreground">
@@ -555,8 +586,8 @@ export function ContentTypeFormModal({
555
586
  value={displayName}
556
587
  onChange={(e) => handleDisplayNameChange(e.target.value)}
557
588
  placeholder="e.g., Blog Post"
558
- disabled={isSubmitting}
559
- autoFocus
589
+ disabled={isSubmitting || isReadOnly}
590
+ autoFocus={!isReadOnly}
560
591
  data-testid="display-name-input"
561
592
  />
562
593
  </div>
@@ -570,7 +601,7 @@ export function ContentTypeFormModal({
570
601
  value={machineName}
571
602
  onChange={(e) => handleMachineNameChange(e.target.value)}
572
603
  placeholder="e.g., blog_post"
573
- disabled={isSubmitting || isEditing}
604
+ disabled={isSubmitting || isEditing || isReadOnly}
574
605
  className={cn(
575
606
  !isValidMachineName(machineName) &&
576
607
  machineName &&
@@ -592,7 +623,7 @@ export function ContentTypeFormModal({
592
623
  value={description}
593
624
  onChange={(e) => setDescription(e.target.value)}
594
625
  placeholder="Optional description of this content type"
595
- disabled={isSubmitting}
626
+ disabled={isSubmitting || isReadOnly}
596
627
  rows={2}
597
628
  />
598
629
  </div>
@@ -602,7 +633,7 @@ export function ContentTypeFormModal({
602
633
  id="singleton"
603
634
  checked={singleton}
604
635
  onCheckedChange={(checked) => setSingleton(checked as boolean)}
605
- disabled={isSubmitting}
636
+ disabled={isSubmitting || isReadOnly}
606
637
  />
607
638
  <Label htmlFor="singleton" className="cursor-pointer">
608
639
  Singleton (only one entry allowed)
@@ -614,17 +645,19 @@ export function ContentTypeFormModal({
614
645
  <div className="space-y-4">
615
646
  <div className="flex items-center justify-between">
616
647
  <h4 className="text-sm font-semibold text-foreground">Fields</h4>
617
- <CmsButton
618
- type="button"
619
- variant="secondary"
620
- size="sm"
621
- onClick={addField}
622
- disabled={isSubmitting}
623
- data-testid="add-field-button"
624
- >
625
- <Plus className="size-3.5" />
626
- Add Field
627
- </CmsButton>
648
+ {!isReadOnly && (
649
+ <CmsButton
650
+ type="button"
651
+ variant="secondary"
652
+ size="sm"
653
+ onClick={addField}
654
+ disabled={isSubmitting}
655
+ data-testid="add-field-button"
656
+ >
657
+ <Plus className="size-3.5" />
658
+ Add Field
659
+ </CmsButton>
660
+ )}
628
661
  </div>
629
662
 
630
663
  <div className="space-y-2">
@@ -632,41 +665,46 @@ export function ContentTypeFormModal({
632
665
  <div
633
666
  key={index}
634
667
  className={cn(
635
- "flex cursor-pointer items-center gap-2 rounded-lg border p-2 transition-colors hover:bg-muted/50",
668
+ "flex items-center gap-2 rounded-lg border p-2 transition-colors",
669
+ !isReadOnly && "cursor-pointer hover:bg-muted/50",
636
670
  activeFieldIndex === index && "border-primary bg-primary/5",
637
671
  )}
638
672
  onClick={() => {
639
- setActiveFieldIndex(index);
640
- setShowFieldEditor(true);
673
+ if (!isReadOnly) {
674
+ setActiveFieldIndex(index);
675
+ setShowFieldEditor(true);
676
+ }
641
677
  }}
642
678
  data-testid={`field-item-${index}`}
643
679
  >
644
- <div className="flex flex-col gap-0.5">
645
- {index > 0 && (
646
- <button
647
- type="button"
648
- onClick={(e) => {
649
- e.stopPropagation();
650
- moveField(index, index - 1);
651
- }}
652
- className="rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
653
- >
654
- <ChevronUp className="size-3" />
655
- </button>
656
- )}
657
- {index < fields.length - 1 && (
658
- <button
659
- type="button"
660
- onClick={(e) => {
661
- e.stopPropagation();
662
- moveField(index, index + 1);
663
- }}
664
- className="rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
665
- >
666
- <ChevronDown className="size-3" />
667
- </button>
668
- )}
669
- </div>
680
+ {!isReadOnly && (
681
+ <div className="flex flex-col gap-0.5">
682
+ {index > 0 && (
683
+ <button
684
+ type="button"
685
+ onClick={(e) => {
686
+ e.stopPropagation();
687
+ moveField(index, index - 1);
688
+ }}
689
+ className="rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
690
+ >
691
+ <ChevronUp className="size-3" />
692
+ </button>
693
+ )}
694
+ {index < fields.length - 1 && (
695
+ <button
696
+ type="button"
697
+ onClick={(e) => {
698
+ e.stopPropagation();
699
+ moveField(index, index + 1);
700
+ }}
701
+ className="rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
702
+ >
703
+ <ChevronDown className="size-3" />
704
+ </button>
705
+ )}
706
+ </div>
707
+ )}
670
708
 
671
709
  <div className="flex size-8 items-center justify-center rounded bg-muted text-muted-foreground">
672
710
  {FIELD_TYPE_INFO[field.type].icon}
@@ -682,23 +720,25 @@ export function ContentTypeFormModal({
682
720
  </p>
683
721
  </div>
684
722
 
685
- <button
686
- type="button"
687
- onClick={(e) => {
688
- e.stopPropagation();
689
- removeField(index);
690
- }}
691
- disabled={isSubmitting || fields.length === 1}
692
- className="rounded p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
693
- >
694
- <X className="size-4" />
695
- </button>
723
+ {!isReadOnly && (
724
+ <button
725
+ type="button"
726
+ onClick={(e) => {
727
+ e.stopPropagation();
728
+ removeField(index);
729
+ }}
730
+ disabled={isSubmitting || fields.length === 1}
731
+ className="rounded p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
732
+ >
733
+ <X className="size-4" />
734
+ </button>
735
+ )}
696
736
  </div>
697
737
  ))}
698
738
  </div>
699
739
 
700
- {/* Field Editor Panel */}
701
- {showFieldEditor && activeField && activeFieldIndex !== null && (
740
+ {/* Field Editor Panel - hidden in read-only mode */}
741
+ {!isReadOnly && showFieldEditor && activeField && activeFieldIndex !== null && (
702
742
  <div
703
743
  className="rounded-lg border bg-muted/30 p-4"
704
744
  data-testid="field-editor"
@@ -900,7 +940,7 @@ export function ContentTypeFormModal({
900
940
 
901
941
  {submitError && (
902
942
  <div
903
- className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800"
943
+ className="diff-removed rounded-lg border px-3 py-2 text-sm"
904
944
  role="alert"
905
945
  data-testid="submit-error"
906
946
  >