create-smoodly-app 0.0.44 → 0.0.46

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 (387) hide show
  1. package/README.md +15 -10
  2. package/dist/index.js +75 -33
  3. package/package.json +2 -2
  4. package/templates/astro-blank/AGENTS.md +55 -0
  5. package/templates/astro-blank/CLAUDE.md +1 -0
  6. package/templates/astro-blank/README.md +88 -0
  7. package/templates/astro-blank/_gitignore +5 -0
  8. package/templates/astro-blank/astro.config.ts +15 -0
  9. package/templates/astro-blank/package.json +24 -0
  10. package/templates/astro-blank/scripts/seed.ts +80 -0
  11. package/templates/astro-blank/src/components/LocaleNav.astro +15 -0
  12. package/templates/astro-blank/src/components/elements/Button.schema.ts +22 -0
  13. package/templates/astro-blank/src/components/pages/Home.schema.ts +17 -0
  14. package/templates/astro-blank/src/components/pages/Page.schema.ts +14 -0
  15. package/templates/astro-blank/src/components/sections/Text.schema.ts +24 -0
  16. package/templates/astro-blank/src/elements/button.astro +10 -0
  17. package/templates/astro-blank/src/entry-pages/posts.astro +23 -0
  18. package/templates/astro-blank/src/layouts/Layout.astro +25 -0
  19. package/templates/astro-blank/src/live.config.ts +13 -0
  20. package/templates/astro-blank/src/page-types/home.astro +15 -0
  21. package/templates/astro-blank/src/page-types/page.astro +15 -0
  22. package/templates/astro-blank/src/pages/404.astro +7 -0
  23. package/templates/astro-blank/src/sections/text.astro +18 -0
  24. package/templates/astro-blank/src/smoodly/collections.ts +24 -0
  25. package/templates/astro-blank/src/smoodly/config.ts +28 -0
  26. package/templates/astro-blank/src/smoodly/elements.ts +5 -0
  27. package/templates/astro-blank/src/smoodly/globals.ts +18 -0
  28. package/templates/astro-blank/src/smoodly/sections.ts +5 -0
  29. package/templates/astro-blank/src/smoodly/toolsets.ts +14 -0
  30. package/templates/astro-blank/src/styles/global.css +57 -0
  31. package/templates/astro-blank/supabase/migrations/20260926120000_smoodly_v1.sql +535 -0
  32. package/templates/astro-blank/supabase/migrations/20260926130000_smoodly_v2.sql +26 -0
  33. package/templates/astro-blank/supabase/migrations/20260926140000_smoodly_v3.sql +38 -0
  34. package/templates/astro-blank/supabase/migrations/20260926150000_smoodly_v4.sql +41 -0
  35. package/templates/astro-blank/supabase/migrations/20260929120000_smoodly_v5.sql +56 -0
  36. package/templates/astro-blank/supabase/migrations/20260929135523_smoodly_v6.sql +200 -0
  37. package/templates/astro-blank/supabase/migrations/20260930040956_smoodly_v7.sql +84 -0
  38. package/templates/astro-blank/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  39. package/templates/astro-blank/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  40. package/templates/{blank/supabase/migrations/20260930041056_smoodly_views.sql → astro-blank/supabase/migrations/20261007200100_smoodly_views.sql} +1 -1
  41. package/templates/astro-blank/tsconfig.json +17 -0
  42. package/templates/astro-blank-intl/AGENTS.md +55 -0
  43. package/templates/astro-blank-intl/CLAUDE.md +1 -0
  44. package/templates/astro-blank-intl/README.md +89 -0
  45. package/templates/astro-blank-intl/_gitignore +5 -0
  46. package/templates/astro-blank-intl/astro.config.ts +17 -0
  47. package/templates/astro-blank-intl/package.json +24 -0
  48. package/templates/astro-blank-intl/scripts/seed.ts +80 -0
  49. package/templates/astro-blank-intl/src/components/LocaleNav.astro +15 -0
  50. package/templates/astro-blank-intl/src/components/elements/Button.schema.ts +22 -0
  51. package/templates/astro-blank-intl/src/components/pages/Home.schema.ts +17 -0
  52. package/templates/astro-blank-intl/src/components/pages/Page.schema.ts +14 -0
  53. package/templates/astro-blank-intl/src/components/sections/Text.schema.ts +24 -0
  54. package/templates/astro-blank-intl/src/elements/button.astro +10 -0
  55. package/templates/astro-blank-intl/src/entry-pages/posts.astro +23 -0
  56. package/templates/astro-blank-intl/src/layouts/Layout.astro +25 -0
  57. package/templates/astro-blank-intl/src/live.config.ts +13 -0
  58. package/templates/astro-blank-intl/src/page-types/home.astro +15 -0
  59. package/templates/astro-blank-intl/src/page-types/page.astro +15 -0
  60. package/templates/astro-blank-intl/src/pages/404.astro +7 -0
  61. package/templates/astro-blank-intl/src/sections/text.astro +18 -0
  62. package/templates/astro-blank-intl/src/smoodly/collections.ts +24 -0
  63. package/templates/astro-blank-intl/src/smoodly/config.ts +29 -0
  64. package/templates/astro-blank-intl/src/smoodly/elements.ts +5 -0
  65. package/templates/astro-blank-intl/src/smoodly/globals.ts +18 -0
  66. package/templates/astro-blank-intl/src/smoodly/sections.ts +5 -0
  67. package/templates/astro-blank-intl/src/smoodly/toolsets.ts +14 -0
  68. package/templates/astro-blank-intl/src/styles/global.css +57 -0
  69. package/templates/astro-blank-intl/supabase/migrations/20260926120000_smoodly_v1.sql +535 -0
  70. package/templates/astro-blank-intl/supabase/migrations/20260926130000_smoodly_v2.sql +26 -0
  71. package/templates/astro-blank-intl/supabase/migrations/20260926140000_smoodly_v3.sql +38 -0
  72. package/templates/astro-blank-intl/supabase/migrations/20260926150000_smoodly_v4.sql +41 -0
  73. package/templates/astro-blank-intl/supabase/migrations/20260929120000_smoodly_v5.sql +56 -0
  74. package/templates/astro-blank-intl/supabase/migrations/20260929135523_smoodly_v6.sql +200 -0
  75. package/templates/astro-blank-intl/supabase/migrations/20260930040956_smoodly_v7.sql +84 -0
  76. package/templates/astro-blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  77. package/templates/astro-blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  78. package/templates/{blank-intl/supabase/migrations/20260930041056_smoodly_views.sql → astro-blank-intl/supabase/migrations/20261007200100_smoodly_views.sql} +1 -1
  79. package/templates/astro-blank-intl/tsconfig.json +17 -0
  80. package/templates/astro-blank-intl.tsconfig.json +24 -0
  81. package/templates/astro-blank.tsconfig.json +24 -0
  82. package/templates/astro-lorem/AGENTS.md +55 -0
  83. package/templates/astro-lorem/CLAUDE.md +1 -0
  84. package/templates/astro-lorem/README.md +60 -0
  85. package/templates/astro-lorem/_gitignore +5 -0
  86. package/templates/astro-lorem/astro.config.ts +15 -0
  87. package/templates/astro-lorem/package.json +24 -0
  88. package/templates/astro-lorem/public/images/about_main.jpg +0 -0
  89. package/templates/astro-lorem/public/images/article_photo.jpg +0 -0
  90. package/templates/astro-lorem/public/images/hero_image.jpg +0 -0
  91. package/templates/astro-lorem/public/images/home_section_2.jpg +0 -0
  92. package/templates/astro-lorem/scripts/seed.ts +405 -0
  93. package/templates/astro-lorem/src/components/ArticleCard.astro +19 -0
  94. package/templates/astro-lorem/src/components/articles.ts +42 -0
  95. package/templates/astro-lorem/src/components/elements/Callout.schema.ts +24 -0
  96. package/templates/astro-lorem/src/components/elements/Cta.schema.ts +21 -0
  97. package/templates/astro-lorem/src/components/news.ts +13 -0
  98. package/templates/astro-lorem/src/components/pages/Articles.schema.ts +20 -0
  99. package/templates/astro-lorem/src/components/pages/Home.schema.ts +26 -0
  100. package/templates/astro-lorem/src/components/pages/News.schema.ts +17 -0
  101. package/templates/astro-lorem/src/components/pages/Page.schema.ts +24 -0
  102. package/templates/astro-lorem/src/components/sections/FavouriteArticles.schema.ts +17 -0
  103. package/templates/astro-lorem/src/components/sections/Footer.schema.ts +31 -0
  104. package/templates/astro-lorem/src/components/sections/Header.schema.ts +21 -0
  105. package/templates/astro-lorem/src/components/sections/Hero.schema.ts +24 -0
  106. package/templates/astro-lorem/src/components/sections/ImageText.schema.ts +30 -0
  107. package/templates/astro-lorem/src/components/sections/PageHero.schema.ts +21 -0
  108. package/templates/astro-lorem/src/components/sections/People.schema.ts +15 -0
  109. package/templates/astro-lorem/src/components/sections/Points.schema.ts +21 -0
  110. package/templates/astro-lorem/src/components/sections/Quote.schema.ts +23 -0
  111. package/templates/astro-lorem/src/elements/callout.astro +13 -0
  112. package/templates/astro-lorem/src/elements/cta.astro +15 -0
  113. package/templates/astro-lorem/src/entry-pages/articles.astro +71 -0
  114. package/templates/astro-lorem/src/entry-pages/news.astro +29 -0
  115. package/templates/astro-lorem/src/layouts/Layout.astro +39 -0
  116. package/templates/astro-lorem/src/live.config.ts +20 -0
  117. package/templates/astro-lorem/src/messages/en.json +7 -0
  118. package/templates/astro-lorem/src/messages/fi.json +7 -0
  119. package/templates/astro-lorem/src/page-types/articlesIndex.astro +24 -0
  120. package/templates/astro-lorem/src/page-types/home.astro +14 -0
  121. package/templates/astro-lorem/src/page-types/newsIndex.astro +28 -0
  122. package/templates/astro-lorem/src/page-types/page.astro +27 -0
  123. package/templates/astro-lorem/src/pages/404.astro +7 -0
  124. package/templates/astro-lorem/src/sections/favouriteArticles.astro +26 -0
  125. package/templates/astro-lorem/src/sections/footer.astro +37 -0
  126. package/templates/astro-lorem/src/sections/header.astro +27 -0
  127. package/templates/astro-lorem/src/sections/hero.astro +28 -0
  128. package/templates/astro-lorem/src/sections/imageText.astro +22 -0
  129. package/templates/astro-lorem/src/sections/pageHero.astro +20 -0
  130. package/templates/astro-lorem/src/sections/peopleSection.astro +24 -0
  131. package/templates/astro-lorem/src/sections/points.astro +33 -0
  132. package/templates/astro-lorem/src/sections/quote.astro +16 -0
  133. package/templates/astro-lorem/src/smoodly/collections.ts +63 -0
  134. package/templates/astro-lorem/src/smoodly/config.ts +48 -0
  135. package/templates/astro-lorem/src/smoodly/elements.ts +6 -0
  136. package/templates/astro-lorem/src/smoodly/globals.ts +29 -0
  137. package/templates/astro-lorem/src/smoodly/sections.ts +18 -0
  138. package/templates/astro-lorem/src/smoodly/toolsets.ts +16 -0
  139. package/templates/astro-lorem/src/strings.ts +17 -0
  140. package/templates/astro-lorem/src/styles/global.css +228 -0
  141. package/templates/astro-lorem/supabase/migrations/20260926120000_smoodly_v1.sql +535 -0
  142. package/templates/astro-lorem/supabase/migrations/20260926130000_smoodly_v2.sql +26 -0
  143. package/templates/astro-lorem/supabase/migrations/20260926140000_smoodly_v3.sql +38 -0
  144. package/templates/astro-lorem/supabase/migrations/20260926150000_smoodly_v4.sql +41 -0
  145. package/templates/astro-lorem/supabase/migrations/20260929120000_smoodly_v5.sql +56 -0
  146. package/templates/astro-lorem/supabase/migrations/20260929135523_smoodly_v6.sql +200 -0
  147. package/templates/astro-lorem/supabase/migrations/20260930040956_smoodly_v7.sql +84 -0
  148. package/templates/astro-lorem/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  149. package/templates/astro-lorem/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  150. package/templates/{lorem/supabase/migrations/20260930041056_smoodly_views.sql → astro-lorem/supabase/migrations/20261007200100_smoodly_views.sql} +1 -1
  151. package/templates/astro-lorem/tsconfig.json +17 -0
  152. package/templates/astro-lorem-intl/AGENTS.md +55 -0
  153. package/templates/astro-lorem-intl/CLAUDE.md +1 -0
  154. package/templates/astro-lorem-intl/README.md +62 -0
  155. package/templates/astro-lorem-intl/_gitignore +5 -0
  156. package/templates/astro-lorem-intl/astro.config.ts +17 -0
  157. package/templates/astro-lorem-intl/package.json +24 -0
  158. package/templates/astro-lorem-intl/public/images/about_main.jpg +0 -0
  159. package/templates/astro-lorem-intl/public/images/article_photo.jpg +0 -0
  160. package/templates/astro-lorem-intl/public/images/hero_image.jpg +0 -0
  161. package/templates/astro-lorem-intl/public/images/home_section_2.jpg +0 -0
  162. package/templates/astro-lorem-intl/scripts/seed.ts +405 -0
  163. package/templates/astro-lorem-intl/src/components/ArticleCard.astro +19 -0
  164. package/templates/astro-lorem-intl/src/components/articles.ts +42 -0
  165. package/templates/astro-lorem-intl/src/components/elements/Callout.schema.ts +24 -0
  166. package/templates/astro-lorem-intl/src/components/elements/Cta.schema.ts +21 -0
  167. package/templates/astro-lorem-intl/src/components/news.ts +13 -0
  168. package/templates/astro-lorem-intl/src/components/pages/Articles.schema.ts +20 -0
  169. package/templates/astro-lorem-intl/src/components/pages/Home.schema.ts +26 -0
  170. package/templates/astro-lorem-intl/src/components/pages/News.schema.ts +17 -0
  171. package/templates/astro-lorem-intl/src/components/pages/Page.schema.ts +24 -0
  172. package/templates/astro-lorem-intl/src/components/sections/FavouriteArticles.schema.ts +17 -0
  173. package/templates/astro-lorem-intl/src/components/sections/Footer.schema.ts +31 -0
  174. package/templates/astro-lorem-intl/src/components/sections/Header.schema.ts +21 -0
  175. package/templates/astro-lorem-intl/src/components/sections/Hero.schema.ts +24 -0
  176. package/templates/astro-lorem-intl/src/components/sections/ImageText.schema.ts +30 -0
  177. package/templates/astro-lorem-intl/src/components/sections/PageHero.schema.ts +21 -0
  178. package/templates/astro-lorem-intl/src/components/sections/People.schema.ts +15 -0
  179. package/templates/astro-lorem-intl/src/components/sections/Points.schema.ts +21 -0
  180. package/templates/astro-lorem-intl/src/components/sections/Quote.schema.ts +23 -0
  181. package/templates/astro-lorem-intl/src/elements/callout.astro +13 -0
  182. package/templates/astro-lorem-intl/src/elements/cta.astro +15 -0
  183. package/templates/astro-lorem-intl/src/entry-pages/articles.astro +71 -0
  184. package/templates/astro-lorem-intl/src/entry-pages/news.astro +29 -0
  185. package/templates/astro-lorem-intl/src/layouts/Layout.astro +39 -0
  186. package/templates/astro-lorem-intl/src/live.config.ts +20 -0
  187. package/templates/astro-lorem-intl/src/messages/en.json +7 -0
  188. package/templates/astro-lorem-intl/src/messages/fi.json +7 -0
  189. package/templates/astro-lorem-intl/src/page-types/articlesIndex.astro +24 -0
  190. package/templates/astro-lorem-intl/src/page-types/home.astro +14 -0
  191. package/templates/astro-lorem-intl/src/page-types/newsIndex.astro +28 -0
  192. package/templates/astro-lorem-intl/src/page-types/page.astro +27 -0
  193. package/templates/astro-lorem-intl/src/pages/404.astro +7 -0
  194. package/templates/astro-lorem-intl/src/sections/favouriteArticles.astro +26 -0
  195. package/templates/astro-lorem-intl/src/sections/footer.astro +37 -0
  196. package/templates/astro-lorem-intl/src/sections/header.astro +27 -0
  197. package/templates/astro-lorem-intl/src/sections/hero.astro +28 -0
  198. package/templates/astro-lorem-intl/src/sections/imageText.astro +22 -0
  199. package/templates/astro-lorem-intl/src/sections/pageHero.astro +20 -0
  200. package/templates/astro-lorem-intl/src/sections/peopleSection.astro +24 -0
  201. package/templates/astro-lorem-intl/src/sections/points.astro +33 -0
  202. package/templates/astro-lorem-intl/src/sections/quote.astro +16 -0
  203. package/templates/astro-lorem-intl/src/smoodly/collections.ts +63 -0
  204. package/templates/astro-lorem-intl/src/smoodly/config.ts +48 -0
  205. package/templates/astro-lorem-intl/src/smoodly/elements.ts +6 -0
  206. package/templates/astro-lorem-intl/src/smoodly/globals.ts +29 -0
  207. package/templates/astro-lorem-intl/src/smoodly/sections.ts +18 -0
  208. package/templates/astro-lorem-intl/src/smoodly/toolsets.ts +16 -0
  209. package/templates/astro-lorem-intl/src/strings.ts +17 -0
  210. package/templates/astro-lorem-intl/src/styles/global.css +228 -0
  211. package/templates/astro-lorem-intl/supabase/migrations/20260926120000_smoodly_v1.sql +535 -0
  212. package/templates/astro-lorem-intl/supabase/migrations/20260926130000_smoodly_v2.sql +26 -0
  213. package/templates/astro-lorem-intl/supabase/migrations/20260926140000_smoodly_v3.sql +38 -0
  214. package/templates/astro-lorem-intl/supabase/migrations/20260926150000_smoodly_v4.sql +41 -0
  215. package/templates/astro-lorem-intl/supabase/migrations/20260929120000_smoodly_v5.sql +56 -0
  216. package/templates/astro-lorem-intl/supabase/migrations/20260929135523_smoodly_v6.sql +200 -0
  217. package/templates/astro-lorem-intl/supabase/migrations/20260930040956_smoodly_v7.sql +84 -0
  218. package/templates/astro-lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  219. package/templates/astro-lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  220. package/templates/{lorem-intl/supabase/migrations/20260930041056_smoodly_views.sql → astro-lorem-intl/supabase/migrations/20261007200100_smoodly_views.sql} +1 -1
  221. package/templates/astro-lorem-intl/tsconfig.json +17 -0
  222. package/templates/astro-lorem-intl.tsconfig.json +24 -0
  223. package/templates/astro-lorem.tsconfig.json +24 -0
  224. package/templates/blank/AGENTS.md +23 -16
  225. package/templates/blank/README.md +20 -15
  226. package/templates/blank/app/(smoodly)/admin/[[...segments]]/route.ts +8 -0
  227. package/templates/blank/components/elements/Button.schema.ts +22 -0
  228. package/templates/blank/components/elements/Button.tsx +2 -20
  229. package/templates/blank/components/pages/Home.schema.ts +17 -0
  230. package/templates/blank/components/pages/Home.tsx +2 -16
  231. package/templates/blank/components/pages/Page.schema.ts +14 -0
  232. package/templates/blank/components/pages/Page.tsx +2 -14
  233. package/templates/blank/components/pages/Post.tsx +5 -4
  234. package/templates/blank/components/sections/Text.schema.ts +24 -0
  235. package/templates/blank/components/sections/Text.tsx +7 -27
  236. package/templates/blank/package.json +1 -1
  237. package/templates/blank/scripts/seed.ts +2 -2
  238. package/templates/blank/smoodly/config.ts +8 -7
  239. package/templates/blank/smoodly/elements.ts +4 -4
  240. package/templates/blank/smoodly/globals.ts +4 -2
  241. package/templates/blank/smoodly/richtext.tsx +4 -5
  242. package/templates/blank/smoodly/sections.ts +4 -4
  243. package/templates/blank/smoodly/server.ts +22 -44
  244. package/templates/blank/smoodly/toolsets.ts +2 -2
  245. package/templates/blank/smoodly/views.ts +15 -0
  246. package/templates/blank/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  247. package/templates/blank/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  248. package/templates/blank/supabase/migrations/20261007200100_smoodly_views.sql +20 -0
  249. package/templates/blank-intl/AGENTS.md +23 -16
  250. package/templates/blank-intl/README.md +20 -15
  251. package/templates/blank-intl/app/(smoodly)/admin/[[...segments]]/route.ts +8 -0
  252. package/templates/blank-intl/components/elements/Button.schema.ts +22 -0
  253. package/templates/blank-intl/components/elements/Button.tsx +2 -20
  254. package/templates/blank-intl/components/pages/Home.schema.ts +17 -0
  255. package/templates/blank-intl/components/pages/Home.tsx +2 -16
  256. package/templates/blank-intl/components/pages/Page.schema.ts +14 -0
  257. package/templates/blank-intl/components/pages/Page.tsx +2 -14
  258. package/templates/blank-intl/components/pages/Post.tsx +5 -4
  259. package/templates/blank-intl/components/sections/Text.schema.ts +24 -0
  260. package/templates/blank-intl/components/sections/Text.tsx +7 -27
  261. package/templates/blank-intl/package.json +1 -1
  262. package/templates/blank-intl/scripts/seed.ts +2 -2
  263. package/templates/blank-intl/smoodly/config.ts +8 -7
  264. package/templates/blank-intl/smoodly/elements.ts +4 -4
  265. package/templates/blank-intl/smoodly/globals.ts +4 -2
  266. package/templates/blank-intl/smoodly/richtext.tsx +4 -5
  267. package/templates/blank-intl/smoodly/sections.ts +4 -4
  268. package/templates/blank-intl/smoodly/server.ts +22 -44
  269. package/templates/blank-intl/smoodly/toolsets.ts +2 -2
  270. package/templates/blank-intl/smoodly/views.ts +15 -0
  271. package/templates/blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  272. package/templates/blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  273. package/templates/blank-intl/supabase/migrations/20261007200100_smoodly_views.sql +20 -0
  274. package/templates/lorem/AGENTS.md +23 -16
  275. package/templates/lorem/README.md +3 -3
  276. package/templates/lorem/app/(smoodly)/admin/[[...segments]]/route.ts +8 -0
  277. package/templates/lorem/components/elements/Callout.schema.ts +24 -0
  278. package/templates/lorem/components/elements/Callout.tsx +2 -24
  279. package/templates/lorem/components/elements/Cta.schema.ts +21 -0
  280. package/templates/lorem/components/elements/Cta.tsx +2 -21
  281. package/templates/lorem/components/pages/Article.tsx +1 -1
  282. package/templates/lorem/components/pages/Articles.schema.ts +20 -0
  283. package/templates/lorem/components/pages/Articles.tsx +2 -20
  284. package/templates/lorem/components/pages/Home.schema.ts +26 -0
  285. package/templates/lorem/components/pages/Home.tsx +2 -26
  286. package/templates/lorem/components/pages/News.schema.ts +17 -0
  287. package/templates/lorem/components/pages/News.tsx +2 -17
  288. package/templates/lorem/components/pages/NewsPost.tsx +1 -1
  289. package/templates/lorem/components/pages/Page.schema.ts +24 -0
  290. package/templates/lorem/components/pages/Page.tsx +2 -24
  291. package/templates/lorem/components/sections/FavouriteArticles.schema.ts +17 -0
  292. package/templates/lorem/components/sections/FavouriteArticles.tsx +2 -17
  293. package/templates/lorem/components/sections/Footer.schema.ts +31 -0
  294. package/templates/lorem/components/sections/Footer.tsx +4 -32
  295. package/templates/lorem/components/sections/Header.schema.ts +21 -0
  296. package/templates/lorem/components/sections/Header.tsx +2 -21
  297. package/templates/lorem/components/sections/Hero.schema.ts +24 -0
  298. package/templates/lorem/components/sections/Hero.tsx +2 -24
  299. package/templates/lorem/components/sections/ImageText.schema.ts +30 -0
  300. package/templates/lorem/components/sections/ImageText.tsx +2 -30
  301. package/templates/lorem/components/sections/PageHero.schema.ts +21 -0
  302. package/templates/lorem/components/sections/PageHero.tsx +2 -21
  303. package/templates/lorem/components/sections/People.schema.ts +15 -0
  304. package/templates/lorem/components/sections/People.tsx +2 -14
  305. package/templates/lorem/components/sections/Points.schema.ts +21 -0
  306. package/templates/lorem/components/sections/Points.tsx +2 -21
  307. package/templates/lorem/components/sections/Quote.schema.ts +23 -0
  308. package/templates/lorem/components/sections/Quote.tsx +2 -23
  309. package/templates/lorem/package.json +1 -1
  310. package/templates/lorem/scripts/seed.ts +18 -18
  311. package/templates/lorem/smoodly/collections.ts +2 -2
  312. package/templates/lorem/smoodly/config.ts +14 -15
  313. package/templates/lorem/smoodly/elements.ts +5 -5
  314. package/templates/lorem/smoodly/globals.ts +10 -7
  315. package/templates/lorem/smoodly/richtext.tsx +5 -5
  316. package/templates/lorem/smoodly/sections.ts +12 -13
  317. package/templates/lorem/smoodly/server.ts +22 -44
  318. package/templates/lorem/smoodly/toolsets.ts +3 -3
  319. package/templates/lorem/smoodly/views.ts +33 -0
  320. package/templates/lorem/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  321. package/templates/lorem/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  322. package/templates/lorem/supabase/migrations/20261007200100_smoodly_views.sql +58 -0
  323. package/templates/lorem-intl/AGENTS.md +23 -16
  324. package/templates/lorem-intl/README.md +3 -3
  325. package/templates/lorem-intl/app/(smoodly)/admin/[[...segments]]/route.ts +8 -0
  326. package/templates/lorem-intl/components/elements/Callout.schema.ts +24 -0
  327. package/templates/lorem-intl/components/elements/Callout.tsx +2 -24
  328. package/templates/lorem-intl/components/elements/Cta.schema.ts +21 -0
  329. package/templates/lorem-intl/components/elements/Cta.tsx +2 -21
  330. package/templates/lorem-intl/components/pages/Article.tsx +1 -1
  331. package/templates/lorem-intl/components/pages/Articles.schema.ts +20 -0
  332. package/templates/lorem-intl/components/pages/Articles.tsx +2 -20
  333. package/templates/lorem-intl/components/pages/Home.schema.ts +26 -0
  334. package/templates/lorem-intl/components/pages/Home.tsx +2 -26
  335. package/templates/lorem-intl/components/pages/News.schema.ts +17 -0
  336. package/templates/lorem-intl/components/pages/News.tsx +2 -17
  337. package/templates/lorem-intl/components/pages/NewsPost.tsx +1 -1
  338. package/templates/lorem-intl/components/pages/Page.schema.ts +24 -0
  339. package/templates/lorem-intl/components/pages/Page.tsx +2 -24
  340. package/templates/lorem-intl/components/sections/FavouriteArticles.schema.ts +17 -0
  341. package/templates/lorem-intl/components/sections/FavouriteArticles.tsx +2 -17
  342. package/templates/lorem-intl/components/sections/Footer.schema.ts +31 -0
  343. package/templates/lorem-intl/components/sections/Footer.tsx +4 -32
  344. package/templates/lorem-intl/components/sections/Header.schema.ts +21 -0
  345. package/templates/lorem-intl/components/sections/Header.tsx +2 -21
  346. package/templates/lorem-intl/components/sections/Hero.schema.ts +24 -0
  347. package/templates/lorem-intl/components/sections/Hero.tsx +2 -24
  348. package/templates/lorem-intl/components/sections/ImageText.schema.ts +30 -0
  349. package/templates/lorem-intl/components/sections/ImageText.tsx +2 -30
  350. package/templates/lorem-intl/components/sections/PageHero.schema.ts +21 -0
  351. package/templates/lorem-intl/components/sections/PageHero.tsx +2 -21
  352. package/templates/lorem-intl/components/sections/People.schema.ts +15 -0
  353. package/templates/lorem-intl/components/sections/People.tsx +2 -14
  354. package/templates/lorem-intl/components/sections/Points.schema.ts +21 -0
  355. package/templates/lorem-intl/components/sections/Points.tsx +2 -21
  356. package/templates/lorem-intl/components/sections/Quote.schema.ts +23 -0
  357. package/templates/lorem-intl/components/sections/Quote.tsx +2 -23
  358. package/templates/lorem-intl/package.json +1 -1
  359. package/templates/lorem-intl/scripts/seed.ts +18 -18
  360. package/templates/lorem-intl/smoodly/collections.ts +2 -2
  361. package/templates/lorem-intl/smoodly/config.ts +14 -15
  362. package/templates/lorem-intl/smoodly/elements.ts +5 -5
  363. package/templates/lorem-intl/smoodly/globals.ts +10 -7
  364. package/templates/lorem-intl/smoodly/richtext.tsx +5 -5
  365. package/templates/lorem-intl/smoodly/sections.ts +12 -13
  366. package/templates/lorem-intl/smoodly/server.ts +22 -44
  367. package/templates/lorem-intl/smoodly/toolsets.ts +3 -3
  368. package/templates/lorem-intl/smoodly/views.ts +33 -0
  369. package/templates/lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  370. package/templates/lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  371. package/templates/lorem-intl/supabase/migrations/20261007200100_smoodly_views.sql +58 -0
  372. package/templates/blank/app/(smoodly)/admin/[[...segments]]/page.tsx +0 -1
  373. package/templates/blank/app/(smoodly)/admin/layout.tsx +0 -15
  374. package/templates/blank/smoodly/admin.tsx +0 -6
  375. package/templates/blank/smoodly/ops.ts +0 -11
  376. package/templates/blank-intl/app/(smoodly)/admin/[[...segments]]/page.tsx +0 -1
  377. package/templates/blank-intl/app/(smoodly)/admin/layout.tsx +0 -15
  378. package/templates/blank-intl/smoodly/admin.tsx +0 -6
  379. package/templates/blank-intl/smoodly/ops.ts +0 -11
  380. package/templates/lorem/app/(smoodly)/admin/[[...segments]]/page.tsx +0 -1
  381. package/templates/lorem/app/(smoodly)/admin/layout.tsx +0 -15
  382. package/templates/lorem/smoodly/admin.tsx +0 -6
  383. package/templates/lorem/smoodly/ops.ts +0 -11
  384. package/templates/lorem-intl/app/(smoodly)/admin/[[...segments]]/page.tsx +0 -1
  385. package/templates/lorem-intl/app/(smoodly)/admin/layout.tsx +0 -15
  386. package/templates/lorem-intl/smoodly/admin.tsx +0 -6
  387. package/templates/lorem-intl/smoodly/ops.ts +0 -11
