@thorprovider/storefront 0.3.1 → 0.4.0-mvp.2

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 (263) hide show
  1. package/README.md +321 -152
  2. package/VENDOR.md +69 -0
  3. package/assets/openspec/config.yaml +38 -0
  4. package/assets/templates/AGENTS.md +76 -0
  5. package/assets/templates/app/app/[[...slug]]/page.tsx +54 -2
  6. package/assets/templates/app/app/[[...slug]]/renderer.tsx +13 -2
  7. package/assets/templates/app/app/globals.css +5 -8
  8. package/assets/templates/app/app/layout.tsx +17 -7
  9. package/assets/templates/app/app/robots.ts +19 -0
  10. package/assets/templates/app/app/sitemap.ts +71 -0
  11. package/assets/templates/app/lib/__STOREFRONT__/actions.ts +612 -0
  12. package/assets/templates/app/lib/__STOREFRONT__/cart-state.ts +66 -0
  13. package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +50 -57
  14. package/assets/templates/app/lib/__STOREFRONT__/commerce.ts +679 -0
  15. package/assets/templates/app/lib/__STOREFRONT__/composition.ts +26 -0
  16. package/assets/templates/app/lib/__STOREFRONT__/handlers.core.ts +197 -0
  17. package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +5 -36
  18. package/assets/templates/app/lib/__STOREFRONT__/icon-map.ts +27 -0
  19. package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +13 -11
  20. package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +264 -51
  21. package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +6 -3
  22. package/assets/templates/app/lib/__STOREFRONT__/schema.ts +136 -0
  23. package/assets/templates/app/lib/__STOREFRONT__/section-observation.ts +182 -0
  24. package/assets/templates/app/lib/__STOREFRONT__/section-sources.ts +130 -0
  25. package/assets/templates/app/lib/__STOREFRONT__/session.core.ts +470 -0
  26. package/assets/templates/app/lib/__STOREFRONT__/session.ts +169 -0
  27. package/assets/templates/app/lib/__STOREFRONT__/shell/content.ts +120 -0
  28. package/assets/templates/app/lib/__STOREFRONT__/shell/props.ts +38 -0
  29. package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +129 -10
  30. package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +5 -1
  31. package/assets/templates/app/lib/__STOREFRONT__/spec/seo.ts +191 -0
  32. package/assets/templates/app/lib/__STOREFRONT__/state.core.ts +257 -0
  33. package/assets/templates/app/lib/__STOREFRONT__/state.ts +7 -31
  34. package/assets/templates/app/lib/__STOREFRONT__/store-context.ts +161 -0
  35. package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.client.tsx +25 -0
  36. package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.tsx +16 -0
  37. package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.client.tsx +24 -0
  38. package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +5 -18
  39. package/assets/templates/app/lib/__STOREFRONT__/views/sections/index.ts +18 -0
  40. package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +26 -14
  41. package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +36 -18
  42. package/assets/templates/app/lib/__STOREFRONT__/views/sections/products.client.tsx +100 -0
  43. package/assets/templates/app/lib/__STOREFRONT__/views/sections/products.tsx +19 -0
  44. package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.cta.ts +19 -0
  45. package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.faq.ts +18 -0
  46. package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.products.ts +39 -0
  47. package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +35 -14
  48. package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +31 -13
  49. package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +5 -3
  50. package/assets/templates/features/checkout/handlers.ts +159 -0
  51. package/assets/templates/features/checkout/state.ts +61 -0
  52. package/assets/templates/features/customer-auth/handlers.ts +59 -0
  53. package/assets/templates/features/customer-auth/state.ts +45 -0
  54. package/assets/templates/features/recommendations/state.ts +11 -0
  55. package/assets/templates/views/account/content.ts +35 -0
  56. package/assets/templates/views/account/index.tsx +114 -0
  57. package/assets/templates/views/account/registry.tsx +439 -0
  58. package/assets/templates/views/account/schema.ts +53 -0
  59. package/assets/templates/views/cart/content.ts +11 -0
  60. package/assets/templates/views/cart/data.ts +23 -0
  61. package/assets/templates/views/cart/index.tsx +53 -0
  62. package/assets/templates/views/cart/props.ts +12 -0
  63. package/assets/templates/views/cart/registry.tsx +81 -0
  64. package/assets/templates/views/cart/schema.ts +52 -0
  65. package/assets/templates/views/category/assembly/assembly.json +29 -0
  66. package/assets/templates/views/category/assembly/index.skeleton.tsx +251 -0
  67. package/assets/templates/views/category/assembly/registry.skeleton.tsx +34 -0
  68. package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/binding.json +5 -0
  69. package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/catalog.part +4 -0
  70. package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/element.part +7 -0
  71. package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/registry.part +18 -0
  72. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/catalog.part +5 -0
  73. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/element.part +34 -0
  74. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/registry.part +71 -0
  75. package/assets/templates/views/category/content.ts +30 -0
  76. package/assets/templates/views/category/data.ts +12 -0
  77. package/assets/templates/views/category/index.tsx +299 -0
  78. package/assets/templates/views/category/props.ts +23 -0
  79. package/assets/templates/views/category/registry.tsx +122 -0
  80. package/assets/templates/views/category/schema.ts +91 -0
  81. package/assets/templates/views/checkout/content.ts +105 -0
  82. package/assets/templates/views/checkout/data.ts +23 -0
  83. package/assets/templates/views/checkout/index.tsx +111 -0
  84. package/assets/templates/views/checkout/props.ts +49 -0
  85. package/assets/templates/views/checkout/registry.tsx +217 -0
  86. package/assets/templates/views/checkout/schema.ts +229 -0
  87. package/assets/templates/views/home/assembly/assembly.json +33 -0
  88. package/assets/templates/views/home/assembly/index.skeleton.tsx +90 -0
  89. package/assets/templates/views/home/assembly/registry.skeleton.tsx +25 -0
  90. package/assets/templates/views/home/assembly/slots/benefits/Feature1/binding.json +5 -0
  91. package/assets/templates/views/home/assembly/slots/benefits/Feature1/catalog.part +4 -0
  92. package/assets/templates/views/home/assembly/slots/benefits/Feature1/element.part +4 -0
  93. package/assets/templates/views/home/assembly/slots/benefits/Feature1/registry.part +11 -0
  94. package/assets/templates/views/home/assembly/slots/benefits/Feature2/binding.json +5 -0
  95. package/assets/templates/views/home/assembly/slots/benefits/Feature2/catalog.part +4 -0
  96. package/assets/templates/views/home/assembly/slots/benefits/Feature2/element.part +4 -0
  97. package/assets/templates/views/home/assembly/slots/benefits/Feature2/registry.part +10 -0
  98. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/binding.json +5 -0
  99. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/catalog.part +4 -0
  100. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/element.part +5 -0
  101. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/registry.part +16 -0
  102. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/binding.json +5 -0
  103. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/catalog.part +4 -0
  104. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/element.part +5 -0
  105. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/registry.part +26 -0
  106. package/assets/templates/views/home/content.ts +70 -0
  107. package/assets/templates/views/home/data.ts +24 -0
  108. package/assets/templates/views/home/index.tsx +57 -85
  109. package/assets/templates/views/home/props.ts +22 -0
  110. package/assets/templates/views/home/registry.tsx +38 -22
  111. package/assets/templates/views/home/schema.ts +93 -0
  112. package/assets/templates/views/product/content.ts +25 -0
  113. package/assets/templates/views/product/data.ts +50 -0
  114. package/assets/templates/views/product/index.tsx +148 -0
  115. package/assets/templates/views/product/props.ts +27 -0
  116. package/assets/templates/views/product/registry.tsx +103 -0
  117. package/assets/templates/views/product/schema.ts +121 -0
  118. package/assets/templates/views/static/content.ts +99 -0
  119. package/assets/templates/views/static/index.tsx +60 -0
  120. package/assets/templates/views/static/registry.tsx +66 -0
  121. package/assets/templates/views/static/schema.ts +45 -0
  122. package/bin/storefront.js +2424 -148
  123. package/examples/merchandising/category-led.json +37 -0
  124. package/examples/merchandising/collection-led.json +37 -0
  125. package/package.json +15 -9
  126. package/recipes/archetypes.json +28 -14
  127. package/recipes/catalog.json +400 -0
  128. package/recipes/catalog.mjs +128 -0
  129. package/recipes/catalog.schema.json +64 -0
  130. package/recipes/feature.schema.json +89 -0
  131. package/recipes/features.json +162 -0
  132. package/recipes/manifest.schema.json +69 -0
  133. package/recipes/operations.schema.json +43 -0
  134. package/recipes/recipe.schema.json +11 -2
  135. package/recipes/recipes.json +76 -19
  136. package/recipes/sections.json +34 -5
  137. package/recipes/validate.mjs +838 -32
  138. package/scripts/candidate.mjs +236 -0
  139. package/scripts/composers.mjs +160 -0
  140. package/scripts/env-file.mjs +86 -0
  141. package/scripts/file-classes.mjs +121 -0
  142. package/scripts/logical-controls.mjs +61 -0
  143. package/scripts/manifest.mjs +298 -0
  144. package/scripts/migrate-english-routes.mjs +276 -0
  145. package/scripts/operation-registry.mjs +256 -0
  146. package/scripts/operations.mjs +720 -0
  147. package/scripts/ownership-conflict.mjs +356 -0
  148. package/scripts/pinning.mjs +573 -0
  149. package/scripts/plan-apply.mjs +987 -0
  150. package/scripts/preflight.mjs +51 -0
  151. package/scripts/reconcile.mjs +549 -0
  152. package/scripts/render-templates.mjs +338 -68
  153. package/scripts/schema-validate.mjs +132 -0
  154. package/scripts/section-compat.mjs +300 -0
  155. package/scripts/section-config.mjs +253 -0
  156. package/scripts/section-operations.mjs +508 -0
  157. package/scripts/starter.mjs +17 -0
  158. package/scripts/sync-assets.mjs +22 -15
  159. package/scripts/theme/color.mjs +185 -0
  160. package/scripts/theme/css-theme.mjs +233 -0
  161. package/scripts/theme/fonts.mjs +123 -0
  162. package/scripts/theme/import-export.mjs +125 -0
  163. package/scripts/theme/preset.mjs +93 -0
  164. package/scripts/theme/theme-plan.mjs +292 -0
  165. package/scripts/theme/tokens.mjs +137 -0
  166. package/scripts/view-assembly.mjs +337 -0
  167. package/scripts/view-selection.mjs +211 -0
  168. package/skills/sf-feature/SKILL.md +80 -0
  169. package/skills/sf-setup/SKILL.md +79 -0
  170. package/skills/sf-theme-gen/SKILL.md +100 -30
  171. package/skills/sf-theme-gen/references/shadcn-theming.md +87 -0
  172. package/skills/{shadcn-theming/references/templates → sf-theme-gen/references}/theme-setup.md +15 -19
  173. package/skills/{shadcn-theming → sf-theme-gen}/references/theming-guide.md +6 -11
  174. package/skills/sf-view-gen/SKILL.md +146 -93
  175. package/skills/sf-view-gen/assets/recipes/archetypes.json +28 -14
  176. package/skills/sf-view-gen/assets/recipes/catalog.json +400 -0
  177. package/skills/sf-view-gen/assets/recipes/catalog.schema.json +64 -0
  178. package/skills/sf-view-gen/assets/recipes/features.json +162 -0
  179. package/skills/sf-view-gen/assets/recipes/recipes.json +76 -19
  180. package/skills/sf-view-gen/assets/recipes/sections.json +34 -5
  181. package/skills/sf-view-gen/assets/templates/views/sections/cta.client.tsx +25 -0
  182. package/skills/sf-view-gen/assets/templates/views/sections/cta.tsx +16 -0
  183. package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +13 -9
  184. package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +2 -7
  185. package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +12 -8
  186. package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +2 -7
  187. package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +14 -10
  188. package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +2 -9
  189. package/skills/sf-view-gen/assets/templates/views/sections/products.client.tsx +100 -0
  190. package/skills/sf-view-gen/assets/templates/views/sections/products.tsx +19 -0
  191. package/skills/sf-view-gen/assets/templates/views/sections/schema.cta.ts +19 -0
  192. package/skills/sf-view-gen/assets/templates/views/sections/schema.faq.ts +18 -0
  193. package/skills/sf-view-gen/assets/templates/views/sections/schema.logoCloud.ts +19 -0
  194. package/skills/sf-view-gen/assets/templates/views/sections/schema.pricing.ts +26 -0
  195. package/skills/sf-view-gen/assets/templates/views/sections/schema.products.ts +39 -0
  196. package/skills/sf-view-gen/assets/templates/views/sections/schema.statistics.ts +25 -0
  197. package/skills/sf-view-gen/assets/templates/views/sections/schema.testimonials.ts +23 -0
  198. package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +16 -8
  199. package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +2 -7
  200. package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +13 -9
  201. package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +2 -5
  202. package/skills/{json-render-core/SKILL.md → sf-view-gen/references/json-render-core.md} +4 -27
  203. package/skills/{json-render-next/SKILL.md → sf-view-gen/references/json-render-next.md} +5 -8
  204. package/skills/{json-render-react/SKILL.md → sf-view-gen/references/json-render-react.md} +3 -5
  205. package/skills/{json-render-shadcn/SKILL.md → sf-view-gen/references/json-render-shadcn.md} +3 -5
  206. package/skills/sf-view-gen/references/views.md +58 -33
  207. package/skills/sf-view-gen/scripts/catalog.mjs +128 -0
  208. package/skills/sf-view-gen/scripts/schema-validate.mjs +132 -0
  209. package/skills/sf-view-gen/scripts/validate.mjs +838 -32
  210. package/skills/storefront-workflow/SKILL.md +179 -0
  211. package/skills/{storefront-best-practices/reference/design.md → storefront-workflow/references/design-consistency.md} +13 -29
  212. package/CHANGELOG.md +0 -61
  213. package/commands/sf-add-view.md +0 -26
  214. package/commands/sf-theme.md +0 -20
  215. package/commands/sf-view.md +0 -25
  216. package/skills/building-storefronts/SKILL.md +0 -178
  217. package/skills/building-storefronts/references/frontend-integration.md +0 -229
  218. package/skills/json-render-remotion/SKILL.md +0 -111
  219. package/skills/json-render-solid/SKILL.md +0 -204
  220. package/skills/nextjs-shadcn/SKILL.md +0 -367
  221. package/skills/nextjs-shadcn/references/architecture.md +0 -499
  222. package/skills/nextjs-shadcn/references/project-setup.md +0 -162
  223. package/skills/nextjs-shadcn/references/shadcn-platform.md +0 -261
  224. package/skills/nextjs-shadcn/references/sidebar.md +0 -274
  225. package/skills/nextjs-shadcn/references/styling.md +0 -555
  226. package/skills/sf-view-gen/assets/templates/views/category/index.tsx +0 -86
  227. package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +0 -48
  228. package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +0 -192
  229. package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +0 -45
  230. package/skills/sf-view-gen/assets/templates/views/product/index.tsx +0 -122
  231. package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +0 -49
  232. package/skills/sf-view-gen/assets/templates/views/user-panel/index.tsx +0 -168
  233. package/skills/sf-view-gen/assets/templates/views/user-panel/registry.tsx +0 -49
  234. package/skills/shadcn-component-discovery/SKILL.md +0 -273
  235. package/skills/shadcn-component-discovery/references/registries.md +0 -226
  236. package/skills/shadcn-theming/SKILL.md +0 -104
  237. package/skills/storefront-best-practices/SKILL.md +0 -421
  238. package/skills/storefront-best-practices/reference/components/breadcrumbs.md +0 -123
  239. package/skills/storefront-best-practices/reference/components/cart-popup.md +0 -189
  240. package/skills/storefront-best-practices/reference/components/country-selector.md +0 -298
  241. package/skills/storefront-best-practices/reference/components/footer.md +0 -112
  242. package/skills/storefront-best-practices/reference/components/hero.md +0 -241
  243. package/skills/storefront-best-practices/reference/components/megamenu.md +0 -239
  244. package/skills/storefront-best-practices/reference/components/navbar.md +0 -397
  245. package/skills/storefront-best-practices/reference/components/popups.md +0 -221
  246. package/skills/storefront-best-practices/reference/components/product-card.md +0 -125
  247. package/skills/storefront-best-practices/reference/components/product-reviews.md +0 -217
  248. package/skills/storefront-best-practices/reference/components/product-slider.md +0 -174
  249. package/skills/storefront-best-practices/reference/components/search.md +0 -101
  250. package/skills/storefront-best-practices/reference/connecting-to-backend.md +0 -391
  251. package/skills/storefront-best-practices/reference/features/promotions.md +0 -307
  252. package/skills/storefront-best-practices/reference/features/wishlist.md +0 -230
  253. package/skills/storefront-best-practices/reference/layouts/account.md +0 -380
  254. package/skills/storefront-best-practices/reference/layouts/cart.md +0 -316
  255. package/skills/storefront-best-practices/reference/layouts/checkout.md +0 -486
  256. package/skills/storefront-best-practices/reference/layouts/home-page.md +0 -264
  257. package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +0 -231
  258. package/skills/storefront-best-practices/reference/layouts/product-details.md +0 -527
  259. package/skills/storefront-best-practices/reference/layouts/product-listing.md +0 -520
  260. package/skills/storefront-best-practices/reference/layouts/static-pages.md +0 -356
  261. package/skills/storefront-best-practices/reference/medusa.md +0 -307
  262. /package/skills/{storefront-best-practices/reference → storefront-workflow/references}/mobile-responsiveness.md +0 -0
  263. /package/skills/{storefront-best-practices/reference → storefront-workflow/references}/seo.md +0 -0
