create-nextblock 0.2.78 → 0.8.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 (413) hide show
  1. package/bin/create-nextblock.js +740 -459
  2. package/package.json +1 -2
  3. package/scripts/sync-template.js +18 -1
  4. package/templates/nextblock-template/.browserslistrc +11 -0
  5. package/templates/nextblock-template/.swcrc +30 -30
  6. package/templates/nextblock-template/README.md +23 -114
  7. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -28
  8. package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +50 -25
  9. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +111 -56
  10. package/templates/nextblock-template/app/(auth-pages)/two-factor/actions.ts +91 -0
  11. package/templates/nextblock-template/app/(auth-pages)/two-factor/components/TwoFactorForm.tsx +118 -0
  12. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -0
  13. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -0
  14. package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +48 -28
  15. package/templates/nextblock-template/app/[slug]/page.tsx +63 -6
  16. package/templates/nextblock-template/app/[slug]/page.utils.ts +374 -157
  17. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -0
  18. package/templates/nextblock-template/app/actions/consent.ts +57 -0
  19. package/templates/nextblock-template/app/actions/formActions.ts +130 -11
  20. package/templates/nextblock-template/app/actions/languageActions.ts +31 -30
  21. package/templates/nextblock-template/app/actions/package-actions.ts +183 -0
  22. package/templates/nextblock-template/app/actions/postActions.ts +146 -48
  23. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +21 -0
  24. package/templates/nextblock-template/app/actions/visualEditingActions.test.ts +179 -0
  25. package/templates/nextblock-template/app/actions/visualEditingActions.ts +345 -0
  26. package/templates/nextblock-template/app/actions.ts +67 -12
  27. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -0
  28. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -0
  29. package/templates/nextblock-template/app/api/ai/global-agent/route.ts +965 -0
  30. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -0
  31. package/templates/nextblock-template/app/api/checkout/route.ts +146 -0
  32. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -0
  33. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -0
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3413 -17
  35. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +7830 -0
  36. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +35 -0
  37. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -0
  38. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -0
  39. package/templates/nextblock-template/app/api/draft/disable/route.ts +25 -0
  40. package/templates/nextblock-template/app/api/draft/route.ts +93 -0
  41. package/templates/nextblock-template/app/api/draft/start/route.ts +77 -0
  42. package/templates/nextblock-template/app/api/media/library/route.ts +65 -0
  43. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -0
  44. package/templates/nextblock-template/app/api/media/record/route.ts +160 -0
  45. package/templates/nextblock-template/app/api/search/route.ts +43 -0
  46. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -0
  47. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -0
  48. package/templates/nextblock-template/app/api/webhooks/freemius/route.ts +34 -0
  49. package/templates/nextblock-template/app/api/webhooks/stripe/route.ts +27 -0
  50. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +392 -128
  51. package/templates/nextblock-template/app/article/[slug]/page.tsx +179 -127
  52. package/templates/nextblock-template/app/article/[slug]/page.utils.ts +262 -77
  53. package/templates/nextblock-template/app/auth/callback/route.ts +31 -58
  54. package/templates/nextblock-template/app/cart/page.tsx +7 -0
  55. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -0
  56. package/templates/nextblock-template/app/checkout/page.tsx +52 -0
  57. package/templates/nextblock-template/app/checkout/success/actions.ts +136 -0
  58. package/templates/nextblock-template/app/checkout/success/page.tsx +186 -0
  59. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +163 -33
  60. package/templates/nextblock-template/app/cms/blocks/actions.ts +424 -235
  61. package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +212 -151
  62. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +41 -20
  63. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +152 -19
  64. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +25 -17
  65. package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +200 -18
  66. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +33 -16
  67. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -0
  68. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +37 -18
  69. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -67
  70. package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +108 -31
  71. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -0
  72. package/templates/nextblock-template/app/cms/blocks/editors/FeaturedProductBlockEditor.tsx +31 -0
  73. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +2 -2
  74. package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +1 -1
  75. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +29 -29
  76. package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +14 -18
  77. package/templates/nextblock-template/app/cms/blocks/editors/ProductGridBlockEditor.tsx +41 -0
  78. package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +318 -118
  79. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +98 -21
  80. package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +1 -1
  81. package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +27 -9
  82. package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +1 -1
  83. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -0
  84. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -0
  85. package/templates/nextblock-template/app/cms/components/CortexGlobalAgentChat.tsx +1507 -0
  86. package/templates/nextblock-template/app/cms/components/DraftStatusActions.tsx +145 -0
  87. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +244 -0
  88. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +38 -24
  89. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -0
  90. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -0
  91. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -0
  92. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -0
  93. package/templates/nextblock-template/app/cms/custom-blocks/components/BlockComposer.tsx +1522 -0
  94. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -0
  95. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -0
  96. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -0
  97. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -0
  98. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -0
  99. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -98
  100. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -0
  101. package/templates/nextblock-template/app/cms/dashboard/page.tsx +182 -154
  102. package/templates/nextblock-template/app/cms/import-export/ContentTransferControls.tsx +391 -0
  103. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -0
  104. package/templates/nextblock-template/app/cms/layout.tsx +29 -10
  105. package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -22
  106. package/templates/nextblock-template/app/cms/media/actions.ts +45 -124
  107. package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +1 -1
  108. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  109. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -64
  110. package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +227 -158
  111. package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +101 -89
  112. package/templates/nextblock-template/app/cms/media/page.tsx +1 -1
  113. package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +2 -2
  114. package/templates/nextblock-template/app/cms/orders/[id]/MarkPaidButton.tsx +44 -0
  115. package/templates/nextblock-template/app/cms/orders/[id]/page.tsx +16 -0
  116. package/templates/nextblock-template/app/cms/orders/actions.ts +201 -0
  117. package/templates/nextblock-template/app/cms/orders/page.tsx +20 -0
  118. package/templates/nextblock-template/app/cms/orders/types.ts +20 -0
  119. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +156 -121
  120. package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -26
  121. package/templates/nextblock-template/app/cms/pages/actions.ts +54 -38
  122. package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +1 -1
  123. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +267 -116
  124. package/templates/nextblock-template/app/cms/pages/page.tsx +25 -18
  125. package/templates/nextblock-template/app/cms/payments/page.tsx +16 -0
  126. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +132 -90
  127. package/templates/nextblock-template/app/cms/posts/actions.ts +71 -72
  128. package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +1 -1
  129. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +256 -245
  130. package/templates/nextblock-template/app/cms/posts/new/page.tsx +1 -1
  131. package/templates/nextblock-template/app/cms/posts/page.tsx +20 -13
  132. package/templates/nextblock-template/app/cms/products/ClientNotionEditor.tsx +16 -0
  133. package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +56 -0
  134. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +292 -0
  135. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -0
  136. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -0
  137. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -0
  138. package/templates/nextblock-template/app/cms/products/new/page.tsx +143 -0
  139. package/templates/nextblock-template/app/cms/products/page.tsx +42 -0
  140. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -0
  141. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -0
  142. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -0
  143. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -0
  144. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -0
  145. package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +2 -2
  146. package/templates/nextblock-template/app/cms/revisions/actions.ts +285 -285
  147. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -16
  148. package/templates/nextblock-template/app/cms/revisions/utils.ts +8 -3
  149. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -0
  150. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -0
  151. package/templates/nextblock-template/app/cms/settings/bot-protection/actions.ts +93 -0
  152. package/templates/nextblock-template/app/cms/settings/bot-protection/components/BotProtectionForm.tsx +129 -0
  153. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -0
  154. package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +1 -1
  155. package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +2 -2
  156. package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +1 -1
  157. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +496 -0
  158. package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +410 -0
  159. package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +248 -0
  160. package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +80 -0
  161. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -0
  162. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -0
  163. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -0
  164. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +203 -44
  165. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -242
  166. package/templates/nextblock-template/app/cms/settings/global-css/actions.ts +65 -0
  167. package/templates/nextblock-template/app/cms/settings/global-css/components/GlobalCssForm.tsx +46 -0
  168. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +24 -0
  169. package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +1 -1
  170. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +2 -2
  171. package/templates/nextblock-template/app/cms/settings/languages/page.tsx +1 -1
  172. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  173. package/templates/nextblock-template/app/cms/settings/logos/actions.ts +82 -6
  174. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -0
  175. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -18
  176. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -16
  177. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -0
  178. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  179. package/templates/nextblock-template/app/cms/settings/logos/page.tsx +120 -82
  180. package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -8
  181. package/templates/nextblock-template/app/cms/settings/packages/activation-form.tsx +84 -0
  182. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -0
  183. package/templates/nextblock-template/app/cms/settings/packages/page.tsx +49 -0
  184. package/templates/nextblock-template/app/cms/settings/privacy/actions.ts +53 -0
  185. package/templates/nextblock-template/app/cms/settings/privacy/components/PrivacyForm.tsx +196 -0
  186. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +26 -0
  187. package/templates/nextblock-template/app/cms/settings/security/actions.ts +251 -0
  188. package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +453 -0
  189. package/templates/nextblock-template/app/cms/settings/security/page.tsx +13 -0
  190. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -0
  191. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -0
  192. package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +28 -23
  193. package/templates/nextblock-template/app/cms/users/actions.ts +105 -40
  194. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +1 -1
  195. package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +65 -152
  196. package/templates/nextblock-template/app/cms/users/page.tsx +15 -10
  197. package/templates/nextblock-template/app/globals.css +9 -0
  198. package/templates/nextblock-template/app/layout.tsx +372 -120
  199. package/templates/nextblock-template/app/lib/seo.test.ts +52 -0
  200. package/templates/nextblock-template/app/lib/seo.ts +279 -0
  201. package/templates/nextblock-template/app/lib/site-settings.ts +87 -0
  202. package/templates/nextblock-template/app/lib/sitemap-utils.ts +224 -39
  203. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -0
  204. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -0
  205. package/templates/nextblock-template/app/lib/ucp/server.ts +1914 -0
  206. package/templates/nextblock-template/app/page.tsx +165 -73
  207. package/templates/nextblock-template/app/product/[slug]/page.tsx +433 -0
  208. package/templates/nextblock-template/app/profile/ProfileAccountSidebar.tsx +73 -0
  209. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -0
  210. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -0
  211. package/templates/nextblock-template/app/profile/account-data.ts +37 -0
  212. package/templates/nextblock-template/app/profile/account-links.ts +22 -0
  213. package/templates/nextblock-template/app/profile/account-types.ts +11 -0
  214. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -0
  215. package/templates/nextblock-template/app/profile/orders/[id]/CustomerOrderDetailPageClient.tsx +79 -0
  216. package/templates/nextblock-template/app/profile/orders/[id]/page.tsx +32 -0
  217. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -0
  218. package/templates/nextblock-template/app/profile/page.tsx +51 -0
  219. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -0
  220. package/templates/nextblock-template/app/profile/password/actions.ts +59 -0
  221. package/templates/nextblock-template/app/profile/password/page.tsx +27 -0
  222. package/templates/nextblock-template/app/providers.tsx +55 -17
  223. package/templates/nextblock-template/app/robots.txt/route.ts +11 -1
  224. package/templates/nextblock-template/app/sitemap.ts +128 -0
  225. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -0
  226. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -0
  227. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -0
  228. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -0
  229. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -0
  230. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -0
  231. package/templates/nextblock-template/components/AppShell.tsx +154 -0
  232. package/templates/nextblock-template/components/BlockRenderer.tsx +210 -64
  233. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -0
  234. package/templates/nextblock-template/components/CartTranslator.tsx +210 -0
  235. package/templates/nextblock-template/components/CurrentContentSetter.tsx +25 -0
  236. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -0
  237. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -0
  238. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -0
  239. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -0
  240. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -0
  241. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -0
  242. package/templates/nextblock-template/components/GitHubLoginButton.tsx +36 -0
  243. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -0
  244. package/templates/nextblock-template/components/Header.tsx +49 -41
  245. package/templates/nextblock-template/components/LanguageSwitcher.tsx +55 -32
  246. package/templates/nextblock-template/components/ResponsiveNav.tsx +138 -43
  247. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -8
  248. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -55
  249. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +42 -37
  250. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +6 -2
  251. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -0
  252. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -0
  253. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -84
  254. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +17 -0
  255. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +19 -0
  256. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +262 -8
  257. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +22 -0
  258. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +320 -37
  259. package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +11 -8
  260. package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +12 -3
  261. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +18 -13
  262. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +90 -0
  263. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +31 -0
  264. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +424 -55
  265. package/templates/nextblock-template/components/blocks/renderers/SectionSlider.tsx +137 -0
  266. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -0
  267. package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +37 -22
  268. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +23 -15
  269. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +1 -3
  270. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +1 -3
  271. package/templates/nextblock-template/components/blocks/types.ts +7 -6
  272. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  273. package/templates/nextblock-template/components/form-message.tsx +32 -26
  274. package/templates/nextblock-template/components/header-auth.tsx +69 -17
  275. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +127 -0
  276. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +59 -0
  277. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -0
  278. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -0
  279. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +464 -0
  280. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  281. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -0
  282. package/templates/nextblock-template/components/visual-editing/NextblockVisualEditing.tsx +1172 -0
  283. package/templates/nextblock-template/context/AuthContext.tsx +23 -90
  284. package/templates/nextblock-template/context/CurrentContentContext.tsx +10 -4
  285. package/templates/nextblock-template/context/LanguageContext.tsx +16 -16
  286. package/templates/nextblock-template/context/language-rest-client.ts +31 -0
  287. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -0
  288. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -0
  289. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -0
  290. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +252 -0
  291. package/templates/nextblock-template/docs/05-DEVELOPER-GUIDE.md +238 -0
  292. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +125 -0
  293. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -0
  294. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +1319 -0
  295. package/templates/nextblock-template/docs/09-LIVE-DRAFT-MODE.md +104 -0
  296. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -0
  297. package/templates/nextblock-template/docs/README.md +34 -0
  298. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12507 -0
  299. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -14
  300. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -0
  301. package/templates/nextblock-template/index.d.ts +2 -0
  302. package/templates/nextblock-template/lib/ai-block-generation.ts +339 -0
  303. package/templates/nextblock-template/lib/ai-client.ts +247 -0
  304. package/templates/nextblock-template/lib/ai-config.ts +81 -0
  305. package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +125 -0
  306. package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +363 -0
  307. package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +405 -0
  308. package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +1228 -0
  309. package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +5 -0
  310. package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +223 -0
  311. package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +2183 -0
  312. package/templates/nextblock-template/lib/ai-global-agent-tools.ts +4807 -0
  313. package/templates/nextblock-template/lib/ai-key-crypto.test.ts +70 -0
  314. package/templates/nextblock-template/lib/ai-key-crypto.ts +132 -0
  315. package/templates/nextblock-template/lib/ai-model-catalog.test.ts +49 -0
  316. package/templates/nextblock-template/lib/ai-model-catalog.ts +41 -0
  317. package/templates/nextblock-template/lib/ai-model-registry.test.ts +231 -0
  318. package/templates/nextblock-template/lib/ai-model-registry.ts +522 -0
  319. package/templates/nextblock-template/lib/auth/cookies.ts +47 -0
  320. package/templates/nextblock-template/lib/auth/crypto.ts +42 -0
  321. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -0
  322. package/templates/nextblock-template/lib/auth/twoFactor.ts +167 -0
  323. package/templates/nextblock-template/lib/auth-redirects.ts +46 -0
  324. package/templates/nextblock-template/lib/blocks/FeaturedProductBlock.tsx +94 -0
  325. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +137 -0
  326. package/templates/nextblock-template/lib/blocks/README.md +13 -670
  327. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +138 -56
  328. package/templates/nextblock-template/lib/blocks/blockTypes.ts +18 -0
  329. package/templates/nextblock-template/lib/blocks/ecommerce-block-schemas.ts +31 -0
  330. package/templates/nextblock-template/lib/cms-transfer/csv.test.ts +77 -0
  331. package/templates/nextblock-template/lib/cms-transfer/csv.ts +399 -0
  332. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -0
  333. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -0
  334. package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +199 -0
  335. package/templates/nextblock-template/lib/cortex-widget-registry.ts +88 -0
  336. package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +237 -0
  337. package/templates/nextblock-template/lib/cortex-widget-schema.ts +393 -0
  338. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -0
  339. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -0
  340. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -0
  341. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +68 -0
  342. package/templates/nextblock-template/lib/custom-block-relation-registry.ts +256 -0
  343. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -0
  344. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -0
  345. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -0
  346. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -0
  347. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -0
  348. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -0
  349. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -0
  350. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -0
  351. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -0
  352. package/templates/nextblock-template/lib/full-backup/server.ts +743 -0
  353. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +45 -0
  354. package/templates/nextblock-template/lib/posts/readTime.ts +60 -0
  355. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -0
  356. package/templates/nextblock-template/lib/privacy/settings.ts +103 -0
  357. package/templates/nextblock-template/lib/privacy/types.ts +67 -0
  358. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -0
  359. package/templates/nextblock-template/lib/promotions/server.ts +741 -0
  360. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -0
  361. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -0
  362. package/templates/nextblock-template/lib/search/server.ts +585 -0
  363. package/templates/nextblock-template/lib/search/types.ts +27 -0
  364. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -0
  365. package/templates/nextblock-template/lib/visual-editing/draft-content.ts +380 -0
  366. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -0
  367. package/templates/nextblock-template/lib/visual-editing/draft-route.ts +82 -0
  368. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -0
  369. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -0
  370. package/templates/nextblock-template/lib/visual-editing/mutations.ts +190 -0
  371. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -0
  372. package/templates/nextblock-template/lib/visual-editing/product-drafts.ts +511 -0
  373. package/templates/nextblock-template/lib/visual-editing/types.ts +122 -0
  374. package/templates/nextblock-template/lib/zod-config.ts +5 -0
  375. package/templates/nextblock-template/next.config.js +190 -66
  376. package/templates/nextblock-template/package.json +34 -30
  377. package/templates/nextblock-template/proxy.ts +435 -253
  378. package/templates/nextblock-template/public/images/NBcover.webp +0 -0
  379. package/templates/nextblock-template/public/images/cap.webp +0 -0
  380. package/templates/nextblock-template/public/images/commerce-plan.webp +0 -0
  381. package/templates/nextblock-template/public/images/commerce-square.webp +0 -0
  382. package/templates/nextblock-template/public/images/commerce-wide.webp +0 -0
  383. package/templates/nextblock-template/public/images/cortex-ai-square.webp +0 -0
  384. package/templates/nextblock-template/public/images/cortex-ai.webp +0 -0
  385. package/templates/nextblock-template/public/images/extensibility.webp +0 -0
  386. package/templates/nextblock-template/public/images/goals.webp +0 -0
  387. package/templates/nextblock-template/public/images/included.webp +0 -0
  388. package/templates/nextblock-template/public/images/nx-graph.webp +0 -0
  389. package/templates/nextblock-template/public/images/pants.webp +0 -0
  390. package/templates/nextblock-template/public/images/t-shirt.webp +0 -0
  391. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -0
  392. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +100 -0
  393. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -0
  394. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -0
  395. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -0
  396. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -0
  397. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -0
  398. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -0
  399. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -0
  400. package/templates/nextblock-template/tailwind.config.js +1 -0
  401. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -0
  402. package/templates/nextblock-template/tools/deploy-supabase.js +69 -71
  403. package/templates/nextblock-template/tsconfig.json +52 -66
  404. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  405. package/templates/nextblock-template/types/jsdom.d.ts +6 -0
  406. package/templates/nextblock-template/app/force-styles.tsx +0 -31
  407. package/templates/nextblock-template/app/sitemap.xml/route.ts +0 -63
  408. package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +0 -273
  409. package/templates/nextblock-template/docs/How to Create a Custom Block.md +0 -149
  410. package/templates/nextblock-template/docs/cms-application-overview.md +0 -56
  411. package/templates/nextblock-template/docs/cms-architecture-overview.md +0 -73
  412. package/templates/nextblock-template/docs/files-structure.md +0 -426
  413. package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +0 -174
