@prenta/core 0.118.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 (1408) hide show
  1. package/CHANGELOG.md +2974 -0
  2. package/LICENSE +21 -0
  3. package/README.md +33 -0
  4. package/dist/a11y/index.d.ts +25 -0
  5. package/dist/a11y/index.d.ts.map +1 -0
  6. package/dist/a11y/index.js +91 -0
  7. package/dist/a11y/index.js.map +1 -0
  8. package/dist/actions.d.ts +130 -0
  9. package/dist/actions.d.ts.map +1 -0
  10. package/dist/actions.js +1324 -0
  11. package/dist/actions.js.map +1 -0
  12. package/dist/ai/brand-voice-alignment.d.ts +17 -0
  13. package/dist/ai/brand-voice-alignment.d.ts.map +1 -0
  14. package/dist/ai/brand-voice-alignment.js +40 -0
  15. package/dist/ai/brand-voice-alignment.js.map +1 -0
  16. package/dist/ai/brand-voice-analyze.d.ts +71 -0
  17. package/dist/ai/brand-voice-analyze.d.ts.map +1 -0
  18. package/dist/ai/brand-voice-analyze.js +151 -0
  19. package/dist/ai/brand-voice-analyze.js.map +1 -0
  20. package/dist/ai/brand-voice-document.d.ts +56 -0
  21. package/dist/ai/brand-voice-document.d.ts.map +1 -0
  22. package/dist/ai/brand-voice-document.js +335 -0
  23. package/dist/ai/brand-voice-document.js.map +1 -0
  24. package/dist/ai/brand-voice-fix.d.ts +26 -0
  25. package/dist/ai/brand-voice-fix.d.ts.map +1 -0
  26. package/dist/ai/brand-voice-fix.js +97 -0
  27. package/dist/ai/brand-voice-fix.js.map +1 -0
  28. package/dist/ai/brand-voice-save.d.ts +11 -0
  29. package/dist/ai/brand-voice-save.d.ts.map +1 -0
  30. package/dist/ai/brand-voice-save.js +51 -0
  31. package/dist/ai/brand-voice-save.js.map +1 -0
  32. package/dist/ai/brand-voice-sources.d.ts +16 -0
  33. package/dist/ai/brand-voice-sources.d.ts.map +1 -0
  34. package/dist/ai/brand-voice-sources.js +106 -0
  35. package/dist/ai/brand-voice-sources.js.map +1 -0
  36. package/dist/ai/brand-voice-structured.d.ts +87 -0
  37. package/dist/ai/brand-voice-structured.d.ts.map +1 -0
  38. package/dist/ai/brand-voice-structured.js +438 -0
  39. package/dist/ai/brand-voice-structured.js.map +1 -0
  40. package/dist/ai/brand-voice.d.ts +40 -0
  41. package/dist/ai/brand-voice.d.ts.map +1 -0
  42. package/dist/ai/brand-voice.js +103 -0
  43. package/dist/ai/brand-voice.js.map +1 -0
  44. package/dist/ai/catalog.d.ts +35 -0
  45. package/dist/ai/catalog.d.ts.map +1 -0
  46. package/dist/ai/catalog.js +370 -0
  47. package/dist/ai/catalog.js.map +1 -0
  48. package/dist/ai/config-store.d.ts +55 -0
  49. package/dist/ai/config-store.d.ts.map +1 -0
  50. package/dist/ai/config-store.js +177 -0
  51. package/dist/ai/config-store.js.map +1 -0
  52. package/dist/ai/content-writing-generate.d.ts +19 -0
  53. package/dist/ai/content-writing-generate.d.ts.map +1 -0
  54. package/dist/ai/content-writing-generate.js +64 -0
  55. package/dist/ai/content-writing-generate.js.map +1 -0
  56. package/dist/ai/feature-defaults.d.ts +20 -0
  57. package/dist/ai/feature-defaults.d.ts.map +1 -0
  58. package/dist/ai/feature-defaults.js +104 -0
  59. package/dist/ai/feature-defaults.js.map +1 -0
  60. package/dist/ai/index.d.ts +24 -0
  61. package/dist/ai/index.d.ts.map +1 -0
  62. package/dist/ai/index.js +24 -0
  63. package/dist/ai/index.js.map +1 -0
  64. package/dist/ai/models.d.ts +37 -0
  65. package/dist/ai/models.d.ts.map +1 -0
  66. package/dist/ai/models.js +126 -0
  67. package/dist/ai/models.js.map +1 -0
  68. package/dist/ai/providers/anthropic.d.ts +10 -0
  69. package/dist/ai/providers/anthropic.d.ts.map +1 -0
  70. package/dist/ai/providers/anthropic.js +63 -0
  71. package/dist/ai/providers/anthropic.js.map +1 -0
  72. package/dist/ai/providers/custom.d.ts +12 -0
  73. package/dist/ai/providers/custom.d.ts.map +1 -0
  74. package/dist/ai/providers/custom.js +76 -0
  75. package/dist/ai/providers/custom.js.map +1 -0
  76. package/dist/ai/providers/http.d.ts +66 -0
  77. package/dist/ai/providers/http.d.ts.map +1 -0
  78. package/dist/ai/providers/http.js +185 -0
  79. package/dist/ai/providers/http.js.map +1 -0
  80. package/dist/ai/providers/index.d.ts +18 -0
  81. package/dist/ai/providers/index.d.ts.map +1 -0
  82. package/dist/ai/providers/index.js +28 -0
  83. package/dist/ai/providers/index.js.map +1 -0
  84. package/dist/ai/providers/openai.d.ts +11 -0
  85. package/dist/ai/providers/openai.d.ts.map +1 -0
  86. package/dist/ai/providers/openai.js +44 -0
  87. package/dist/ai/providers/openai.js.map +1 -0
  88. package/dist/ai/providers/xai.d.ts +10 -0
  89. package/dist/ai/providers/xai.d.ts.map +1 -0
  90. package/dist/ai/providers/xai.js +38 -0
  91. package/dist/ai/providers/xai.js.map +1 -0
  92. package/dist/ai/resolve.d.ts +41 -0
  93. package/dist/ai/resolve.d.ts.map +1 -0
  94. package/dist/ai/resolve.js +143 -0
  95. package/dist/ai/resolve.js.map +1 -0
  96. package/dist/ai/runtime.d.ts +88 -0
  97. package/dist/ai/runtime.d.ts.map +1 -0
  98. package/dist/ai/runtime.js +192 -0
  99. package/dist/ai/runtime.js.map +1 -0
  100. package/dist/ai/sampling.d.ts +17 -0
  101. package/dist/ai/sampling.d.ts.map +1 -0
  102. package/dist/ai/sampling.js +51 -0
  103. package/dist/ai/sampling.js.map +1 -0
  104. package/dist/ai/types.d.ts +193 -0
  105. package/dist/ai/types.d.ts.map +1 -0
  106. package/dist/ai/types.js +11 -0
  107. package/dist/ai/types.js.map +1 -0
  108. package/dist/ai/usage.d.ts +18 -0
  109. package/dist/ai/usage.d.ts.map +1 -0
  110. package/dist/ai/usage.js +70 -0
  111. package/dist/ai/usage.js.map +1 -0
  112. package/dist/api/collection-introspection.d.ts +46 -0
  113. package/dist/api/collection-introspection.d.ts.map +1 -0
  114. package/dist/api/collection-introspection.js +82 -0
  115. package/dist/api/collection-introspection.js.map +1 -0
  116. package/dist/api/guarded-db.d.ts +14 -0
  117. package/dist/api/guarded-db.d.ts.map +1 -0
  118. package/dist/api/guarded-db.js +62 -0
  119. package/dist/api/guarded-db.js.map +1 -0
  120. package/dist/api/handler-factory.d.ts +7 -0
  121. package/dist/api/handler-factory.d.ts.map +1 -0
  122. package/dist/api/handler-factory.js +380 -0
  123. package/dist/api/handler-factory.js.map +1 -0
  124. package/dist/api/handlers.d.ts +8 -0
  125. package/dist/api/handlers.d.ts.map +1 -0
  126. package/dist/api/handlers.js +62 -0
  127. package/dist/api/handlers.js.map +1 -0
  128. package/dist/api/index.d.ts +8 -0
  129. package/dist/api/index.d.ts.map +1 -0
  130. package/dist/api/index.js +8 -0
  131. package/dist/api/index.js.map +1 -0
  132. package/dist/api/openapi.d.ts +3 -0
  133. package/dist/api/openapi.d.ts.map +1 -0
  134. package/dist/api/openapi.js +539 -0
  135. package/dist/api/openapi.js.map +1 -0
  136. package/dist/api/route-helpers.d.ts +369 -0
  137. package/dist/api/route-helpers.d.ts.map +1 -0
  138. package/dist/api/route-helpers.js +1574 -0
  139. package/dist/api/route-helpers.js.map +1 -0
  140. package/dist/api/router.d.ts +21 -0
  141. package/dist/api/router.d.ts.map +1 -0
  142. package/dist/api/router.js +75 -0
  143. package/dist/api/router.js.map +1 -0
  144. package/dist/api/routes/ai-seo.d.ts +3 -0
  145. package/dist/api/routes/ai-seo.d.ts.map +1 -0
  146. package/dist/api/routes/ai-seo.js +482 -0
  147. package/dist/api/routes/ai-seo.js.map +1 -0
  148. package/dist/api/routes/ai-settings.d.ts +3 -0
  149. package/dist/api/routes/ai-settings.d.ts.map +1 -0
  150. package/dist/api/routes/ai-settings.js +1078 -0
  151. package/dist/api/routes/ai-settings.js.map +1 -0
  152. package/dist/api/routes/auth.d.ts +3 -0
  153. package/dist/api/routes/auth.d.ts.map +1 -0
  154. package/dist/api/routes/auth.js +877 -0
  155. package/dist/api/routes/auth.js.map +1 -0
  156. package/dist/api/routes/collab.d.ts +3 -0
  157. package/dist/api/routes/collab.d.ts.map +1 -0
  158. package/dist/api/routes/collab.js +426 -0
  159. package/dist/api/routes/collab.js.map +1 -0
  160. package/dist/api/routes/dashboard.d.ts +7 -0
  161. package/dist/api/routes/dashboard.d.ts.map +1 -0
  162. package/dist/api/routes/dashboard.js +540 -0
  163. package/dist/api/routes/dashboard.js.map +1 -0
  164. package/dist/api/routes/documents.d.ts +11 -0
  165. package/dist/api/routes/documents.d.ts.map +1 -0
  166. package/dist/api/routes/documents.js +340 -0
  167. package/dist/api/routes/documents.js.map +1 -0
  168. package/dist/api/routes/email-settings.d.ts +3 -0
  169. package/dist/api/routes/email-settings.d.ts.map +1 -0
  170. package/dist/api/routes/email-settings.js +248 -0
  171. package/dist/api/routes/email-settings.js.map +1 -0
  172. package/dist/api/routes/folders.d.ts +3 -0
  173. package/dist/api/routes/folders.d.ts.map +1 -0
  174. package/dist/api/routes/folders.js +195 -0
  175. package/dist/api/routes/folders.js.map +1 -0
  176. package/dist/api/routes/forms.d.ts +3 -0
  177. package/dist/api/routes/forms.d.ts.map +1 -0
  178. package/dist/api/routes/forms.js +776 -0
  179. package/dist/api/routes/forms.js.map +1 -0
  180. package/dist/api/routes/globals.d.ts +3 -0
  181. package/dist/api/routes/globals.d.ts.map +1 -0
  182. package/dist/api/routes/globals.js +157 -0
  183. package/dist/api/routes/globals.js.map +1 -0
  184. package/dist/api/routes/media.d.ts +3 -0
  185. package/dist/api/routes/media.d.ts.map +1 -0
  186. package/dist/api/routes/media.js +807 -0
  187. package/dist/api/routes/media.js.map +1 -0
  188. package/dist/api/routes/meta.d.ts +3 -0
  189. package/dist/api/routes/meta.d.ts.map +1 -0
  190. package/dist/api/routes/meta.js +97 -0
  191. package/dist/api/routes/meta.js.map +1 -0
  192. package/dist/api/routes/navigations.d.ts +7 -0
  193. package/dist/api/routes/navigations.d.ts.map +1 -0
  194. package/dist/api/routes/navigations.js +81 -0
  195. package/dist/api/routes/navigations.js.map +1 -0
  196. package/dist/api/routes/page-templates.d.ts +3 -0
  197. package/dist/api/routes/page-templates.d.ts.map +1 -0
  198. package/dist/api/routes/page-templates.js +265 -0
  199. package/dist/api/routes/page-templates.js.map +1 -0
  200. package/dist/api/routes/presence.d.ts +3 -0
  201. package/dist/api/routes/presence.d.ts.map +1 -0
  202. package/dist/api/routes/presence.js +35 -0
  203. package/dist/api/routes/presence.js.map +1 -0
  204. package/dist/api/routes/preview.d.ts +3 -0
  205. package/dist/api/routes/preview.d.ts.map +1 -0
  206. package/dist/api/routes/preview.js +111 -0
  207. package/dist/api/routes/preview.js.map +1 -0
  208. package/dist/api/routes/redirects.d.ts +3 -0
  209. package/dist/api/routes/redirects.d.ts.map +1 -0
  210. package/dist/api/routes/redirects.js +937 -0
  211. package/dist/api/routes/redirects.js.map +1 -0
  212. package/dist/api/routes/saved-sections.d.ts +3 -0
  213. package/dist/api/routes/saved-sections.d.ts.map +1 -0
  214. package/dist/api/routes/saved-sections.js +1071 -0
  215. package/dist/api/routes/saved-sections.js.map +1 -0
  216. package/dist/api/routes/scheduling.d.ts +3 -0
  217. package/dist/api/routes/scheduling.d.ts.map +1 -0
  218. package/dist/api/routes/scheduling.js +391 -0
  219. package/dist/api/routes/scheduling.js.map +1 -0
  220. package/dist/api/routes/sections.d.ts +3 -0
  221. package/dist/api/routes/sections.d.ts.map +1 -0
  222. package/dist/api/routes/sections.js +512 -0
  223. package/dist/api/routes/sections.js.map +1 -0
  224. package/dist/api/routes/security-center.d.ts +3 -0
  225. package/dist/api/routes/security-center.d.ts.map +1 -0
  226. package/dist/api/routes/security-center.js +71 -0
  227. package/dist/api/routes/security-center.js.map +1 -0
  228. package/dist/api/routes/seo-public.d.ts +14 -0
  229. package/dist/api/routes/seo-public.d.ts.map +1 -0
  230. package/dist/api/routes/seo-public.js +1366 -0
  231. package/dist/api/routes/seo-public.js.map +1 -0
  232. package/dist/api/routes/seo.d.ts +8 -0
  233. package/dist/api/routes/seo.d.ts.map +1 -0
  234. package/dist/api/routes/seo.js +2256 -0
  235. package/dist/api/routes/seo.js.map +1 -0
  236. package/dist/api/routes/system.d.ts +3 -0
  237. package/dist/api/routes/system.d.ts.map +1 -0
  238. package/dist/api/routes/system.js +353 -0
  239. package/dist/api/routes/system.js.map +1 -0
  240. package/dist/api/routes/url-resolution.d.ts +3 -0
  241. package/dist/api/routes/url-resolution.d.ts.map +1 -0
  242. package/dist/api/routes/url-resolution.js +522 -0
  243. package/dist/api/routes/url-resolution.js.map +1 -0
  244. package/dist/api/routes/users.d.ts +3 -0
  245. package/dist/api/routes/users.d.ts.map +1 -0
  246. package/dist/api/routes/users.js +1177 -0
  247. package/dist/api/routes/users.js.map +1 -0
  248. package/dist/api/routes/webhooks.d.ts +3 -0
  249. package/dist/api/routes/webhooks.d.ts.map +1 -0
  250. package/dist/api/routes/webhooks.js +422 -0
  251. package/dist/api/routes/webhooks.js.map +1 -0
  252. package/dist/appearance/color.d.ts +24 -0
  253. package/dist/appearance/color.d.ts.map +1 -0
  254. package/dist/appearance/color.js +90 -0
  255. package/dist/appearance/color.js.map +1 -0
  256. package/dist/appearance/favicon-icon.d.ts +12 -0
  257. package/dist/appearance/favicon-icon.d.ts.map +1 -0
  258. package/dist/appearance/favicon-icon.js +58 -0
  259. package/dist/appearance/favicon-icon.js.map +1 -0
  260. package/dist/appearance/favicon-paths.d.ts +6 -0
  261. package/dist/appearance/favicon-paths.d.ts.map +1 -0
  262. package/dist/appearance/favicon-paths.js +6 -0
  263. package/dist/appearance/favicon-paths.js.map +1 -0
  264. package/dist/appearance/index.d.ts +15 -0
  265. package/dist/appearance/index.d.ts.map +1 -0
  266. package/dist/appearance/index.js +7 -0
  267. package/dist/appearance/index.js.map +1 -0
  268. package/dist/appearance/logo-shape.d.ts +26 -0
  269. package/dist/appearance/logo-shape.d.ts.map +1 -0
  270. package/dist/appearance/logo-shape.js +62 -0
  271. package/dist/appearance/logo-shape.js.map +1 -0
  272. package/dist/appearance/resolve.d.ts +52 -0
  273. package/dist/appearance/resolve.d.ts.map +1 -0
  274. package/dist/appearance/resolve.js +219 -0
  275. package/dist/appearance/resolve.js.map +1 -0
  276. package/dist/appearance/theme-tokens.d.ts +49 -0
  277. package/dist/appearance/theme-tokens.d.ts.map +1 -0
  278. package/dist/appearance/theme-tokens.js +229 -0
  279. package/dist/appearance/theme-tokens.js.map +1 -0
  280. package/dist/appearance/types.d.ts +110 -0
  281. package/dist/appearance/types.d.ts.map +1 -0
  282. package/dist/appearance/types.js +16 -0
  283. package/dist/appearance/types.js.map +1 -0
  284. package/dist/astro-cache.d.ts +80 -0
  285. package/dist/astro-cache.d.ts.map +1 -0
  286. package/dist/astro-cache.js +108 -0
  287. package/dist/astro-cache.js.map +1 -0
  288. package/dist/astro-content.d.ts +81 -0
  289. package/dist/astro-content.d.ts.map +1 -0
  290. package/dist/astro-content.js +151 -0
  291. package/dist/astro-content.js.map +1 -0
  292. package/dist/astro-redirects.d.ts +38 -0
  293. package/dist/astro-redirects.d.ts.map +1 -0
  294. package/dist/astro-redirects.js +45 -0
  295. package/dist/astro-redirects.js.map +1 -0
  296. package/dist/astro-security.d.ts +34 -0
  297. package/dist/astro-security.d.ts.map +1 -0
  298. package/dist/astro-security.js +31 -0
  299. package/dist/astro-security.js.map +1 -0
  300. package/dist/astro-site.d.ts +69 -0
  301. package/dist/astro-site.d.ts.map +1 -0
  302. package/dist/astro-site.js +81 -0
  303. package/dist/astro-site.js.map +1 -0
  304. package/dist/auth/index.d.ts +17 -0
  305. package/dist/auth/index.d.ts.map +1 -0
  306. package/dist/auth/index.js +12 -0
  307. package/dist/auth/index.js.map +1 -0
  308. package/dist/auth/invite-email.d.ts +12 -0
  309. package/dist/auth/invite-email.d.ts.map +1 -0
  310. package/dist/auth/invite-email.js +57 -0
  311. package/dist/auth/invite-email.js.map +1 -0
  312. package/dist/auth/invites.d.ts +72 -0
  313. package/dist/auth/invites.d.ts.map +1 -0
  314. package/dist/auth/invites.js +279 -0
  315. package/dist/auth/invites.js.map +1 -0
  316. package/dist/auth/mfa-pending.d.ts +24 -0
  317. package/dist/auth/mfa-pending.d.ts.map +1 -0
  318. package/dist/auth/mfa-pending.js +38 -0
  319. package/dist/auth/mfa-pending.js.map +1 -0
  320. package/dist/auth/oauth.d.ts +139 -0
  321. package/dist/auth/oauth.d.ts.map +1 -0
  322. package/dist/auth/oauth.js +376 -0
  323. package/dist/auth/oauth.js.map +1 -0
  324. package/dist/auth/password.d.ts +57 -0
  325. package/dist/auth/password.d.ts.map +1 -0
  326. package/dist/auth/password.js +150 -0
  327. package/dist/auth/password.js.map +1 -0
  328. package/dist/auth/providers/github.d.ts +9 -0
  329. package/dist/auth/providers/github.d.ts.map +1 -0
  330. package/dist/auth/providers/github.js +10 -0
  331. package/dist/auth/providers/github.js.map +1 -0
  332. package/dist/auth/providers/google.d.ts +9 -0
  333. package/dist/auth/providers/google.d.ts.map +1 -0
  334. package/dist/auth/providers/google.js +10 -0
  335. package/dist/auth/providers/google.js.map +1 -0
  336. package/dist/auth/providers/microsoft.d.ts +9 -0
  337. package/dist/auth/providers/microsoft.d.ts.map +1 -0
  338. package/dist/auth/providers/microsoft.js +11 -0
  339. package/dist/auth/providers/microsoft.js.map +1 -0
  340. package/dist/auth/reset-email.d.ts +10 -0
  341. package/dist/auth/reset-email.d.ts.map +1 -0
  342. package/dist/auth/reset-email.js +30 -0
  343. package/dist/auth/reset-email.js.map +1 -0
  344. package/dist/auth/reset.d.ts +42 -0
  345. package/dist/auth/reset.d.ts.map +1 -0
  346. package/dist/auth/reset.js +142 -0
  347. package/dist/auth/reset.js.map +1 -0
  348. package/dist/auth/roles.d.ts +51 -0
  349. package/dist/auth/roles.d.ts.map +1 -0
  350. package/dist/auth/roles.js +159 -0
  351. package/dist/auth/roles.js.map +1 -0
  352. package/dist/auth/session.d.ts +38 -0
  353. package/dist/auth/session.d.ts.map +1 -0
  354. package/dist/auth/session.js +106 -0
  355. package/dist/auth/session.js.map +1 -0
  356. package/dist/auth/team.d.ts +99 -0
  357. package/dist/auth/team.d.ts.map +1 -0
  358. package/dist/auth/team.js +327 -0
  359. package/dist/auth/team.js.map +1 -0
  360. package/dist/auth/totp.d.ts +31 -0
  361. package/dist/auth/totp.d.ts.map +1 -0
  362. package/dist/auth/totp.js +134 -0
  363. package/dist/auth/totp.js.map +1 -0
  364. package/dist/auth/user-admin.d.ts +119 -0
  365. package/dist/auth/user-admin.d.ts.map +1 -0
  366. package/dist/auth/user-admin.js +292 -0
  367. package/dist/auth/user-admin.js.map +1 -0
  368. package/dist/backup/index.d.ts +19 -0
  369. package/dist/backup/index.d.ts.map +1 -0
  370. package/dist/backup/index.js +24 -0
  371. package/dist/backup/index.js.map +1 -0
  372. package/dist/billing/stripe-meter.d.ts +22 -0
  373. package/dist/billing/stripe-meter.d.ts.map +1 -0
  374. package/dist/billing/stripe-meter.js +101 -0
  375. package/dist/billing/stripe-meter.js.map +1 -0
  376. package/dist/brand-voice/index.d.ts +7 -0
  377. package/dist/brand-voice/index.d.ts.map +1 -0
  378. package/dist/brand-voice/index.js +7 -0
  379. package/dist/brand-voice/index.js.map +1 -0
  380. package/dist/cache/http.d.ts +140 -0
  381. package/dist/cache/http.d.ts.map +1 -0
  382. package/dist/cache/http.js +213 -0
  383. package/dist/cache/http.js.map +1 -0
  384. package/dist/cache/index.d.ts +15 -0
  385. package/dist/cache/index.d.ts.map +1 -0
  386. package/dist/cache/index.js +32 -0
  387. package/dist/cache/index.js.map +1 -0
  388. package/dist/client.d.ts +51 -0
  389. package/dist/client.d.ts.map +1 -0
  390. package/dist/client.js +59 -0
  391. package/dist/client.js.map +1 -0
  392. package/dist/codegen/index.d.ts +4 -0
  393. package/dist/codegen/index.d.ts.map +1 -0
  394. package/dist/codegen/index.js +366 -0
  395. package/dist/codegen/index.js.map +1 -0
  396. package/dist/collections/index.d.ts +19 -0
  397. package/dist/collections/index.d.ts.map +1 -0
  398. package/dist/collections/index.js +30 -0
  399. package/dist/collections/index.js.map +1 -0
  400. package/dist/collections/presets.d.ts +90 -0
  401. package/dist/collections/presets.d.ts.map +1 -0
  402. package/dist/collections/presets.js +573 -0
  403. package/dist/collections/presets.js.map +1 -0
  404. package/dist/config/define.d.ts +8 -0
  405. package/dist/config/define.d.ts.map +1 -0
  406. package/dist/config/define.js +7 -0
  407. package/dist/config/define.js.map +1 -0
  408. package/dist/config/index.d.ts +31 -0
  409. package/dist/config/index.d.ts.map +1 -0
  410. package/dist/config/index.js +196 -0
  411. package/dist/config/index.js.map +1 -0
  412. package/dist/config/runtime.d.ts +107 -0
  413. package/dist/config/runtime.d.ts.map +1 -0
  414. package/dist/config/runtime.js +77 -0
  415. package/dist/config/runtime.js.map +1 -0
  416. package/dist/config/types.d.ts +924 -0
  417. package/dist/config/types.d.ts.map +1 -0
  418. package/dist/config/types.js +2 -0
  419. package/dist/config/types.js.map +1 -0
  420. package/dist/content/ai-api.d.ts +21 -0
  421. package/dist/content/ai-api.d.ts.map +1 -0
  422. package/dist/content/ai-api.js +25 -0
  423. package/dist/content/ai-api.js.map +1 -0
  424. package/dist/content/content-graph.d.ts +25 -0
  425. package/dist/content/content-graph.d.ts.map +1 -0
  426. package/dist/content/content-graph.js +40 -0
  427. package/dist/content/content-graph.js.map +1 -0
  428. package/dist/content/extract.d.ts +7 -0
  429. package/dist/content/extract.d.ts.map +1 -0
  430. package/dist/content/extract.js +33 -0
  431. package/dist/content/extract.js.map +1 -0
  432. package/dist/content/index.d.ts +9 -0
  433. package/dist/content/index.d.ts.map +1 -0
  434. package/dist/content/index.js +6 -0
  435. package/dist/content/index.js.map +1 -0
  436. package/dist/content/slug.d.ts +40 -0
  437. package/dist/content/slug.d.ts.map +1 -0
  438. package/dist/content/slug.js +78 -0
  439. package/dist/content/slug.js.map +1 -0
  440. package/dist/content/structured-data.d.ts +163 -0
  441. package/dist/content/structured-data.d.ts.map +1 -0
  442. package/dist/content/structured-data.js +479 -0
  443. package/dist/content/structured-data.js.map +1 -0
  444. package/dist/content-health/cron.d.ts +68 -0
  445. package/dist/content-health/cron.d.ts.map +1 -0
  446. package/dist/content-health/cron.js +167 -0
  447. package/dist/content-health/cron.js.map +1 -0
  448. package/dist/content-health/scanner.d.ts +119 -0
  449. package/dist/content-health/scanner.d.ts.map +1 -0
  450. package/dist/content-health/scanner.js +257 -0
  451. package/dist/content-health/scanner.js.map +1 -0
  452. package/dist/cron/index.d.ts +90 -0
  453. package/dist/cron/index.d.ts.map +1 -0
  454. package/dist/cron/index.js +299 -0
  455. package/dist/cron/index.js.map +1 -0
  456. package/dist/db/adapters/mysql.d.ts +5 -0
  457. package/dist/db/adapters/mysql.d.ts.map +1 -0
  458. package/dist/db/adapters/mysql.js +26 -0
  459. package/dist/db/adapters/mysql.js.map +1 -0
  460. package/dist/db/adapters/postgres.d.ts +7 -0
  461. package/dist/db/adapters/postgres.d.ts.map +1 -0
  462. package/dist/db/adapters/postgres.js +25 -0
  463. package/dist/db/adapters/postgres.js.map +1 -0
  464. package/dist/db/adapters/sqlite.d.ts +5 -0
  465. package/dist/db/adapters/sqlite.d.ts.map +1 -0
  466. package/dist/db/adapters/sqlite.js +27 -0
  467. package/dist/db/adapters/sqlite.js.map +1 -0
  468. package/dist/db/client-types.d.ts +75 -0
  469. package/dist/db/client-types.d.ts.map +1 -0
  470. package/dist/db/client-types.js +2 -0
  471. package/dist/db/client-types.js.map +1 -0
  472. package/dist/db/create-adapter.d.ts +11 -0
  473. package/dist/db/create-adapter.d.ts.map +1 -0
  474. package/dist/db/create-adapter.js +43 -0
  475. package/dist/db/create-adapter.js.map +1 -0
  476. package/dist/db/find-in-batches.d.ts +12 -0
  477. package/dist/db/find-in-batches.d.ts.map +1 -0
  478. package/dist/db/find-in-batches.js +20 -0
  479. package/dist/db/find-in-batches.js.map +1 -0
  480. package/dist/db/index.d.ts +9 -0
  481. package/dist/db/index.d.ts.map +1 -0
  482. package/dist/db/index.js +5 -0
  483. package/dist/db/index.js.map +1 -0
  484. package/dist/db/model-types.d.ts +602 -0
  485. package/dist/db/model-types.d.ts.map +1 -0
  486. package/dist/db/model-types.js +14 -0
  487. package/dist/db/model-types.js.map +1 -0
  488. package/dist/db.d.ts +20 -0
  489. package/dist/db.d.ts.map +1 -0
  490. package/dist/db.js +35 -0
  491. package/dist/db.js.map +1 -0
  492. package/dist/diagnostics/api-metrics.d.ts +135 -0
  493. package/dist/diagnostics/api-metrics.d.ts.map +1 -0
  494. package/dist/diagnostics/api-metrics.js +376 -0
  495. package/dist/diagnostics/api-metrics.js.map +1 -0
  496. package/dist/diagnostics/env.d.ts +44 -0
  497. package/dist/diagnostics/env.d.ts.map +1 -0
  498. package/dist/diagnostics/env.js +354 -0
  499. package/dist/diagnostics/env.js.map +1 -0
  500. package/dist/diagnostics/logger.d.ts +38 -0
  501. package/dist/diagnostics/logger.d.ts.map +1 -0
  502. package/dist/diagnostics/logger.js +89 -0
  503. package/dist/diagnostics/logger.js.map +1 -0
  504. package/dist/diagnostics/sentry.d.ts +8 -0
  505. package/dist/diagnostics/sentry.d.ts.map +1 -0
  506. package/dist/diagnostics/sentry.js +54 -0
  507. package/dist/diagnostics/sentry.js.map +1 -0
  508. package/dist/email/resolve.d.ts +27 -0
  509. package/dist/email/resolve.d.ts.map +1 -0
  510. package/dist/email/resolve.js +68 -0
  511. package/dist/email/resolve.js.map +1 -0
  512. package/dist/email/sender.d.ts +71 -0
  513. package/dist/email/sender.d.ts.map +1 -0
  514. package/dist/email/sender.js +41 -0
  515. package/dist/email/sender.js.map +1 -0
  516. package/dist/email/settings-store.d.ts +116 -0
  517. package/dist/email/settings-store.d.ts.map +1 -0
  518. package/dist/email/settings-store.js +222 -0
  519. package/dist/email/settings-store.js.map +1 -0
  520. package/dist/fields/component-block.d.ts +23 -0
  521. package/dist/fields/component-block.d.ts.map +1 -0
  522. package/dist/fields/component-block.js +137 -0
  523. package/dist/fields/component-block.js.map +1 -0
  524. package/dist/fields/index.d.ts +19 -0
  525. package/dist/fields/index.d.ts.map +1 -0
  526. package/dist/fields/index.js +95 -0
  527. package/dist/fields/index.js.map +1 -0
  528. package/dist/fields/presets.d.ts +144 -0
  529. package/dist/fields/presets.d.ts.map +1 -0
  530. package/dist/fields/presets.js +350 -0
  531. package/dist/fields/presets.js.map +1 -0
  532. package/dist/fields/relations.d.ts +87 -0
  533. package/dist/fields/relations.d.ts.map +1 -0
  534. package/dist/fields/relations.js +231 -0
  535. package/dist/fields/relations.js.map +1 -0
  536. package/dist/forms/analytics.d.ts +62 -0
  537. package/dist/forms/analytics.d.ts.map +1 -0
  538. package/dist/forms/analytics.js +95 -0
  539. package/dist/forms/analytics.js.map +1 -0
  540. package/dist/forms/attribution.d.ts +175 -0
  541. package/dist/forms/attribution.d.ts.map +1 -0
  542. package/dist/forms/attribution.js +577 -0
  543. package/dist/forms/attribution.js.map +1 -0
  544. package/dist/forms/client-validation.d.ts +50 -0
  545. package/dist/forms/client-validation.d.ts.map +1 -0
  546. package/dist/forms/client-validation.js +215 -0
  547. package/dist/forms/client-validation.js.map +1 -0
  548. package/dist/forms/delivery.d.ts +89 -0
  549. package/dist/forms/delivery.d.ts.map +1 -0
  550. package/dist/forms/delivery.js +477 -0
  551. package/dist/forms/delivery.js.map +1 -0
  552. package/dist/forms/encryption.d.ts +20 -0
  553. package/dist/forms/encryption.d.ts.map +1 -0
  554. package/dist/forms/encryption.js +80 -0
  555. package/dist/forms/encryption.js.map +1 -0
  556. package/dist/forms/entries.d.ts +68 -0
  557. package/dist/forms/entries.d.ts.map +1 -0
  558. package/dist/forms/entries.js +557 -0
  559. package/dist/forms/entries.js.map +1 -0
  560. package/dist/forms/files.d.ts +38 -0
  561. package/dist/forms/files.d.ts.map +1 -0
  562. package/dist/forms/files.js +134 -0
  563. package/dist/forms/files.js.map +1 -0
  564. package/dist/forms/index.d.ts +21 -0
  565. package/dist/forms/index.d.ts.map +1 -0
  566. package/dist/forms/index.js +20 -0
  567. package/dist/forms/index.js.map +1 -0
  568. package/dist/forms/notify.d.ts +80 -0
  569. package/dist/forms/notify.d.ts.map +1 -0
  570. package/dist/forms/notify.js +357 -0
  571. package/dist/forms/notify.js.map +1 -0
  572. package/dist/forms/prefetch.d.ts +17 -0
  573. package/dist/forms/prefetch.d.ts.map +1 -0
  574. package/dist/forms/prefetch.js +36 -0
  575. package/dist/forms/prefetch.js.map +1 -0
  576. package/dist/forms/public-html.d.ts +22 -0
  577. package/dist/forms/public-html.d.ts.map +1 -0
  578. package/dist/forms/public-html.js +145 -0
  579. package/dist/forms/public-html.js.map +1 -0
  580. package/dist/forms/service.d.ts +102 -0
  581. package/dist/forms/service.d.ts.map +1 -0
  582. package/dist/forms/service.js +625 -0
  583. package/dist/forms/service.js.map +1 -0
  584. package/dist/forms/spam.d.ts +33 -0
  585. package/dist/forms/spam.d.ts.map +1 -0
  586. package/dist/forms/spam.js +93 -0
  587. package/dist/forms/spam.js.map +1 -0
  588. package/dist/forms/submission.d.ts +132 -0
  589. package/dist/forms/submission.d.ts.map +1 -0
  590. package/dist/forms/submission.js +348 -0
  591. package/dist/forms/submission.js.map +1 -0
  592. package/dist/forms/types.d.ts +259 -0
  593. package/dist/forms/types.d.ts.map +1 -0
  594. package/dist/forms/types.js +20 -0
  595. package/dist/forms/types.js.map +1 -0
  596. package/dist/forms/validation.d.ts +19 -0
  597. package/dist/forms/validation.d.ts.map +1 -0
  598. package/dist/forms/validation.js +166 -0
  599. package/dist/forms/validation.js.map +1 -0
  600. package/dist/forms/webhooks.d.ts +60 -0
  601. package/dist/forms/webhooks.d.ts.map +1 -0
  602. package/dist/forms/webhooks.js +360 -0
  603. package/dist/forms/webhooks.js.map +1 -0
  604. package/dist/forms/webmcp.d.ts +36 -0
  605. package/dist/forms/webmcp.d.ts.map +1 -0
  606. package/dist/forms/webmcp.js +67 -0
  607. package/dist/forms/webmcp.js.map +1 -0
  608. package/dist/forms/xlsx.d.ts +10 -0
  609. package/dist/forms/xlsx.d.ts.map +1 -0
  610. package/dist/forms/xlsx.js +207 -0
  611. package/dist/forms/xlsx.js.map +1 -0
  612. package/dist/generated/browser.d.ts +220 -0
  613. package/dist/generated/browser.js +17 -0
  614. package/dist/generated/client.d.ts +239 -0
  615. package/dist/generated/client.js +34 -0
  616. package/dist/generated/commonInputTypes.d.ts +887 -0
  617. package/dist/generated/commonInputTypes.js +10 -0
  618. package/dist/generated/enums.d.ts +83 -0
  619. package/dist/generated/enums.js +81 -0
  620. package/dist/generated/internal/class.d.ts +517 -0
  621. package/dist/generated/internal/class.js +52 -0
  622. package/dist/generated/internal/prismaNamespace.d.ts +4184 -0
  623. package/dist/generated/internal/prismaNamespace.js +932 -0
  624. package/dist/generated/internal/prismaNamespaceBrowser.d.ts +968 -0
  625. package/dist/generated/internal/prismaNamespaceBrowser.js +903 -0
  626. package/dist/generated/models/AiCrawlerHit.d.ts +1115 -0
  627. package/dist/generated/models/AiCrawlerHit.js +1 -0
  628. package/dist/generated/models/ApiKey.d.ts +1388 -0
  629. package/dist/generated/models/ApiKey.js +1 -0
  630. package/dist/generated/models/ApiRequestMetric.d.ts +1229 -0
  631. package/dist/generated/models/ApiRequestMetric.js +1 -0
  632. package/dist/generated/models/AuditLog.d.ts +1062 -0
  633. package/dist/generated/models/AuditLog.js +1 -0
  634. package/dist/generated/models/BackupRecord.d.ts +1100 -0
  635. package/dist/generated/models/BackupRecord.js +1 -0
  636. package/dist/generated/models/ContentIssue.d.ts +1318 -0
  637. package/dist/generated/models/ContentIssue.js +1 -0
  638. package/dist/generated/models/ContentLock.d.ts +1295 -0
  639. package/dist/generated/models/ContentLock.js +1 -0
  640. package/dist/generated/models/ContentTemplate.d.ts +1254 -0
  641. package/dist/generated/models/ContentTemplate.js +1 -0
  642. package/dist/generated/models/Document.d.ts +3443 -0
  643. package/dist/generated/models/Document.js +1 -0
  644. package/dist/generated/models/Folder.d.ts +1689 -0
  645. package/dist/generated/models/Folder.js +1 -0
  646. package/dist/generated/models/FormDeliveryAttempt.d.ts +1705 -0
  647. package/dist/generated/models/FormDeliveryAttempt.js +1 -0
  648. package/dist/generated/models/FormSchemaVersion.d.ts +1105 -0
  649. package/dist/generated/models/FormSchemaVersion.js +1 -0
  650. package/dist/generated/models/FormSubmission.d.ts +2125 -0
  651. package/dist/generated/models/FormSubmission.js +1 -0
  652. package/dist/generated/models/FormSubmissionFile.d.ts +1438 -0
  653. package/dist/generated/models/FormSubmissionFile.js +1 -0
  654. package/dist/generated/models/FormWebhook.d.ts +1271 -0
  655. package/dist/generated/models/FormWebhook.js +1 -0
  656. package/dist/generated/models/InAppNotification.d.ts +1295 -0
  657. package/dist/generated/models/InAppNotification.js +1 -0
  658. package/dist/generated/models/Invite.d.ts +1240 -0
  659. package/dist/generated/models/Invite.js +1 -0
  660. package/dist/generated/models/Media.d.ts +2759 -0
  661. package/dist/generated/models/Media.js +1 -0
  662. package/dist/generated/models/MediaUsage.d.ts +1301 -0
  663. package/dist/generated/models/MediaUsage.js +1 -0
  664. package/dist/generated/models/OAuthAccount.d.ts +1300 -0
  665. package/dist/generated/models/OAuthAccount.js +1 -0
  666. package/dist/generated/models/PageTemplate.d.ts +1118 -0
  667. package/dist/generated/models/PageTemplate.js +1 -0
  668. package/dist/generated/models/PasswordResetToken.d.ts +1225 -0
  669. package/dist/generated/models/PasswordResetToken.js +1 -0
  670. package/dist/generated/models/Redirect.d.ts +1342 -0
  671. package/dist/generated/models/Redirect.js +1 -0
  672. package/dist/generated/models/Redirect404Hit.d.ts +1107 -0
  673. package/dist/generated/models/Redirect404Hit.js +1 -0
  674. package/dist/generated/models/RedirectSuggestion.d.ts +1185 -0
  675. package/dist/generated/models/RedirectSuggestion.js +1 -0
  676. package/dist/generated/models/SavedSection.d.ts +1156 -0
  677. package/dist/generated/models/SavedSection.js +1 -0
  678. package/dist/generated/models/ScriptTag.d.ts +1291 -0
  679. package/dist/generated/models/ScriptTag.js +1 -0
  680. package/dist/generated/models/SeoAuditRun.d.ts +1570 -0
  681. package/dist/generated/models/SeoAuditRun.js +1 -0
  682. package/dist/generated/models/SeoIssue.d.ts +1873 -0
  683. package/dist/generated/models/SeoIssue.js +1 -0
  684. package/dist/generated/models/Session.d.ts +1330 -0
  685. package/dist/generated/models/Session.js +1 -0
  686. package/dist/generated/models/Site.d.ts +1062 -0
  687. package/dist/generated/models/Site.js +1 -0
  688. package/dist/generated/models/User.d.ts +4344 -0
  689. package/dist/generated/models/User.js +1 -0
  690. package/dist/generated/models/Version.d.ts +1334 -0
  691. package/dist/generated/models/Version.js +1 -0
  692. package/dist/generated/models/WebhookDeliveryLog.d.ts +1479 -0
  693. package/dist/generated/models/WebhookDeliveryLog.js +1 -0
  694. package/dist/generated/models/WebhookEndpoint.d.ts +1331 -0
  695. package/dist/generated/models/WebhookEndpoint.js +1 -0
  696. package/dist/generated/models/WorkflowState.d.ts +1436 -0
  697. package/dist/generated/models/WorkflowState.js +1 -0
  698. package/dist/generated/models.d.ts +37 -0
  699. package/dist/generated/models.js +1 -0
  700. package/dist/graphql/index.d.ts +11 -0
  701. package/dist/graphql/index.d.ts.map +1 -0
  702. package/dist/graphql/index.js +74 -0
  703. package/dist/graphql/index.js.map +1 -0
  704. package/dist/graphql/resolvers.d.ts +8 -0
  705. package/dist/graphql/resolvers.d.ts.map +1 -0
  706. package/dist/graphql/resolvers.js +89 -0
  707. package/dist/graphql/resolvers.js.map +1 -0
  708. package/dist/graphql/schema-builder.d.ts +3 -0
  709. package/dist/graphql/schema-builder.d.ts.map +1 -0
  710. package/dist/graphql/schema-builder.js +103 -0
  711. package/dist/graphql/schema-builder.js.map +1 -0
  712. package/dist/health/index.d.ts +27 -0
  713. package/dist/health/index.d.ts.map +1 -0
  714. package/dist/health/index.js +43 -0
  715. package/dist/health/index.js.map +1 -0
  716. package/dist/i18n/index.d.ts +22 -0
  717. package/dist/i18n/index.d.ts.map +1 -0
  718. package/dist/i18n/index.js +37 -0
  719. package/dist/i18n/index.js.map +1 -0
  720. package/dist/index.d.ts +125 -0
  721. package/dist/index.d.ts.map +1 -0
  722. package/dist/index.js +132 -0
  723. package/dist/index.js.map +1 -0
  724. package/dist/media/content-hash.d.ts +3 -0
  725. package/dist/media/content-hash.d.ts.map +1 -0
  726. package/dist/media/content-hash.js +11 -0
  727. package/dist/media/content-hash.js.map +1 -0
  728. package/dist/media/denormalize.d.ts +23 -0
  729. package/dist/media/denormalize.d.ts.map +1 -0
  730. package/dist/media/denormalize.js +170 -0
  731. package/dist/media/denormalize.js.map +1 -0
  732. package/dist/media/index.d.ts +8 -0
  733. package/dist/media/index.d.ts.map +1 -0
  734. package/dist/media/index.js +5 -0
  735. package/dist/media/index.js.map +1 -0
  736. package/dist/media/merge-document-media.d.ts +23 -0
  737. package/dist/media/merge-document-media.d.ts.map +1 -0
  738. package/dist/media/merge-document-media.js +146 -0
  739. package/dist/media/merge-document-media.js.map +1 -0
  740. package/dist/media/metadata-patch.d.ts +21 -0
  741. package/dist/media/metadata-patch.d.ts.map +1 -0
  742. package/dist/media/metadata-patch.js +37 -0
  743. package/dist/media/metadata-patch.js.map +1 -0
  744. package/dist/media/optimize.d.ts +61 -0
  745. package/dist/media/optimize.d.ts.map +1 -0
  746. package/dist/media/optimize.js +168 -0
  747. package/dist/media/optimize.js.map +1 -0
  748. package/dist/media/seo.d.ts +110 -0
  749. package/dist/media/seo.d.ts.map +1 -0
  750. package/dist/media/seo.js +263 -0
  751. package/dist/media/seo.js.map +1 -0
  752. package/dist/media/usage-sync.d.ts +37 -0
  753. package/dist/media/usage-sync.d.ts.map +1 -0
  754. package/dist/media/usage-sync.js +234 -0
  755. package/dist/media/usage-sync.js.map +1 -0
  756. package/dist/middleware.d.ts +17 -0
  757. package/dist/middleware.d.ts.map +1 -0
  758. package/dist/middleware.js +159 -0
  759. package/dist/middleware.js.map +1 -0
  760. package/dist/multisite/index.d.ts +20 -0
  761. package/dist/multisite/index.d.ts.map +1 -0
  762. package/dist/multisite/index.js +26 -0
  763. package/dist/multisite/index.js.map +1 -0
  764. package/dist/navigation/unresolved-targets.d.ts +35 -0
  765. package/dist/navigation/unresolved-targets.d.ts.map +1 -0
  766. package/dist/navigation/unresolved-targets.js +109 -0
  767. package/dist/navigation/unresolved-targets.js.map +1 -0
  768. package/dist/next/preview.d.ts +10 -0
  769. package/dist/next/preview.d.ts.map +1 -0
  770. package/dist/next/preview.js +17 -0
  771. package/dist/next/preview.js.map +1 -0
  772. package/dist/next/script-tags.d.ts +15 -0
  773. package/dist/next/script-tags.d.ts.map +1 -0
  774. package/dist/next/script-tags.js +58 -0
  775. package/dist/next/script-tags.js.map +1 -0
  776. package/dist/next-site.d.ts +29 -0
  777. package/dist/next-site.d.ts.map +1 -0
  778. package/dist/next-site.js +60 -0
  779. package/dist/next-site.js.map +1 -0
  780. package/dist/next.d.ts +33 -0
  781. package/dist/next.d.ts.map +1 -0
  782. package/dist/next.js +142 -0
  783. package/dist/next.js.map +1 -0
  784. package/dist/notifications/index.d.ts +20 -0
  785. package/dist/notifications/index.d.ts.map +1 -0
  786. package/dist/notifications/index.js +22 -0
  787. package/dist/notifications/index.js.map +1 -0
  788. package/dist/page-builder/a11y-fix.d.ts +20 -0
  789. package/dist/page-builder/a11y-fix.d.ts.map +1 -0
  790. package/dist/page-builder/a11y-fix.js +182 -0
  791. package/dist/page-builder/a11y-fix.js.map +1 -0
  792. package/dist/page-builder/ai-pipeline.d.ts +43 -0
  793. package/dist/page-builder/ai-pipeline.d.ts.map +1 -0
  794. package/dist/page-builder/ai-pipeline.js +165 -0
  795. package/dist/page-builder/ai-pipeline.js.map +1 -0
  796. package/dist/page-builder/blocks.d.ts +27 -0
  797. package/dist/page-builder/blocks.d.ts.map +1 -0
  798. package/dist/page-builder/blocks.js +313 -0
  799. package/dist/page-builder/blocks.js.map +1 -0
  800. package/dist/page-builder/design-scorer.d.ts +27 -0
  801. package/dist/page-builder/design-scorer.d.ts.map +1 -0
  802. package/dist/page-builder/design-scorer.js +612 -0
  803. package/dist/page-builder/design-scorer.js.map +1 -0
  804. package/dist/page-builder/index.d.ts +18 -0
  805. package/dist/page-builder/index.d.ts.map +1 -0
  806. package/dist/page-builder/index.js +20 -0
  807. package/dist/page-builder/index.js.map +1 -0
  808. package/dist/page-builder/schema.d.ts +107 -0
  809. package/dist/page-builder/schema.d.ts.map +1 -0
  810. package/dist/page-builder/schema.js +90 -0
  811. package/dist/page-builder/schema.js.map +1 -0
  812. package/dist/page-builder/seo-analyzer.d.ts +61 -0
  813. package/dist/page-builder/seo-analyzer.d.ts.map +1 -0
  814. package/dist/page-builder/seo-analyzer.js +778 -0
  815. package/dist/page-builder/seo-analyzer.js.map +1 -0
  816. package/dist/page-builder/templates.d.ts +9 -0
  817. package/dist/page-builder/templates.d.ts.map +1 -0
  818. package/dist/page-builder/templates.js +742 -0
  819. package/dist/page-builder/templates.js.map +1 -0
  820. package/dist/page-builder/tree.d.ts +17 -0
  821. package/dist/page-builder/tree.d.ts.map +1 -0
  822. package/dist/page-builder/tree.js +160 -0
  823. package/dist/page-builder/tree.js.map +1 -0
  824. package/dist/page-builder/types.d.ts +112 -0
  825. package/dist/page-builder/types.d.ts.map +1 -0
  826. package/dist/page-builder/types.js +4 -0
  827. package/dist/page-builder/types.js.map +1 -0
  828. package/dist/page-builder/validate.d.ts +6 -0
  829. package/dist/page-builder/validate.d.ts.map +1 -0
  830. package/dist/page-builder/validate.js +87 -0
  831. package/dist/page-builder/validate.js.map +1 -0
  832. package/dist/plan/features.d.ts +17 -0
  833. package/dist/plan/features.d.ts.map +1 -0
  834. package/dist/plan/features.js +73 -0
  835. package/dist/plan/features.js.map +1 -0
  836. package/dist/posts/query.d.ts +50 -0
  837. package/dist/posts/query.d.ts.map +1 -0
  838. package/dist/posts/query.js +105 -0
  839. package/dist/posts/query.js.map +1 -0
  840. package/dist/presence/index.d.ts +24 -0
  841. package/dist/presence/index.d.ts.map +1 -0
  842. package/dist/presence/index.js +99 -0
  843. package/dist/presence/index.js.map +1 -0
  844. package/dist/preview/event-bus.d.ts +39 -0
  845. package/dist/preview/event-bus.d.ts.map +1 -0
  846. package/dist/preview/event-bus.js +73 -0
  847. package/dist/preview/event-bus.js.map +1 -0
  848. package/dist/preview/index.d.ts +36 -0
  849. package/dist/preview/index.d.ts.map +1 -0
  850. package/dist/preview/index.js +68 -0
  851. package/dist/preview/index.js.map +1 -0
  852. package/dist/preview/sse-stream.d.ts +47 -0
  853. package/dist/preview/sse-stream.d.ts.map +1 -0
  854. package/dist/preview/sse-stream.js +111 -0
  855. package/dist/preview/sse-stream.js.map +1 -0
  856. package/dist/privacy/index.d.ts +33 -0
  857. package/dist/privacy/index.d.ts.map +1 -0
  858. package/dist/privacy/index.js +15 -0
  859. package/dist/privacy/index.js.map +1 -0
  860. package/dist/public-redirects.d.ts +78 -0
  861. package/dist/public-redirects.d.ts.map +1 -0
  862. package/dist/public-redirects.js +187 -0
  863. package/dist/public-redirects.js.map +1 -0
  864. package/dist/public-security-headers.d.ts +27 -0
  865. package/dist/public-security-headers.d.ts.map +1 -0
  866. package/dist/public-security-headers.js +40 -0
  867. package/dist/public-security-headers.js.map +1 -0
  868. package/dist/publishing/publish-sync.d.ts +50 -0
  869. package/dist/publishing/publish-sync.d.ts.map +1 -0
  870. package/dist/publishing/publish-sync.js +124 -0
  871. package/dist/publishing/publish-sync.js.map +1 -0
  872. package/dist/purge/adapters.d.ts +60 -0
  873. package/dist/purge/adapters.d.ts.map +1 -0
  874. package/dist/purge/adapters.js +81 -0
  875. package/dist/purge/adapters.js.map +1 -0
  876. package/dist/purge/index.d.ts +12 -0
  877. package/dist/purge/index.d.ts.map +1 -0
  878. package/dist/purge/index.js +10 -0
  879. package/dist/purge/index.js.map +1 -0
  880. package/dist/purge/receiver.d.ts +95 -0
  881. package/dist/purge/receiver.d.ts.map +1 -0
  882. package/dist/purge/receiver.js +176 -0
  883. package/dist/purge/receiver.js.map +1 -0
  884. package/dist/realtime/comments.d.ts +217 -0
  885. package/dist/realtime/comments.d.ts.map +1 -0
  886. package/dist/realtime/comments.js +282 -0
  887. package/dist/realtime/comments.js.map +1 -0
  888. package/dist/realtime/gateway.d.ts +107 -0
  889. package/dist/realtime/gateway.d.ts.map +1 -0
  890. package/dist/realtime/gateway.js +215 -0
  891. package/dist/realtime/gateway.js.map +1 -0
  892. package/dist/realtime/index.d.ts +21 -0
  893. package/dist/realtime/index.d.ts.map +1 -0
  894. package/dist/realtime/index.js +27 -0
  895. package/dist/realtime/index.js.map +1 -0
  896. package/dist/realtime/notification-bus.d.ts +60 -0
  897. package/dist/realtime/notification-bus.d.ts.map +1 -0
  898. package/dist/realtime/notification-bus.js +77 -0
  899. package/dist/realtime/notification-bus.js.map +1 -0
  900. package/dist/realtime/notification-stream.d.ts +50 -0
  901. package/dist/realtime/notification-stream.d.ts.map +1 -0
  902. package/dist/realtime/notification-stream.js +148 -0
  903. package/dist/realtime/notification-stream.js.map +1 -0
  904. package/dist/realtime/notifications.d.ts +201 -0
  905. package/dist/realtime/notifications.d.ts.map +1 -0
  906. package/dist/realtime/notifications.js +233 -0
  907. package/dist/realtime/notifications.js.map +1 -0
  908. package/dist/realtime/persistence.d.ts +72 -0
  909. package/dist/realtime/persistence.d.ts.map +1 -0
  910. package/dist/realtime/persistence.js +53 -0
  911. package/dist/realtime/persistence.js.map +1 -0
  912. package/dist/realtime/sse.d.ts +20 -0
  913. package/dist/realtime/sse.d.ts.map +1 -0
  914. package/dist/realtime/sse.js +31 -0
  915. package/dist/realtime/sse.js.map +1 -0
  916. package/dist/redirects/auto.d.ts +45 -0
  917. package/dist/redirects/auto.d.ts.map +1 -0
  918. package/dist/redirects/auto.js +60 -0
  919. package/dist/redirects/auto.js.map +1 -0
  920. package/dist/redirects/fix-link.d.ts +15 -0
  921. package/dist/redirects/fix-link.d.ts.map +1 -0
  922. package/dist/redirects/fix-link.js +66 -0
  923. package/dist/redirects/fix-link.js.map +1 -0
  924. package/dist/redirects/graph.d.ts +92 -0
  925. package/dist/redirects/graph.d.ts.map +1 -0
  926. package/dist/redirects/graph.js +176 -0
  927. package/dist/redirects/graph.js.map +1 -0
  928. package/dist/redirects/index.d.ts +17 -0
  929. package/dist/redirects/index.d.ts.map +1 -0
  930. package/dist/redirects/index.js +10 -0
  931. package/dist/redirects/index.js.map +1 -0
  932. package/dist/redirects/legitimacy.d.ts +23 -0
  933. package/dist/redirects/legitimacy.d.ts.map +1 -0
  934. package/dist/redirects/legitimacy.js +64 -0
  935. package/dist/redirects/legitimacy.js.map +1 -0
  936. package/dist/redirects/recovery.d.ts +92 -0
  937. package/dist/redirects/recovery.d.ts.map +1 -0
  938. package/dist/redirects/recovery.js +210 -0
  939. package/dist/redirects/recovery.js.map +1 -0
  940. package/dist/redirects/rerank.d.ts +24 -0
  941. package/dist/redirects/rerank.d.ts.map +1 -0
  942. package/dist/redirects/rerank.js +82 -0
  943. package/dist/redirects/rerank.js.map +1 -0
  944. package/dist/redirects/suggest.d.ts +23 -0
  945. package/dist/redirects/suggest.d.ts.map +1 -0
  946. package/dist/redirects/suggest.js +80 -0
  947. package/dist/redirects/suggest.js.map +1 -0
  948. package/dist/redirects/typo.d.ts +10 -0
  949. package/dist/redirects/typo.d.ts.map +1 -0
  950. package/dist/redirects/typo.js +51 -0
  951. package/dist/redirects/typo.js.map +1 -0
  952. package/dist/redirects/validate-destination.d.ts +13 -0
  953. package/dist/redirects/validate-destination.d.ts.map +1 -0
  954. package/dist/redirects/validate-destination.js +51 -0
  955. package/dist/redirects/validate-destination.js.map +1 -0
  956. package/dist/relationships/index.d.ts +13 -0
  957. package/dist/relationships/index.d.ts.map +1 -0
  958. package/dist/relationships/index.js +12 -0
  959. package/dist/relationships/index.js.map +1 -0
  960. package/dist/scheduling/index.d.ts +45 -0
  961. package/dist/scheduling/index.d.ts.map +1 -0
  962. package/dist/scheduling/index.js +141 -0
  963. package/dist/scheduling/index.js.map +1 -0
  964. package/dist/script-tags/index.d.ts +3 -0
  965. package/dist/script-tags/index.d.ts.map +1 -0
  966. package/dist/script-tags/index.js +3 -0
  967. package/dist/script-tags/index.js.map +1 -0
  968. package/dist/script-tags/invalidate.d.ts +3 -0
  969. package/dist/script-tags/invalidate.d.ts.map +1 -0
  970. package/dist/script-tags/invalidate.js +16 -0
  971. package/dist/script-tags/invalidate.js.map +1 -0
  972. package/dist/script-tags/resolve.d.ts +41 -0
  973. package/dist/script-tags/resolve.d.ts.map +1 -0
  974. package/dist/script-tags/resolve.js +130 -0
  975. package/dist/script-tags/resolve.js.map +1 -0
  976. package/dist/search/index.d.ts +25 -0
  977. package/dist/search/index.d.ts.map +1 -0
  978. package/dist/search/index.js +189 -0
  979. package/dist/search/index.js.map +1 -0
  980. package/dist/sections/bridge.d.ts +8 -0
  981. package/dist/sections/bridge.d.ts.map +1 -0
  982. package/dist/sections/bridge.js +211 -0
  983. package/dist/sections/bridge.js.map +1 -0
  984. package/dist/sections/define.d.ts +21 -0
  985. package/dist/sections/define.d.ts.map +1 -0
  986. package/dist/sections/define.js +160 -0
  987. package/dist/sections/define.js.map +1 -0
  988. package/dist/sections/embed.d.ts +24 -0
  989. package/dist/sections/embed.d.ts.map +1 -0
  990. package/dist/sections/embed.js +78 -0
  991. package/dist/sections/embed.js.map +1 -0
  992. package/dist/sections/fields.d.ts +37 -0
  993. package/dist/sections/fields.d.ts.map +1 -0
  994. package/dist/sections/fields.js +50 -0
  995. package/dist/sections/fields.js.map +1 -0
  996. package/dist/sections/helpers.d.ts +149 -0
  997. package/dist/sections/helpers.d.ts.map +1 -0
  998. package/dist/sections/helpers.js +373 -0
  999. package/dist/sections/helpers.js.map +1 -0
  1000. package/dist/sections/index.d.ts +22 -0
  1001. package/dist/sections/index.d.ts.map +1 -0
  1002. package/dist/sections/index.js +18 -0
  1003. package/dist/sections/index.js.map +1 -0
  1004. package/dist/sections/media-enrich.d.ts +45 -0
  1005. package/dist/sections/media-enrich.d.ts.map +1 -0
  1006. package/dist/sections/media-enrich.js +137 -0
  1007. package/dist/sections/media-enrich.js.map +1 -0
  1008. package/dist/sections/migrate.d.ts +42 -0
  1009. package/dist/sections/migrate.d.ts.map +1 -0
  1010. package/dist/sections/migrate.js +66 -0
  1011. package/dist/sections/migrate.js.map +1 -0
  1012. package/dist/sections/registry.d.ts +46 -0
  1013. package/dist/sections/registry.d.ts.map +1 -0
  1014. package/dist/sections/registry.js +598 -0
  1015. package/dist/sections/registry.js.map +1 -0
  1016. package/dist/sections/types.d.ts +192 -0
  1017. package/dist/sections/types.d.ts.map +1 -0
  1018. package/dist/sections/types.js +21 -0
  1019. package/dist/sections/types.js.map +1 -0
  1020. package/dist/sections/validate.d.ts +12 -0
  1021. package/dist/sections/validate.d.ts.map +1 -0
  1022. package/dist/sections/validate.js +186 -0
  1023. package/dist/sections/validate.js.map +1 -0
  1024. package/dist/sections/widget-embed.d.ts +45 -0
  1025. package/dist/sections/widget-embed.d.ts.map +1 -0
  1026. package/dist/sections/widget-embed.js +129 -0
  1027. package/dist/sections/widget-embed.js.map +1 -0
  1028. package/dist/security/access.d.ts +35 -0
  1029. package/dist/security/access.d.ts.map +1 -0
  1030. package/dist/security/access.js +133 -0
  1031. package/dist/security/access.js.map +1 -0
  1032. package/dist/security/anomaly-detection.d.ts +17 -0
  1033. package/dist/security/anomaly-detection.d.ts.map +1 -0
  1034. package/dist/security/anomaly-detection.js +39 -0
  1035. package/dist/security/anomaly-detection.js.map +1 -0
  1036. package/dist/security/api-key-enhanced.d.ts +68 -0
  1037. package/dist/security/api-key-enhanced.d.ts.map +1 -0
  1038. package/dist/security/api-key-enhanced.js +76 -0
  1039. package/dist/security/api-key-enhanced.js.map +1 -0
  1040. package/dist/security/audit.d.ts +46 -0
  1041. package/dist/security/audit.d.ts.map +1 -0
  1042. package/dist/security/audit.js +67 -0
  1043. package/dist/security/audit.js.map +1 -0
  1044. package/dist/security/breach-check.d.ts +3 -0
  1045. package/dist/security/breach-check.d.ts.map +1 -0
  1046. package/dist/security/breach-check.js +27 -0
  1047. package/dist/security/breach-check.js.map +1 -0
  1048. package/dist/security/captcha.d.ts +67 -0
  1049. package/dist/security/captcha.d.ts.map +1 -0
  1050. package/dist/security/captcha.js +186 -0
  1051. package/dist/security/captcha.js.map +1 -0
  1052. package/dist/security/center/gather.d.ts +20 -0
  1053. package/dist/security/center/gather.d.ts.map +1 -0
  1054. package/dist/security/center/gather.js +240 -0
  1055. package/dist/security/center/gather.js.map +1 -0
  1056. package/dist/security/center/index.d.ts +13 -0
  1057. package/dist/security/center/index.d.ts.map +1 -0
  1058. package/dist/security/center/index.js +4 -0
  1059. package/dist/security/center/index.js.map +1 -0
  1060. package/dist/security/center/settings.d.ts +48 -0
  1061. package/dist/security/center/settings.d.ts.map +1 -0
  1062. package/dist/security/center/settings.js +218 -0
  1063. package/dist/security/center/settings.js.map +1 -0
  1064. package/dist/security/center/status.d.ts +10 -0
  1065. package/dist/security/center/status.d.ts.map +1 -0
  1066. package/dist/security/center/status.js +249 -0
  1067. package/dist/security/center/status.js.map +1 -0
  1068. package/dist/security/center/summaries.d.ts +21 -0
  1069. package/dist/security/center/summaries.d.ts.map +1 -0
  1070. package/dist/security/center/summaries.js +65 -0
  1071. package/dist/security/center/summaries.js.map +1 -0
  1072. package/dist/security/center/types.d.ts +142 -0
  1073. package/dist/security/center/types.d.ts.map +1 -0
  1074. package/dist/security/center/types.js +12 -0
  1075. package/dist/security/center/types.js.map +1 -0
  1076. package/dist/security/client-ip.d.ts +23 -0
  1077. package/dist/security/client-ip.d.ts.map +1 -0
  1078. package/dist/security/client-ip.js +97 -0
  1079. package/dist/security/client-ip.js.map +1 -0
  1080. package/dist/security/cors.d.ts +11 -0
  1081. package/dist/security/cors.d.ts.map +1 -0
  1082. package/dist/security/cors.js +33 -0
  1083. package/dist/security/cors.js.map +1 -0
  1084. package/dist/security/csp-nonces.d.ts +5 -0
  1085. package/dist/security/csp-nonces.d.ts.map +1 -0
  1086. package/dist/security/csp-nonces.js +24 -0
  1087. package/dist/security/csp-nonces.js.map +1 -0
  1088. package/dist/security/csrf.d.ts +5 -0
  1089. package/dist/security/csrf.d.ts.map +1 -0
  1090. package/dist/security/csrf.js +20 -0
  1091. package/dist/security/csrf.js.map +1 -0
  1092. package/dist/security/encrypted-fields.d.ts +14 -0
  1093. package/dist/security/encrypted-fields.d.ts.map +1 -0
  1094. package/dist/security/encrypted-fields.js +94 -0
  1095. package/dist/security/encrypted-fields.js.map +1 -0
  1096. package/dist/security/headers.d.ts +11 -0
  1097. package/dist/security/headers.d.ts.map +1 -0
  1098. package/dist/security/headers.js +32 -0
  1099. package/dist/security/headers.js.map +1 -0
  1100. package/dist/security/index.d.ts +40 -0
  1101. package/dist/security/index.d.ts.map +1 -0
  1102. package/dist/security/index.js +26 -0
  1103. package/dist/security/index.js.map +1 -0
  1104. package/dist/security/internal-keys.d.ts +15 -0
  1105. package/dist/security/internal-keys.d.ts.map +1 -0
  1106. package/dist/security/internal-keys.js +33 -0
  1107. package/dist/security/internal-keys.js.map +1 -0
  1108. package/dist/security/ip-allowlist.d.ts +15 -0
  1109. package/dist/security/ip-allowlist.d.ts.map +1 -0
  1110. package/dist/security/ip-allowlist.js +141 -0
  1111. package/dist/security/ip-allowlist.js.map +1 -0
  1112. package/dist/security/ip-canon.d.ts +71 -0
  1113. package/dist/security/ip-canon.d.ts.map +1 -0
  1114. package/dist/security/ip-canon.js +352 -0
  1115. package/dist/security/ip-canon.js.map +1 -0
  1116. package/dist/security/middleware.d.ts +20 -0
  1117. package/dist/security/middleware.d.ts.map +1 -0
  1118. package/dist/security/middleware.js +45 -0
  1119. package/dist/security/middleware.js.map +1 -0
  1120. package/dist/security/rate-limit-policy.d.ts +40 -0
  1121. package/dist/security/rate-limit-policy.d.ts.map +1 -0
  1122. package/dist/security/rate-limit-policy.js +57 -0
  1123. package/dist/security/rate-limit-policy.js.map +1 -0
  1124. package/dist/security/rate-limit.d.ts +55 -0
  1125. package/dist/security/rate-limit.d.ts.map +1 -0
  1126. package/dist/security/rate-limit.js +298 -0
  1127. package/dist/security/rate-limit.js.map +1 -0
  1128. package/dist/security/reauth.d.ts +16 -0
  1129. package/dist/security/reauth.d.ts.map +1 -0
  1130. package/dist/security/reauth.js +56 -0
  1131. package/dist/security/reauth.js.map +1 -0
  1132. package/dist/security/redact.d.ts +12 -0
  1133. package/dist/security/redact.d.ts.map +1 -0
  1134. package/dist/security/redact.js +44 -0
  1135. package/dist/security/redact.js.map +1 -0
  1136. package/dist/security/safe-fetch.d.ts +57 -0
  1137. package/dist/security/safe-fetch.d.ts.map +1 -0
  1138. package/dist/security/safe-fetch.js +71 -0
  1139. package/dist/security/safe-fetch.js.map +1 -0
  1140. package/dist/security/sanitize-document.d.ts +14 -0
  1141. package/dist/security/sanitize-document.d.ts.map +1 -0
  1142. package/dist/security/sanitize-document.js +117 -0
  1143. package/dist/security/sanitize-document.js.map +1 -0
  1144. package/dist/security/sanitize.d.ts +13 -0
  1145. package/dist/security/sanitize.d.ts.map +1 -0
  1146. package/dist/security/sanitize.js +64 -0
  1147. package/dist/security/sanitize.js.map +1 -0
  1148. package/dist/security/secret-storage.d.ts +22 -0
  1149. package/dist/security/secret-storage.d.ts.map +1 -0
  1150. package/dist/security/secret-storage.js +75 -0
  1151. package/dist/security/secret-storage.js.map +1 -0
  1152. package/dist/security/security-txt.d.ts +12 -0
  1153. package/dist/security/security-txt.d.ts.map +1 -0
  1154. package/dist/security/security-txt.js +19 -0
  1155. package/dist/security/security-txt.js.map +1 -0
  1156. package/dist/security/session-limits.d.ts +17 -0
  1157. package/dist/security/session-limits.d.ts.map +1 -0
  1158. package/dist/security/session-limits.js +14 -0
  1159. package/dist/security/session-limits.js.map +1 -0
  1160. package/dist/security/upload.d.ts +32 -0
  1161. package/dist/security/upload.d.ts.map +1 -0
  1162. package/dist/security/upload.js +129 -0
  1163. package/dist/security/upload.js.map +1 -0
  1164. package/dist/security/webhook.d.ts +30 -0
  1165. package/dist/security/webhook.d.ts.map +1 -0
  1166. package/dist/security/webhook.js +133 -0
  1167. package/dist/security/webhook.js.map +1 -0
  1168. package/dist/seo/ai-bots.d.ts +56 -0
  1169. package/dist/seo/ai-bots.d.ts.map +1 -0
  1170. package/dist/seo/ai-bots.js +125 -0
  1171. package/dist/seo/ai-bots.js.map +1 -0
  1172. package/dist/seo/ai-crawler-log.d.ts +14 -0
  1173. package/dist/seo/ai-crawler-log.d.ts.map +1 -0
  1174. package/dist/seo/ai-crawler-log.js +48 -0
  1175. package/dist/seo/ai-crawler-log.js.map +1 -0
  1176. package/dist/seo/alt-text.d.ts +39 -0
  1177. package/dist/seo/alt-text.d.ts.map +1 -0
  1178. package/dist/seo/alt-text.js +67 -0
  1179. package/dist/seo/alt-text.js.map +1 -0
  1180. package/dist/seo/analysis.d.ts +75 -0
  1181. package/dist/seo/analysis.d.ts.map +1 -0
  1182. package/dist/seo/analysis.js +641 -0
  1183. package/dist/seo/analysis.js.map +1 -0
  1184. package/dist/seo/audit-engine.d.ts +124 -0
  1185. package/dist/seo/audit-engine.d.ts.map +1 -0
  1186. package/dist/seo/audit-engine.js +534 -0
  1187. package/dist/seo/audit-engine.js.map +1 -0
  1188. package/dist/seo/audit-runner.d.ts +84 -0
  1189. package/dist/seo/audit-runner.d.ts.map +1 -0
  1190. package/dist/seo/audit-runner.js +372 -0
  1191. package/dist/seo/audit-runner.js.map +1 -0
  1192. package/dist/seo/audit-signals.d.ts +28 -0
  1193. package/dist/seo/audit-signals.d.ts.map +1 -0
  1194. package/dist/seo/audit-signals.js +133 -0
  1195. package/dist/seo/audit-signals.js.map +1 -0
  1196. package/dist/seo/config-store.d.ts +99 -0
  1197. package/dist/seo/config-store.d.ts.map +1 -0
  1198. package/dist/seo/config-store.js +194 -0
  1199. package/dist/seo/config-store.js.map +1 -0
  1200. package/dist/seo/confirm-broken-links.d.ts +48 -0
  1201. package/dist/seo/confirm-broken-links.d.ts.map +1 -0
  1202. package/dist/seo/confirm-broken-links.js +135 -0
  1203. package/dist/seo/confirm-broken-links.js.map +1 -0
  1204. package/dist/seo/content-markdown.d.ts +26 -0
  1205. package/dist/seo/content-markdown.d.ts.map +1 -0
  1206. package/dist/seo/content-markdown.js +235 -0
  1207. package/dist/seo/content-markdown.js.map +1 -0
  1208. package/dist/seo/document-content.d.ts +18 -0
  1209. package/dist/seo/document-content.d.ts.map +1 -0
  1210. package/dist/seo/document-content.js +80 -0
  1211. package/dist/seo/document-content.js.map +1 -0
  1212. package/dist/seo/document-path.d.ts +33 -0
  1213. package/dist/seo/document-path.d.ts.map +1 -0
  1214. package/dist/seo/document-path.js +44 -0
  1215. package/dist/seo/document-path.js.map +1 -0
  1216. package/dist/seo/feeds.d.ts +44 -0
  1217. package/dist/seo/feeds.d.ts.map +1 -0
  1218. package/dist/seo/feeds.js +95 -0
  1219. package/dist/seo/feeds.js.map +1 -0
  1220. package/dist/seo/fix-brief-export.d.ts +47 -0
  1221. package/dist/seo/fix-brief-export.d.ts.map +1 -0
  1222. package/dist/seo/fix-brief-export.js +224 -0
  1223. package/dist/seo/fix-brief-export.js.map +1 -0
  1224. package/dist/seo/index-status.d.ts +56 -0
  1225. package/dist/seo/index-status.d.ts.map +1 -0
  1226. package/dist/seo/index-status.js +193 -0
  1227. package/dist/seo/index-status.js.map +1 -0
  1228. package/dist/seo/index.d.ts +68 -0
  1229. package/dist/seo/index.d.ts.map +1 -0
  1230. package/dist/seo/index.js +36 -0
  1231. package/dist/seo/index.js.map +1 -0
  1232. package/dist/seo/issue-batch-briefs.d.ts +37 -0
  1233. package/dist/seo/issue-batch-briefs.d.ts.map +1 -0
  1234. package/dist/seo/issue-batch-briefs.js +85 -0
  1235. package/dist/seo/issue-batch-briefs.js.map +1 -0
  1236. package/dist/seo/issue-content-brief.d.ts +51 -0
  1237. package/dist/seo/issue-content-brief.d.ts.map +1 -0
  1238. package/dist/seo/issue-content-brief.js +267 -0
  1239. package/dist/seo/issue-content-brief.js.map +1 -0
  1240. package/dist/seo/issue-content-research.d.ts +51 -0
  1241. package/dist/seo/issue-content-research.d.ts.map +1 -0
  1242. package/dist/seo/issue-content-research.js +303 -0
  1243. package/dist/seo/issue-content-research.js.map +1 -0
  1244. package/dist/seo/issue-fix-ai.d.ts +39 -0
  1245. package/dist/seo/issue-fix-ai.d.ts.map +1 -0
  1246. package/dist/seo/issue-fix-ai.js +320 -0
  1247. package/dist/seo/issue-fix-ai.js.map +1 -0
  1248. package/dist/seo/issue-fix-redirect.d.ts +18 -0
  1249. package/dist/seo/issue-fix-redirect.d.ts.map +1 -0
  1250. package/dist/seo/issue-fix-redirect.js +153 -0
  1251. package/dist/seo/issue-fix-redirect.js.map +1 -0
  1252. package/dist/seo/issue-fix.d.ts +72 -0
  1253. package/dist/seo/issue-fix.d.ts.map +1 -0
  1254. package/dist/seo/issue-fix.js +266 -0
  1255. package/dist/seo/issue-fix.js.map +1 -0
  1256. package/dist/seo/link-graph.d.ts +54 -0
  1257. package/dist/seo/link-graph.d.ts.map +1 -0
  1258. package/dist/seo/link-graph.js +0 -0
  1259. package/dist/seo/link-graph.js.map +1 -0
  1260. package/dist/seo/link-probe.d.ts +49 -0
  1261. package/dist/seo/link-probe.d.ts.map +1 -0
  1262. package/dist/seo/link-probe.js +147 -0
  1263. package/dist/seo/link-probe.js.map +1 -0
  1264. package/dist/seo/llms-select.d.ts +34 -0
  1265. package/dist/seo/llms-select.d.ts.map +1 -0
  1266. package/dist/seo/llms-select.js +67 -0
  1267. package/dist/seo/llms-select.js.map +1 -0
  1268. package/dist/seo/llms-txt.d.ts +40 -0
  1269. package/dist/seo/llms-txt.d.ts.map +1 -0
  1270. package/dist/seo/llms-txt.js +177 -0
  1271. package/dist/seo/llms-txt.js.map +1 -0
  1272. package/dist/seo/meta-tags.d.ts +108 -0
  1273. package/dist/seo/meta-tags.d.ts.map +1 -0
  1274. package/dist/seo/meta-tags.js +353 -0
  1275. package/dist/seo/meta-tags.js.map +1 -0
  1276. package/dist/seo/migrate-meta.d.ts +23 -0
  1277. package/dist/seo/migrate-meta.d.ts.map +1 -0
  1278. package/dist/seo/migrate-meta.js +114 -0
  1279. package/dist/seo/migrate-meta.js.map +1 -0
  1280. package/dist/seo/migrate-schema.d.ts +28 -0
  1281. package/dist/seo/migrate-schema.d.ts.map +1 -0
  1282. package/dist/seo/migrate-schema.js +119 -0
  1283. package/dist/seo/migrate-schema.js.map +1 -0
  1284. package/dist/seo/page-meta.d.ts +179 -0
  1285. package/dist/seo/page-meta.d.ts.map +1 -0
  1286. package/dist/seo/page-meta.js +710 -0
  1287. package/dist/seo/page-meta.js.map +1 -0
  1288. package/dist/seo/public-site-url.d.ts +16 -0
  1289. package/dist/seo/public-site-url.d.ts.map +1 -0
  1290. package/dist/seo/public-site-url.js +39 -0
  1291. package/dist/seo/public-site-url.js.map +1 -0
  1292. package/dist/seo/resolve-path.d.ts +22 -0
  1293. package/dist/seo/resolve-path.d.ts.map +1 -0
  1294. package/dist/seo/resolve-path.js +53 -0
  1295. package/dist/seo/resolve-path.js.map +1 -0
  1296. package/dist/seo/rewrite-public-origin.d.ts +12 -0
  1297. package/dist/seo/rewrite-public-origin.d.ts.map +1 -0
  1298. package/dist/seo/rewrite-public-origin.js +17 -0
  1299. package/dist/seo/rewrite-public-origin.js.map +1 -0
  1300. package/dist/seo/robots.d.ts +45 -0
  1301. package/dist/seo/robots.d.ts.map +1 -0
  1302. package/dist/seo/robots.js +90 -0
  1303. package/dist/seo/robots.js.map +1 -0
  1304. package/dist/seo/schema-sync.d.ts +65 -0
  1305. package/dist/seo/schema-sync.d.ts.map +1 -0
  1306. package/dist/seo/schema-sync.js +163 -0
  1307. package/dist/seo/schema-sync.js.map +1 -0
  1308. package/dist/seo/score.d.ts +132 -0
  1309. package/dist/seo/score.d.ts.map +1 -0
  1310. package/dist/seo/score.js +216 -0
  1311. package/dist/seo/score.js.map +1 -0
  1312. package/dist/seo/serving-origin.d.ts +42 -0
  1313. package/dist/seo/serving-origin.d.ts.map +1 -0
  1314. package/dist/seo/serving-origin.js +83 -0
  1315. package/dist/seo/serving-origin.js.map +1 -0
  1316. package/dist/seo/site-identity.d.ts +60 -0
  1317. package/dist/seo/site-identity.d.ts.map +1 -0
  1318. package/dist/seo/site-identity.js +109 -0
  1319. package/dist/seo/site-identity.js.map +1 -0
  1320. package/dist/seo/sitemap-notify.d.ts +123 -0
  1321. package/dist/seo/sitemap-notify.d.ts.map +1 -0
  1322. package/dist/seo/sitemap-notify.js +270 -0
  1323. package/dist/seo/sitemap-notify.js.map +1 -0
  1324. package/dist/seo/sitemap-public.d.ts +43 -0
  1325. package/dist/seo/sitemap-public.d.ts.map +1 -0
  1326. package/dist/seo/sitemap-public.js +213 -0
  1327. package/dist/seo/sitemap-public.js.map +1 -0
  1328. package/dist/seo/title-templates.d.ts +80 -0
  1329. package/dist/seo/title-templates.d.ts.map +1 -0
  1330. package/dist/seo/title-templates.js +175 -0
  1331. package/dist/seo/title-templates.js.map +1 -0
  1332. package/dist/server-site.d.ts +55 -0
  1333. package/dist/server-site.d.ts.map +1 -0
  1334. package/dist/server-site.js +184 -0
  1335. package/dist/server-site.js.map +1 -0
  1336. package/dist/setup/index.d.ts +68 -0
  1337. package/dist/setup/index.d.ts.map +1 -0
  1338. package/dist/setup/index.js +153 -0
  1339. package/dist/setup/index.js.map +1 -0
  1340. package/dist/site.d.ts +178 -0
  1341. package/dist/site.d.ts.map +1 -0
  1342. package/dist/site.js +222 -0
  1343. package/dist/site.js.map +1 -0
  1344. package/dist/storage/auto-wire.d.ts +14 -0
  1345. package/dist/storage/auto-wire.d.ts.map +1 -0
  1346. package/dist/storage/auto-wire.js +60 -0
  1347. package/dist/storage/auto-wire.js.map +1 -0
  1348. package/dist/storage/index.d.ts +21 -0
  1349. package/dist/storage/index.d.ts.map +1 -0
  1350. package/dist/storage/index.js +14 -0
  1351. package/dist/storage/index.js.map +1 -0
  1352. package/dist/templates/index.d.ts +16 -0
  1353. package/dist/templates/index.d.ts.map +1 -0
  1354. package/dist/templates/index.js +23 -0
  1355. package/dist/templates/index.js.map +1 -0
  1356. package/dist/upgrade/changelog.d.ts +18 -0
  1357. package/dist/upgrade/changelog.d.ts.map +1 -0
  1358. package/dist/upgrade/changelog.js +60 -0
  1359. package/dist/upgrade/changelog.js.map +1 -0
  1360. package/dist/upgrade/index.d.ts +7 -0
  1361. package/dist/upgrade/index.d.ts.map +1 -0
  1362. package/dist/upgrade/index.js +4 -0
  1363. package/dist/upgrade/index.js.map +1 -0
  1364. package/dist/upgrade/upgrade-pr.d.ts +16 -0
  1365. package/dist/upgrade/upgrade-pr.d.ts.map +1 -0
  1366. package/dist/upgrade/upgrade-pr.js +128 -0
  1367. package/dist/upgrade/upgrade-pr.js.map +1 -0
  1368. package/dist/upgrade/version-check.d.ts +59 -0
  1369. package/dist/upgrade/version-check.d.ts.map +1 -0
  1370. package/dist/upgrade/version-check.js +142 -0
  1371. package/dist/upgrade/version-check.js.map +1 -0
  1372. package/dist/validation/publish.d.ts +53 -0
  1373. package/dist/validation/publish.d.ts.map +1 -0
  1374. package/dist/validation/publish.js +75 -0
  1375. package/dist/validation/publish.js.map +1 -0
  1376. package/dist/webhooks/index.d.ts +99 -0
  1377. package/dist/webhooks/index.d.ts.map +1 -0
  1378. package/dist/webhooks/index.js +387 -0
  1379. package/dist/webhooks/index.js.map +1 -0
  1380. package/dist/webhooks/signing.d.ts +68 -0
  1381. package/dist/webhooks/signing.d.ts.map +1 -0
  1382. package/dist/webhooks/signing.js +98 -0
  1383. package/dist/webhooks/signing.js.map +1 -0
  1384. package/dist/workflow/index.d.ts +8 -0
  1385. package/dist/workflow/index.d.ts.map +1 -0
  1386. package/dist/workflow/index.js +56 -0
  1387. package/dist/workflow/index.js.map +1 -0
  1388. package/dist/workflows/index.d.ts +30 -0
  1389. package/dist/workflows/index.d.ts.map +1 -0
  1390. package/dist/workflows/index.js +14 -0
  1391. package/dist/workflows/index.js.map +1 -0
  1392. package/package.json +250 -0
  1393. package/prisma/migrations/0001_init/migration.sql +941 -0
  1394. package/prisma/migrations/0002_media_usage_field_path/migration.sql +11 -0
  1395. package/prisma/migrations/0003_ai_crawler_hits/migration.sql +21 -0
  1396. package/prisma/migrations/0004_drop_search_vector/migration.sql +4 -0
  1397. package/prisma/migrations/0005_script_tag_delivery_controls/migration.sql +8 -0
  1398. package/prisma/migrations/0006_totp_last_step/migration.sql +4 -0
  1399. package/prisma/migrations/0007_workflow_fk_drop_dead_enums/migration.sql +27 -0
  1400. package/prisma/migrations/0008_form_delivery_attempts/migration.sql +29 -0
  1401. package/prisma/migrations/0009_webhook_secret_rotation/migration.sql +6 -0
  1402. package/prisma/migrations/0010_media_is_decorative/migration.sql +3 -0
  1403. package/prisma/migrations/0011_media_content_hash/migration.sql +4 -0
  1404. package/prisma/migrations/0012_rename_to_prenta/migration.sql +89 -0
  1405. package/prisma/migrations/migration_lock.toml +3 -0
  1406. package/prisma/schema.prisma +995 -0
  1407. package/prisma/seed.ts +82 -0
  1408. package/schema-fragments/cms-schema.prisma +526 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,2974 @@
1
+ # @actuate-media/cms-core
2
+
3
+ ## 0.118.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5eea995: Product rename: ActuateCMS is now **Prenta**. Every package moves from the
8
+ `@actuate-media/*` scope to `@prenta/*` with simplified names (`@prenta/core`,
9
+ `@prenta/admin`, `create-prenta`), and version numbers continue from where the
10
+ old scope left off.
11
+
12
+ This is a clean break — no backwards-compat aliases:
13
+ - **npm packages**: `@actuate-media/cms-core` → `@prenta/core`,
14
+ `@actuate-media/cms-admin` → `@prenta/admin`, all other packages keep their
15
+ suffix under `@prenta/*`. `create-actuate-cms` → `create-prenta`
16
+ (`npm create prenta`), `create-actuate-astro` → `create-prenta-astro`.
17
+ CLI bins: `actuate`/`actuate-cms` → `prenta`/`prenta-cms`; `actuate-mcp` →
18
+ `prenta-mcp`.
19
+ - **Database**: migration `0012_rename_to_prenta` renames all 36 `actuate_*`
20
+ tables to `prenta_*` in place (plus prefixed constraints, indexes, and
21
+ sequences). Run `prisma migrate deploy` when upgrading.
22
+ - **Runtime identifiers**: `getActuateConfig` → `getPrentaConfig`,
23
+ `handleActuateAPI` → `handlePrentaAPI`, `ActuateClient` → `PrentaClient`,
24
+ `ActuateDB` → `PrentaDB`, config file `actuate.config.ts` →
25
+ `prenta.config.ts`, CSS scopes `.actuate-admin`/`.actuate-canvas` →
26
+ `.prenta-admin`/`.prenta-canvas`, preview protocol `actuate-preview:*` →
27
+ `prenta-preview:*`, webhook headers `X-Actuate-*` → `X-Prenta-*`, session
28
+ cookie `actuate_session` → `prenta_session`, env vars `ACTUATE_*` →
29
+ `PRENTA_*`, purge route `/api/actuate/purge` → `/api/prenta/purge`.
30
+ - **Sessions and webhooks**: all sessions reset on upgrade (cookie + JWT issuer
31
+ changed); webhook consumers must re-verify signatures against the new
32
+ `X-Prenta-*` headers.
33
+
34
+ The old `@actuate-media/*` packages are deprecated on npm and will receive no
35
+ further releases.
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [5eea995]
40
+ - @prenta/realtime@0.2.0
41
+
42
+ ## 0.117.0
43
+
44
+ ### Minor Changes
45
+
46
+ - 02be20a: Visual editing Stage 3: path-aware section content updates, preview bridge edit/mode/request-media protocol with suppress-echo for canvas text edits, and media field-kind annotations on default section renderers.
47
+
48
+ ## 0.116.0
49
+
50
+ ### Minor Changes
51
+
52
+ - 73854fc: Add `siteIdentityJsonLd*` / `jsonLdHasOrganization` so any page can emit resolvable WebSite + Organization JSON-LD (fixes dangling `#organization` @ids). Add `assertServingOriginConfigured` ops guard (Launch Gate E) and wire it into the Astro starter middleware + CMS API route boot; BaseLayout falls back to site-identity JSON-LD when page meta lacks Organization.
53
+
54
+ ## 0.115.0
55
+
56
+ ### Minor Changes
57
+
58
+ - 576f076: Fix `blogPostCollection` archivePath footgun: deriving with a new slug/urlPrefix rebases `seo.archivePath` onto the urlPrefix unless set explicitly; `defineConfig` warns on duplicate archive paths. Astro starter/example: hybrid prerender defaults (`404.astro` prerendered; CMS catch-all / home / blog index explicit `prerender = false`).
59
+
60
+ ## 0.114.0
61
+
62
+ ### Minor Changes
63
+
64
+ - dfffe08: Add `publicPageCacheControl()` on `@actuate-media/cms-core/astro/cache` (ADR 0011 cache-static defaults: 1h s-maxage / 24h SWR; preview → `private, no-store`) and wire the Astro starter + example site to use it instead of hardcoded Cache-Control strings. Purge remains path/URL-based per ADR 0011.
65
+
66
+ ## 0.113.0
67
+
68
+ ### Minor Changes
69
+
70
+ - 38aa813: Resilient `createSiteClient`: default 12s `AbortSignal.timeout()` on CMS fetches, and degrade to `null` on non-OK / network / timeout instead of throwing — so a CMS blip fails open for every Astro/Next public consumer. New export: `DEFAULT_SITE_CLIENT_TIMEOUT_MS`; opt out with `timeoutMs: 0`.
71
+
72
+ ## 0.112.0
73
+
74
+ ### Minor Changes
75
+
76
+ - cf97a40: Launch-critical Astro SEO + freshness: `documentPublicPath` / `rewritePublicOrigin`, first-class `AstroPageMeta.robots`, admin/MCP path builders (#739), Yoast child sitemap + XSL proxies with origin rewrite, production purge 503 without CDN credentials, globals purge webhooks, and Launch Gate docs.
77
+
78
+ ## 0.111.0
79
+
80
+ ### Minor Changes
81
+
82
+ - 90f9801: Purge-on-publish as product (ADR 0011): split-deploy public sites can now get ISR-grade freshness — cache-static delivery with instant eviction on publish.
83
+
84
+ **cms-core**
85
+ - New `@actuate-media/cms-core/purge` subpath: `createPublishPurgeReceiver()` — a framework-agnostic `(Request) => Response` handler that consumes publish-lifecycle webhooks. Verifies HMAC signature + timestamp skew (via `verifyActuateWebhook`), dedupes on `X-Actuate-Delivery` (pluggable store, in-memory default, marked only after a successful purge so CMS retries re-purge), maps documents to site paths via `resolvePaths` (default: full purge; `createPrefixPathResolver` helper included), and calls a `CachePurgeAdapter`.
86
+ - Shipped purge adapters (zero-dependency, plain fetch): `createCloudflarePurgeAdapter` (zone `purge_cache` by URL with 30-file batching, `purge_everything` fallback), `createVercelDeployHookPurgeAdapter` (full purge via redeploy), `createVercelIsrPurgeAdapter` (targeted, redeploy-free revalidation via the prerender bypass token).
87
+ - New `@actuate-media/cms-core/astro/cache` subpath: `createUpstashSiteCacheAdapter()` — a zero-dependency Upstash REST `SiteCacheAdapter` with `delete()`/`clear()` so the purge receiver can drain the runtime KV layer on publish (`onPurged: () => cache.clear()`).
88
+ - New `document.unpublished` webhook event, emitted when a document leaves PUBLISHED (was previously an indistinguishable `document.updated`). Purge receivers rely on it to evict taken-down pages; endpoints subscribed only to `document.updated` no longer receive unpublish transitions.
89
+ - Publish-sync guidance (`publicHostNote`) now points editors at the purge endpoint (`/api/actuate/purge` in the Astro starter) when no publish webhook is configured, instead of only describing TTL lag.
90
+
91
+ **cms-admin**
92
+ - Settings → Webhooks now creates endpoints subscribed to the full publish lifecycle (`document.published`, `document.unpublished`, `document.updated`, `document.deleted`) so purge/rebuild receivers see unpublishes, live-page edits, and deletions — not just publishes.
93
+
94
+ ### Patch Changes
95
+
96
+ - 90f9801: Scheduled publish/unpublish now runs through the same effects pipeline as manual publish. Previously `processScheduledPublish` / `processScheduledUnpublish` did a bare `db.document.update()`, so timed publishes never emitted `document.published` / `document.updated` webhooks (purge-on-publish receivers and rebuild hooks were never notified), never notified the sitemap, never invalidated public caches, and skipped the Version audit row, media-usage sync, and plugin `afterUpdate` hooks. Scheduled transitions now call `updateDocument` with the document's last editor as the acting user, which also means the server-side publish gate applies: a scheduled page/post that fails a hard invariant (no title / no URL) stays SCHEDULED and is reported in the cron result's `errors` instead of going live broken.
97
+
98
+ ## 0.110.2
99
+
100
+ ### Patch Changes
101
+
102
+ - 56c1309: Fix page tag saves silently no-oping when `tags` / `primaryTag` were undeclared (passthrough + scaffold `page-tags` collection), and replace the status-token tag palette with distinct `--tag-*` colors so pink is no longer warning orange.
103
+
104
+ ## 0.110.1
105
+
106
+ ### Patch Changes
107
+
108
+ - f4766d4: Bump `sharp` to `^0.35.0` so consumers inherit the patched libvips line (GHSA-f88m-g3jw-g9cj) instead of resolving `0.34.5` from the previous pin.
109
+
110
+ ## 0.110.0
111
+
112
+ ### Minor Changes
113
+
114
+ - 46ea4a7: Hash final stored media bytes (SHA-256) on upload and return the existing record with `deduplicated` / `duplicateOf` when a match exists, so seed re-runs and byte-identical twins no longer accumulate. Opt out with `forceUpload`.
115
+
116
+ ## 0.109.0
117
+
118
+ ### Minor Changes
119
+
120
+ - e4e55de: Add `POST /media/bulk-update` and `POST /media/bulk-delete` with per-id results (max 50), plus MCP tools `bulk_update_media` and `bulk_delete_media` for library-wide metadata backfills without one round trip per asset.
121
+
122
+ ## 0.108.0
123
+
124
+ ### Minor Changes
125
+
126
+ - 6eaffa5: Treat decorative media (`isDecorative` + empty alt) as alt-complete in the media SEO scorer, add a non-scoring `low-quality-alt` advisory, deep-merge media fields on document update so partial `featuredImage` patches keep `url`/`id`, and expose `isDecorative` on `update_media`.
127
+
128
+ ## 0.107.3
129
+
130
+ ### Patch Changes
131
+
132
+ - 1c2afa5: Give anonymous public read endpoints (`/resolve`, `/public/*`, etc.) their own higher rate-limit budget so SSR front-ends sharing one egress IP are not 429’d by crawler traffic.
133
+
134
+ ## 0.107.2
135
+
136
+ ### Patch Changes
137
+
138
+ - b305f0c: Confirm internal-link “404” audit findings against the live site before flagging, and never suggest or auto-apply a recovery redirect for a source URL that still returns 2xx/3xx (prevents taking hybrid/native routes offline).
139
+
140
+ ## 0.107.1
141
+
142
+ ### Patch Changes
143
+
144
+ - a26f61a: Fix Live link health false positives: extract outbound URLs from unescaped document fields (not `JSON.stringify`), strip trailing `\` artifacts, probe with GET + browser UA, treat 3xx as healthy and 403/405/429/999 as unverifiable, and skip first-party Vercel Blob media.
145
+
146
+ ## 0.107.0
147
+
148
+ ### Minor Changes
149
+
150
+ - f676bcc: Resolve View live / View Site from the effective public site URL (`GET /site/public-url`, DB SEO overrides first), warn when that URL matches the CMS admin host, unlock Settings → General site URL overrides for split deploys, and default Astro-split `NEXT_PUBLIC_SITE_URL` to the Astro apex.
151
+
152
+ ## 0.106.1
153
+
154
+ ### Patch Changes
155
+
156
+ - 9d5dad3: Harden media storage URL SSRF checks via validateWebhookUrl (#566), clamp negative list `page` params so Prisma never sees a negative skip (#561), and cover DocumentEdit's shared unload guard `returnValue` behavior (#553).
157
+
158
+ ## 0.106.0
159
+
160
+ ### Minor Changes
161
+
162
+ - 3d1a79f: Timestamp-bound HMAC webhook signing with skew/replay verification (`verifyActuateWebhook`), delivery-id headers on form webhooks, publish secret reveal + 24h rotation grace, and consumer verification docs (#679).
163
+
164
+ ## 0.105.0
165
+
166
+ ### Minor Changes
167
+
168
+ - 78671c4: Distinguish CMS Published from public-host freshness: publish-sync API, webhook delivery inbox/retry, cleanup-cron webhook retries, and admin Public host sync + Settings → Webhooks UI (#677).
169
+
170
+ ## 0.104.0
171
+
172
+ ### Minor Changes
173
+
174
+ - 258cbc3: Warn when navigation items link to unpublished or missing internal pages (soft warnings in MCP `validate_navigation_tree`, admin nav builder, and `POST /navigations/validate-targets`).
175
+
176
+ ## 0.103.0
177
+
178
+ ### Minor Changes
179
+
180
+ - 2f2fe29: First-class CAPTCHA policy on public form submit: enforce toggle, fail-open/closed on provider outage with timeout + audit logging, and Settings → Security status card (#676).
181
+
182
+ ## 0.102.0
183
+
184
+ ### Minor Changes
185
+
186
+ - f8a2c07: Persist per-submission form notify/webhook delivery attempts with admin Deliveries inbox, retry, and operator alerts on permanent failure (#675).
187
+
188
+ ## 0.101.2
189
+
190
+ ### Patch Changes
191
+
192
+ - afceb12: Await form submission notification/webhook side effects so they complete on serverless hosts instead of being frozen after the response returns.
193
+
194
+ ## 0.101.1
195
+
196
+ ### Patch Changes
197
+
198
+ - 81aa012: Fix editor save wiping undeclared SEO/media fields (#671), align analysis/readability word counts, and close related clobber paths: SEO write passthrough keys, nested `{ data }` document PUT flatten, flat pages-service payloads, and `focusKeyphrase` → `focusKeyword` normalization.
199
+
200
+ ## 0.101.0
201
+
202
+ ### Minor Changes
203
+
204
+ - 58f3ca1: Split-deploy hardening: denormalize media IDs on document write, regenerate JSON-LD on published updates, include Organization parent for Corporation/OnlineBusiness subtypes, and ship Astro security-headers middleware matching Next baselines.
205
+
206
+ ## 0.100.0
207
+
208
+ ### Minor Changes
209
+
210
+ - cc6ad93: Form notification defaults and shortcode glossary: Reply-To defaults to submitter email, subject includes site name, emails include attribution, Vercel adapter preserves replyTo, and the admin Notifications panel ships a copy-paste shortcode glossary plus Send test.
211
+ - cc6ad93: Split-deploy SEO redirects: ship `applyPublicRedirect` / `createAstroRedirectMiddleware`, wire Astro starters and Next scaffold to the shared engine, clarify Never hit vs Stale, and return 409 on duplicate source updates.
212
+
213
+ ### Patch Changes
214
+
215
+ - cc6ad93: Hard-delete API keys on remove (instead of soft-revoke) and replace browser password prompts with an in-app reauth dialog.
216
+
217
+ ## 0.99.0
218
+
219
+ ### Minor Changes
220
+
221
+ - 80d4e5f: Marketing attribution tracking for forms — captures where every lead came from, automatically.
222
+
223
+ **cms-core**: rebuilt `forms/attribution` (now also published at `@actuate-media/cms-core/forms/attribution`) with a first-touch (30-day) / last-touch (24-hour) model merged at submit, the full ad click-ID registry (gclid, fbclid, msclkid, ttclid, li_fat_id, dclid, twclid, rdt_cid + long-tail IDs in `extraIds`), and referrer classification into marketing channels (paid / organic search / social / email / **ai** / referral / direct) with friendly source names ("ChatGPT" instead of "chat.openai.com"). Form entry payloads now expose the full `attribution` blob, CSV exports gained Source / Medium / Campaign / Channel / Referrer / Landing Page columns, and `form.submitted` webhooks forward the merged attribution to CRM consumers. Note: `AttributionData` gained required fields (`channel`, `referrerName`, `extraIds`, `firstTouchAt`, `lastTouchAt`, `version`) and `clickIds` grew from 5 to 8 networks.
224
+
225
+ **sections-react**: `<ActuateForm>` now records an attribution touch on mount and attaches the merged first/last-touch payload to every submission — no setup required. Capture is consent-gated when `@actuate-media/plugin-consent` is installed (waits for an analytics grant; proceeds when the plugin is absent). New `<AttributionTracker />` component for the site layout improves first-touch fidelity across multi-page journeys.
226
+
227
+ **cms-admin**: the form entry detail pane now shows the full attribution picture — channel with friendly source name, source/medium/campaign/term/content, landing page, referrer, device type, first-touch date, and any ad click IDs — with graceful fallback to the legacy UTM columns for older entries.
228
+
229
+ - 80d4e5f: Complete form entries export: server-side CSV/XLSX for every view.
230
+
231
+ **cms-core**
232
+ - New dependency-free XLSX workbook writer (`forms/xlsx.ts`) — deflate-compressed OOXML, inline strings (injection-safe by construction, values round-trip exactly).
233
+ - `GET /forms/entries/export` now accepts `format=csv|xlsx` and an optional `formId`: omitting it exports entries across **all forms** (Form column + union of data keys). Audit log records scope and format.
234
+ - New `exportFormEntriesXlsx()` service alongside `exportFormEntries()` (both share one export table with attribution columns; `formId` is now optional on both).
235
+
236
+ **cms-admin**
237
+ - The Export buttons on Submissions and All Entries now download the full server-side export (all pages, decrypted values, attribution columns, up to 5,000 rows) instead of only the 25 visible rows — with a CSV / Excel (.xlsx) format menu.
238
+ - All Entries export honors the active form filter.
239
+ - Zapier integration helper now documents the `attribution.*` fields available in webhook payloads.
240
+
241
+ ## 0.98.0
242
+
243
+ ### Minor Changes
244
+
245
+ - c6b006c: Astro discoverability and best practices: `create-actuate-astro` + `--framework astro-site`, Live Content Collections loader (`@actuate-media/cms-core/astro/content`), islands/preview docs, and fleet first-cutover runbook.
246
+ - 21aa417: Consolidate the two form-editing surfaces onto the typed Forms API and make GA4 form analytics work end-to-end.
247
+
248
+ **cms-admin** — The Edit Form page is rebuilt as the canonical full-page form editor, backed by the same typed Forms API as the Forms list's inline Schema pane (no more legacy `/collections/forms` writes that bypassed schema versioning). It now covers everything in one place: Details (name/description/status + post-submit confirmation), Fields (versioned schema editor), **Notifications** (previously only reachable from the Schema pane), Analytics (GA4), Embed, and Integrations (webhooks). Confirmation settings now persist to `successMessage` / `redirectUrl` — the fields the public submission pipeline actually reads (the old editor wrote a `confirmation` object the runtime ignored). New forms are created through `POST /forms`, gaining slug generation, an initial schema version, and notification defaults, then route into the full editor.
249
+
250
+ **cms-core** — `FormDefinition` gains a sanitized `analytics` (GA4) config, persisted via the forms service (`createForm` / `updateForm` / `duplicateForm`) and exposed on the public form schema only when tracking is enabled. New `normalizeAnalyticsConfig` export and a client-safe `@actuate-media/cms-core/forms/analytics` subpath export.
251
+
252
+ **sections-react** — `<ActuateForm>` now fires GA4 `form_start` / `form_submit` / `form_error` events (with optional conversion value) when the form has analytics enabled. Also fixes the success state to read the submit endpoint's `confirmation` _object_ (`{ type, message, redirectUrl }`) — previously it was treated as a string, which broke the success message and ignored server-driven redirects. New `resolveSubmitOutcome` helper with regression coverage.
253
+
254
+ **plugin-forms** — The `forms` collection field map now declares every key the typed forms service persists (`description`, `notificationSettings`, `embedSettings`, `spamSettings`, `successMessage`, `redirectUrl`, `analytics`, `activeSchemaVersionId`, …). The data layer strips undeclared keys on write, so consumer apps registering this plugin would previously drop notification/embed/spam settings silently. Status options now match the typed `FormStatus` (`active` / `draft` / `archived`).
255
+
256
+ ## 0.97.0
257
+
258
+ ### Minor Changes
259
+
260
+ - fb89234: Add `createAstroSiteHelpers` (`@actuate-media/cms-core/astro/site`) for framework-neutral / Astro public sites, with optional pluggable cache. `createSiteClient` now also resolves `ACTUATE_CMS_URL` before `NEXT_PUBLIC_SITE_URL` for split-deploy CMS origins.
261
+
262
+ ## 0.96.0
263
+
264
+ ### Minor Changes
265
+
266
+ - fdfae4c: Make `graphql`, `graphql-yoga`, and `@sentry/node` optional peer dependencies (optional-deps Tier 2).
267
+
268
+ These were hard `dependencies`, so every install paid for them even when the app
269
+ used neither the GraphQL API nor Sentry error reporting. They are now **optional
270
+ peer dependencies** — cms-core loads them lazily and degrades gracefully when
271
+ they're absent.
272
+
273
+ **Breaking changes:**
274
+ - **`createGraphQLHandler` is now async** (`Promise<YogaServerInstance>`). It
275
+ lazily imports `graphql-yoga` on first use. If you call it directly, `await` it:
276
+ ```diff
277
+ - const yoga = createGraphQLHandler({ config, secret })
278
+ + const yoga = await createGraphQLHandler({ config, secret })
279
+ ```
280
+ The built-in `/api/cms/graphql` route already awaits it internally — no change
281
+ needed if you use the standard handler.
282
+ - **GraphQL now requires installing its peers.** To use `/api/cms/graphql`,
283
+ install `graphql` + `graphql-yoga`. Without them the endpoint throws a clear,
284
+ actionable error. Apps scaffolded with `create-actuate-cms` include both.
285
+ - **Sentry now requires installing `@sentry/node`.** Setting `SENTRY_DSN` only
286
+ reports errors when the package is installed; otherwise reporting is a silent
287
+ no-op (it never throws). `initServerSentry` / `captureServerException` are now
288
+ async internally (fire-and-forget at their call sites).
289
+
290
+ No change for apps that don't use GraphQL or Sentry — they simply stop pulling in
291
+ these packages.
292
+
293
+ ## 0.95.1
294
+
295
+ ### Patch Changes
296
+
297
+ - 2ccc0a7: Ship a compiled Prisma client so `@actuate-media/cms-core/generated/client`
298
+ and the DB-adapter helpers actually resolve (#550).
299
+
300
+ The `prisma-client` generator emits **only `.ts`** into the (gitignored)
301
+ `generated/` directory, but nothing ever compiled it: the package `build` was
302
+ `tsc` over `src/` alone, while the `./generated/client` export and the
303
+ `createDatabaseClient*` adapters both pointed at `.js`/`.d.ts` files that were
304
+ never produced. The tarball shipped the raw `.ts` (referencing `.js` siblings),
305
+ so any **non-scaffold** consumer importing the generated client — or using the
306
+ Postgres/MySQL/SQLite adapters — hit a broken import. It went unnoticed because
307
+ scaffolded apps generate their own client and the in-repo adapters have no
308
+ callers, so the monorepo (which transpiles the `.ts` directly) never exercised
309
+ the published path.
310
+
311
+ Fix: a second, isolated build pass (`tsconfig.generated.json`) compiles
312
+ `generated/` into `dist/generated/`, `build` now runs `prisma generate` first
313
+ (the client is gitignored, so it must be regenerated at build time), and the
314
+ `./generated/client` export points at the compiled `dist/generated/client.js` +
315
+ `.d.ts` — the same path the adapters' `../../generated/client.js` resolves to
316
+ from `dist/`. The uncompiled `.ts` is dropped from the published `files`.
317
+ No public API changed; a previously-broken import path now works.
318
+
319
+ ## 0.95.0
320
+
321
+ ### Minor Changes
322
+
323
+ - 0bd5153: Stop every content write from purging the entire cache (#544).
324
+
325
+ A document create/update/delete used to fire the wholesale `actuate` cache tag,
326
+ so a single edit revalidated the **whole site** — cache hit-rate collapsed under
327
+ editorial load. Writes now fire **narrow** tags only: the collection, the
328
+ document, and the document's public URL path(s) — `actuate:path:/blog/my-post`,
329
+ plus the **old path on a slug rename** and the deleted path on delete. Editing
330
+ one page now busts only that page.
331
+ - The write-side path is built with the same `resolveDocumentUrl` the resolver
332
+ inverts, so it exactly matches the `actuate:path:<path>` tag the `/resolve`
333
+ endpoint and ISR site pages apply on read (locked by a round-trip parity test).
334
+ - **Globals keep the wholesale tag** — site settings / navigation are read on
335
+ every page and change rarely, so they still refresh site-wide instantly.
336
+ - The read side still seeds the `actuate` root tag, so `revalidateTag('actuate')`
337
+ remains an operator "purge everything" escape hatch.
338
+
339
+ **Behavior note (documented):** content that _embeds_ a document via `populate`
340
+ (e.g. an author's name on a posts list) now refreshes on its own ISR TTL rather
341
+ than instantly, since writes tag by the document's own path. Fire
342
+ `revalidateTag('actuate')` for an immediate cross-content purge.
343
+
344
+ ## 0.94.0
345
+
346
+ ### Minor Changes
347
+
348
+ - d9096d9: Move `pg` and `@prisma/adapter-pg` from dependencies to **optional peer
349
+ dependencies**.
350
+
351
+ These Postgres driver packages were forced onto every consumer — including
352
+ SQLite-only or non-database setups — even though cms-core only imports them
353
+ **dynamically**, when its Postgres driver adapter is actually used. They're now
354
+ optional peers, so they're no longer installed unless you need them.
355
+
356
+ **Migration:** if you connect cms-core to Postgres via its built-in driver
357
+ adapter (`createPostgresAdapter` / the `database: { provider: 'prisma' }`
358
+ auto-wire), install the drivers yourself:
359
+
360
+ ```bash
361
+ npm install pg @prisma/adapter-pg
362
+ ```
363
+
364
+ Apps scaffolded with `create-actuate-cms` already declare both, so the default
365
+ path is unaffected. If they're missing, the adapter throws a clear
366
+ "PostgreSQL support requires pg and @prisma/adapter-pg" error rather than a
367
+ cryptic module-not-found.
368
+
369
+ ## 0.93.11
370
+
371
+ ### Patch Changes
372
+
373
+ - fe2b661: Add a Report-Only Content-Security-Policy on the admin mount (#567 follow-up).
374
+
375
+ `withActuateCMS()` now emits `Content-Security-Policy-Report-Only` scoped to the
376
+ admin path (`<adminPath>/:path*`). It is **observability, not enforcement** — it
377
+ never blocks a request; the browser reports resources outside the policy (e.g. an
378
+ injected third-party script) so operators can see what an enforcing CSP would
379
+ catch before enabling one. Scoped to the admin (not the whole site) because a CSP
380
+ is content-specific, and kept permissive for the admin's real needs (`'self'` +
381
+ inline scripts/styles + the `https:` storage host). An enforcing CSP and
382
+ `Permissions-Policy` remain opt-in. Composes with any consumer `headers()`.
383
+
384
+ ## 0.93.10
385
+
386
+ ### Patch Changes
387
+
388
+ - 78c6d0f: Fix TOTP re-authentication silently always failing.
389
+
390
+ `verifyReauth(..., 'totp')` passed the **encrypted** `totpSecret` straight to
391
+ `verifyTOTP` without decrypting it, so a correct authenticator code never matched
392
+ — TOTP-based re-auth for sensitive actions (delete user, change role/settings,
393
+ export data, reset another user's password) could never succeed. It now decrypts
394
+ the secret first, matching the login flow, and fails closed if decryption throws.
395
+ Adds the first tests for `verifyReauth` (the gap that let this ship).
396
+
397
+ ## 0.93.9
398
+
399
+ ### Patch Changes
400
+
401
+ - 06e7fbc: Schema hygiene: add the WorkflowState→Document foreign key (#559) and drop six
402
+ dead enums (#558).
403
+ - **#559** — `WorkflowState.documentId` was `@unique` with no relation, allowing
404
+ orphan rows and blocking cascade deletes. It now has a real `document`
405
+ relation with `onDelete: Cascade`. The migration purges any orphans first
406
+ (the workflow feature is unbuilt, so the table is empty in practice).
407
+ - **#558** — six enums (`AuditEvent`, `NotificationType`, `OrderStatus`,
408
+ `PaymentStatus`, `DiscountType`, `ProductType`) were emitted as `CREATE TYPE`
409
+ into every consumer database but referenced by no model. Removed from the
410
+ schema and `DROP TYPE IF EXISTS`'d (no `CASCADE`, so a reuse elsewhere fails
411
+ loudly rather than dropping dependent columns).
412
+
413
+ **Migration required:** `0007_workflow_fk_drop_dead_enums` — run
414
+ `prisma migrate deploy`. Synced across all three schema trees; no application
415
+ code change (the dropped enums had no code references; the FK is additive).
416
+
417
+ ## 0.93.8
418
+
419
+ ### Patch Changes
420
+
421
+ - 28dd310: Make TOTP codes single-use to prevent replay (#565).
422
+
423
+ `verifyTOTP` accepts any code valid within its ±1 time-step window (~90s) with no
424
+ consumed-step tracking, so a captured-but-still-valid authenticator code could be
425
+ replayed to authenticate. The login flow now records the last consumed TOTP
426
+ time-step and rejects any code whose step was already used.
427
+ - New `verifyTOTPStep()` returns the matched time-step counter (and a
428
+ `generateTOTP()` helper for tooling/tests); `verifyTOTP()` is unchanged.
429
+ - `POST /auth/totp/login` consumes the step via a guarded `updateMany`
430
+ (`WHERE lastTotpStep IS NULL OR < step`), which also closes the TOCTOU race
431
+ between two parallel requests carrying the same code. Backup codes keep their
432
+ existing single-use handling.
433
+
434
+ **Migration required:** adds a nullable `User.lastTotpStep` column
435
+ (`0006_totp_last_step`). Additive and safe on existing rows — run
436
+ `prisma migrate deploy`. Behavior change: a valid code can be used only once
437
+ within its 30s window (re-entering the same code makes the user wait for the next
438
+ one), which is the intended replay protection.
439
+
440
+ ## 0.93.7
441
+
442
+ ### Patch Changes
443
+
444
+ - ac2cf04: Ship baseline security headers on all HTML responses (#567).
445
+
446
+ `getSecurityHeaders()` and friends existed but were wired to nothing, so the
447
+ admin SPA and public SSR HTML shipped with no clickjacking, MIME-sniffing, or
448
+ HSTS defaults (the Security docs claimed otherwise). `withActuateCMS()` now adds
449
+ a conservative baseline via Next's `headers()` for every route, composed with
450
+ (never clobbering) any `headers()` the consumer already defines:
451
+ - `X-Content-Type-Options: nosniff`
452
+ - `X-Frame-Options: SAMEORIGIN` — blocks cross-origin clickjacking while keeping
453
+ the admin's **same-origin** site-preview iframe working (deliberately not
454
+ `DENY`).
455
+ - `Referrer-Policy: strict-origin-when-cross-origin`
456
+ - `Strict-Transport-Security: max-age=63072000; includeSubDomains` (no `preload`)
457
+
458
+ `Content-Security-Policy` and `Permissions-Policy` are intentionally **not**
459
+ defaulted — a blanket policy on `/:path*` would break legitimate consumer
460
+ features or the admin's inline scripts/styles; they remain opt-in. Adds an
461
+ optional `headers?` field to the exported `NextConfigLike`. Security docs
462
+ corrected to describe the actual defaults.
463
+
464
+ ## 0.93.6
465
+
466
+ ### Patch Changes
467
+
468
+ - 886238a: Enforce collection `access.read` on `GET /collections/:slug/field-values` (#563).
469
+
470
+ The field-value autocomplete endpoint gated only on `requireCollectionScope`
471
+ (which checks API-key scopes, not the collection's `access.read` rule), so any
472
+ authenticated principal could enumerate the distinct in-use values of a field
473
+ (tags, categories, author names, statuses, …) on a collection whose `access.read`
474
+ would deny them. It now calls `assertCollectionAccess(slug, 'read', ctx)` before
475
+ querying — collection-level, exactly like the `list`/`get` routes — and maps the
476
+ resulting denial to `403` via `actionError`.
477
+
478
+ (The related #543 memory-footprint optimization on this handler — projecting the
479
+ field value in SQL instead of loading full document blobs — is deferred to a
480
+ follow-up with real-Postgres coverage, since a raw-query change can't be verified
481
+ against the unit mock DB.)
482
+
483
+ ## 0.93.5
484
+
485
+ ### Patch Changes
486
+
487
+ - 430ae43: Fix OAuth login failing at the database + honor the session policy (#564).
488
+
489
+ `handleOAuthCallback` created the session row with only `{ userId, expiresAt }`,
490
+ but `Session.token` is a required, unique column — so against real Prisma **every
491
+ OAuth login threw** ("Argument `token` is missing") and bounced the user back to
492
+ the login screen. It passed tests only because the test fake-db skips
493
+ required-field validation (there was no real-DB coverage of the OAuth callback).
494
+
495
+ The callback now mints the session exactly like the password/TOTP flows: prune
496
+ to `maxConcurrentSessions` first, create the JWT with a known session id, and
497
+ write the row with that `id` + `token` + a policy-derived `expiresAt`. OAuth
498
+ sessions therefore now also honor `security.session.timeoutHours` and
499
+ `maxConcurrentSessions` (previously hardcoded to 7 days with no concurrent-session
500
+ enforcement) and record `ipAddress`/`userAgent` for the session UI.
501
+
502
+ Behavior change to note: an OAuth session's lifetime is now the configured
503
+ timeout — identical to today when left at the 168h default, shorter only if an
504
+ admin lowered it. `handleOAuthCallback` gains an optional `session` option
505
+ (injected by the API layer); its `{ token, user }` return is unchanged.
506
+
507
+ ## 0.93.4
508
+
509
+ ### Patch Changes
510
+
511
+ - 899ca9a: Security: verify OAuth email before account adoption (#562) and require write
512
+ scope for media optimization (#566).
513
+
514
+ **OAuth account takeover (nOAuth, #562).** `handleOAuthCallback` adopted an
515
+ existing passwordless account whenever the provider returned a matching email —
516
+ but Google's `email_verified` claim and Microsoft's identity were never checked,
517
+ and Microsoft is accepted from the multi-tenant `common` authority. An attacker
518
+ whose provider asserts an _unverified_ address equal to a victim's could sign in
519
+ as that account. The callback now adopts an existing account by email **only when
520
+ the provider verified the email**: `getUserProfile` sets `emailVerified` per
521
+ provider (Google from `email_verified`; GitHub `true`; Microsoft `false` — its
522
+ `common`-authority identity can't be trusted), and an unverified match is
523
+ rejected with guidance to link from Settings instead. Already-linked users are
524
+ unaffected (they resolve by `(provider, providerAccountId)` first). Note: this
525
+ adds `emailVerified` to the exported `OAuthUserProfile`.
526
+
527
+ **Media optimize authorization (#566).** `POST /media/:id/optimize` re-encodes
528
+ and **replaces** the stored blob (deleting the original) but required only
529
+ authentication — any VIEWER, self-signup CLIENT, or scopeless API key could
530
+ iterate media IDs and destroy/rewrite originals. It now requires media scope +
531
+ a write role, mirroring `PUT`/`DELETE` on the same resource, and fetches the
532
+ original through `safeFetch` (SSRF preflight + timeout) instead of raw `fetch`.
533
+
534
+ ## 0.93.3
535
+
536
+ ### Patch Changes
537
+
538
+ - 77b3922: Fix an HTTP 500 when reusing the slug of a soft-deleted document (#560).
539
+
540
+ The `(collection, slug)` unique index counts soft-deleted rows, so deleting a
541
+ document left its slug permanently stranded — creating, renaming, or publishing
542
+ a live document onto that slug threw a Prisma `P2002` that surfaced as an opaque
543
+ `500 Internal server error`, with the blocking row invisible in the admin (all
544
+ readers filter `deletedAt: null`).
545
+
546
+ Two changes:
547
+ - **`deleteDocument` now frees the slug** (sets it to `null`) inside the delete
548
+ transaction, so the URL can be reused immediately. The pre-delete slug is still
549
+ captured for cache/sitemap effects and preserved in the `DELETE` version
550
+ snapshot.
551
+ - **`actionError` maps Prisma `P2002` to `409 Conflict`** with a safe, specific
552
+ message ("A document with this slug already exists in this collection.") — so a
553
+ genuine clash between two _live_ documents returns a clean, client-fixable
554
+ conflict instead of a 500. Covers create, update/rename, publish, and duplicate.
555
+
556
+ ## 0.93.2
557
+
558
+ ### Patch Changes
559
+
560
+ - 4421c32: Fix media upload 500 ("Cannot find package 'sharp'") on Vercel.
561
+
562
+ cms-core optimizes uploaded images with `sharp` and marks it a Next
563
+ `serverExternalPackage` (a native module can't be bundled). Because it's also
564
+ dynamically imported (`import(/* webpackIgnore */ 'sharp')`), Next never traces
565
+ it — so unless the app depends on `sharp` directly, Vercel omits it from the
566
+ function bundle and every image upload 500s.
567
+
568
+ Two fixes:
569
+ - `create-actuate-cms` now adds `sharp` to the generated `package.json`
570
+ dependencies (like it already does for `@vercel/blob`), so scaffolded sites
571
+ bundle it.
572
+ - The `/media/upload` handler now degrades gracefully: if optimization throws
573
+ (e.g. sharp is missing), it stores the original image and logs a warning
574
+ instead of returning a 500. Uploads succeed; add `sharp` to re-enable
575
+ webp/resize/blur-placeholder.
576
+
577
+ Existing sites hit by this: `npm install sharp` and redeploy.
578
+
579
+ ## 0.93.1
580
+
581
+ ### Patch Changes
582
+
583
+ - 898ec8b: Slim and bound the `GET /seo/pages` endpoint.
584
+
585
+ `normalizeSeoPage` spread the entire document (`...page`) into every response
586
+ row — including the raw `data` and `structuredData` JSONB. For section-built
587
+ pages those blobs are large, so the response and server memory grew linearly
588
+ with published content (megabytes to render a table that needs a dozen scalar
589
+ fields). It now returns only the derived summary fields
590
+ (id/collection/updatedAt/url/title/score/issues/metaTitle/metaDescription/
591
+ canonical/schemaType).
592
+
593
+ The route is also bounded: it defaults to `take: 1000` (max 5000) and accepts
594
+ `?limit`/`?offset`, returning `{ data, limit, offset }`, so a large site can page
595
+ instead of pulling every published document at once. The derived-field contract
596
+ the admin relies on is unchanged.
597
+
598
+ ## 0.93.0
599
+
600
+ ### Minor Changes
601
+
602
+ - c231dd0: Add `definePlugin` and migrate all 10 plugins onto the canonical `PluginDefinition` contract.
603
+
604
+ cms-core already exported `PluginDefinition`, but every plugin hand-rolled a
605
+ local `ActuatePlugin` interface — a lossy subset that dropped `version`/`init`
606
+ and, in blocks/email/ai, widened hook payloads to `unknown[]`/`event: string`.
607
+ That's copy-paste drift with no shared source of truth.
608
+
609
+ `cms-core` now exports `definePlugin(plugin: PluginDefinition)` (an identity
610
+ helper like `defineConfig`), and each plugin imports the canonical type instead
611
+ of its own: the local `ActuatePlugin` interfaces are deleted, factory return
612
+ types are `PluginDefinition`, and the widened `hooks.ts` types are retyped as
613
+ `PluginHook[]`. Type-only change — no runtime behavior, plugin `name`, or config
614
+ options changed. Net −69 lines.
615
+
616
+ ## 0.92.0
617
+
618
+ ### Minor Changes
619
+
620
+ - cc18833: SEO audit remediation — 47 verified fixes across the SEO system.
621
+
622
+ Highlights: redirect middleware no longer excludes legacy `.html/.php/.aspx`
623
+ URLs (migrations); robots.txt now `Allow`s the default OG image (`/api/cms/og.png`)
624
+ so social shares render; RSS always emits the mandatory channel `<description>`;
625
+ structured-data auto-detection no longer emits invalid `Review/Event/JobPosting`
626
+ (required fields enforced or the node is suppressed) and no longer mis-types
627
+ collections by substring (`newsletters`→NewsArticle etc.); the sitemap endpoint
628
+ no longer selects full document JSON for up to 45k rows; IndexNow submits on
629
+ every publish instead of being swallowed by the sitemap debounce; the exported
630
+ `renderJsonLdScript()` now escapes `<`; the editor no longer shows two
631
+ conflicting scores and the Content-tab analysis now understands section-built
632
+ pages; plus pagination `rel`/canonical, `og:locale` normalization + alternates,
633
+ canonical absolutization, VideoObject/Product/WebSite-SearchAction builders,
634
+ redirect loop/case handling, plugin-seo sitemap caps + real `lastmod`, and
635
+ plugin link-health routed through `safeFetch`. Adds several new exports on
636
+ `@actuate-media/cms-core` (e.g. `buildVideoObjectSchema`, `buildProductSchema`).
637
+
638
+ ## 0.91.1
639
+
640
+ ### Patch Changes
641
+
642
+ - f9513f3: Prevent a global login lockout on deployments where the client IP can't be
643
+ resolved. When no trusted proxy header is present, login / forgot-password /
644
+ reset-password requests all share one `login:unknown` bucket; using the strict
645
+ 5-per-15-min login limiter there meant a handful of anonymous requests could
646
+ return HTTP 429 to **every** user (a denial of authentication).
647
+
648
+ The shared unknown-IP bucket now uses a higher-ceiling limiter (100/15min) so a
649
+ few anonymous attempts can't lock everyone out, while still bounding abuse. The
650
+ app also logs a one-time startup warning when it can't resolve a trusted client
651
+ IP, pointing operators to set `ACTUATE_TRUST_PROXY=1` (documented) behind a
652
+ reverse proxy — the real fix for non-Vercel hosts.
653
+
654
+ ## 0.91.0
655
+
656
+ ### Minor Changes
657
+
658
+ - b53b4a4: Sync `MediaUsage` join rows on every document create, update, delete, duplicate, and global write. Scans document JSON for media field IDs and image URLs in rich-text HTML so the media library "Used on" drawer stays accurate. Adds `backfillMediaUsage()` export and `actuate migrate:media-usage` for existing content.
659
+ - de419f2: Give plugin hooks a frozen, typed event contract. cms-core now exports
660
+ `PluginHook`, `PluginHookEvent`, and `PluginHookPayload` (document + upload
661
+ variants) as the single source of truth, and dispatches a complete, consistent
662
+ set of events: `afterCreate` / `afterUpdate` / `afterDelete` — each also in a
663
+ collection-namespaced form (`afterUpdate:navigations`, `afterCreate:orders`) —
664
+ plus `afterUpload` (fired post-upload from the media route). Every handler now
665
+ receives one typed payload; document field values live on `payload.data`.
666
+
667
+ Previously core fired only bare `afterCreate`/`afterUpdate` with the raw row, so
668
+ several shipped plugins' hooks were dead: `plugin-media`'s `afterUpload` never
669
+ fired, `plugin-navigation` read fields that didn't exist, and the namespaced
670
+ events `plugin-forms`/`plugin-commerce` declared were never dispatched. Each
671
+ plugin has been aligned to the shared type and dispatched events (and
672
+ `plugin-redirects`' request-time `onRedirectMatch` hook — which the lifecycle
673
+ bus never dispatched — was removed). Undispatched event names are now a
674
+ compile-time error.
675
+
676
+ - 898a3f8: Make plugins actually extend the CMS. Previously `config.plugins` only had their
677
+ `hooks` harvested — a plugin's `collections`, `init`, `fields`, and
678
+ `adminNavItems` were declared on the type but never consumed, so adding a plugin
679
+ registered nothing.
680
+
681
+ `defineConfig` now runs a plugin runner (`applyPlugins`): for each plugin, in
682
+ order, it applies `init(config)`, merges the plugin's `collections` (the
683
+ consumer's config and earlier plugins win on slug conflicts, so a plugin can
684
+ never clobber your collection), and accumulates reusable `fields` presets and
685
+ `adminNavItems`. Merged plugin collections flow through the existing collection
686
+ registry, so they are immediately manageable in the admin (the sidebar derives
687
+ its Content nav from `config.collections`) and queryable over REST. The admin
688
+ sidebar also renders plugin-contributed `adminNavItems` (deduped by path).
689
+
690
+ - d6665dd: Enforce access control on populated relations. `?populate` previously spliced a
691
+ referenced document's raw `data` into the response with no filtering, which (1)
692
+ disclosed unpublished (DRAFT/SCHEDULED) referenced documents through the public,
693
+ unauthenticated `/resolve?populate` endpoint, and (2) bypassed field-level
694
+ `access.read` rules and internal `_`-key stripping on authenticated reads.
695
+
696
+ `buildRelationLookup(db, options)` now strips internal keys and applies the
697
+ target collection's `access.read` for the requesting viewer on all populated
698
+ data, and takes a `publicOnly` option (set on `/resolve`) that restricts
699
+ populated targets to PUBLISHED documents. Also hardens document
700
+ create/update/delete against a missing Prisma `mediaUsage` delegate so a
701
+ secondary index write can never abort the primary content write.
702
+
703
+ ### Patch Changes
704
+
705
+ - 041a228: Search now uses the `idx_actuate_documents_fts` GIN index. The query previously
706
+ OR-combined the tsvector match with an unindexable `ILIKE`, which forced a
707
+ sequential scan of `actuate_documents` on every search; the FTS match now runs
708
+ as the primary (indexable) query and the `ILIKE` substring scan only runs as a
709
+ fallback when FTS matches nothing. Search page size is also clamped (max 100).
710
+ Additionally ships the `schema-fragments/` directory in the package tarball so
711
+ the `./prisma/schema` subpath export resolves in a published install.
712
+ - 041a228: Add the MIT `license` field to every published package (previously missing) and correct `repository.url` to `github.com/actuate-media/actuatecms` (13 packages carried the wrong org).
713
+ - 4198920: Move `cms-schema.prisma` out of `prisma/` so the Prisma language server no longer merges the consumer fragment with the canonical schema. Add a reference PostgreSQL `datasource` so fragment-native types validate in the IDE. The `@actuate-media/cms-core/prisma/schema` export path is unchanged.
714
+ - Updated dependencies [041a228]
715
+ - @actuate-media/realtime@0.1.2
716
+
717
+ ## 0.90.1
718
+
719
+ ### Patch Changes
720
+
721
+ - 3b6d7b3: Add client-safe `@actuate-media/cms-core/seo/analysis` export. Restructure the post/page SEO tab into Yoast-style accordion sections (Focus Keyword, Search Appearance, Social, Analysis, Advanced, Insights, Linking) with live content analysis and AI assists.
722
+
723
+ ## 0.90.0
724
+
725
+ ### Minor Changes
726
+
727
+ - 4afb589: Add legitimacy-gated 404 redirect recovery: wire public 404 logging, filter probe URLs before suggesting redirects, connect broken internal links to the SEO audit approve flow, and seed suggestions during audits.
728
+ - 4afb589: 404 recovery pass 2: AI semantic re-ranking for ambiguous matches, opt-in auto-301 setting, and in-content link fixes for editor typos.
729
+ - 4afb589: Add link graph snapshot and AI-powered internal link suggestions (`GET /seo/links/snapshot`, `POST /seo/links/ai-index`, `POST /seo/links/suggest`). New SEO Operations Center **Links** tab tracks internal/external distribution, orphaned pages, coverage, and live link health scans.
730
+ - 4afb589: Apply the site title template at render time so `%site%` is always included for pages and posts (including RSS feeds and the SEO editor). Clamp AI title/description lengths to industry norms, inherit OG/Twitter from meta tags by default, and accept case-insensitive template tokens like `%Site%`.
731
+
732
+ ### Patch Changes
733
+
734
+ - 4afb589: WCAG-aligned AI alt text: improved vision prompts, feature-gated `/ai/media-metadata` with `aiAltText` suggestions, post-upload auto-suggest in Media, MCP `generate_media_alt` tool, and validation warnings for static image paths outside the media library.
735
+ - 4afb589: Media upload optimization: high-quality **lossy WebP** by default (q82 / q86 for transparent PNGs) for strong compression with minimal visible quality loss. Lossless WebP available via `optimizeImage({ lossless: true })`. Original bytes kept when WebP would not reduce file size.
736
+ - 4afb589: Phase 0 SEO hardening: block protocol-relative redirect destinations, enforce admin sitemapEnabled on public sitemap routes, force noindex on preview /resolve responses, unify document robots resolution across sitemap/meta/composePageMeta, audit-log redirect mutations, and batch link-health document scans.
737
+
738
+ ## 0.89.0
739
+
740
+ ### Minor Changes
741
+
742
+ - 41bd56a: Correctness fixes from the post-Phase-3 polish plan.
743
+
744
+ **plugin-consent — granular consent categories.** Consent is no longer binary: visitors can grant individual categories (`analytics`, `marketing`, `functional`, `preferences`) via a new "Manage preferences" expansion in `ConsentBanner`. Custom selections persist as JSON in the same `am_cookie_consent` key; `all`/`essential` still persist as legacy strings so previously shipped consent gates keep working, and legacy stored values migrate transparently on read. New exports: `CONSENT_CATEGORIES`, `ConsentCategory`, `ConsentState`, `readConsentState`, `writeConsentState`, `setConsentCategories`, `hasCategoryConsent`, `categoriesForLevel`, `normalizeConsentSelection`, `waitForConsentState`. `useConsent` now reports `categories` and an `allows(category)` helper; `loadConsentGatedAnalytics` gates GA4/Google Ads on `analytics` and Meta Pixel on `marketing` when passed a consent state.
745
+
746
+ **cms-core — script-tag consent gate honors categories.** `wrapCodeInConsentGate` now grants when the visitor accepted all cookies _or_ granted the tag's specific category through a custom selection, instead of requiring full consent for every gated tag. Legacy stored `'all'` strings still pass.
747
+
748
+ **cms-admin — router query strings + idle-logout warning.** `useAdminRouter` now includes the query string in `currentPath` on mount and popstate, so deep links like `/pages/new?parent=<id>` and `/settings?tab=users` survive hard reloads and back/forward. New `parseRouteQuery` helper and `search` return value; the command palette's `?selected=` deep links now open the media detail panel and user drawer. `useIdleLogout` gains a warning stage — one minute before idle sign-out a toast offers "Stay signed in" to reset the clock.
749
+
750
+ ## 0.88.0
751
+
752
+ ### Minor Changes
753
+
754
+ - 9046381: Design parity phase 3 (wave 2): enforced security settings, Post Type editor, Page Builder sections panel, Templates card grid, and the remaining split-pane editor conversions.
755
+
756
+ **cms-core**
757
+ - Security settings gain enforced session and access controls, stored in the settings global: `session.timeoutHours` (1/8/24/168) now drives the JWT expiry, Session row `expiresAt`, and cookie `Max-Age` at login; `session.idleLogoutEnabled` is exposed to clients via a new `GET /auth/session-policy` endpoint; `access.ipAllowlist` is enforced on every API request as the union of the DB list and config `admin.ipAllowlist`, with per-line CIDR validation and a confirmable `IP_LOCKOUT_RISK` guard when a save would lock the caller out; `access.auditLoggingEnabled` makes `logEvent()` a no-op except for security-critical events (failed logins, MFA changes, and security-settings changes are always recorded).
758
+ - Security Center reporting now reflects the configured session max age, audit state, and effective allowlist instead of hardcoded values.
759
+
760
+ **cms-admin**
761
+ - New Post Type editor at `/posts/types/:slug/edit` (replacing the fallthrough to the documents list): toolbar with breadcrumb and post-count badge, identity panel with live API endpoint preview, and a Field Builder that renders the collection's real schema with type/required badges plus an Add Field snippet generator (auto-derived keys, duplicate detection) for the config-defined schema.
762
+ - Settings → Security gains Sessions (timeout select, idle auto-logout toggle) and Access Control (editable IP allowlist with inline validation and lockout confirm, audit logging toggle) sections; idle logout is enforced client-side via a new `useIdleLogout` hook.
763
+ - The node-tree Page Builder gains a collapsible left sections panel (dnd-kit reorder, select-to-inspect, localStorage persistence), and Page Templates is rebuilt as the design's card grid with search, category filters, and Edit/Delete for non-built-in templates.
764
+ - SEO Content, collection Entries, and Forms schema editing move from overlay drawers to inline inspector panes (380/400/390px) using the shared SplitPane primitives, preserving all existing fields, validation, and save flows.
765
+
766
+ ## 0.87.0
767
+
768
+ ### Minor Changes
769
+
770
+ - 163ae1d: Design parity phase 3 (wave 1): split-pane editors, Script Tag delivery controls, Media SEO Audit, Pages tree view, and Dashboard onboarding.
771
+
772
+ **cms-core**
773
+ - `ScriptTag` gains `loadAsync`, `disableInPreview` (default on), and `consentCategory` fields (migration `0005_script_tag_delivery_controls`), accepted and validated on the script-tag API routes.
774
+ - `resolveScriptTagsForPath` accepts `{ preview }` and skips preview-disabled tags; the public `GET /script-tags/resolve` endpoint supports `?preview=1`. `loadAsync` injects `async` into external scripts, and consent-gated tags are wrapped in a bootstrap that waits for the consent plugin's "accept all" before injecting. The resolved response shape is unchanged.
775
+ - `ScriptTagGroup` now preserves `async`/`defer` attributes on external scripts.
776
+ - `GET /media` items include server-derived `seoScore`, `seoGrade`, and `seoIssues` (from `calculateMediaSeoScore`), and `PUT /media/:id` accepts `caption`, `description`, and `focusKeyword`.
777
+
778
+ **cms-admin**
779
+ - New `SplitPaneLayout` / `InspectorPane` primitives implementing the design's inline editor-pane pattern; Script Tags is converted to a split-pane view with placement tiles, priority stepper, scope tiles, consent category grid, and Load async / Disable in preview toggles.
780
+ - Media gains a Library / SEO Audit tab bar with summary cards, filter pills, an audit table with score bars, and an Edit flow into the library detail panel (which now edits caption, description, and focus keyword).
781
+ - Pages gains a List / Tree view toggle: hierarchy from `parentPageId` with path-prefix fallback, expand/collapse rows, template pills, and an "Add sub-page" action that preselects the parent (also editable via a new Parent page picker in Page settings).
782
+ - The Dashboard shows a getting-started onboarding block (derived 6-step checklist + quick links) in place of the activity grid while the site has no content or media.
783
+
784
+ **create-actuate-cms**
785
+ - Scaffolded middleware flags `?preview=` requests and the site layout resolves script tags with `preview: true` (uncached) so tags marked "disable in preview" never fire during CMS preview; Prisma assets mirror the new migration.
786
+
787
+ ## 0.86.0
788
+
789
+ ### Minor Changes
790
+
791
+ - 7c105f0: Design-parity phase 2: list-view furniture and header layout per the design handoff.
792
+
793
+ **cms-core**
794
+ - New `POST /collections/:slug/:id/duplicate` endpoint exposing the existing `duplicateDocument` action (create-scope gated, audit-logged, documented in the OpenAPI spec).
795
+ - `GET /posts` rows now include an `image` field — the document's featured-image URL (checks `featuredImage`/`image`/`coverImage`/`thumbnail`, string or `{ url }` media object) for list thumbnails.
796
+
797
+ **cms-admin**
798
+ - Pages and Posts lists gained per-row hover actions (Edit / Duplicate / Delete) via a new shared `RowActions` / `RowActionButton` primitive matching the design's `.ra-icon-btn` spec. Row delete confirms first; the homepage row's delete stays disabled.
799
+ - The Posts list shows the featured-image thumbnail when the post has one, falling back to the type-color block.
800
+ - Media Library header shows Total / Images / Videos / Documents / Storage-used stat pills per the design.
801
+ - Hero CTAs moved from the top bar into the page header (design `.page-hdr` → `.hdr-r`): Dashboard ("New Post" + "View Site"), Pages ("New page"), and Posts ("New Post").
802
+ - The account avatar uses the accent token instead of a hardcoded blue-purple gradient.
803
+ - SEO Technical → Crawl Settings now surfaces all six module toggles the API already supported: sitemap enabled, auto-regenerate on publish, and ping-search-engines join the existing three.
804
+
805
+ ## 0.85.3
806
+
807
+ ### Patch Changes
808
+
809
+ - 685958f: Correct 0.x upgrade guidance: document that npm update only applies patches within the pinned minor; use `actuate upgrade` for minor bumps. Admin Updates panel and update server now show the full upgrade command chain.
810
+
811
+ Clarify scaffold README populate/script-tag and upgrade steps.
812
+
813
+ ## 0.85.2
814
+
815
+ ### Patch Changes
816
+
817
+ - 6a6101a: Align admin UI with the ActuateCMS design system v1.0 handoff: canonical tokens, theme remapping, shell layout, shared Toggle/Input/Card primitives, and appearance accent tint variables.
818
+
819
+ ## 0.85.1
820
+
821
+ ### Patch Changes
822
+
823
+ - 6d254f3: Fix Script Tags delivery and admin toggles: green/red enabled switches with optimistic saves, in-process resolve + executable script rendering in scaffold layouts, pathname header for scoped tags, and cache invalidation on tag writes.
824
+
825
+ ## 0.85.0
826
+
827
+ ### Minor Changes
828
+
829
+ - 1a3cf91: Agent-ready public forms (WebMCP): declarative tool metadata on public form schemas, zero-JS `renderPublicFormHtml` / `ActuateFormHtml`, urlencoded submit parsing, HTML confirmation responses, scaffold form prefetch, `actuate doctor` check, and docs.
830
+
831
+ ## 0.84.0
832
+
833
+ ### Minor Changes
834
+
835
+ - e1ad198: Batch 4 (prototype → production): Organization admin UI in Settings → SEO (name, URL, logo, sameAs), organization.url in JSON-LD, and conditional AggregateRating on Service/Product pages when a reviews widget-embed section includes schema rating fields.
836
+ - e1ad198: Platform site-launch defaults: export `createNextSiteHelpers()` from `@actuate-media/cms-core/next/site` for ISR-friendly cached `/resolve`, scaffold new projects with production plugins and cached catch-all pages, and extend `actuate doctor` with site-launch checks (plugins, SEO routes, cached resolve, SSR sections).
837
+ - 6592e09: Add the `widget-embed` section type for lazy-loaded Elfsight, Trustpilot, and Calendly widgets with reserved min-height (CLS-safe) and optional cookie-consent gating via plugin-consent.
838
+
839
+ ### Patch Changes
840
+
841
+ - a59a501: Batch 6 site performance and SEO: path-scoped ISR cache tags in createNextSiteHelpers, scaffold CMS API cache wiring with onInvalidate, JSON-LD in the catch-all, font preload template, and doctor checks for API cache and structured data HTML.
842
+
843
+ ## 0.83.0
844
+
845
+ ### Minor Changes
846
+
847
+ - 3983016: Yoast-style public sitemap URLs (`/post-sitemap.xml`, `/post-sitemap2.xml`, …), human-readable XSLT browser views, and middleware rewrites so collection sitemaps resolve at the site root.
848
+
849
+ ## 0.82.0
850
+
851
+ ### Minor Changes
852
+
853
+ - c4c41f3: Editor preview improvements for custom consumer templates (#473): configurable `admin.editor.defaultSitePreview`, post canvas reads `data.title` first, and a Site preview nudge on the structure canvas.
854
+ - b22a7bc: Phase 2 batch 1: paginate hot-path redirect/user queries with batched loaders, and enable ISR + Actuate tag revalidation on the dev site public pages. Admin clients fetch paginated redirect rules and request up to 200 team members per page.
855
+
856
+ ## 0.81.0
857
+
858
+ ### Minor Changes
859
+
860
+ - b8b930b: Drop the unused `searchVector` column (FTS uses `plainText` + `title`), add optional Stripe Billing Meter hooks for AI token and SEO AI usage, and document Prisma Postgres backup retention for DR.
861
+
862
+ ## 0.80.1
863
+
864
+ ### Patch Changes
865
+
866
+ - af18915: Migrate API handler, actions, cron, resolve, media, and auth hot paths from raw `console.*` to the structured logger with route-scoped error details on unhandled API failures.
867
+
868
+ ## 0.80.0
869
+
870
+ ### Minor Changes
871
+
872
+ - 5fce703: Enable AI monthly token budget enforcement when a limit is configured. Add plan-tier feature gating for advanced SEO Operations Center features (inline apply, content briefs, research, batch briefs) with admin upgrade messaging and pricing tier documentation.
873
+
874
+ ## 0.79.3
875
+
876
+ ### Patch Changes
877
+
878
+ - 8229d94: Wire Sentry for server API errors and admin ErrorBoundary reporting. Extend the daily cleanup cron to prune document versions (90 days), webhook delivery logs (30 days), and API request metrics (30 days). Add disaster recovery runbook documentation.
879
+
880
+ ## 0.79.2
881
+
882
+ ### Patch Changes
883
+
884
+ - 2966293: Strict rate limiters (login, TOTP, AI, uploads) fail closed when Upstash Redis is missing or errors in production; general API rate limiting remains fail-open (roadmap #393).
885
+
886
+ ## 0.79.1
887
+
888
+ ### Patch Changes
889
+
890
+ - 0ad67c9: Enforce collection `access.read` on document GET/list actions and GraphQL resolvers. Returns 403 when the rule denies the caller (roadmap #392).
891
+
892
+ ## 0.79.0
893
+
894
+ ### Minor Changes
895
+
896
+ - 39e2419: Phase 1–2: inline SEO audit fixes (deterministic + AI approve). Phase A: markdown export of manual/content fixes for dev-team handoff. Phase B: AI content briefs for thin content, heading, and alt-text issues. Phase C: topic research subagent, research-and-brief flow, and batch brief pre-generation. MCP: seven new tools for the audit fix workflow (`export_seo_fix_brief`, suggest/apply fix, content briefs, research-and-brief, batch generate) plus server instructions.
897
+
898
+ ## 0.78.0
899
+
900
+ ### Minor Changes
901
+
902
+ - bc51a1d: Align SEO audit with live page meta output, expand audit checks, wire redirect/link signals, and add `actuate migrate:meta` for bulk meta backfill.
903
+
904
+ ## 0.77.3
905
+
906
+ ### Patch Changes
907
+
908
+ - 9d4e24c: Hide the redundant site name headline on the login screen and in the admin sidebar when a logo image is displayed.
909
+
910
+ Serve favicons as properly sized 32×32 PNGs (and 180×180 apple-touch icons) from dedicated public routes so SVG and oversized sources render correctly in browser tabs.
911
+
912
+ Align SEO audit schema/canonical detection with public page rendering, add `actuate migrate:schema` to backfill structuredDataType on existing content, and fix route-contract tests for new favicon endpoints.
913
+
914
+ - 869620b: Align SEO audit, render, and Content SEO editing: auto-canonical and effective schema type resolution match `composePageMeta()`, schema fields sync on save, Content SEO drawer adds schema type, plugin removes dead `beforeCreate` hook and re-exports cms-core schema detection.
915
+
916
+ ## 0.77.2
917
+
918
+ ### Patch Changes
919
+
920
+ - 9875fa3: Omit temperature for reasoning and Opus 4.7+ models in the governed AI runtime so brand voice analyze and other features work when the default model is Claude Opus. Surface a clearer admin error when providers reject sampling parameters.
921
+
922
+ ## 0.77.1
923
+
924
+ ### Patch Changes
925
+
926
+ - 4dafd90: Fix brand voice analyze failures when Settings > AI uses legacy Anthropic curated model ids (`claude-*-4-latest`): remap saved ids to current API model strings, refresh the curated fallback catalog, surface provider error details on HTTP 400/404, and add plugin-ai fallback plus a setup readiness banner when governed AI is incomplete.
927
+
928
+ ## 0.77.0
929
+
930
+ ### Minor Changes
931
+
932
+ - d97e388: Add brand voice analysis: scan published pages and posts to draft a profile via `POST /ai/brand-voice/analyze`, with a Generate from content panel in Settings > AI.
933
+ - d97e388: Brand voice Phase 2: portable `brand-voice-profile.md` documents with export/import, an advanced markdown editor, and server-side sync between the document and quick-edit fields.
934
+ - d97e388: Brand voice Phase 3: structured rule editors (vocabulary, channel overrides, worked examples), deterministic alignment scoring via `POST /ai/brand-voice/score`, and a check-alignment panel in Settings > AI.
935
+ - d97e388: Phase 4 brand voice: wire alignment scoring into co-author and SEO field generation, add analyze-and-fix endpoint with hybrid deterministic + AI rewrite, and extend profile analyzer with SSRF-safe URL fetch and text file uploads.
936
+
937
+ ## 0.76.3
938
+
939
+ ### Patch Changes
940
+
941
+ - 7a6a140: Support Vercel Marketplace Blob auth via OIDC (`BLOB_STORE_ID` + `VERCEL_OIDC_TOKEN`) instead of requiring `BLOB_READ_WRITE_TOKEN`. Fix `@vercel/blob` client options to use `oidcToken` + `storeId` (never pass OIDC as `token`). Update env diagnostics, upload errors, and `actuate vercel:blob-link` accordingly.
942
+
943
+ ## 0.76.2
944
+
945
+ ### Patch Changes
946
+
947
+ - 90b5826: Allow `prisma generate` without DATABASE_URL by using optional env in prisma.config.ts (CI and fresh clones).
948
+
949
+ ## 0.76.1
950
+
951
+ ### Patch Changes
952
+
953
+ - 4899af9: Fix consumer production builds on Next.js 16 by not injecting a no-op webpack hook in `withActuateCMS`. Add `CRON_SECRET` to scaffold `.env.example`. Remove obsolete `earlyAccess` from scaffolded `prisma.config.ts`. Load project `.env` files in `actuate doctor` and `actuate deploy:check`.
954
+ - df1176a: Remove deprecated `earlyAccess: true` from Prisma config (Prisma 7.8 TypeScript compatibility).
955
+
956
+ ## 0.76.0
957
+
958
+ ### Minor Changes
959
+
960
+ - c656ddc: Add an `embed` (video) section type for YouTube, Vimeo, and Loom.
961
+ - **cms-core:** new `embed` section in the registry (`url`, `title`, `ratio`, `caption`) plus a shared, deterministic `parseEmbed()` provider allowlist (`@actuate-media/cms-core/page-sections`). Section validation rejects any URL that isn't a recognised YouTube/Vimeo/Loom link, so an arbitrary URL can never reach an iframe.
962
+ - **sections-react:** `EmbedSection` renderer — a lazy-loaded provider iframe inside a fixed aspect-ratio box (16:9 / 4:3 / 1:1 / 9:16) so the embed reserves its space and never causes layout shift (CLS). The URL is re-parsed through the same allowlist at render time.
963
+ - **cms-admin:** matching editor-canvas body for `embed` (provider + ratio placeholder) so the page/post editors preview the section instead of showing a generic "custom section" placeholder.
964
+ - **mcp-server:** `list_section_types` description now lists `embed`.
965
+
966
+ ## 0.75.0
967
+
968
+ ### Minor Changes
969
+
970
+ - 5446671: SEO sitemap: wire up the "Include images in sitemap" crawl setting and remove two non-functional toggles.
971
+ - **cms-core:** the per-collection XML sitemap now emits Google image-sitemap `<image:image>` entries for each document's featured image and any `hero`/`feature`/`gallery` section images when "Include images in sitemap" is enabled (the default). URLs are absolutized against the site origin, deduped, and capped per page; the `xmlns:image` namespace is declared only when at least one image is present.
972
+ - **cms-admin + cms-core:** removed the `noindexPaginatedArchives` and `noindexTagPages` crawl toggles. They round-tripped through storage but were never applied — paginated/tag archive pages are rendered by the consumer's frontend, which the CMS doesn't control, so these could not be enforced server-side. Removing them avoids a misleading no-op in Settings → SEO → Technical.
973
+
974
+ ## 0.74.0
975
+
976
+ ### Minor Changes
977
+
978
+ - 2b61b66: Core Web Vitals: give the `hero` section an optional image rendered as the page LCP, and serve AVIF in scaffolded sites.
979
+ - **Hero image (LCP):** the canonical `hero` section now has an optional `imageUrl`/`imageAlt` field. When set, the renderer draws it below the copy as the page's single priority (eager, `fetchpriority=high`) image with a full-width `sizes` hint and intrinsic dimensions — the explicit, dimensioned LCP element the best-practice spec calls for (rather than leaving the LCP to chance). Alt text is required when an image is set (existing accessibility rule). Dimensions are backfilled by the `/resolve` enrichment (hero is now enriched like `feature`).
980
+ - **AVIF delivery:** `create-actuate-cms` now sets `images.formats: ['image/avif', 'image/webp']` so scaffolded sites serve AVIF (≈50% smaller than JPEG) first, then WebP — a direct LCP win via `next/image` format negotiation.
981
+
982
+ ## 0.73.0
983
+
984
+ ### Minor Changes
985
+
986
+ - 7b7f0be: Core Web Vitals: get intrinsic image dimensions onto every image so `next/image` engages and CLS is eliminated.
987
+
988
+ Without `width`/`height`, consumers silently fall back to an unsized `<img>` (no srcset, layout shift). This closes that gap two ways:
989
+ - **Read-time (`/resolve`) enrichment** now covers the **post featured image** and **`hero` section images** (previously only `feature`/`gallery`). The resolver looks up dimensions + alt from `actuate_media` and returns the featured image as `post.featuredImage = { url, width, height, alt }` (new `ResolveFeaturedImage` type). Exposes a `featuredImageUrl()` helper. Purely additive — the stored document is never mutated.
990
+ - **Author-time persistence**: selecting an image in the section editor's media picker now writes the intrinsic `width`/`height` alongside the URL (`imageUrl` → `imageWidth`/`imageHeight`; gallery `url` → `width`/`height`), so dimensions travel with the document even without `/resolve`. Exposes `companionDimensionKeys()`.
991
+
992
+ `create-actuate-cms` + `apps/dev`: the generated post page now feeds the resolved featured-image dimensions into the post header for a CLS-free, dimensioned LCP image.
993
+
994
+ ## 0.72.1
995
+
996
+ ### Patch Changes
997
+
998
+ - 2fa4ebb: Fix admin brand logo: always show the logo (never the favicon), make it responsive, and degrade gracefully on broken images.
999
+ - **cms-core**: `resolveAdminBrand` no longer falls back to the favicon source for the sidebar mark. The favicon is a browser-tab icon, and using it in the admin made the brand appear to "switch" between the logo and the favicon (collapsed rail and the brief no-primary boot state). With no dedicated logo mark, the admin mark is now `null` so the sidebar falls back to the primary logo.
1000
+ - **cms-admin**: The sidebar `BrandLogo` now caps logo width (`max-w-full`) so wide horizontal logos scale down instead of overflowing, prefers the logo in the collapsed rail, and attaches `onError` handlers that fall through the logo chain (and finally the neutral placeholder) instead of rendering the browser's broken-image icon when an asset URL is broken/expired/deleted. The Settings → Appearance upload preview also falls back to the placeholder icon on image load failure.
1001
+
1002
+ ## 0.72.0
1003
+
1004
+ ### Minor Changes
1005
+
1006
+ - 6f140bc: Auto-wire the `platform.storage` shorthand to a real adapter server-side, and keep `actuate.config.ts` client-safe.
1007
+
1008
+ `actuate.config.ts` is imported by the admin client bundle, so putting a platform adapter (e.g. `platform: vercel()`) in it pulls `node:*`/`@vercel/blob` into the client build and breaks it with `Module not found: node:fs`. The config must stay adapter-free.
1009
+ - **cms-core**: When the API handler boots and sees a remote `platform.storage` shorthand (`vercel-blob` → `@actuate-media/platform-vercel`, `s3` → `@actuate-media/platform-aws`) with no adapter registered, it now dynamically imports the matching platform package and registers its storage adapter server-side. The import specifier is computed so bundlers never try to statically resolve an optional peer; a missing package degrades to a clear guidance warning instead of a silent upload failure. An explicitly registered adapter always wins. `setStorageAdapter` / `getStorageAdapter` are now also re-exported from `@actuate-media/cms-core/api` for server-side registration of custom adapters.
1010
+ - **cms-core**: Clearer upload/diagnostics errors — when a Vercel Blob store is linked (`BLOB_STORE_ID`) but `BLOB_READ_WRITE_TOKEN` is missing, messages now explain that Vercel does not provide the token via OIDC and it must be added per-environment.
1011
+ - **create-actuate-cms**: The generated `actuate.config.ts` uses the client-safe `storage: { provider: 'vercel-blob' | 's3' }` shorthand again (reverting the adapter-in-config change that broke the client build); uploads work out of the box via server-side auto-wiring.
1012
+
1013
+ ## 0.71.1
1014
+
1015
+ ### Patch Changes
1016
+
1017
+ - 1c81772: Make storage configuration honest. `platform.storage: { provider: 'vercel-blob' }` is a descriptive shorthand that never registered an adapter, so uploads silently failed with a misleading `BLOB_READ_WRITE_TOKEN` error. Now:
1018
+ - cms-core logs a clear boot-time warning when a remote `provider` shorthand is set but no adapter is registered, and `/media/upload` (used by media + appearance uploads) returns an error that names the real gap — a missing adapter, not a missing token.
1019
+ - `create-actuate-cms` generates `platform: vercel()` (a real adapter) instead of the inert shorthand, and the docs gain a dedicated Storage section.
1020
+
1021
+ - 9e0d82b: Stop persisting the lifecycle `status` inside document `data`. The lifecycle status is a top-level column, but a lifecycle-valued `status` passed in (or left behind by a migration/import script) was hoisted to the column **and** kept in the `data` JSON, where it confused the admin UI. `createDocument`/`updateDocument` now strip a lifecycle-valued `status` from `data` on write (and an edit cleans up already-polluted documents). Collection-defined `status` fields with non-lifecycle values — e.g. forms' `draft`/`active`/`archived` — are preserved.
1022
+
1023
+ ## 0.71.0
1024
+
1025
+ ### Minor Changes
1026
+
1027
+ - 9f5e5dc: Add the AI crawler activity panel (llms.txt measurement layer):
1028
+ - **`cms-core`** — new `AiCrawlerHit` model (`actuate_ai_crawler_hits`) + migration, aggregated per `(bot, surface)` so storage stays bounded. `detectAiBot()` classifies known AI user-agents (training vs retrieval) from the existing taxonomy; `llms.txt`, `llms-full.txt`, and `.md` requests from a known crawler are recorded best-effort. New `GET /ai-crawlers` (admin) returns aggregated activity, and an opt-in honeypot (`seo.llms.honeypot`) appends a trap link to `llms.txt` served by `GET /ai-crawlers/trap`.
1029
+ - **`cms-admin`** — new "AI crawler activity" panel under SEO → Technical showing per-bot hits, type, surface, and last-seen, plus totals. Frames the numbers honestly (visibility, not a traffic channel).
1030
+ - **`create-actuate-cms`** — scaffolded Prisma assets include the new migration so fresh projects get crawler tracking out of the box.
1031
+
1032
+ Recording is always best-effort and gracefully no-ops on databases that predate the model.
1033
+
1034
+ ## 0.70.0
1035
+
1036
+ ### Minor Changes
1037
+
1038
+ - 0bcc567: Add per-page Markdown for the AI/agent layer:
1039
+ - **`GET /md?path=…`** (`@actuate-media/cms-core`) — returns a clean Markdown rendering of a single published page via `documentToMarkdown()`. Served with `X-Robots-Tag: noindex`; noindexed and `llmsInclude: NEVER` documents return `404` (the selection filter is the privacy boundary).
1040
+ - **Clean `/{path}.md` URLs** — `create-actuate-cms` middleware rewrites `/{path}.md` → `/api/cms/md`, so any page is fetchable as Markdown by appending `.md` (e.g. `/about.md`). The HTML page stays canonical and `.md` URLs are never in the sitemap.
1041
+ - **Shared path resolver** — extracted `matchPathToCollection()` so `/md` and `/resolve` agree on which document a public URL maps to (no logic drift).
1042
+
1043
+ ## 0.69.0
1044
+
1045
+ ### Minor Changes
1046
+
1047
+ - a889054: Add the advanced `llms.txt` layer — clean Markdown content export for AI agents:
1048
+ - **`documentToMarkdown()` serializer** (`@actuate-media/cms-core`) — renders a published document to clean Markdown. Rich-text/HTML slots are converted with Turndown; structured section content (and the page-builder block tree) is walked generically using an allow-list of prose/heading keys, so non-content data (URLs, ids, settings) never leaks and new section types render without per-type code.
1049
+ - **`/llms-full.txt`** — opt-in route (`seo.llms.fullEnabled`) serving the same curated, noindex, sitemap-excluded selection as `/llms.txt` with each page's Markdown inlined and delimited by its canonical URL. Bounded by `seo.llms.maxFullBytes` (default 2 MB) with clean truncation.
1050
+ - **Shared selection** — `/llms.txt` and `/llms-full.txt` now share one selector (`selectLlmsDocs` / `toLlmsPage`) so curation and the noindex privacy boundary can't drift.
1051
+ - **Scaffold** — `create-actuate-cms` proxies `/llms-full.txt` at the domain root (always generated; enabling is config-only).
1052
+
1053
+ ## 0.68.0
1054
+
1055
+ ### Minor Changes
1056
+
1057
+ - 4bf16c4: Harden the `/llms.txt` AI discovery file:
1058
+ - **Root proxy** — `create-actuate-cms` now scaffolds `app/llms.txt/route.ts`, exposing the file at the spec-required domain root (`/llms.txt`) in addition to `/api/cms/llms.txt`.
1059
+ - **noindex** — the route now sends `X-Robots-Tag: noindex` (and the scaffolded proxy preserves it) so the machine-facing index isn't itself indexed.
1060
+ - **Sanitization** — titles, descriptions, and the site summary are collapsed to a single line and stripped of markdown link-breaking and control/zero-width characters, then length-capped. Reduces corruption and instruction-smuggling risk from editor fields.
1061
+ - **Curation controls** — new `seo.llms` config: `disabled`, `eligibleCollections` (restrict AUTO selection to high-value types), `maxPerSection` (default 25), and `maxEntries` (default 50) so large archives can't flood the file.
1062
+
1063
+ - 47bed20: Add per-document and site-level curation for the `/llms.txt` AI discovery file:
1064
+ - **Per-doc curation** — `llmsInclude` (AUTO/ALWAYS/NEVER), `llmsDescription`, `llmsSection`, `llmsOptional`, and `llmsPriority` are read by the generator and round-tripped through the `/seo/content/:type/:id` API. ALWAYS pins an entry in regardless of the eligibility heuristic; NEVER excludes it.
1065
+ - **Spec correctness** — entries flagged optional are emitted under the reserved `## Optional` section; explicit section overrides and per-section priority sorting are honored; an optional reviewed intro block can follow the summary.
1066
+ - **Noindex exclusion** — noindexed documents are now excluded from `llms.txt` (the selection filter is the privacy boundary).
1067
+ - **Site config** — new `seo.llms.summary` (the reviewed blockquote, falling back to `defaultMetaDescription`) and `seo.llms.intro`.
1068
+ - **Admin UX** — the Edit SEO drawer gains an "AI discovery (llms.txt)" section: include mode, curated agent description with a character counter, section, and an optional toggle.
1069
+
1070
+ ## 0.67.0
1071
+
1072
+ ### Minor Changes
1073
+
1074
+ - 3ab0649: Add a nuanced AI-crawler policy to robots.txt generation. Training crawlers
1075
+ (GPTBot, ClaudeBot, Google-Extended, CCBot, …) and retrieval/answer-engine
1076
+ crawlers (OAI-SearchBot, PerplexityBot, ChatGPT-User, …) can now be allowed or
1077
+ blocked independently via `seo.robots.aiPolicy: { training, retrieval }`, so a
1078
+ site can stay out of training sets while remaining citable in AI answers. The
1079
+ legacy `seo.robots.blockAIBots` boolean still works (blocks both groups) and is
1080
+ used only when `aiPolicy` is not set. Curated 2026 bot lists are exported as
1081
+ `AI_TRAINING_BOTS` / `AI_RETRIEVAL_BOTS`.
1082
+ - 5545bfd: Auto-create a 301 redirect when a published document's URL changes. When a
1083
+ document that is (and stays) published changes its `slug` or `data.path`, the
1084
+ old URL — already indexed and linked — is kept alive with a permanent redirect
1085
+ to the new URL. Planning is loop-safe and flattens chains: rules pointing at the
1086
+ old path are repointed to the new one, and a stale rule whose source is the
1087
+ now-live new path is removed. Runs post-commit and best-effort, so a redirect
1088
+ failure never rolls back the content save.
1089
+ - 3f7fbf3: Add RSS 2.0 (`/feed.xml`) and JSON Feed 1.1 (`/feed.json`) content feeds.
1090
+
1091
+ Both serve the newest published items across post-type collections (falling back
1092
+ to pages when there are no post collections), newest first, capped at 50, with
1093
+ canonical URLs and per-document `noindex` exclusion. Item descriptions prefer the
1094
+ GEO key takeaway, then the meta description, then the excerpt. Pure builders are
1095
+ exported as `buildRssFeed` / `buildJsonFeed`. New `create-actuate-cms` projects
1096
+ scaffold root `/feed.xml` + `/feed.json` proxy routes.
1097
+
1098
+ - bf00622: Add a GEO "key takeaway" field — a concise, citable 1-2 sentence page summary
1099
+ for generative answer engines.
1100
+ - Editable in the document SEO panel and the SEO Operations Center, each with an
1101
+ AI Generate button.
1102
+ - New `generateKeyTakeaway()` in plugin-ai and a `keyTakeaway` branch on
1103
+ `POST /ai/seo/generate-field`.
1104
+ - Rendered automatically as the Schema.org `abstract` on the page/article node,
1105
+ as a meta-description fallback, and as the preferred `llms.txt` description.
1106
+ - Stored as `data.keyTakeaway` (or `pageSettings.keyTakeaway`).
1107
+
1108
+ - f05c89b: `composePageMeta()` now emits a connected Schema.org `@graph` instead of a
1109
+ single flat node, so every public page (and the `/resolve` + `/seo/meta`
1110
+ responses that build on it) ships one traversable graph: WebSite +
1111
+ Organization (with `sameAs` and an optional SearchAction), the page node
1112
+ (WebPage or Article/BlogPosting), a BreadcrumbList derived from the URL path,
1113
+ FAQ `mainEntity`, a Person node for the author, and a linked specialized entity
1114
+ node for non-page types (LocalBusiness, Service, …). A doc-level
1115
+ `structuredData` override is still honored verbatim.
1116
+
1117
+ This changes the shape of `ComposedPageMeta.jsonLd` from a flat object to
1118
+ `{ '@context', '@graph': [...] }`. Consumers that render it as a single
1119
+ `<script type="application/ld+json">` need no changes; code that read specific
1120
+ top-level keys off `jsonLd` should read them off the relevant `@graph` node.
1121
+
1122
+ - eb3786d: Metadata completeness for the public render path:
1123
+ - `composePageMeta()` now emits a Next.js `metadataBase` (from `seo.siteUrl`) so
1124
+ relative OG/canonical URLs resolve to absolute ones.
1125
+ - `og:image` falls back to the page's own hero / featured / cover image (string
1126
+ or `{ url }`) before the site default and the dynamic `/og.png`.
1127
+ - hreflang alternates are emitted in `<head>` (`<link rel="alternate" hreflang>`)
1128
+ and as `alternates.languages` in the Next metadata, including `x-default`.
1129
+ `/resolve` supplies them from the document's `localeGroupId` siblings.
1130
+ - `/llms.txt` now emits absolute, canonical URLs (honoring `data.path` and
1131
+ `urlPrefix`) and uses the configured `seo.siteName` / `seo.defaultMetaDescription`
1132
+ instead of a hardcoded empty origin and "Actuate CMS" name.
1133
+
1134
+ - 88161e8: Add a server-side publish validation gate. Publishing publicly-routed content
1135
+ (collections typed `page`/`post`) now runs a shared validator in the data layer
1136
+ — so the SDK, MCP tools, agents, and the API can't publish a page that's broken
1137
+ in ways the admin would have caught. Hard, content-agnostic invariants block a
1138
+ publish (a published page must have a title and a URL via slug or `data.path`);
1139
+ softer issues (e.g. missing meta description) are returned as warnings and never
1140
+ block. Configurable via `seo.publishValidation` (`block` default / `warn` /
1141
+ `off`). The documents API now maps these and other action errors to correct
1142
+ status codes (422 validation, 403 permission/access, 404 not-found) instead of a
1143
+ generic 500.
1144
+ - 2f1f398: Add authoring guardrails to the page and post editors. The slug field now shows
1145
+ inline validation (malformed/reserved) using the same `validateSlug` rules the
1146
+ server publish gate enforces, and the client pre-publish check runs that same
1147
+ validation so failures appear in the validation summary instead of only as a
1148
+ post-publish toast. SEO title/description fields gain accessible character
1149
+ counters with quality bands (icon + label, not color alone) and smart
1150
+ placeholders that preview the title/excerpt fallback.
1151
+
1152
+ `@actuate-media/cms-core/client` now re-exports the browser-safe slug helpers
1153
+ (`validateSlug`, `normalizeSlug`, `isValidSlug`, `isReservedSlug`, etc.).
1154
+
1155
+ - a3c5904: Add consistent content freshness signals. A shared `resolveContentDates` helper
1156
+ decides `datePublished`/`dateModified` for every public surface (honoring
1157
+ editorial date fields like `publishedDate`/`publishDate`/`date`), and both
1158
+ JSON-LD and Open Graph now use it so structured data and social tags agree.
1159
+
1160
+ `llms.txt` entries now include an `Updated: YYYY-MM-DD` marker, JSON Feed items
1161
+ emit `date_modified` when newer than publication, the post-archive sitemap
1162
+ `<lastmod>` is derived from the newest post (instead of the current time), and
1163
+ the authenticated SEO preview endpoints now use `publishedAt` for publish time
1164
+ (matching production) instead of `createdAt`.
1165
+
1166
+ - ee53cfc: Unify image alt-text analysis behind a single decorative-aware analyzer
1167
+ (`analyzeImageAlts` / `countMissingAltImages` / `isDecorativeImgTag`) so the SEO
1168
+ score, audit engine, content-health scanner, and cron scan report consistent
1169
+ "missing alt" counts. Decorative images (`alt=""`, `role="presentation"`,
1170
+ `aria-hidden="true"`) are correctly excluded from the missing-alt count.
1171
+
1172
+ The media picker now seeds an inserted image's alt text from the asset's curated
1173
+ library alt text instead of the file name.
1174
+
1175
+ - 218c5b0: Add canonical slug rules and server-side validation. A new `content/slug`
1176
+ module (`normalizeSlug`, `isValidSlug`, `isReservedSlug`, `validateSlug`,
1177
+ `SLUG_PATTERN`, `RESERVED_SLUGS`) is the single source of truth for slug format.
1178
+ The publish gate now rejects malformed (uppercase, spaces, underscores,
1179
+ leading/trailing/double hyphens) and reserved (`admin`, `api`, `sitemap.xml`, …)
1180
+ slugs server-side, and the admin `SlugField` normalizes input to the same rules.
1181
+
1182
+ Unify SEO field-name reads so a value set in any editor surface is honored on
1183
+ the public page: canonical now accepts `canonicalUrl` (plugin-seo / SEO
1184
+ workspace) in addition to `canonical`; robots now accepts `noindex`/`nofollow`
1185
+ (SEO workspace) and `robotsNoindex`/`robotsNofollow` (plugin-seo) alongside
1186
+ `noIndex`/`noFollow`; the SEO analysis endpoint accepts `focusKeyword` alongside
1187
+ `focusKeyphrase`.
1188
+
1189
+ - df5d0de: SEO sitemap hardening: canonical URLs, noindex exclusion, accurate lastmod, hreflang, and IndexNow.
1190
+
1191
+ **cms-core**
1192
+ - Sitemaps and the canonical `<link>` / `og:url` now resolve through a single shared `resolveDocumentUrl()` helper, so they can never disagree. It honours an explicit `data.path` (e.g. `/` for the homepage, `/about/story`), maps `home`/`index` to the site root, and otherwise uses `<urlPrefix>/<slug>`. Fixes homepages being listed as `/home` and any page whose `data.path` differed from `/{slug}`.
1193
+ - Noindexed documents (`noIndex`/`noindex`/`robotsPolicy: noindex*`, collection/site defaults, non-production guard) are now excluded from the sitemap so it never conflicts with a page's own robots meta tag.
1194
+ - The "Include posts in sitemap" crawl setting is now honoured by sitemap generation (both the index and the per-collection sitemap, which 404s when posts are excluded).
1195
+ - The "Index entire site" master switch is now enforced: when off, `robots.txt` emits a blanket `Disallow: /` for all crawlers, the sitemap (index + per-collection) serves empty, and publish-time search-engine/IndexNow pings are suppressed. This governs the public site only — CMS admin/API protection (auth + the admin's own `noindex` meta tag + the always-present `/admin` and `/api/` disallows) is unaffected.
1196
+ - Sitemap-index `<lastmod>` now reflects each collection's newest published change instead of "now", so crawlers can trust it.
1197
+ - `robots.txt` and the SEO settings now advertise the canonical public `/sitemap.xml` instead of the internal `/api/cms/sitemap.xml`.
1198
+ - hreflang `<xhtml:link rel="alternate">` annotations (incl. `x-default`) are emitted for translated documents when i18n is configured with more than one locale. No overhead for single-locale sites.
1199
+ - IndexNow support: when `INDEXNOW_KEY` is set, changed URLs are submitted to IndexNow (Bing, Yandex, …) on publish/update/delete, and the key is served at `/api/cms/indexnow-key.txt`. New exports: `resolveDocumentUrl`, `buildIndexNowSubmission`, `INDEXNOW_ENDPOINT`.
1200
+
1201
+ **create-actuate-cms**
1202
+ - Scaffolded projects ship root `app/sitemap.xml/route.ts` and `app/robots.txt/route.ts` proxy routes so the canonical `/sitemap.xml` and `/robots.txt` URLs resolve out of the box.
1203
+
1204
+ ## 0.66.0
1205
+
1206
+ ### Minor Changes
1207
+
1208
+ - a6a17e9: Split login: serve the auth screens on their own non-obvious path while the admin app stays at `/admin`.
1209
+ - **cms-core**: new `admin.loginPath` config. `createCMSMiddleware` applies the same guards (IP allowlist, CMS_SECRET check, setup bypass) to the login mount and redirects expired sessions there. Emailed password-reset and invite links point at the login mount when configured.
1210
+ - **cms-admin**: new `LoginRoot` component mounts login (+ MFA), the first-run setup wizard, and forgot/reset-password on a separate route (e.g. `/securelogin`, with setup at `/securelogin/setup`), then hands off to the admin app after sign-in. `<AdminRoot loginPath>` makes the admin mount render a 404 lookalike for signed-out visitors instead of a login form — no redirect, so the login location is never leaked.
1211
+ - **create-actuate-cms**: new `--login-path` flag and prompt (default `/securelogin`). New projects scaffold the split-login layout by default: login/setup at the login path, admin app at the admin path, config + proxy wired accordingly. Pass `--login-path` equal to `--admin-path` for the classic inline login.
1212
+
1213
+ ## 0.65.0
1214
+
1215
+ ### Minor Changes
1216
+
1217
+ - 3e2daa4: Inline branded preview — see the draft through your real site without leaving the editor.
1218
+ - cms-admin: the page and post editors gain a "Site preview" toggle in the top bar that swaps the structure canvas for an iframe of the public site rendering the draft (same signed `?preview=<token>` contract as "View on site"). Unsaved edits are persisted first, the frame reloads after every save, and the existing viewport controls size the frame.
1219
+ - cms-core: `createSiteClient().resolveDocument(path, { previewToken })` forwards a preview token to `/resolve` so consumer routes can render drafts at their real URL.
1220
+ - create-actuate-cms: the scaffolded catch-all route now forwards `?preview=` to the resolver and shows a draft banner, so new projects support "View on site" and inline preview out of the box.
1221
+
1222
+ ## 0.64.0
1223
+
1224
+ ### Minor Changes
1225
+
1226
+ - 135d0c7: Taxonomy Phase B — admin facet filters and taxonomy columns on collection lists.
1227
+ - cms-core: `GET /collections/:slug` now accepts `filter.<field>=<value>` query params for fields declared in the collection config. Text/select fields filter by exact match; tag-shaped array fields filter by containment, matching both the canonical `[{ tag }]` row shape and legacy plain-string arrays. Undeclared fields are silently ignored. `listDocuments` gains a `dataFilters` option and `DataFieldFilter` is exported.
1228
+ - cms-admin: the collection list grows a facet filter bar (select fields use config options; free-text `category` and tag-shaped arrays load the collection's in-use values via the field-values API) plus taxonomy columns showing category/tag values per row, with a one-click "Clear filters" action.
1229
+
1230
+ ## 0.63.0
1231
+
1232
+ ### Minor Changes
1233
+
1234
+ - e3da3bc: Taxonomy Phase A — structured categories and tag autocomplete (UpChat platform feedback P1 + P2).
1235
+
1236
+ **cms-core**
1237
+ - New `GET /collections/:slug/field-values?field=&q=` endpoint returning distinct in-use values for a declared text/select/array field (case-insensitive merge, most-used first, capped at 25). Powers admin tag autocomplete and category suggestions. Auth + collection read scope enforced; undeclared or long-form fields are rejected.
1238
+ - New `categoryField({ options })` field preset — renders as a structured `select` when options are provided, free text otherwise.
1239
+ - `blogPostCollection({ categoryOptions: [...] })` (and every collection preset with a category field) upgrades the free-text category to a select without hand-rolling field overrides. Plain strings are slugified into `{ label, value }` pairs; explicit `fields.category` overrides still win.
1240
+
1241
+ **cms-admin**
1242
+ - New `TagInput` chip editor with autocomplete from the collection's existing vocabulary. Dedupes case-insensitively and resolves to the canonical casing already in use — no more `AI` / `ai` / `Artificial Intelligence` drift.
1243
+ - Post details modal is now field-config aware: a `select` category renders a dropdown (legacy free-text values stay selectable), and an `array` tags field renders the chip input. Free-text configs keep their current inputs.
1244
+ - `EditorPost` gains `tags`, round-tripped as canonical `[{ tag }]` rows; legacy plain-string arrays are still read.
1245
+
1246
+ ## 0.62.0
1247
+
1248
+ ### Minor Changes
1249
+
1250
+ - d6b21e7: Add Settings > Email: configure the transactional email provider from the admin UI.
1251
+ - **cms-admin**: new Email tab in Settings with a provider picker (Resend, SMTP.com, Amazon SES, generic SMTP), per-provider credential fields, masked stored secrets (last-4 only, write-only inputs), a "Send test email" button, and notices for environment/platform-managed configuration.
1252
+ - **cms-core**: new `__email_config__` store with API keys and SMTP passwords encrypted at rest (`CMS_ENCRYPTION_KEY`), `GET/PUT /email/settings` + `POST /email/settings/test` admin routes (test sends only to the signed-in admin's own address, rate-limited), and a DB-aware `resolveEmailSenderAsync()` used by invites, password resets, and form notifications. Resolution order: platform adapter → admin-configured settings → environment variables.
1253
+ - **plugin-email**: registers a provider factory with cms-core so admin-configured settings build real Resend/SMTP.com/SES/SMTP senders without env vars.
1254
+
1255
+ ## 0.61.0
1256
+
1257
+ ### Minor Changes
1258
+
1259
+ - 0c3a6eb: Posts UX improvements from external-consumer feedback:
1260
+ - New `admin.excludeFromPostsAggregate: true` collection flag keeps a post-shaped collection out of the admin's aggregate Posts area (cross-collection list, type tabs, New Post picker) while leaving its own admin list and API routes fully accessible. Enforced server-side in `GET /posts` and mirrored in the admin client.
1261
+ - `blogPostCollection()` now satisfies the admin Post Editor's full field contract out of the box: it gains `sections` (json), `category`, and `publishDate` (replacing `publishedDate` — update seeds/queries that referenced the old key), and `body` is no longer hard-required so section-driven posts can publish.
1262
+ - `create-actuate-cms` scaffolds now define `posts: blogPostCollection()` instead of an inline collection, so new projects always start with an editor-compatible posts schema.
1263
+
1264
+ ## 0.60.0
1265
+
1266
+ ### Minor Changes
1267
+
1268
+ - 854152d: Developer experience improvements from external-consumer feedback:
1269
+ - Every published package now ships its `CHANGELOG.md` in the npm tarball, and packages that were missing a `README.md` (cli, cms-admin, cms-core, import, platform-vercel, all plugins, sections-react) now have one — so release notes and usage docs are visible on npmjs.com and in `node_modules`.
1270
+ - New client-safe form validation entry point: `@actuate-media/cms-core/forms/client` exports `validateFormFieldClient()`, `validateFormValuesClient()`, visibility/conditional helpers, and the exact `FORM_EMAIL_RE` / `FORM_PHONE_RE` regexes the server enforces — zero Node dependencies, so public-site forms can mirror server rules without reimplementing them. The server validator now imports the same primitives, so client and server can never drift.
1271
+
1272
+ ### Patch Changes
1273
+
1274
+ - bf5f857: `GET`/`PUT /post-templates/:type` now preserve sections whose type has no registered definition instead of silently dropping them. A template saved from the admin (which round-trips unmanaged sections) no longer loses seed/code-owned section types server-side; the derived `unmanaged` marker is stripped before persisting.
1275
+ - Updated dependencies [854152d]
1276
+ - @actuate-media/realtime@0.1.1
1277
+
1278
+ ## 0.59.1
1279
+
1280
+ ### Patch Changes
1281
+
1282
+ - 15b3f45: Fix silent media upload data loss: `/media/upload` now returns a 500 (and creates no media record) when blob/adapter storage fails, instead of returning 201 with a dead `/api/cms/media/file/…` URL that nothing serves. Storage errors are logged with their cause (typically a missing `BLOB_READ_WRITE_TOKEN`). `/media/:id/optimize` likewise fails without touching the record when the optimized variant cannot be stored. Successful uploads now include `url` in the response so the admin can render previews immediately.
1283
+
1284
+ ## 0.59.0
1285
+
1286
+ ### Minor Changes
1287
+
1288
+ - f962e2d: New `defineSectionTypes()` authoring helper for custom section types. It is to `sections.types` what `defineConfig` is to the config file: a typed identity function with eager, fail-fast validation that throws one aggregated error naming every problem (blank/duplicate ids, built-in id collisions, unknown field types, select fields without options, repeaters without a sub-schema — with recursive sub-field checks). This replaces the silent-skip behavior consumers previously hit at runtime registration, where a malformed type simply never appeared in the Add Section gallery. Also exports `validateSectionTypeInputs()` (non-throwing issue list for tooling) and `SECTION_FIELD_TYPES` (runtime mirror of the field-type union). All three are available from both the package root (beside `defineConfig`) and the `/page-sections` subpath, along with the `CustomSectionTypeInput` and `SectionsConfig` types now re-exported from the root.
1289
+
1290
+ ### Patch Changes
1291
+
1292
+ - 73a117a: Add API-handler contract tests covering the full REST surface (265 routes). A checked-in contract table pins every registered route (in first-match-wins registration order) to the exact status an unauthenticated request must receive, with a documented allowlist for the 19 deliberately-anonymous endpoints. The suite fails loudly when a route is dropped or renamed, when a protected route accidentally becomes public, when a handler 500s on anonymous traffic, or when an error response stops emitting the `{ error: string }` JSON envelope. No runtime changes.
1293
+
1294
+ ## 0.58.0
1295
+
1296
+ ### Minor Changes
1297
+
1298
+ - 363bb3e: Strongly-typed database surface (M4): replaced the pervasive `db: any` / `type Db = any` pattern with a structural `ActuateDB` client type — one typed delegate per shipped model plus the `$`-method surface — backed by generated row/enum types for all 34 models and 17 enums (`scripts/generate-db-types.mjs`, output committed at `src/db/model-types.ts`).
1299
+ - `guardedDb()`, `getDB<ActuateDB>()`, action transactions, auth/setup/forms/webhooks/cron/scheduling/SEO services now get delegate-name, method-name, and row-shape checking.
1300
+ - New public type exports: `ActuateDB`, `ModelDelegate`, `QueryArgs`.
1301
+ - Drift is caught two ways in CI: the e2e package compiles the real PrismaClient from the canonical schema and asserts it satisfies `ActuateDB` (`db-type-guard.ts`), and a freshness test verifies every schema model/enum appears in the generated types.
1302
+ - Typed rows surfaced two latent unguarded relation accesses (password-reset token user, webhook retry endpoint) which now fail closed.
1303
+
1304
+ Query arguments deliberately remain loose (`QueryArgs = any`) — reproducing Prisma's generic argument inference by hand isn't maintainable, and anything narrower breaks real-client assignability.
1305
+
1306
+ ## 0.57.1
1307
+
1308
+ ### Patch Changes
1309
+
1310
+ - a5f8bc0: Internal refactor: split the 14,381-line `api/handlers.ts` monolith into 25 domain route modules under `api/routes/` (auth, documents, media, forms, SEO, users, webhooks, etc.), plus shared `api/route-helpers.ts` and `api/guarded-db.ts` modules. `registerCMSRoutes` is now a thin aggregator that preserves the original route registration order. No behavior or public API changes — `registerCMSRoutes`, `handleActuateAPI`, and all route paths are unchanged.
1311
+
1312
+ ## 0.57.0
1313
+
1314
+ ### Minor Changes
1315
+
1316
+ - 8e73cf5: Custom section types are now first-class. `actuate.config.ts` accepts `sections.types` as the canonical key (the previously-unreleased `sections.customTypes` remains as a deprecated back-compat alias, resolved via `resolveCustomSectionTypes`). The unscoped `GET /page-sections/types` endpoint now returns custom types alongside built-ins so admins can hydrate their registry from the API.
1317
+
1318
+ Add an explicit `onUnknown` strategy to `coerceSections` (`'drop' | 'preserve' | 'error'`) plus `coerceSectionsWithReport` and `UnknownSectionTypeError`, so callers can warn or block on unknown section types instead of silently dropping them. The legacy `preserveUnknown` flag still maps to `'preserve'`.
1319
+
1320
+ Add a generic `repeater` field type with a nested `fields` sub-schema and `minRows`/`maxRows`/`rowLabel` constraints; `stats` and `gallery` are now repeater presets. New helpers: `resolveRepeaterField`, `isRepeaterField`, `blankRepeaterRow`. Repeater rows (including required sub-fields and image alt text) are validated with row-scoped error/warning paths.
1321
+
1322
+ Add a `pagePathField()` field preset for an explicit page URL path.
1323
+
1324
+ ## 0.56.0
1325
+
1326
+ ### Minor Changes
1327
+
1328
+ - 99565a1: Add config-declared custom section types and preserve externally-managed sections (Opal feedback).
1329
+ - New `sections.customTypes` on `ActuateCMSConfig` — declare custom section types (id, name, scope, fields, optional presentation/defaults) that are recognized, listed in discovery, and validated like built-ins. Registered at server boot in `handleActuateAPI`.
1330
+ - New registry API on `@actuate-media/cms-core/page-sections`: `registerSectionTypes`, `resetCustomSectionTypes`, `getAllSectionTypes`, plus `CustomSectionTypeInput` / `SectionsConfig` types. `getSectionType` / `getSectionTypesForScope` / `isKnownSectionType` / `sectionTypeLabel` now merge built-ins with custom types (built-ins always win an id collision).
1331
+ - `coerceSections(raw, { preserveUnknown: true })` keeps entries whose type isn't registered, flagged `unmanaged`, with content/settings verbatim and order intact, instead of dropping them. The page-section API (REST + MCP) now loads/persists with preservation so add/reorder/update/remove never drop seed/code-owned sections, and the derived `unmanaged` marker is never written back to storage.
1332
+ - `PageSection.sectionType` is now a `string` (was the built-in `SectionTypeId` union) and `PageSection` gains an optional `unmanaged` flag. `createSection` / `buildSection` accept any registered type id.
1333
+
1334
+ ## 0.55.0
1335
+
1336
+ ### Minor Changes
1337
+
1338
+ - 74662c0: Standardize `MediaUsage.fieldName` to `fieldPath` and harden Blob diagnostics.
1339
+ - Renames the `MediaUsage.fieldName` column to `fieldPath` to match the API layer, server actions, and `actuate doctor` expectations, with a new additive migration `0002_media_usage_field_path` (run `prisma migrate deploy`). Note: instances that already hand-renamed this column should mark 0002 as applied.
1340
+ - Runtime env diagnostics now escalate a partial Vercel Blob link (a store is connected via `BLOB_STORE_ID`/webhook key but `BLOB_READ_WRITE_TOKEN` is missing) from a warning to an error, since uploads fail in that state.
1341
+ - `createCMSMiddleware` now accepts `ActuateCMSConfig` directly (type-only), so consumers can pass their config without an `as unknown as` cast.
1342
+ - The optional MySQL/SQLite database adapters now mark their lazy `import()`s as bundler-ignored (`webpackIgnore`/`turbopackIgnore`), eliminating spurious "Module not found" build warnings for drivers a Postgres project never installs.
1343
+
1344
+ ## 0.54.0
1345
+
1346
+ ### Minor Changes
1347
+
1348
+ - 29f32ba: Deepen `GET /api/cms/collections` field introspection (roadmap WS-B4). The discovery endpoint now recursively summarizes `array` element subfields (with `minRows`/`maxRows`) and `blocks` variant subfields (keyed by block `slug`, with labels), in addition to the existing `relationTo`/`hasMany`/`options` hints — so an AI agent can plan a deep create/update without guessing nested shapes. The summary stays an allow-listed projection (no validators/hooks/access functions leak), is depth-capped defensively, and is exposed as a reusable `summarizeCollectionFields()` export (`FieldSummary`/`BlockSummary` types).
1349
+
1350
+ ## 0.53.0
1351
+
1352
+ ### Minor Changes
1353
+
1354
+ - aa4bd1a: Unify the page content model at write time (ADR 0002). Visual Page-Builder pages now compile their `_layout` tree to the canonical `data.sections[]` on every save, so every reader (SDK, MCP, search, content graph, `/resolve`) sees one contract instead of the lazy read-time bridge.
1355
+ - **cms-core**: `createDocument`/`updateDocument` derive `sections` from a `layout` envelope; the `/page-sections/*` write routes refuse a builder-owned page (one with `_layout`) with HTTP 409 to prevent silently-overwritten edits; the `/resolve` bridge is now a legacy fallback for un-migrated pages. New `migratePageBuilderSections()` export backfills legacy pages (idempotent).
1356
+ - **cms-admin**: the flat Page Editor opens a builder-owned page read-only with an "Open in Page Builder" action; `EditorPage` gains a `builderOwned` flag.
1357
+ - **cli**: new `actuate migrate:sections` command (with `--dry-run` / `--batch-size`) to backfill canonical sections for legacy page-builder documents.
1358
+
1359
+ ## 0.52.0
1360
+
1361
+ ### Minor Changes
1362
+
1363
+ - 01eef99: Security & reliability hardening from the production-readiness audit.
1364
+ - **Write-time HTML sanitization is now recursive (stored-XSS fix).** Previously only top-level `richText` fields were sanitized on write. HTML nested in `array`/`blocks` fields, the canonical `sections[]` rich-text slots, and the page-builder `_layout` tree was stored raw — a stored-XSS path reachable by any authenticated writer because the public renderer trusts stored HTML. All reachable author-HTML sinks are now sanitized before storage via the new `sanitize-document` module.
1365
+ - **AI analysis routes are rate-limited and input-capped.** `/ai/audit-document` and `/ai/suggest-internal-links` now enforce a per-user limit (60/hr) and a 50k-character input cap, and emit `ai_audit_document` / `ai_suggest_internal_links` audit-log events.
1366
+ - **`/media/upload` gains a dedicated strict limit** (20/hr/user) in addition to the global IP limit.
1367
+ - **Vision image fetches go through `safeFetch`** (SSRF-safe: host re-validation, DNS-rebinding defense, no redirects) with an 8 MB cap; `/ai/media-metadata` now passes real image bytes to the vision model instead of a bare URL.
1368
+ - **`/redirects/record-404` is now reachable from the public site.** It is CSRF-exempt (it only increments an unauthenticated 404 counter) and bounds its stored `path`/`referrer` to 2048 chars.
1369
+ - **`/public/globals/:slug` and `/public/navigations/:slug` no longer serve `ARCHIVED` documents.** They remain unauthenticated site chrome (not PUBLISHED-gated), but an explicitly retired record is excluded.
1370
+ - A runtime warning is logged when a collection's `fields` is defined as an array instead of an object map (silent data loss otherwise).
1371
+
1372
+ ## 0.51.2
1373
+
1374
+ ### Patch Changes
1375
+
1376
+ - aeee3a4: Docs: correct the migration-baseline upgrade note shipped in 0.51.1.
1377
+
1378
+ The previous note told deployments "previously migrated with `prisma migrate deploy`" to run `prisma migrate resolve --applied 0001_init`. That guidance is wrong for exactly that audience: such a database already has a `0001_init` row in `_prisma_migrations`, so `resolve --applied 0001_init` errors with `P3008: migration is already recorded as applied`. Those deployments need **no action** — `migrate deploy` applies only _pending_ migrations and does not re-validate the squashed baseline, so it is a no-op.
1379
+
1380
+ `prisma migrate resolve --applied 0001_init` is only needed to baseline a database that already has the `actuate_*` tables but has **no** migration history (e.g. one provisioned via `db push`, or restored from a dump) before adopting `migrate deploy`. Run `prisma migrate status` first if unsure.
1381
+
1382
+ ## 0.51.1
1383
+
1384
+ ### Patch Changes
1385
+
1386
+ - f88a406: Fix document-create/update hijacking a collection's own `status` field, and reconcile the migration history into a clean baseline.
1387
+ - **Forms (and any collection with a `status` field) can be created again.** `createDocument`/`updateDocument` treated a `data.status` value as the document lifecycle status. Collections such as Forms store their own `status` (`draft`/`active`/`archived`) inside `data`, so creating a form threw `Cannot create a document with status "draft"` and activating one would have written an invalid enum value. We now only hoist `status` onto the document column when it is an actual `DocumentStatus`; any other value stays in `data`. Regression tests added in `actions/document-crud.test.ts` and exercised end-to-end in the new `@actuate-media/e2e` harness.
1388
+ - **Migration history baseline.** The committed migrations did not apply from an empty database (`0003` indexed `actuate_documents.title` before any migration created it) and were ~223 columns / several tables behind `schema.prisma`, so `prisma migrate deploy` on a fresh DB crashed. Migrations `0001`–`0012` are squashed into a single `0001_init` baseline generated from the current schema (plus the raw full-text-search GIN index). A PGlite-based drift guard now asserts the migrations apply from empty and reproduce the schema.
1389
+
1390
+ **Action required for existing deployments** that were previously migrated with `prisma migrate deploy`: run `prisma migrate resolve --applied 0001_init` once so Prisma records the baseline as already applied instead of attempting to re-create existing tables. Fresh installs need no action.
1391
+
1392
+ - **Config footgun guard.** `setActuateConfig` now logs a one-time warning when a collection declares `fields` as an array instead of the object-map shape cms-core expects (an array silently drops every custom field on write).
1393
+
1394
+ ## 0.51.0
1395
+
1396
+ ### Minor Changes
1397
+
1398
+ - a413616: Add MCP relationship-resolver tools (WS-C C2) so agents stop guessing document IDs.
1399
+ - **cms-core (additive, backward-compatible):**
1400
+ - `listDocuments` / `GET /collections/:slug` now accept an exact `slug` filter. Because `slug` is `@@unique([collection, slug])`, this is the canonical single-query slug→id lookup.
1401
+ - The `GET /collections` discovery endpoint now surfaces `relationTo` (for relationship fields), `hasMany`, and select `options` in each field summary, so an agent can learn where a relationship field points. (Overlaps the WS-B4 schema-surfacing work.)
1402
+ - **mcp-server:** three new tools — `get_relationship_fields` (list relationship/media/select fields + their `relationTo`), `resolve_relationship` (exact slug → `{ id, slug, title, status }`), and `list_relationship_targets` (browse candidate documents when the slug is unknown). README updated with a "Resolving relationships" section.
1403
+
1404
+ - 204da7a: WS-E (E3): media resolution + responsive images in the public render path.
1405
+ - **sections-react:** new pluggable `renderImage` `RenderOptions` hook (with
1406
+ exported `RenderImage`/`RenderImageProps` types and a `defaultRenderImage`).
1407
+ The default emits a lazy, `decoding="async"` `<img>` and forwards intrinsic
1408
+ `width`/`height` when known; a Next.js consumer can pass a `next/image`-backed
1409
+ renderer for responsive `srcset` + optimization. The `feature`, `gallery`, and
1410
+ post-header images now route through it (`<PostHeader>` gains an optional
1411
+ `options` prop). The package stays React-only — no `next` dependency.
1412
+ - **cms-core:** the `/resolve` endpoint now enriches section images with
1413
+ intrinsic `width`/`height` and library `altText`, matched from `actuate_media`
1414
+ by stored URL (`storageKey`/`publicUrl`). Additive and best-effort — the
1415
+ stored document is never mutated and a lookup failure never breaks resolve.
1416
+ `feature` sections gain `imageWidth`/`imageHeight` (+ backfilled `imageAlt`);
1417
+ `gallery` rows gain `width`/`height` (+ backfilled `alt`).
1418
+ - **create-actuate-cms:** the generated catch-all page renders CMS images via a
1419
+ `next/image`-backed `renderImage` (falling back to a fluid `<img>` when the
1420
+ CMS doesn't know dimensions), and `next.config` now allow-lists the storage
1421
+ host under `images.remotePatterns` so optimization works out of the box.
1422
+
1423
+ - 2f18c56: WS-E (E2): public form rendering — a built site can now render and submit a CMS form end to end.
1424
+ - **sections-react:** new `<ActuateForm>` Client Component fetches the public form
1425
+ schema (`GET /api/cms/public/forms/:slug`), renders the fields (text/email/
1426
+ url/tel/number/date, textarea, select, checkbox, radio), and submits to the
1427
+ schema's `submitPath`. Spam protection is wired automatically: a hidden
1428
+ honeypot (keyed from `spam.honeypotFieldKey`), an `elapsedMs` "time to submit"
1429
+ timer, and an optional consumer-supplied `captchaToken`. Pre-fetch a schema via
1430
+ the `form` prop to skip the round-trip (SSR/streaming). Adds a `baseUrl`
1431
+ `RenderOptions` field (defaults to same-origin). Exposes pure, tested helpers
1432
+ (`coercePublicForm`, `buildSubmitBody`, `fieldControl`, `firstMissingRequired`).
1433
+ - **cms-core:** new canonical `form` section type. Set `content.formSlug` (plus
1434
+ optional heading/intro) on any page or post and the matching renderer embeds
1435
+ the form. The registry-drift guard now covers it (renderer ↔ type).
1436
+ - **cms-admin:** the form Embed panel now shows the real public endpoints
1437
+ (`/api/cms/public/forms/:slug` + `/submit`) and a copy-paste `<ActuateForm>`
1438
+ snippet, replacing the previously-advertised `/api/forms/*`, iframe, and
1439
+ `embed.js` routes that were never implemented. Adds a `mail` section icon.
1440
+
1441
+ - e2e0bd7: WS-E (E4): public navigation endpoint + navigation rendering in the scaffold.
1442
+ - **cms-core:** new unauthenticated `GET /api/cms/public/navigations/:slug` that
1443
+ returns a clean `{ name, slug, location, items }` tree. Like globals it is site
1444
+ chrome — not status-gated — and safe when the navigation plugin isn't installed
1445
+ (no match → 404). A shared, pure `coercePublicNavItems` helper sanitizes the
1446
+ open `data.items` JSON: drops unlabeled nodes, strips internal `id`s, caps
1447
+ recursion depth, and only passes through `_blank`/`_self` targets. Both
1448
+ `createSiteClient` and `createServerSiteClient` gain `getNavigation(slug)`
1449
+ returning `PublicNavigation | null`, and `PublicNavigation` / `PublicNavItem`
1450
+ are now exported types.
1451
+ - **sections-react:** new presentational `<SiteNav>` — an accessible `<nav>`
1452
+ landmark whose submenus use native `<details>`/`<summary>` disclosure
1453
+ (keyboard-operable, no client JS). New-tab links get
1454
+ `rel="noopener noreferrer"`; an empty menu renders nothing. Exports `SiteNav`
1455
+ plus `SiteNavProps`/`SiteNavItem`.
1456
+ - **create-actuate-cms:** `lib/cms.ts` re-exports `getNavigation`, and the
1457
+ generated site layout fetches the `main` menu and renders a header with the
1458
+ site name + `<SiteNav>`, degrading gracefully to just the name when no menu
1459
+ exists yet.
1460
+
1461
+ ## 0.50.0
1462
+
1463
+ ### Minor Changes
1464
+
1465
+ - d7a144c: Add a page-builder → canonical sections bridge so existing `_layout` documents aren't stranded (ADR 0001, WS-A5).
1466
+ - New `layoutTreeToSections()` export on the `@actuate-media/cms-core/page-sections` subpath converts a page-builder `_layout` `BlockNode` tree into a best-effort canonical `PageSection[]`: it flattens the page → section → container/row/column → block nesting in document order, maps each block's `blockType` to the closest canonical section type, degrades unknown/complex blocks to a `text` section that surfaces their content, and propagates the enclosing builder section's anchor (`htmlId`) and hidden visibility. It is pure, DB-free, and never throws (`savedSectionRef` nodes are skipped — they require a separate lookup).
1467
+ - The `/resolve` endpoint now applies the bridge at read time: when a page/post has a `_layout` tree but no flat `sections`, the resolved response is enriched with derived sections so the content renders through `@actuate-media/sections-react`'s `<Sections>`. This is non-destructive — the stored document is never mutated, and a document's own `sections` always take precedence.
1468
+
1469
+ ## 0.49.0
1470
+
1471
+ ### Minor Changes
1472
+
1473
+ - 9ba332c: Converge the scaffolder onto the canonical page-content contract (ADR 0001, WS-A4).
1474
+ - **create-actuate-cms**: New projects now use the canonical `sections` (JSON `PageSection[]`) field for both `pages` and `posts`, and the generated public catch-all renders `data.sections` with `<Sections>` from `@actuate-media/sections-react` — passing cms-core's allow-list `sanitizeHtml` so author HTML (e.g. the article-body section) is sanitized before the DOM, and rendering a `<PostHeader>` for post-type documents. The deprecated Model C path is retired: the `content[]`/`BLOCK_MAP` `BlockRenderer`, `HeroBlock`, `CTABlock`, `TextBlock`, `ImageTextBlock`, `SiteHeader`, and `SiteFooter` generators (and the `blocks`-gated `layout`/headers/footers config) are removed. Generated apps now depend on `@actuate-media/sections-react` and emit the matching Tailwind `@source` directive.
1475
+ - **@actuate-media/cms-core**: `ResolveResult` (the typed `createSiteClient().resolveDocument()` return) now exposes the optional `meta`, `metadata`, `jsonLd`, `jsonLdHtml`, `template`, `post`, and `preview` fields the `/resolve` endpoint already returns, plus the new `ResolvePostAuthor`, `ResolveRelatedPost`, `ResolvePostContext`, and `ResolvePreview` types — so consumers can render the full post experience (header, author, related posts, SEO metadata) without re-declaring the shape.
1476
+
1477
+ ## 0.48.0
1478
+
1479
+ ### Minor Changes
1480
+
1481
+ - ae1af8f: Authoring correctness fixes for the AI site-building workflow (roadmap WS-B/E quick wins).
1482
+ - **cms-core**: `createDocument` now honors a requested `status` (`DRAFT` or
1483
+ `PUBLISHED`) instead of always forcing `DRAFT`. Publishing on create is gated
1484
+ to EDITOR/ADMIN, stamps `publishedAt`, and emits the `document.published`
1485
+ webhook + sitemap notify. This fixes `POST /page-builder/create?publish=true`
1486
+ (and `status: 'PUBLISHED'`), which previously created a draft silently.
1487
+ Statuses other than `DRAFT`/`PUBLISHED` on create are rejected with guidance
1488
+ to schedule/archive via update.
1489
+ - **client**: `listMedia()` now returns the canonical
1490
+ `{ docs, total, page, pageSize, totalPages }` (`MediaList`) envelope. The
1491
+ server returns the asset array under `items`/`data` (never `docs`), so the
1492
+ previous typing made `listMedia().docs` always `undefined`. Adds exported
1493
+ `MediaAsset` and `MediaList` types and an optional `init` (signal/cache) arg.
1494
+ - **mcp-server**: the `list_media` tool now normalizes the same `items`→`docs`
1495
+ shape so its output matches `list_documents`.
1496
+ - **create-actuate-cms**: generated `TextBlock`/`ImageTextBlock` now sanitize
1497
+ author-supplied HTML with `sanitizeHtml` before `dangerouslySetInnerHTML`
1498
+ (was an XSS vector on hostile content).
1499
+
1500
+ ## 0.47.0
1501
+
1502
+ ### Minor Changes
1503
+
1504
+ - 7d11b8d: Production-ready Settings → Updates tab with real release intelligence.
1505
+ - **cms-core**: Fix a critical permission bug where `GET/PUT /updates/config` and
1506
+ `POST /updates/apply` rejected real admins (they compared `session.role` to the
1507
+ lowercase `'admin'` while roles are stored uppercase). They now use
1508
+ `requireAdminScope`, so OWNER/ADMIN sessions (and admin-scoped API keys) can
1509
+ manage updates. `GET /updates/check` no longer leaks the GitHub repo to
1510
+ non-admins and reports a `canManage` flag. Update checks now run through a new
1511
+ hybrid resolver `getUpdateStatus()` that queries the Actuate update server
1512
+ first (rich, real changelog/severity/migration/breaking-change data derived
1513
+ from the published CHANGELOG) and transparently falls back to a direct npm
1514
+ registry check. New exports: `getUpdateStatus`, `checkForUpdatesViaServer`, and
1515
+ the `ChangeType` / `ChangelogItem` / `PluginCompatibilityInfo` types; richer
1516
+ `VersionInfo`. `fetchChangelog()` now targets the real `/api/changelog`
1517
+ endpoint (overridable via `ACTUATE_UPDATE_SERVER_URL`).
1518
+ - **cms-admin**: Rebuild the Updates tab as a dedicated `UpdatesTab` + `useUpdates`
1519
+ hook (extracted from `Settings.tsx`). The GitHub integration now uses the shared
1520
+ `SettingsControlRow` + sticky `SettingsSaveBar` dirty-state pattern, and the tab
1521
+ surfaces real release notes (categorized changelog), a security-update badge,
1522
+ "migrations required" and breaking-change callouts, and plugin compatibility.
1523
+ Write actions are permission-aware (hidden for non-admins).
1524
+ - **cli**: `update-check` / `upgrade` now resolve the update-server base URL from
1525
+ `ACTUATE_UPDATE_SERVER_URL` (defaulting to the production host), so the
1526
+ `/api/versions` endpoint is configurable for self-hosted update servers.
1527
+
1528
+ ## 0.46.0
1529
+
1530
+ ### Minor Changes
1531
+
1532
+ - 4e6ead2: Add Brand Voice. Admins can define a reusable brand voice (tone, audience, guidelines, and
1533
+ sample copy) in Settings > AI. When enabled, the governed AI runtime injects it into the system
1534
+ prompt of content-generation features — co-author and SEO titles/descriptions — so every AI
1535
+ surface writes in one consistent, on-brand voice.
1536
+ - `cms-core`: new `BrandVoiceProfile` type on `AISettings`, a `brand-voice` module
1537
+ (`buildBrandVoiceSystemPrompt`, `applyBrandVoice`, `getActiveBrandVoice`, `normalizeBrandVoice`),
1538
+ a `useBrandVoice` option on `generateTextForFeature`, and `PUT /ai/settings` persistence
1539
+ (length-clamped, no secrets).
1540
+ - `cms-admin`: a Brand Voice card in the AI settings tab wired through `useAiSettings`.
1541
+
1542
+ ## 0.45.0
1543
+
1544
+ ### Minor Changes
1545
+
1546
+ - df17a3f: Unify AI execution behind a governed runtime. Provider adapters now support text
1547
+ generation (OpenAI/xAI/custom via the OpenAI-compatible chat API, Anthropic via
1548
+ the messages API), and a new `ai/runtime` resolves a feature to its connection +
1549
+ routed model, enforces the feature toggle and monthly token budget, resolves the
1550
+ API key from env or the encrypted store, and records real token usage for the
1551
+ usage meter. The `/ai/coauthor` endpoint now runs through this governed path
1552
+ first — using the provider and per-feature model configured in Settings > AI —
1553
+ and falls back to the legacy `@actuate-media/plugin-ai` global provider only when
1554
+ no governed provider is configured. Disabled features return 403 and budget
1555
+ overruns return 429.
1556
+
1557
+ New exports: `generateTextForFeature`, `resolveFeatureRuntime`,
1558
+ `isAiFeatureEnabled`, and `adapterGenerateText`.
1559
+
1560
+ - 11b4dfa: Route the SEO assistant endpoints through the governed AI runtime. `/ai/seo/generate-field`
1561
+ (titles, meta descriptions, focus keyword), `/ai/seo/explain-issue`, `/ai/seo/generate-schema`,
1562
+ and `/ai/seo/summarize` now generate via the provider + per-feature model configured in
1563
+ Settings > AI (gated by the `ai.features.seoMetaGeneration` toggle, enforcing the monthly token
1564
+ budget and recording real usage). They fall back to the legacy `@actuate-media/plugin-ai`
1565
+ provider only when no governed provider is configured. A disabled feature returns 403, a budget
1566
+ overrun 429, and a provider failure 502; when nothing is configured the response is 503 with a
1567
+ "Connect a provider in Settings > AI" message.
1568
+
1569
+ ## 0.44.0
1570
+
1571
+ ### Minor Changes
1572
+
1573
+ - fcede90: Settings > AI: per-feature model routing. Each AI feature can now run on a
1574
+ specific model (e.g. a cheap model for alt-text, a frontier model for content
1575
+ writing) or inherit the default. Adds `resolveFeatureModel` /
1576
+ `resolveAllFeatureModels` to cms-core as the authoritative feature→model
1577
+ resolver (capability-aware, with capability-typed and fallback defaults), and
1578
+ extends `PUT /ai/settings` to persist + validate per-feature `modelId`/
1579
+ `providerId` overrides. The admin AI Features card gains a per-feature model
1580
+ picker that filters to capability-compatible models and warns when a routed
1581
+ model is missing a required capability (e.g. a text-only model on a vision
1582
+ feature).
1583
+
1584
+ ## 0.43.0
1585
+
1586
+ ### Minor Changes
1587
+
1588
+ - 89eebe0: Add Settings > AI (Phases 1-2): DB-backed provider connections + dynamic model catalog.
1589
+
1590
+ **cms-core**
1591
+ - New `src/ai` module: provider connections (OpenAI, Anthropic, xAI, custom OpenAI-compatible), a normalized model catalog, feature flags, and budget settings, all stored in an `__ai_config__` config document (no Prisma migration, mirroring the SEO config-store).
1592
+ - Provider API keys are encrypted at rest with `encryptSecret` and never returned to the client — responses expose only a masked `apiKeyLast4` and a source flag. When a provider's env var (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `XAI_API_KEY`) is set, that key wins and is surfaced as env-managed/read-only.
1593
+ - SSRF-safe adapters: built-in providers use fixed hostnames; custom base URLs are validated and DNS-resolved via the existing webhook guards.
1594
+ - Dynamic catalog merges live `/v1/models` listings with a curated capability/pricing map, with a curated fallback when live listing is unavailable and `unavailable` marking for configured-but-missing models.
1595
+ - Monthly usage summary aggregated from existing audit-log token totals (real counts; 0 until per-run metering lands).
1596
+ - New ADMIN-gated, audited routes: `GET/PUT /ai/settings`, `POST/PUT/DELETE /ai/providers(/:id)`, `POST /ai/providers/:id/test`, `POST /ai/providers/:id/sync-models`, `GET /ai/models`, `GET /ai/usage/summary`.
1597
+
1598
+ **cms-admin**
1599
+ - Replaced the read-only AI status panel with a full `AISettingsTab`: AI Assistant card (default provider + model selector with capability badges and Refresh), provider connection management (add/test/disconnect, masked or env-managed key handling with confirm dialogs), global AI feature toggles, and a real monthly usage meter with a budget limit.
1600
+ - Feature enforcement across modules and per-run usage metering are deferred to later phases; toggles and budget persist now.
1601
+
1602
+ ## 0.42.0
1603
+
1604
+ ### Minor Changes
1605
+
1606
+ - 8dfeb94: Add a **Settings > SEO** tab for site-wide SEO defaults, backed by the shared SEO configuration service (no duplicate state with `/seo` or Settings > General).
1607
+ - **cms-core**: extend `SiteSEOConfig` with `titleTemplate`, `defaultMetaDescription`, `defaultSocialImageAssetId`, and a `verification` block; add title-template token validation/application, search-engine verification meta-tag rendering, and wire all three into the public page-meta renderer. New `notifySitemapOnPublish` service performs production-gated, SSRF-safe sitemap regeneration + Google/Bing pings, fired as a best-effort effect when public content is published, updated, unpublished, or deleted. Cache invalidation runs on every change while the timestamp write + outbound ping are debounced (default 60s) so bursts of publishes don't hammer the shared `__seo_config__` row or spam search engines. `PUT /seo/config` and `PUT /seo/crawl-settings` now validate new fields and write field-level audit logs (verification token values redacted).
1608
+ - **cms-admin**: new `SeoSettingsTab` (Meta Defaults with SERP/social preview, Search Engine Verification, Sitemap Defaults, full-workspace callout, Advanced disclosure) driven by a `useSeoSettings` hook that reads/writes the shared `/seo/config` and `/seo/crawl-settings` endpoints with dirty tracking, validation, permission-aware read-only states, and dangerous-change confirmation. `MediaPickerModal` gains an `onSelectItem` callback so the default social image stores its asset id.
1609
+
1610
+ ## 0.41.0
1611
+
1612
+ ### Minor Changes
1613
+
1614
+ - 7336b5f: Add content-ownership transfer to Settings > Users.
1615
+
1616
+ cms-core: new `transferContentOwnership` / `countOwnedContent` helpers reassign
1617
+ a member's authored documents, uploaded media, and created templates to another
1618
+ active user (immutable version/audit history is never touched). New admin-only,
1619
+ audited `POST /users/:id/transfer-content` endpoint validates the recipient
1620
+ (must exist and be active) and applies the same Owner/last-Owner guards as the
1621
+ other per-user actions. `contentOwnedCount` in the user detail now reflects all
1622
+ three content types, not just documents.
1623
+
1624
+ cms-admin: the user detail drawer Overview now offers "Transfer content" with an
1625
+ active-member picker before revoking access. Auxiliary dialogs (transfer,
1626
+ confirm, reauth) now render inside the Drawer's Radix focus scope so they are
1627
+ keyboard-reachable and no longer marked `aria-hidden` behind the drawer.
1628
+
1629
+ ## 0.40.0
1630
+
1631
+ ### Minor Changes
1632
+
1633
+ - 951963a: Build the Settings > Users access-control system: real invites, roles, seats, and access review.
1634
+
1635
+ **@actuate-media/cms-core**
1636
+ - Schema: add `OWNER` and `VIEWER` to the `UserRole` enum, a new `InviteStatus` enum, a new `Invite` model (`actuate_invites`), and additive user-lifecycle columns (`suspendedAt`, `lockedAt`, `forcePasswordReset`, `mfaRequired`, `failedLoginCount`, `lastActiveAt`, `invitedById`, `acceptedInviteAt`). Two hand-authored migrations (`0011_user_roles_owner_viewer`, `0012_invites_and_user_lifecycle`) — enum values are added in their own migration so Postgres can use them as defaults afterward. All user columns are nullable/defaulted so existing rows migrate cleanly.
1637
+ - Add a `roles` module (also exported as the `@actuate-media/cms-core/roles` subpath): display mapping (`OWNER/ADMIN/EDITOR/VIEWER` plus legacy `AUTHOR→Editor`, `CLIENT→Viewer`), assignable roles, privilege ranking, and per-role permission derivation.
1638
+ - Add an `Invite` lifecycle service (`createInvite`, `listInvites`, `resendInvite`, `cancelInvite`, `updateInviteRole`, `extendInvite`, `expireStaleInvites`, `markInviteAcceptedForEmail`). Invite tokens are hashed at rest, single-use, and expire (7/14/30 days). Completing the password-reset flow for an invited email now marks the invite `ACCEPTED` atomically.
1639
+ - Add a `team` module (`listTeamMembers`, `getUserStats`, `computeAccessReview`) that derives seat usage and access-review recommendations (inactive 90d+, external domain, admin/owner without MFA, stale invite) from real user/security signals.
1640
+ - New admin endpoints: enriched `GET /users` (search/role/status filters), `GET /users/stats`, `PATCH /users/:id/role`, hardened `DELETE /users/:id` (revoke = suspend, last-Owner protection), `GET /invites` + `POST /invites/:id/resend|cancel` + `PATCH /invites/:id`, and `GET /access-review` + `POST /access-review/:id/dismiss`. Role assignment/removal of Owner is Owner-only, self-escalation is blocked, role downgrades revoke sessions, and seat limits are enforced. All sensitive actions are audit-logged.
1641
+
1642
+ **@actuate-media/cms-admin**
1643
+ - Rebuild the Settings > Users tab to match the design: dynamic summary cards (members, pending invites, admins, seats used), an actionable access-review banner, a combined team table (active members + pending invites) with role-filter pills and search, per-row role dropdowns and revoke/resend/cancel actions, and an invite modal with role radio cards and advanced options (custom message, expiry). Includes loading, empty, filtered-empty, and error states; destructive and Owner-assignment actions require confirmation.
1644
+
1645
+ **create-actuate-cms**
1646
+ - Sync the scaffold's Prisma schema and migrations with the new Users-management schema so newly created projects start with the `Invite` model, new roles, and user-lifecycle columns.
1647
+
1648
+ - 951963a: Settings > Users phase 2: per-user detail, sessions, activity, and security actions.
1649
+
1650
+ **@actuate-media/cms-core**
1651
+ - Add a `user-admin` read-model module (`getUserDetail`, `listUserSessions`, `listUserActivity`, `getUserPermissionView`, `parseUserAgent`) that shapes a single user's overview, security posture (MFA / password / lock / SSO-only), sessions (with device parsing + "this device" marker), audit activity (events where the user is actor or target), and derived permissions.
1652
+ - New admin-only, audited per-user endpoints: `GET /users/:id`, `GET /users/:id/sessions`, `DELETE /users/:id/sessions/:sessionId`, `POST /users/:id/sessions/revoke-all`, `GET /users/:id/activity`, `GET /users/:id/permissions`, `POST /users/:id/password-reset`, `POST /users/:id/force-password-reset`, `POST /users/:id/mfa-reset`, `POST /users/:id/require-mfa`, `POST /users/:id/suspend`, `POST /users/:id/reactivate`, `POST /users/:id/lock`, `POST /users/:id/unlock`. Owner actions are Owner-only, self-actions and last-Owner removal are blocked, MFA reset and Admin/Owner password resets require password reauthentication, and session-affecting actions revoke sessions.
1653
+ - Login now rejects suspended (`suspendedAt`) and locked (`lockedAt`) accounts so an admin Suspend/Lock takes effect on the next sign-in attempt — distinct from full deactivation (`isActive=false`), which preserves the account's content and history.
1654
+
1655
+ **@actuate-media/cms-admin**
1656
+ - Add the User detail drawer opened from a Team members row (and the row "View details" action): Overview, Security, Sessions, Activity, and Permissions tabs. Security actions (send/force password reset, reset/require MFA, lock/unlock, suspend/reactivate, revoke one/all sessions) are wired to the new endpoints with confirmation dialogs and an inline re-authentication prompt that retries with the `x-reauth-password` header when the server requires it. Includes loading, empty, and error states across all tabs.
1657
+
1658
+ ## 0.39.0
1659
+
1660
+ ### Minor Changes
1661
+
1662
+ - f043e18: Fix day-one auth dead-ends and wire up transactional email + user invites.
1663
+
1664
+ **@actuate-media/cms-core**
1665
+ - Add a transactional email sender registry (`setEmailSender`, `getRegisteredEmailSender`, `resolveEmailSender`). Password-reset and form-notification email now resolve a sender via this registry, so apps that configure email purely through `emailPlugin()` actually send (previously only `config.platform.email` was consulted, so reset/notification mail silently never sent).
1666
+ - Password reset links are now built from the configured admin path instead of a hardcoded `/admin`, so resets work on apps mounted elsewhere.
1667
+ - Add an admin-only `POST /users/invite` endpoint plus `createUserInvite` / `normalizeInviteRole` / `VALID_INVITE_ROLES`. Invites provision the user with an unusable password and a single-use, 7-day token, then email a "set your password" link (reusing the existing `/reset-password` page). When no email sender is configured the response returns the link so an admin can share it manually. Rate-limited per admin.
1668
+
1669
+ **@actuate-media/cms-admin**
1670
+ - `AdminRoot` gains an `onLogout` prop. The account-menu "Logout" item now revokes the server session (`POST /auth/logout`), clears local session state, and returns to the login screen — previously it navigated to a non-existent `/logout` route (404) and left the session valid.
1671
+ - The "Invite User" dialog is now a real flow backed by `POST /users/invite` (loading state, error handling, valid roles, optional name) instead of a fake success toast.
1672
+
1673
+ **create-actuate-cms**
1674
+ - The scaffolded admin page now wires `onVerifyTotp` (so MFA users can complete the second-factor step) and `onLogout`, matching the dev app.
1675
+
1676
+ **@actuate-media/plugin-email**
1677
+ - `emailPlugin()` now registers its resolved provider with cms-core's email sender registry, so configuring email via the plugin is sufficient for password-reset, invite, and form-notification email.
1678
+
1679
+ - 05537a8: Replace the decorative AI settings tab with an honest, read-only status panel.
1680
+
1681
+ The previous AI tab shipped a fake API-key field (the key was never saved), a no-op "Verify" button, a misleading "encrypted at rest" claim, hardcoded zero usage metrics, and nine feature toggles that were read by nothing. None of it configured AI — that happens server-side via `@actuate-media/plugin-ai` and environment variables.
1682
+ - `cms-core`: new admin-only `GET /ai/status` endpoint reporting `{ installed, configured, provider }`. It detects whether the AI plugin is installed and a provider is configured, and hints at which provider env var is present — never exposing key values.
1683
+ - `cms-admin`: the AI tab now renders a truthful status panel (active / installed-but-unconfigured / not-installed) plus setup guidance, and no longer pretends to store credentials.
1684
+
1685
+ - 456e5f2: Fix client onboarding so a freshly scaffolded project actually has a working database.
1686
+
1687
+ **create-actuate-cms**
1688
+ - Scaffolded projects now ship the canonical Prisma schema (all `actuate_*` models with `@@map`) and the full migration history, synced from `@actuate-media/cms-core` via `scripts/sync-prisma-assets.ts` and guarded by a drift test. Previously the scaffolder hand-maintained a divergent schema (14 of 33 models, no `@@map`) and shipped no migrations, so `prisma migrate deploy` in the build created no tables and core features (full-text search, i18n, API metrics, API keys, webhooks, notifications, etc.) were broken.
1689
+ - The generated `prisma.config.ts` now warns when migrations would run against the pooled `DATABASE_URL` because `DIRECT_DATABASE_URL` is unset.
1690
+ - The generated admin page now shows an actionable "run migrations" / backend-unreachable message instead of silently dropping to the login screen when the database has no tables or `/setup/status` fails.
1691
+ - Next-steps + deployment docs now use `prisma migrate deploy` (applies the bundled migrations).
1692
+
1693
+ **@actuate-media/cms-core**
1694
+ - `checkSetupRequired` now reports `migrationsRequired: true` (new `SetupStatus` field) when the database is reachable but the tables don't exist yet, and `POST /setup/create-admin` returns an actionable 503 in that case. New exported helper `isSchemaMissingError`.
1695
+ - The admin middleware now returns an actionable HTML page (with remediation steps) when `CMS_SECRET` is missing, instead of a bare `500`.
1696
+
1697
+ - 90e80b6: Fix `plugin-email` test timeouts in CI by importing the email-sender registry from a lightweight subpath.
1698
+
1699
+ `emailPlugin()` previously imported `setEmailSender` from the full `@actuate-media/cms-core` barrel (handlers, middleware, the whole graph). Because the plugin's tests re-import the module on every `vi.resetModules()`, transforming that barrel repeatedly blew past the 5s test timeout on cold CI runners.
1700
+ - `cms-core`: adds a new `@actuate-media/cms-core/email-sender` subpath export exposing `setEmailSender` / `getRegisteredEmailSender` / `resolveEmailSender` (the registry only depends on the config runtime).
1701
+ - `plugin-email`: imports `setEmailSender` from that subpath instead of the barrel. No runtime barrel import remains.
1702
+
1703
+ ## 0.38.0
1704
+
1705
+ ### Minor Changes
1706
+
1707
+ - 008544f: Complete the MFA loop: account enrollment, login challenge, and backup-code recovery.
1708
+
1709
+ Previously the TOTP backend existed but had no user-facing plumbing — there was no
1710
+ way to enroll from the admin, the login screen couldn't complete a second-factor
1711
+ challenge, and backup codes were generated but never consumed anywhere (a lost
1712
+ authenticator meant permanent lockout). The "Require MFA" policy was therefore
1713
+ impossible to satisfy.
1714
+
1715
+ **cms-core**
1716
+ - `POST /auth/totp/login` now accepts a one-time backup code as well as a TOTP code.
1717
+ Matches are constant-time and single-use (the code is removed and remaining codes
1718
+ re-encrypted); `login_success` audit records whether a backup code was used.
1719
+ - New pure helpers `normalizeBackupCode` / `findBackupCodeMatch` in `auth/totp`.
1720
+ - `GET /auth/me` now returns `totpEnabled` so the admin can show enrollment state.
1721
+
1722
+ **cms-admin**
1723
+ - New `/profile` view (fixes the previously dead account-menu link) with an
1724
+ **Account security** card: password reauth → QR code + manual key + downloadable
1725
+ one-time backup codes → verify, plus a reauth-gated disable flow.
1726
+ - The login screen now renders a **two-factor challenge** step (authenticator code
1727
+ with a backup-code fallback). `AdminRoot`/`Login` gain an `onVerifyTotp` prop and
1728
+ `onLogin` may now return `{ requiresTOTP, mfaPendingToken }`.
1729
+ - The Security tab's "MFA not set up" warning now links to the enrollment flow.
1730
+ - Adds `qrcode.react` for QR rendering.
1731
+
1732
+ ## 0.37.0
1733
+
1734
+ ### Minor Changes
1735
+
1736
+ - 06940a1: Add the Settings → Security tab as a real Security Center.
1737
+
1738
+ **cms-core**
1739
+ - New `@actuate-media/cms-core/security-center` subpath export with pure, client-safe
1740
+ helpers: `computeSecurityStatus`, `parseSecuritySettings`, `validateSecuritySettings`,
1741
+ `validateIpRange`, `mfaEnableLockoutError`, `summarizeUsers`, `summarizeApiKeys`, and types.
1742
+ - New API routes: `GET /security` (computed posture + cross-module summaries +
1743
+ secret/encryption verdicts, never secret values), `PUT /security` (persists enforced
1744
+ MFA + session policy with server-side lockout-safe MFA enabling, audited as
1745
+ `security_settings_changed`), and `POST /security/sessions/revoke-all` (revokes the
1746
+ caller's other sessions, audited).
1747
+ - Concurrent-session enforcement now honors the stored `security.session.maxConcurrentSessions`
1748
+ policy (config/default precedence preserved when unset; `0` = unlimited).
1749
+ - Login responses include `mfaSetupRequired` when org-wide MFA is required but the account
1750
+ has not enrolled past its grace window.
1751
+ - `settings` global writes now audit nested `security` changes.
1752
+
1753
+ **cms-admin**
1754
+ - New Security tab with a computed Security Status card, Authentication & MFA, Session
1755
+ Policy (with revoke-other-sessions), Access & Request Protection, and Secrets/Encryption/Audit
1756
+ cards. Wired to the real `/security` endpoints with source badges, dangerous-change
1757
+ confirmations, lockout-safe MFA toggle, validation, and read-only states for non-admins.
1758
+ Replaces the previous decorative security toggles.
1759
+
1760
+ ## 0.36.0
1761
+
1762
+ ### Minor Changes
1763
+
1764
+ - 3667ac2: Add the Settings → Appearance tab: a production-ready brand + admin-theme system.
1765
+
1766
+ **cms-core**
1767
+ - New `@actuate-media/cms-core/appearance` subpath module with pure utilities:
1768
+ logo-shape classification, WCAG contrast checks, admin accent-token
1769
+ generation, and brand/favicon resolution (admin + public fallback chains).
1770
+ - `GET /public/brand` (unauthenticated) resolves public logos + favicon
1771
+ metadata for the public frontend; `GET /appearance` (authenticated) returns
1772
+ stored appearance/brand settings plus resolved admin/public/favicon views and
1773
+ per-asset URLs for editor previews.
1774
+ - `PUT /globals/:slug` audit logging now records nested `appearance`/`brand`
1775
+ changes.
1776
+
1777
+ **cms-admin**
1778
+ - New Appearance tab (Branding, Admin Theme, Typography & Motion, live Brand
1779
+ Preview) wired to the Media Center for logo/favicon uploads (assets referenced
1780
+ by ID, never raw paths), with shape warnings, dark-logo warnings, accent
1781
+ contrast validation, source badges, and read-only/permission states.
1782
+ - `ThemeProvider` now applies color mode, accent color, sidebar density, content
1783
+ font, and reduce-motion live, and feeds the resolved admin brand to the
1784
+ sidebar logo (with site-initials fallback).
1785
+
1786
+ ## 0.35.1
1787
+
1788
+ ### Patch Changes
1789
+
1790
+ - b3f51f2: Fix "Global not found" error on the Settings page (and any global editor) before the first save. The authenticated `GET /globals/:slug` route now returns an empty object for a global that has no document yet — globals are upsert-on-write, so a never-saved slug is a valid empty state rather than a 404. The public `/public/globals/:slug` route keeps its config-gated 404 for genuinely undeclared globals.
1791
+
1792
+ ## 0.35.0
1793
+
1794
+ ### Minor Changes
1795
+
1796
+ - 58eff9b: Build a production-ready General Settings tab with a computed Site Index Status card.
1797
+
1798
+ **cms-core**
1799
+ - Add `getSiteIndexStatus()` / `computeSiteIndexStatus()` (exported from the SEO module) that derives the effective crawl/index state (indexable / protected / blocked / partially_blocked / unknown) from the SAME effective SEO config the public renderer uses — environment, site URL, robots, sitemap, and last audit — plus a `robotsTxtBlocksEntireSite()` helper.
1800
+ - Add `GET /seo/index-status` (ADMIN) returning the computed status, per-check rows, and actions.
1801
+ - Audit `PUT /globals/settings` with a per-key before/after diff (`settings_changed`, including source + environment).
1802
+ - Migrate the `GET /seo/meta/:documentId` preview to read robots defaults from the effective SEO config instead of the legacy `settings` global, so preview, public render, and General Settings share one source of truth.
1803
+
1804
+ **cms-admin**
1805
+ - Replace the General tab with a fully wired implementation: Site Index Status card, Site Information (with config-managed source badges + live Site URL validation), Language & Region, and SEO & Robots Defaults — the SEO toggles now read/write the shared `/seo/config` (no duplicate store).
1806
+ - Add dirty-state Save/Reset bar, validation summary, confirmation dialogs for dangerous indexing changes (production noindex on; non-production noindex off), loading skeletons, error/retry, and read-only gating for non-admins.
1807
+ - Use semantic theme tokens (purple `brand` accent, status colors) so the tab matches the design system in light and dark modes.
1808
+
1809
+ ## 0.34.0
1810
+
1811
+ ### Minor Changes
1812
+
1813
+ - a6d6674: Forms PII/encryption, retention, audit, export, and MCP tools (PR8).
1814
+
1815
+ **cms-core**
1816
+ - **Field-level encryption at rest**: submission values for fields flagged `encrypted` (or high-PII fields when `forms.encryptSensitiveFields` is enabled) are now encrypted with the `enc:v1:` AES-256-GCM envelope before persistence and transparently decrypted on every read path. Plaintext/legacy rows pass through unchanged; missing key on read fails closed.
1817
+ - **PII retention**: `forms.retentionDays` is now wired into the cleanup cron, hard-deleting form submissions (and cascading their files) older than the window. Opt-in — disabled by default.
1818
+ - **Audit logging**: viewing a single entry now logs `form.entry.viewed` and CSV export logs `form.entries.exported`, both with user id and IP, for PII-access traceability.
1819
+ - **CSV export endpoint**: `GET /forms/entries/export?formId=…` streams a decrypted CSV of a form's submissions (bounded to 5000 rows) with formula-injection-safe cells. New exported helpers `exportFormEntries`, `encryptSubmissionData`, `decryptSubmissionData`, `fieldShouldEncrypt`.
1820
+
1821
+ **mcp-server**
1822
+ - New `list_form_entries` and `get_form_entry` tools over the decrypted submissions inbox, both requiring an explicit `formId`/`entryId` (no enumeration) and documented as PII-sensitive. `get_form_entry` reads are audited server-side.
1823
+
1824
+ ## 0.33.0
1825
+
1826
+ ### Minor Changes
1827
+
1828
+ - 9f4192b: Add the Forms notifications + webhooks pipeline (PR6).
1829
+ - **Email notifications**: on a clean submission, send admin alerts to the form's configured recipients and an optional submitter autoresponder through the configured `platform.email` adapter. Supports `{{placeholder}}` subject/message templates (HTML-escaped), a fields table that can exclude sensitive/PII fields, and a resolved `Reply-To` from a chosen field.
1830
+ - **Per-form webhooks**: new `createFormWebhook` / `listFormWebhooks` / `updateFormWebhook` / `deleteFormWebhook` / `testFormWebhook` / `dispatchFormWebhooks` services. Secrets are stored encrypted and returned only once at creation; payloads are HMAC-SHA256 signed; delivery is SSRF-safe (`safeFetch`) with bounded retries + exponential backoff. Events: `form.submitted`, `form.spam_detected`, `form.entry.starred`, `form.entry.archived`.
1831
+ - **API**: `GET/POST /forms/:id/webhooks`, `PATCH/DELETE /forms/:id/webhooks/:webhookId`, `POST /forms/:id/webhooks/:webhookId/test`, and `POST /forms/:id/notifications/test`.
1832
+ - Side-effects fire non-blocking from the submission pipeline so a slow or failing channel never delays or breaks the public response. Spam submissions suppress email but still fire `form.spam_detected` webhooks.
1833
+ - `EmailAdapter` send options gain optional `replyTo` / `from` (additive, non-breaking).
1834
+
1835
+ ## 0.32.0
1836
+
1837
+ ### Minor Changes
1838
+
1839
+ - 5a44d79: Add the public form submission pipeline (Forms PR5).
1840
+ - New public, unauthenticated endpoints: `GET /public/forms/:slug` (embeddable
1841
+ schema projection with server-only fields stripped) and
1842
+ `POST /public/forms/:slug/submit`, plus CORS preflight (`OPTIONS`) support.
1843
+ - Server-authoritative submission processing: per-field validation +
1844
+ type-coercion against the active schema version, HTML sanitization of all
1845
+ string values (stored-XSS defense), and dropping of keys not in the schema.
1846
+ - Spam protection strategies: honeypot, time-to-submit, email/domain blocklist,
1847
+ keyword blocklist, and origin domain allowlist. Spam is persisted but hidden
1848
+ from the default inbox.
1849
+ - File-upload validation (MIME allowlist + magic-byte sniffing + size cap) wired
1850
+ to the configured storage adapter; metadata persisted to `FormSubmissionFile`.
1851
+ - Sequential per-form entry numbers assigned in a transaction; client IPs are
1852
+ hashed (never stored raw); attribution/UTM captured; submissions rate-limited
1853
+ per IP+form and CAPTCHA-verified when configured.
1854
+ - The legacy `POST /forms/:id/submit` route now runs through the same pipeline.
1855
+
1856
+ ## 0.31.0
1857
+
1858
+ ### Minor Changes
1859
+
1860
+ - ca3c1f6: Forms system — data model foundation (PR 1 of 9).
1861
+
1862
+ Adds the persistence layer and shared contracts for the full Forms workflow:
1863
+ - New Prisma models: `FormSchemaVersion` (versioned field-schema snapshots so historical submissions always render against the schema they were captured with), `FormSubmissionFile` (file-upload metadata stored via the storage adapter, cascade-deleted with its submission), and `FormWebhook` (per-form signed outbound webhooks).
1864
+ - Expanded `FormSubmission` with the submission-workflow columns: `entryNumber`, `schemaVersionId`, `normalizedData`, `senderName/Email/Phone`, `preview`, `unread`/`starred`/`archived`, `spamStatus`, attribution metadata (`ipAddressHash`, `userAgent`, `referrer`, `pageUrl`, `utm*`), `readAt`, `archivedAt`, and `updatedAt`. The migration is safe on populated databases (all additive/defaulted, `updatedAt` backfilled, existing rows marked read).
1865
+ - New shared TypeScript contracts under `@actuate-media/cms-core` (`FormDefinition`, `FormField`, `FormFieldType`, `FormSchemaVersion`, `FormSubmission`, `FormSubmissionFile`, `FormNotificationSettings`, `FormSpamSettings`, `FormEmbedSettings`, `FormWebhookConfig`, query-param and stats types).
1866
+ - New `forms` site-config section (`FormsConfig`): feature flag, allowed field types, spam defaults, file-upload limits, notification/webhook/autoresponder toggles, retention, PII encryption, and the public submission path.
1867
+
1868
+ No runtime behavior changes yet; subsequent PRs wire services, API routes, admin UI, public submission, notifications, and seed data on top of this model.
1869
+
1870
+ - 15d8c08: Forms system — core services + admin API routes (PR 2 of 9).
1871
+
1872
+ Adds the service layer and REST endpoints that back the Forms admin UI:
1873
+ - **Forms service** (`forms/service.ts`): `listForms`, `getFormById`, `createForm`, `updateForm`, `duplicateForm`, `archiveForm`/`restoreForm`, `deleteForm` (blocks deletion when submissions exist unless forced), schema versioning (`saveFormSchema`/`getFormSchema` creates a new `FormSchemaVersion` on every save), per-form notification/embed settings, `getFormStats`, and `getFormsSidebarCounts`. Form definitions are stored via the standard document data-layer (inheriting versioning, cache invalidation, and audit logging). Schema validation enforces unique/valid keys, allowed field types, option presence, and the configured field cap.
1874
+ - **Entries service** (`forms/entries.ts`): `listEntries` with search + form/unread/starred/archived/spam/date filters (archived and spam excluded by default), `getEntryWithSchema` (renders an entry against the schema version it was captured with), `getEntryCounts` (chip totals + per-form unread map), read/unread/star/archive mutations, `bulkUpdateEntries`, and `deleteEntry`. A normalization layer derives sender/preview from the payload for legacy rows.
1875
+ - **API routes**: `GET/POST /forms`, `GET/PATCH/DELETE /forms/:id`, `POST /forms/:id/duplicate`, `GET/PUT /forms/:id/schema`, `GET/PUT /forms/:id/notifications`, `PUT /forms/:id/notify`, `GET/PUT /forms/:id/embed`, `GET /forms/stats`, `GET /forms/sidebar-counts`, `GET /forms/entries`, `GET /forms/entries/counts`, `GET/PATCH/DELETE /forms/entries/:entryId`, and `POST /forms/entries/bulk`. All require an authenticated write role and emit audit-log events.
1876
+
1877
+ Behavior change: `GET /forms` now returns the typed `FormDefinition` shape (including entry counters). `fields` is now the field array; the legacy `fields`-as-count value is available as `fieldCount`, and `submissions` mirrors `totalEntries` so existing admin views keep working until they migrate (PR 3).
1878
+
1879
+ Also fixes a pre-existing sitemap test that omitted the `type: 'page'` collection flag the SEO scoping now requires.
1880
+
1881
+ - 8891547: Scope the SEO surfaces to real front-end content and keep the admin out of search indexes.
1882
+ - **cms-core**: The Content SEO list, audit, scan, internal-link candidates, **and the XML sitemap** now only include documents from collections explicitly typed `page` or `post` (visible and sitemap-eligible). Structural/utility collections (navigation menus, footers, tags, templates) — which have no `type` — and ad-hoc rows whose collection isn't declared in the config (e.g. benchmark/test data) are excluded, so a menu can no longer leak into the sitemap as an indexable URL. Adds the `frontEndContentCollectionTypes(config)` helper and threads a `collectionTypes` option through `gatherAuditEntities` / `runAndPersistAudit`; `processSeoScan` gains an `allowedCollections` option. The `POST /seo/scan` route now reuses `processSeoScan` (also fixing its case-sensitive `alt=`/`<h1` checks).
1883
+ - **cms-admin**: `AdminRoot` now ensures a `noindex, nofollow` robots meta tag while the admin is mounted, so private admin UI is never indexed regardless of how the consumer renders the admin page.
1884
+
1885
+ ## 0.30.0
1886
+
1887
+ ### Minor Changes
1888
+
1889
+ - 2cd5d3a: SEO production-readiness (Important tier, batch 1 — backend security/access):
1890
+ - **Read-endpoint access control.** `/seo/analysis`, `/seo/readability`, `/seo/internal-links`, `/seo/schema` and `/seo/meta` now enforce the target collection's `access.read` rule (and API-key collection scope), so a principal scoped to one collection can no longer pull SEO intelligence for documents in another. Adds a new exported `assertCollectionAccess(collection, operation, ctx, doc?)` helper.
1891
+ - **AI audit logging.** Every successful `/ai/seo/*` generation (`generate-field`, `explain-issue`, `generate-schema`, `summarize`) now writes an `ai_seo_generate` audit-log event — these calls are billable and feed public meta/schema, so they need a forensic trail.
1892
+ - **Prompt-injection + token-cost hardening.** Untrusted document/issue content interpolated into AI prompts is now length-clamped (2k chars) and fenced with explicit "treat as data, never instructions" delimiters across all SEO AI endpoints, including the title/description branches that previously sent unbounded content.
1893
+
1894
+ - 8d45b42: SEO production-readiness (Important tier, batch 2 — backend reliability/correctness):
1895
+ - **Severity ordering.** Issue lists (`/seo/issues`, `/seo/overview`, AI summary) now rank by explicit risk (critical → warning → info) instead of an alphabetical `severity` sort that wrongly ranked `info` above `warning`.
1896
+ - **Sitemap pagination.** Per-collection sitemaps no longer cap at 5,000 URLs and silently drop the rest. Collections larger than the 50k protocol limit are split across `?page=N` files referenced from the sitemap index (45k URLs/file). Sitemap-index `<loc>` values are now XML-escaped.
1897
+ - **Consistent robots resolution.** `composePageMeta` now resolves robots through the shared `resolveRobotsDirectives`, honoring the `robotsPolicy` select field and a new `seo.robots.noIndexNonProduction` guard (plus `defaultNoIndex`/`defaultNoFollow`) so staging/preview deploys aren't indexed. Adds an optional `environment` input.
1898
+ - **Audit reliability.** `runAndPersistAudit` refuses to start while another audit is RUNNING (prevents parallel runs duplicating `SeoIssue` rows), `gatherAuditEntities` is bounded with a `take` cap, and the expensive `/seo/scan` endpoint is now rate-limited and bounded.
1899
+
1900
+ - 90f53a6: Fix SEO Operations Center production-readiness blockers.
1901
+ - **SSRF (`GET /seo/summary`)**: the link-health probe now uses `safeFetch`
1902
+ (blocks private/loopback IPs, no redirect following), only probes same-origin
1903
+ internal links, skips all probing when `NEXT_PUBLIC_SITE_URL` is unset, and is
1904
+ gated to EDITOR+ — matching `/seo/link-health`.
1905
+ - **`GET /llms.txt` leak**: now excludes soft-deleted documents and
1906
+ internal/system collections (`__seo_config__`, `__*`, etc.) via the shared
1907
+ `isInternalCollection` helper, so "deleted" and internal content can no longer
1908
+ appear on a public surface.
1909
+ - **`PUT /seo/content` data-layer bypass**: SEO field edits now route through the
1910
+ new `updateDocumentSeoFields` action, which enforces the collection's
1911
+ `access.update` rule, snapshots a `Version` row, recomputes the search index,
1912
+ and fires the `afterUpdate`/cache-invalidate effects — while preserving the
1913
+ undeclared SEO convention keys the generic field-access layer would strip.
1914
+ Inbound SEO fields are now type- and length-validated.
1915
+ - **cms-admin**: SEO read fetchers in `seo-service` now re-throw on `cmsApi`
1916
+ errors so the error/retry UI works instead of rendering a misleading empty
1917
+ fallback (e.g. a fake score of 0 / grade "Critical") on a backend failure.
1918
+
1919
+ ### Patch Changes
1920
+
1921
+ - 1bd4a49: SEO production-readiness Polish tier — post-launch nice-to-haves from the SEO review.
1922
+
1923
+ cms-core:
1924
+ - **Markdown-aware heading analysis** — `analyzeContent` and `countH1` now detect
1925
+ Markdown ATX (`#`/`##`) and Setext (`===`/`---`) headings in addition to HTML
1926
+ tags, so markdown-bodied posts are no longer wrongly flagged "No H1 found" /
1927
+ "no subheadings".
1928
+ - **Correct default OG image URL** — the auto-generated `og:image` now points at
1929
+ `/api/cms/og.png` (where the endpoint is actually mounted) instead of `/og.png`,
1930
+ which 404'd for social crawlers.
1931
+ - **Consistent environment resolution** — `resolveSeoEnvironment()` centralises the
1932
+ production gate, preferring `VERCEL_ENV` over `NODE_ENV` everywhere (preview
1933
+ deploys report `NODE_ENV=production`), so `noIndexNonProduction` reliably
1934
+ noindexes preview deployments.
1935
+ - **Admin route boundary** — the CMS middleware matches `/admin` and `/api/cms` on
1936
+ a path-segment boundary, so sibling public routes like `/administrators` or
1937
+ `/admin-news` are no longer treated as admin routes.
1938
+ - **AI status semantics** — SEO AI endpoints now return `503` when the provider is
1939
+ installed-but-unconfigured (vs `501` when the plugin is genuinely missing).
1940
+ - Removed a duplicated `gradeForScoreBand` helper in favour of the canonical
1941
+ `gradeForSeoScore`.
1942
+
1943
+ cms-admin:
1944
+ - SEO filter chips use accessible toggle-button semantics (`aria-pressed`) instead
1945
+ of misused `role="tab"`.
1946
+ - Crawl-settings toggles use the Radix `Switch` primitive and are disabled while a
1947
+ save is in flight.
1948
+ - Score progress bars expose an accessible name and `aria-valuetext`; drawers
1949
+ without a description no longer trigger Radix's missing-description warning.
1950
+ - Redirect CSV export defers object-URL revocation (and mounts the anchor) so the
1951
+ download isn't cancelled before it starts.
1952
+ - The SEO AI service treats both `501` and `503` as the graceful "AI unavailable"
1953
+ state.
1954
+
1955
+ ## 0.29.0
1956
+
1957
+ ### Minor Changes
1958
+
1959
+ - a3b702f: Media Center foundation (phase 1):
1960
+ - New media SEO scoring module (`calculateMediaSeoScore` + types) that grades assets on alt text, filename quality, dimensions, captions, and size.
1961
+ - Additive `Media` schema fields (`originalFileName`, `caption`, `description`, `focusKeyword`, `tags`, `publicUrl`, `storageProvider`, `durationSeconds`, `thumbnailUrl`, `responsiveVariants`, `status`, `deletedAt`) plus a `MediaStatus` enum and supporting indexes, with the `0008_media_center` migration.
1962
+
1963
+ All fields are nullable or defaulted, so the change is additive and safe to apply to an existing database.
1964
+
1965
+ - 5fa84d3: Media library now reports real usage. `GET /media` joins the `MediaUsage` table and returns a `usedOn` array (page title + admin editor path) per asset, so the "orphaned" indicator is accurate instead of flagging every file. Adds `POST /ai/media-metadata`, which generates accessible alt text (and a derived title) for an image via the AI vision provider, with a graceful 501 when the AI plugin/provider isn't configured.
1966
+ - 5fa84d3: SEO Operations Center — a full detect → prioritize → explain → route → fix → audit → generate workflow.
1967
+
1968
+ **cms-core**
1969
+ - Additive `0009_seo` migration (mirrored to `apps/dev`): `SEOAuditRun`, `SEOIssue` (+ severity/status/category enums), `Redirect` extensions (`hits`, `lastHitAt`, `enabled`, `priority`, `preserveQuery`, `source`), `Redirect404Hit`, and `RedirectSuggestion`. All columns are nullable or defaulted, so it is safe to apply to an existing database.
1970
+ - Pure, explainable scoring model (`seo/score.ts`): `calculatePageSeoScore` / `calculateSiteSeoScore` with documented weights and breakdowns.
1971
+ - Persistent audit engine (`seo/audit-engine.ts`) with cross-run issue lifecycle (open / resolved / ignored / recurring) and a redirect engine (chain/loop detection, safe flatten, hit tracking, 404 logging, gated AI 404 recovery).
1972
+ - New endpoints: `POST /seo/audit`, `GET /seo/audits[/:id]`, `GET /seo/issues[/:id]`, issue `resolve`/`ignore`/`reopen`/`fix`, `GET /seo/overview`, redirect CRUD + import/export + chains/loops/flatten/suggest, technical settings (`/seo/sitemap/*`, `/seo/crawl-settings`, `/seo/robots`), and approval-gated `/ai/seo/*` (generate-field, explain-issue, generate-schema, summarize) that degrade gracefully with 501 when `plugin-ai` is unconfigured.
1973
+ - `/resolve` now returns Next.js-ready `metadata` plus validated JSON-LD; public `robots.txt` honors the admin-edited body.
1974
+
1975
+ **cms-admin**
1976
+ - Rebuilt `SEO.tsx` into a five-tab Operations Center (Overview / Content / Technical / Redirects / Audit) with `?tab=` deep-link routing, dynamic badge counts, a typed `seo-service.ts` client, reusable token-only primitives and a focus-trapped drawer, editor/fix/copilot drawers, and full loading / empty / error / permission states.
1977
+
1978
+ **mcp-server**
1979
+ - New SEO tools: `seo_overview`, `run_seo_audit`, `list_seo_audit_runs`, `list_seo_issues`, `get_seo_issue`, `set_seo_issue_status`, `list_redirect_rules`, `seo_redirect_overview`, `seo_redirect_chains`.
1980
+
1981
+ **create-actuate-cms**
1982
+ - Scaffolded projects now ship a redirect-resolving `middleware.ts` backed by the database redirect table.
1983
+
1984
+ ### Patch Changes
1985
+
1986
+ - 766b63c: Surface database-unavailable errors during first-run setup instead of masking them as "setup required". `checkSetupRequired()` and `createInitialAdmin()` now rethrow connection/initialization failures (Prisma `P1001`/`P1002`, `ECONNREFUSED`, `PrismaClientInitializationError`, etc.) so the `/setup/status` and `/setup/create-admin` endpoints return a 5xx for a DB outage rather than silently rendering the account-creation wizard and then failing with an opaque 400.
1987
+
1988
+ ## 0.28.1
1989
+
1990
+ ### Patch Changes
1991
+
1992
+ - fa54768: Accept SMTP.com's `SMTP_KEY` / `SMTP_CHANNEL` / `SMTP_EMAIL` env vars as
1993
+ aliases for `SMTPCOM_API_KEY` / `SMTPCOM_CHANNEL` / `EMAIL_FROM`.
1994
+
1995
+ `resolveEmailConfigFromEnv()` now infers the `smtpcom` provider from either
1996
+ naming scheme (the generic relay's `SMTP_HOST` still takes precedence when
1997
+ present), and the `/health` env diagnostic recognises the same keys so a
1998
+ correctly-configured SMTP.com deployment no longer warns about a missing
1999
+ `RESEND_API_KEY`.
2000
+
2001
+ ## 0.28.0
2002
+
2003
+ ### Minor Changes
2004
+
2005
+ - bdeadb2: Resolve real author and related-posts data for post-type documents.
2006
+
2007
+ `GET /resolve` now returns a `post` object for post-type collections:
2008
+ - `post.author` — `{ name, avatarUrl }` derived from the document's creator, so the Author Bio section shows the real author instead of a placeholder.
2009
+ - `post.relatedPosts` — up to 3 recent published siblings in the same collection (`{ title, url, excerpt }`), so the Related Posts section links real content.
2010
+
2011
+ Both fields are additive and optional; existing consumers are unaffected.
2012
+
2013
+ - 1768a8a: Render the post type's template header on the public site so the live page matches the editor canvas and preview.
2014
+ - `GET /resolve` now returns `template.header` (a normalized `PostHeaderConfig`) for post-type documents, resolved from the post type's `post-templates` doc at read time. The template stays the single source of truth, so editing it restyles every post of that type without re-saving each post.
2015
+ - New `computeReadingTime()` export drives the header's "min read" figure from the post body (200 wpm, HTML stripped, min 1 minute) instead of the previous hardcoded "5 min read".
2016
+
2017
+ - d85e89e: Add a server-side aggregate `GET /posts` endpoint for the admin Posts area.
2018
+ Filtering, sorting and pagination now run at the database level (one query plus
2019
+ one count) and respect per-collection read scope, so the Posts list scales past
2020
+ the old ~500-row-per-type client cap instead of pulling every document into the
2021
+ browser. The admin `fetchPosts` service now calls this endpoint; its public
2022
+ `Post` / `PostType` / `PostAuthor` shapes are unchanged.
2023
+
2024
+ ### Patch Changes
2025
+
2026
+ - 5567daf: Post templates: seed sensible defaults and route the admin through the dedicated endpoint.
2027
+ - A post type with no saved template now returns the default starting template (centered header + article body + related posts) instead of an empty one, so new posts and the template editor are never blank. Applied on the server (`GET /post-templates/:type`) and defended client-side.
2028
+ - The admin post-editor service now reads/writes templates through the dedicated, audited `/post-templates/:type` endpoints (scoped + logged) instead of the raw `post-templates` collection API.
2029
+
2030
+ ## 0.27.4
2031
+
2032
+ ### Patch Changes
2033
+
2034
+ - c091125: Recognise Vercel Marketplace Upstash credentials. The Vercel Upstash (Redis/KV) integration provisions `KV_REST_API_URL` / `KV_REST_API_TOKEN`, not `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN`, so a correctly-provisioned store previously fell back to the in-memory rate limiter and the `/health` check kept warning.
2035
+
2036
+ The rate limiter (`createRateLimiter` / `resolveUpstashCredentials`), the platform-vercel rate-limit adapter, and the env health check now read the `KV_REST_API_*` names as a fallback (native `UPSTASH_*` names still win when both are present). No config change is needed for stores provisioned through the Vercel Marketplace.
2037
+
2038
+ ## 0.27.3
2039
+
2040
+ ### Patch Changes
2041
+
2042
+ - 6f05e8d: Make the email health check (`RESEND_API_KEY` entry in `validateEnvShape` / `/api/cms/health`) provider-aware. It now reports "Configured" when any supported email provider is set up via env (SMTP, SES, or SMTP.com), instead of always warning about a missing Resend key. Mirrors the env resolution in `@actuate-media/plugin-email`.
2043
+
2044
+ ## 0.27.2
2045
+
2046
+ ### Patch Changes
2047
+
2048
+ - 07f52af: Accept Prisma Accelerate / Prisma Postgres connection schemes (`prisma://`,
2049
+ `prisma+postgres://`) in the `DATABASE_URL` env-shape validator. Previously the
2050
+ `/health` and admin diagnostics endpoints flagged a valid Accelerate URL as an
2051
+ invalid scheme, reporting the instance as unhealthy even though the runtime
2052
+ connects successfully through the Accelerate transport.
2053
+
2054
+ ## 0.27.1
2055
+
2056
+ ### Patch Changes
2057
+
2058
+ - 4fb0097: Dashboard performance: cache the expensive SEO compute and reduce refetch churn.
2059
+ - **cms-core:** `/stats` now caches its average-SEO-score computation per content-scope for a short TTL (60s). That score otherwise reads and scans the `data` JSON of up to 200 published documents on every dashboard load — the heaviest part of the endpoint. On a warm cache the read is skipped entirely; transient DB failures are not cached. Module-level so it survives across warm requests; a cold start recomputes once.
2060
+ - **cms-admin:** `useApiData` gains an opt-in `cacheMs` option. When set, successful responses are cached in-memory by endpoint and reused on the next mount (cache-first), so views that remount frequently don't re-issue identical requests. `refetch()` always bypasses the cache. Default behaviour is unchanged for existing callers. The Dashboard enables a 30s cache on its four requests, so navigating away and back no longer re-fires them; first load and independent streaming are preserved.
2061
+
2062
+ ## 0.27.0
2063
+
2064
+ ### Minor Changes
2065
+
2066
+ - 91885de: Add a section-aware bridge so AI agents (e.g. in Cursor / VS Code) can build pages section-by-section through MCP, producing real, editable, publishable Page Editor sections rather than disposable markup.
2067
+ - **cms-core**: new `@actuate-media/cms-core/page-sections` subpath exporting the flat Page Editor section registry, pure section-array helpers, and a deterministic `validateSectionContent`. This is now the single source of truth shared by the admin editor, the REST API, and MCP. Adds REST routes: `GET /page-sections/types`, `POST /page-sections/validate`, and section CRUD over a page document's `data.sections` (`GET|POST /page-sections/:collection/:id`, `PATCH|DELETE /page-sections/:collection/:id/:sectionId`, `POST /page-sections/:collection/:id/reorder`). Mutations go through the audited `updateDocument` path (versions, hooks, cache invalidation, live-preview push) and reuse collection scope checks. Writes are rejected on collections that don't declare a `sections` JSON field.
2068
+ - **mcp-server**: nine new tools — `list_section_types`, `get_section_schema`, `validate_section_content`, `list_page_sections`, `add_page_section`, `update_page_section`, `reorder_page_sections`, `remove_page_section`, `toggle_page_section_visibility`.
2069
+ - **cms-admin**: the Page Editor section registry and section helpers now re-export from `@actuate-media/cms-core/page-sections`; per-section publish validation is delegated to the shared `validateSectionContent`. No behavior change — internal refactor for a single source of truth.
2070
+
2071
+ - 91885de: Add a design-driven Posts template system that mirrors the Pages section editor.
2072
+ - **cms-core**: tag section types with a `scope` (`page` | `post`), add post-oriented section types (`article-body`, `quote`, `author-bio`, `related-posts`, `gallery`), `PostHeaderConfig` / `PostTemplate` types, the `getSectionTypesForScope` helper, and new field types (`richText`, `boolean`, `relationship`, `gallery`). Adds `GET/PUT /post-templates/:type` API routes and a `?scope=` filter on `GET /page-sections/types`.
2073
+ - **cms-admin**: new `PostSectionEditor`, `PostTemplateEditor` (with header panel), `PostPreview`, post section renderers, and a `post-editor-service` data layer. The Add Section gallery now filters by scope, and post type cards expose an "Edit template" action.
2074
+ - **mcp-server**: scope-aware `list_section_types`, plus `get_post_template` and `save_post_template` tools so an external AI agent can read and build a post type's template.
2075
+
2076
+ ### Patch Changes
2077
+
2078
+ - 91885de: Add the Posts admin area (All Posts + Post Types).
2079
+ - `/posts` aggregates every collection marked `type: 'post'` (or
2080
+ `admin.group: 'Posts'`) into a single sortable, filterable table
2081
+ with type tabs, search, status/author filters, bulk actions
2082
+ (publish / move to draft / delete), and pagination. Counts come
2083
+ from the data layer, not constants.
2084
+ - `/posts/types` shows a card grid per post-type collection with
2085
+ accent border, icon, description, post count, field count, and
2086
+ URL base — plus a dashed "+ New Post Type" card linking to setup
2087
+ instructions.
2088
+ - `/posts/new` lands on a type picker before opening the existing
2089
+ per-collection editor.
2090
+ - `/posts/types/new` offers a **Quick Post Type** gallery (Case
2091
+ Studies, News, Portfolio, Events, Team) that pre-fills a tailored
2092
+ field set, icon, and accent color, plus a manual form — both feed a
2093
+ paste-ready `actuate.config.ts` snippet generator. Collections
2094
+ remain code-defined because the read-only serverless filesystem
2095
+ rules out runtime config writes.
2096
+ - Sidebar collapses every post-type collection under a single
2097
+ "Posts" parent with fixed `All Posts` / `Post Types` children.
2098
+ - `/api/cms/collections` now surfaces `admin.description`, `icon`,
2099
+ `color`, and `group` so the admin client can render type cards
2100
+ and tabs without a separate fetch.
2101
+ - New `admin.color` field on `CollectionDefinition` (accent token).
2102
+
2103
+ V1 deliberately skips an in-app Post Type editor / field builder —
2104
+ the cards click through to the existing per-collection list and
2105
+ new Post Types are added via the snippet generator. A follow-up PR
2106
+ introduces a DB-overlay for visual metadata (icon / color /
2107
+ description) editable from the admin UI.
2108
+
2109
+ - 4451a57: `/api/cms/stats` now filters the dashboard's recent activity feed,
2110
+ total document count, per-collection counts, status counts, and SEO
2111
+ score corpus to "user-facing CMS content" only.
2112
+ - If the CMS config registers collections (the production shape),
2113
+ those slugs are the allowlist. Anything else — benchmark fixtures,
2114
+ orphan rows from removed collections, test data — is hidden.
2115
+ - If `config.collections` is empty (dev consumers that ship
2116
+ collections to the admin client only), the endpoint falls back to
2117
+ a small denylist of internal slugs (`benchmarks`, `bench`).
2118
+
2119
+ This stops the benchmark suite's `benchmarks` collection from leaking
2120
+ into the dashboard.
2121
+
2122
+ ## 0.26.0
2123
+
2124
+ ### Minor Changes
2125
+
2126
+ - 560d1de: Dashboard redesign + real-data backends.
2127
+
2128
+ `@actuate-media/cms-admin`:
2129
+ - Sidebar now uses explicit Content and Settings group headers with nested
2130
+ Pages children (Saved Sections, Templates). The collapse control moved
2131
+ to the sidebar footer.
2132
+ - Header hoists primary page actions (e.g. "+ New Post", "View Site")
2133
+ into the top utility bar so the page hero is no longer the source of
2134
+ truth for those affordances. The notification bell is wired to the
2135
+ real `NotificationBell` component.
2136
+ - Dashboard greeting picks the user's first name, and the "Delivery API"
2137
+ - "Content Health" cards now read live data from new endpoints.
2138
+
2139
+ `@actuate-media/cms-core`:
2140
+ - New `ApiRequestMetric` table (minute-bucketed counter + log-scale
2141
+ latency histogram) and `recordApiRequest` instrumentation wired into
2142
+ `handler-factory.ts`. Writes are fire-and-forget — request paths are
2143
+ never blocked on the metric DB.
2144
+ - New `ContentIssue` table + nightly `processContentHealthScan` cron
2145
+ (`/api/cms/cron/content-health`) that runs a pure-logic scanner for
2146
+ missing meta descriptions, missing alt text, outdated content
2147
+ (>90 days), and broken internal links via HEAD probes.
2148
+ - New endpoints `GET /api/cms/stats/api-delivery` and
2149
+ `GET /api/cms/stats/content-health` return the dashboard's
2150
+ aggregates with stable, never-undefined response shapes.
2151
+ - Migration `0007_dashboard_metrics` adds both tables with the indexes
2152
+ the new queries need. Safe to apply to populated DBs (no backfill).
2153
+
2154
+ `create-actuate-cms`:
2155
+ - Scaffolded `vercel.json` now includes the nightly content-health
2156
+ cron entry, and the deployment guide documents it.
2157
+
2158
+ ## 0.25.1
2159
+
2160
+ ### Patch Changes
2161
+
2162
+ - e72948a: Fix `compareToDummyHash` eager-init parity test: also warm Node's
2163
+ libuv crypto worker pool before measuring. PR #170 awaited the
2164
+ eager-init promise but missed that the first PBKDF2 call across the
2165
+ entire process pays an additional ~100-150ms cold-start to spawn the
2166
+ worker thread + initialise the C++ binding context. The first run on
2167
+ `main` (run 26599207053) failed with e1=291ms vs e2=150ms — same ~2×
2168
+ ratio as the historical bug, but caused by worker-pool warmup rather
2169
+ than a real regression. The test now does a throwaway warmup call
2170
+ after the eager-init wait so both measurements run on a hot worker
2171
+ pool.
2172
+ - 40d48da: Two flake fixes from the OSS maintenance pass:
2173
+ - **`NotificationBell` SSE/REST race.** The `seenIdsRef` lookup index
2174
+ was reconciled with `items` via `useEffect([items])`, which runs
2175
+ after React commits. An SSE push that arrived between a REST
2176
+ refresh resolving and the effect running would read a stale ref,
2177
+ fall through the de-dup gate, and append a duplicate row. The ref
2178
+ is now maintained synchronously at every items-mutation call site,
2179
+ and the SSE handler also re-checks the latest snapshot inside the
2180
+ `setItems` updater as a final safety net.
2181
+ - **`compareToDummyHash` eager-init test determinism.** Exported a
2182
+ test-only `_waitForDummyHashReady()` so the timing-parity test can
2183
+ measure verify-only latency (rather than racing the eager hash
2184
+ precompute on a cold module). Added a paired test that asserts the
2185
+ eager-init promise resolves with no work pending on the consumer
2186
+ side — catches future refactors that accidentally drop the
2187
+ top-level invocation.
2188
+
2189
+ ## 0.25.0
2190
+
2191
+ ### Minor Changes
2192
+
2193
+ - a0f6eba: Rich `@`-mention picker for comments (last Phase 7 carry-over).
2194
+
2195
+ **`cms-core`**: New `GET /api/cms/users/search?q=<query>` endpoint —
2196
+ authenticated (any role), case-insensitive `contains` matching against
2197
+ `name` + `email`, capped at 10 results, returns only `{ id, name,
2198
+ email }` (no role / hash / audit metadata). Empty / whitespace
2199
+ queries return an empty array so the endpoint can't be used as a
2200
+ directory-enumeration tool.
2201
+
2202
+ **`cms-admin`**: New `<MentionableTextarea>` component plus pure
2203
+ helpers in `lib/mention-picker.ts` (`detectMentionContext`,
2204
+ `insertMentionToken`, `searchMentionUsers`). Wired into the three
2205
+ comment composer surfaces in `CommentSidePanel`: the new-thread
2206
+ textarea, the reply input, and the in-place edit textarea. The
2207
+ picker is keyboard-driven (Arrow Up / Down, Enter / Tab to commit,
2208
+ Escape to close) with a Radix-style listbox + ARIA roles, and
2209
+ gracefully handles slow responses (drops stale requests when typing
2210
+ quickly).
2211
+
2212
+ **`mcp-server`**: New `search_users(query)` tool exposes the same
2213
+ lookup so AI agents can resolve teammates by name fragment before
2214
+ drafting a comment. Wire format is unchanged — the picker writes
2215
+ `@<userId>` tokens that the existing `extractMentionedUserIds`
2216
+ parser already understands, so no notification schema migration is
2217
+ required.
2218
+
2219
+ No breaking changes.
2220
+
2221
+ ## 0.24.0
2222
+
2223
+ ### Minor Changes
2224
+
2225
+ - 4732184: Per-user SSE push channel for the notification bell (Phase 7 carry-over).
2226
+
2227
+ `@actuate-media/cms-core` exposes a new authenticated SSE endpoint:
2228
+
2229
+ GET /api/cms/notifications/stream
2230
+
2231
+ The stream pushes each `comment_reply`, `comment_mention`, and
2232
+ `comment_resolved` notification the instant it's persisted to
2233
+ `DocumentNotification`. The wire format is `event: ready` (once on
2234
+ connect, with the subscribed `userId`) followed by `event:
2235
+ notification` frames carrying the full DTO, plus a 25 s heartbeat
2236
+ keepalive and a 4 min server-initiated close so `EventSource`
2237
+ reconnects before platform max-duration caps fire.
2238
+
2239
+ `@actuate-media/cms-admin` switches `NotificationBell` over to an
2240
+ `EventSource`-backed subscription with the existing REST endpoints as
2241
+ a convergence backstop (the in-process bus is single-node by design;
2242
+ the poller now runs every 60 s instead of 30 s). The new
2243
+ `subscribeNotificationStream` helper is exported for any consumer that
2244
+ wants to drive its own UI off the same channel.
2245
+
2246
+ Both packages bump `minor`: the cms-core change adds a new public
2247
+ route and a new realtime export surface; the cms-admin change adds a
2248
+ new `NotificationsApi.subscribe` member and exports the
2249
+ `NotificationStreamHandle` / `NotificationStreamOptions` /
2250
+ `EventSourceLike` types. No breaking changes — callers who don't pass
2251
+ a custom `api` continue to work unchanged.
2252
+
2253
+ ## 0.23.0
2254
+
2255
+ ### Minor Changes
2256
+
2257
+ - 21db54b: Phase 7 PR F — MCP-parity audit stylistic cleanup.
2258
+
2259
+ This is the final follow-up to `docs/mcp-parity-audit.md`. After this
2260
+ PR the audit is fully retired — P0, P1, and stylistic notes are all
2261
+ addressed.
2262
+
2263
+ ### `@actuate-media/cms-core`
2264
+
2265
+ Adds `GET /api/cms/collections`: a lightweight discovery endpoint
2266
+ sourced directly from `getActuateConfig()`. Returns
2267
+ `{ data: CollectionMeta[] }` with one entry per configured collection
2268
+ (`slug`, `labels`, `type`, `urlPrefix`, `hidden`, `fieldCount`,
2269
+ `fields[{name,type,required}]`). Unauthenticated by design — the
2270
+ response only exposes schema-shape metadata that is already public via
2271
+ `/openapi.json` — and the contract is intentionally tight (only
2272
+ `name` / `type` / `required` per field, no validators, hooks, or
2273
+ access closures leak).
2274
+
2275
+ ### `@actuate-media/mcp-server`
2276
+
2277
+ Three audit-mandated stylistic cleanups:
2278
+ 1. **AI-create-X collapse (6 tools → 3).** Removed `create_service`,
2279
+ `create_location`, `create_team_member`. Their behaviour is folded
2280
+ into `create_in_collection` via an optional `template` arg
2281
+ (`'service' | 'location' | 'team_member' | 'generic'`) that
2282
+ selects a built-in prompt-enrichment recipe plus a default
2283
+ collection slug. Kept `create_blog_post` and `create_case_study`
2284
+ as type-specific tools because their field-sets are distinctive
2285
+ enough to be standalone agent intents.
2286
+
2287
+ **Breaking change** (acknowledged — pre-1.0, audit-recommended):
2288
+ the three removed tool names will return "tool not found" on next
2289
+ release. Migration is mechanical:
2290
+ - `create_service({ category, ... })`
2291
+ → `create_in_collection({ template: 'service', category, ... })`
2292
+ - `create_location({ cityState, ... })`
2293
+ → `create_in_collection({ template: 'location', cityState, ... })`
2294
+ - `create_team_member({ name, role, ... })`
2295
+ → `create_in_collection({ template: 'team_member', name, role, ... })`
2296
+
2297
+ 2. **`audit_document` split.** Replaced with `audit_document_by_id`
2298
+ (requires `collection` + `id`) and `audit_inline_content` (requires
2299
+ `title` + `body`). Both forward to the same `/ai/audit-document`
2300
+ endpoint; the split exists so agents pick the right mode at the
2301
+ tool boundary instead of constructing a half-filled blob and
2302
+ discovering the "must provide either (collection,id) or body" rule
2303
+ via a server-side error.
2304
+
2305
+ **Breaking change** (audit-recommended): `audit_document` is
2306
+ removed. Migration: pick the explicit tool that matches the
2307
+ intent — `audit_document_by_id` for stored docs,
2308
+ `audit_inline_content` for ad-hoc text.
2309
+
2310
+ 3. **`list_collections` reliability.** Now prefers the new
2311
+ `/api/cms/collections` endpoint as the primary source so the tool
2312
+ stays accurate when the OpenAPI generator drifts. Falls back to
2313
+ OpenAPI only on a real HTTP 404 (older CMS server) — the decision
2314
+ uses `ActuateClientError.status` (newly exported from the client),
2315
+ not a regex against the error message, so an unrelated server
2316
+ error that happens to contain "not found" in its body no longer
2317
+ triggers the fallback. Non-404 errors propagate.
2318
+
2319
+ **Return-shape change** (consistent contract): `data` is now
2320
+ ALWAYS a `CollectionSummary[]` array regardless of which path
2321
+ responded. The OpenAPI fallback path normalises into the same
2322
+ array shape that the new `/collections` endpoint emits, so
2323
+ callers no longer need to branch on `source` to know whether they
2324
+ got an array or an object map. The returned envelope is
2325
+ `{ data: CollectionSummary[], source: 'collections' | 'openapi' }`.
2326
+
2327
+ **Newly exported:** `ActuateClientError` (subclass of `Error`
2328
+ carrying `status: number`). Useful for any consumer that needs
2329
+ to branch on the HTTP status from a `request()` failure.
2330
+
2331
+ ### Tests
2332
+ - mcp-server: 113 tests total (was 97 in PR E). 16 new — template
2333
+ routing for each of service / location / team_member, audit split
2334
+ validation, list_collections primary + fallback + 500-propagation.
2335
+ - cms-core: 4 new tests for the discovery endpoint covering field
2336
+ summaries, `hidden` flag, missing-config 500, and the
2337
+ no-leak-of-validators-or-hooks contract.
2338
+
2339
+ ## 0.22.0
2340
+
2341
+ ### Minor Changes
2342
+
2343
+ - abc7924: Phase 5.5 #1: tag-based edge caching for public document reads.
2344
+ - **`@actuate-media/client`**: `list`, `get`, `global`, `resolve`, and
2345
+ `listMedia` now automatically attach per-resource cache tags
2346
+ (`actuate:collection:<slug>`, `actuate:doc:<id>`,
2347
+ `actuate:global:<slug>`, `actuate:path:<path>`) alongside the existing
2348
+ wholesale `actuate` tag. New exported helpers `actuateTag(kind, slug)`
2349
+ and `actuateTags(kind, ...)` return the canonical tag set so a
2350
+ webhook handler can call `revalidateTag()` with the exact same
2351
+ strings the SDK uses for reads. The `tags` field on `defaultCache`
2352
+ remains opt-in; resource tags are merged on top of it and deduped.
2353
+ `revalidate: false` (no-store) bypasses tag emission entirely.
2354
+ - **`@actuate-media/cms-core`**: new top-level `cache?: CacheConfig`
2355
+ field on `ActuateCMSConfig`. When `cache.publicReads.enabled` is true
2356
+ the four read endpoints (`GET /collections/:slug`,
2357
+ `GET /collections/:slug/:id`, `GET /globals/:slug`, `GET /resolve`)
2358
+ emit `Cache-Control: public, s-maxage=<maxAge>, stale-while-revalidate=<swr>`
2359
+ for unauthenticated and API-key requests. Session-cookie requests
2360
+ always get `private, no-store`; preview-token resolve requests bypass
2361
+ the cache. New `cache.onInvalidate` hook fires after every
2362
+ create/update/delete and global update with the canonical tag set;
2363
+ failures are swallowed so cache invalidation can never roll back a
2364
+ successful write. The cache layer is off by default — existing
2365
+ consumers see no behaviour change until they opt in. See
2366
+ `docs/caching.md` for the full contract.
2367
+
2368
+ - d0a5275: Phase 5.5 #5 — Vercel Runtime Cache (layer 2) for public reads.
2369
+
2370
+ Adds an optional second cache layer between the CDN tier shipped in
2371
+ Phase 5.5 #1 and the database. When a CDN entry expires, the request
2372
+ reaches the function — and a runtime-cache hit lets the function
2373
+ return in ~10–20 ms instead of paying the full ~80 ms Postgres
2374
+ roundtrip.
2375
+ - New `CacheConfig.runtimeCache` config field — `{ adapter, ttl? }`.
2376
+ The adapter contract (`get` + `set` with TTL + tags) matches Vercel's
2377
+ `getCache()` from `@vercel/functions` so it can be passed through
2378
+ unchanged. Self-hosted projects can plug in any KV store (Upstash,
2379
+ in-memory, Redis) by satisfying the same interface.
2380
+ - New `cachedRead` helper in `packages/cms-core/src/cache/http.ts`.
2381
+ Wraps the four public read endpoints
2382
+ (`GET /collections/:slug`, `GET /collections/:slug/:id`,
2383
+ `GET /globals/:slug`, `GET /resolve`). Stamps responses with
2384
+ `X-Actuate-Cache: HIT|MISS`. Session-cookie requests bypass the
2385
+ layer entirely (per-user data must never enter a shared cache),
2386
+ matching the existing CDN-layer rule.
2387
+ - New `cacheKeyFor` helper — derives cache keys from `pathname` plus
2388
+ sorted query, so `?page=1&pageSize=20` and `?pageSize=20&page=1`
2389
+ share a cache entry while `?populate=author` cleanly separates
2390
+ populated/non-populated reads.
2391
+ - Adapter failures (`get` or `set` throwing) are logged and swallowed.
2392
+ Cache outages degrade silently to a direct DB read; user-visible
2393
+ requests never fail because the cache fell over.
2394
+ - Size guard: payloads > ~1.5 MB skip the `set` call to stay under
2395
+ Vercel's 2 MB runtime-cache item ceiling.
2396
+
2397
+ No public API changes for existing consumers. The new field is
2398
+ optional and the four read endpoints behave identically when it's
2399
+ unset.
2400
+
2401
+ ### Patch Changes
2402
+
2403
+ - 1957416: Fix silent production regression introduced in PR #132 (`relationLoadStrategy: 'join'` queries on the dashboard recent-docs panel, document versions list, and `doc_get` bench scenario):
2404
+ - Add the `relationJoins` Preview-feature flag to `packages/cms-core/prisma/schema.prisma` and `apps/dev/prisma/schema.prisma`. Without it Prisma 7 rejects `relationLoadStrategy: 'join'` as an "Unknown argument" — the optimisation never actually fired, the dashboard recent-docs panel silently returned empty (`safeFindMany` swallowed the error), the versions panel returned 500, and `/api/bench` reported 100% errors for `doc_get`.
2405
+ - Stop the `safeCount` / `safeFindMany` / `safeGroupBy` helpers in `api/handlers.ts` from swallowing all errors. They still return the empty value (`0` / `[]`) to keep the request path alive when an optional Prisma model is missing — but now log unexpected errors to `console.warn` so the next time a query is silently failing in production it shows up in Vercel + Sentry logs.
2406
+ - New regression test (`schema-preview-features.test.ts`) pins both `relationJoins` and `fullTextSearchPostgres` on the schema so this class of bug can't recur.
2407
+
2408
+ `apps/dev`: regenerated Prisma client (no source changes — the new flag enables the existing `relationLoadStrategy: 'join'` calls).
2409
+
2410
+ - 93a910a: Phase 5.5 #2 + #3 — cache-miss latency pass.
2411
+ - `populateRelations` / `validateRelations` now batch every relation
2412
+ fetch into a single `RelationLookup.fetchByIds(allCollections, allIds)`
2413
+ call instead of issuing one DB round-trip per target collection.
2414
+ Documents that link into N collections now cost 1 round-trip on
2415
+ populate, not N.
2416
+ - Dashboard recent-docs and version-list queries opt into Prisma 7's
2417
+ `relationLoadStrategy: 'join'`, folding the `createdBy` / `updatedBy`
2418
+ / `changedBy` user joins into one SQL round-trip. Same shape applied
2419
+ to the benchmarks Prisma provider's `getDocument` scenario so
2420
+ `/api/bench` measures the new geometry.
2421
+ - `apps/dev` API routes pin `preferredRegion = 'iad1'` so the deployed
2422
+ Vercel Function co-locates with the Prisma Postgres tier the
2423
+ Marketplace integration provisions in the same region. Override
2424
+ documented in `docs/deployment.md`.
2425
+
2426
+ - 0f50407: Security: bump `sanitize-html` from 2.17.3 to 2.17.4 to fix a default XSS via raw-text `<xmp>` passthrough (GHSA-vjp9-mfvj-w4qg, CRITICAL). The advisory only fires when content is rendered with the default `allowedTags`; cms-core's bundled defaults are unaffected, but the patch closes the entire class of bypasses for any downstream caller that uses the bundled sanitizer.
2427
+
2428
+ ## 0.21.0
2429
+
2430
+ ### Minor Changes
2431
+
2432
+ - 1675eac: Add component-aware blocks — Phase 4 foundation slice.
2433
+
2434
+ Ships the new `@actuate-media/component-blocks` package, which reads a directory of React component source files and emits a typed `Manifest` describing every exported component's props (`string` / `number` / `boolean` / `enum` / `union` / `object` / `array`). Same-file type aliases are resolved automatically. Includes a CLI (`actuate-component-blocks extract --root <dir> --out <file>`) and a programmatic `extractManifest()` API.
2435
+
2436
+ Adds the `componentBlock` field type to `@actuate-media/cms-core`, which consumes a manifest and validates stored block values — rejecting unknown components, missing required props, wrong-type values, and out-of-set enum picks. Field-level `allow` and `defaultComponent` options are supported.
2437
+
2438
+ This is the foundation slice; admin form generation, migration-diff CLI, and discriminated-union pickers ship in follow-up PRs. See `docs/component-blocks.md`.
2439
+
2440
+ ## 0.20.0
2441
+
2442
+ ### Minor Changes
2443
+
2444
+ - 8f2d3f8: Redesign the admin Dashboard and fix the recurring "content cut off on the left" layout bug.
2445
+
2446
+ **Dashboard (`@actuate-media/cms-admin`)**
2447
+ - New `Dashboard.tsx` matching the production design: time-of-day greeting, hero action row (New Post / View Site), horizontally-scrollable quick actions, five stat cards, two-column main grid (Recent Activity + Publishing Queue + Content Health), and a four-tile Content Delivery footer (Total Documents, Scheduled, Form Responses, Active Webhooks).
2448
+ - All data derived from the existing `/stats` and `/health` endpoints — no additional network round-trips. Recent activity, publishing queue, content health, and delivery tiles are computed client-side via `useMemo` so the backend never blocks the frontend.
2449
+ - Fully responsive: stat cards collapse from 5 → 3 → 2 columns at `lg` / `sm` / mobile breakpoints, the main grid stacks below `lg`, and quick actions scroll horizontally on narrow screens.
2450
+ - Deterministic per-author avatar colors (hash-based) so the activity feed is stable across renders without state or extra fetches.
2451
+
2452
+ **Layout cutoff fix (`@actuate-media/cms-admin`)**
2453
+ - Added `lg:shrink-0` to the desktop Sidebar wrapper. Without it, on viewports between `lg` (1024px) and ~1280px the flex sidebar column could compress below the Sidebar's intrinsic 256px width, causing dashboard content to overflow into and visually overlap the sidebar. Combined with the existing `min-w-0` on the main column, the row now reliably renders as `[256px Sidebar][1fr Main]` at every desktop width.
2454
+ - Dashboard is wrapped in `max-w-[1400px] mx-auto w-full` so on ultra-wide displays the content stays in a comfortable reading column instead of stretching edge-to-edge.
2455
+
2456
+ **Stats endpoint extension (`@actuate-media/cms-core`)**
2457
+ - `GET /stats` now returns `webhookCount` and `webhookActiveCount`, calculated via two cheap `safeCount` calls in the existing `Promise.allSettled` batch. The dashboard's "Active Webhooks" tile uses these instead of issuing a second request, keeping page load to a single stats fetch.
2458
+
2459
+ - 0ac9024: Inline AI co-author HTTP endpoint + MCP tool (Phase 2).
2460
+
2461
+ Adds `POST /api/cms/ai/coauthor` with four sub-actions (`rewrite`,
2462
+ `expand`, `compress`, `proofread`) that wrap the plugin-ai writing
2463
+ primitives behind a single endpoint. Rate-limited under the same
2464
+ bucket as content generation (20 calls/hour/user). Missing provider
2465
+ configuration surfaces as HTTP 501 instead of 500.
2466
+
2467
+ Exposed to AI agents via the MCP tool `coauthor_text` with the same
2468
+ input shape, so the admin UI and AI agents can drive identical
2469
+ text-transformation workflows.
2470
+
2471
+ Tests: 5 endpoint tests covering each action + validation errors.
2472
+
2473
+ - 5d18097: Wire per-type SEO defaults into the admin Settings panel — the last loose end from Slice A (SEO Completeness).
2474
+
2475
+ **`cms-core`**
2476
+ - New `seo/config-store` module persists admin edits in a single reserved-slug document (`__seo_config__`) and merges them on top of the static `actuate.config.ts` `seo` block at runtime, field-by-field.
2477
+ - New endpoints `GET /seo/config` and `PUT /seo/config` (admin-only). Saving uses partial-update semantics so editing the site-wide section never wipes per-collection overrides (and vice versa).
2478
+ - `/sitemap.xml`, `/sitemaps/:slug.xml`, `/robots.txt`, `/og.png`, and `/resolve` now load the merged config per request via `loadEffectiveConfig()`, so admin changes are live immediately with no redeploy.
2479
+ - Empty strings in PUT payloads are treated as "unset" (fall back to static config) — the panel is purely additive.
2480
+
2481
+ **`cms-admin`**
2482
+ - New **Settings → SEO** tab backed by `<SEOConfigPanel />`. Edits site-wide defaults (siteUrl, siteName, OG image, Twitter handle, change-freq, priority, robots toggles, AI-bot blocking) and per-collection defaults (Schema.org type, archive path, sitemap priority/changefreq, noindex/nofollow, exclude-from-sitemap).
2483
+ - Static `actuate.config.ts` values render as input placeholders so admins always see what they're overriding.
2484
+
2485
+ 12 new unit tests cover the merge semantics, unset behaviour, DB roundtrip, upsert (no duplicate rows), and DB-error fallback.
2486
+
2487
+ - 313a23f: Add Slice F — AI quality pass.
2488
+
2489
+ `plugin-ai` exposes a new `auditDocument()` composer that combines SEO,
2490
+ readability (Flesch-Kincaid / Gunning Fog / SMOG averaged + remapped to a
2491
+ 0-100 score), accessibility, and freshness scoring into a single
2492
+ composite grade with prioritized actionable issues. The composer is
2493
+ deterministic — it never calls an LLM — so it's cheap enough to invoke
2494
+ on every save or debounced keystroke.
2495
+
2496
+ `cms-core` adds two HTTP endpoints:
2497
+ - `POST /ai/audit-document` — runs the composite audit against a stored
2498
+ document (`{ collection, id }`) or ad-hoc content. Honors field-level
2499
+ access control when loading from the DB.
2500
+ - `POST /ai/suggest-internal-links` — scans published documents for
2501
+ phrases the supplied content could link to, returning ranked
2502
+ anchor-text + target-URL suggestions. Optionally scopes to a single
2503
+ collection.
2504
+
2505
+ `mcp-server` exposes both endpoints as MCP tools (`audit_document` and
2506
+ `suggest_internal_links`) so AI agents can grade drafts and propose
2507
+ internal links before publish.
2508
+
2509
+ Docs: `docs/ai-quality-audit.md`.
2510
+
2511
+ ## 0.19.0
2512
+
2513
+ ### Minor Changes
2514
+
2515
+ - 39f5941: Slice E — Preview & Publishing Polish.
2516
+
2517
+ **Draft preview links**
2518
+ - Preview tokens now accept a configurable `ttlSeconds` (clamped to
2519
+ `[60, 2,592,000]`, default 7 days). The old 5-minute hard-coded TTL
2520
+ was too short for sharing with clients.
2521
+ - `POST /preview/token` now verifies the document exists before
2522
+ issuing a token, includes the doc's `slug` in the response so the
2523
+ admin can build a friendly URL, and records the issuing user in the
2524
+ JWT `sub` claim for audit logs.
2525
+ - `GET /resolve?preview=<token>` drops the `status: PUBLISHED` filter
2526
+ when a valid token is presented, returning draft content at the
2527
+ document's real URL. The response includes a `preview: { active,
2528
+ expiresAt }` block so frontends can render a "preview mode" banner.
2529
+ Tokens are bound to a single `(collection, documentId)` — using one
2530
+ against a different URL returns `403`.
2531
+
2532
+ **Scheduled publishing**
2533
+ - New `POST /collections/:slug/:id/schedule` endpoint sets
2534
+ `scheduledAt` and/or `scheduledUnpublishAt` (validated as future
2535
+ ISO 8601 timestamps with `unpublish > publish`) and flips status to
2536
+ `SCHEDULED`. The existing scheduling cron picks the doc up at the
2537
+ scheduled time.
2538
+ - New `DELETE /collections/:slug/:id/schedule` cancels a pending
2539
+ schedule. `SCHEDULED` docs revert to `DRAFT`; `PUBLISHED` docs with
2540
+ a pending unpublish stay live.
2541
+
2542
+ **Admin UI**
2543
+ - New **Share preview** toolbar button on `DocumentEdit` opens a
2544
+ dialog to generate signed preview URLs with selectable TTL
2545
+ (1 hour / 1 day / 7 days / 30 days) and a copy-to-clipboard control.
2546
+ - New **Schedule** button in the Status panel opens a datetime picker
2547
+ for setting both publish and (optionally) unpublish times. Active
2548
+ schedules render an inline blue pill with the times. "Reschedule"
2549
+ and "Cancel schedule" supported.
2550
+ - New exported components: `SharePreviewLinkDialog`,
2551
+ `SchedulePublishDialog`.
2552
+
2553
+ **Tests & docs**
2554
+ - 20 new tests: preview adapter TTL clamping, JWT round-trip, secret
2555
+ isolation; `/preview/token` + `/resolve?preview=` happy/sad paths;
2556
+ schedule create/cancel/validation paths.
2557
+ - New `docs/preview-and-scheduling.md` walkthrough; `docs/api-reference.md`
2558
+ expanded with the new endpoints.
2559
+
2560
+ ## 0.18.0
2561
+
2562
+ ### Minor Changes
2563
+
2564
+ - 4433301: Phase 2 Slice D — Content Relations & References.
2565
+ - **cms-core**: runtime support for the `relationship` field type — referential validation and opt-in depth-1 population.
2566
+ - **Validation**: every create/update verifies that referenced IDs point at live (non-deleted) documents in the declared target collection(s). Broken references fail with a precise error (`Relation field "author" references document "team-x42" which does not exist in collection "team".`). Updates only re-validate fields the caller actually touched.
2567
+ - **Population**: pass `?populate=author,relatedPosts` (or `*`) on `GET /collections/:slug/:id` and `GET /resolve` to expand stored IDs into `{ id, collection, title, slug, status, data }` summaries. Batches by target collection (≤ 1 query per collection per request); soft-deleted refs fall back to raw IDs.
2568
+ - **Presets**: new `relationField()` and `relatedDocsField()` helpers. Built-in collection presets now wire `posts.relatedPosts → posts`, `case-studies.services → services` (the `locations.servicesOffered → services` relation continues unchanged).
2569
+ - **Multi-target**: `relationTo: ['posts', 'case-studies']` continues to work — validator accepts a doc if it lives in any target.
2570
+ - Public API: `validateRelations`, `populateRelations`, `collectRelationRefs`, `parsePopulateParam`, `buildRelationLookup`, plus types `RelationLookup`, `RelationSummary`, `PopulateOptions`.
2571
+ - **plugin-ai**: the document content generator now skips `relationship` fields when building the LLM prompt (the model can't know your document IDs) and accepts ID strings only on output. Prevents the AI from inventing broken references that the validator would then reject.
2572
+ - **23 new tests** in `cms-core/src/__tests__/fields/relations.test.ts` covering ref collection, validation, population, populate-param parsing, multi-target relations, and array-nested relations. Full suite: 620 cms-core tests passing.
2573
+ - **Docs**: new `docs/relations.md` (declaration, validation, populate, admin UI, common patterns), `?populate=` documented in `docs/api-reference.md` for both `/collections/:slug/:id` and `/resolve`.
2574
+ - **Admin UI**: the existing `RelationshipField` picker continues to work — search, multi-select, status badges, "Create new" shortcut. No changes required.
2575
+
2576
+ ## 0.17.0
2577
+
2578
+ ### Minor Changes
2579
+
2580
+ - e6a18b5: Phase 2 Slice C — AI Content Authoring for any collection.
2581
+ - **plugin-ai**: new `generateDocumentContent()` helper that maps a prompt onto a collection's field schema and returns structured field data ready to persist. Supports tone, target audience, brand voice profile, and existing-content context. Coerces LLM output to declared field types (numbers, booleans, dates, select options, array items) and drops fields the schema doesn't declare.
2582
+ - **cms-core**: new `POST /collections/:slug/ai-create` endpoint — the non-page counterpart to `/page-builder/create`. Generates a complete document for any collection, persists as draft, optionally publishes via a follow-up update when the caller has admin scope. Refuses page-builder collections (use `/page-builder/create` instead). Hard caps prompt at 4000 chars and shares the per-user 20/hour AI rate-limit bucket. Returns a `warning` field when create succeeds but publish is denied.
2583
+ - **mcp-server**: six new tools — `create_in_collection` (generic) plus per-type sugar tools `create_blog_post`, `create_case_study` (with `clientName` / `industry` hints), `create_service` (`category` hint), `create_location` (`cityState` hint), and `create_team_member` (`name` / `role` hints). Each per-type tool targets the conventional collection slug shipped by the cms-core collection presets, with an explicit `collection` override for sites using custom slugs.
2584
+ - 19 new tests (10 unit tests in plugin-ai for the content generator, 9 integration tests in cms-core for the API endpoint covering auth, validation, persistence, publish flow, and the page-builder refusal path).
2585
+ - Docs: updated `docs/api-reference.md` with the new endpoint, `docs/mcp.md` and `packages/mcp-server/README.md` with the new tools and example agent prompts.
2586
+
2587
+ ## 0.16.0
2588
+
2589
+ ### Minor Changes
2590
+
2591
+ - 9377c39: **Post-type Polish (Phase 2, Slice B)** — one-line content types for the eight patterns nearly every marketing site needs, plus matching page-builder starter templates.
2592
+ - **Collection presets** — `blogPostCollection()`, `caseStudyCollection()`, `portfolioCollection()`, `serviceCollection()`, `locationCollection()`, `teamMemberCollection()`, `faqCollection()`, `testimonialCollection()`. Each returns a complete `CollectionDefinition` with sensible fields, admin defaults, `seo.defaultSchemaType` set to the right Schema.org type (BlogPosting / Article / CreativeWork / Service / LocalBusiness / Person / FAQPage / Review), archive paths for post-type collections, sitemap priorities, and admin grouping. All accept `CollectionPresetOverrides` (extraFields, seo merge, label / slug / urlPrefix overrides).
2593
+ - **Field presets** — reusable bundles you can spread into any collection: `titleField`, `slugField`, `excerptField`, `richContentField`, `featuredImageField`, `tagsField`, `publishedDateField`, `authorField`, `quoteField`, `ctaField`, `seoFields`, `locationFields`, `geoFields`, `priceFields`.
2594
+ - **Four new page-builder templates** — `case-study` (hero / metrics / challenge / solution / results / quote / CTA), `portfolio` (cover / info strip / description / gallery / CTA), `service` (hero with CTA / description / benefits / pricing / FAQ), `team-member` (avatar hero / bio / contact links). Joins the existing `blank`, `landing`, `about`, `contact`, `location`, and `blog-post` templates.
2595
+ - **Per-collection template suggestion** — `GET /api/cms/page-templates?collection=:slug` now reorders the response so suggested templates bubble to the top, annotates each template with `suggested` + `suggestionRank`, and returns a `suggestion` object pointing at the primary template ID. `getTemplatesForCollection('posts')` exposes the mapping in code.
2596
+ - **33 new tests** (16 unit field-preset + 17 unit collection-preset + suggestion lookup). Full suite: 588 passed.
2597
+ - **Docs updated** — `docs/configuration.md` adds the Collection Presets, Field Presets, and Page-builder Templates per Type sections.
2598
+
2599
+ ## 0.15.0
2600
+
2601
+ ### Minor Changes
2602
+
2603
+ - 00ffcf7: **SEO Completeness (Phase 2, Slice A)** — production-grade SEO surfaces wired into the core CMS with zero extra plugins.
2604
+ - **New `composePageMeta()` helper** — One-shot SEO composer: pass a resolved document, its collection, and the CMS config and get back a Next.js-ready `metadata` object, computed JSON-LD (Article / BlogPosting / LocalBusiness / Service / etc.), pre-rendered `<meta>` / `<script>` HTML, and the canonical URL. Auto-detects Schema.org type from collection slug (`posts → BlogPosting`, `locations → LocalBusiness`, `services → Service`, …) and respects per-collection `defaultSchemaType` / `defaultRobots` / `defaultOgImage` overrides.
2605
+ - **`/resolve` now returns `meta`, `jsonLd`, and `jsonLdHtml`** — SSR consumers (Next.js apps, AI agents, MCP clients) get a ready-to-render meta tag block and JSON-LD `<script>` tag without re-deriving anything from raw doc data.
2606
+ - **Public sitemap + robots + OG routes** — `GET /sitemap.xml`, `GET /sitemaps/:slug.xml`, `GET /robots.txt`, and `GET /og.png` are served automatically from the CMS handler once `seo.siteUrl` is set. Each route can be disabled individually (`seo.sitemap.disabled`, `seo.robots.disabled`, `seo.ogImage.disabled`).
2607
+ - **Per-collection SEO config** — `CollectionDefinition.seo` now accepts `defaultSchemaType`, `defaultRobots`, `defaultOgImage`, in addition to the existing `sitemapPriority` / `sitemapChangeFreq` / `excludeFromSitemap` / `archivePath`.
2608
+ - **Site-wide SEO config** — New top-level `seo` block on `ActuateCMSConfig` for `siteUrl`, `siteName`, `defaultOgImage`, `twitterHandle`, `organization`, `robots`, `sitemap`, and `ogImage` defaults.
2609
+ - **AI-bot blocking** — `seo.robots.blockAIBots: true` adds disallow rules for GPTBot, ChatGPT-User, ClaudeBot, Claude-Web, anthropic-ai, Bytespider, CCBot, and Google-Extended.
2610
+ - **27 new tests** covering `composePageMeta()` (15) and the public SEO routes (12).
2611
+ - **Updated docs** — `docs/seo.md` adds the zero-config-surfaces section and `composePageMeta` walkthrough; `docs/api-reference.md` documents the new public routes and the enriched `/resolve` response.
2612
+
2613
+ ## 0.14.0
2614
+
2615
+ ### Minor Changes
2616
+
2617
+ - e87471d: Phase 1: AI access. API key authentication (act*sk*\* bearer tokens) with scope-based authorization across collections, globals, media, and the page builder. New /api-keys admin endpoints + UI for creating, listing, and revoking keys with password reauth. CSRF is skipped for API-key requests. New /page-builder/create one-shot endpoint that generates an AI page and persists it as a document in a single call. New @actuate-media/mcp-server package exposes the CMS to AI agents (Claude, Cursor, ChatGPT, etc.) via the Model Context Protocol — full CRUD, globals, media, page generation, and one-shot create tools. Adds 44 new tests covering the API key auth path, scope enforcement, CSRF skip, and MCP client/tools.
2618
+
2619
+ ## 0.13.0
2620
+
2621
+ ### Minor Changes
2622
+
2623
+ - 9cf5d10: Engineering audit — medium and low items.
2624
+
2625
+ **New features**
2626
+ - `validateEnvShape()` (`diagnostics/env.ts`): runtime check that `CMS_SECRET`, `CMS_ENCRYPTION_KEY`, `DATABASE_URL`, `BLOB_READ_WRITE_TOKEN`, `UPSTASH_REDIS_*`, `RESEND_API_KEY`, and `CRON_SECRET` are not just present but well-formed (length, hex, placeholder detection). Surfaced via `/api/cms/health` so misconfiguration is caught at deploy time, not at first runtime use. Adds an `unhealthy` status when any required var is malformed.
2627
+ - `createLogger(scope)` (`diagnostics/logger.ts`): structured per-scope logger that respects `ACTUATE_LOG_LEVEL` (`silent`/`error`/`warn`/`info`/`debug`) and `ACTUATE_LOG_FORMAT=json` for log-aggregator ingestion. Defaults to `warn` in production, `info` elsewhere.
2628
+ - `getActuateConfig()` / `setActuateConfig()` (`config/runtime.ts`): typed accessor for the per-process CMS config, replacing scattered `(globalThis as any).__actuateConfig` casts. Documents the single-process / set-once contract in detail.
2629
+
2630
+ **Hardening**
2631
+ - `/media/upload` now requires a valid `Content-Length` header before parsing the body, closing a chunked-encoding bypass that allowed unbounded memory consumption (audit M7).
2632
+ - In-memory rate limiter (`createInMemoryRateLimiter`) now has a 10,000-key LRU cap with periodic expired-entry sweeping. Prevents unbounded `Map` growth under enumeration / IP-spoofing (audit M1).
2633
+ - Upstash rate-limiter outages now emit a throttled `rate_limit.degraded` audit log entry in addition to the existing `console.error`, giving operators a durable record of when the limiter was failing open (audit M2).
2634
+
2635
+ **Schema**
2636
+ - `AuthConfig` now properly types `maxConcurrentSessions`, `passwordPolicy`, and `oauth.{providers, allowSelfSignup}` — these were already consumed at runtime but cast through `any`.
2637
+ - `ActuateCMSConfig.redirects.allowedExternalHosts` is now part of the public type.
2638
+
2639
+ **Bug fixes**
2640
+ - Removed dead `config.plugins.forms.hooks` access in the form-submission handler — `plugins` is an array, not an object keyed by name, so this path always evaluated to `undefined` and was masked by the `as any` cast.
2641
+ - `/api/cms/health` now derives the response status from env validation in **both** the success and the DB-failure paths (Bugbot review on PR #41, Medium). The early-return on `db()` failure previously hardcoded `status: 'degraded'`, so a deploy with a broken DB _and_ a malformed `CMS_SECRET` would report `degraded` instead of the more accurate `unhealthy` — defeating the M6 goal of catching env misconfig at `/health` time.
2642
+ - `validateEnvShape()` "ok" messages no longer disclose exact secret lengths (Bugbot review on PR #41, Low). The previous wording — `Configured (42 chars).` — leaked configured `CMS_SECRET` and `CRON_SECRET` lengths via the unauthenticated `/health` endpoint, narrowing brute-force search space with no operational benefit. Lengths still appear in error/warn messages because those only surface on misconfiguration that the operator must fix.
2643
+ - Removed dead exported `log` singleton from `diagnostics/logger.ts` (Bugbot review on PR #41, Low). Every consumer uses `createLogger(scope)`; the convenience export was never imported.
2644
+ - Removed unreachable `isPlaceholder(encKey)` branch in `validateEnvShape()` (Bugbot review on PR #41, Low). After the `length === 64` and `HEX_RE` checks pass, the value is guaranteed pure hex characters, but every pattern in `PLACEHOLDER_PATTERNS` requires non-hex letters — so the branch could never fire. The `HEX_RE` failure message already reads "Looks like a placeholder" verbally, which covers the common case (e.g. `aes256-local-dev-key-change-in-prod`).
2645
+ - `/api/cms/health` now consults all three legitimate `CMS_SECRET` sources via a wrapped `EnvSource` passed to `validateEnvShape()` — `process.env.CMS_SECRET`, the legacy `process.env.CMS_SESSION_SECRET`, and `getActuateConfig()?.secret` (Bugbot review on PR #43, Medium). Previously the validator only inspected `process.env.CMS_SECRET`, so a deploy that configured the secret via `actuate.config.ts` got the contradictory response `secretConfigured: true` AND `status: "unhealthy"` — false alarm that would trip Kubernetes readiness probes / load-balancer health checks. The validator's `'missing'` message now also names the config-file path so the diagnostic matches `getSessionSecret()`'s own behavior.
2646
+
2647
+ **Documentation**
2648
+ - `README.md`: filled in with project overview, repo layout, dev quickstart, Windows long-path setup instructions, env-var reference, and release flow.
2649
+ - `.cursor/rules/security.mdc`: corrected to say `sanitize-html` (matches code) instead of `isomorphic-dompurify`.
2650
+ - `.cursor/rules/architecture.mdc`: documents the `getActuateConfig()` accessor and forbids direct `globalThis` reads.
2651
+
2652
+ **Tests** (+ 80 net new tests; 525 total passing across cms-core)
2653
+ - `__tests__/auth/oauth.test.ts` (29): PKCE primitives, state JWT round-trip, nonce binding, provider mismatch rejection, password-protected account refusal, self-signup gating, OAuthAccount-based linking, session creation.
2654
+ - `__tests__/auth/reset.test.ts` (17): single-use tokens, expiry, deactivated-user rejection, password policy enforcement, atomic update + session revocation, replay protection.
2655
+ - `__tests__/diagnostics/env.test.ts` (12): shape validation across every required and optional env var, plus 3 parametrized regression cases asserting "ok" messages do not include exact lengths for `CMS_SECRET`, `CMS_ENCRYPTION_KEY`, or `CRON_SECRET`.
2656
+ - `__tests__/diagnostics/logger.test.ts` (7): level filtering, JSON output, prefix formatting.
2657
+ - `__tests__/security/rate-limit.test.ts` (+2): LRU cap eviction and most-recently-used preservation.
2658
+ - `__tests__/api/health.test.ts` (5): `/health` returns `unhealthy` (not `degraded`) when env is broken AND db throws; `/health` returns `degraded` when only db throws and env is fine; `/health` JSON contains no `\d+\s*chars?` pattern (length-leak regression); `/health` does not report `unhealthy` when the secret is supplied via `CMS_SESSION_SECRET` (legacy env); `/health` does not report `unhealthy` when the secret is supplied via `actuate.config.ts → secret` (config-file path).
2659
+
2660
+ **Internal**
2661
+ - `turbo.json`: explicit `outputs: []` for `@actuate-media/update-server#build` (it runs `tsc --noEmit`, no artifacts).
2662
+ - Migrated `console.*` calls in `rate-limit`, `audit`, and `oauth` to the new structured logger.
2663
+ - Replaced `@ts-ignore` with `@ts-expect-error` on the streaming `RequestInit` cast.
2664
+
2665
+ ## 0.12.0
2666
+
2667
+ ### Minor Changes
2668
+
2669
+ - 370fa95: Security hardening (engineering-audit High items):
2670
+ - **SSRF DNS-rebinding defense**: `safeFetch` now resolves the hostname before fetching and rejects when any A/AAAA record lands in a private range. Previously the URL was only validated as written, so an attacker controlling DNS for `evil.tld` could return a public IP at validation time and `127.0.0.1` at fetch time.
2671
+ - **SSRF IP canonicalization rewrite**: replaced regex-based hostname matching in `validateWebhookUrl` with a proper IP canonicalizer (`security/ip-canon.ts`). Now blocks every encoding `getaddrinfo` accepts — decimal (`http://2130706433`), octal (`http://0177.0.0.1`), hex (`http://0x7f.0.0.1`), short-form (`http://127.1`), IPv4-mapped IPv6 (`http://[::ffff:127.0.0.1]`), bracketed IPv6, and the carrier-grade NAT range (`100.64.0.0/10`). Cloud-metadata hostnames (`metadata.google.internal`, `*.internal`) are also rejected explicitly.
2672
+ - **AES-256 key validation**: `encryptField` / `decryptField` now throw `InvalidEncryptionKeyError` at the call site if `CMS_ENCRYPTION_KEY` isn't exactly 64 hex chars. Previously a misconfigured key (e.g. the placeholder `aes256-local-dev-key-change-in-prod`) only surfaced as an opaque WebCrypto error the first time a field was written — late, in production, hard to diagnose.
2673
+ - **JWT payload shape validation**: `verifySession` and OAuth `verifyState` now assert the decoded payload has the expected fields (`userId`, `role`, `sessionId`) and strip extra attacker-supplied claims before returning. Throws `InvalidSessionPayloadError` / `InvalidOAuthStateError` on malformed payloads.
2674
+ - **Login user-enumeration timing defense**: when the email doesn't exist OR the account is OAuth-only, the login handler now still runs PBKDF2 against a dummy hash before returning the error. Previously the response-time delta let attackers enumerate valid emails.
2675
+ - **PBKDF2 strength bump**: hash iterations raised from 100,000 → 600,000 (OWASP / NIST 2023+ guidance for PBKDF2-HMAC-SHA256). The hash format embeds the iteration count, so old 100k hashes still verify; `verifyPassword` reads the count from the stored string. Login opportunistically re-hashes weak stored hashes after a successful sign-in.
2676
+ - **Cron endpoints**: added `/api/cms/cron/publish`, `/api/cms/cron/cleanup`, `/api/cms/cron/seo-scan` matching the documented Vercel cron paths. Auth is via `Authorization: Bearer ${CRON_SECRET}` (constant-time compare, fail-closed when the env var is unset). Cleanup hard-deletes expired sessions, old audit logs (90d), trashed documents (30d), and used password reset tokens (1d).
2677
+
2678
+ New tests: 99 added across `auth/password`, `auth/session`, `security/encrypted-fields`, `security/ssrf`, `security/safe-fetch`, and `cron/`.
2679
+
2680
+ ### Patch Changes
2681
+
2682
+ - 370fa95: Security follow-ups from the Bugbot review of PR #40 (engineering-audit High items):
2683
+ - **SSRF — malformed IP literals now fail closed.** `canonicalizeHostname()` previously returned `{isHostname: false}` for IP-shaped strings that failed to parse (e.g. `::1::1`, `300.0.0.1`) without setting `ipv4`/`ipv6`. `validateWebhookUrl` and `safeFetch.resolveAndCheck` then treated that as "valid public IP, no DNS lookup needed" and let the request through. The canonicalizer now exposes an explicit `isValidIp` flag and the SSRF callsites reject malformed IP literals up front.
2684
+ - **Login timing — dummy PBKDF2 hash is now eager.** `compareToDummyHash` previously initialized the dummy hash lazily on first call, so the first unknown-email login after a cold start was ~1s slower than subsequent ones, partially defeating the timing-side-channel defense added in the original High fix. The dummy hash is now precomputed at module load (top-level Promise) and `compareToDummyHash` awaits it.
2685
+ - **Cron auth — constant-time compare no longer leaks secret length.** `isAuthorizedCronRequest` compared the bearer token to `CRON_SECRET` after an early `if (a.length !== b.length) return false`, which made the response time depend on the submitted token's length. Replaced with HMAC-SHA256 of both inputs (always 32 bytes) compared via `node:crypto.timingSafeEqual` against a per-process random key.
2686
+ - **Cron endpoints — accept GET (Vercel Cron) in addition to POST.** Vercel Cron sends GET requests by default. The original implementation registered the cron paths as POST-only, so every Vercel-scheduled invocation would have returned 404/405 and silently skipped. Endpoints are now registered for both GET and POST so Vercel Cron and self-hosted POST schedulers (k8s CronJob, GH Actions `curl -X POST`, EventBridge) both work.
2687
+ - **Cron `modelExists` correctly rejects null delegates.** The guard used `typeof db[name] === 'object'`, which is `true` for `null` (the historical `typeof null === 'object'` quirk). With `{ session: null }` the guard returned true and the subsequent `db.session.deleteMany(...)` threw a TypeError that the outer try/catch only papered over. Now explicitly checks `delegate !== null`.
2688
+ - **Cron SEO scan H1 detection is case-insensitive.** The image-missing-alt scan uses `/gi`, but the H1-presence check used `content.includes('<h1')`, so documents with valid `<H1>` headings were falsely flagged as missing. Replaced with `/<h1\b/i.test(content)` to match the HTML spec and the rest of the function's behavior.
2689
+ - **Cron SEO scan alt-text check is case-insensitive.** Sibling bug to the H1 fix above: the `<img>` regex was `/gi` (case-insensitive), but the `alt=` substring test inside the filter was case-sensitive (`img.includes('alt=')`), so `<IMG ALT="text">` matched the regex and was then falsely counted as missing alt text. Replaced with `/\balt\s*=/i.test(img)` so all uppercase / mixed-case attribute spellings are recognized.
2690
+
2691
+ New tests:
2692
+ - `security/ssrf.test.ts` — 9 cases covering malformed IPv4/IPv6 literals, embedded-null hostnames, oversize labels, and `validateWebhookUrl`/`resolveAndCheck` rejecting them.
2693
+ - `auth/password.test.ts` — regression assertion that the first `compareToDummyHash` call is not significantly slower than later calls.
2694
+ - `cron/cron.test.ts` — 5 cases covering varying-length `CRON_SECRET` attempts (constant-time guarantee), plus 1 regression for null model delegates, 4 parametrized cases for case-insensitive H1 detection, and 5 parametrized cases for case-insensitive alt-text detection.
2695
+ - `api/cron-routes.test.ts` — 12 cases covering GET (Vercel Cron path) and POST for all three cron endpoints, plus 401 paths.
2696
+
2697
+ ## 0.11.2
2698
+
2699
+ ### Patch Changes
2700
+
2701
+ - 6d2ea6e: Two fixes that emerged while stabilizing the e2e suite:
2702
+ - **cms-core**: `createRateLimiter()` now respects `ACTUATE_DISABLE_RATE_LIMIT=1` and returns a permissive limiter that always allows requests. This is for test harnesses where every test shares the same client IP and exhausts the strict per-IP buckets within the first few login attempts. Production never sets this var; the deploy guide and Vercel templates omit it. Previously the env var bypass only existed at the per-handler `checkRateLimitAsync` wrapper in `handlers.ts`, so the global API rate limit in `handler-factory.ts` (which uses `rateLimiter.check()` directly) ignored it and broke retries with HTTP 429.
2703
+ - **cms-admin**: `<SEO />` view crashed with "M.filter is not a function" the first time it rendered. The `/seo/link-health` endpoint returns `{ truncated, checked, issues: [...] }` (an object), but `useApiData<any[]>` typed it as an array and the `linkHealth = linkHealthData ?? []` coercion silently put the wrapper object into the `linkHealth` variable. Calls like `linkHealth.filter(l => l.status === 404)` then threw on every render — including when the user was on the (default) Pages tab, because Radix `<Tabs.Content>`'s children are evaluated eagerly even when the tab is unmounted. Fixed the typing and switched to `linkHealthData?.issues ?? []`.
2704
+
2705
+ ## 0.11.1
2706
+
2707
+ ### Patch Changes
2708
+
2709
+ - 22c314b: Add `ACTUATE_DISABLE_RATE_LIMIT=1` env-var escape hatch for test
2710
+ harnesses. Production never sets it; the wired bypass lives at the
2711
+ single `checkRateLimitAsync` chokepoint in `api/handlers.ts`.
2712
+
2713
+ Why: the e2e Playwright suite shares a single `login:unknown` rate
2714
+ limiter bucket across all 12 specs (the dev server doesn't set
2715
+ `X-Forwarded-For`, so `getClientIp()` returns `'unknown'`). The strict
2716
+ 5-attempts-per-15-min login limiter exhausts after the first three
2717
+ specs, causing every subsequent test to fail with "Too many login
2718
+ attempts." This was masked for months by an unrelated `pnpm
2719
+ format:check` CI failure that gated the whole `e2e` job.
2720
+
2721
+ The bypass is keyed off an explicit env var rather than `NODE_ENV ===
2722
+ 'test'` because some Next.js build steps run with `NODE_ENV=test` and
2723
+ we want the rate limiter exercised by unit tests that don't opt in.
2724
+
2725
+ ## 0.11.0
2726
+
2727
+ ### Minor Changes
2728
+
2729
+ - 6db988e: Major engineering quality + integration upgrades.
2730
+
2731
+ **Security & reliability (cms-core)**
2732
+ - Audit log: fixed runtime crash from `orderBy: createdAt` → `timestamp`; never throws on insert; allows null `details`.
2733
+ - IPv4 + IPv6 + CIDR allowlist with canonical comparison; reject `unknown` IPs against non-empty allowlists.
2734
+ - Centralised trusted client IP extraction via `x-vercel-forwarded-for` / `x-real-ip` / `x-forwarded-for` (with explicit `trustProxy`).
2735
+ - Atomic Upstash `INCR` + `EXPIRE NX` rate limiter, fail-open with logging.
2736
+ - Encryption at rest for OAuth tokens, TOTP secrets, backup codes, and webhook secrets via `CMS_ENCRYPTION_KEY`.
2737
+ - Hardened TOTP login: `mfa_pending` JWT + per-IP/per-user rate limit + request fingerprint binding (no userId leak between steps).
2738
+ - Reauth required for TOTP setup/disable; revoke other sessions on TOTP changes.
2739
+ - OAuth state nonce bound to a cookie; `allowSelfSignup` flag; never silently link to password-protected accounts.
2740
+ - SSRF-safe `safeFetch()` for webhooks and `/seo/link-health`; concurrency cap + URL count cap; private IPs/loopback/link-local rejected.
2741
+ - Magic-byte + MIME validation + DOMPurify SVG sanitisation on `/media/upload`; tightened WebP/AVIF/GIF/SVG detectors.
2742
+ - Webhook + plugin hook dispatch moved outside the DB transaction so a rollback never fires events for a doc that no longer exists.
2743
+ - AI: per-user rate limit + token/cost budget + input length cap + balanced-brace JSON extractor + `validateTree` after every step + secret redaction in audit prompts.
2744
+ - CSRF exemption tightened to exact-prefix match on the rewritten path (no more `pathname.includes`).
2745
+ - `refreshSession`: validate session row exists & is active before re-issuing.
2746
+ - Public SEO endpoints require auth; `/llms.txt` filters PUBLISHED + non-deleted.
2747
+ - `/forms`, `/forms/:id/submissions`, `/search/global` users restricted to EDITOR+.
2748
+ - Open-redirect host allowlist for `/redirects`; folder routes validate scope on move/delete; password-reset email enumeration blocked.
2749
+ - `/script-tags/resolve` returns proper public cache headers.
2750
+
2751
+ **Integration fixes (consumer feedback)**
2752
+ - **`createServerSiteClient(prisma, config)`** — new server-only Site client that reads from Prisma directly. Use in Server Components, `generateMetadata`, route loaders. Avoids the `Invalid URL` crash that `createSiteClient()` throws under bare Node, and skips the self-fetch overhead.
2753
+ - **`createSiteClient()`** now auto-resolves `baseUrl` from `NEXT_PUBLIC_SITE_URL` or `VERCEL_URL` when not explicitly provided.
2754
+ - **`/public/globals/:slug`** defaults to public when no `access.read` is set (was a 403 footgun).
2755
+ - **`cmsApi`** auto-bootstraps the `actuate_csrf` cookie before non-GET requests; `AdminRoot` also primes it on mount when a session exists. Fixes the silent 403 every consumer hit when signing in via a custom Server Action.
2756
+ - **AdminShell** routes collections with `type: 'page'` through `<PageBuilder>` instead of `<DocumentEdit>`. Built-in admin paths (`/users`, `/media`, etc.) are reserved before collection routes so a user-defined `users` collection can't shadow the Users view.
2757
+ - **`BlockCatalog`** now reads custom blocks from `globalThis.__actuateConfig.pageBuilder.blocks` automatically. Also accepts an explicit `customBlocks` option and an `includeCore: false` flag.
2758
+ - **Page Builder envelope** unified at the top level — `layout` and `pageSettings` are accepted on POST/PUT and returned at top-level on GET. Storage shape is `data._layout` / `data._pageSettings`; both wire shapes accepted on input for backwards compatibility.
2759
+ - **`/media/upload`** now uses the configured platform storage adapter (e.g. `platform-vercel`) when set; honors `token`/`prefix` options. Falls back to direct `@vercel/blob` import for legacy installs.
2760
+ - **BlockEditor** field types fleshed out: real TipTap editor for `richText`, working media picker for `media`, JSON editor with validation for `json`, plus `slug`, `date`, and an array editor for primitive arrays.
2761
+ - **`pageBuilder.previewTheme`** now applied to the canvas via scoped CSS variables.
2762
+ - OpenAPI: documented `/public/globals/:slug` and `/resolve`.
2763
+
2764
+ **DX**
2765
+ - `.env.example` documents required formats for `CMS_ENCRYPTION_KEY` (64-hex), `BLOB_READ_WRITE_TOKEN`, and `RESEND_*`.
2766
+ - Tests: 266 passing (was 230); regression tests for IP allowlist, client-ip, redact, secret-storage, audit, upload-magic, server-site, custom block discovery.
2767
+
2768
+ ## 0.10.4
2769
+
2770
+ ### Patch Changes
2771
+
2772
+ - 58bf2e6: Add site-wide SEO robots defaults with per-page index/follow override controls.
2773
+
2774
+ ## 0.10.3
2775
+
2776
+ ### Patch Changes
2777
+
2778
+ - e2fabee: Add AI-friendly deployment diagnostics, align generated schemas with first-run admin features, and harden setup guidance for design-first deployments.
2779
+
2780
+ ## 0.10.2
2781
+
2782
+ ### Patch Changes
2783
+
2784
+ - b66800c: Align admin-facing API response shapes with cms-admin consumers, add Vercel Blob image configuration, and harden admin views against partial data.
2785
+
2786
+ ## 0.10.1
2787
+
2788
+ ### Patch Changes
2789
+
2790
+ - b224105: Fix post-release developer experience regressions in public site globals, scaffolded Prisma wiring, and seed/populate database initialization.
2791
+
2792
+ ## 0.10.0
2793
+
2794
+ ### Minor Changes
2795
+
2796
+ - 266b657: Add first-class public site helpers, CLI init/populate workflows, and generated frontend boilerplate for globals, document resolution, and media normalization.
2797
+
2798
+ ## 0.9.0
2799
+
2800
+ ### Minor Changes
2801
+
2802
+ - c3c55c7: Add AI-powered page generation pipeline with prompt-to-page workflow.
2803
+
2804
+ **cms-core**: AI pipeline types (GenerationContext, GenerationStep, StepResult, GenerationResult), prompt template builders for structure/content/SEO/accessibility steps, deterministic accessibility auto-fix engine (auditAccessibility, fixAccessibility), and API endpoints for /page-builder/generate, /page-builder/generate-block, /page-builder/audit-a11y, and /page-builder/fix-a11y.
2805
+
2806
+ **plugin-ai**: Multi-step generatePage() pipeline that runs structure → content → SEO → accessibility steps sequentially via AIProvider, with validation retry, graceful fallback, progress callbacks, and token tracking. Single-block generateBlockContent() for context-aware content generation.
2807
+
2808
+ **cms-admin**: AI Generate Dialog with prompt input, tone selection, step toggles, business context fields, real-time generation progress, and step-by-step review with accept/regenerate flow. AI Block Assist button for one-click content generation per block. AI button integrated into the builder toolbar.
2809
+
2810
+ - 2ef3505: Add SEO analysis and design scoring for the page builder. cms-core gains `analyzeSEO()` (content extraction, readability, 15+ SEO checks, per-block hints), `analyzeDesign()` (6-category scoring for visual hierarchy, spacing consistency, content density, color usage, image quality, and mobile readiness), and semantic block intelligence with smart suggestions. cms-admin replaces the placeholder SEO and Design tabs in the page builder ContextPanel with fully functional `BuilderSEOPanel` (score ring, checks, readability grid, SERP/social previews, inline editing) and `DesignScorePanel` (score ring, category progress bars, expandable checks, severity-ranked suggestions).
2811
+ - 2269f10: Add page templates and saved sections for the page builder. cms-core gains 6 built-in page templates (blank, landing, about, contact, location, blog-post) with full container tree structures, plus CRUD API endpoints for both page templates and saved sections with tree validation, auto-seeding, and audit logging. cms-admin adds a TemplatePicker modal for new page creation, a SavedSections CRUD management view with category filtering, and admin routing for the new views.
2812
+
2813
+ ## 0.8.0
2814
+
2815
+ ### Minor Changes
2816
+
2817
+ - e19430e: Add page builder foundation: container tree types, Zod validation schema, tree manipulation utilities, unified block catalog with 10 core types and named variants, and Prisma models for page templates and saved sections. New `pageBuilder` config option on `ActuateCMSConfig`. New `./page-builder` export path.
2818
+
2819
+ ## 0.7.0
2820
+
2821
+ ### Minor Changes
2822
+
2823
+ - b5de8dd: Add password reset flow for admin login. Users click "Forgot Password?" on the login page, enter their email, and receive a time-limited reset link via the configured email adapter (Resend). The reset token is hashed before storage and expires after 1 hour. On successful reset, all existing sessions are revoked.
2824
+ - New PasswordResetToken Prisma model with migration 0005_password_reset_tokens.
2825
+ - POST /auth/forgot-password and POST /auth/reset-password API endpoints with rate limiting.
2826
+ - Core auth module (reset.ts) with token generation, email dispatch, and password update logic.
2827
+ - ForgotPassword and ResetPassword admin views with full form validation.
2828
+ - AdminRoot routes /forgot-password and /reset-password in the unauthenticated shell.
2829
+
2830
+ ## 0.6.0
2831
+
2832
+ ### Minor Changes
2833
+
2834
+ - 54c7a3b: Add Script Tags management system for injecting tracking codes, analytics, and custom scripts into public site pages. Admins create tags with HTML/JS code, choose placement (head, body open, body close), set scope (entire site, parent pages + descendants, or exact URLs), and assign priority order. Includes new ScriptTag database model, CRUD + public resolve API endpoints, admin list and editor views, and updated scaffolder site layout with automatic tag rendering.
2835
+
2836
+ ## 0.5.0
2837
+
2838
+ ### Minor Changes
2839
+
2840
+ - c08cf8b: Add layout variants system for customizable per-page headers, footers, and other layout regions. Layout regions are defined in config, stored as regular collection documents, and assigned to pages with automatic parent-to-child URL path inheritance. The /resolve endpoint returns resolved layout variants alongside page data. Admin UI includes layout assignment on document edit, collection grouping in sidebar, and default layout settings.
2841
+
2842
+ ### Patch Changes
2843
+
2844
+ - 80aa939: Add scaffold site rendering layer with catch-all route, block components, layout components, and critical config wiring fixes.
2845
+ - Fix API route template to pass config to handleActuateAPI (enables /resolve and layout regions)
2846
+ - Fix admin page template to pass real config to AdminRoot (enables layout sidebar and blocks field)
2847
+ - Fix /resolve endpoint to handle root path (/) by looking up "home" or "index" slug
2848
+ - Add blocks-first page fields, header/footer collections, and layout config to scaffold (conditional on blocks plugin)
2849
+ - Generate catch-all page route with dynamic SEO metadata and first-boot welcome page
2850
+ - Generate BlockRenderer, HeroBlock, CTABlock, TextBlock, ImageTextBlock components
2851
+ - Generate SiteHeader and SiteFooter layout components with graceful fallbacks
2852
+
2853
+ ## 0.4.0
2854
+
2855
+ ### Minor Changes
2856
+
2857
+ - f2cf7d7: Redesign Dashboard and Pages views: extended /stats API with statusCounts, formCount, avgSeoScore, and author names; new /seo/summary endpoint; recharts donut chart for content overview; SEO performance panel; folder color badges and SEO score column in Pages view.
2858
+
2859
+ ## 0.3.1
2860
+
2861
+ ### Patch Changes
2862
+
2863
+ - 0621037: Make Dashboard collection-aware instead of hardcoding Posts/Pages labels.
2864
+
2865
+ **cms-core:**
2866
+ - `/stats` API now returns `collectionCounts` (per-collection document counts via groupBy)
2867
+ - Recent documents include `collection` field in response
2868
+
2869
+ **cms-admin:**
2870
+ - Dashboard reads `config.collections` to derive stat card labels, counts, routes, and quick actions
2871
+ - "Edit" links use `/{doc.collection}/{doc.id}` instead of hardcoded `/posts/{id}`
2872
+ - "New" quick actions are generated from configured collections
2873
+ - Falls back to generic "Documents" / "Pages" labels when no config is provided
2874
+
2875
+ ## 0.3.0
2876
+
2877
+ ### Minor Changes
2878
+
2879
+ - 4e3a5eb: RC2-RC4 and hardening release.
2880
+
2881
+ **cms-core:**
2882
+ - Replace isomorphic-dompurify with sanitize-html (eliminates Vercel ESM crash)
2883
+ - Remove cms-core from serverExternalPackages, move to transpilePackages
2884
+ - Add GET /api/cms/health endpoint with model probing and diagnostics
2885
+ - Harden db() Proxy with try/catch on property access and rawDb()
2886
+ - Harden safeCount/safeFindMany against Proxy throws
2887
+ - Add startup validation for missing CMS_SECRET (503 instead of 500)
2888
+ - Bypass auth on /stats when secret is missing (returns zero-data)
2889
+ - Loosen NextConfigLike types (experimental/images/webpack as unknown)
2890
+ - Implement login anomaly detection (new IP, brute force, unusual hours)
2891
+ - Implement webhook DNS resolution with private range checking
2892
+ - Implement TOTP reauth verification
2893
+ - Add Zod validation for secret in defineConfig (min 32 chars)
2894
+ - Read CMS_CORE_VERSION from package.json at runtime instead of hardcoding '0.1.0'
2895
+
2896
+ **cms-admin:**
2897
+ - Ship pre-compiled CSS (dist/actuate-admin.css) with all 48 responsive classes
2898
+ - New export: @actuate-media/cms-admin/styles/precompiled.css
2899
+ - Add CSS containment (`contain: layout style`) on .actuate-admin
2900
+ - Fix ShortcutHelp modal using React state instead of DOM manipulation
2901
+ - Add fixed-position layout isolation wrapper to AdminRoot
2902
+ - Add useApiData retry limits (maxRetries=3) with exponential backoff
2903
+ - Dashboard shows health-aware "Database Setup Required" banner
2904
+
2905
+ **cli:**
2906
+ - Add `actuate db:init` command (injects CMS models into Prisma schema)
2907
+ - Add `actuate db:status` command (checks model presence and DB connectivity)
2908
+
2909
+ **create-actuate-cms:**
2910
+ - Embed full CMS schema (9 models + enum) in generated schema.prisma
2911
+ - Add @actuate-media/cli to scaffolded devDependencies
2912
+ - Use pre-compiled CSS import in scaffolded admin layout
2913
+
2914
+ ## 0.2.3
2915
+
2916
+ ### Patch Changes
2917
+
2918
+ - Fix /stats endpoint: use Promise.allSettled so missing Prisma models return 0 instead of 500. Dashboard now shows a soft warning instead of a red error banner when stats are unavailable. Add !important to font-family on .actuate-admin to prevent consumer site fonts from cascading into the admin panel.
2919
+
2920
+ ## 0.2.2
2921
+
2922
+ ### Patch Changes
2923
+
2924
+ - Loosen withActuateCMS types: accept minimal config objects without requiring full CollectionDefinition (labels, fields). Replace Partial<ActuateCMSConfig> with WithActuateCMSOptions and NextConfigWithActuate with NextConfigLike that aligns with Next.js's NextConfig shape. Consumers no longer need `as unknown` casts.
2925
+
2926
+ ## 0.2.1
2927
+
2928
+ ### Patch Changes
2929
+
2930
+ - Fix Turbopack conflict: remove cms-core from transpilePackages since it's already in serverExternalPackages. Turbopack forbids a package in both lists. Also add a safety filter so any overlap between the two lists is resolved in favor of serverExternalPackages.
2931
+
2932
+ ## 0.2.0
2933
+
2934
+ ### Minor Changes
2935
+
2936
+ - 682dac7: Fix consumer integration issues discovered during MaidPro.co deployment
2937
+ - **cms-core**: API handlers gracefully degrade when CMS Prisma models are missing (returns 501 instead of 500)
2938
+ - **cms-core**: Accept CMS secret via `actuate.config.ts` (`secret` field) in addition to `CMS_SECRET` env var
2939
+ - **cms-core**: `withActuateCMS` now accepts `Partial<ActuateCMSConfig>` (no more forced type casts)
2940
+ - **cms-core**: `withActuateCMS` auto-injects all `@actuate-media/*` packages into `transpilePackages`
2941
+ - **cms-core**: Expanded Prisma schema fragment with all CMS models for consumer merge
2942
+ - **cms-admin**: Removed duplicate Tailwind import from admin styles (consumers already load Tailwind)
2943
+ - **platform-vercel**: Email adapter accepts both `defaultFrom` and `from` for sender address
2944
+ - **plugin-email**: Email plugin accepts both `defaultFrom` and `from` for consistency
2945
+
2946
+ ### Patch Changes
2947
+
2948
+ - 3fd38a2: Fix all 13 integration issues discovered during MaidPro.co deployment
2949
+
2950
+ **cms-core:**
2951
+ - Add .js extensions to all ESM relative imports (58 files)
2952
+ - Replace workspace:\* with versioned deps in all packages
2953
+ - Add sideEffects:false for proper tree-shaking
2954
+ - Subpath exports (/config, /api, /middleware, /next) avoid barrel side effects
2955
+ - Stats/search routes use safeCount/safeFindMany for missing Prisma models
2956
+ - Ship prisma/cms-schema.prisma with all required CMS models
2957
+ - Accept both CMS_SECRET and CMS_SESSION_SECRET with clear error messages
2958
+ - Fix hardcoded /admin paths in OAuth flow (now uses getAdminPath())
2959
+ - Loosen withActuateCMS and createCMSMiddleware parameter types
2960
+ - Auto-inject transpilePackages and serverExternalPackages in withActuateCMS
2961
+ - Unify PluginDefinition with ActuatePlugin interface
2962
+
2963
+ **cms-admin:**
2964
+ - Scope all 40+ CSS theme variables to .actuate-admin instead of :root
2965
+ - ThemeProvider applies dark class to .actuate-admin, not document.documentElement
2966
+ - Scaffold uses (admin)/(site) route groups for full CSS/script isolation
2967
+ - Sidebar branding supports custom logo via config.admin.branding
2968
+
2969
+ **platform-vercel:**
2970
+ - Email adapter accepts both `from` and `defaultFrom`
2971
+
2972
+ **All plugin/platform packages:**
2973
+ - Fix ESM .js extensions in compiled output
2974
+ - Replace workspace:\* with ^0.1.0 for publishable deps