@@ -0,0 +1,39 @@
1
+ // Optional section fragment: products (mirrors recipes/sections.json) — shared contract.
2
+ // Zod mirror of the resolved Products section props, shared by the server catalog
3
+ // (products.tsx) and the client registry (products.client.tsx). The product list
4
+ // reuses the canonical presentation contract owned by @thorprovider/blocks so the
5
+ // section renders exactly what the host mapped from the catalog result.
6
+
7
+ import { z } from 'zod'
8
+ import { productPresentationSchema } from '@thorprovider/blocks'
9
+
10
+ // The per-instance resolution status the host loader computes for a data-bound
11
+ // section. `ready`/`empty` carry resolved products; the remaining states carry no
12
+ // products and render an actionable, truthful message instead.
13
+ export const productsStatusSchema = z.enum([
14
+ 'unconfigured',
15
+ 'ready',
16
+ 'empty',
17
+ 'invalid-query',
18
+ 'unsupported-query',
19
+ 'unmounted-capability',
20
+ 'data-error',
21
+ ])
22
+
23
+ export const productsPropsSchema = z.object({
24
+ sectionId: z.string().min(1).optional(),
25
+ sectionType: z.string().min(1).optional(),
26
+ variant: z.string().optional(),
27
+ // Defensive defaults: a missing resolved state (e.g. the view loader could not
28
+ // run) degrades to a truthful error instead of crashing the render.
29
+ status: productsStatusSchema.default('data-error'),
30
+ products: z.array(productPresentationSchema).default([]),
31
+ heading: z.string().optional(),
32
+ description: z.string().optional(),
33
+ objective: z.string().optional(),
34
+ ctaLabel: z.string().optional(),
35
+ ctaHref: z.string().optional(),
36
+ })
37
+
38
+ export type ProductsProps = z.infer<typeof productsPropsSchema>
39
+ export type ProductsStatus = z.infer<typeof productsStatusSchema>
@@ -0,0 +1,25 @@
1
+ // Optional section fragment: statistics (mirrors recipes/sections.json) — shared contract.
2
+ // Zod mirror of the StatisticItem/Statistic1Props block contracts, shared by
3
+ // the server catalog (statistics.tsx) and the client registry
4
+ // (statistics.client.tsx). The block expects `icon` as a Lucide component;
5
+ // specs ship the icon NAME, so it is a string here and the client registry
6
+ // resolves it via resolveIcon.
7
+
8
+ import { z } from 'zod'
9
+
10
+ export const statisticItemSchema = z.object({
11
+ id: z.string(),
12
+ title: z.string(),
13
+ value: z.string(),
14
+ description: z.string(),
15
+ progress: z.number(),
16
+ progressLabel: z.string(),
17
+ icon: z.string().optional(),
18
+ })
19
+
20
+ export const statisticsPropsSchema = z.object({
21
+ title: z.string().optional(),
22
+ badge: z.string().optional(),
23
+ description: z.string().optional(),
24
+ items: z.array(statisticItemSchema),
25
+ })
@@ -0,0 +1,23 @@
1
+ // Optional section fragment: testimonials (mirrors recipes/sections.json) — shared contract.
2
+ // Zod mirror of the Testimonial1Item/Testimonial1Props block contracts, shared
3
+ // by the server catalog (testimonials.tsx) and the client registry
4
+ // (testimonials.client.tsx).
5
+
6
+ import { z } from 'zod'
7
+
8
+ export const testimonialItemSchema = z.object({
9
+ id: z.string(),
10
+ name: z.string(),
11
+ role: z.string(),
12
+ avatar: z.string(),
13
+ content: z.string(),
14
+ rating: z.number(),
15
+ })
16
+
17
+ export const testimonialsPropsSchema = z.object({
18
+ title: z.string().optional(),
19
+ prevLabel: z.string().optional(),
20
+ nextLabel: z.string().optional(),
21
+ slideAriaLabel: z.string().optional(),
22
+ items: z.array(testimonialItemSchema),
23
+ })
@@ -4,15 +4,23 @@
4
4
  // The matching server-side catalog entry lives in statistics.tsx.