@@ -0,0 +1,154 @@
1
+ -- Smoodly core schema, version 8 (docs/superpowers/specs/2026-10-07-delivery-model-design.md).
2
+ -- Shipped in the smoodly package; `npx smoodly migrate` copies it into supabase/migrations/.
3
+ -- Never edit a published file: the next change is the next file.
4
+ -- The delta over version 7: the delivery model's two derived tables,
5
+ -- published_pages and published_entries, with the space trigger and the
6
+ -- write-key-only policies every content table has (no row cap: they are
7
+ -- derived), smoodly_list_published over the raw published snapshot, and
8
+ -- the published_routes view, one lookup for any address.
9
+
10
+ set client_min_messages = warning;
11
+
12
+ -- The delivery model (spec 2026-10-07): Publish writes the finished document of a page, an entry
13
+ -- or a global here; a live read is one lookup. Derived and rebuildable from the editing tables.
14
+ create table if not exists "published_pages" (
15
+ "space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
16
+ "page_id" uuid not null references "pages" ("id") on delete cascade,
17
+ "locale" text not null,
18
+ "parent_id" uuid,
19
+ "sort" numeric,
20
+ "path" text,
21
+ "title" text not null,
22
+ "has_page" boolean not null,
23
+ "link_fields" jsonb not null default '{}'::jsonb,
24
+ "document" jsonb,
25
+ "tags" text[] not null default '{}',
26
+ "model" text not null,
27
+ "stale" boolean not null default false,
28
+ "published_at" timestamptz not null default now(),
29
+ primary key ("page_id", "locale")
30
+ );
31
+ create unique index if not exists "published_pages_address_idx"
32
+ on "published_pages" ("space_id", "locale", "path") where "path" is not null;
33
+ create index if not exists "published_pages_tree_idx"
34
+ on "published_pages" ("space_id", "locale", "parent_id", "sort");
35
+
36
+ create table if not exists "published_entries" (
37
+ "space_id" uuid not null default '00000000-0000-0000-0000-000000000000',
38
+ "entry_id" uuid not null references "entries" ("id") on delete cascade,
39
+ "locale" text not null,
40
+ "collection" text not null,
41
+ "parent_id" uuid,
42
+ "sort" numeric,
43
+ "slug" text,
44
+ "path" text,
45
+ "created_at" timestamptz not null,
46
+ "updated_at" timestamptz not null,
47
+ "fields" jsonb not null,
48
+ "document" jsonb not null,
49
+ "tags" text[] not null default '{}',
50
+ "model" text not null,
51
+ "stale" boolean not null default false,
52
+ "published_at" timestamptz not null default now(),
53
+ primary key ("entry_id", "locale")
54
+ );
55
+ create index if not exists "published_entries_list_idx"
56
+ on "published_entries" ("space_id", "collection", "locale");
57
+ create unique index if not exists "published_entries_address_idx"
58
+ on "published_entries" ("space_id", "locale", "path") where "path" is not null;
59
+ create index if not exists "published_entries_fields_idx"
60
+ on "published_entries" using gin ("fields");
61
+
62
+ alter table "published_pages" enable row level security;
63
+ alter table "published_entries" enable row level security;
64
+
65
+ -- smoodly_list_entries without the draft branch, over the delivery table (spec 2026-10-07 §3):
66
+ -- `where` and `order` read the raw published snapshot, the row answers with its document.
67
+ -- Invoker rights, so RLS decides what a key sees.
68
+ create or replace function "smoodly_list_published"(
69
+ p_collection text, p_locale text, p_where jsonb, p_order jsonb,
70
+ p_limit integer, p_parent uuid, p_has_parent boolean, p_ids uuid[]
71
+ ) returns table ("id" uuid, "document" jsonb, "model" text, "stale" boolean) language sql stable set search_path = public as $$
72
+ with shown as (
73
+ select p."entry_id" as "id", p."sort", p."created_at", p."updated_at", p."fields", p."document", p."model", p."stale"
74
+ from "published_entries" p
75
+ where p."collection" = p_collection and p."locale" = p_locale
76
+ and (not p_has_parent or p."parent_id" is not distinct from p_parent)
77
+ and (p_ids is null or p."entry_id" = any(p_ids))
78
+ )
79
+ select s."id", s."document", s."model", s."stale" from shown s
80
+ where not exists (
81
+ select 1 from jsonb_each(coalesce(p_where, '{}'::jsonb)) w
82
+ where not (case when w.value ? 'in'
83
+ then (s."fields" ->> w.key) = any (select jsonb_array_elements_text(w.value -> 'in'))
84
+ else s."fields" @> jsonb_build_object(w.key, w.value -> 'contains') end))
85
+ order by
86
+ case when p_order ->> 'by' = 'manual' then s."sort" end asc nulls last,
87
+ case when p_order ->> 'direction' = 'asc' then
88
+ case p_order ->> 'by' when 'createdAt' then to_jsonb(s."created_at") when 'updatedAt' then to_jsonb(s."updated_at")
89
+ when 'manual' then null else nullif(nullif(s."fields" -> (p_order ->> 'by'), 'null'::jsonb), '""'::jsonb) end end asc nulls last,
90
+ case when p_order ->> 'direction' = 'desc' then
91
+ case p_order ->> 'by' when 'createdAt' then to_jsonb(s."created_at") when 'updatedAt' then to_jsonb(s."updated_at")
92
+ when 'manual' then null else nullif(nullif(s."fields" -> (p_order ->> 'by'), 'null'::jsonb), '""'::jsonb) end end desc nulls last,
93
+ s."created_at" asc, s."id" asc
94
+ limit p_limit;
95
+ $$;
96
+
97
+ -- One lookup for any address (spec 2026-10-07 §3): a page or an entry, under the caller's own policies.
98
+ create or replace view "published_routes" with (security_invoker = true) as
99
+ select "space_id", "locale", "path", 'page' as "kind", "page_id" as "id", "document", "model", "stale", "tags", null::text as "collection"
100
+ from "published_pages" where "path" is not null and "has_page"
101
+ union all
102
+ select "space_id", "locale", "path", 'entry' as "kind", "entry_id" as "id", "document", "model", "stale", "tags", "collection"
103
+ from "published_entries" where "path" is not null;
104
+
105
+ -- Stamped with the caller's space on insert, as every content table.
106
+ drop trigger if exists "smoodly_a_set_space" on "published_pages";
107
+ create trigger "smoodly_a_set_space" before insert on "published_pages"
108
+ for each row execute function "smoodly_set_space"();
109
+
110
+ drop trigger if exists "smoodly_a_set_space" on "published_entries";
111
+ create trigger "smoodly_a_set_space" before insert on "published_entries"
112
+ for each row execute function "smoodly_set_space"();
113
+
114
+ -- A write key reads and writes its own space; no other key reads (spec 2026-09-29 content API contract §8).
115
+ drop policy if exists "smoodly_write" on "published_pages";
116
+
117
+ drop policy if exists "smoodly_insert" on "published_pages";
118
+ create policy "smoodly_insert" on "published_pages" for insert
119
+ with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
120
+
121
+ drop policy if exists "smoodly_update" on "published_pages";
122
+ create policy "smoodly_update" on "published_pages" for update
123
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
124
+ with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
125
+
126
+ drop policy if exists "smoodly_delete" on "published_pages";
127
+ create policy "smoodly_delete" on "published_pages" for delete
128
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
129
+
130
+ drop policy if exists "smoodly_read" on "published_pages";
131
+ create policy "smoodly_read" on "published_pages" for select
132
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
133
+
134
+ drop policy if exists "smoodly_write" on "published_entries";
135
+
136
+ drop policy if exists "smoodly_insert" on "published_entries";
137
+ create policy "smoodly_insert" on "published_entries" for insert
138
+ with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
139
+
140
+ drop policy if exists "smoodly_update" on "published_entries";
141
+ create policy "smoodly_update" on "published_entries" for update
142
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
143
+ with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
144
+
145
+ drop policy if exists "smoodly_delete" on "published_entries";
146
+ create policy "smoodly_delete" on "published_entries" for delete
147
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
148
+
149
+ drop policy if exists "smoodly_read" on "published_entries";
150
+ create policy "smoodly_read" on "published_entries" for select
151
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
152
+
153
+ insert into "smoodly_meta" ("key", "value") values ('schema_version', '8')
154
+ on conflict ("key") do update set "value" = excluded."value";
@@ -0,0 +1,30 @@
1
+ -- Smoodly core schema, version 9 (packages/smoodly/MAINTAINERS.md, register 2026-10-07: what the delivery model left).
2
+ -- Shipped in the smoodly package; `npx smoodly migrate` copies it into supabase/migrations/.
3
+ -- Never edit a published file: the next change is the next file.
4
+ -- The delta over version 8: smoodly_lookup_address, the route row and the
5
+ -- locale's global items in one call, so a live page is one database call
6
+ -- instead of the page then its layout's globals.
7
+
8
+ set client_min_messages = warning;
9
+
10
+ -- The address and the locale's global items in one call (schema v9; register 2026-10-07: a miss was two
11
+ -- rounds, the page then the layout's globals). The route row first, kind 'page' or 'entry'; then one row per
12
+ -- named global, kind 'global', the collection its name. A miss answers nothing: the globals ride with a page
13
+ -- only. Invoker rights, so RLS decides what a key sees.
14
+ create or replace function "smoodly_lookup_address"(p_locale text, p_path text, p_globals text[])
15
+ returns table ("kind" text, "id" uuid, "collection" text, "document" jsonb, "model" text, "stale" boolean, "tags" text[])
16
+ language sql stable set search_path = public as $$
17
+ with route as (
18
+ select r."kind", r."id", r."collection", r."document", r."model", r."stale", r."tags"
19
+ from "published_routes" r where r."locale" = p_locale and r."path" = p_path limit 1
20
+ )
21
+ select * from route
22
+ union all
23
+ select 'global' as "kind", e."entry_id", e."collection", e."document", e."model", e."stale", e."tags"
24
+ from "published_entries" e
25
+ where e."locale" = p_locale and e."collection" = any(coalesce(p_globals, '{}'::text[]))
26
+ and exists (select 1 from route);
27
+ $$;
28
+
29
+ insert into "smoodly_meta" ("key", "value") values ('schema_version', '9')
30
+ on conflict ("key") do update set "value" = excluded."value";
@@ -0,0 +1,58 @@
1
+ -- Smoodly: read views for this app's collections, written by `npx smoodly migrate`
2
+ -- (2026-10-07T20:01:00.000Z). One block per collection; the marker's hash is the block's SQL, compared on the next run.
3
+ -- Never edit a written file: change the collection and run migrate again.
4
+
5
+ -- smoodly:collection people sha256:929798ebb4fa2bb6
6
+ create or replace view "people" with (security_invoker = true) as
7
+ select
8
+ e."id",
9
+ l."locale",
10
+ l."slug",
11
+ e."sort",
12
+ e."created_at",
13
+ l."updated_at",
14
+ coalesce(v."fields", e."fields" || l."fields")->>'name' as "name",
15
+ coalesce(v."fields", e."fields" || l."fields")->>'role' as "role"
16
+ from "entries" e
17
+ join "entry_locales" l on l."entry_id" = e."id"
18
+ left join "entry_versions" v on v."id" = l."published_version_id"
19
+ where e."collection" = 'people' and l."status" = 'published';
20
+
21
+ -- smoodly:collection articles sha256:dcbe071d70e13943
22
+ create or replace view "articles" with (security_invoker = true) as
23
+ select
24
+ e."id",
25
+ l."locale",
26
+ l."slug",
27
+ e."sort",
28
+ e."created_at",
29
+ l."updated_at",
30
+ coalesce(v."fields", e."fields" || l."fields")->>'title' as "title",
31
+ coalesce(v."fields", e."fields" || l."fields")->'image' as "image",
32
+ (coalesce(v."fields", e."fields" || l."fields")->>'date')::timestamptz as "date",
33
+ coalesce(v."fields", e."fields" || l."fields")->>'author' as "author",
34
+ coalesce(v."fields", e."fields" || l."fields")->>'excerpt' as "excerpt",
35
+ coalesce(v."fields", e."fields" || l."fields")->'body' as "body",
36
+ coalesce(v."fields", e."fields" || l."fields")->'tags' as "tags"
37
+ from "entries" e
38
+ join "entry_locales" l on l."entry_id" = e."id"
39
+ left join "entry_versions" v on v."id" = l."published_version_id"
40
+ where e."collection" = 'articles' and l."status" = 'published';
41
+
42
+ -- smoodly:collection news sha256:03a94ad5ba35ffe4
43
+ create or replace view "news" with (security_invoker = true) as
44
+ select
45
+ e."id",
46
+ l."locale",
47
+ l."slug",
48
+ e."sort",
49
+ e."created_at",
50
+ l."updated_at",
51
+ coalesce(v."fields", e."fields" || l."fields")->>'title' as "title",
52
+ (coalesce(v."fields", e."fields" || l."fields")->>'date')::timestamptz as "date",
53
+ coalesce(v."fields", e."fields" || l."fields")->>'excerpt' as "excerpt",
54
+ coalesce(v."fields", e."fields" || l."fields")->'body' as "body"
55
+ from "entries" e
56
+ join "entry_locales" l on l."entry_id" = e."id"
57
+ left join "entry_versions" v on v."id" = l."published_version_id"
58
+ where e."collection" = 'news' and l."status" = 'published';
@@ -7,11 +7,19 @@ where things are.
7
7
 