@@ -0,0 +1,1319 @@
1
+ # NextBlock Cortex AI Architecture
2
+
3
+ This document is a handoff and maintenance guide for the current NextBlock Cortex AI implementation. It is intended for future developers and for AI coding agents that need high-fidelity context in a new thread.
4
+
5
+ Do not copy real API keys, Freemius license keys, encryption secrets, Supabase service keys, or other secret values into this document or into prompts. Only environment variable names are documented here.
6
+
7
+ ## Executive Summary
8
+
9
+ NextBlock Cortex AI is the premium AI package for NextBlock. Its internal package id is:
10
+
11
+ ```txt
12
+ cortex-ai
13
+ ```
14
+
15
+ The package currently implements three major capabilities:
16
+
17
+ 1. Premium package activation and BYOK key management.
18
+ 2. OpenRouter-backed model routing with free-model fallback behavior.
19
+ 3. AI features inside the CMS:
20
+ - Inline Tiptap rich-text assistance that generates clean HTML fragments and lets the editor parse them normally.
21
+ - A page-aware global dashboard agent that can update navigation/footer state, search CMS documentation-like content, and mutate current page/post/product fields or blocks through typed tools.
22
+
23
+ The implementation now splits editing responsibilities:
24
+
25
+ - The inline editor path is HTML-first and intentionally lightweight. It is for rich-text fragments inside the active Tiptap document, not full CMS block generation.
26
+ - Full page, product, post, section, and block editing goes through the global agent and strict Zod tool arguments.
27
+ - Existing editor JSON schemas remain important for stored product `description_json`, schema diagnostics, and global-agent field validation.
28
+ - Server-side key handling is isolated in server-only modules.
29
+ - Database writes happen through authenticated server actions or service-role Supabase calls, not client-side mutation.
30
+
31
+ ## Current Status
32
+
33
+ Implemented:
34
+
35
+ - Package registry entry for `cortex-ai`.
36
+ - Freemius product/plan metadata:
37
+ - `fm_product_id`: `28609`
38
+ - `fm_plan_id`: `47122`
39
+ - Sandbox reset auto-activates Cortex AI when `FREEMIUS_AI_SANDBOX_KEY` is present.
40
+ - Encrypted OpenRouter BYOK storage in `site_settings`.
41
+ - RLS hardening so `site_settings.key = 'cortex_ai_openrouter_api_key'` is not publicly readable.
42
+ - Cortex AI settings page under `/cms/settings/cortex-ai`.
43
+ - OpenRouter client, free-model fallback registry, and stored-BYOK paid model selection.
44
+ - Tiptap editor JSON schemas and schema-to-JSON-schema helper.
45
+ - `/api/ai/generate-blocks` endpoint for inline HTML fragment generation.
46
+ - Editor prompt UI in `NotionEditor`.
47
+ - `/api/ai/global-agent` endpoint with page context, tool calling, and SSE streaming.
48
+ - Persistent dashboard chat UI with local browser chat threads.
49
+ - Tools for:
50
+ - `update_navigation_bar`
51
+ - `update_footer`
52
+ - `search_documentation`
53
+ - `read_current_cms_item`
54
+ - `update_current_cms_fields`
55
+ - `update_content_block`
56
+ - `update_section_column_block`
57
+ - `fetch_ecommerce_stats`
58
+ - Multilingual navigation/footer tool arguments using either language codes or language names.
59
+ - Guardrails against OpenRouter free-model rate limits, raw tool-call leakage, and stuck loading streams.
60
+ - Custom-block "build widget" generation: `/api/ai/cortex/build-widget` and the custom-block agent tools (`apps/nextblock/lib/ai-global-agent-custom-block-tools.ts`) produce data-driven `custom_block_definitions` from a prompt. See [10-CUSTOM-BLOCKS.md](./10-CUSTOM-BLOCKS.md) for the block model.
61
+
62
+ Known incomplete or future work:
63
+
64
+ - Footer link updates currently replace footer links for the selected locale. Footer append mode is not yet implemented.
65
+ - Documentation search is keyword/scored search over `posts` and `pages`, not a vector embedding RAG system yet.
66
+ - The sandbox should eventually seed a visible product/package item for Cortex AI, similar to ecommerce. The preferred image asset is `apps/nextblock/public/images/cortex-ai-square.webp`.
67
+ - Block insertion is intentionally left for a follow-up pass with explicit idempotency keys.
68
+
69
+ ## Important Files
70
+
71
+ ### Package and Environment
72
+
73
+ | File | Purpose |
74
+ | --- | --- |
75
+ | `libs/utils/src/lib/nextblock-packages.ts` | Package registry. Contains `cortex-ai` metadata and Freemius product/plan ids. |
76
+ | `apps/nextblock/lib/ai-config.ts` | Server-only Cortex AI constants and environment accessors. |
77
+ | `apps/nextblock/lib/ai-key-crypto.ts` | AES-256-GCM encryption/decryption helpers for stored OpenRouter BYOK keys. |
78
+ | `.env.exemple` | Documents `FREEMIUS_AI_SANDBOX_KEY`, `OPENROUTER_API_KEY`, and `CORTEX_AI_ENCRYPTION_KEY`. |
79
+ | `libs/environment.d.ts` | Type declarations for Cortex AI environment variables. |
80
+
81
+ ### Database and Sandbox
82
+
83
+ | File | Purpose |
84
+ | --- | --- |
85
+ | `libs/db/src/supabase/migrations/00000000000011_setup_cortex_ai_settings.sql` | RLS hardening for the sensitive `site_settings` Cortex AI key row. |
86
+ | `apps/nextblock/app/api/cron/reset-sandbox/route.ts` | Sandbox reset route. Upserts active package activation for `cortex-ai` when `FREEMIUS_AI_SANDBOX_KEY` exists. |
87
+ | `apps/nextblock/app/api/cron/reset-sandbox/sandboxResetSql.ts` | Generated SQL bundle that includes the Cortex AI migration. |
88
+
89
+ ### Routing and OpenRouter
90
+
91
+ | File | Purpose |
92
+ | --- | --- |
93
+ | `apps/nextblock/lib/ai-client.ts` | Creates OpenRouter provider/client with credential resolution and text-generation helper. |
94
+ | `apps/nextblock/lib/ai-model-catalog.ts` | Server-only OpenRouter model catalog fetcher. |
95
+ | `apps/nextblock/lib/ai-model-registry.ts` | Free model registry, routing policy builder, model filtering/parsing helpers, rate-limit detection, fallback runner. |
96
+ | `apps/nextblock/scripts/verify-cortex-ai-routing.ts` | Manual verification script for OpenRouter routing. |
97
+
98
+ ### Inline Editor Assistance
99
+
100
+ | File | Purpose |
101
+ | --- | --- |
102
+ | `libs/utils/src/lib/editor-blocks.ts` | Main Tiptap JSON Zod schemas, allowed node/mark types, JSON Schema extraction. Still used for product descriptions and agent validation. |
103
+ | `schemas/editor-blocks.ts` | Re-export shim for schema imports from app scripts/lib code. |
104
+ | `apps/nextblock/lib/ai-block-generation.ts` | Inline editor HTML-fragment generation using `generateText`, routing fallback, and lightweight output validation. |
105
+ | `apps/nextblock/app/api/ai/generate-blocks/route.ts` | Compatibility route for inline editor generation. Returns `{ html, credentialSource, modelId }`. |
106
+ | `libs/editor/src/lib/NotionEditor.tsx` | Editor prompt UI and HTML insertion behavior via normal Tiptap parsing. |
107
+ | `apps/nextblock/scripts/validate-editor-block-schema.ts` | Validates editor schema against sample content and emits diagnostics. |
108
+ | `apps/nextblock/scripts/verify-cortex-ai-generate-blocks.ts` | Manual live generation verification script. |
109
+
110
+ ### Global Agent
111
+
112
+ | File | Purpose |
113
+ | --- | --- |
114
+ | `apps/nextblock/lib/ai-global-agent-tools.ts` | Tool schemas and execution functions. |
115
+ | `apps/nextblock/app/api/ai/global-agent/route.ts` | Global agent route and SSE streaming orchestration. |
116
+ | `apps/nextblock/app/cms/components/CortexGlobalAgentChat.tsx` | Persistent dashboard chat UI with thread history. |
117
+ | `apps/nextblock/app/cms/components/CortexAiPageContext.tsx` | Client page-context provider/registrar used by CMS edit screens and the global chat. |
118
+ | `apps/nextblock/lib/ai-global-agent-tools.test.ts` | Unit tests for tool executors. |
119
+ | `apps/nextblock/scripts/verify-cortex-ai-global-tools.ts` | Focused verifier for global tools. |
120
+
121
+ ### CMS Integration
122
+
123
+ | File | Purpose |
124
+ | --- | --- |
125
+ | `apps/nextblock/app/cms/layout.tsx` | Server layout checks package activation for ecommerce and Cortex AI. |
126
+ | `apps/nextblock/app/cms/CmsClientLayout.tsx` | Adds Cortex AI settings nav item, wraps CMS in the page-context provider, and conditionally renders global chat. |
127
+ | `apps/nextblock/app/cms/settings/cortex-ai/page.tsx` | Settings page for activation/key status, BYOK forms, and compatible model selection. |
128
+ | `apps/nextblock/app/cms/settings/cortex-ai/actions.ts` | Server actions for reading, saving, and clearing BYOK keys and model selections. |
129
+ | `apps/nextblock/app/cms/dashboard/actions.ts` | Dashboard package state; checks `cortex-ai` to hide/show AI premium CTA. |
130
+ | `apps/nextblock/components/Header.tsx` and `apps/nextblock/components/ResponsiveNav.tsx` | Hydration-safe public header controls after Radix ID mismatch fixes. |
131
+ | `apps/nextblock/app/cms/components/FeedbackModal.tsx` | Hydration-safe feedback dialog trigger. |
132
+
133
+ ## Package Activation
134
+
135
+ The package id is `cortex-ai`. Do not use the old id `ai`.
136
+
137
+ The package registry entry lives in `libs/utils/src/lib/nextblock-packages.ts`:
138
+
139
+ ```ts
140
+ 'cortex-ai': {
141
+ id: 'cortex-ai',
142
+ name: 'NextBlock Cortex AI',
143
+ description: 'Native JSONB block generation and OpenRouter integration.',
144
+ fm_product_id: '28609',
145
+ fm_plan_id: '47122',
146
+ purchase_url: 'https://nextblock.dev',
147
+ }
148
+ ```
149
+
150
+ Activation checks use:
151
+
152
+ ```ts
153
+ verifyPackageOnline('cortex-ai')
154
+ ```
155
+
156
+ Current usage:
157
+
158
+ - CMS layout gates the chat with `verifyPackageOnline('cortex-ai')`.
159
+ - Settings page reports package active/inactive.
160
+ - Global agent route rejects requests if Cortex AI is inactive.
161
+ - Dashboard premium CTA checks `stats.isAiActive`, now derived from active package id `cortex-ai`.
162
+
163
+ ## Environment Variables
164
+
165
+ Environment variables are documented in `.env.exemple` and typed in `libs/environment.d.ts`.
166
+
167
+ ```txt
168
+ FREEMIUS_AI_SANDBOX_KEY=
169
+ OPENROUTER_API_KEY=
170
+ CORTEX_AI_ENCRYPTION_KEY=
171
+ ```
172
+
173
+ ### FREEMIUS_AI_SANDBOX_KEY
174
+
175
+ Used only for sandbox activation.
176
+
177
+ If present during sandbox reset, `apps/nextblock/app/api/cron/reset-sandbox/route.ts` upserts an active `package_activations` row:
178
+
179
+ ```txt
180
+ package_id = cortex-ai
181
+ license_key = FREEMIUS_AI_SANDBOX_KEY
182
+ status = active
183
+ ```
184
+
185
+ The upsert uses `onConflict: 'license_key, package_id'` to avoid duplicate reset failures.
186
+
187
+ ### OPENROUTER_API_KEY
188
+
189
+ Server-side OpenRouter key used for sandbox-safe free-model routing when no stored BYOK exists.
190
+
191
+ Credential priority is:
192
+
193
+ 1. Manual API key passed to helper functions, used mainly in tests/scripts.
194
+ 2. Encrypted key stored in `site_settings`.
195
+ 3. `OPENROUTER_API_KEY`.
196
+ 4. No credential, which throws an error.
197
+
198
+ Stored BYOK intentionally takes precedence over `OPENROUTER_API_KEY`. This lets admins keep a sandbox/free environment key in `.env.local` while enabling paid compatible model selection only after they save a stored BYOK in the CMS.
199
+
200
+ Important: `openrouter/free` is a free model-router id, not a replacement for authentication. The app still needs an OpenRouter API key from either the environment or stored BYOK.
201
+
202
+ When the active credential source is `env`, Cortex AI always routes through exactly the configured `CORTEX_AI_FREE_MODEL_FALLBACK_REGISTRY` list. Non-free explicit model requests are ignored for env-only routing.
203
+
204
+ **Sandbox Behavior:** When `NEXT_PUBLIC_IS_SANDBOX=true`, the server environments won't save any OpenRouter key or model selection to the database. The user will be requested to input a key that is stored purely in their browser's `localStorage` (`cortex_ai_sandbox_openrouter_api_key`) with browser-local model selection (`cortex_ai_sandbox_openrouter_model_selection`). The inline editor and global chat pass those values as request headers (`x-sandbox-openrouter-key` and `x-sandbox-openrouter-model`) for the user's own request only.
205
+
206
+ OpenRouter free models can still hit free-model rate limits. A user with no credits or no credit card can see errors like `free-models-per-day`. Cortex AI catches these where possible and falls back to configured alternate models, but OpenRouter account-level limits may still block all free requests.
207
+
208
+ ### CORTEX_AI_ENCRYPTION_KEY
209
+
210
+ Required only for saving/decrypting DB-stored BYOK keys.
211
+
212
+ Implementation detail:
213
+
214
+ - `apps/nextblock/lib/ai-key-crypto.ts` hashes this secret with SHA-256 to derive a 32-byte AES key.
215
+ - Stored keys use AES-256-GCM with a 12-byte random IV and auth tag.
216
+ - Changing this value invalidates previously encrypted stored keys.
217
+
218
+ Recommended value shape:
219
+
220
+ - Long random string.
221
+ - At least 32 characters.
222
+ - Do not commit it.
223
+ - Keep the same value for an environment as long as stored keys need to remain decryptable.
224
+
225
+ ## BYOK Storage and RLS
226
+
227
+ Stored OpenRouter keys are saved in:
228
+
229
+ ```txt
230
+ public.site_settings.key = cortex_ai_openrouter_api_key
231
+ ```
232
+
233
+ The value is a JSON envelope:
234
+
235
+ ```ts
236
+ {
237
+ algorithm: 'aes-256-gcm',
238
+ authTag: string,
239
+ ciphertext: string,
240
+ iv: string,
241
+ last4: string,
242
+ updatedAt: string,
243
+ version: 1
244
+ }
245
+ ```
246
+
247
+ The migration `00000000000011_setup_cortex_ai_settings.sql` hardens RLS:
248
+
249
+ - Public users can read non-sensitive site settings.
250
+ - The sensitive Cortex AI key row is readable only by authenticated admins.
251
+ - The sensitive row is writable/deletable only by authenticated admins.
252
+ - Existing non-sensitive site settings remain writable by current `ADMIN`/`WRITER` policy.
253
+
254
+ Stored model selection is saved separately in:
255
+
256
+ ```txt
257
+ public.site_settings.key = cortex_ai_openrouter_model_selection
258
+ ```
259
+
260
+ The value is not secret and uses this shape:
261
+
262
+ ```ts
263
+ {
264
+ modelId: string,
265
+ name: string,
266
+ supportedParameters: string[],
267
+ pricing: Record<string, string>,
268
+ contextLength: number | null,
269
+ updatedAt: string
270
+ }
271
+ ```
272
+
273
+ The selection is only honored when a stored BYOK exists. Clearing the stored BYOK also clears the selected model. Model selection does not require a database migration because `site_settings` is already the platform key/value store and this row is non-sensitive.
274
+
275
+ Settings UI behavior:
276
+
277
+ - Page: `/cms/settings/cortex-ai`.
278
+ - Server actions re-check authenticated user role as `ADMIN`.
279
+ - Stored BYOK is never displayed in plaintext.
280
+ - The UI only shows masked `**** last4` status.
281
+ - If only `OPENROUTER_API_KEY` exists, UI states that env routing is locked to the three free models.
282
+ - If stored BYOK exists, UI fetches compatible OpenRouter text models that support `tools` and `structured_outputs`, then allows an admin to save one selected model.
283
+ - If `NEXT_PUBLIC_IS_SANDBOX=true`, the UI uses a client component to save keys and model selection purely to `localStorage`, and bypasses the database to prevent accidental key leaks across a shared sandbox environment.
284
+
285
+ ## OpenRouter Client Architecture
286
+
287
+ The OpenRouter client is implemented in `apps/nextblock/lib/ai-client.ts`.
288
+
289
+ It uses:
290
+
291
+ ```ts
292
+ createOpenAICompatible
293
+ ```
294
+
295
+ from `@ai-sdk/openai-compatible`, with:
296
+
297
+ ```txt
298
+ baseURL = https://openrouter.ai/api/v1
299
+ name = openrouter
300
+ supportsStructuredOutputs = true
301
+ includeUsage = true
302
+ ```
303
+
304
+ Custom OpenRouter headers:
305
+
306
+ ```txt
307
+ HTTP-Referer = NEXT_PUBLIC_URL or https://nextblock.dev
308
+ X-Title = NextBlock Cortex AI
309
+ ```
310
+
311
+ Credential resolution is:
312
+
313
+ ```txt
314
+ manual -> stored BYOK -> OPENROUTER_API_KEY -> none
315
+ ```
316
+
317
+ When the resolved source is `stored`, the client also reads `cortex_ai_openrouter_model_selection` and exposes it to the routing policy. When the source is `env`, model selection is ignored and the policy locks requests to the free registry.
318
+
319
+ All AI client/config modules intentionally throw if imported into browser code:
320
+
321
+ ```ts
322
+ if (typeof window !== 'undefined') {
323
+ throw new Error(...)
324
+ }
325
+ ```
326
+
327
+ This prevents accidental client-side exposure of secrets.
328
+
329
+ ## Model Registry and Fallback
330
+
331
+ The model registry lives in `apps/nextblock/lib/ai-model-registry.ts`.
332
+
333
+ Default free router constant:
334
+
335
+ ```txt
336
+ openrouter/free
337
+ ```
338
+
339
+ This constant is retained for compatibility, but Cortex AI's preferred generation and agent model chains use explicit free models that advertise both `structured_outputs` and tool-calling support.
340
+
341
+ Configured all-purpose free fallbacks:
342
+
343
+ ```txt
344
+ qwen/qwen3-next-80b-a3b-instruct:free
345
+ nvidia/nemotron-3-super-120b-a12b:free
346
+ nvidia/nemotron-nano-9b-v2:free
347
+ ```
348
+
349
+ Registries:
350
+
351
+ - `structuredJsonPreferred`: retained for compatibility with older structured-generation code paths and schema diagnostics.
352
+ - `toolCallingPreferred`: retained as the global-agent free default list.
353
+
354
+ Both registries intentionally use the same model list. The inline editor no longer depends on model-native structured JSON output, but current paid model selection still requires `tools` and `structured_outputs` because the global agent needs tool calling and existing schema utilities still validate structured editor documents.
355
+
356
+ Paid model selection:
357
+
358
+ - `CORTEX_AI_REQUIRED_MODEL_PARAMETERS = ['tools', 'structured_outputs']`.
359
+ - `apps/nextblock/lib/ai-model-catalog.ts` fetches `https://openrouter.ai/api/v1/models?supported_parameters=tools,structured_outputs&output_modalities=text`.
360
+ - Catalog filtering keeps only non-expired text-output models that advertise all required parameters.
361
+ - `buildCortexAiRoutingPolicy` is the single policy entrypoint for inline editor generation, global agent routing, and shared text generation.
362
+ - Env-key routing always returns exactly the free fallback registry.
363
+ - Stored-BYOK routing returns `[selectedModel, ...freeFallbacks]` when a selected compatible model exists, otherwise it returns the free fallback registry.
364
+ - Manual-key routing can use a requested model id, mainly for tests and scripts.
365
+ - Optional request parameters such as `temperature` are stripped for the selected stored model if its saved `supportedParameters` metadata does not include that parameter.
366
+
367
+ Fallback behavior:
368
+
369
+ - `runWithCortexAiModelFallback` deduplicates model ids.
370
+ - Default retry condition is OpenRouter HTTP 429.
371
+ - Inline HTML generation overrides the retry predicate to also retry recoverable empty/invalid fragment, provider, timeout, and 5xx errors.
372
+ - 401/402/403 are treated as non-recoverable for inline generation.
373
+ - Routing errors are summarized for the UI from first/last real model attempt messages while full per-model attempts stay in server logs.
374
+
375
+ Rate-limit detection:
376
+
377
+ - Uses AI SDK `APICallError` where available.
378
+ - Also checks common `statusCode`, `status`, `response.status`, and nested `cause` shapes.
379
+
380
+ ## Editor Block Schema Architecture
381
+
382
+ The main schema file is `libs/utils/src/lib/editor-blocks.ts`.
383
+
384
+ It exports:
385
+
386
+ - `editorBlockDocumentSchema`
387
+ - `editorGeneratedBlockDocumentSchema`
388
+ - `createEditorGeneratedTableDocumentSchema`
389
+ - `getEditorBlocksJsonSchema`
390
+ - `getEditorBlocksSchemaAwarenessString`
391
+ - `validateEditorBlockDocument`
392
+ - `safeValidateEditorBlockDocument`
393
+
394
+ The root schema is:
395
+
396
+ ```ts
397
+ {
398
+ type: 'doc',
399
+ content?: EditorBlockNode[]
400
+ }
401
+ ```
402
+
403
+ Allowed full editor node types:
404
+
405
+ ```txt
406
+ doc
407
+ text
408
+ paragraph
409
+ heading
410
+ blockquote
411
+ codeBlock
412
+ bulletList
413
+ orderedList
414
+ listItem
415
+ taskList
416
+ taskItem
417
+ table
418
+ tableRow
419
+ tableCell
420
+ tableHeader
421
+ horizontalRule
422
+ hardBreak
423
+ image
424
+ divBlock
425
+ spanComponent
426
+ svg
427
+ styleTag
428
+ scriptTag
429
+ alertWidget
430
+ ctaWidget
431
+ ```
432
+
433
+ Allowed mark types:
434
+
435
+ ```txt
436
+ bold
437
+ italic
438
+ strike
439
+ code
440
+ link
441
+ highlight
442
+ textStyle
443
+ subscript
444
+ superscript
445
+ ```
446
+
447
+ There are two related schema surfaces:
448
+
449
+ 1. Full validation schema.
450
+ - Allows the existing editor/database content surface.
451
+ - Includes richer node types such as `image`, `divBlock`, `svg`, `styleTag`, and `scriptTag`.
452
+ 2. Generated-content schema.
453
+ - Smaller and safer subset from the previous strict generated-output flow.
454
+ - Prevents the model from generating unsafe or overly complex structures.
455
+ - Includes paragraphs, headings, blockquotes, code blocks, lists, task lists, tables, horizontal rules, alert widgets, and CTA widgets.
456
+
457
+ The inline editor no longer asks the model to emit this JSON directly. These schemas remain useful for:
458
+
459
+ - Stored product `description_json`.
460
+ - Schema verification scripts.
461
+ - Global-agent tools that update product descriptions or other stored editor JSON.
462
+
463
+ The legacy strict JSON generator kept a special strict table schema:
464
+
465
+ - Exactly one top-level `table`.
466
+ - Minimum rows based on prompt.
467
+ - Minimum columns based on prompt.
468
+ - Every row must contain cells/headers.
469
+ - Every cell/header must contain at least one paragraph with text.
470
+
471
+ This was added for the old strict JSON path because generic structured generation often produced weak or invalid pricing tables. The replacement inline assistant asks for valid HTML table markup and relies on Tiptap's HTML parser.
472
+
473
+ ## Inline HTML Editor Assistance
474
+
475
+ High-level flow:
476
+
477
+ ```txt
478
+ NotionEditor prompt
479
+ -> POST /api/ai/generate-blocks
480
+ -> require ADMIN or WRITER
481
+ -> generateEditorHtmlFragment()
482
+ -> Vercel AI SDK generateText()
483
+ -> lightweight HTML fragment validation
484
+ -> return { html, credentialSource, modelId }
485
+ -> editor setContent(html) or insertContent(html)
486
+ ```
487
+
488
+ Route:
489
+
490
+ ```txt
491
+ apps/nextblock/app/api/ai/generate-blocks/route.ts
492
+ ```
493
+
494
+ Request schema:
495
+
496
+ ```ts
497
+ {
498
+ prompt: string; // 3..4000 chars
499
+ context?: string; // max 2000 chars
500
+ }
501
+ ```
502
+
503
+ Access:
504
+
505
+ - Requires authenticated user.
506
+ - Requires profile role `ADMIN` or `WRITER`.
507
+
508
+ Response:
509
+
510
+ - Returns:
511
+
512
+ ```ts
513
+ {
514
+ html: string;
515
+ credentialSource: 'env' | 'stored' | 'manual';
516
+ modelId: string;
517
+ }
518
+ ```
519
+
520
+ - Adds diagnostic headers:
521
+ - `x-cortex-ai-credential-source`
522
+ - `x-cortex-ai-model`
523
+
524
+ Generator:
525
+
526
+ ```txt
527
+ apps/nextblock/lib/ai-block-generation.ts
528
+ ```
529
+
530
+ Prompt persona:
531
+
532
+ ```txt
533
+ NextBlock Cortex AI inline rich-text assistant
534
+ ```
535
+
536
+ Important prompt rules:
537
+
538
+ - Return only an HTML fragment.
539
+ - No markdown code fences.
540
+ - No explanations.
541
+ - Do not include `<!doctype>`, `<html>`, `<head>`, or `<body>`.
542
+ - Use semantic headings, paragraphs, lists, tables, blockquotes, code blocks, and horizontal rules.
543
+ - For tables, use valid `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, and `<td>`.
544
+ - Use `<style>` or `<script>` only when explicitly requested. The editor already has `StyleTagNode`, `ScriptTagNode`, and source-mode parsing for those tags.
545
+
546
+ Vercel AI SDK usage:
547
+
548
+ ```ts
549
+ generateText({
550
+ prompt,
551
+ system,
552
+ maxRetries: 0,
553
+ })
554
+ ```
555
+
556
+ Editor insertion:
557
+
558
+ ```txt
559
+ libs/editor/src/lib/NotionEditor.tsx
560
+ ```
561
+
562
+ Behavior:
563
+
564
+ - If the editor is empty, Cortex AI uses `editor.commands.setContent(payload.html)`.
565
+ - If the editor already has content and there is no active text selection, it appends `payload.html` at the end of the document.
566
+ - If there is an active text selection, it replaces that selection with `payload.html`.
567
+ - Existing content is preserved for non-empty editors.
568
+ - The client sends insertion context (`append-to-end`, `replace-selection`, or empty document), selected text when present, and a trailing slice of existing editor text so the model can continue without duplicating content.
569
+ - The server rejects empty fragments, markdown fences, full HTML documents, obvious conversational wrappers, plain text with no HTML tags, uneven table rows, and tables with empty cells.
570
+ - The server also strips empty top-level paragraphs/headings and normalizes generated tables to remove blank spacer rows/columns before insertion.
571
+
572
+ The old strict `generateObject()` Tiptap JSON path is replaced for inline prompts. The route name remains `/api/ai/generate-blocks` for compatibility, but the successful payload is now HTML-first.
573
+
574
+ ## Global Agent Architecture
575
+
576
+ The global dashboard agent has two main pieces:
577
+
578
+ 1. Tool registry and execution functions.
579
+ 2. Streaming route and chat UI.
580
+
581
+ ### Tool Registry
582
+
583
+ File:
584
+
585
+ ```txt
586
+ apps/nextblock/lib/ai-global-agent-tools.ts
587
+ ```
588
+
589
+ Exported tool schemas:
590
+
591
+ - `updateNavigationBarInputSchema`
592
+ - `updateFooterInputSchema`
593
+ - `searchDocumentationInputSchema`
594
+ - `cortexAiPageContextSchema`
595
+ - `readCurrentCmsItemInputSchema`
596
+ - `updateCurrentCmsFieldsInputSchema`
597
+ - `updateContentBlockInputSchema`
598
+ - `updateSectionColumnBlockInputSchema`
599
+ - `fetchEcommerceStatsInputSchema`
600
+
601
+ Exported executors:
602
+
603
+ - `executeUpdateNavigationBar`
604
+ - `executeUpdateFooter`
605
+ - `executeSearchDocumentation`
606
+ - `executeReadCurrentCmsItem`
607
+ - `executeUpdateCurrentCmsFields`
608
+ - `executeUpdateContentBlock`
609
+ - `executeUpdateSectionColumnBlock`
610
+ - `executeFetchEcommerceStats`
611
+
612
+ Tool factory:
613
+
614
+ ```ts
615
+ createCortexGlobalAgentTools(context)
616
+ ```
617
+
618
+ Tools are passed to Vercel AI SDK `streamText`.
619
+
620
+ The new CMS editing tools require a current `pageContext` supplied by the chat request. They are admin-only for this rollout because the global-agent route requires `ADMIN`.
621
+
622
+ ### update_navigation_bar
623
+
624
+ Purpose:
625
+
626
+ - Update public header navigation for a locale.
627
+
628
+ Input:
629
+
630
+ ```ts
631
+ {
632
+ items: Array<{
633
+ label: string;
634
+ url: string;
635
+ target?: '_self' | '_blank';
636
+ children?: Array<{ label: string; url: string; target?: '_self' | '_blank' }>;
637
+ }>;
638
+ languageCode?: string; // locale code or language name
639
+ mode?: 'append' | 'replace' | 'update';
640
+ match?: { label?: string; url?: string };
641
+ }
642
+ ```
643
+
644
+ URL validation allows:
645
+
646
+ ```txt
647
+ /
648
+ #
649
+ http://
650
+ https://
651
+ mailto:
652
+ tel:
653
+ ```
654
+
655
+ Database table:
656
+
657
+ ```txt
658
+ navigation_items
659
+ ```
660
+
661
+ Important behavior:
662
+
663
+ - `append` preserves existing links.
664
+ - `replace` deletes all existing items for `menu_key = HEADER` and the selected language.
665
+ - `update` changes one existing item found by `match.label`, `match.url`, or the replacement URL.
666
+ - Append is idempotent by normalized URL.
667
+ - If the same URL already exists for that menu/language, it increments `skippedCount` instead of inserting a duplicate.
668
+ - Children are inserted with `parent_id`.
669
+ - Root `order` is based on existing top-level max order.
670
+
671
+ Language behavior:
672
+
673
+ - `languageCode` can be a code (`fr`) or a name (`French`).
674
+ - Active languages are loaded from `languages`.
675
+ - Matching normalizes accents/case.
676
+ - Supported aliases currently include common names such as `english`, `french`, `francais`, `spanish`, etc.
677
+
678
+ This fixed the case where a prompt like `can you also add it in French?` could stall or fail if a model supplied `French` instead of `fr`.
679
+
680
+ ### update_footer
681
+
682
+ Purpose:
683
+
684
+ - Update public footer links and/or footer copyright.
685
+
686
+ Input:
687
+
688
+ ```ts
689
+ {
690
+ languageCode?: string;
691
+ links?: NavigationItemInput[];
692
+ copyright?: Record<string, string>;
693
+ }
694
+ ```
695
+
696
+ Behavior:
697
+
698
+ - `links` currently replace `menu_key = FOOTER` for the selected language.
699
+ - `copyright` upserts `site_settings.key = footer_copyright`.
700
+ - The same language-name resolver is used for footer links.
701
+
702
+ Important limitation:
703
+
704
+ - No append mode for footer links yet. If a user asks to add one footer link, current behavior may replace the footer link set if the model calls `update_footer` with only that link.
705
+
706
+ ### search_documentation
707
+
708
+ Purpose:
709
+
710
+ - Provide project/documentation context to the agent.
711
+
712
+ Input:
713
+
714
+ ```ts
715
+ {
716
+ query: string;
717
+ limit?: number; // 1..8, default 4
718
+ }
719
+ ```
720
+
721
+ Behavior:
722
+
723
+ - Searches published `posts` and `pages`.
724
+ - Uses simple lowercase term matching/scoring, not vector embeddings.
725
+ - Returns snippets with:
726
+ - `title`
727
+ - `url`
728
+ - `source`
729
+ - `excerpt`
730
+
731
+ Future RAG work should replace or augment this with an embeddings table and vector similarity search.
732
+
733
+ ### read_current_cms_item
734
+
735
+ Purpose:
736
+
737
+ - Read the page, post, or product currently being edited.
738
+ - Return page/post/product metadata and, for pages/posts, ordered block summaries or full block content.
739
+
740
+ Input:
741
+
742
+ ```ts
743
+ {
744
+ includeBlocks?: boolean; // default true
745
+ includeBlockContent?: boolean; // default false
746
+ }
747
+ ```
748
+
749
+ Behavior:
750
+
751
+ - Requires `pageContext` from the chat request.
752
+ - Fetches from `pages`, `posts`, or `products` by current entity id.
753
+ - For pages/posts, fetches `blocks` by `page_id` or `post_id` and sorts by `order`.
754
+ - Omits block `content` unless `includeBlockContent` is true, keeping normal reads compact.
755
+
756
+ ### update_current_cms_fields
757
+
758
+ Purpose:
759
+
760
+ - Update metadata fields on the current page, post, or product.
761
+
762
+ Supported fields:
763
+
764
+ - Pages: `title`, `slug`, `status`, `meta_title`, `meta_description`.
765
+ - Posts: `title`, `slug`, `status`, `label`, `subtitle`, `excerpt`, `published_at`, `feature_image_id`, `meta_title`, `meta_description`.
766
+ - Products: `title`, `slug`, `status`, `short_description`, `description_json`, `meta_title`, `meta_description`.
767
+
768
+ Behavior:
769
+
770
+ - Requires `pageContext`.
771
+ - Validates page/post status as `draft`, `published`, or `archived`.
772
+ - Validates product status as `draft`, `active`, or `archived`.
773
+ - Validates product `description_json` against `editorBlockDocumentSchema`.
774
+ - Revalidates the CMS edit path and public page/post/product path.
775
+
776
+ ### update_content_block
777
+
778
+ Purpose:
779
+
780
+ - Update an existing top-level block on the current page or post.
781
+
782
+ Input:
783
+
784
+ ```ts
785
+ {
786
+ blockId: number;
787
+ blockType?: BlockType; // assertion only
788
+ content: Record<string, unknown>;
789
+ }
790
+ ```
791
+
792
+ Behavior:
793
+
794
+ - Requires current page/post `pageContext`.
795
+ - Refuses to update blocks outside the current page/post.
796
+ - Treats `blockType` as an assertion, not a type-changing request.
797
+ - Validates `content` with `validateBlockContent(existingBlockType, content)`.
798
+ - Updates only `blocks.content` and `updated_at`.
799
+
800
+ ### update_section_column_block
801
+
802
+ Purpose:
803
+
804
+ - Update an existing nested block inside a current page/post `section` block.
805
+
806
+ Input:
807
+
808
+ ```ts
809
+ {
810
+ parentBlockId: number;
811
+ columnIndex: number;
812
+ blockIndex: number;
813
+ blockType?: BlockType; // assertion only
814
+ content: Record<string, unknown>;
815
+ }
816
+ ```
817
+
818
+ Behavior:
819
+
820
+ - Requires current page/post `pageContext`.
821
+ - Refuses to update parent blocks outside the current page/post.
822
+ - Parent must be a `section` block (the only nested-column parent type; legacy
823
+ `hero` blocks are now sections with an `is_hero` flag).
824
+ - Validates nested content against the nested block type.
825
+ - Validates final parent section content before saving.
826
+
827
+ ### fetch_ecommerce_stats
828
+
829
+ Purpose:
830
+
831
+ - Fetch quantitative ecommerce statistics and reports from the database.
832
+ - Answer questions about revenue, order counts, and top-selling products over a time range.
833
+
834
+ Input:
835
+
836
+ ```ts
837
+ {
838
+ currency?: string; // ISO code, default "USD"
839
+ query: string; // The analytical question
840
+ reportType?: 'revenue' | 'orders' | 'products' | 'general';
841
+ timeRange?: 'last_7_days' | 'last_30_days' | 'last_month' | 'last_90_days' | 'all_time';
842
+ }
843
+ ```
844
+
845
+ Behavior:
846
+
847
+ - Read-only: does not require confirmation.
848
+ - Queries `order_items` joined with `orders` and `products`.
849
+ - Filters by `orders.status = 'paid'`.
850
+ - Supports aggregation by product and currency.
851
+ - Provides a summary of total orders, total revenue, and a list of top products.
852
+ - Restricted to authenticated admins in the `global-agent` route.
853
+
854
+ ### Revalidation
855
+
856
+ Tool mutations call:
857
+
858
+ ```txt
859
+ revalidatePath('/', 'layout')
860
+ revalidatePath('/cms/navigation')
861
+ ```
862
+
863
+ Navigation/footer mutations keep public layout/nav and CMS navigation screens in sync. Current CMS item/block mutations revalidate the active edit screen and the public page, article, or product URL when a slug is available.
864
+
865
+ ## Global Agent Route
866
+
867
+ File:
868
+
869
+ ```txt
870
+ apps/nextblock/app/api/ai/global-agent/route.ts
871
+ ```
872
+
873
+ Access:
874
+
875
+ - Requires authenticated `ADMIN`.
876
+ - Requires active `cortex-ai` package.
877
+
878
+ Request schema:
879
+
880
+ ```ts
881
+ {
882
+ messages: Array<{
883
+ role: 'system' | 'user' | 'assistant';
884
+ content: string;
885
+ }>;
886
+ pageContext?: {
887
+ contentType: 'page' | 'post' | 'product';
888
+ entityId: number | string;
889
+ slug?: string | null;
890
+ title?: string | null;
891
+ languageId?: number | null;
892
+ currentEditor?: {
893
+ blockId?: number | string | null;
894
+ blockType?: string | null;
895
+ field?: string | null;
896
+ };
897
+ } | null;
898
+ }
899
+ ```
900
+
901
+ Old `{ messages }` requests remain valid. If no `pageContext` is supplied, the agent can still use navigation/footer/search tools but should not perform current-item mutations.
902
+
903
+ Limits:
904
+
905
+ - Max 40 messages.
906
+ - Max 8000 chars per message.
907
+
908
+ Model orchestration:
909
+
910
+ - Uses `streamText`.
911
+ - Uses `buildCortexAiRoutingPolicy`.
912
+ - Uses `stepCountIs(6)`.
913
+ - Temperature is `0.1`.
914
+ - Max output tokens is `2000`.
915
+ - Per-model attempt timeout is `30000ms`.
916
+
917
+ System prompt:
918
+
919
+ - Agent identity: `NextBlock Cortex AI`.
920
+ - Explicit Planner -> Executor -> Evaluator behavior.
921
+ - Use typed tools for mutations.
922
+ - Append header links unless replacement is clearly requested.
923
+ - Use current page/post/product context for phrases like "this page", "this product", or "this block".
924
+ - Do not update content outside the supplied current CMS context.
925
+ - Map language names to codes, e.g. French -> fr.
926
+ - Follow-up language requests should reuse prior requested item.
927
+
928
+ ### SSE Protocol
929
+
930
+ The route returns `text/event-stream`.
931
+
932
+ Events:
933
+
934
+ ```ts
935
+ type CortexAgentStreamEvent =
936
+ | { type: 'meta'; credentialSource: string; modelId: string }
937
+ | { type: 'text-delta'; text: string }
938
+ | { type: 'tool-call'; toolName: string; toolCallId?: string; input?: unknown }
939
+ | { type: 'tool-result'; toolName: string; toolCallId?: string; output?: unknown }
940
+ | { type: 'tool-error'; message: string; toolName?: string; toolCallId?: string }
941
+ | { type: 'error'; message: string }
942
+ | { type: 'finish' };
943
+ ```
944
+
945
+ ### Defensive Streaming Choices
946
+
947
+ The global agent route intentionally buffers assistant text instead of streaming every token immediately.
948
+
949
+ Reason:
950
+
951
+ - Some OpenRouter free models can emit raw tool-call payload text such as `</TOOLCALL>` or JSON fragments instead of using the SDK tool-call channel.
952
+ - Buffering allows the route to detect and suppress raw tool-call leakage before the user sees it.
953
+
954
+ Raw tool-call leak detection checks for:
955
+
956
+ - `<toolcall`
957
+ - `</toolcall`
958
+ - `"arguments"`
959
+ - tool names such as `"update_navigation_bar"`, `"update_current_cms_fields"`, and `"update_section_column_block"`
960
+
961
+ Rate-limit text detection checks for:
962
+
963
+ - `rate limit exceeded`
964
+ - `free-models-per-day`
965
+ - `too many requests`
966
+
967
+ Fallback strategy:
968
+
969
+ - If no tool has run and the attempt hits 429/raw-tool/rate-limit text, the route can try the next model.
970
+ - If a tool has already succeeded, the route does not retry another model because retrying can duplicate side effects.
971
+ - If a tool succeeded but final natural-language response fails, the route sends a deterministic confirmation such as:
972
+ - `Done. I updated the navigation bar.`
973
+ - `That navigation link already exists, so I left the header unchanged.`
974
+ - `Done. I updated the footer.`
975
+ - `Done. I updated the current CMS fields.`
976
+ - `Done. I updated the current content block.`
977
+
978
+ This was added after a real issue where:
979
+
980
+ 1. The navigation mutation succeeded.
981
+ 2. The final model response hit a free-model rate limit.
982
+ 3. The UI showed an error or raw tool-call text.
983
+
984
+ The current implementation treats the DB tool result as the source of truth once a mutation succeeds.
985
+
986
+ ## Dashboard Chat UI
987
+
988
+ File:
989
+
990
+ ```txt
991
+ apps/nextblock/app/cms/components/CortexGlobalAgentChat.tsx
992
+ ```
993
+
994
+ Rendered from:
995
+
996
+ ```txt
997
+ apps/nextblock/app/cms/CmsClientLayout.tsx
998
+ ```
999
+
1000
+ Condition:
1001
+
1002
+ ```tsx
1003
+ {isAdmin && isCortexAiActive && <CortexGlobalAgentChat />}
1004
+ ```
1005
+
1006
+ Features:
1007
+
1008
+ - Floating brain icon launcher.
1009
+ - Right-side popup panel.
1010
+ - Persistent local browser thread history.
1011
+ - Sends current CMS page/post/product context with chat requests when available.
1012
+ - New thread button.
1013
+ - Delete old thread button.
1014
+ - Stop streaming button.
1015
+ - Tool-call status rows:
1016
+ - `Updating navigation bar...`
1017
+ - `Footer updated`
1018
+ - `Documentation searched`
1019
+ - Metadata badge showing credential source and model id.
1020
+
1021
+ Storage:
1022
+
1023
+ ```txt
1024
+ localStorage key = nextblock-cortex-global-agent-chat-threads
1025
+ legacy sessionStorage key = nextblock-cortex-global-agent-chat
1026
+ ```
1027
+
1028
+ Limits:
1029
+
1030
+ - Max stored threads: 20.
1031
+ - Max stored messages per thread: 40.
1032
+ - Request timeout: 45000ms.
1033
+
1034
+ Important behavior:
1035
+
1036
+ - The UI aborts requests after timeout and shows a clean error instead of leaving a spinner forever.
1037
+ - The UI cancels the stream reader after receiving `finish`.
1038
+ - The component returns `null` until mounted, preventing SSR/client localStorage mismatches.
1039
+ - `CortexAiPageContextProvider` wraps the CMS layout. Edit screens register page, post, and product context via `CortexAiPageContextRegistrar`; the chat also parses `/cms/pages/:id/edit`, `/cms/posts/:id/edit`, and `/cms/products/:id/edit` as a fallback.
1040
+
1041
+ ## Hydration Fixes Related to Cortex AI Work
1042
+
1043
+ During implementation, React hydration warnings appeared around Radix-generated IDs. The visible stack pointed at buttons/selects/dialogs, but the root cause was a different component tree/order between server render and first client render.
1044
+
1045
+ Fixes:
1046
+
1047
+ - `ResponsiveNav` now renders Radix-heavy search/auth/language/currency/cart controls inside a local `ClientOnly` wrapper.
1048
+ - `Header` passes render functions instead of reusing the same React element instance in both desktop and mobile nav sections.
1049
+ - `FeedbackModal` renders a plain trigger button before mount, then wraps it with Radix `Dialog` after hydration.
1050
+ - `CortexGlobalAgentChat` is also mounted only after the client has loaded thread state.
1051
+ - `ProductFormClientShell` renders a deterministic placeholder on SSR/initial hydration, then mounts the Radix-heavy product form controls client-side.
1052
+
1053
+ These choices keep SSR and first client render aligned while preserving interactive behavior after hydration.
1054
+
1055
+ ## Dashboard Premium CTA
1056
+
1057
+ File:
1058
+
1059
+ ```txt
1060
+ apps/nextblock/app/cms/dashboard/actions.ts
1061
+ ```
1062
+
1063
+ Important detail:
1064
+
1065
+ - Dashboard stats now check `activePackages.has('cortex-ai')`.
1066
+ - Older code checked `activePackages.has('ai')`, which incorrectly showed the "Upgrade to Premium" CTA even when Cortex AI was active.
1067
+
1068
+ The CTA component itself lives in:
1069
+
1070
+ ```txt
1071
+ apps/nextblock/app/cms/dashboard/components/DashboardComponents.tsx
1072
+ ```
1073
+
1074
+ It returns `null` when both commerce and Cortex AI are active:
1075
+
1076
+ ```ts
1077
+ if (hasCommerce && hasAi) return null;
1078
+ ```
1079
+
1080
+ ## Tests and Verification
1081
+
1082
+ Package scripts:
1083
+
1084
+ ```txt
1085
+ npm run verify:cortex-ai-routing
1086
+ npm run verify:cortex-ai-generate-blocks
1087
+ npm run verify:cortex-ai-global-tools
1088
+ npm run verify:cortex-ai-build-widget
1089
+ npm run verify:editor-block-schema
1090
+ ```
1091
+
1092
+ Useful commands:
1093
+
1094
+ ```bash
1095
+ npm run verify:cortex-ai-global-tools
1096
+ npm run verify:cortex-ai-routing -- --mode=both
1097
+ npm run verify:cortex-ai-generate-blocks -- "Generate a 3-tier pricing table"
1098
+ npm run verify:editor-block-schema
1099
+ npx nx lint nextblock --skip-nx-cache
1100
+ npx nx build nextblock --skip-nx-cache
1101
+ ```
1102
+
1103
+ Vitest files:
1104
+
1105
+ ```txt
1106
+ apps/nextblock/lib/ai-key-crypto.test.ts
1107
+ apps/nextblock/lib/ai-model-catalog.test.ts
1108
+ apps/nextblock/lib/ai-model-registry.test.ts
1109
+ apps/nextblock/lib/ai-global-agent-tools.test.ts
1110
+ ```
1111
+
1112
+ Notes:
1113
+
1114
+ - Live OpenRouter verification needs a valid `OPENROUTER_API_KEY` or stored BYOK.
1115
+ - Free model limits may make live routing/generation tests flaky.
1116
+ - Prefer focused verification scripts for Cortex AI changes instead of running broad test suites unless an error requires it.
1117
+
1118
+ ## Common Troubleshooting
1119
+
1120
+ ### The chat bubble stays loading
1121
+
1122
+ Current protections:
1123
+
1124
+ - Server-side per-model timeout: 30 seconds.
1125
+ - Client request timeout: 45 seconds.
1126
+ - Client stops reading on `finish`.
1127
+
1128
+ If it still happens:
1129
+
1130
+ 1. Hard refresh the browser to clear a stuck request.
1131
+ 2. Check browser console for fetch/stream errors.
1132
+ 3. Check server logs from `/api/ai/global-agent`.
1133
+ 4. Verify OpenRouter account limits.
1134
+ 5. Verify `OPENROUTER_API_KEY` or stored BYOK exists.
1135
+
1136
+ ### The agent says rate limit exceeded
1137
+
1138
+ OpenRouter free models can hit account-level daily limits. This can happen even with a real API key if the account has no credits or free quota is exhausted.
1139
+
1140
+ Mitigations:
1141
+
1142
+ - Add OpenRouter credits.
1143
+ - Save a stored BYOK and select a compatible paid model in `/cms/settings/cortex-ai`.
1144
+ - Add or change fallback models in `ai-model-registry.ts`.
1145
+
1146
+ ### Inline editor generation fails with a generic fallback error
1147
+
1148
+ The inline route now summarizes first/last real model errors from routing attempts and returns that message in the JSON body. Server logs still include the full per-model `attempts` array.
1149
+
1150
+ Check:
1151
+
1152
+ - Sandbox BYOK is present in `localStorage` under `cortex_ai_sandbox_openrouter_api_key`.
1153
+ - Sandbox model selection is present under `cortex_ai_sandbox_openrouter_model_selection`.
1154
+ - Request headers include `x-sandbox-openrouter-key` and `x-sandbox-openrouter-model` in sandbox.
1155
+ - Env-only routing is not expected to use paid models; it always uses the free registry.
1156
+ - The model returned an HTML fragment, not markdown fences, a full HTML document, or conversational prose.
1157
+
1158
+ ### The agent added a link but then showed an error
1159
+
1160
+ The mutation may have succeeded before the model hit a final-response error. Current route behavior should synthesize a clean confirmation after a successful tool result.
1161
+
1162
+ Navigation append is idempotent by URL, so retrying the same request should skip duplicate URLs.
1163
+
1164
+ ### "Add it in French" does not work
1165
+
1166
+ The tool backend can resolve language names and aliases, but the language must exist and be active in `languages`.
1167
+
1168
+ Check:
1169
+
1170
+ - `languages.code = 'fr'`
1171
+ - `languages.name = 'French'` or compatible alias
1172
+ - `is_active` is not false
1173
+
1174
+ ### Stored key cannot be decrypted
1175
+
1176
+ Likely causes:
1177
+
1178
+ - `CORTEX_AI_ENCRYPTION_KEY` changed.
1179
+ - Stored envelope was manually edited.
1180
+ - Stored key was encrypted in a different environment.
1181
+
1182
+ Resolution:
1183
+
1184
+ - Clear the stored key in `/cms/settings/cortex-ai`.
1185
+ - Set the intended encryption key.
1186
+ - Save the OpenRouter key again.
1187
+
1188
+ ### Cortex AI package active but dashboard still shows AI upsell
1189
+
1190
+ Check:
1191
+
1192
+ - Active package row has `package_id = 'cortex-ai'`.
1193
+ - Dashboard code checks `cortex-ai`, not `ai`.
1194
+ - Hard refresh or clear Next cache if stale.
1195
+
1196
+ ### Hydration warning involving Radix IDs
1197
+
1198
+ Likely cause:
1199
+
1200
+ - A client component renders a different tree on first client render than SSR.
1201
+
1202
+ Known fixed areas:
1203
+
1204
+ - Public nav Radix controls are client-only after mount.
1205
+ - Feedback modal trigger is stable before mount.
1206
+ - Chat component waits until mounted.
1207
+ - Product edit form controls wait until mounted through `ProductFormClientShell`.
1208
+
1209
+ If new warnings appear, inspect for:
1210
+
1211
+ - `typeof window` branches inside render.
1212
+ - `Date.now()` or `Math.random()` during render.
1213
+ - LocalStorage/sessionStorage reads during initial state that affect rendered tree.
1214
+ - Reusing the same React element in two places.
1215
+
1216
+ ## Security Notes
1217
+
1218
+ - Never expose OpenRouter API keys to client components.
1219
+ - Keep AI config/client modules server-only.
1220
+ - Stored BYOK plaintext is never persisted.
1221
+ - Stored BYOK plaintext is only available transiently server-side after decrypt.
1222
+ - Settings server actions re-check admin role.
1223
+ - AI route handlers re-check authentication/role.
1224
+ - Global agent DB mutations use service-role Supabase only on the server.
1225
+ - Sensitive `site_settings` row is protected by RLS.
1226
+ - Do not log plaintext API keys.
1227
+ - Do not paste real secrets into documentation, PRs, screenshots, or AI prompts.
1228
+
1229
+ ## Extension Guide
1230
+
1231
+ ### Adding a New Agent Tool
1232
+
1233
+ 1. Add a strict Zod input schema in `ai-global-agent-tools.ts`.
1234
+ 2. Add a pure executor function that accepts input and `ToolExecutionContext`.
1235
+ 3. Re-check any database assumptions inside the executor.
1236
+ 4. Use service-role Supabase through context.
1237
+ 5. Return a small structured result with `success: true`.
1238
+ 6. Add the tool to `createCortexGlobalAgentTools`.
1239
+ 7. Update the global agent system prompt if needed.
1240
+ 8. Add focused tests in `ai-global-agent-tools.test.ts`.
1241
+ 9. Update `verify-cortex-ai-global-tools.ts`.
1242
+ 10. Consider deterministic completion copy in `getToolCompletionMessage`.
1243
+
1244
+ Rules:
1245
+
1246
+ - Tool arguments must be typed.
1247
+ - Avoid raw SQL unless absolutely necessary.
1248
+ - Make side-effecting operations idempotent when possible.
1249
+ - Do not retry side-effecting tool calls after success.
1250
+
1251
+ ### Adding a New Editor Node Type
1252
+
1253
+ 1. Confirm the node exists in the actual Tiptap extension set.
1254
+ 2. Add it to the full schema in `libs/utils/src/lib/editor-blocks.ts`.
1255
+ 3. Decide if it is safe for stored AI-authored JSON fields such as product `description_json`.
1256
+ 4. If safe, add it to the generated/schema-constrained JSON surface used by validators and agent tools.
1257
+ 5. Update `EDITOR_BLOCK_ALLOWED_NODE_TYPES`.
1258
+ 6. Update schema awareness string if needed.
1259
+ 7. If the node has an HTML representation, verify the inline assistant can insert it through Tiptap's normal HTML parser/source-mode path.
1260
+ 8. Run:
1261
+
1262
+ ```bash
1263
+ npm run verify:editor-block-schema
1264
+ ```
1265
+
1266
+ 9. Test inline generation with:
1267
+
1268
+ ```bash
1269
+ npm run verify:cortex-ai-generate-blocks -- "Generate content using the new node type"
1270
+ ```
1271
+
1272
+ ### Adding a New Free Model
1273
+
1274
+ 1. Add the model id to `CORTEX_AI_FREE_MODEL_FALLBACK_REGISTRY`.
1275
+ 2. Confirm the model supports the target feature:
1276
+ - tool calling for global agent
1277
+ - clean text/HTML fragment generation for the inline editor
1278
+ 3. Run:
1279
+
1280
+ ```bash
1281
+ npm run verify:cortex-ai-routing -- --mode=free
1282
+ ```
1283
+
1284
+ 4. If used for generation, test:
1285
+
1286
+ ```bash
1287
+ npm run verify:cortex-ai-generate-blocks -- --model=MODEL_ID "Generate a 3-tier pricing table"
1288
+ ```
1289
+
1290
+ ## Current Follow-Up Work Items
1291
+
1292
+ 1. Seed a Cortex AI product/package showcase in sandbox reset, similar to ecommerce.
1293
+ - Use `apps/nextblock/public/images/cortex-ai-square.webp`.
1294
+ - Freemius product id: `28609`.
1295
+ - Freemius plan id: `47122`.
1296
+ 2. Add footer append mode to avoid replacing all footer links for small edits.
1297
+ 3. Replace keyword documentation search with embedding-based RAG.
1298
+ 4. Add server-side chat thread persistence if browser-local history is not enough.
1299
+ 5. Add page-aware block insertion tools with explicit idempotency keys.
1300
+ 6. Add explicit package gating to editor prompt visibility if desired. The current route enforces access/credentials, but the editor prompt UI is not itself hidden by package state in `NotionEditor`.
1301
+ 7. Consider search/filter affordances in the model picker if the compatible OpenRouter catalog becomes too large for a basic select.
1302
+
1303
+ ## Mental Model for Future Agents
1304
+
1305
+ When modifying Cortex AI, keep these invariants:
1306
+
1307
+ 1. `cortex-ai` is the package id. Do not reintroduce `ai`.
1308
+ 2. Never put secrets in client code.
1309
+ 3. Stored BYOK overrides `OPENROUTER_API_KEY` so paid model selection can work even in sandbox.
1310
+ 4. Env-only routing must stay locked to the three explicit free models.
1311
+ 5. Stored BYOK requires `CORTEX_AI_ENCRYPTION_KEY`.
1312
+ 6. Stored model selection must only use models with `tools` and `structured_outputs`.
1313
+ 7. Inline editor generation returns HTML fragments, not strict Tiptap JSON.
1314
+ 8. Stored product descriptions and global-agent editor JSON fields still validate against editor schemas.
1315
+ 9. Global mutations must go through typed tools.
1316
+ 10. Side-effecting tools should be idempotent when possible.
1317
+ 11. If a side-effecting tool succeeds and the model fails afterward, report the tool result instead of retrying blindly.
1318
+ 12. Free OpenRouter models are useful but unstable; guard against 429s, malformed tool-call text, invalid HTML fragments, and no-output generation.
1319
+ 13. Multilingual mutations should use active rows from `languages`, not hardcoded assumptions.