create-nextblock 0.2.78 → 0.8.1

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 +793 -472
  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
@@ -1,4 +1,4 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, type DependencyList } from 'react';
2
2
 
3
3
  /**
4
4
  * Hook to handle keyboard shortcuts.
@@ -8,19 +8,26 @@ import { useEffect } from 'react';
8
8
  * @param callback The function to call when the key combination is pressed
9
9
  * @param deps Dependencies array for the effect
10
10
  */
11
- export function useHotkeys(key: string, callback: (event: KeyboardEvent) => void, deps: any[] = []) {
12
- useEffect(() => {
13
- const handleKeyDown = (event: KeyboardEvent) => {
14
- const isCtrl = event.ctrlKey || event.metaKey; // cmd on mac, ctrl on windows
15
- const keyLower = event.key.toLowerCase();
16
-
17
- // Check for ctrl+s / cmd+s
18
- if ((key === 'ctrl+s' || key === 'meta+s') && isCtrl && keyLower === 's') {
19
- event.preventDefault();
20
- callback(event);
21
- }
22
- };
23
-
11
+ export function useHotkeys(
12
+ key: string,
13
+ callback: (event: KeyboardEvent) => void,
14
+ deps: DependencyList = [],
15
+ ) {
16
+ useEffect(() => {
17
+ const normalizedShortcut = key.toLowerCase();
18
+
19
+ const handleKeyDown = (event: KeyboardEvent) => {
20
+ const isCtrl = event.ctrlKey || event.metaKey; // cmd on mac, ctrl on windows
21
+ const keyLower = typeof event.key === 'string' ? event.key.toLowerCase() : '';
22
+ const isSaveKey = keyLower === 's' || event.code === 'KeyS';
23
+
24
+ // Check for ctrl+s / cmd+s
25
+ if ((normalizedShortcut === 'ctrl+s' || normalizedShortcut === 'meta+s') && isCtrl && isSaveKey) {
26
+ event.preventDefault();
27
+ callback(event);
28
+ }
29
+ };
30
+
24
31
  window.addEventListener('keydown', handleKeyDown);
25
32
  return () => window.removeEventListener('keydown', handleKeyDown);
26
33
  }, [key, ...deps]); // callback should be stable or included in deps if handled by caller
@@ -0,0 +1,101 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import { useLanguage } from '../context/LanguageContext';
5
+ import type {
6
+ GlobalSearchFilter,
7
+ GlobalSearchResponse,
8
+ GlobalSearchResult,
9
+ } from '../lib/search/types';
10
+
11
+ type SearchStatus = 'idle' | 'loading' | 'success' | 'error';
12
+
13
+ export function useGlobalSearch({
14
+ initialQuery = '',
15
+ initialFilter = 'all',
16
+ limit = 12,
17
+ }: {
18
+ initialQuery?: string;
19
+ initialFilter?: GlobalSearchFilter;
20
+ limit?: number;
21
+ } = {}) {
22
+ const { currentLocale } = useLanguage();
23
+ const [query, setQuery] = useState(initialQuery);
24
+ const [filter, setFilter] = useState<GlobalSearchFilter>(initialFilter);
25
+ const [results, setResults] = useState<GlobalSearchResult[]>([]);
26
+ const [counts, setCounts] = useState<GlobalSearchResponse['counts']>({
27
+ all: 0,
28
+ page: 0,
29
+ post: 0,
30
+ product: 0,
31
+ });
32
+ const [status, setStatus] = useState<SearchStatus>('idle');
33
+
34
+ const normalizedQuery = useMemo(() => query.replace(/\s+/g, ' ').trim(), [query]);
35
+ const canSearch = normalizedQuery.length >= 2;
36
+
37
+ useEffect(() => {
38
+ if (!canSearch) {
39
+ setResults([]);
40
+ setCounts({ all: 0, page: 0, post: 0, product: 0 });
41
+ setStatus('idle');
42
+ return;
43
+ }
44
+
45
+ const controller = new AbortController();
46
+ const timeoutId = window.setTimeout(async () => {
47
+ setStatus('loading');
48
+
49
+ const params = new URLSearchParams({
50
+ q: normalizedQuery,
51
+ type: filter,
52
+ limit: String(limit),
53
+ });
54
+
55
+ if (currentLocale) {
56
+ params.set('locale', currentLocale);
57
+ }
58
+
59
+ try {
60
+ const response = await fetch(`/api/search?${params.toString()}`, {
61
+ signal: controller.signal,
62
+ });
63
+
64
+ if (!response.ok) {
65
+ throw new Error(`Search request failed with ${response.status}`);
66
+ }
67
+
68
+ const payload = (await response.json()) as GlobalSearchResponse;
69
+ setResults(payload.results);
70
+ setCounts(payload.counts);
71
+ setStatus('success');
72
+ } catch (error) {
73
+ if ((error as Error).name === 'AbortError') {
74
+ return;
75
+ }
76
+
77
+ console.error('Global search request failed:', error);
78
+ setResults([]);
79
+ setCounts({ all: 0, page: 0, post: 0, product: 0 });
80
+ setStatus('error');
81
+ }
82
+ }, 180);
83
+
84
+ return () => {
85
+ controller.abort();
86
+ window.clearTimeout(timeoutId);
87
+ };
88
+ }, [canSearch, currentLocale, filter, limit, normalizedQuery]);
89
+
90
+ return {
91
+ query,
92
+ setQuery,
93
+ filter,
94
+ setFilter,
95
+ results,
96
+ counts,
97
+ status,
98
+ canSearch,
99
+ isLoading: status === 'loading',
100
+ };
101
+ }
@@ -3,3 +3,5 @@ declare module '*.svg' {
3
3
  export const ReactComponent: any;
4
4
  export default content;
5
5
  }
6
+
7
+ declare module 'uuid';
@@ -0,0 +1,339 @@
1
+ import { generateText } from 'ai';
2
+ import { z } from './zod-config';
3
+
4
+ import {
5
+ buildCortexAiRoutingPolicy,
6
+ createCortexAiOpenRouterClient,
7
+ } from './ai-client';
8
+ import {
9
+ getHttpStatusCode,
10
+ isOpenRouterRecoverableRoutingError,
11
+ omitUnsupportedCortexAiModelOptions,
12
+ runWithCortexAiModelFallback,
13
+ type CortexAiModelAttempt,
14
+ type CortexAiOpenRouterModelId,
15
+ type CortexAiStoredModelSelection,
16
+ } from './ai-model-registry';
17
+
18
+ export const generateEditorBlocksRequestSchema = z.strictObject({
19
+ context: z.string().max(2000).optional(),
20
+ prompt: z.string().min(3).max(4000),
21
+ });
22
+
23
+ export type GenerateEditorBlocksRequest = z.infer<typeof generateEditorBlocksRequestSchema>;
24
+
25
+ export type GenerateEditorHtmlFragmentResult = {
26
+ attempts: readonly CortexAiModelAttempt[];
27
+ credentialSource: 'env' | 'stored' | 'manual';
28
+ html: string;
29
+ modelId: CortexAiOpenRouterModelId;
30
+ };
31
+
32
+ const CORTEX_AI_HTML_GENERATION_ATTEMPT_TIMEOUT_MS = 60_000;
33
+
34
+ function buildInlineHtmlAssistantSystemPrompt() {
35
+ return [
36
+ 'You are NextBlock Cortex AI, an inline rich-text assistant for a Tiptap editor.',
37
+ 'Return ONLY an HTML fragment. Do not return markdown fences, JSON, prose explanations, or commentary.',
38
+ 'Do not include <!doctype>, <html>, <head>, or <body>. The output is inserted inside an existing editor document.',
39
+ 'Use semantic HTML: headings, paragraphs, unordered and ordered lists, blockquotes, pre/code blocks, horizontal rules, and tables.',
40
+ 'Use the current editor context for continuity, but do not repeat existing copy unless the user explicitly asks for a rewrite.',
41
+ 'For tables, use valid <table>, <thead>, <tbody>, <tr>, <th>, and <td> markup with aligned rows and non-empty cells.',
42
+ 'Never use blank table spacer rows, blank spacer columns, empty cells, <colgroup>, colspan, or rowspan unless the user explicitly asks for a blank template.',
43
+ 'If CSS or JavaScript is explicitly requested, use proper <style> or <script> tags. The editor preserves those tags through its source-mode HTML parser.',
44
+ 'Keep copy production-ready, editable, and concise unless the user asks for longer content.',
45
+ ].join(' ');
46
+ }
47
+
48
+ function buildHtmlGenerationPrompt(params: GenerateEditorBlocksRequest) {
49
+ return [
50
+ 'Create an HTML fragment for this inline editor request:',
51
+ params.prompt,
52
+ /\b(table|pricing table|comparison table)\b/i.test(params.prompt)
53
+ ? [
54
+ 'Table requirements:',
55
+ '- Use a short header row.',
56
+ '- Use only the real content columns requested by the user.',
57
+ '- Do not add blank spacer columns or blank spacer rows.',
58
+ '- Do not use <colgroup>, colspan, or rowspan.',
59
+ '- Put normal descriptive prose before or after the table, not inside a table cell.',
60
+ '- Body rows must match the header column count.',
61
+ '- Every header and body cell must contain meaningful text.',
62
+ ].join('\n')
63
+ : null,
64
+ params.context ? `Current editor context:\n${params.context}` : null,
65
+ ]
66
+ .filter(Boolean)
67
+ .join('\n\n');
68
+ }
69
+
70
+ function decodeBasicHtmlEntities(value: string) {
71
+ return value
72
+ .replace(/&nbsp;/gi, ' ')
73
+ .replace(/&amp;/gi, '&')
74
+ .replace(/&lt;/gi, '<')
75
+ .replace(/&gt;/gi, '>')
76
+ .replace(/&quot;/gi, '"')
77
+ .replace(/&#39;/gi, "'");
78
+ }
79
+
80
+ function getHtmlText(value: string) {
81
+ return decodeBasicHtmlEntities(
82
+ value
83
+ .replace(/<script\b[\s\S]*?<\/script>/gi, '')
84
+ .replace(/<style\b[\s\S]*?<\/style>/gi, '')
85
+ .replace(/<br\s*\/?>/gi, ' ')
86
+ .replace(/<[^>]+>/g, ' ')
87
+ )
88
+ .replace(/\s+/g, ' ')
89
+ .trim();
90
+ }
91
+
92
+ function isEmptyHtml(value: string) {
93
+ return getHtmlText(value).length === 0;
94
+ }
95
+
96
+ function stripEmptyTopLevelBlocks(html: string) {
97
+ return html
98
+ .replace(/<(p|h[1-6])\b[^>]*>(?:\s|&nbsp;|<br\s*\/?>)*<\/\1>/gi, '')
99
+ .trim();
100
+ }
101
+
102
+ function normalizeTableHtml(tableHtml: string) {
103
+ const rowMatches = [...tableHtml.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/gi)];
104
+
105
+ if (rowMatches.length === 0) {
106
+ return tableHtml;
107
+ }
108
+
109
+ const rows = rowMatches
110
+ .map((rowMatch) => {
111
+ const cells = [...rowMatch[1].matchAll(/<(td|th)\b[^>]*>([\s\S]*?)<\/\1>/gi)].map(
112
+ (cellMatch) => ({
113
+ innerHtml: cellMatch[2].trim(),
114
+ tag: cellMatch[1].toLowerCase() as 'td' | 'th',
115
+ })
116
+ );
117
+
118
+ return { cells };
119
+ })
120
+ .filter((row) => row.cells.some((cell) => !isEmptyHtml(cell.innerHtml)));
121
+
122
+ if (rows.length === 0) {
123
+ return tableHtml;
124
+ }
125
+
126
+ const maxColumnCount = Math.max(...rows.map((row) => row.cells.length));
127
+ const emptyColumnIndexes = new Set<number>();
128
+
129
+ for (let columnIndex = 0; columnIndex < maxColumnCount; columnIndex++) {
130
+ const isColumnEmpty = rows.every((row) => {
131
+ const cell = row.cells[columnIndex];
132
+ return !cell || isEmptyHtml(cell.innerHtml);
133
+ });
134
+
135
+ if (isColumnEmpty) {
136
+ emptyColumnIndexes.add(columnIndex);
137
+ }
138
+ }
139
+
140
+ const normalizedRows = rows
141
+ .map((row) => ({
142
+ cells: row.cells.filter((cell, cellIndex) => !emptyColumnIndexes.has(cellIndex)),
143
+ }))
144
+ .filter((row) => row.cells.length > 0);
145
+
146
+ if (normalizedRows.length === 0) {
147
+ return tableHtml;
148
+ }
149
+
150
+ const renderRow = (row: (typeof normalizedRows)[number]) =>
151
+ `<tr>${row.cells.map((cell) => `<${cell.tag}>${cell.innerHtml}</${cell.tag}>`).join('')}</tr>`;
152
+ const hasHeaderRow = normalizedRows[0].cells.some((cell) => cell.tag === 'th');
153
+
154
+ if (!hasHeaderRow) {
155
+ return `<table><tbody>${normalizedRows.map(renderRow).join('')}</tbody></table>`;
156
+ }
157
+
158
+ const [headerRow, ...bodyRows] = normalizedRows;
159
+
160
+ return `<table><thead>${renderRow(headerRow)}</thead><tbody>${bodyRows
161
+ .map(renderRow)
162
+ .join('')}</tbody></table>`;
163
+ }
164
+
165
+ function normalizeGeneratedTables(html: string) {
166
+ return html.replace(/<table\b[\s\S]*?<\/table>/gi, (tableHtml) =>
167
+ normalizeTableHtml(tableHtml)
168
+ );
169
+ }
170
+
171
+ function assertTablesHaveMeaningfulCells(html: string) {
172
+ const tableMatches = [...html.matchAll(/<table\b[\s\S]*?<\/table>/gi)];
173
+
174
+ for (const tableMatch of tableMatches) {
175
+ const tableHtml = tableMatch[0];
176
+ const rowMatches = [...tableHtml.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/gi)];
177
+
178
+ if (rowMatches.length === 0) {
179
+ throw new Error('Cortex AI returned a table without rows.');
180
+ }
181
+
182
+ const columnCounts: number[] = [];
183
+
184
+ for (const rowMatch of rowMatches) {
185
+ const cells = [...rowMatch[1].matchAll(/<(td|th)\b[^>]*>([\s\S]*?)<\/\1>/gi)];
186
+
187
+ if (cells.length === 0) {
188
+ throw new Error('Cortex AI returned a table row without cells.');
189
+ }
190
+
191
+ for (const cellMatch of cells) {
192
+ if (isEmptyHtml(cellMatch[2])) {
193
+ throw new Error('Cortex AI returned a table with empty cells.');
194
+ }
195
+ }
196
+
197
+ columnCounts.push(cells.length);
198
+ }
199
+
200
+ if (new Set(columnCounts).size > 1) {
201
+ throw new Error('Cortex AI returned a table with uneven row widths.');
202
+ }
203
+ }
204
+ }
205
+
206
+ function normalizeCommonHtmlWrappers(rawText: string) {
207
+ let html = rawText.trim();
208
+
209
+ if (
210
+ ((html.startsWith('"') && html.endsWith('"')) ||
211
+ (html.startsWith("'") && html.endsWith("'"))) &&
212
+ html.slice(1, -1).includes('<')
213
+ ) {
214
+ html = html.slice(1, -1).trim();
215
+ }
216
+
217
+ const bodyMatch = html.match(/<body\b[^>]*>([\s\S]*?)<\/body>/i);
218
+ if (bodyMatch?.[1]) {
219
+ html = bodyMatch[1].trim();
220
+ }
221
+
222
+ return stripEmptyTopLevelBlocks(normalizeGeneratedTables(html));
223
+ }
224
+
225
+ export function validateGeneratedEditorHtmlFragment(rawText: string) {
226
+ const html = normalizeCommonHtmlWrappers(rawText);
227
+
228
+ if (!html.trim()) {
229
+ throw new Error('Cortex AI returned empty HTML.');
230
+ }
231
+
232
+ if (/```/.test(html)) {
233
+ throw new Error('Cortex AI returned markdown code fences instead of an HTML fragment.');
234
+ }
235
+
236
+ if (/<!doctype\b|<html\b|<head\b|<body\b/i.test(html)) {
237
+ throw new Error('Cortex AI returned a full HTML document instead of an HTML fragment.');
238
+ }
239
+
240
+ if (/^(sure|certainly|of course|here(?:'s| is)|below is|i can)\b/i.test(html)) {
241
+ throw new Error('Cortex AI returned a conversational response instead of an HTML fragment.');
242
+ }
243
+
244
+ if (!/<[a-z][\w:-]*(?:\s[^>]*)?>/i.test(html)) {
245
+ throw new Error('Cortex AI returned plain text instead of semantic HTML.');
246
+ }
247
+
248
+ assertTablesHaveMeaningfulCells(html);
249
+
250
+ return html;
251
+ }
252
+
253
+ function isRecoverableHtmlGenerationError(error: unknown) {
254
+ const statusCode = getHttpStatusCode(error);
255
+
256
+ if (statusCode === 401 || statusCode === 402 || statusCode === 403) {
257
+ return false;
258
+ }
259
+
260
+ if (isOpenRouterRecoverableRoutingError(error)) {
261
+ return true;
262
+ }
263
+
264
+ if (statusCode && statusCode >= 500) {
265
+ return true;
266
+ }
267
+
268
+ const message = error instanceof Error ? error.message : String(error);
269
+ return /NoContentGenerated|No content generated|Provider returned error|empty HTML|markdown code fences|full HTML document|conversational response|plain text|aborted|abort|timeout|timed out/i.test(
270
+ message
271
+ );
272
+ }
273
+
274
+ export async function generateEditorHtmlFragment(
275
+ params: GenerateEditorBlocksRequest & {
276
+ apiKey?: string;
277
+ fallbackModelIds?: readonly CortexAiOpenRouterModelId[];
278
+ modelId?: CortexAiOpenRouterModelId;
279
+ modelSelection?: CortexAiStoredModelSelection | null;
280
+ }
281
+ ): Promise<GenerateEditorHtmlFragmentResult> {
282
+ const { apiKey, fallbackModelIds, modelId, modelSelection, ...requestParams } = params;
283
+ const request = generateEditorBlocksRequestSchema.parse(requestParams);
284
+ const client = await createCortexAiOpenRouterClient({ apiKey, modelSelection });
285
+ const routingPolicy = buildCortexAiRoutingPolicy({
286
+ credentialSource: client.credentialSource,
287
+ fallbackModelIds,
288
+ requestedModelId: modelId,
289
+ selectedModel: client.modelSelection,
290
+ });
291
+
292
+ const generation = await runWithCortexAiModelFallback({
293
+ modelIds: routingPolicy.modelIds,
294
+ shouldRetry: isRecoverableHtmlGenerationError,
295
+ execute: async (attemptModelId) => {
296
+ const abortController = new AbortController();
297
+ const timeoutId = setTimeout(
298
+ () => abortController.abort(),
299
+ CORTEX_AI_HTML_GENERATION_ATTEMPT_TIMEOUT_MS
300
+ );
301
+
302
+ try {
303
+ const attemptOptions = omitUnsupportedCortexAiModelOptions(
304
+ {
305
+ abortSignal: abortController.signal,
306
+ maxOutputTokens: 5000,
307
+ maxRetries: 0,
308
+ prompt: buildHtmlGenerationPrompt(request),
309
+ system: buildInlineHtmlAssistantSystemPrompt(),
310
+ temperature: 0.2,
311
+ } as Record<string, unknown>,
312
+ {
313
+ modelId: attemptModelId,
314
+ modelSelection: routingPolicy.modelSelection,
315
+ }
316
+ );
317
+
318
+ const result = await generateText({
319
+ ...attemptOptions,
320
+ model: client.model(attemptModelId),
321
+ } as Parameters<typeof generateText>[0]);
322
+
323
+ return validateGeneratedEditorHtmlFragment(result.text);
324
+ } finally {
325
+ clearTimeout(timeoutId);
326
+ }
327
+ },
328
+ });
329
+
330
+ return {
331
+ attempts: generation.attempts,
332
+ credentialSource: client.credentialSource,
333
+ html: generation.result,
334
+ modelId: generation.modelId,
335
+ };
336
+ }
337
+
338
+ export const INLINE_HTML_ASSISTANT_SYSTEM_PROMPT =
339
+ 'Built at runtime for HTML-fragment rich-text assistance.';