8
8
  ## Rules
9
9
 
10
- - **Register everything in `smoodly/`.** Every section, element,
11
- collection and global item is listed in its barrel (`sections.ts`,
12
- `elements.ts`, `collections.ts`, `globals.ts`) and `smoodly/config.ts`
13
- is the one registry. A component that is not registered does not exist
14
- for the admin.
10
+ - **Register schemas in the config, components in `smoodly/views.ts`.**
11
+ Every section, element and page type is two files: `Name.schema.ts`
12
+ and `Name.tsx`. The schema is listed in its barrel (`sections.ts`,
13
+ `elements.ts`, the config's `pages`; collections and global items in
14
+ `collections.ts`, `globals.ts`) and `smoodly/config.ts` is the one
15
+ registry. The component is listed in `smoodly/views.ts`, an element's
16
+ also in `smoodly/richtext.tsx`; an entry page is listed only in
17
+ `smoodly/views.ts`. A schema with no component, or a component with
18
+ no schema, is a named error on the first render.
19
+ - **A `.schema.ts` file imports no component** and nothing from
20
+ `smoodly/server.ts` or `smoodly/richtext.tsx`. Zones, toolsets, global
21
+ items and `node()` take schemas (`z.locked(heroSchema)`), never
22
+ components.
15
23
  - **After a new collection or a `smoodly` upgrade, run `npx smoodly migrate`**