5
5
 
6
6
  import { Statistic1 as StatisticBlock } from '@thorprovider/blocks'
7
+ import { statisticsPropsSchema } from './schema.statistics'
8
+ import { resolveIcon } from '../../icon-map'
7
9
  import type { ViewRegistry } from '../types'
8
10
 
9
11
  export const registry: ViewRegistry = {
10
- Statistics: ({ props }) => (
11
- <StatisticBlock
12
- badge={props.badge as string}
13
- title={(props.title as string) ?? 'Números que nos respaldan'}
14
- description={props.description as string}
15
- items={props.items as never}
16
- />
17
- ),
12
+ Statistics: ({ props }) => {
13
+ const parsed = statisticsPropsSchema.parse(props)
14
+ return (
15
+ <StatisticBlock
16
+ badge={parsed.badge}
17
+ title={parsed.title ?? 'Números que nos respaldan'}
18
+ description={parsed.description}
19
+ items={parsed.items.map((item) => ({
20
+ ...item,
21
+ icon: resolveIcon(item.icon),
22
+ }))}
23
+ />
24
+ )
25
+ },
18
26
  }
@@ -2,18 +2,13 @@
2
2
  // Server-side part: element type + zod catalog entry. The component mappings
3
3
  // live in statistics.client.tsx ('use client').
