@growth-labs/cms 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1093) hide show
  1. package/README.md +165 -0
  2. package/dist/engine/activity-log.d.ts +17 -0
  3. package/dist/engine/activity-log.d.ts.map +1 -0
  4. package/dist/engine/activity-log.js +17 -0
  5. package/dist/engine/activity-log.js.map +1 -0
  6. package/dist/engine/ai-prompts.d.ts +57 -0
  7. package/dist/engine/ai-prompts.d.ts.map +1 -0
  8. package/dist/engine/ai-prompts.js +90 -0
  9. package/dist/engine/ai-prompts.js.map +1 -0
  10. package/dist/engine/ai-writeback.d.ts +36 -0
  11. package/dist/engine/ai-writeback.d.ts.map +1 -0
  12. package/dist/engine/ai-writeback.js +45 -0
  13. package/dist/engine/ai-writeback.js.map +1 -0
  14. package/dist/engine/api-keys.d.ts +76 -0
  15. package/dist/engine/api-keys.d.ts.map +1 -0
  16. package/dist/engine/api-keys.js +165 -0
  17. package/dist/engine/api-keys.js.map +1 -0
  18. package/dist/engine/content-insights.d.ts +36 -0
  19. package/dist/engine/content-insights.d.ts.map +1 -0
  20. package/dist/engine/content-insights.js +114 -0
  21. package/dist/engine/content-insights.js.map +1 -0
  22. package/dist/engine/contributors.d.ts +25 -0
  23. package/dist/engine/contributors.d.ts.map +1 -0
  24. package/dist/engine/contributors.js +59 -0
  25. package/dist/engine/contributors.js.map +1 -0
  26. package/dist/engine/cron.d.ts +15 -0
  27. package/dist/engine/cron.d.ts.map +1 -0
  28. package/dist/engine/cron.js +33 -0
  29. package/dist/engine/cron.js.map +1 -0
  30. package/dist/engine/d1.d.ts +16 -0
  31. package/dist/engine/d1.d.ts.map +1 -0
  32. package/dist/engine/d1.js +13 -0
  33. package/dist/engine/d1.js.map +1 -0
  34. package/dist/engine/foundry-dispatch.d.ts +52 -0
  35. package/dist/engine/foundry-dispatch.d.ts.map +1 -0
  36. package/dist/engine/foundry-dispatch.js +290 -0
  37. package/dist/engine/foundry-dispatch.js.map +1 -0
  38. package/dist/engine/import-parsers.d.ts +11 -0
  39. package/dist/engine/import-parsers.d.ts.map +1 -0
  40. package/dist/engine/import-parsers.js +373 -0
  41. package/dist/engine/import-parsers.js.map +1 -0
  42. package/dist/engine/index.d.ts +28 -0
  43. package/dist/engine/index.d.ts.map +1 -0
  44. package/dist/engine/index.js +57 -0
  45. package/dist/engine/index.js.map +1 -0
  46. package/dist/engine/invites.d.ts +78 -0
  47. package/dist/engine/invites.d.ts.map +1 -0
  48. package/dist/engine/invites.js +158 -0
  49. package/dist/engine/invites.js.map +1 -0
  50. package/dist/engine/members.d.ts +59 -0
  51. package/dist/engine/members.d.ts.map +1 -0
  52. package/dist/engine/members.js +124 -0
  53. package/dist/engine/members.js.map +1 -0
  54. package/dist/engine/membership-rules.d.ts +25 -0
  55. package/dist/engine/membership-rules.d.ts.map +1 -0
  56. package/dist/engine/membership-rules.js +44 -0
  57. package/dist/engine/membership-rules.js.map +1 -0
  58. package/dist/engine/og-render.d.ts +40 -0
  59. package/dist/engine/og-render.d.ts.map +1 -0
  60. package/dist/engine/og-render.js +26 -0
  61. package/dist/engine/og-render.js.map +1 -0
  62. package/dist/engine/publish-guard.d.ts +58 -0
  63. package/dist/engine/publish-guard.d.ts.map +1 -0
  64. package/dist/engine/publish-guard.js +80 -0
  65. package/dist/engine/publish-guard.js.map +1 -0
  66. package/dist/engine/publisher.d.ts +171 -0
  67. package/dist/engine/publisher.d.ts.map +1 -0
  68. package/dist/engine/publisher.js +597 -0
  69. package/dist/engine/publisher.js.map +1 -0
  70. package/dist/engine/revisions.d.ts +39 -0
  71. package/dist/engine/revisions.d.ts.map +1 -0
  72. package/dist/engine/revisions.js +203 -0
  73. package/dist/engine/revisions.js.map +1 -0
  74. package/dist/engine/sanitize.d.ts +52 -0
  75. package/dist/engine/sanitize.d.ts.map +1 -0
  76. package/dist/engine/sanitize.js +155 -0
  77. package/dist/engine/sanitize.js.map +1 -0
  78. package/dist/engine/seed-membership.d.ts +29 -0
  79. package/dist/engine/seed-membership.d.ts.map +1 -0
  80. package/dist/engine/seed-membership.js +65 -0
  81. package/dist/engine/seed-membership.js.map +1 -0
  82. package/dist/engine/seo.d.ts +20 -0
  83. package/dist/engine/seo.d.ts.map +1 -0
  84. package/dist/engine/seo.js +50 -0
  85. package/dist/engine/seo.js.map +1 -0
  86. package/dist/engine/slug-redirects.d.ts +8 -0
  87. package/dist/engine/slug-redirects.d.ts.map +1 -0
  88. package/dist/engine/slug-redirects.js +26 -0
  89. package/dist/engine/slug-redirects.js.map +1 -0
  90. package/dist/engine/slug.d.ts +6 -0
  91. package/dist/engine/slug.d.ts.map +1 -0
  92. package/dist/engine/slug.js +28 -0
  93. package/dist/engine/slug.js.map +1 -0
  94. package/dist/engine/soft-delete.d.ts +8 -0
  95. package/dist/engine/soft-delete.d.ts.map +1 -0
  96. package/dist/engine/soft-delete.js +28 -0
  97. package/dist/engine/soft-delete.js.map +1 -0
  98. package/dist/engine/tags.d.ts +14 -0
  99. package/dist/engine/tags.d.ts.map +1 -0
  100. package/dist/engine/tags.js +79 -0
  101. package/dist/engine/tags.js.map +1 -0
  102. package/dist/engine/topics.d.ts +10 -0
  103. package/dist/engine/topics.d.ts.map +1 -0
  104. package/dist/engine/topics.js +140 -0
  105. package/dist/engine/topics.js.map +1 -0
  106. package/dist/engine/url-guard.d.ts +12 -0
  107. package/dist/engine/url-guard.d.ts.map +1 -0
  108. package/dist/engine/url-guard.js +129 -0
  109. package/dist/engine/url-guard.js.map +1 -0
  110. package/dist/engine/validator/checks/bare-url-not-autolinked.d.ts +20 -0
  111. package/dist/engine/validator/checks/bare-url-not-autolinked.d.ts.map +1 -0
  112. package/dist/engine/validator/checks/bare-url-not-autolinked.js +54 -0
  113. package/dist/engine/validator/checks/bare-url-not-autolinked.js.map +1 -0
  114. package/dist/engine/validator/checks/broken-footnote-label.d.ts +16 -0
  115. package/dist/engine/validator/checks/broken-footnote-label.d.ts.map +1 -0
  116. package/dist/engine/validator/checks/broken-footnote-label.js +17 -0
  117. package/dist/engine/validator/checks/broken-footnote-label.js.map +1 -0
  118. package/dist/engine/validator/checks/double-encoded-entities.d.ts +18 -0
  119. package/dist/engine/validator/checks/double-encoded-entities.d.ts.map +1 -0
  120. package/dist/engine/validator/checks/double-encoded-entities.js +23 -0
  121. package/dist/engine/validator/checks/double-encoded-entities.js.map +1 -0
  122. package/dist/engine/validator/checks/empty-alt-text.d.ts +14 -0
  123. package/dist/engine/validator/checks/empty-alt-text.d.ts.map +1 -0
  124. package/dist/engine/validator/checks/empty-alt-text.js +23 -0
  125. package/dist/engine/validator/checks/empty-alt-text.js.map +1 -0
  126. package/dist/engine/validator/checks/heading-hierarchy-skip.d.ts +11 -0
  127. package/dist/engine/validator/checks/heading-hierarchy-skip.d.ts.map +1 -0
  128. package/dist/engine/validator/checks/heading-hierarchy-skip.js +20 -0
  129. package/dist/engine/validator/checks/heading-hierarchy-skip.js.map +1 -0
  130. package/dist/engine/validator/checks/html-comment-leak.d.ts +20 -0
  131. package/dist/engine/validator/checks/html-comment-leak.d.ts.map +1 -0
  132. package/dist/engine/validator/checks/html-comment-leak.js +30 -0
  133. package/dist/engine/validator/checks/html-comment-leak.js.map +1 -0
  134. package/dist/engine/validator/checks/iframe-missing-dims-and-wrapper.d.ts +12 -0
  135. package/dist/engine/validator/checks/iframe-missing-dims-and-wrapper.d.ts.map +1 -0
  136. package/dist/engine/validator/checks/iframe-missing-dims-and-wrapper.js +17 -0
  137. package/dist/engine/validator/checks/iframe-missing-dims-and-wrapper.js.map +1 -0
  138. package/dist/engine/validator/checks/invisible-control-chars.d.ts +24 -0
  139. package/dist/engine/validator/checks/invisible-control-chars.d.ts.map +1 -0
  140. package/dist/engine/validator/checks/invisible-control-chars.js +30 -0
  141. package/dist/engine/validator/checks/invisible-control-chars.js.map +1 -0
  142. package/dist/engine/validator/checks/paywall-marker-leak.d.ts +17 -0
  143. package/dist/engine/validator/checks/paywall-marker-leak.d.ts.map +1 -0
  144. package/dist/engine/validator/checks/paywall-marker-leak.js +22 -0
  145. package/dist/engine/validator/checks/paywall-marker-leak.js.map +1 -0
  146. package/dist/engine/validator/checks/raw-block-html.d.ts +28 -0
  147. package/dist/engine/validator/checks/raw-block-html.d.ts.map +1 -0
  148. package/dist/engine/validator/checks/raw-block-html.js +38 -0
  149. package/dist/engine/validator/checks/raw-block-html.js.map +1 -0
  150. package/dist/engine/validator/checks/stale-body-html.d.ts +28 -0
  151. package/dist/engine/validator/checks/stale-body-html.d.ts.map +1 -0
  152. package/dist/engine/validator/checks/stale-body-html.js +15 -0
  153. package/dist/engine/validator/checks/stale-body-html.js.map +1 -0
  154. package/dist/engine/validator/checks/unresolved-footnote-anchor.d.ts +11 -0
  155. package/dist/engine/validator/checks/unresolved-footnote-anchor.d.ts.map +1 -0
  156. package/dist/engine/validator/checks/unresolved-footnote-anchor.js +48 -0
  157. package/dist/engine/validator/checks/unresolved-footnote-anchor.js.map +1 -0
  158. package/dist/engine/validator/checks/word-gdocs-paste-artifacts.d.ts +23 -0
  159. package/dist/engine/validator/checks/word-gdocs-paste-artifacts.d.ts.map +1 -0
  160. package/dist/engine/validator/checks/word-gdocs-paste-artifacts.js +47 -0
  161. package/dist/engine/validator/checks/word-gdocs-paste-artifacts.js.map +1 -0
  162. package/dist/engine/validator/index.d.ts +75 -0
  163. package/dist/engine/validator/index.d.ts.map +1 -0
  164. package/dist/engine/validator/index.js +313 -0
  165. package/dist/engine/validator/index.js.map +1 -0
  166. package/dist/engine/validator/scan.d.ts +28 -0
  167. package/dist/engine/validator/scan.d.ts.map +1 -0
  168. package/dist/engine/validator/scan.js +97 -0
  169. package/dist/engine/validator/scan.js.map +1 -0
  170. package/dist/engine/validator/types.d.ts +50 -0
  171. package/dist/engine/validator/types.d.ts.map +1 -0
  172. package/dist/engine/validator/types.js +51 -0
  173. package/dist/engine/validator/types.js.map +1 -0
  174. package/dist/engine/webhook-signer.d.ts +39 -0
  175. package/dist/engine/webhook-signer.d.ts.map +1 -0
  176. package/dist/engine/webhook-signer.js +117 -0
  177. package/dist/engine/webhook-signer.js.map +1 -0
  178. package/dist/engine/webhooks.d.ts +75 -0
  179. package/dist/engine/webhooks.d.ts.map +1 -0
  180. package/dist/engine/webhooks.js +139 -0
  181. package/dist/engine/webhooks.js.map +1 -0
  182. package/dist/index.d.ts +5 -0
  183. package/dist/index.d.ts.map +1 -0
  184. package/dist/index.js +40 -0
  185. package/dist/index.js.map +1 -0
  186. package/dist/integration/index.d.ts +6 -0
  187. package/dist/integration/index.d.ts.map +1 -0
  188. package/dist/integration/index.js +294 -0
  189. package/dist/integration/index.js.map +1 -0
  190. package/dist/integration/options.d.ts +105 -0
  191. package/dist/integration/options.d.ts.map +1 -0
  192. package/dist/integration/options.js +25 -0
  193. package/dist/integration/options.js.map +1 -0
  194. package/dist/integration/vite-plugin.d.ts +4 -0
  195. package/dist/integration/vite-plugin.d.ts.map +1 -0
  196. package/dist/integration/vite-plugin.js +37 -0
  197. package/dist/integration/vite-plugin.js.map +1 -0
  198. package/dist/providers/index.d.ts +3 -0
  199. package/dist/providers/index.d.ts.map +1 -0
  200. package/dist/providers/index.js +3 -0
  201. package/dist/providers/index.js.map +1 -0
  202. package/dist/providers/null.d.ts +9 -0
  203. package/dist/providers/null.d.ts.map +1 -0
  204. package/dist/providers/null.js +144 -0
  205. package/dist/providers/null.js.map +1 -0
  206. package/dist/providers/types.d.ts +277 -0
  207. package/dist/providers/types.d.ts.map +1 -0
  208. package/dist/providers/types.js +2 -0
  209. package/dist/providers/types.js.map +1 -0
  210. package/dist/routes/ai.d.ts +25 -0
  211. package/dist/routes/ai.d.ts.map +1 -0
  212. package/dist/routes/ai.js +381 -0
  213. package/dist/routes/ai.js.map +1 -0
  214. package/dist/routes/analytics.d.ts +15 -0
  215. package/dist/routes/analytics.d.ts.map +1 -0
  216. package/dist/routes/analytics.js +61 -0
  217. package/dist/routes/analytics.js.map +1 -0
  218. package/dist/routes/api-keys.d.ts +13 -0
  219. package/dist/routes/api-keys.d.ts.map +1 -0
  220. package/dist/routes/api-keys.js +109 -0
  221. package/dist/routes/api-keys.js.map +1 -0
  222. package/dist/routes/authors.d.ts +19 -0
  223. package/dist/routes/authors.d.ts.map +1 -0
  224. package/dist/routes/authors.js +202 -0
  225. package/dist/routes/authors.js.map +1 -0
  226. package/dist/routes/authz-matrix.d.ts +78 -0
  227. package/dist/routes/authz-matrix.d.ts.map +1 -0
  228. package/dist/routes/authz-matrix.js +170 -0
  229. package/dist/routes/authz-matrix.js.map +1 -0
  230. package/dist/routes/calendar.d.ts +19 -0
  231. package/dist/routes/calendar.d.ts.map +1 -0
  232. package/dist/routes/calendar.js +89 -0
  233. package/dist/routes/calendar.js.map +1 -0
  234. package/dist/routes/config.d.ts +70 -0
  235. package/dist/routes/config.d.ts.map +1 -0
  236. package/dist/routes/config.js +23 -0
  237. package/dist/routes/config.js.map +1 -0
  238. package/dist/routes/content-insights.d.ts +18 -0
  239. package/dist/routes/content-insights.d.ts.map +1 -0
  240. package/dist/routes/content-insights.js +137 -0
  241. package/dist/routes/content-insights.js.map +1 -0
  242. package/dist/routes/content.d.ts +145 -0
  243. package/dist/routes/content.d.ts.map +1 -0
  244. package/dist/routes/content.js +1374 -0
  245. package/dist/routes/content.js.map +1 -0
  246. package/dist/routes/context.d.ts +104 -0
  247. package/dist/routes/context.d.ts.map +1 -0
  248. package/dist/routes/context.js +26 -0
  249. package/dist/routes/context.js.map +1 -0
  250. package/dist/routes/cron.d.ts +8 -0
  251. package/dist/routes/cron.d.ts.map +1 -0
  252. package/dist/routes/cron.js +20 -0
  253. package/dist/routes/cron.js.map +1 -0
  254. package/dist/routes/dashboard.d.ts +12 -0
  255. package/dist/routes/dashboard.d.ts.map +1 -0
  256. package/dist/routes/dashboard.js +113 -0
  257. package/dist/routes/dashboard.js.map +1 -0
  258. package/dist/routes/imports.d.ts +10 -0
  259. package/dist/routes/imports.d.ts.map +1 -0
  260. package/dist/routes/imports.js +149 -0
  261. package/dist/routes/imports.js.map +1 -0
  262. package/dist/routes/index.d.ts +75 -0
  263. package/dist/routes/index.d.ts.map +1 -0
  264. package/dist/routes/index.js +141 -0
  265. package/dist/routes/index.js.map +1 -0
  266. package/dist/routes/media-lib.d.ts +75 -0
  267. package/dist/routes/media-lib.d.ts.map +1 -0
  268. package/dist/routes/media-lib.js +305 -0
  269. package/dist/routes/media-lib.js.map +1 -0
  270. package/dist/routes/media.d.ts +32 -0
  271. package/dist/routes/media.d.ts.map +1 -0
  272. package/dist/routes/media.js +756 -0
  273. package/dist/routes/media.js.map +1 -0
  274. package/dist/routes/preview.d.ts +19 -0
  275. package/dist/routes/preview.d.ts.map +1 -0
  276. package/dist/routes/preview.js +150 -0
  277. package/dist/routes/preview.js.map +1 -0
  278. package/dist/routes/rbac-invites.d.ts +31 -0
  279. package/dist/routes/rbac-invites.d.ts.map +1 -0
  280. package/dist/routes/rbac-invites.js +174 -0
  281. package/dist/routes/rbac-invites.js.map +1 -0
  282. package/dist/routes/rbac.d.ts +12 -0
  283. package/dist/routes/rbac.d.ts.map +1 -0
  284. package/dist/routes/rbac.js +126 -0
  285. package/dist/routes/rbac.js.map +1 -0
  286. package/dist/routes/shell.d.ts +22 -0
  287. package/dist/routes/shell.d.ts.map +1 -0
  288. package/dist/routes/shell.js +123 -0
  289. package/dist/routes/shell.js.map +1 -0
  290. package/dist/routes/subscriptions.d.ts +21 -0
  291. package/dist/routes/subscriptions.d.ts.map +1 -0
  292. package/dist/routes/subscriptions.js +127 -0
  293. package/dist/routes/subscriptions.js.map +1 -0
  294. package/dist/routes/tags.d.ts +23 -0
  295. package/dist/routes/tags.d.ts.map +1 -0
  296. package/dist/routes/tags.js +68 -0
  297. package/dist/routes/tags.js.map +1 -0
  298. package/dist/routes/topics.d.ts +12 -0
  299. package/dist/routes/topics.d.ts.map +1 -0
  300. package/dist/routes/topics.js +49 -0
  301. package/dist/routes/topics.js.map +1 -0
  302. package/dist/routes/webhooks.d.ts +31 -0
  303. package/dist/routes/webhooks.d.ts.map +1 -0
  304. package/dist/routes/webhooks.js +173 -0
  305. package/dist/routes/webhooks.js.map +1 -0
  306. package/dist/schema/index.d.ts +4 -0
  307. package/dist/schema/index.d.ts.map +1 -0
  308. package/dist/schema/index.js +6 -0
  309. package/dist/schema/index.js.map +1 -0
  310. package/dist/schema/insights-ingest.d.ts +959 -0
  311. package/dist/schema/insights-ingest.d.ts.map +1 -0
  312. package/dist/schema/insights-ingest.js +112 -0
  313. package/dist/schema/insights-ingest.js.map +1 -0
  314. package/dist/schema/migrations.d.ts +63 -0
  315. package/dist/schema/migrations.d.ts.map +1 -0
  316. package/dist/schema/migrations.js +589 -0
  317. package/dist/schema/migrations.js.map +1 -0
  318. package/dist/schema/tables.d.ts +11 -0
  319. package/dist/schema/tables.d.ts.map +1 -0
  320. package/dist/schema/tables.js +56 -0
  321. package/dist/schema/tables.js.map +1 -0
  322. package/dist/schema/types.d.ts +476 -0
  323. package/dist/schema/types.d.ts.map +1 -0
  324. package/dist/schema/types.js +37 -0
  325. package/dist/schema/types.js.map +1 -0
  326. package/dist/ui/api/_authz.d.ts +6 -0
  327. package/dist/ui/api/_authz.d.ts.map +1 -0
  328. package/dist/ui/api/_authz.js +74 -0
  329. package/dist/ui/api/_authz.js.map +1 -0
  330. package/dist/ui/api/_content-config.d.ts +22 -0
  331. package/dist/ui/api/_content-config.d.ts.map +1 -0
  332. package/dist/ui/api/_content-config.js +50 -0
  333. package/dist/ui/api/_content-config.js.map +1 -0
  334. package/dist/ui/api/activity.d.ts +3 -0
  335. package/dist/ui/api/activity.d.ts.map +1 -0
  336. package/dist/ui/api/activity.js +28 -0
  337. package/dist/ui/api/activity.js.map +1 -0
  338. package/dist/ui/api/analytics.d.ts +3 -0
  339. package/dist/ui/api/analytics.d.ts.map +1 -0
  340. package/dist/ui/api/analytics.js +36 -0
  341. package/dist/ui/api/analytics.js.map +1 -0
  342. package/dist/ui/api/authors/[id].d.ts +4 -0
  343. package/dist/ui/api/authors/[id].d.ts.map +1 -0
  344. package/dist/ui/api/authors/[id].js +17 -0
  345. package/dist/ui/api/authors/[id].js.map +1 -0
  346. package/dist/ui/api/authors.d.ts +4 -0
  347. package/dist/ui/api/authors.d.ts.map +1 -0
  348. package/dist/ui/api/authors.js +12 -0
  349. package/dist/ui/api/authors.js.map +1 -0
  350. package/dist/ui/api/calendar.d.ts +3 -0
  351. package/dist/ui/api/calendar.d.ts.map +1 -0
  352. package/dist/ui/api/calendar.js +16 -0
  353. package/dist/ui/api/calendar.js.map +1 -0
  354. package/dist/ui/api/content/[id]/ai/headlines.d.ts +3 -0
  355. package/dist/ui/api/content/[id]/ai/headlines.d.ts.map +1 -0
  356. package/dist/ui/api/content/[id]/ai/headlines.js +7 -0
  357. package/dist/ui/api/content/[id]/ai/headlines.js.map +1 -0
  358. package/dist/ui/api/content/[id]/ai/meta-description.d.ts +3 -0
  359. package/dist/ui/api/content/[id]/ai/meta-description.d.ts.map +1 -0
  360. package/dist/ui/api/content/[id]/ai/meta-description.js +6 -0
  361. package/dist/ui/api/content/[id]/ai/meta-description.js.map +1 -0
  362. package/dist/ui/api/content/[id]/ai/og-image.d.ts +3 -0
  363. package/dist/ui/api/content/[id]/ai/og-image.d.ts.map +1 -0
  364. package/dist/ui/api/content/[id]/ai/og-image.js +7 -0
  365. package/dist/ui/api/content/[id]/ai/og-image.js.map +1 -0
  366. package/dist/ui/api/content/[id]/ai/proofread.d.ts +3 -0
  367. package/dist/ui/api/content/[id]/ai/proofread.d.ts.map +1 -0
  368. package/dist/ui/api/content/[id]/ai/proofread.js +7 -0
  369. package/dist/ui/api/content/[id]/ai/proofread.js.map +1 -0
  370. package/dist/ui/api/content/[id]/ai/takeaways.d.ts +3 -0
  371. package/dist/ui/api/content/[id]/ai/takeaways.d.ts.map +1 -0
  372. package/dist/ui/api/content/[id]/ai/takeaways.js +6 -0
  373. package/dist/ui/api/content/[id]/ai/takeaways.js.map +1 -0
  374. package/dist/ui/api/content/[id]/contributors.d.ts +4 -0
  375. package/dist/ui/api/content/[id]/contributors.d.ts.map +1 -0
  376. package/dist/ui/api/content/[id]/contributors.js +8 -0
  377. package/dist/ui/api/content/[id]/contributors.js.map +1 -0
  378. package/dist/ui/api/content/[id]/preview-token.d.ts +3 -0
  379. package/dist/ui/api/content/[id]/preview-token.d.ts.map +1 -0
  380. package/dist/ui/api/content/[id]/preview-token.js +16 -0
  381. package/dist/ui/api/content/[id]/preview-token.js.map +1 -0
  382. package/dist/ui/api/content/[id]/revisions/[rev]/restore.d.ts +3 -0
  383. package/dist/ui/api/content/[id]/revisions/[rev]/restore.d.ts.map +1 -0
  384. package/dist/ui/api/content/[id]/revisions/[rev]/restore.js +7 -0
  385. package/dist/ui/api/content/[id]/revisions/[rev]/restore.js.map +1 -0
  386. package/dist/ui/api/content/[id]/revisions/[rev].d.ts +3 -0
  387. package/dist/ui/api/content/[id]/revisions/[rev].d.ts.map +1 -0
  388. package/dist/ui/api/content/[id]/revisions/[rev].js +6 -0
  389. package/dist/ui/api/content/[id]/revisions/[rev].js.map +1 -0
  390. package/dist/ui/api/content/[id]/revisions.d.ts +3 -0
  391. package/dist/ui/api/content/[id]/revisions.d.ts.map +1 -0
  392. package/dist/ui/api/content/[id]/revisions.js +6 -0
  393. package/dist/ui/api/content/[id]/revisions.js.map +1 -0
  394. package/dist/ui/api/content/[id]/seo-score.d.ts +3 -0
  395. package/dist/ui/api/content/[id]/seo-score.d.ts.map +1 -0
  396. package/dist/ui/api/content/[id]/seo-score.js +7 -0
  397. package/dist/ui/api/content/[id]/seo-score.js.map +1 -0
  398. package/dist/ui/api/content/[id].d.ts +5 -0
  399. package/dist/ui/api/content/[id].d.ts.map +1 -0
  400. package/dist/ui/api/content/[id].js +17 -0
  401. package/dist/ui/api/content/[id].js.map +1 -0
  402. package/dist/ui/api/content/bulk.d.ts +3 -0
  403. package/dist/ui/api/content/bulk.d.ts.map +1 -0
  404. package/dist/ui/api/content/bulk.js +7 -0
  405. package/dist/ui/api/content/bulk.js.map +1 -0
  406. package/dist/ui/api/content/counts.d.ts +3 -0
  407. package/dist/ui/api/content/counts.d.ts.map +1 -0
  408. package/dist/ui/api/content/counts.js +8 -0
  409. package/dist/ui/api/content/counts.js.map +1 -0
  410. package/dist/ui/api/content/foundry-callback.d.ts +3 -0
  411. package/dist/ui/api/content/foundry-callback.d.ts.map +1 -0
  412. package/dist/ui/api/content/foundry-callback.js +8 -0
  413. package/dist/ui/api/content/foundry-callback.js.map +1 -0
  414. package/dist/ui/api/content/import/confirm.d.ts +3 -0
  415. package/dist/ui/api/content/import/confirm.d.ts.map +1 -0
  416. package/dist/ui/api/content/import/confirm.js +11 -0
  417. package/dist/ui/api/content/import/confirm.js.map +1 -0
  418. package/dist/ui/api/content/import/parse.d.ts +3 -0
  419. package/dist/ui/api/content/import/parse.d.ts.map +1 -0
  420. package/dist/ui/api/content/import/parse.js +11 -0
  421. package/dist/ui/api/content/import/parse.js.map +1 -0
  422. package/dist/ui/api/content/insights-ingest.d.ts +3 -0
  423. package/dist/ui/api/content/insights-ingest.d.ts.map +1 -0
  424. package/dist/ui/api/content/insights-ingest.js +15 -0
  425. package/dist/ui/api/content/insights-ingest.js.map +1 -0
  426. package/dist/ui/api/content-insights/dismiss.d.ts +3 -0
  427. package/dist/ui/api/content-insights/dismiss.d.ts.map +1 -0
  428. package/dist/ui/api/content-insights/dismiss.js +16 -0
  429. package/dist/ui/api/content-insights/dismiss.js.map +1 -0
  430. package/dist/ui/api/content-insights/index.d.ts +3 -0
  431. package/dist/ui/api/content-insights/index.d.ts.map +1 -0
  432. package/dist/ui/api/content-insights/index.js +12 -0
  433. package/dist/ui/api/content-insights/index.js.map +1 -0
  434. package/dist/ui/api/content-insights/undismiss.d.ts +3 -0
  435. package/dist/ui/api/content-insights/undismiss.d.ts.map +1 -0
  436. package/dist/ui/api/content-insights/undismiss.js +16 -0
  437. package/dist/ui/api/content-insights/undismiss.js.map +1 -0
  438. package/dist/ui/api/content.d.ts +4 -0
  439. package/dist/ui/api/content.d.ts.map +1 -0
  440. package/dist/ui/api/content.js +16 -0
  441. package/dist/ui/api/content.js.map +1 -0
  442. package/dist/ui/api/dashboard.d.ts +3 -0
  443. package/dist/ui/api/dashboard.d.ts.map +1 -0
  444. package/dist/ui/api/dashboard.js +22 -0
  445. package/dist/ui/api/dashboard.js.map +1 -0
  446. package/dist/ui/api/me.d.ts +3 -0
  447. package/dist/ui/api/me.d.ts.map +1 -0
  448. package/dist/ui/api/me.js +18 -0
  449. package/dist/ui/api/me.js.map +1 -0
  450. package/dist/ui/api/media/[id].d.ts +3 -0
  451. package/dist/ui/api/media/[id].d.ts.map +1 -0
  452. package/dist/ui/api/media/[id].js +17 -0
  453. package/dist/ui/api/media/[id].js.map +1 -0
  454. package/dist/ui/api/media/images.d.ts +3 -0
  455. package/dist/ui/api/media/images.d.ts.map +1 -0
  456. package/dist/ui/api/media/images.js +6 -0
  457. package/dist/ui/api/media/images.js.map +1 -0
  458. package/dist/ui/api/media/library/[id].d.ts +3 -0
  459. package/dist/ui/api/media/library/[id].d.ts.map +1 -0
  460. package/dist/ui/api/media/library/[id].js +6 -0
  461. package/dist/ui/api/media/library/[id].js.map +1 -0
  462. package/dist/ui/api/media/library.d.ts +3 -0
  463. package/dist/ui/api/media/library.d.ts.map +1 -0
  464. package/dist/ui/api/media/library.js +17 -0
  465. package/dist/ui/api/media/library.js.map +1 -0
  466. package/dist/ui/api/media/podcast/abort.d.ts +3 -0
  467. package/dist/ui/api/media/podcast/abort.d.ts.map +1 -0
  468. package/dist/ui/api/media/podcast/abort.js +4 -0
  469. package/dist/ui/api/media/podcast/abort.js.map +1 -0
  470. package/dist/ui/api/media/podcast/complete.d.ts +3 -0
  471. package/dist/ui/api/media/podcast/complete.d.ts.map +1 -0
  472. package/dist/ui/api/media/podcast/complete.js +4 -0
  473. package/dist/ui/api/media/podcast/complete.js.map +1 -0
  474. package/dist/ui/api/media/podcast/init.d.ts +3 -0
  475. package/dist/ui/api/media/podcast/init.d.ts.map +1 -0
  476. package/dist/ui/api/media/podcast/init.js +4 -0
  477. package/dist/ui/api/media/podcast/init.js.map +1 -0
  478. package/dist/ui/api/media/podcast/part.d.ts +3 -0
  479. package/dist/ui/api/media/podcast/part.d.ts.map +1 -0
  480. package/dist/ui/api/media/podcast/part.js +4 -0
  481. package/dist/ui/api/media/podcast/part.js.map +1 -0
  482. package/dist/ui/api/media/podcast.d.ts +3 -0
  483. package/dist/ui/api/media/podcast.d.ts.map +1 -0
  484. package/dist/ui/api/media/podcast.js +6 -0
  485. package/dist/ui/api/media/podcast.js.map +1 -0
  486. package/dist/ui/api/media/videos/abort.d.ts +3 -0
  487. package/dist/ui/api/media/videos/abort.d.ts.map +1 -0
  488. package/dist/ui/api/media/videos/abort.js +6 -0
  489. package/dist/ui/api/media/videos/abort.js.map +1 -0
  490. package/dist/ui/api/media/videos/complete.d.ts +3 -0
  491. package/dist/ui/api/media/videos/complete.d.ts.map +1 -0
  492. package/dist/ui/api/media/videos/complete.js +6 -0
  493. package/dist/ui/api/media/videos/complete.js.map +1 -0
  494. package/dist/ui/api/media/videos/init.d.ts +3 -0
  495. package/dist/ui/api/media/videos/init.d.ts.map +1 -0
  496. package/dist/ui/api/media/videos/init.js +6 -0
  497. package/dist/ui/api/media/videos/init.js.map +1 -0
  498. package/dist/ui/api/media/videos/part.d.ts +3 -0
  499. package/dist/ui/api/media/videos/part.d.ts.map +1 -0
  500. package/dist/ui/api/media/videos/part.js +6 -0
  501. package/dist/ui/api/media/videos/part.js.map +1 -0
  502. package/dist/ui/api/notifications.d.ts +4 -0
  503. package/dist/ui/api/notifications.d.ts.map +1 -0
  504. package/dist/ui/api/notifications.js +20 -0
  505. package/dist/ui/api/notifications.js.map +1 -0
  506. package/dist/ui/api/search.d.ts +3 -0
  507. package/dist/ui/api/search.d.ts.map +1 -0
  508. package/dist/ui/api/search.js +18 -0
  509. package/dist/ui/api/search.js.map +1 -0
  510. package/dist/ui/api/settings/api-keys/[id].d.ts +3 -0
  511. package/dist/ui/api/settings/api-keys/[id].d.ts.map +1 -0
  512. package/dist/ui/api/settings/api-keys/[id].js +22 -0
  513. package/dist/ui/api/settings/api-keys/[id].js.map +1 -0
  514. package/dist/ui/api/settings/api-keys.d.ts +4 -0
  515. package/dist/ui/api/settings/api-keys.d.ts.map +1 -0
  516. package/dist/ui/api/settings/api-keys.js +19 -0
  517. package/dist/ui/api/settings/api-keys.js.map +1 -0
  518. package/dist/ui/api/settings/domains.d.ts +3 -0
  519. package/dist/ui/api/settings/domains.d.ts.map +1 -0
  520. package/dist/ui/api/settings/domains.js +32 -0
  521. package/dist/ui/api/settings/domains.js.map +1 -0
  522. package/dist/ui/api/settings/integrations.d.ts +3 -0
  523. package/dist/ui/api/settings/integrations.d.ts.map +1 -0
  524. package/dist/ui/api/settings/integrations.js +32 -0
  525. package/dist/ui/api/settings/integrations.js.map +1 -0
  526. package/dist/ui/api/settings/members/[userId].d.ts +4 -0
  527. package/dist/ui/api/settings/members/[userId].d.ts.map +1 -0
  528. package/dist/ui/api/settings/members/[userId].js +26 -0
  529. package/dist/ui/api/settings/members/[userId].js.map +1 -0
  530. package/dist/ui/api/settings/members/invite.d.ts +3 -0
  531. package/dist/ui/api/settings/members/invite.d.ts.map +1 -0
  532. package/dist/ui/api/settings/members/invite.js +21 -0
  533. package/dist/ui/api/settings/members/invite.js.map +1 -0
  534. package/dist/ui/api/settings/members.d.ts +3 -0
  535. package/dist/ui/api/settings/members.d.ts.map +1 -0
  536. package/dist/ui/api/settings/members.js +17 -0
  537. package/dist/ui/api/settings/members.js.map +1 -0
  538. package/dist/ui/api/settings/webhooks/[id]/test.d.ts +3 -0
  539. package/dist/ui/api/settings/webhooks/[id]/test.d.ts.map +1 -0
  540. package/dist/ui/api/settings/webhooks/[id]/test.js +24 -0
  541. package/dist/ui/api/settings/webhooks/[id]/test.js.map +1 -0
  542. package/dist/ui/api/settings/webhooks/[id].d.ts +3 -0
  543. package/dist/ui/api/settings/webhooks/[id].d.ts.map +1 -0
  544. package/dist/ui/api/settings/webhooks/[id].js +23 -0
  545. package/dist/ui/api/settings/webhooks/[id].js.map +1 -0
  546. package/dist/ui/api/settings/webhooks.d.ts +4 -0
  547. package/dist/ui/api/settings/webhooks.d.ts.map +1 -0
  548. package/dist/ui/api/settings/webhooks.js +21 -0
  549. package/dist/ui/api/settings/webhooks.js.map +1 -0
  550. package/dist/ui/api/subscriptions.d.ts +4 -0
  551. package/dist/ui/api/subscriptions.d.ts.map +1 -0
  552. package/dist/ui/api/subscriptions.js +47 -0
  553. package/dist/ui/api/subscriptions.js.map +1 -0
  554. package/dist/ui/api/tags/[id].d.ts +4 -0
  555. package/dist/ui/api/tags/[id].d.ts.map +1 -0
  556. package/dist/ui/api/tags/[id].js +18 -0
  557. package/dist/ui/api/tags/[id].js.map +1 -0
  558. package/dist/ui/api/tags/index.d.ts +4 -0
  559. package/dist/ui/api/tags/index.d.ts.map +1 -0
  560. package/dist/ui/api/tags/index.js +13 -0
  561. package/dist/ui/api/tags/index.js.map +1 -0
  562. package/dist/ui/api/topics/[id].d.ts +3 -0
  563. package/dist/ui/api/topics/[id].d.ts.map +1 -0
  564. package/dist/ui/api/topics/[id].js +15 -0
  565. package/dist/ui/api/topics/[id].js.map +1 -0
  566. package/dist/ui/api/topics/index.d.ts +4 -0
  567. package/dist/ui/api/topics/index.d.ts.map +1 -0
  568. package/dist/ui/api/topics/index.js +12 -0
  569. package/dist/ui/api/topics/index.js.map +1 -0
  570. package/dist/ui/api/workspace-settings.d.ts +4 -0
  571. package/dist/ui/api/workspace-settings.d.ts.map +1 -0
  572. package/dist/ui/api/workspace-settings.js +20 -0
  573. package/dist/ui/api/workspace-settings.js.map +1 -0
  574. package/dist/ui/client/boot-state.d.ts +15 -0
  575. package/dist/ui/client/boot-state.d.ts.map +1 -0
  576. package/dist/ui/client/boot-state.js +36 -0
  577. package/dist/ui/client/boot-state.js.map +1 -0
  578. package/dist/ui/client/mount.d.ts +3 -0
  579. package/dist/ui/client/mount.d.ts.map +1 -0
  580. package/dist/ui/client/mount.js +37 -0
  581. package/dist/ui/client/mount.js.map +1 -0
  582. package/dist/ui/commands.d.ts +23 -0
  583. package/dist/ui/commands.d.ts.map +1 -0
  584. package/dist/ui/commands.js +48 -0
  585. package/dist/ui/commands.js.map +1 -0
  586. package/dist/ui/components/CmsApp.d.ts +16 -0
  587. package/dist/ui/components/CmsApp.d.ts.map +1 -0
  588. package/dist/ui/components/CmsApp.js +74 -0
  589. package/dist/ui/components/CmsApp.js.map +1 -0
  590. package/dist/ui/components/CommandPalette.d.ts +7 -0
  591. package/dist/ui/components/CommandPalette.d.ts.map +1 -0
  592. package/dist/ui/components/CommandPalette.js +61 -0
  593. package/dist/ui/components/CommandPalette.js.map +1 -0
  594. package/dist/ui/components/NoAccessScreen.d.ts +2 -0
  595. package/dist/ui/components/NoAccessScreen.d.ts.map +1 -0
  596. package/dist/ui/components/NoAccessScreen.js +42 -0
  597. package/dist/ui/components/NoAccessScreen.js.map +1 -0
  598. package/dist/ui/components/ShareModal.d.ts +27 -0
  599. package/dist/ui/components/ShareModal.d.ts.map +1 -0
  600. package/dist/ui/components/ShareModal.js +208 -0
  601. package/dist/ui/components/ShareModal.js.map +1 -0
  602. package/dist/ui/components/SharePickers.d.ts +39 -0
  603. package/dist/ui/components/SharePickers.d.ts.map +1 -0
  604. package/dist/ui/components/SharePickers.js +352 -0
  605. package/dist/ui/components/SharePickers.js.map +1 -0
  606. package/dist/ui/components/ShareStatsPanel.d.ts +22 -0
  607. package/dist/ui/components/ShareStatsPanel.d.ts.map +1 -0
  608. package/dist/ui/components/ShareStatsPanel.js +317 -0
  609. package/dist/ui/components/ShareStatsPanel.js.map +1 -0
  610. package/dist/ui/components/Sidebar.d.ts +7 -0
  611. package/dist/ui/components/Sidebar.d.ts.map +1 -0
  612. package/dist/ui/components/Sidebar.js +20 -0
  613. package/dist/ui/components/Sidebar.js.map +1 -0
  614. package/dist/ui/components/SiteSwitcher.d.ts +4 -0
  615. package/dist/ui/components/SiteSwitcher.d.ts.map +1 -0
  616. package/dist/ui/components/SiteSwitcher.js +35 -0
  617. package/dist/ui/components/SiteSwitcher.js.map +1 -0
  618. package/dist/ui/components/Topbar.d.ts +9 -0
  619. package/dist/ui/components/Topbar.d.ts.map +1 -0
  620. package/dist/ui/components/Topbar.js +20 -0
  621. package/dist/ui/components/Topbar.js.map +1 -0
  622. package/dist/ui/editor/AiAssistPanel.d.ts +8 -0
  623. package/dist/ui/editor/AiAssistPanel.d.ts.map +1 -0
  624. package/dist/ui/editor/AiAssistPanel.js +221 -0
  625. package/dist/ui/editor/AiAssistPanel.js.map +1 -0
  626. package/dist/ui/editor/ContentForm.d.ts +46 -0
  627. package/dist/ui/editor/ContentForm.d.ts.map +1 -0
  628. package/dist/ui/editor/ContentForm.js +821 -0
  629. package/dist/ui/editor/ContentForm.js.map +1 -0
  630. package/dist/ui/editor/Rte.d.ts +16 -0
  631. package/dist/ui/editor/Rte.d.ts.map +1 -0
  632. package/dist/ui/editor/Rte.js +272 -0
  633. package/dist/ui/editor/Rte.js.map +1 -0
  634. package/dist/ui/editor/ai-assist.d.ts +43 -0
  635. package/dist/ui/editor/ai-assist.d.ts.map +1 -0
  636. package/dist/ui/editor/ai-assist.js +114 -0
  637. package/dist/ui/editor/ai-assist.js.map +1 -0
  638. package/dist/ui/editor/autosave.d.ts +18 -0
  639. package/dist/ui/editor/autosave.d.ts.map +1 -0
  640. package/dist/ui/editor/autosave.js +23 -0
  641. package/dist/ui/editor/autosave.js.map +1 -0
  642. package/dist/ui/editor/content-payload.d.ts +19 -0
  643. package/dist/ui/editor/content-payload.d.ts.map +1 -0
  644. package/dist/ui/editor/content-payload.js +97 -0
  645. package/dist/ui/editor/content-payload.js.map +1 -0
  646. package/dist/ui/editor/editor-media-upload.d.ts +6 -0
  647. package/dist/ui/editor/editor-media-upload.d.ts.map +1 -0
  648. package/dist/ui/editor/editor-media-upload.js +20 -0
  649. package/dist/ui/editor/editor-media-upload.js.map +1 -0
  650. package/dist/ui/editor/serialize.d.ts +6 -0
  651. package/dist/ui/editor/serialize.d.ts.map +1 -0
  652. package/dist/ui/editor/serialize.js +479 -0
  653. package/dist/ui/editor/serialize.js.map +1 -0
  654. package/dist/ui/editor/tweet-embed.d.ts +4 -0
  655. package/dist/ui/editor/tweet-embed.d.ts.map +1 -0
  656. package/dist/ui/editor/tweet-embed.js +49 -0
  657. package/dist/ui/editor/tweet-embed.js.map +1 -0
  658. package/dist/ui/hash-router.d.ts +5 -0
  659. package/dist/ui/hash-router.d.ts.map +1 -0
  660. package/dist/ui/hash-router.js +25 -0
  661. package/dist/ui/hash-router.js.map +1 -0
  662. package/dist/ui/icons.d.ts +32 -0
  663. package/dist/ui/icons.d.ts.map +1 -0
  664. package/dist/ui/icons.js +86 -0
  665. package/dist/ui/icons.js.map +1 -0
  666. package/dist/ui/inspector/Field.d.ts +12 -0
  667. package/dist/ui/inspector/Field.d.ts.map +1 -0
  668. package/dist/ui/inspector/Field.js +8 -0
  669. package/dist/ui/inspector/Field.js.map +1 -0
  670. package/dist/ui/inspector/FoundryTab.d.ts +9 -0
  671. package/dist/ui/inspector/FoundryTab.d.ts.map +1 -0
  672. package/dist/ui/inspector/FoundryTab.js +362 -0
  673. package/dist/ui/inspector/FoundryTab.js.map +1 -0
  674. package/dist/ui/inspector/HistoryTab.d.ts +7 -0
  675. package/dist/ui/inspector/HistoryTab.d.ts.map +1 -0
  676. package/dist/ui/inspector/HistoryTab.js +289 -0
  677. package/dist/ui/inspector/HistoryTab.js.map +1 -0
  678. package/dist/ui/inspector/Inspector.d.ts +13 -0
  679. package/dist/ui/inspector/Inspector.d.ts.map +1 -0
  680. package/dist/ui/inspector/Inspector.js +163 -0
  681. package/dist/ui/inspector/Inspector.js.map +1 -0
  682. package/dist/ui/inspector/OrganizeTab.d.ts +15 -0
  683. package/dist/ui/inspector/OrganizeTab.d.ts.map +1 -0
  684. package/dist/ui/inspector/OrganizeTab.js +319 -0
  685. package/dist/ui/inspector/OrganizeTab.js.map +1 -0
  686. package/dist/ui/inspector/PublishTab.d.ts +18 -0
  687. package/dist/ui/inspector/PublishTab.d.ts.map +1 -0
  688. package/dist/ui/inspector/PublishTab.js +339 -0
  689. package/dist/ui/inspector/PublishTab.js.map +1 -0
  690. package/dist/ui/inspector/Section.d.ts +10 -0
  691. package/dist/ui/inspector/Section.d.ts.map +1 -0
  692. package/dist/ui/inspector/Section.js +40 -0
  693. package/dist/ui/inspector/Section.js.map +1 -0
  694. package/dist/ui/inspector/SeoTab.d.ts +19 -0
  695. package/dist/ui/inspector/SeoTab.d.ts.map +1 -0
  696. package/dist/ui/inspector/SeoTab.js +328 -0
  697. package/dist/ui/inspector/SeoTab.js.map +1 -0
  698. package/dist/ui/inspector/foundry-stages.d.ts +36 -0
  699. package/dist/ui/inspector/foundry-stages.d.ts.map +1 -0
  700. package/dist/ui/inspector/foundry-stages.js +101 -0
  701. package/dist/ui/inspector/foundry-stages.js.map +1 -0
  702. package/dist/ui/inspector/inspector-data.d.ts +80 -0
  703. package/dist/ui/inspector/inspector-data.d.ts.map +1 -0
  704. package/dist/ui/inspector/inspector-data.js +172 -0
  705. package/dist/ui/inspector/inspector-data.js.map +1 -0
  706. package/dist/ui/inspector/organize-data.d.ts +23 -0
  707. package/dist/ui/inspector/organize-data.d.ts.map +1 -0
  708. package/dist/ui/inspector/organize-data.js +28 -0
  709. package/dist/ui/inspector/organize-data.js.map +1 -0
  710. package/dist/ui/inspector/revision-diff.d.ts +49 -0
  711. package/dist/ui/inspector/revision-diff.d.ts.map +1 -0
  712. package/dist/ui/inspector/revision-diff.js +166 -0
  713. package/dist/ui/inspector/revision-diff.js.map +1 -0
  714. package/dist/ui/inspector/seo-helpers.d.ts +37 -0
  715. package/dist/ui/inspector/seo-helpers.d.ts.map +1 -0
  716. package/dist/ui/inspector/seo-helpers.js +37 -0
  717. package/dist/ui/inspector/seo-helpers.js.map +1 -0
  718. package/dist/ui/inspector/tab-visibility.d.ts +14 -0
  719. package/dist/ui/inspector/tab-visibility.d.ts.map +1 -0
  720. package/dist/ui/inspector/tab-visibility.js +28 -0
  721. package/dist/ui/inspector/tab-visibility.js.map +1 -0
  722. package/dist/ui/nav.d.ts +16 -0
  723. package/dist/ui/nav.d.ts.map +1 -0
  724. package/dist/ui/nav.js +33 -0
  725. package/dist/ui/nav.js.map +1 -0
  726. package/dist/ui/pages/admin.astro +32 -0
  727. package/dist/ui/preview/draft-page.d.ts +37 -0
  728. package/dist/ui/preview/draft-page.d.ts.map +1 -0
  729. package/dist/ui/preview/draft-page.js +212 -0
  730. package/dist/ui/preview/draft-page.js.map +1 -0
  731. package/dist/ui/preview/preview-layout.d.ts +23 -0
  732. package/dist/ui/preview/preview-layout.d.ts.map +1 -0
  733. package/dist/ui/preview/preview-layout.js +30 -0
  734. package/dist/ui/preview/preview-layout.js.map +1 -0
  735. package/dist/ui/screens/AnalyticsScreen.d.ts +2 -0
  736. package/dist/ui/screens/AnalyticsScreen.d.ts.map +1 -0
  737. package/dist/ui/screens/AnalyticsScreen.js +408 -0
  738. package/dist/ui/screens/AnalyticsScreen.js.map +1 -0
  739. package/dist/ui/screens/AuthorsScreen.d.ts +2 -0
  740. package/dist/ui/screens/AuthorsScreen.d.ts.map +1 -0
  741. package/dist/ui/screens/AuthorsScreen.js +225 -0
  742. package/dist/ui/screens/AuthorsScreen.js.map +1 -0
  743. package/dist/ui/screens/CalendarScreen.d.ts +6 -0
  744. package/dist/ui/screens/CalendarScreen.d.ts.map +1 -0
  745. package/dist/ui/screens/CalendarScreen.js +327 -0
  746. package/dist/ui/screens/CalendarScreen.js.map +1 -0
  747. package/dist/ui/screens/ContentInsightsScreen.d.ts +2 -0
  748. package/dist/ui/screens/ContentInsightsScreen.d.ts.map +1 -0
  749. package/dist/ui/screens/ContentInsightsScreen.js +129 -0
  750. package/dist/ui/screens/ContentInsightsScreen.js.map +1 -0
  751. package/dist/ui/screens/ContentRoute.d.ts +2 -0
  752. package/dist/ui/screens/ContentRoute.d.ts.map +1 -0
  753. package/dist/ui/screens/ContentRoute.js +32 -0
  754. package/dist/ui/screens/ContentRoute.js.map +1 -0
  755. package/dist/ui/screens/DashboardScreen.d.ts +6 -0
  756. package/dist/ui/screens/DashboardScreen.d.ts.map +1 -0
  757. package/dist/ui/screens/DashboardScreen.js +273 -0
  758. package/dist/ui/screens/DashboardScreen.js.map +1 -0
  759. package/dist/ui/screens/EditorScreen.d.ts +7 -0
  760. package/dist/ui/screens/EditorScreen.d.ts.map +1 -0
  761. package/dist/ui/screens/EditorScreen.js +426 -0
  762. package/dist/ui/screens/EditorScreen.js.map +1 -0
  763. package/dist/ui/screens/LibraryScreen.d.ts +6 -0
  764. package/dist/ui/screens/LibraryScreen.d.ts.map +1 -0
  765. package/dist/ui/screens/LibraryScreen.js +580 -0
  766. package/dist/ui/screens/LibraryScreen.js.map +1 -0
  767. package/dist/ui/screens/MediaScreen.d.ts +2 -0
  768. package/dist/ui/screens/MediaScreen.d.ts.map +1 -0
  769. package/dist/ui/screens/MediaScreen.js +173 -0
  770. package/dist/ui/screens/MediaScreen.js.map +1 -0
  771. package/dist/ui/screens/SettingsScreen.d.ts +4 -0
  772. package/dist/ui/screens/SettingsScreen.d.ts.map +1 -0
  773. package/dist/ui/screens/SettingsScreen.js +751 -0
  774. package/dist/ui/screens/SettingsScreen.js.map +1 -0
  775. package/dist/ui/screens/SocialShareScreen.d.ts +2 -0
  776. package/dist/ui/screens/SocialShareScreen.d.ts.map +1 -0
  777. package/dist/ui/screens/SocialShareScreen.js +224 -0
  778. package/dist/ui/screens/SocialShareScreen.js.map +1 -0
  779. package/dist/ui/screens/SubscriptionsScreen.d.ts +2 -0
  780. package/dist/ui/screens/SubscriptionsScreen.d.ts.map +1 -0
  781. package/dist/ui/screens/SubscriptionsScreen.js +441 -0
  782. package/dist/ui/screens/SubscriptionsScreen.js.map +1 -0
  783. package/dist/ui/screens/TopicsScreen.d.ts +2 -0
  784. package/dist/ui/screens/TopicsScreen.d.ts.map +1 -0
  785. package/dist/ui/screens/TopicsScreen.js +360 -0
  786. package/dist/ui/screens/TopicsScreen.js.map +1 -0
  787. package/dist/ui/screens/analytics-data.d.ts +19 -0
  788. package/dist/ui/screens/analytics-data.d.ts.map +1 -0
  789. package/dist/ui/screens/analytics-data.js +42 -0
  790. package/dist/ui/screens/analytics-data.js.map +1 -0
  791. package/dist/ui/screens/calendar-data.d.ts +45 -0
  792. package/dist/ui/screens/calendar-data.d.ts.map +1 -0
  793. package/dist/ui/screens/calendar-data.js +70 -0
  794. package/dist/ui/screens/calendar-data.js.map +1 -0
  795. package/dist/ui/screens/content-insights-data.d.ts +54 -0
  796. package/dist/ui/screens/content-insights-data.d.ts.map +1 -0
  797. package/dist/ui/screens/content-insights-data.js +82 -0
  798. package/dist/ui/screens/content-insights-data.js.map +1 -0
  799. package/dist/ui/screens/content-view.d.ts +21 -0
  800. package/dist/ui/screens/content-view.d.ts.map +1 -0
  801. package/dist/ui/screens/content-view.js +17 -0
  802. package/dist/ui/screens/content-view.js.map +1 -0
  803. package/dist/ui/screens/library-data.d.ts +76 -0
  804. package/dist/ui/screens/library-data.d.ts.map +1 -0
  805. package/dist/ui/screens/library-data.js +116 -0
  806. package/dist/ui/screens/library-data.js.map +1 -0
  807. package/dist/ui/screens/media-upload.d.ts +19 -0
  808. package/dist/ui/screens/media-upload.d.ts.map +1 -0
  809. package/dist/ui/screens/media-upload.js +187 -0
  810. package/dist/ui/screens/media-upload.js.map +1 -0
  811. package/dist/ui/screens/public-url.d.ts +24 -0
  812. package/dist/ui/screens/public-url.d.ts.map +1 -0
  813. package/dist/ui/screens/public-url.js +74 -0
  814. package/dist/ui/screens/public-url.js.map +1 -0
  815. package/dist/ui/screens/registry.d.ts +3 -0
  816. package/dist/ui/screens/registry.d.ts.map +1 -0
  817. package/dist/ui/screens/registry.js +38 -0
  818. package/dist/ui/screens/registry.js.map +1 -0
  819. package/dist/ui/screens/render-state.d.ts +105 -0
  820. package/dist/ui/screens/render-state.d.ts.map +1 -0
  821. package/dist/ui/screens/render-state.js +127 -0
  822. package/dist/ui/screens/render-state.js.map +1 -0
  823. package/dist/ui/screens/settings-data.d.ts +55 -0
  824. package/dist/ui/screens/settings-data.d.ts.map +1 -0
  825. package/dist/ui/screens/settings-data.js +89 -0
  826. package/dist/ui/screens/settings-data.js.map +1 -0
  827. package/dist/ui/screens/settings-panels-data.d.ts +58 -0
  828. package/dist/ui/screens/settings-panels-data.d.ts.map +1 -0
  829. package/dist/ui/screens/settings-panels-data.js +88 -0
  830. package/dist/ui/screens/settings-panels-data.js.map +1 -0
  831. package/dist/ui/screens/social-share-data.d.ts +307 -0
  832. package/dist/ui/screens/social-share-data.d.ts.map +1 -0
  833. package/dist/ui/screens/social-share-data.js +447 -0
  834. package/dist/ui/screens/social-share-data.js.map +1 -0
  835. package/dist/ui/screens/topics-data.d.ts +38 -0
  836. package/dist/ui/screens/topics-data.d.ts.map +1 -0
  837. package/dist/ui/screens/topics-data.js +50 -0
  838. package/dist/ui/screens/topics-data.js.map +1 -0
  839. package/dist/ui/styles/broadsheet.css +394 -0
  840. package/dist/ui/theme.d.ts +23 -0
  841. package/dist/ui/theme.d.ts.map +1 -0
  842. package/dist/ui/theme.js +87 -0
  843. package/dist/ui/theme.js.map +1 -0
  844. package/dist/ui/tweaks.d.ts +7 -0
  845. package/dist/ui/tweaks.d.ts.map +1 -0
  846. package/dist/ui/tweaks.js +31 -0
  847. package/dist/ui/tweaks.js.map +1 -0
  848. package/dist/ui/use-hash-router.d.ts +6 -0
  849. package/dist/ui/use-hash-router.d.ts.map +1 -0
  850. package/dist/ui/use-hash-router.js +15 -0
  851. package/dist/ui/use-hash-router.js.map +1 -0
  852. package/dist/ui/use-tweaks.d.ts +6 -0
  853. package/dist/ui/use-tweaks.d.ts.map +1 -0
  854. package/dist/ui/use-tweaks.js +24 -0
  855. package/dist/ui/use-tweaks.js.map +1 -0
  856. package/dist/ui/workspace-context.d.ts +34 -0
  857. package/dist/ui/workspace-context.d.ts.map +1 -0
  858. package/dist/ui/workspace-context.js +32 -0
  859. package/dist/ui/workspace-context.js.map +1 -0
  860. package/migrations/0001_create_cms_tables.sql +200 -0
  861. package/migrations/0002_review_softdelete_board.sql +112 -0
  862. package/migrations/0003_content_contributors.sql +16 -0
  863. package/migrations/0004_seo_faq_columns.sql +6 -0
  864. package/migrations/0005_revision_delta_columns.sql +5 -0
  865. package/migrations/0006_processing_trigger_token.sql +4 -0
  866. package/migrations/0007_content_slug_redirects.sql +9 -0
  867. package/migrations/0008_workspace_settings.sql +17 -0
  868. package/migrations/0009_workspace_memberships.sql +36 -0
  869. package/migrations/0010_api_keys_webhooks.sql +21 -0
  870. package/migrations/0011_notifications_activity.sql +22 -0
  871. package/migrations/0012_content_imports.sql +10 -0
  872. package/migrations/0013_media_normalization.sql +4 -0
  873. package/migrations/0014_api_key_prefix.sql +3 -0
  874. package/migrations/0015_cms_topics.sql +7 -0
  875. package/migrations/0016_content_insights.sql +53 -0
  876. package/package.json +82 -0
  877. package/src/engine/activity-log.ts +39 -0
  878. package/src/engine/ai-prompts.ts +124 -0
  879. package/src/engine/ai-writeback.ts +62 -0
  880. package/src/engine/api-keys.ts +239 -0
  881. package/src/engine/content-insights.ts +198 -0
  882. package/src/engine/contributors.ts +95 -0
  883. package/src/engine/cron.ts +62 -0
  884. package/src/engine/d1.ts +29 -0
  885. package/src/engine/foundry-dispatch.ts +417 -0
  886. package/src/engine/import-parsers.ts +478 -0
  887. package/src/engine/index.ts +230 -0
  888. package/src/engine/invites.ts +271 -0
  889. package/src/engine/members.ts +216 -0
  890. package/src/engine/membership-rules.ts +63 -0
  891. package/src/engine/og-render.ts +59 -0
  892. package/src/engine/publish-guard.ts +123 -0
  893. package/src/engine/publisher.ts +1032 -0
  894. package/src/engine/revisions.ts +292 -0
  895. package/src/engine/sanitize.ts +183 -0
  896. package/src/engine/seed-membership.ts +92 -0
  897. package/src/engine/seo.ts +72 -0
  898. package/src/engine/slug-redirects.ts +34 -0
  899. package/src/engine/slug.ts +33 -0
  900. package/src/engine/soft-delete.ts +42 -0
  901. package/src/engine/tags.ts +95 -0
  902. package/src/engine/topics.ts +158 -0
  903. package/src/engine/url-guard.ts +136 -0
  904. package/src/engine/validator/checks/bare-url-not-autolinked.ts +78 -0
  905. package/src/engine/validator/checks/broken-footnote-label.ts +33 -0
  906. package/src/engine/validator/checks/double-encoded-entities.ts +46 -0
  907. package/src/engine/validator/checks/empty-alt-text.ts +35 -0
  908. package/src/engine/validator/checks/heading-hierarchy-skip.ts +33 -0
  909. package/src/engine/validator/checks/html-comment-leak.ts +58 -0
  910. package/src/engine/validator/checks/iframe-missing-dims-and-wrapper.ts +34 -0
  911. package/src/engine/validator/checks/invisible-control-chars.ts +58 -0
  912. package/src/engine/validator/checks/paywall-marker-leak.ts +43 -0
  913. package/src/engine/validator/checks/raw-block-html.ts +65 -0
  914. package/src/engine/validator/checks/stale-body-html.ts +39 -0
  915. package/src/engine/validator/checks/unresolved-footnote-anchor.ts +61 -0
  916. package/src/engine/validator/checks/word-gdocs-paste-artifacts.ts +72 -0
  917. package/src/engine/validator/index.ts +385 -0
  918. package/src/engine/validator/scan.ts +103 -0
  919. package/src/engine/validator/types.ts +114 -0
  920. package/src/engine/webhook-signer.ts +139 -0
  921. package/src/engine/webhooks.ts +224 -0
  922. package/src/index.ts +79 -0
  923. package/src/integration/index.ts +298 -0
  924. package/src/integration/options.ts +30 -0
  925. package/src/integration/vite-plugin.ts +37 -0
  926. package/src/providers/index.ts +2 -0
  927. package/src/providers/null.ts +160 -0
  928. package/src/providers/types.ts +284 -0
  929. package/src/routes/ai.ts +461 -0
  930. package/src/routes/analytics.ts +78 -0
  931. package/src/routes/api-keys.ts +133 -0
  932. package/src/routes/authors.ts +282 -0
  933. package/src/routes/authz-matrix.ts +239 -0
  934. package/src/routes/calendar.ts +127 -0
  935. package/src/routes/config.ts +99 -0
  936. package/src/routes/content-insights.ts +159 -0
  937. package/src/routes/content.ts +1753 -0
  938. package/src/routes/context.ts +146 -0
  939. package/src/routes/cron.ts +27 -0
  940. package/src/routes/dashboard.ts +174 -0
  941. package/src/routes/imports.ts +190 -0
  942. package/src/routes/index.ts +295 -0
  943. package/src/routes/media-lib.ts +405 -0
  944. package/src/routes/media.ts +944 -0
  945. package/src/routes/preview.ts +182 -0
  946. package/src/routes/rbac-invites.ts +220 -0
  947. package/src/routes/rbac.ts +155 -0
  948. package/src/routes/shell.ts +163 -0
  949. package/src/routes/subscriptions.ts +167 -0
  950. package/src/routes/tags.ts +93 -0
  951. package/src/routes/topics.ts +58 -0
  952. package/src/routes/webhooks.ts +233 -0
  953. package/src/schema/index.ts +45 -0
  954. package/src/schema/insights-ingest.ts +126 -0
  955. package/src/schema/migrations.ts +599 -0
  956. package/src/schema/tables.ts +59 -0
  957. package/src/schema/types.ts +576 -0
  958. package/src/ui/api/_authz.ts +100 -0
  959. package/src/ui/api/_content-config.ts +75 -0
  960. package/src/ui/api/activity.ts +33 -0
  961. package/src/ui/api/analytics.ts +42 -0
  962. package/src/ui/api/authors/[id].ts +23 -0
  963. package/src/ui/api/authors.ts +19 -0
  964. package/src/ui/api/calendar.ts +21 -0
  965. package/src/ui/api/content/[id]/ai/headlines.ts +10 -0
  966. package/src/ui/api/content/[id]/ai/meta-description.ts +11 -0
  967. package/src/ui/api/content/[id]/ai/og-image.ts +10 -0
  968. package/src/ui/api/content/[id]/ai/proofread.ts +10 -0
  969. package/src/ui/api/content/[id]/ai/takeaways.ts +11 -0
  970. package/src/ui/api/content/[id]/contributors.ts +13 -0
  971. package/src/ui/api/content/[id]/preview-token.ts +21 -0
  972. package/src/ui/api/content/[id]/revisions/[rev]/restore.ts +12 -0
  973. package/src/ui/api/content/[id]/revisions/[rev].ts +11 -0
  974. package/src/ui/api/content/[id]/revisions.ts +9 -0
  975. package/src/ui/api/content/[id]/seo-score.ts +10 -0
  976. package/src/ui/api/content/[id].ts +23 -0
  977. package/src/ui/api/content/bulk.ts +10 -0
  978. package/src/ui/api/content/counts.ts +11 -0
  979. package/src/ui/api/content/foundry-callback.ts +11 -0
  980. package/src/ui/api/content/import/confirm.ts +16 -0
  981. package/src/ui/api/content/import/parse.ts +16 -0
  982. package/src/ui/api/content/insights-ingest.ts +24 -0
  983. package/src/ui/api/content-insights/dismiss.ts +23 -0
  984. package/src/ui/api/content-insights/index.ts +21 -0
  985. package/src/ui/api/content-insights/undismiss.ts +23 -0
  986. package/src/ui/api/content.ts +21 -0
  987. package/src/ui/api/dashboard.ts +28 -0
  988. package/src/ui/api/me.ts +23 -0
  989. package/src/ui/api/media/[id].ts +22 -0
  990. package/src/ui/api/media/images.ts +9 -0
  991. package/src/ui/api/media/library/[id].ts +9 -0
  992. package/src/ui/api/media/library.ts +22 -0
  993. package/src/ui/api/media/podcast/abort.ts +6 -0
  994. package/src/ui/api/media/podcast/complete.ts +6 -0
  995. package/src/ui/api/media/podcast/init.ts +6 -0
  996. package/src/ui/api/media/podcast/part.ts +6 -0
  997. package/src/ui/api/media/podcast.ts +9 -0
  998. package/src/ui/api/media/videos/abort.ts +9 -0
  999. package/src/ui/api/media/videos/complete.ts +9 -0
  1000. package/src/ui/api/media/videos/init.ts +9 -0
  1001. package/src/ui/api/media/videos/part.ts +9 -0
  1002. package/src/ui/api/notifications.ts +26 -0
  1003. package/src/ui/api/search.ts +23 -0
  1004. package/src/ui/api/settings/api-keys/[id].ts +28 -0
  1005. package/src/ui/api/settings/api-keys.ts +25 -0
  1006. package/src/ui/api/settings/domains.ts +37 -0
  1007. package/src/ui/api/settings/integrations.ts +40 -0
  1008. package/src/ui/api/settings/members/[userId].ts +33 -0
  1009. package/src/ui/api/settings/members/invite.ts +27 -0
  1010. package/src/ui/api/settings/members.ts +23 -0
  1011. package/src/ui/api/settings/webhooks/[id]/test.ts +30 -0
  1012. package/src/ui/api/settings/webhooks/[id].ts +29 -0
  1013. package/src/ui/api/settings/webhooks.ts +27 -0
  1014. package/src/ui/api/subscriptions.ts +56 -0
  1015. package/src/ui/api/tags/[id].ts +24 -0
  1016. package/src/ui/api/tags/index.ts +18 -0
  1017. package/src/ui/api/topics/[id].ts +20 -0
  1018. package/src/ui/api/topics/index.ts +17 -0
  1019. package/src/ui/api/workspace-settings.ts +26 -0
  1020. package/src/ui/client/boot-state.ts +42 -0
  1021. package/src/ui/client/mount.tsx +41 -0
  1022. package/src/ui/commands.ts +62 -0
  1023. package/src/ui/components/CmsApp.tsx +149 -0
  1024. package/src/ui/components/CommandPalette.tsx +118 -0
  1025. package/src/ui/components/NoAccessScreen.tsx +79 -0
  1026. package/src/ui/components/ShareModal.tsx +650 -0
  1027. package/src/ui/components/SharePickers.tsx +790 -0
  1028. package/src/ui/components/ShareStatsPanel.tsx +721 -0
  1029. package/src/ui/components/Sidebar.tsx +86 -0
  1030. package/src/ui/components/SiteSwitcher.tsx +100 -0
  1031. package/src/ui/components/Topbar.tsx +93 -0
  1032. package/src/ui/editor/AiAssistPanel.tsx +407 -0
  1033. package/src/ui/editor/ContentForm.tsx +1462 -0
  1034. package/src/ui/editor/Rte.tsx +382 -0
  1035. package/src/ui/editor/ai-assist.ts +139 -0
  1036. package/src/ui/editor/autosave.ts +36 -0
  1037. package/src/ui/editor/content-payload.ts +125 -0
  1038. package/src/ui/editor/editor-media-upload.ts +26 -0
  1039. package/src/ui/editor/serialize.ts +522 -0
  1040. package/src/ui/editor/tweet-embed.ts +60 -0
  1041. package/src/ui/hash-router.ts +30 -0
  1042. package/src/ui/icons.tsx +208 -0
  1043. package/src/ui/inspector/Field.tsx +30 -0
  1044. package/src/ui/inspector/FoundryTab.tsx +613 -0
  1045. package/src/ui/inspector/HistoryTab.tsx +482 -0
  1046. package/src/ui/inspector/Inspector.tsx +328 -0
  1047. package/src/ui/inspector/OrganizeTab.tsx +534 -0
  1048. package/src/ui/inspector/PublishTab.tsx +626 -0
  1049. package/src/ui/inspector/Section.tsx +81 -0
  1050. package/src/ui/inspector/SeoTab.tsx +573 -0
  1051. package/src/ui/inspector/foundry-stages.ts +140 -0
  1052. package/src/ui/inspector/inspector-data.ts +232 -0
  1053. package/src/ui/inspector/organize-data.ts +51 -0
  1054. package/src/ui/inspector/revision-diff.ts +213 -0
  1055. package/src/ui/inspector/seo-helpers.ts +71 -0
  1056. package/src/ui/inspector/tab-visibility.ts +37 -0
  1057. package/src/ui/nav.ts +48 -0
  1058. package/src/ui/pages/admin.astro +32 -0
  1059. package/src/ui/preview/draft-page.tsx +395 -0
  1060. package/src/ui/preview/preview-layout.ts +49 -0
  1061. package/src/ui/screens/AnalyticsScreen.tsx +938 -0
  1062. package/src/ui/screens/AuthorsScreen.tsx +524 -0
  1063. package/src/ui/screens/CalendarScreen.tsx +694 -0
  1064. package/src/ui/screens/ContentInsightsScreen.tsx +417 -0
  1065. package/src/ui/screens/ContentRoute.tsx +35 -0
  1066. package/src/ui/screens/DashboardScreen.tsx +654 -0
  1067. package/src/ui/screens/EditorScreen.tsx +673 -0
  1068. package/src/ui/screens/LibraryScreen.tsx +1350 -0
  1069. package/src/ui/screens/MediaScreen.tsx +357 -0
  1070. package/src/ui/screens/SettingsScreen.tsx +1841 -0
  1071. package/src/ui/screens/SocialShareScreen.tsx +670 -0
  1072. package/src/ui/screens/SubscriptionsScreen.tsx +1240 -0
  1073. package/src/ui/screens/TopicsScreen.tsx +912 -0
  1074. package/src/ui/screens/analytics-data.ts +68 -0
  1075. package/src/ui/screens/calendar-data.ts +126 -0
  1076. package/src/ui/screens/content-insights-data.ts +127 -0
  1077. package/src/ui/screens/content-view.ts +30 -0
  1078. package/src/ui/screens/library-data.ts +177 -0
  1079. package/src/ui/screens/media-upload.ts +283 -0
  1080. package/src/ui/screens/public-url.ts +81 -0
  1081. package/src/ui/screens/registry.tsx +53 -0
  1082. package/src/ui/screens/render-state.ts +228 -0
  1083. package/src/ui/screens/settings-data.ts +140 -0
  1084. package/src/ui/screens/settings-panels-data.ts +142 -0
  1085. package/src/ui/screens/social-share-data.ts +753 -0
  1086. package/src/ui/screens/topics-data.ts +75 -0
  1087. package/src/ui/styles/broadsheet.css +394 -0
  1088. package/src/ui/theme.ts +104 -0
  1089. package/src/ui/tweaks.ts +37 -0
  1090. package/src/ui/use-hash-router.ts +17 -0
  1091. package/src/ui/use-tweaks.ts +31 -0
  1092. package/src/ui/workspace-context.tsx +62 -0
  1093. package/src/virtual.d.ts +4 -0