16
24
  and apply what it writes (`supabase db push` hosted, `supabase migration up`
17
25
  local). It copies the core schema files the package ships and writes a
@@ -23,16 +31,15 @@ where things are.
23
31
  the admin's own operations, so validation and publish checks apply and
24
32
  a failure throws. Never write rows or call the stores directly. The
25
33
  seed is idempotent; run it with `npm run seed`.
26
- - **Never import the admin into the site.** Nothing under `app/(site)`,
27
- `app/[locale]` or `components/`, and nothing they import
28
- (`smoodly/server.ts` included), may import `@/smoodly/admin` or
29
- `smoodly/admin/next`: Next ships every client module a route imports,
30
- rendered or not, and the admin's chunk must not reach visitors. The
31
- package's `smoodly/admin` entry has no client code; server-side
32
- admin glue such as `supabaseAdminAuth` comes from there.
33
- - **Two root layouts.** The site's layout lives in its route group or
34
- locale segment and is never the admin's parent; `app/(smoodly)/admin`
35
- is a second root layout. Do not add an `app/layout.tsx`.
34
+ - **The admin is one route.** `app/(smoodly)/admin/[[...segments]]/route.ts`
35
+ serves the admin's page, its files and its operations from the
36
+ `smoodly` package; the folder name must match `admin.path` in the
37
+ config. The admin is prebuilt: nothing of it is compiled into the
38
+ site, and it takes no layout. Server-side pieces such as
39
+ `supabaseAdminAuth` and the content API come from `smoodly/admin`.
40
+ - **One root layout.** The site's layout lives in its route group or
41
+ locale segment. Do not add an `app/layout.tsx`: it would wrap that
42
+ layout in a second `<html>`.
36
43
  - **Toolsets and elements.** A richtext field names a toolset