4
4
 
5
- import { z } from 'zod'
5
+ import { statisticsPropsSchema } from './schema.statistics'
6
6
 
7
7
  export const statisticsSection = {
8
8
  elementType: 'Statistics',
9
9
  catalog: {
10
10
  Statistics: {
11
- props: z.object({
12
- title: z.string().optional(),
13
- badge: z.string().optional(),
14
- description: z.string().optional(),
15
- items: z.array(z.record(z.string(), z.unknown())),
16
- }),
11
+ props: statisticsPropsSchema,
17
12
  description: 'Key metrics or trust statistics.',
18
13
  },
19
14
  },
@@ -4,16 +4,20 @@
4
4
  // The matching server-side catalog entry lives in testimonials.tsx.
5
5
 
6
6
  import { Testimonial1 as TestimonialBlock } from '@thorprovider/blocks'
7
+ import { testimonialsPropsSchema } from './schema.testimonials'
7
8
  import type { ViewRegistry } from '../types'
8
9
 
9
10
  export const registry: ViewRegistry = {
10
- Testimonials: ({ props }) => (
11
- <TestimonialBlock
12
- title={(props.title as string) ?? 'Lo que dicen nuestros clientes'}
13
- items={props.items as never}
14
- prevLabel={(props.prevLabel as string) ?? 'Anterior'}
15
- nextLabel={(props.nextLabel as string) ?? 'Siguiente'}
16
- slideAriaLabel={(props.slideAriaLabel as string) ?? 'Testimonio'}
17
- />
18
- ),
11
+ Testimonials: ({ props }) => {
12
+ const parsed = testimonialsPropsSchema.parse(props)
13
+ return (
14
+ <TestimonialBlock
15
+ title={parsed.title ?? 'Lo que dicen nuestros clientes'}
16
+ items={parsed.items}
17
+ prevLabel={parsed.prevLabel ?? 'Anterior'}
18
+ nextLabel={parsed.nextLabel ?? 'Siguiente'}
19
+ slideAriaLabel={parsed.slideAriaLabel ?? 'Testimonio'}
20
+ />
21
+ )
22
+ },
19
23
  }
@@ -2,16 +2,13 @@
2
2
  // Server-side part: element type + zod catalog entry. The component mappings
3
3
  // live in testimonials.client.tsx ('use client').
4
4
 
5
- import { z } from 'zod'
5
+ import { testimonialsPropsSchema } from './schema.testimonials'
6
6
 
7
7
  export const testimonialsSection = {
8
8
  elementType: 'Testimonials',
9
9
  catalog: {
10
10
  Testimonials: {
11
- props: z.object({
12
- title: z.string().optional(),
13
- items: z.array(z.record(z.string(), z.unknown())),
14
- }),
11
+ props: testimonialsPropsSchema,
15
12
  description: 'Customer testimonials or social proof.',
16
13
  },
17
14
  },
@@ -1,38 +1,15 @@
1
- ---
2
- name: core
3
- description: Core package for defining schemas, catalogs, and AI prompt generation for json-render. Use when working with @json-render/core, defining schemas, creating catalogs, or building JSON specs for UI/video generation.
4
- ---
1
+ # @json-render/core reference
5
2
 
6
- # @json-render/core
7
-
8
- Core package for schema definition, catalog creation, and spec streaming.
3
+ Reference material for the `sf-view-gen` skill: schema definition, catalog
4
+ creation, and spec shape for `@json-render/core`.
9
5
 
10
6
  ## Key Concepts
11
7
 
12
8
  - **Schema**: Defines the structure of specs and catalogs (use `defineSchema`)
13
9
  - **Catalog**: Maps component/action names to their definitions (use `defineCatalog`)
14
- - **Spec**: JSON output from AI that conforms to the schema
10
+ - **Spec**: JSON output that conforms to the schema
15
11
  - **SpecStream**: JSONL streaming format for progressive spec building
16
12
 
17
- ## Experimental Decision-Model Composition
18
-
19
- For decision-model composition, import `experimental_composeSpec` and `experimental_createEvaluator` from `@json-render/core`. These APIs are unreleased; use a source build until published, then pin exact versions. Experimental exports and `Experimental_` types can change in any release.
20
-
21
- - Run the Gateway evaluator server-side with `{ model: "typesafe-ai/jev", apiKey: process.env.AI_GATEWAY_API_KEY! }`. A plain model identifier is required; Jev is the current example; do not import a provider constructor.
22
- - Call `experimental_composeSpec({ catalog, candidates, prompt, evaluate, initialState, signal })`. It is an async generator; stream `step.spec` snapshots to your existing renderer and inspect `complete.stopReason` (`finish`, `limit`, `unavailable`). Errors and cancellation throw; retain the last snapshot as partial UI.
23
- - New trees default to `strategy: "batch"`: one evaluation selects root/membership, then a second arranges the selected elements when needed. The first snapshot contains selected content in catalog order under the root's default/first slot. Resource variants share one exclusive question; repeated counts include the root. Root selection takes precedence over conflicting speculative membership for that recipe/resource. Equal sibling positions retain catalog order. Combined layouts are validated before publication; cycles or excessive depth throw. `maxElements` caps batched creation (default 32). Limit-truncated selections or a missing required layout call return `limit`. Use `strategy: "sequential"` for legacy `next`/`parent` adapters or sequential creation. Edits stay sequential.
24
- - Batched trace steps use `choice: "select" | "layout"` and an `answers` record. Count each trace as one evaluation, including its tokens and latency once. Custom evaluators must answer every offered question; names/choices are opaque and include `root`/`select_*`, then `parent_*`/`order_*` for batches.
25
- - For follow-up edits, pass the selected version as `initialSpec`. It is cloned and validated; the evaluator may add, replace, remove non-root subtrees, or move/reorder them. Unchanged IDs, bindings, and state are preserved. Optional `elementDescriptions` shares identifying descriptions without exposing raw props/state. `initialState` overrides the seed state. Seeds must be valid trees within the catalog, expression subset, and depth limit. Matching recipes consume usage/resource limits; removals/replacements release them. Replacements/moves use two evaluations (select target, then recipe/destination), each counted against the budget. Treat operation and position keys as opaque.
26
- - Supply atomic candidates with `{ id, description, element: { type, props, on?, visible? }, root?, maxUses?, resource? }`. Catalog alone is insufficient: the app must supply values and binding recipes. Jev chooses elements and parent slots, never free-form text or code. It never executes actions.
27
- - Candidates are configured component instances, not page templates. Build them from current app records/operations or bind props to `initialState`; offer explicit alternatives for chart types, field configurations, and layout variants. The model chooses grouping and order within those options. Name required sections in prompts; structural validity does not imply semantic completeness.
28
- - V1 supports flat Spec catalogs, named slots, literals, `$state`, `$bindState`, and state visibility. No prebuilt children, repeat/watch, computed/template/conditional props, or custom directives. Success/error callbacks must reference allowed actions. Events must be declared in the component catalog.
29
- - Props and action params are validated against initial state without applying schema transforms/defaults. Supply valid initial values and validate/authorize action calls at runtime. Built-ins without parameter schemas get name validation only.
30
- - `root` defaults true, `maxUses` defaults one, shared `resource` values make alternatives mutually exclusive. Defaults: 32 evaluations (terminal calls included; no extra finish call for batches), depth eight, 10-second Gateway timeout per call. Supply an overall abort signal.
31
- - Candidate descriptions, prompt, instructions, topology, and explicit `context` are sent to the evaluator. Initial state and raw props/binding values are not sent automatically.
32
- - For custom providers implement `Experimental_CompositionEvaluator`: accept `{ state, questions, signal }`, return `{ answers: { [question]: { choice, confidence? } }, usage?: { inputTokens? } }`. Only return offered criteria keys.
33
-
34
- See `packages/core/README.md` and `/docs/jev` for app integration and source-build instructions. The web playground is an example consumer, not a dependency of the API.
35
-
36
13
  ## Defining a Schema
37
14
 
38
15
  ```typescript
@@ -1,17 +1,14 @@
1
- ---
2
- name: next
3
- description: Next.js renderer for json-render that turns JSON specs into full Next.js applications with routes, layouts, SSR, and metadata. Use when working with @json-render/next, building Next.js apps from JSON specs, or creating AI-generated multi-page applications.
4
- ---
1
+ # @json-render/next reference
5
2
 
6
- # @json-render/next
3
+ Reference material for the `sf-view-gen` skill: the Next.js rendering shape that
4
+ converts the json-render spec into routes, layouts, SSR, and metadata.
7
5
 
8
6
  Next.js renderer that converts JSON specs into full Next.js applications with routes, pages, layouts, metadata, and SSR support.
9
7
 
10
8
  ## Quick Start
11
9
 
12
- ```bash
13
- npm install @json-render/core @json-render/react @json-render/next
14
- ```
10
+ `@json-render/core`, `@json-render/react`, and `@json-render/next` are already
11
+ dependencies of the generated storefront; no install step is needed.
15
12
 
16
13
  ### 1. Define Your Spec
17
14
 
@@ -1,9 +1,7 @@
1
- ---
2
- name: react
3
- description: React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.
4
- ---
1
+ # @json-render/react reference
5
2
 
6
- # @json-render/react
3
+ Reference material for the `sf-view-gen` skill: registry mapping, providers,
4
+ bindings, and action wiring for the React renderer.
7
5
 
8
6
  React renderer that converts JSON specs into React component trees.
9
7
 
@@ -1,9 +1,7 @@
1
- ---
2
- name: shadcn
3
- description: Pre-built shadcn/ui components for json-render. Use when working with @json-render/shadcn, adding standard UI components to a catalog, or building web UIs with Radix UI + Tailwind CSS components.
4
- ---
1
+ # @json-render/shadcn reference
5
2
 
6
- # @json-render/shadcn
3
+ Reference material for the `sf-view-gen` skill: the standard shadcn/ui component
4
+ definitions and implementations available to a catalog.
7
5
 
8
6
  Pre-built shadcn/ui component definitions and implementations for json-render. Provides 36 components built on Radix UI + Tailwind CSS.
9
7
 
@@ -1,15 +1,22 @@
1
- # View templates
1
+ # View modules reference
2
2
 
3
- Each folder here is a self-contained view module. `npx @thorprovider/storefront create`
4
- copies `home/` at init; `/sf-add-view` copies one folder per added view.
3
+ Reference material for the `sf-view-gen` skill. Each view is a self-contained
4
+ module the CLI generates into `lib/<name>/views/<view>/`. The CLI — not the
5
+ agent — writes the barrel and every derived file; `npx @thorprovider/storefront
6
+ view add <view>` generates one view's derived files and reconciles the barrel.
7
+
8
+ The available views are whatever `npx @thorprovider/storefront views --json`
9
+ reports; this file names none of them as the authoritative set.
5
10
 
6
11
  ## Module shape
7
12
 
8
- Each view is the **two-file split** (RSC):
13
+ Each view is the **three-file split** (RSC):
9
14
 
10
15
  - `views/<view>/index.tsx` — pure Server Component module: name, elements,
11
16
  zod catalog, route, loader. Never imports values from `@json-render/react`
12
- or `@thorprovider/blocks`.
17
+ or `@thorprovider/blocks`. **Derived — the CLI writes it; never hand-edit it.**
18
+ - `views/<view>/schema.ts` — pure Zod schemas mirroring the block contracts
19
+ the view renders (props only). No React, no `'use client'`, no Next imports.
13
20
  - `views/<view>/registry.tsx` — `'use client'`: exports
14
21
  `registry: ViewRegistry`, the component mappings
15
22
  (`Record<string, (ctx: BaseComponentProps<Record<string, unknown>>) => ReactNode>`).
@@ -39,31 +46,49 @@ view never edits them.
39
46
 
40
47
  ## Registering a view
41
48
 
42
- Add **one import and one entry** to `lib/<name>/views/index.ts`:
43
-
44
- ```ts
45
- import { home } from './home'
46
- import { product } from './product'
47
-
48
- export const views: ViewModule[] = [home, product]
49
- ```
50
-
51
- ## Variant substitution convention
52
-
53
- Every slot imports its block variant under a **stable alias** and ships the
54
- recipe's default variant:
55
-
56
- ```ts
57
- import { ProductOverview1 as ProductOverviewBlock } from '@thorprovider/blocks'
58
- ```
59
-
60
- To apply a different (recipe-allowed) variant, replace **only the imported
61
- identifier** — never the alias, never the props:
62
-
63
- ```ts
64
- import { ProductOverview5 as ProductOverviewBlock } from '@thorprovider/blocks'
65
- ```
66
-
67
- The alias is the element type's contract; the identifier is the presentation
68
- choice. Keeping props identical across variants is why the catalog declares the
69
- stable subset of each element's props.
49
+ The barrel `lib/<name>/views/index.ts` (server) and `lib/<name>/views/registry.ts`
50
+ (client) are **generated from the manifest by the CLI** on every reconcile. Do
51
+ not hand-edit them; add or remove a view with
52
+ `npx @thorprovider/storefront view add|remove <name>`. The CLI emits one import
53
+ and one entry per installed view, so a view removed from the manifest cannot
54
+ reappear through folder scanning.
55
+
56
+ ## Variants
57
+
58
+ A slot's variant is a presentation choice recorded in the manifest and applied by
59
+ the reconciler. Change it through the CLI — `view add`/`remove` with
60
+ `--variant slot=Name` (a supported variant the registry reports), or a
61
+ `section.add` / `section.reorder` operation for editorial sections. Do not
62
+ hand-edit a derived `index.tsx` or registry to swap an imported identifier; the
63
+ next `sync` would overwrite it and the manifest would disagree with the files.
64
+
65
+ ## Data contracts and icons
66
+
67
+ Two rules that keep generated views from crashing at render time:
68
+
69
+ 1. **Mappers are applied at loaders, never in registries.** Adapter canon does
70
+ not match block props 1:1 (e.g. a category may carry `productCount?: number`
71
+ or no count at all, and some count props are optional). The view's loader maps
72
+ adapter data through `lib/<name>/mappers.ts` before it reaches state.
73
+ Registries receive already-mapped, block-shaped props. When a value is unknown
74
+ (e.g. no category count), **omit** it — never fabricate a `0` default, which
75
+ would render a false "0 items". A genuine `0` from a richer source is
76
+ preserved.
77
+
78
+ 2. **Icons cross registries as components, via `icon-map`.** Several blocks type
79
+ icon props as `LucideIcon` (a component). A string like `'truck'` bound
80
+ directly renders a literal `<truck>` tag in the DOM. Registries resolve icon
81
+ names through `lib/<name>/icon-map.ts` (`resolveIcon(name)`, kebab-case →
82
+ `LucideIcon`, neutral `Circle` fallback). When adding a view that uses a new
83
+ icon, extend `icon-map.ts` with an entry — never inline a string where a
84
+ component is expected.
85
+
86
+ Supporting rules for the same goal:
87
+
88
+ - View schemas live in `views/<view>/schema.ts` and **mirror the block's Zod
89
+ contracts locally** — never import schemas from `@thorprovider/blocks` (the
90
+ scaffold runs Zod v4 while blocks ships v3; cross-major schema assignment
91
+ breaks `defineCatalog`).
92
+ - Registries **parse props through the view schema** (`schema.parse(props)`)
93
+ instead of casting bindings (`as never` / `as any` are forbidden in scaffold
94
+ code). Type errors on `parse` are the contract check.
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ // Semantic executable catalog: the single contract that states, per view slot
3
+ // and candidate block variant, whether the variant is `executable` or `held`,
4
+ // the block it renders, the canonical data shape it consumes, the events it
5
+ // emits, its controlled/uncontrolled state, its requirements, and an optional
6
+ // prop bridge from the canonical shape to the block props.
7
+ //
8
+ // The published shape lives in `recipes/catalog.schema.json`; this module loads
9
+ // that same artifact and adds only the invariants the supported schema subset
10
+ // cannot express (a held entry must carry a reason; a slot's default must be a
11
+ // declared variant; an unverified capability must name its owning change). It
12
+ // does not classify the initial set, derive `supported`, or wire any
13
+ // generator/introspection — those are owned by later tasks.
14
+
15
+ import { existsSync, readFileSync } from 'node:fs'
16
+ import { dirname, join } from 'node:path'
17
+ import { fileURLToPath } from 'node:url'
18
+ import { validateAgainstSchema, validateSchemaSupported } from '../scripts/schema-validate.mjs'
19
+
20
+ const here = dirname(fileURLToPath(import.meta.url))
21
+
22
+ // Layout-agnostic catalog location: at the package root the catalog lives beside
23
+ // this module (`recipes/`); in an installed skill it is distributed under
24
+ // `skills/sf-view-gen/assets/recipes/` and addressed by relative path. Either
25
+ // layout holds the same synced artifact, so there is one catalog source. The
26
+ // `../scripts/schema-validate.mjs` import resolves to the package `scripts/` at
27
+ // the root and to the skill's own `scripts/` once distributed (same directory).
28
+ const catalogDir = existsSync(join(here, 'catalog.json'))
29
+ ? here
30
+ : join(here, '..', 'assets', 'recipes')
31
+
32
+ export const CATALOG_VERSION = 1
33
+ export const CATALOG_SCHEMA_ID = 'https://thorcommerce.dev/storefront/catalog.schema.json'
34
+
35
+ const catalogSchema = JSON.parse(readFileSync(join(catalogDir, 'catalog.schema.json'), 'utf8'))
36
+
37
+ function isPlainObject(value) {
38
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
39
+ }
40
+
41
+ function hasOwn(object, key) {
42
+ return Object.prototype.hasOwnProperty.call(object, key)
43
+ }
44
+
45
+ export function loadCatalogSchema() {
46
+ return catalogSchema
47
+ }
48
+
49
+ /**
50
+ * Load the published semantic catalog document (`recipes/catalog.json`) — the
51
+ * single source of the executable/held classification. Callers validate it with
52
+ * `validateCatalog`; classification is data, not logic (E1 task 1.2).
53
+ */
54
+ export function loadCatalog() {
55
+ return JSON.parse(readFileSync(join(catalogDir, 'catalog.json'), 'utf8'))
56
+ }
57
+
58
+ /**
59
+ * The slot variant entry schema, read from the published catalog schema so the
60
+ * per-entry contract has exactly one source. Throws loudly if the schema is
61
+ * reshaped, rather than silently validating against nothing.
62
+ */
63
+ function variantEntrySchema() {
64
+ const entry =
65
+ catalogSchema?.properties?.slots?.additionalProperties?.properties?.variants
66
+ ?.additionalProperties
67
+ if (!isPlainObject(entry)) {
68
+ throw new Error('catalog.schema.json does not declare a slot variant entry schema.')
69
+ }
70
+ return entry
71
+ }
72
+
73
+ /**
74
+ * Validate one slot variant entry against the published contract.
75
+ * Returns { ok, errors }.
76
+ */
77
+ export function validateCatalogEntry(entry) {
78
+ const errors = validateAgainstSchema(entry, variantEntrySchema(), 'catalog entry')
79
+ return { ok: errors.length === 0, errors }
80
+ }
81
+
82
+ /**
83
+ * Validate a complete catalog document against the published contract plus the
84
+ * invariants the schema subset cannot express. Returns { ok, errors }.
85
+ */
86
+ export function validateCatalog(catalog) {
87
+ if (!isPlainObject(catalog)) {
88
+ return { ok: false, errors: ['Catalog must be a JSON object.'] }
89
+ }
90
+ const errors = validateSchemaSupported(catalogSchema)
91
+ errors.push(...validateAgainstSchema(catalog, catalogSchema, 'catalog'))
92
+
93
+ const slots = isPlainObject(catalog.slots) ? catalog.slots : {}
94
+ for (const [slotId, slot] of Object.entries(slots)) {
95
+ if (!isPlainObject(slot)) continue
96
+ const variants = isPlainObject(slot.variants) ? slot.variants : {}
97
+ if (Object.keys(variants).length === 0) {
98
+ errors.push(`Slot "${slotId}" must declare at least one variant.`)
99
+ }
100
+ if (typeof slot.default === 'string' && !hasOwn(variants, slot.default)) {
101
+ errors.push(`Slot "${slotId}" default "${slot.default}" is not a declared variant.`)
102
+ }
103
+ for (const [variantName, entry] of Object.entries(variants)) {
104
+ if (!isPlainObject(entry)) continue
105
+ if (entry.status === 'held' && !(typeof entry.reason === 'string' && entry.reason.length > 0)) {
106
+ errors.push(`Held variant "${slotId}.${variantName}" must declare a non-empty reason.`)
107
+ }
108
+ }
109
+ }
110
+
111
+ // An unverified capability is held: it must name the change that owns its
112
+ // implementation, so a consumer can see that the capability is not available
113
+ // and who will make it available. This is the honest gate the schema subset
114
+ // cannot express (E1 task 4.2 / CU-04).
115
+ const capabilities = isPlainObject(catalog.capabilities) ? catalog.capabilities : {}
116
+ for (const [capabilityId, capability] of Object.entries(capabilities)) {
117
+ if (!isPlainObject(capability)) continue
118
+ if (
119
+ capability.verification === 'unverified' &&
120
+ !(typeof capability.owner === 'string' && capability.owner.length > 0)
121
+ ) {
122
+ errors.push(
123
+ `Unverified capability "${capabilityId}" must declare the owning change in "owner".`
124
+ )
125
+ }
126
+ }
127
+ return { ok: errors.length === 0, errors }
128
+ }