@@ -0,0 +1,1032 @@
1
+ // The publishing engine — the content data-access CORE.
2
+ //
3
+ // Ported verbatim from fulcrum-labs/fronts src/lib/publisher.ts (inventory
4
+ // §1 + §6: extract). The engine takes an injected D1Database (the site
5
+ // supplies its own binding); NO Fronts imports remain:
6
+ // - the Fronts `D1Database` ambient type → package-local `./d1` (structural).
7
+ // - the Fronts `./primary-categories` `PrimaryCategorySlug` theme-token →
8
+ // `string | null` (site-supplied taxonomy). The `'analysis'` default is
9
+ // preserved verbatim as the documented Fronts default; a site can pass any
10
+ // value.
11
+ // - `initialVideoProcessingState` from `./foundry/video-processing`
12
+ // (bead 08) is inlined here as a constant; the bead-08 media glue remains
13
+ // the source of truth for the rest of the video state machine.
14
+ //
15
+ // CRITICAL (inventory §6): `duplicateContentItem` does FOUR `INSERT … SELECT`
16
+ // copies whose explicit column lists are hand-maintained and must stay
17
+ // positionally aligned to the live schema. `created_at`/`updated_at` are
18
+ // deliberately OMITTED so the copy gets fresh `DEFAULT (unixepoch())`
19
+ // timestamps; the video copy hard-codes `processing_state = 'ready'` so a
20
+ // duplicate is NOT re-queued to Foundry. Any future `content_items` column
21
+ // addition requires a matching edit here or the duplicate silently drops it.
22
+ import type { D1Database } from './d1.js'
23
+ import { evaluateArticleBody, type PublishGuardOutcome } from './publish-guard.js'
24
+ import { slugify } from './slug.js'
25
+
26
+ type ContentType = 'article' | 'video' | 'podcast' | 'newsletter'
27
+ type ContentVisibility = 'free' | 'premium'
28
+
29
+ /**
30
+ * The processing_state a freshly created video starts in. Mirrors
31
+ * fulcrum-labs/fronts `foundry/video-processing.ts` `initialVideoProcessingState`
32
+ * (bead 08 owns the full state machine). Inlined here so the data-access core
33
+ * carries no media-glue dependency.
34
+ */
35
+ const initialVideoProcessingState = 'pending'
36
+
37
+ const WORDS_PER_MINUTE = 200
38
+
39
+ export class PublishContentValidationError extends Error {
40
+ readonly errors: PublishGuardOutcome['errors']
41
+ readonly summary: string
42
+
43
+ constructor(outcome: PublishGuardOutcome) {
44
+ super('Article body failed pre-publish validation')
45
+ this.name = 'PublishContentValidationError'
46
+ this.errors = outcome.errors
47
+ this.summary = outcome.summary
48
+ }
49
+ }
50
+
51
+ /** Strip markdown syntax and count words */
52
+ export function countWords(text: string): number {
53
+ const plain = text
54
+ .replace(/```[\s\S]*?```/g, '') // code blocks
55
+ .replace(/`[^`]+`/g, '') // inline code
56
+ .replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1') // links/images
57
+ .replace(/[#*_~>|`-]/g, '') // markdown syntax chars
58
+ .trim()
59
+ if (!plain) return 0
60
+ return plain.split(/\s+/).filter(Boolean).length
61
+ }
62
+
63
+ /** Estimate reading time in minutes (minimum 1) */
64
+ export function estimateReadTime(wordCount: number): number {
65
+ return Math.max(1, Math.round(wordCount / WORDS_PER_MINUTE))
66
+ }
67
+
68
+ export interface BaseContentInput {
69
+ slug: string
70
+ title: string
71
+ seoTitle?: string | null
72
+ description?: string | null
73
+ excerpt?: string | null
74
+ byline?: string | null
75
+ /**
76
+ * Theme-token taxonomy (site-supplied vocabulary). Stored in
77
+ * `content_items.channel`. Defaults to `'analysis'` (the Fronts default)
78
+ * when omitted on create.
79
+ */
80
+ primaryCategory?: string | null
81
+ primaryTopic?: string | null
82
+ featured?: boolean
83
+ visibility?: ContentVisibility
84
+ authorId?: string | null
85
+ heroImageId?: string | null
86
+ heroImageAlt?: string | null
87
+ heroImageCaption?: string | null
88
+ socialImageId?: string | null
89
+ canonicalUrl?: string | null
90
+ tags?: string[]
91
+ /** The SEO focus keyword for this content item (migration 0004 column). */
92
+ seoFocusKeyword?: string | null
93
+ }
94
+
95
+ export interface ArticleInput extends BaseContentInput {
96
+ type: 'article'
97
+ content: {
98
+ bodyMarkdown: string
99
+ bodyHtml?: string | null
100
+ subtitle?: string | null
101
+ wordCount?: number | null
102
+ readTimeMinutes?: number | null
103
+ editorTakeaways?: string[] | null
104
+ }
105
+ }
106
+
107
+ export interface VideoInput extends BaseContentInput {
108
+ type: 'video'
109
+ content: {
110
+ script: string
111
+ videoId: string
112
+ durationSeconds?: number | null
113
+ thumbnailImageId?: string | null
114
+ processingSourceUrl?: string | null
115
+ processingSourceKind?: string | null
116
+ }
117
+ }
118
+
119
+ export interface PodcastInput extends BaseContentInput {
120
+ type: 'podcast'
121
+ content: {
122
+ transcript: string
123
+ audioR2Key: string
124
+ durationSeconds?: number | null
125
+ }
126
+ }
127
+
128
+ export interface NewsletterInput extends BaseContentInput {
129
+ type: 'newsletter'
130
+ content: {
131
+ bodyMarkdown: string
132
+ bodyHtml?: string | null
133
+ subtitle?: string | null
134
+ wordCount?: number | null
135
+ readTimeMinutes?: number | null
136
+ editorTakeaways?: string[] | null
137
+ }
138
+ }
139
+
140
+ export type CreateContentInput = ArticleInput | VideoInput | PodcastInput | NewsletterInput
141
+
142
+ export interface UpdateContentInput extends Partial<BaseContentInput> {
143
+ content?: Partial<ArticleInput['content'] | VideoInput['content'] | PodcastInput['content']>
144
+ relations?: Array<{
145
+ relatedId: string
146
+ rank: number
147
+ reason?: string | null
148
+ }>
149
+ aiLockedFields?: string[]
150
+ }
151
+
152
+ /**
153
+ * The shape `getContentItem` returns from `SELECT * FROM content_items`.
154
+ * Note this is the engine's read-side row (nullable `featured`/timestamps as
155
+ * D1 returns them); the strict migration-contract row type lives in
156
+ * `@growth-labs/cms` schema as `ContentItemRow`.
157
+ */
158
+ export interface ContentItemRecord {
159
+ id: string
160
+ type: ContentType
161
+ status: string
162
+ visibility: ContentVisibility
163
+ slug: string
164
+ title: string
165
+ seo_title: string | null
166
+ description: string | null
167
+ excerpt: string | null
168
+ byline: string | null
169
+ channel: string | null
170
+ primary_topic: string | null
171
+ featured: number | null
172
+ author_id: string | null
173
+ hero_image_id: string | null
174
+ hero_image_alt: string | null
175
+ hero_image_caption: string | null
176
+ social_image_id: string | null
177
+ canonical_url: string | null
178
+ publish_at: number | null
179
+ published_at: number | null
180
+ publish_tz: string | null
181
+ ai_locked_fields: string | null
182
+ published_revision_id: string | null
183
+ seo_focus_keyword: string | null
184
+ created_at: number | null
185
+ updated_at: number | null
186
+ }
187
+
188
+ export async function ensureUniqueSlug(
189
+ db: D1Database,
190
+ slug: string,
191
+ excludeId?: string,
192
+ ): Promise<string> {
193
+ const base = slugify(slug)
194
+ let candidate = base
195
+ let suffix = 2
196
+
197
+ while (true) {
198
+ const row = await db
199
+ .prepare('SELECT id FROM content_items WHERE slug = ? LIMIT 1')
200
+ .bind(candidate)
201
+ .first<{ id: string }>()
202
+
203
+ if (!row || (excludeId && row.id === excludeId)) {
204
+ return candidate
205
+ }
206
+
207
+ candidate = `${base}-${suffix}`
208
+ suffix += 1
209
+ }
210
+ }
211
+
212
+ function normalizeTags(tags?: string[]): string[] {
213
+ if (!tags) return []
214
+ return tags
215
+ .map((tag) => slugify(tag))
216
+ .filter(Boolean)
217
+ .slice(0, 10)
218
+ }
219
+
220
+ function serializeTakeaways(value: string[] | null | undefined): string | null {
221
+ if (!value) return null
222
+ const normalized = value
223
+ .map((item) => item.trim())
224
+ .filter(Boolean)
225
+ .slice(0, 4)
226
+ return normalized.length > 0 ? JSON.stringify(normalized) : null
227
+ }
228
+
229
+ async function ensureTags(db: D1Database, tags: string[]): Promise<{ id: string; slug: string }[]> {
230
+ const normalized = normalizeTags(tags)
231
+ const results: { id: string; slug: string }[] = []
232
+
233
+ for (const tag of normalized) {
234
+ const existing = await db
235
+ .prepare('SELECT id, slug FROM content_tags WHERE slug = ? LIMIT 1')
236
+ .bind(tag)
237
+ .first<{ id: string; slug: string }>()
238
+
239
+ if (existing) {
240
+ results.push(existing)
241
+ continue
242
+ }
243
+
244
+ const id = crypto.randomUUID()
245
+ await db
246
+ .prepare('INSERT INTO content_tags (id, slug, label) VALUES (?, ?, ?)')
247
+ .bind(id, tag, tag)
248
+ .run()
249
+
250
+ results.push({ id, slug: tag })
251
+ }
252
+
253
+ return results
254
+ }
255
+
256
+ export async function setContentTags(
257
+ db: D1Database,
258
+ contentId: string,
259
+ tags: string[],
260
+ ): Promise<void> {
261
+ const normalized = normalizeTags(tags)
262
+ await db.prepare('DELETE FROM content_tag_links WHERE content_id = ?').bind(contentId).run()
263
+ if (normalized.length === 0) return
264
+
265
+ const tagRows = await ensureTags(db, normalized)
266
+
267
+ const insert = db.prepare('INSERT INTO content_tag_links (content_id, tag_id) VALUES (?, ?)')
268
+
269
+ for (const tag of tagRows) {
270
+ await insert.bind(contentId, tag.id).run()
271
+ }
272
+ }
273
+
274
+ export async function setContentRelations(
275
+ db: D1Database,
276
+ contentId: string,
277
+ relations: Array<{ relatedId: string; rank: number; reason?: string | null }>,
278
+ ): Promise<void> {
279
+ await db.prepare('DELETE FROM content_relations WHERE content_id = ?').bind(contentId).run()
280
+ if (relations.length === 0) return
281
+
282
+ const insert = db.prepare(
283
+ 'INSERT INTO content_relations (content_id, related_id, rank, reason) VALUES (?, ?, ?, ?)',
284
+ )
285
+
286
+ for (const rel of relations) {
287
+ await insert.bind(contentId, rel.relatedId, rel.rank, rel.reason || null).run()
288
+ }
289
+ }
290
+
291
+ export async function getContentRelations(
292
+ db: D1Database,
293
+ contentId: string,
294
+ ): Promise<Array<{ related_id: string; rank: number; reason: string | null }>> {
295
+ const result = await db
296
+ .prepare(
297
+ 'SELECT related_id, rank, reason FROM content_relations WHERE content_id = ? ORDER BY rank ASC',
298
+ )
299
+ .bind(contentId)
300
+ .all<{ related_id: string; rank: number; reason: string | null }>()
301
+ return result.results || []
302
+ }
303
+
304
+ export async function createContent(
305
+ db: D1Database,
306
+ input: CreateContentInput,
307
+ ): Promise<{ id: string; slug: string }> {
308
+ const id = crypto.randomUUID()
309
+ const slug = await ensureUniqueSlug(db, input.slug)
310
+ const visibility = input.visibility || 'free'
311
+ const primaryCategory = input.primaryCategory || 'analysis'
312
+
313
+ await db
314
+ .prepare(
315
+ `
316
+ INSERT INTO content_items (
317
+ id, type, status, visibility, slug, title, seo_title, description, excerpt,
318
+ byline, channel, primary_topic, featured, author_id, hero_image_id,
319
+ hero_image_alt, hero_image_caption, social_image_id, canonical_url
320
+ ) VALUES (?, ?, 'draft', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
321
+ `,
322
+ )
323
+ .bind(
324
+ id,
325
+ input.type,
326
+ visibility,
327
+ slug,
328
+ input.title,
329
+ input.seoTitle || null,
330
+ input.description || null,
331
+ input.excerpt || null,
332
+ input.byline || null,
333
+ primaryCategory,
334
+ input.primaryTopic || null,
335
+ input.featured ? 1 : 0,
336
+ input.authorId || null,
337
+ input.heroImageId || null,
338
+ input.heroImageAlt || null,
339
+ input.heroImageCaption || null,
340
+ input.socialImageId || null,
341
+ input.canonicalUrl || null,
342
+ )
343
+ .run()
344
+
345
+ if (input.type === 'article' || input.type === 'newsletter') {
346
+ const wc = input.content.wordCount ?? countWords(input.content.bodyMarkdown)
347
+ const rt = input.content.readTimeMinutes ?? estimateReadTime(wc)
348
+ await db
349
+ .prepare(
350
+ `INSERT INTO article_content (content_id, body_markdown, body_html, word_count, read_time_minutes, subtitle, editor_takeaways)
351
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
352
+ )
353
+ .bind(
354
+ id,
355
+ input.content.bodyMarkdown,
356
+ input.content.bodyHtml || null,
357
+ wc || null,
358
+ rt || null,
359
+ input.content.subtitle || null,
360
+ serializeTakeaways(input.content.editorTakeaways),
361
+ )
362
+ .run()
363
+ }
364
+
365
+ if (input.type === 'video') {
366
+ await db
367
+ .prepare(
368
+ `INSERT INTO video_content (
369
+ content_id,
370
+ script,
371
+ video_id,
372
+ duration_seconds,
373
+ thumbnail_image_id,
374
+ processing_state,
375
+ processing_source_url,
376
+ processing_source_kind
377
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
378
+ )
379
+ .bind(
380
+ id,
381
+ input.content.script,
382
+ input.content.videoId,
383
+ input.content.durationSeconds || null,
384
+ input.content.thumbnailImageId || null,
385
+ initialVideoProcessingState,
386
+ input.content.processingSourceUrl || null,
387
+ input.content.processingSourceKind || null,
388
+ )
389
+ .run()
390
+ }
391
+
392
+ if (input.type === 'podcast') {
393
+ await db
394
+ .prepare(
395
+ `INSERT INTO podcast_content (content_id, transcript, audio_r2_key, duration_seconds)
396
+ VALUES (?, ?, ?, ?)`,
397
+ )
398
+ .bind(
399
+ id,
400
+ input.content.transcript,
401
+ input.content.audioR2Key,
402
+ input.content.durationSeconds || null,
403
+ )
404
+ .run()
405
+ }
406
+
407
+ if (input.tags) {
408
+ await setContentTags(db, id, input.tags)
409
+ }
410
+
411
+ return { id, slug }
412
+ }
413
+
414
+ export async function getContentItem(
415
+ db: D1Database,
416
+ id: string,
417
+ ): Promise<ContentItemRecord | null> {
418
+ return db
419
+ .prepare('SELECT * FROM content_items WHERE id = ? LIMIT 1')
420
+ .bind(id)
421
+ .first<ContentItemRecord>()
422
+ }
423
+
424
+ export async function updateContentItem(
425
+ db: D1Database,
426
+ id: string,
427
+ input: UpdateContentInput,
428
+ ): Promise<{ slug: string } | null> {
429
+ const existing = await getContentItem(db, id)
430
+ if (!existing) return null
431
+
432
+ const hasOwn = (source: object, key: string) => Object.hasOwn(source, key)
433
+
434
+ const slug = input.slug ? await ensureUniqueSlug(db, input.slug, id) : existing.slug
435
+ const visibility = input.visibility || existing.visibility
436
+ const featured =
437
+ typeof input.featured === 'boolean' ? (input.featured ? 1 : 0) : existing.featured || 0
438
+ const primaryCategory = hasOwn(input, 'primaryCategory')
439
+ ? (input.primaryCategory ?? null)
440
+ : (existing.channel ?? 'analysis')
441
+ const aiLockedFields =
442
+ input.aiLockedFields !== undefined
443
+ ? JSON.stringify(input.aiLockedFields)
444
+ : existing.ai_locked_fields || '[]'
445
+ const title = hasOwn(input, 'title') ? (input.title ?? existing.title) : existing.title
446
+ const seoTitle = hasOwn(input, 'seoTitle') ? (input.seoTitle ?? null) : existing.seo_title
447
+ const description = hasOwn(input, 'description')
448
+ ? (input.description ?? null)
449
+ : existing.description
450
+ const excerpt = hasOwn(input, 'excerpt') ? (input.excerpt ?? null) : existing.excerpt
451
+ const byline = hasOwn(input, 'byline') ? (input.byline ?? null) : existing.byline
452
+ const primaryTopic = hasOwn(input, 'primaryTopic')
453
+ ? (input.primaryTopic ?? null)
454
+ : existing.primary_topic
455
+ const authorId = hasOwn(input, 'authorId') ? (input.authorId ?? null) : existing.author_id
456
+ const heroImageId = hasOwn(input, 'heroImageId')
457
+ ? (input.heroImageId ?? null)
458
+ : existing.hero_image_id
459
+ const heroImageAlt = hasOwn(input, 'heroImageAlt')
460
+ ? (input.heroImageAlt ?? null)
461
+ : existing.hero_image_alt
462
+ const heroImageCaption = hasOwn(input, 'heroImageCaption')
463
+ ? (input.heroImageCaption ?? null)
464
+ : existing.hero_image_caption
465
+ const socialImageId = hasOwn(input, 'socialImageId')
466
+ ? (input.socialImageId ?? null)
467
+ : existing.social_image_id
468
+ const canonicalUrl = hasOwn(input, 'canonicalUrl')
469
+ ? (input.canonicalUrl ?? null)
470
+ : existing.canonical_url
471
+ const seoFocusKeyword = hasOwn(input, 'seoFocusKeyword')
472
+ ? (input.seoFocusKeyword ?? null)
473
+ : (existing.seo_focus_keyword ?? null)
474
+
475
+ await db
476
+ .prepare(
477
+ `
478
+ UPDATE content_items SET
479
+ slug = ?,
480
+ title = ?,
481
+ seo_title = ?,
482
+ description = ?,
483
+ excerpt = ?,
484
+ byline = ?,
485
+ channel = ?,
486
+ primary_topic = ?,
487
+ featured = ?,
488
+ visibility = ?,
489
+ author_id = ?,
490
+ hero_image_id = ?,
491
+ hero_image_alt = ?,
492
+ hero_image_caption = ?,
493
+ social_image_id = ?,
494
+ canonical_url = ?,
495
+ ai_locked_fields = ?,
496
+ seo_focus_keyword = ?,
497
+ updated_at = unixepoch()
498
+ WHERE id = ?
499
+ `,
500
+ )
501
+ .bind(
502
+ slug,
503
+ title,
504
+ seoTitle,
505
+ description,
506
+ excerpt,
507
+ byline,
508
+ primaryCategory,
509
+ primaryTopic,
510
+ featured,
511
+ visibility,
512
+ authorId,
513
+ heroImageId,
514
+ heroImageAlt,
515
+ heroImageCaption,
516
+ socialImageId,
517
+ canonicalUrl,
518
+ aiLockedFields,
519
+ seoFocusKeyword,
520
+ id,
521
+ )
522
+ .run()
523
+
524
+ if (input.tags !== undefined) {
525
+ await setContentTags(db, id, input.tags)
526
+ }
527
+
528
+ if (input.relations !== undefined) {
529
+ await setContentRelations(db, id, input.relations)
530
+ }
531
+
532
+ if (input.content) {
533
+ if (existing.type === 'article' || existing.type === 'newsletter') {
534
+ const current = await db
535
+ .prepare(
536
+ `
537
+ SELECT body_markdown, body_html, word_count, read_time_minutes, subtitle, editor_takeaways
538
+ FROM article_content
539
+ WHERE content_id = ?
540
+ LIMIT 1
541
+ `,
542
+ )
543
+ .bind(id)
544
+ .first<{
545
+ body_markdown: string
546
+ body_html: string | null
547
+ word_count: number | null
548
+ read_time_minutes: number | null
549
+ subtitle: string | null
550
+ editor_takeaways: string | null
551
+ }>()
552
+ if (!current) return { slug }
553
+
554
+ const articleContent = input.content as Partial<ArticleInput['content']>
555
+ const bodyMarkdownProvided = hasOwn(articleContent, 'bodyMarkdown')
556
+ const wordCountProvided = hasOwn(articleContent, 'wordCount')
557
+ const readTimeProvided = hasOwn(articleContent, 'readTimeMinutes')
558
+ const nextBodyMarkdown = bodyMarkdownProvided
559
+ ? (articleContent.bodyMarkdown ?? current.body_markdown)
560
+ : current.body_markdown
561
+ const nextWordCount = wordCountProvided
562
+ ? (articleContent.wordCount ?? null)
563
+ : bodyMarkdownProvided
564
+ ? nextBodyMarkdown
565
+ ? countWords(nextBodyMarkdown)
566
+ : null
567
+ : current.word_count
568
+ const nextReadTime = readTimeProvided
569
+ ? (articleContent.readTimeMinutes ?? null)
570
+ : bodyMarkdownProvided
571
+ ? nextWordCount
572
+ ? estimateReadTime(nextWordCount)
573
+ : null
574
+ : current.read_time_minutes
575
+ await db
576
+ .prepare(
577
+ `
578
+ UPDATE article_content SET
579
+ body_markdown = ?,
580
+ body_html = ?,
581
+ word_count = ?,
582
+ read_time_minutes = ?,
583
+ subtitle = ?,
584
+ editor_takeaways = ?,
585
+ ai_takeaways_at = CASE WHEN ? THEN NULL ELSE ai_takeaways_at END
586
+ WHERE content_id = ?
587
+ `,
588
+ )
589
+ .bind(
590
+ nextBodyMarkdown,
591
+ hasOwn(articleContent, 'bodyHtml')
592
+ ? (articleContent.bodyHtml ?? null)
593
+ : bodyMarkdownProvided
594
+ ? null
595
+ : current.body_html,
596
+ nextWordCount,
597
+ nextReadTime,
598
+ hasOwn(articleContent, 'subtitle') ? (articleContent.subtitle ?? null) : current.subtitle,
599
+ hasOwn(articleContent, 'editorTakeaways')
600
+ ? serializeTakeaways(articleContent.editorTakeaways)
601
+ : current.editor_takeaways,
602
+ existing.status === 'published' && bodyMarkdownProvided ? 1 : 0,
603
+ id,
604
+ )
605
+ .run()
606
+ }
607
+
608
+ if (existing.type === 'video') {
609
+ const current = await db
610
+ .prepare(
611
+ `
612
+ SELECT script, video_id, duration_seconds, thumbnail_image_id,
613
+ processing_source_url, processing_source_kind
614
+ FROM video_content
615
+ WHERE content_id = ?
616
+ LIMIT 1
617
+ `,
618
+ )
619
+ .bind(id)
620
+ .first<{
621
+ script: string
622
+ video_id: string
623
+ duration_seconds: number | null
624
+ thumbnail_image_id: string | null
625
+ processing_source_url: string | null
626
+ processing_source_kind: string | null
627
+ }>()
628
+ if (!current) return { slug }
629
+
630
+ const videoContent = input.content as Partial<VideoInput['content']>
631
+ const nextSourceUrl = hasOwn(videoContent, 'processingSourceUrl')
632
+ ? (videoContent.processingSourceUrl ?? null)
633
+ : current.processing_source_url
634
+ const nextSourceKind = hasOwn(videoContent, 'processingSourceKind')
635
+ ? (videoContent.processingSourceKind ?? null)
636
+ : current.processing_source_kind
637
+ const sourceChanged =
638
+ nextSourceUrl !== current.processing_source_url ||
639
+ nextSourceKind !== current.processing_source_kind
640
+ await db
641
+ .prepare(
642
+ `
643
+ UPDATE video_content SET
644
+ script = ?,
645
+ video_id = ?,
646
+ duration_seconds = ?,
647
+ thumbnail_image_id = ?,
648
+ processing_source_url = ?,
649
+ processing_source_kind = ?,
650
+ processing_state = CASE WHEN ? THEN 'pending' ELSE processing_state END,
651
+ processing_correlation_id = CASE WHEN ? THEN NULL ELSE processing_correlation_id END,
652
+ processing_trigger_token = CASE WHEN ? THEN NULL ELSE processing_trigger_token END,
653
+ processing_started_at = CASE WHEN ? THEN NULL ELSE processing_started_at END,
654
+ processing_completed_at = CASE WHEN ? THEN NULL ELSE processing_completed_at END,
655
+ processing_last_event_at = CASE WHEN ? THEN NULL ELSE processing_last_event_at END,
656
+ processing_error = CASE WHEN ? THEN NULL ELSE processing_error END,
657
+ source_fetched = CASE WHEN ? THEN 0 ELSE source_fetched END,
658
+ transcript_ready = CASE WHEN ? THEN 0 ELSE transcript_ready END,
659
+ hls_ready = CASE WHEN ? THEN 0 ELSE hls_ready END,
660
+ transcript_url = CASE WHEN ? THEN NULL ELSE transcript_url END,
661
+ hls_manifest_url = CASE WHEN ? THEN NULL ELSE hls_manifest_url END,
662
+ hls_poster_url = CASE WHEN ? THEN NULL ELSE hls_poster_url END
663
+ WHERE content_id = ?
664
+ `,
665
+ )
666
+ .bind(
667
+ hasOwn(videoContent, 'script') ? (videoContent.script ?? current.script) : current.script,
668
+ hasOwn(videoContent, 'videoId')
669
+ ? (videoContent.videoId ?? current.video_id)
670
+ : current.video_id,
671
+ hasOwn(videoContent, 'durationSeconds')
672
+ ? (videoContent.durationSeconds ?? null)
673
+ : current.duration_seconds,
674
+ hasOwn(videoContent, 'thumbnailImageId')
675
+ ? (videoContent.thumbnailImageId ?? null)
676
+ : current.thumbnail_image_id,
677
+ nextSourceUrl,
678
+ nextSourceKind,
679
+ sourceChanged ? 1 : 0,
680
+ sourceChanged ? 1 : 0,
681
+ sourceChanged ? 1 : 0,
682
+ sourceChanged ? 1 : 0,
683
+ sourceChanged ? 1 : 0,
684
+ sourceChanged ? 1 : 0,
685
+ sourceChanged ? 1 : 0,
686
+ sourceChanged ? 1 : 0,
687
+ sourceChanged ? 1 : 0,
688
+ sourceChanged ? 1 : 0,
689
+ sourceChanged ? 1 : 0,
690
+ sourceChanged ? 1 : 0,
691
+ sourceChanged ? 1 : 0,
692
+ id,
693
+ )
694
+ .run()
695
+ }
696
+
697
+ if (existing.type === 'podcast') {
698
+ const current = await db
699
+ .prepare(
700
+ `
701
+ SELECT transcript, audio_r2_key, duration_seconds
702
+ FROM podcast_content
703
+ WHERE content_id = ?
704
+ LIMIT 1
705
+ `,
706
+ )
707
+ .bind(id)
708
+ .first<{
709
+ transcript: string
710
+ audio_r2_key: string
711
+ duration_seconds: number | null
712
+ }>()
713
+ if (!current) return { slug }
714
+
715
+ const podcastContent = input.content as Partial<PodcastInput['content']>
716
+ await db
717
+ .prepare(
718
+ `
719
+ UPDATE podcast_content SET
720
+ transcript = ?,
721
+ audio_r2_key = ?,
722
+ duration_seconds = ?
723
+ WHERE content_id = ?
724
+ `,
725
+ )
726
+ .bind(
727
+ hasOwn(podcastContent, 'transcript')
728
+ ? (podcastContent.transcript ?? current.transcript)
729
+ : current.transcript,
730
+ hasOwn(podcastContent, 'audioR2Key')
731
+ ? (podcastContent.audioR2Key ?? current.audio_r2_key)
732
+ : current.audio_r2_key,
733
+ hasOwn(podcastContent, 'durationSeconds')
734
+ ? (podcastContent.durationSeconds ?? null)
735
+ : current.duration_seconds,
736
+ id,
737
+ )
738
+ .run()
739
+ }
740
+ }
741
+
742
+ return { slug }
743
+ }
744
+
745
+ export async function scheduleContent(
746
+ db: D1Database,
747
+ id: string,
748
+ publishAt: number,
749
+ publishTz = 'Europe/Paris',
750
+ ): Promise<void> {
751
+ await db
752
+ .prepare(
753
+ `UPDATE content_items SET status = 'scheduled', publish_at = ?, publish_tz = ?, updated_at = unixepoch() WHERE id = ?`,
754
+ )
755
+ .bind(publishAt, publishTz, id)
756
+ .run()
757
+ }
758
+
759
+ export async function evaluateContentBodyForPublish(
760
+ db: D1Database,
761
+ id: string,
762
+ source: string,
763
+ ): Promise<PublishGuardOutcome | null> {
764
+ const row = await db
765
+ .prepare(
766
+ `SELECT
767
+ c.type,
768
+ ac.body_markdown,
769
+ ac.body_html
770
+ FROM content_items c
771
+ LEFT JOIN article_content ac ON ac.content_id = c.id
772
+ WHERE c.id = ?
773
+ LIMIT 1`,
774
+ )
775
+ .bind(id)
776
+ .first<{
777
+ type: ContentType
778
+ body_markdown: string | null
779
+ body_html: string | null
780
+ }>()
781
+
782
+ if (!row || (row.type !== 'article' && row.type !== 'newsletter')) {
783
+ return null
784
+ }
785
+
786
+ return evaluateArticleBody(
787
+ {
788
+ body_markdown: row.body_markdown,
789
+ body_html: row.body_html,
790
+ },
791
+ {
792
+ source,
793
+ contentId: id,
794
+ requireBody: true,
795
+ },
796
+ )
797
+ }
798
+
799
+ export async function assertContentBodyValidForPublish(
800
+ db: D1Database,
801
+ id: string,
802
+ source: string,
803
+ ): Promise<void> {
804
+ const outcome = await evaluateContentBodyForPublish(db, id, source)
805
+ if (outcome && !outcome.ok) {
806
+ throw new PublishContentValidationError(outcome)
807
+ }
808
+ }
809
+
810
+ export async function publishContent(
811
+ db: D1Database,
812
+ id: string,
813
+ publishedAt: number,
814
+ ): Promise<void> {
815
+ await db
816
+ .prepare(
817
+ `UPDATE content_items SET status = 'published', published_at = ?, publish_at = NULL, updated_at = unixepoch() WHERE id = ?`,
818
+ )
819
+ .bind(publishedAt, id)
820
+ .run()
821
+ }
822
+
823
+ /**
824
+ * Atomically claim a SCHEDULED item for publishing.
825
+ *
826
+ * Performs the scheduled→published transition ONLY when the row is still
827
+ * `status = 'scheduled'`, and reports whether THIS call won the claim. This is
828
+ * the concurrency guard for the scheduled-publish sweep: when two cron runs
829
+ * overlap, both may SELECT the same due row, but only the first conditional
830
+ * UPDATE flips the status — the second sees `changes = 0` and must no-op
831
+ * (skipping the duplicate publish + duplicate published-revision write).
832
+ *
833
+ * Returns true if this call published the item, false if it was already taken.
834
+ */
835
+ export async function claimScheduledForPublish(
836
+ db: D1Database,
837
+ id: string,
838
+ publishedAt: number,
839
+ ): Promise<boolean> {
840
+ const res = await db
841
+ .prepare(
842
+ `UPDATE content_items
843
+ SET status = 'published', published_at = ?, publish_at = NULL, updated_at = unixepoch()
844
+ WHERE id = ? AND status = 'scheduled'`,
845
+ )
846
+ .bind(publishedAt, id)
847
+ .run()
848
+ const changes = (res.meta as { changes?: number } | undefined)?.changes ?? 0
849
+ return changes > 0
850
+ }
851
+
852
+ export async function unpublishContent(db: D1Database, id: string): Promise<void> {
853
+ await db
854
+ .prepare(
855
+ `UPDATE content_items
856
+ SET status = 'draft',
857
+ published_at = NULL,
858
+ publish_at = NULL,
859
+ updated_at = unixepoch()
860
+ WHERE id = ?`,
861
+ )
862
+ .bind(id)
863
+ .run()
864
+ }
865
+
866
+ export async function duplicateContentItem(
867
+ db: D1Database,
868
+ sourceId: string,
869
+ ): Promise<{ id: string; slug: string } | null> {
870
+ const source = await getContentItem(db, sourceId)
871
+ if (!source) return null
872
+
873
+ const newId = crypto.randomUUID()
874
+ const newSlug = await ensureUniqueSlug(db, `${source.slug}-copy`)
875
+
876
+ // INSERT…SELECT copy #1 — content_items. The explicit column list is
877
+ // hand-maintained and MUST stay positionally aligned to the schema.
878
+ // created_at/updated_at are OMITTED so the copy gets fresh
879
+ // DEFAULT (unixepoch()) timestamps; status forced to 'draft', featured to
880
+ // 0, publish_at/published_at/published_revision_id to NULL. (inventory §6)
881
+ //
882
+ // SEO carries over (seo_title, seo_focus_keyword, seo_score, ai_og_image_id)
883
+ // — a duplicate should keep the SEO work already done on the source. But
884
+ // board_position is the source item's slot in the editorial board ordering;
885
+ // a fresh draft copy must NOT inherit it, so it is reset to NULL.
886
+ await db
887
+ .prepare(
888
+ `INSERT INTO content_items (
889
+ id, type, status, visibility, slug, title, seo_title, description, excerpt,
890
+ byline, channel, primary_topic, featured, author_id,
891
+ hero_image_id, hero_image_alt, hero_image_caption, social_image_id,
892
+ canonical_url, publish_at, published_at,
893
+ publish_tz, ai_locked_fields, published_revision_id,
894
+ seo_focus_keyword, seo_score, ai_og_image_id, board_position
895
+ )
896
+ SELECT
897
+ ?, type, 'draft', visibility, ?, title || ' (Copy)', seo_title, description, excerpt,
898
+ byline, channel, primary_topic, 0, author_id,
899
+ hero_image_id, hero_image_alt, hero_image_caption, social_image_id,
900
+ canonical_url, NULL, NULL,
901
+ publish_tz, ai_locked_fields, NULL,
902
+ seo_focus_keyword, seo_score, ai_og_image_id, NULL
903
+ FROM content_items
904
+ WHERE id = ?`,
905
+ )
906
+ .bind(newId, newSlug, sourceId)
907
+ .run()
908
+
909
+ if (source.type === 'article' || source.type === 'newsletter') {
910
+ // INSERT…SELECT copy #2 — article_content. Body + editor takeaways + the
911
+ // generated faq_json carry across so the duplicate keeps its SEO/FAQ work.
912
+ await db
913
+ .prepare(
914
+ `INSERT INTO article_content (content_id, body_markdown, body_html, word_count, read_time_minutes, subtitle, editor_takeaways, faq_json)
915
+ SELECT ?, body_markdown, body_html, word_count, read_time_minutes, subtitle, editor_takeaways, faq_json
916
+ FROM article_content WHERE content_id = ?`,
917
+ )
918
+ .bind(newId, sourceId)
919
+ .run()
920
+ } else if (source.type === 'video') {
921
+ // INSERT…SELECT copy #3 — video_content. processing_state is
922
+ // hard-coded to 'ready' so a duplicated video is NOT re-queued to
923
+ // Foundry. (inventory §6)
924
+ await db
925
+ .prepare(
926
+ `INSERT INTO video_content (content_id, script, video_id, duration_seconds, thumbnail_image_id, processing_state)
927
+ SELECT ?, script, video_id, duration_seconds, thumbnail_image_id, 'ready'
928
+ FROM video_content WHERE content_id = ?`,
929
+ )
930
+ .bind(newId, sourceId)
931
+ .run()
932
+ } else if (source.type === 'podcast') {
933
+ await db
934
+ .prepare(
935
+ `INSERT INTO podcast_content (content_id, transcript, audio_r2_key, duration_seconds)
936
+ SELECT ?, transcript, audio_r2_key, duration_seconds
937
+ FROM podcast_content WHERE content_id = ?`,
938
+ )
939
+ .bind(newId, sourceId)
940
+ .run()
941
+ }
942
+
943
+ // INSERT…SELECT copy #4 — content_tag_links (carry the same tags).
944
+ await db
945
+ .prepare(
946
+ `INSERT INTO content_tag_links (content_id, tag_id)
947
+ SELECT ?, tag_id FROM content_tag_links WHERE content_id = ?`,
948
+ )
949
+ .bind(newId, sourceId)
950
+ .run()
951
+
952
+ return { id: newId, slug: newSlug }
953
+ }
954
+
955
+ export async function createRevision(
956
+ db: D1Database,
957
+ contentId: string,
958
+ createdBy?: string | null,
959
+ ): Promise<void> {
960
+ const payload = await getContentSnapshot(db, contentId)
961
+ if (!payload) return
962
+ const revisionId = crypto.randomUUID()
963
+
964
+ await db
965
+ .prepare(
966
+ 'INSERT INTO content_revisions (id, content_id, payload_json, created_at, created_by) VALUES (?, ?, ?, unixepoch(), ?)',
967
+ )
968
+ .bind(revisionId, contentId, JSON.stringify(payload), createdBy || null)
969
+ .run()
970
+
971
+ await db
972
+ .prepare('UPDATE content_items SET published_revision_id = ? WHERE id = ?')
973
+ .bind(revisionId, contentId)
974
+ .run()
975
+ }
976
+
977
+ export async function getContentSnapshot(
978
+ db: D1Database,
979
+ contentId: string,
980
+ ): Promise<Record<string, unknown> | null> {
981
+ const base = await db
982
+ .prepare('SELECT * FROM content_items WHERE id = ? LIMIT 1')
983
+ .bind(contentId)
984
+ .first<Record<string, unknown>>()
985
+
986
+ if (!base) return null
987
+
988
+ const type = base.type as ContentType
989
+ let content: Record<string, unknown> | null = null
990
+
991
+ if (type === 'article' || type === 'newsletter') {
992
+ content = await db
993
+ .prepare('SELECT * FROM article_content WHERE content_id = ? LIMIT 1')
994
+ .bind(contentId)
995
+ .first<Record<string, unknown>>()
996
+ }
997
+
998
+ if (type === 'video') {
999
+ content = await db
1000
+ .prepare('SELECT * FROM video_content WHERE content_id = ? LIMIT 1')
1001
+ .bind(contentId)
1002
+ .first<Record<string, unknown>>()
1003
+ }
1004
+
1005
+ if (type === 'podcast') {
1006
+ content = await db
1007
+ .prepare('SELECT * FROM podcast_content WHERE content_id = ? LIMIT 1')
1008
+ .bind(contentId)
1009
+ .first<Record<string, unknown>>()
1010
+ }
1011
+
1012
+ const tags = await db
1013
+ .prepare(
1014
+ 'SELECT t.slug FROM content_tag_links l JOIN content_tags t ON t.id = l.tag_id WHERE l.content_id = ?',
1015
+ )
1016
+ .bind(contentId)
1017
+ .all<{ slug: string }>()
1018
+
1019
+ const related = await db
1020
+ .prepare(
1021
+ 'SELECT related_id, rank, reason FROM content_relations WHERE content_id = ? ORDER BY rank ASC',
1022
+ )
1023
+ .bind(contentId)
1024
+ .all<{ related_id: string; rank: number; reason: string | null }>()
1025
+
1026
+ return {
1027
+ item: base,
1028
+ content,
1029
+ tags: (tags.results || []).map((row) => row.slug),
1030
+ related: related.results || [],
1031
+ }
1032
+ }