37
44
  (`smoodly/toolsets.ts`); a toolset embeds elements by value; the config
38
45
  must register those elements too. Files the toolset imports use
@@ -45,7 +52,7 @@ where things are.
45
52
  - **Multilingual sites.** Content locales are Smoodly's; URL locales are
46
53
  next-intl's. The locale set is `i18n/routing.ts`. UI strings of the
47
54
  site's own go through next-intl's messages in `messages/<locale>.json`.
48
- - **Fixed versus open pages.** A page shape with a `slug` is fixed: one
55
+ - **Fixed versus open pages.** A page type with a `slug` is fixed: one
49
56
  record, created for you, never deleted. Without one it is open: editors
50
57
  create as many as they like.
51
58
 
@@ -10,16 +10,16 @@ for an agent (or a person) building the site.
10
10
 
11
11
  - `app/[locale]/page.tsx` — the home page: one line calling renderSmoodlyPath([], { locale }).
12
12
  - `app/[locale]/[...path]/page.tsx` — every other page and every collection entry, at any depth, with translated segments per locale.
13
- - `app/[locale]/layout.tsx` — the site's root layout: fonts, `app/globals.css`, the footer from global content; the admin has its own under `app/(smoodly)/admin/`.
13
+ - `app/[locale]/layout.tsx` — the site's root layout: fonts, `app/globals.css`, the footer from global content. The admin is one route, `app/(smoodly)/admin/[[...segments]]/route.ts`, and takes no layout.
14
14
  - `i18n/routing.ts` — the locales and the default; `smoodly/config.ts` reads them from here. `i18n/request.ts` and `i18n/navigation.ts` are next-intl's stock files; `proxy.ts` is its locale routing (`/admin` excluded).
15
15
  - `components/pages/` — the page shapes and the entry page: `Home` (locked hero, freeform body), `Articles` (a fixed page at the collection's path — its index — with a code-fetched grid), `Page` (open; About lives here), `Article` (the entry page), `News` and `NewsPost` (a collection at the nested address `company/news`, its index and entry page).
16
16
  - `components/PageFrame.tsx` — the header and `<main>` every page view renders (a layout cannot know which page is on screen); with more than one locale, the language links from the page's `locales` map — the same page in the other language, or its home when there is no translation.
17
- - `components/sections/` — the building blocks editors compose with, and the header, footer and quote behind the global items. Add one, list it in `smoodly/sections.ts`.
17
+ - `components/sections/` — the building blocks editors compose with, and the header, footer and quote behind the global items. Each is two files: `Name.schema.ts` (the schema) and `Name.tsx` (the component). Add one, list its schema in `smoodly/sections.ts` and its component in `smoodly/views.ts`.
18
18
  - `components/strings.ts`, `messages/` — the site's own UI strings and the request locale, one seam.
19
19
  - `public/images/` — the starter's photos; image fields hold URLs like `/images/hero_image.jpg`.
20
20
  - `scripts/seed.ts` — the starter content, written through the content API (`content()`). `npm run seed` is idempotent.
21
21
  - `smoodly/collections.ts` — structured content (people, articles, news). Add a collection, run `npx smoodly migrate`, apply the file it writes.
22
- - `smoodly/` — the wiring: `config.ts` (the registry and settings), `collections.ts`, `sections.ts`, `server.ts` (site layer, renderer, the content API `content()`), `ops.ts` (the admin's one server action), `admin.tsx` (the admin mount).
22
+ - `smoodly/` — the wiring: `config.ts` (the registry of schemas and the settings; it imports no component), `collections.ts`, `sections.ts` and `elements.ts` (the schemas the config registers), `views.ts` (the components that render them), `server.ts` (site layer, renderer, the content API `content()`).
23
23
  - `.env.local` — `SMOODLY_URL`, `SMOODLY_SECRET_KEY`, `SMOODLY_PUBLISHABLE_KEY`.
24
24
 
25
25
  Translated URLs: a page's slug and title are edited per locale in the
@@ -0,0 +1,8 @@
1
+ // The admin: its page, the package's files and the operations endpoint, all from this one route. The
2
+ // admin is prebuilt in the `smoodly` package; nothing of it is compiled into the site. The folder name
3
+ // must match `admin.path` in smoodly/config.ts.
4
+ import { createAdminRoute } from "smoodly/next";
5
+ import { config } from "@/smoodly/config";
6
+ import { adminDeps, presence, publicAuth } from "@/smoodly/server";
7
+
8
+ export const { GET, POST } = createAdminRoute(() => ({ config, ...adminDeps(), signIn: publicAuth(), presence }));
@@ -0,0 +1,24 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // An Element: a small registered block editors insert inside an article
4
+ // body (the `richtext` context). Its body is richtext too — marks only —
5
+ // rendered by the package's pure RichText (a schema file imports no view:
6
+ // the view imports this file, and an import back would cycle).
7
+ export const calloutSchema = smoodly.schema.element({
8
+ name: "callout",
9
+ title: "Callout",
10
+ icon: "!",
11
+ description: "A highlighted note inside the text.",
12
+ contexts: ["richtext"],
13
+ fields: {
14
+ title: f.text().max(80),
15
+ body: f.richtext({ toolset: smoodly.toolset({ marks: ["bold", "italic", "link"] }) }),
16
+ },
17
+ settings: {
18
+ tone: f.select([{ value: "info", label: "Info" }, { value: "success", label: "Success" }, { value: "warning", label: "Warning" }]).default("info"),
19
+ },
20
+ sample: {
21
+ title: "Good to know",
22
+ body: { type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }] }] },
23
+ },
24
+ });
@@ -1,27 +1,5 @@
1
- import { f, RichText, smoodly, type Props } from "smoodly";
2
-
3
- // An Element: a small registered block editors insert inside an article
4
- // body (the `richtext` context). Its body is richtext too — marks only —
5
- // rendered by the package's pure RichText (never the project's bound one:
6
- // smoodly/richtext.tsx imports this file, and an import back would cycle).
7
- export const calloutSchema = smoodly.schema.element({
8
- name: "callout",
9
- title: "Callout",
10
- icon: "!",
11
- description: "A highlighted note inside the text.",
12
- contexts: ["richtext"],
13
- fields: {
14
- title: f.text().max(80),
15
- body: f.richtext({ toolset: smoodly.toolset({ marks: ["bold", "italic", "link"] }) }),
16
- },
17
- settings: {
18
- tone: f.select([{ value: "info", label: "Info" }, { value: "success", label: "Success" }, { value: "warning", label: "Warning" }]).default("info"),
19
- },
20
- sample: {
21
- title: "Good to know",
22
- body: { type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }] }] },
23
- },
24
- });
1
+ import { RichText, smoodly, type Props } from "smoodly/react";
2
+ import { calloutSchema } from "./Callout.schema";
25
3
 
26
4
  function CalloutView({ title, body, settings }: Props<typeof calloutSchema>) {
27
5
  return (
@@ -0,0 +1,21 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // A call to action inside an article body: heading, a line of text and
4
+ // one button. `variant` is a style — never translated.
5
+ export const ctaSchema = smoodly.schema.element({
6
+ name: "cta",
7
+ title: "Call to action",
8
+ icon: "→",
9
+ description: "A heading, a line and a button.",
10
+ contexts: ["richtext"],
11
+ fields: {
12
+ heading: f.text().max(80),
13
+ text: f.text().max(200).optional(),
14
+ label: f.text().max(40),
15
+ href: f.link(),
16
+ },
17
+ settings: {
18
+ variant: f.select([{ value: "primary", label: "Primary" }, { value: "outline", label: "Outline" }]).default("primary"),
19
+ },
20
+ sample: { heading: "Ready to start?", text: "Sed ut perspiciatis unde omnis iste natus.", label: "Get in touch", href: "/company/about-us" },
21
+ });
@@ -1,24 +1,5 @@
1
- import { f, smoodly, type Props } from "smoodly";
2
-
3
- // A call to action inside an article body: heading, a line of text and
4
- // one button. `variant` is a style — never translated.
5
- export const ctaSchema = smoodly.schema.element({
6
- name: "cta",
7
- title: "Call to action",
8
- icon: "→",
9
- description: "A heading, a line and a button.",
10
- contexts: ["richtext"],
11
- fields: {
12
- heading: f.text().max(80),
13
- text: f.text().max(200).optional(),
14
- label: f.text().max(40),
15
- href: f.link(),
16
- },
17
- settings: {
18
- variant: f.select([{ value: "primary", label: "Primary" }, { value: "outline", label: "Outline" }]).default("primary"),
19
- },
20
- sample: { heading: "Ready to start?", text: "Sed ut perspiciatis unde omnis iste natus.", label: "Get in touch", href: "/company/about-us" },
21
- });
1
+ import { smoodly, type Props } from "smoodly/react";
2
+ import { ctaSchema } from "./Cta.schema";
22
3
 
23
4
  function CtaView({ heading, text, label, href, settings }: Props<typeof ctaSchema>) {
24
5
  return (
@@ -1,4 +1,4 @@
1
- import { smoodly } from "smoodly";
1
+ import { smoodly } from "smoodly/react";
2
2
  import { RichText } from "@/smoodly/richtext";
3
3
  import { SmoodlyImage } from "smoodly/image";
4
4
  import { articles } from "@/smoodly/collections";
@@ -0,0 +1,20 @@
1
+ import { smoodly, z } from "smoodly";
2
+ import { pageHeroSchema } from "../sections/PageHero.schema";
3
+
4
+ // The Articles index: a fixed page whose slugs are the collection's own
5
+ // segments (smoodly/collections.ts), which makes it the collection's
6
+ // index page — the mount in the Pages list opens this page's editor. The
7
+ // editor owns the hero; the grid below is developer JSX that reads the
8
+ // articles through `q`, newest first (the collection's `order`), cached
9
+ // under the collection's tag. In the editor canvas the site runs in draft
10
+ // mode, so the grid reads the live rows too: an unpublished article shows
11
+ // there as it will (spec 2026-09-11 §2).
12
+ export const articlesSchema = smoodly.schema.page({
13
+ name: "articlesIndex",
14
+ title: "Articles",
15
+ slug: { en: "posts", fi: "artikkelit" },
16
+ zones: {
17
+ hero: z.locked(pageHeroSchema),
18
+ },
19
+ seo: true,
20
+ });
@@ -1,27 +1,9 @@
1
- import { smoodly, z, Zone, type Props } from "smoodly";
2
- import { PageHero } from "../sections/PageHero";
1
+ import { smoodly, Zone, type Props } from "smoodly/react";
3
2
  import { ArticleCard } from "../ArticleCard";
4
3
  import { PageFrame } from "../PageFrame";
5
4
  import { ui } from "../strings";
6
5
  import { q } from "@/smoodly/server";
7
-
8
- // The Articles index: a fixed page whose slugs are the collection's own
9
- // segments (smoodly/collections.ts), which makes it the collection's
10
- // index page — the mount in the Pages list opens this page's editor. The
11
- // editor owns the hero; the grid below is developer JSX that reads the
12
- // articles through `q`, newest first (the collection's `order`), cached
13
- // under the collection's tag. In the editor canvas the site runs in draft
14
- // mode, so the grid reads the live rows too: an unpublished article shows
15
- // there as it will (spec 2026-09-11 §2).
16
- export const articlesSchema = smoodly.schema.page({
17
- name: "articlesIndex",
18
- title: "Articles",
19
- slug: { en: "posts", fi: "artikkelit" },
20
- zones: {
21
- hero: z.locked(PageHero),
22
- },
23
- seo: true,
24
- });
6
+ import { articlesSchema } from "./Articles.schema";
25
7
 
26
8
  async function ArticlesView({ zones, page }: Props<typeof articlesSchema>) {
27
9
  const { locale, url } = await ui();
@@ -0,0 +1,26 @@
1
+ import { smoodly, z } from "smoodly";
2
+ import { heroSchema } from "../sections/Hero.schema";
3
+ import { imageTextSchema } from "../sections/ImageText.schema";
4
+ import { pointsSchema } from "../sections/Points.schema";
5
+ import { favouriteArticlesSchema } from "../sections/FavouriteArticles.schema";
6
+ import { peopleSchema } from "../sections/People.schema";
7
+ import { quoteSchema } from "../sections/Quote.schema";
8
+
9
+ // A page type: zones declared here, placed by the page type's view (DESIGN.md §3).
10
+ // `slug` makes it fixed — one record the editor can neither delete nor
11
+ // duplicate — and the config's homePageSlug maps that record to "/".
12
+ // The hero is locked: fields only, never added, moved or removed. The
13
+ // body is freeform over five sections. Quote is among them because the
14
+ // picker offers a global item only where the zone allows its section: an
15
+ // editor may drop a one-off quote, or place the global one from the
16
+ // picker's Global group.
17
+ export const homeSchema = smoodly.schema.page({
18
+ name: "home",
19
+ title: "Home",
20
+ slug: "home",
21
+ zones: {
22
+ hero: z.locked(heroSchema),
23
+ body: z.freeform({ sections: [imageTextSchema, pointsSchema, favouriteArticlesSchema, peopleSchema, quoteSchema], custom: true }),
24
+ },
25
+ seo: true,
26
+ });
@@ -1,30 +1,6 @@
1
- import { smoodly, z, Zone, type Props } from "smoodly";
2
- import { Hero } from "../sections/Hero";
3
- import { ImageText } from "../sections/ImageText";
4
- import { Points } from "../sections/Points";
5
- import { FavouriteArticles } from "../sections/FavouriteArticles";
6
- import { People } from "../sections/People";
7
- import { Quote } from "../sections/Quote";
1
+ import { smoodly, Zone, type Props } from "smoodly/react";
8
2
  import { PageFrame } from "../PageFrame";
9
-
10
- // A page shape: zones declared, <Zone /> placed, one file (DESIGN.md §3).
11
- // `slug` makes it fixed — one record the editor can neither delete nor
12
- // duplicate — and the config's homePageSlug maps that record to "/".
13
- // The hero is locked: fields only, never added, moved or removed. The
14
- // body is freeform over five sections. Quote is among them because the
15
- // picker offers a global item only where the zone allows its section: an
16
- // editor may drop a one-off quote, or place the global one from the
17
- // picker's Global group.
18
- export const homeSchema = smoodly.schema.page({
19
- name: "home",
20
- title: "Home",
21
- slug: "home",
22
- zones: {
23
- hero: z.locked(Hero),
24
- body: z.freeform({ sections: [ImageText, Points, FavouriteArticles, People, Quote], custom: true }),
25
- },
26
- seo: true,
27
- });
3
+ import { homeSchema } from "./Home.schema";
28
4
 
29
5
  function HomeView({ zones, page }: Props<typeof homeSchema>) {
30
6
  return (
@@ -0,0 +1,17 @@
1
+ import { smoodly, z } from "smoodly";
2
+ import { pageHeroSchema } from "../sections/PageHero.schema";
3
+
4
+ // The News index: a fixed page on the news collection's own nested
5
+ // address (smoodly/collections.ts), so it is the collection's index page.
6
+ // Code owns /company/news whole: the Company page above it is an
7
+ // editor's, and renaming it leaves the news where it is. The editor owns
8
+ // the hero; the list below reads the posts through `q`, newest first.
9
+ export const newsSchema = smoodly.schema.page({
10
+ name: "newsIndex",
11
+ title: "News",
12
+ slug: { en: "company/news", fi: "yritys/uutiset" },
13
+ zones: {
14
+ hero: z.locked(pageHeroSchema),
15
+ },
16
+ seo: true,
17
+ });
@@ -1,24 +1,9 @@
1
- import { smoodly, z, Zone, type Props } from "smoodly";
2
- import { PageHero } from "../sections/PageHero";
1
+ import { smoodly, Zone, type Props } from "smoodly/react";
3
2
  import { PageFrame } from "../PageFrame";
4
3
  import { formatDate } from "../articles";
5
4
  import { ui } from "../strings";
6
5
  import { q } from "@/smoodly/server";
7
-
8
- // The News index: a fixed page on the news collection's own nested
9
- // address (smoodly/collections.ts), so it is the collection's index page.
10
- // Code owns /company/news whole: the Company page above it is an
11
- // editor's, and renaming it leaves the news where it is. The editor owns
12
- // the hero; the list below reads the posts through `q`, newest first.
13
- export const newsSchema = smoodly.schema.page({
14
- name: "newsIndex",
15
- title: "News",
16
- slug: { en: "company/news", fi: "yritys/uutiset" },
17
- zones: {
18
- hero: z.locked(PageHero),
19
- },
20
- seo: true,
21
- });
6
+ import { newsSchema } from "./News.schema";
22
7
 
23
8
  async function NewsView({ zones, page }: Props<typeof newsSchema>) {
24
9
  const { locale, url } = await ui();
@@ -1,4 +1,4 @@
1
- import { smoodly } from "smoodly";
1
+ import { smoodly } from "smoodly/react";
2
2
  import { RichText } from "@/smoodly/richtext";
3
3
  import { news } from "@/smoodly/collections";
4
4
  import { PageFrame } from "../PageFrame";