@thorprovider/storefront 0.3.1 → 0.4.0-mvp.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +248 -99
  3. package/assets/templates/app/app/[[...slug]]/page.tsx +13 -2
  4. package/assets/templates/app/app/layout.tsx +18 -4
  5. package/assets/templates/app/app/robots.ts +13 -0
  6. package/assets/templates/app/app/sitemap.ts +12 -0
  7. package/assets/templates/app/lib/__STOREFRONT__/actions.ts +542 -0
  8. package/assets/templates/app/lib/__STOREFRONT__/cart-state.ts +66 -0
  9. package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +40 -57
  10. package/assets/templates/app/lib/__STOREFRONT__/commerce.ts +608 -0
  11. package/assets/templates/app/lib/__STOREFRONT__/handlers.core.ts +165 -0
  12. package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +5 -36
  13. package/assets/templates/app/lib/__STOREFRONT__/icon-map.ts +27 -0
  14. package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +13 -11
  15. package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +232 -51
  16. package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +3 -3
  17. package/assets/templates/app/lib/__STOREFRONT__/schema.ts +121 -0
  18. package/assets/templates/app/lib/__STOREFRONT__/session.core.ts +470 -0
  19. package/assets/templates/app/lib/__STOREFRONT__/session.ts +169 -0
  20. package/assets/templates/app/lib/__STOREFRONT__/shell/content.ts +96 -0
  21. package/assets/templates/app/lib/__STOREFRONT__/shell/props.ts +34 -0
  22. package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +100 -11
  23. package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +5 -1
  24. package/assets/templates/app/lib/__STOREFRONT__/spec/seo.ts +26 -0
  25. package/assets/templates/app/lib/__STOREFRONT__/state.core.ts +257 -0
  26. package/assets/templates/app/lib/__STOREFRONT__/state.ts +7 -31
  27. package/assets/templates/app/lib/__STOREFRONT__/store-context.ts +142 -0
  28. package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.client.tsx +25 -0
  29. package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.tsx +16 -0
  30. package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.client.tsx +24 -0
  31. package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +5 -18
  32. package/assets/templates/app/lib/__STOREFRONT__/views/sections/index.ts +17 -0
  33. package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +26 -14
  34. package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +36 -18
  35. package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.cta.ts +19 -0
  36. package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.faq.ts +18 -0
  37. package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +35 -14
  38. package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +31 -13
  39. package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +5 -3
  40. package/assets/templates/features/checkout/handlers.ts +153 -0
  41. package/assets/templates/features/checkout/state.ts +61 -0
  42. package/assets/templates/features/customer-auth/handlers.ts +59 -0
  43. package/assets/templates/features/customer-auth/state.ts +36 -0
  44. package/assets/templates/features/recommendations/state.ts +11 -0
  45. package/assets/templates/views/account/content.ts +35 -0
  46. package/assets/templates/views/account/index.tsx +114 -0
  47. package/assets/templates/views/account/registry.tsx +378 -0
  48. package/assets/templates/views/account/schema.ts +52 -0
  49. package/assets/templates/views/category/assembly/assembly.json +28 -0
  50. package/assets/templates/views/category/assembly/index.skeleton.tsx +193 -0
  51. package/assets/templates/views/category/assembly/registry.skeleton.tsx +33 -0
  52. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/catalog.part +5 -0
  53. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/element.part +34 -0
  54. package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/registry.part +71 -0
  55. package/assets/templates/views/category/content.ts +30 -0
  56. package/assets/templates/views/category/data.ts +12 -0
  57. package/assets/templates/views/category/index.tsx +230 -0
  58. package/assets/templates/views/category/props.ts +16 -0
  59. package/assets/templates/views/category/registry.tsx +103 -0
  60. package/assets/templates/views/category/schema.ts +78 -0
  61. package/assets/templates/views/checkout/content.ts +105 -0
  62. package/assets/templates/views/checkout/data.ts +23 -0
  63. package/assets/templates/views/checkout/index.tsx +111 -0
  64. package/assets/templates/views/checkout/props.ts +49 -0
  65. package/assets/templates/views/checkout/registry.tsx +217 -0
  66. package/assets/templates/views/checkout/schema.ts +229 -0
  67. package/assets/templates/views/home/assembly/assembly.json +24 -0
  68. package/assets/templates/views/home/assembly/index.skeleton.tsx +72 -0
  69. package/assets/templates/views/home/assembly/registry.skeleton.tsx +25 -0
  70. package/assets/templates/views/home/assembly/slots/benefits/Feature1/binding.json +5 -0
  71. package/assets/templates/views/home/assembly/slots/benefits/Feature1/catalog.part +4 -0
  72. package/assets/templates/views/home/assembly/slots/benefits/Feature1/element.part +4 -0
  73. package/assets/templates/views/home/assembly/slots/benefits/Feature1/registry.part +11 -0
  74. package/assets/templates/views/home/assembly/slots/benefits/Feature2/binding.json +5 -0
  75. package/assets/templates/views/home/assembly/slots/benefits/Feature2/catalog.part +4 -0
  76. package/assets/templates/views/home/assembly/slots/benefits/Feature2/element.part +4 -0
  77. package/assets/templates/views/home/assembly/slots/benefits/Feature2/registry.part +10 -0
  78. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/binding.json +5 -0
  79. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/catalog.part +4 -0
  80. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/element.part +5 -0
  81. package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/registry.part +16 -0
  82. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/binding.json +5 -0
  83. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/catalog.part +4 -0
  84. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/element.part +5 -0
  85. package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/registry.part +26 -0
  86. package/assets/templates/views/home/content.ts +70 -0
  87. package/assets/templates/views/home/data.ts +15 -0
  88. package/assets/templates/views/home/index.tsx +38 -84
  89. package/assets/templates/views/home/props.ts +22 -0
  90. package/assets/templates/views/home/registry.tsx +38 -22
  91. package/assets/templates/views/home/schema.ts +93 -0
  92. package/assets/templates/views/product/content.ts +25 -0
  93. package/assets/templates/views/product/data.ts +48 -0
  94. package/assets/templates/views/product/index.tsx +104 -0
  95. package/assets/templates/views/product/props.ts +20 -0
  96. package/assets/templates/views/product/registry.tsx +78 -0
  97. package/assets/templates/views/product/schema.ts +108 -0
  98. package/assets/templates/views/static/content.ts +99 -0
  99. package/assets/templates/views/static/index.tsx +60 -0
  100. package/assets/templates/views/static/registry.tsx +66 -0
  101. package/assets/templates/views/static/schema.ts +45 -0
  102. package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/index.tsx +8 -70
  103. package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/registry.tsx +23 -18
  104. package/assets/templates/views/user-panel/schema.ts +79 -0
  105. package/bin/storefront.js +1278 -79
  106. package/commands/sf-add-view.md +44 -13
  107. package/commands/sf-feature.md +28 -0
  108. package/commands/sf-theme.md +14 -8
  109. package/commands/sf-view.md +54 -12
  110. package/package.json +6 -4
  111. package/recipes/archetypes.json +28 -1
  112. package/recipes/catalog.json +361 -0
  113. package/recipes/catalog.mjs +128 -0
  114. package/recipes/catalog.schema.json +64 -0
  115. package/recipes/feature.schema.json +89 -0
  116. package/recipes/features.json +139 -0
  117. package/recipes/manifest.schema.json +69 -0
  118. package/recipes/operations.schema.json +43 -0
  119. package/recipes/recipe.schema.json +11 -2
  120. package/recipes/recipes.json +60 -5
  121. package/recipes/sections.json +24 -5
  122. package/recipes/validate.mjs +784 -32
  123. package/scripts/candidate.mjs +236 -0
  124. package/scripts/composers.mjs +140 -0
  125. package/scripts/file-classes.mjs +116 -0
  126. package/scripts/logical-controls.mjs +61 -0
  127. package/scripts/manifest.mjs +298 -0
  128. package/scripts/operation-registry.mjs +256 -0
  129. package/scripts/operations.mjs +720 -0
  130. package/scripts/ownership-conflict.mjs +356 -0
  131. package/scripts/pinning.mjs +504 -0
  132. package/scripts/plan-apply.mjs +843 -0
  133. package/scripts/preflight.mjs +51 -0
  134. package/scripts/reconcile.mjs +511 -0
  135. package/scripts/render-templates.mjs +245 -28
  136. package/scripts/schema-validate.mjs +132 -0
  137. package/scripts/section-compat.mjs +300 -0
  138. package/scripts/section-operations.mjs +508 -0
  139. package/scripts/sync-assets.mjs +22 -15
  140. package/scripts/view-assembly.mjs +291 -0
  141. package/scripts/view-selection.mjs +211 -0
  142. package/skills/sf-feature/SKILL.md +67 -0
  143. package/skills/sf-theme-gen/SKILL.md +12 -5
  144. package/skills/sf-view-gen/SKILL.md +123 -93
  145. package/skills/sf-view-gen/assets/recipes/archetypes.json +28 -1
  146. package/skills/sf-view-gen/assets/recipes/catalog.json +361 -0
  147. package/skills/sf-view-gen/assets/recipes/catalog.schema.json +64 -0
  148. package/skills/sf-view-gen/assets/recipes/features.json +139 -0
  149. package/skills/sf-view-gen/assets/recipes/recipes.json +60 -5
  150. package/skills/sf-view-gen/assets/recipes/sections.json +24 -5
  151. package/skills/sf-view-gen/assets/templates/views/sections/cta.client.tsx +25 -0
  152. package/skills/sf-view-gen/assets/templates/views/sections/cta.tsx +16 -0
  153. package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +13 -9
  154. package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +2 -7
  155. package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +12 -8
  156. package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +2 -7
  157. package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +14 -10
  158. package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +2 -9
  159. package/skills/sf-view-gen/assets/templates/views/sections/schema.cta.ts +19 -0
  160. package/skills/sf-view-gen/assets/templates/views/sections/schema.faq.ts +18 -0
  161. package/skills/sf-view-gen/assets/templates/views/sections/schema.logoCloud.ts +19 -0
  162. package/skills/sf-view-gen/assets/templates/views/sections/schema.pricing.ts +26 -0
  163. package/skills/sf-view-gen/assets/templates/views/sections/schema.statistics.ts +25 -0
  164. package/skills/sf-view-gen/assets/templates/views/sections/schema.testimonials.ts +23 -0
  165. package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +16 -8
  166. package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +2 -7
  167. package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +13 -9
  168. package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +2 -5
  169. package/skills/sf-view-gen/references/views.md +44 -10
  170. package/skills/sf-view-gen/scripts/catalog.mjs +128 -0
  171. package/skills/sf-view-gen/scripts/schema-validate.mjs +132 -0
  172. package/skills/sf-view-gen/scripts/validate.mjs +784 -32
  173. package/skills/storefront-workflow/SKILL.md +116 -0
  174. package/skills/sf-view-gen/assets/templates/views/category/index.tsx +0 -86
  175. package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +0 -48
  176. package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +0 -192
  177. package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +0 -45
  178. package/skills/sf-view-gen/assets/templates/views/product/index.tsx +0 -122
  179. package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +0 -49
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://thorcommerce.dev/storefront/catalog.schema.json",
4
+ "title": "Thor Commerce Storefront Semantic Catalog",
5
+ "description": "The semantic executable catalog: per view slot and candidate variant, its execution status (executable | held), the block it renders, its canonical data shape, emitted events, controlled/uncontrolled state, requirements, and an optional prop bridge. Held entries carry a reason. Also declares operational capabilities with their backend/data requirements and a verification state (verified | unverified); an unverified capability names its owning change in `owner`. Versioned by catalogVersion.",
6
+ "type": "object",
7
+ "required": ["catalogVersion", "slots"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "$schema": { "type": "string" },
11
+ "catalogVersion": { "type": "number", "enum": [1] },
12
+ "description": { "type": "string" },
13
+ "slots": {
14
+ "type": "object",
15
+ "additionalProperties": {
16
+ "type": "object",
17
+ "required": ["default", "variants"],
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "default": { "type": "string", "minLength": 1 },
21
+ "variants": {
22
+ "type": "object",
23
+ "additionalProperties": {
24
+ "type": "object",
25
+ "required": ["status", "block", "dataShape", "events", "state", "requirements"],
26
+ "additionalProperties": false,
27
+ "properties": {
28
+ "status": { "type": "string", "enum": ["executable", "held"] },
29
+ "reason": { "type": "string", "minLength": 1 },
30
+ "block": { "type": "string", "minLength": 1 },
31
+ "dataShape": { "type": "string", "minLength": 1 },
32
+ "events": { "type": "array", "items": { "type": "string" } },
33
+ "state": { "type": "string", "enum": ["controlled", "uncontrolled"] },
34
+ "requirements": { "type": "array", "items": { "type": "string" } },
35
+ "bridge": {
36
+ "type": "object",
37
+ "required": ["from", "to"],
38
+ "additionalProperties": false,
39
+ "properties": {
40
+ "from": { "type": "string", "minLength": 1 },
41
+ "to": { "type": "string", "minLength": 1 }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ },
50
+ "capabilities": {
51
+ "type": "object",
52
+ "additionalProperties": {
53
+ "type": "object",
54
+ "required": ["requirements", "verification"],
55
+ "additionalProperties": false,
56
+ "properties": {
57
+ "requirements": { "type": "array", "items": { "type": "string" } },
58
+ "verification": { "type": "string", "enum": ["verified", "unverified"] },
59
+ "owner": { "type": "string", "minLength": 1 }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,139 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "features": [
4
+ {
5
+ "name": "checkout",
6
+ "mvp": true,
7
+ "description": "Multi-step checkout: cart validation, shipping, payment, and order placement.",
8
+ "route": "/checkout",
9
+ "views": ["checkout"],
10
+ "state": [
11
+ "checkout",
12
+ "checkoutCountries",
13
+ "checkoutPaymentMethods",
14
+ "checkoutPending",
15
+ "checkoutError",
16
+ "lastOrder",
17
+ "lastOrderLines",
18
+ "orderPlaced"
19
+ ],
20
+ "handlers": ["placeOrder", "goToCheckout", "continueShopping"],
21
+ "adapter": { "needs": ["createCart", "completeCheckout"] },
22
+ "config": { "type": "object", "additionalProperties": false, "properties": {} },
23
+ "configRejection": {
24
+ "code": "E_UNPROVEN_CHECKOUT_OPTION",
25
+ "message": "checkout variants and route options are not proven; the checkout feature accepts an empty config only"
26
+ },
27
+ "surfaces": []
28
+ },
29
+ {
30
+ "name": "customer-auth",
31
+ "mvp": true,
32
+ "description": "Customer authentication: registration, login, logout, and a minimal signed-in state. Owns the account view.",
33
+ "route": "/cuenta",
34
+ "views": ["account"],
35
+ "state": [
36
+ "accountCustomer",
37
+ "accountAuthenticated",
38
+ "accountMode",
39
+ "accountPending",
40
+ "accountError"
41
+ ],
42
+ "handlers": ["logout", "showRegister", "showLogin"],
43
+ "adapter": { "needs": [] },
44
+ "config": { "type": "object", "additionalProperties": false, "properties": {} },
45
+ "surfaces": []
46
+ },
47
+ {
48
+ "name": "static-pages",
49
+ "mvp": true,
50
+ "description": "Content-driven static pages (contact, legal, about) served with per-page metadata. Owns the static view.",
51
+ "route": "/contacto",
52
+ "views": ["static"],
53
+ "state": [],
54
+ "handlers": [],
55
+ "adapter": { "needs": [] },
56
+ "config": { "type": "object", "additionalProperties": false, "properties": {} },
57
+ "surfaces": []
58
+ },
59
+ {
60
+ "name": "benefits",
61
+ "mvp": true,
62
+ "description": "Bounded editorial benefits feature that mounts at the home benefits slot; config selects the Feature1/Feature2 presentation while the item content stays user-owned.",
63
+ "views": [],
64
+ "state": [],
65
+ "handlers": [],
66
+ "adapter": { "needs": [] },
67
+ "config": {
68
+ "type": "object",
69
+ "additionalProperties": false,
70
+ "properties": {
71
+ "presentation": { "type": "string", "enum": ["compact", "detailed"], "default": "compact" }
72
+ }
73
+ },
74
+ "presentations": { "compact": "Feature1", "detailed": "Feature2" },
75
+ "mounts": [{ "id": "home-benefits", "host": "home", "slot": "benefits" }],
76
+ "surfaces": []
77
+ },
78
+ {
79
+ "name": "catalog-search",
80
+ "mvp": true,
81
+ "description": "Optional category-listing search control (E2). Installation is presence, not an enabled flag; the config only supplies an optional placeholder. The search input contributes to the single controlled CategoryFilter2 at the category.search logical slot and never adds a block.",
82
+ "views": [],
83
+ "state": [],
84
+ "handlers": [],
85
+ "adapter": { "needs": [] },
86
+ "config": {
87
+ "type": "object",
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "placeholder": { "type": "string" }
91
+ }
92
+ },
93
+ "mounts": [{ "id": "category-search", "host": "category", "slot": "search" }],
94
+ "surfaces": []
95
+ },
96
+ {
97
+ "name": "category-filters",
98
+ "mvp": true,
99
+ "description": "Optional category-listing filter controls (E2). Installation is presence, not an enabled flag; the config selects which demonstrated basic filters show (filterSet) and whether the price control is shown (showPrice). The controls contribute to the single controlled CategoryFilter2 at the category.filters logical slot and never add a block.",
100
+ "views": [],
101
+ "state": [],
102
+ "handlers": [],
103
+ "adapter": { "needs": [] },
104
+ "config": {
105
+ "type": "object",
106
+ "additionalProperties": false,
107
+ "properties": {
108
+ "filterSet": {
109
+ "type": "array",
110
+ "items": { "type": "string", "enum": ["categories", "collections"] },
111
+ "default": ["categories", "collections"]
112
+ },
113
+ "showPrice": { "type": "boolean", "default": false }
114
+ }
115
+ },
116
+ "configRejection": {
117
+ "code": "E_UNVERIFIED_PRICE_RANGE",
118
+ "message": "showPrice: true requires a verified price range for the listing scope; capability \"category.price.range\" is unverified (held), so configure showPrice: false until a real range is demonstrated",
119
+ "when": { "field": "showPrice", "equals": true },
120
+ "capability": "category.price.range"
121
+ },
122
+ "mounts": [{ "id": "category-filters", "host": "category", "slot": "filters" }],
123
+ "surfaces": []
124
+ },
125
+ {
126
+ "name": "recommendations",
127
+ "mvp": false,
128
+ "description": "Related-products surface injected into the product view.",
129
+ "views": [],
130
+ "state": ["recommendedProducts"],
131
+ "handlers": [],
132
+ "adapter": { "needs": ["getRelatedProducts"], "fallback": "catalog" },
133
+ "config": { "type": "object", "additionalProperties": false, "properties": {} },
134
+ "surfaces": [
135
+ { "host": "product", "slot": "product.related", "element": "ProductCard" }
136
+ ]
137
+ }
138
+ ]
139
+ }
@@ -4,6 +4,7 @@
4
4
  "views": [
5
5
  {
6
6
  "name": "home",
7
+ "mvp": true,
7
8
  "description": "Storefront landing page with hero, benefits, and category highlights",
8
9
  "route": "/",
9
10
  "loader": "home",
@@ -19,10 +20,14 @@
19
20
  "categories": ["ProductCategory1", "ProductCategory2", "ProductCategory4", "ProductCategory5"],
20
21
  "productCard": ["ProductCard1", "ProductCard2", "ProductCard3", "ProductCard5"]
21
22
  },
22
- "optionalSections": ["testimonials", "faq", "statistics", "logoCloud", "pricing"]
23
+ "optionalSections": ["testimonials", "faq", "statistics", "logoCloud", "pricing"],
24
+ "registeredSlots": ["hero", "benefits", "categories"],
25
+ "allowedSections": ["faq", "cta"],
26
+ "maxSections": 4
23
27
  },
24
28
  {
25
29
  "name": "product",
30
+ "mvp": true,
26
31
  "description": "Single product detail page with overview, gallery, and reviews",
27
32
  "route": "/productos/[id]",
28
33
  "loader": "product",
@@ -36,10 +41,14 @@
36
41
  "reviews": ["ReviewRating1", "ReviewRating2"],
37
42
  "productCard": ["ProductCard1", "ProductCard2", "ProductCard3", "ProductCard5"]
38
43
  },
39
- "optionalSections": ["testimonials", "faq", "statistics"]
44
+ "optionalSections": ["testimonials", "faq", "statistics"],
45
+ "registeredSlots": ["overview", "reviews"],
46
+ "allowedSections": [],
47
+ "maxSections": 4
40
48
  },
41
49
  {
42
50
  "name": "category",
51
+ "mvp": true,
43
52
  "description": "Product listing filtered by category with filters and sorting",
44
53
  "route": "/categorias/[[...slug]]",
45
54
  "loader": "category",
@@ -52,10 +61,14 @@
52
61
  "filter": ["CategoryFilter1", "CategoryFilter2", "CategoryFilter4"],
53
62
  "productCard": ["ProductCard1", "ProductCard2", "ProductCard3", "ProductCard5"]
54
63
  },
55
- "optionalSections": ["logoCloud", "testimonials"]
64
+ "optionalSections": ["logoCloud", "testimonials"],
65
+ "registeredSlots": ["filter", "productCard"],
66
+ "allowedSections": ["faq", "cta"],
67
+ "maxSections": 4
56
68
  },
57
69
  {
58
70
  "name": "checkout",
71
+ "mvp": true,
59
72
  "description": "Multi-step checkout with cart summary, shipping, and payment",
60
73
  "route": "/checkout",
61
74
  "loader": "checkout",
@@ -68,10 +81,49 @@
68
81
  "checkout": ["CheckoutForm1", "CheckoutForm2", "CheckoutForm3"],
69
82
  "confirmation": ["CheckoutConfirmation"]
70
83
  },
71
- "optionalSections": []
84
+ "optionalSections": [],
85
+ "registeredSlots": ["checkout", "confirmation"],
86
+ "allowedSections": [],
87
+ "maxSections": 4
88
+ },
89
+ {
90
+ "name": "account",
91
+ "mvp": true,
92
+ "description": "Customer account area with sign-in, registration, and a minimal signed-in state",
93
+ "route": "/cuenta",
94
+ "loader": "account",
95
+ "requiredDataKeys": ["accountCustomer"],
96
+ "defaultVariants": {
97
+ "login": "LoginPage1",
98
+ "register": "SignUpPage1"
99
+ },
100
+ "allowedVariants": {
101
+ "login": ["LoginPage1", "LoginPage2"],
102
+ "register": ["SignUpPage1"],
103
+ "orders": ["OrderHistory1", "OrderHistory2"]
104
+ },
105
+ "optionalSections": [],
106
+ "registeredSlots": ["login", "register", "orders"],
107
+ "allowedSections": [],
108
+ "maxSections": 4
109
+ },
110
+ {
111
+ "name": "static",
112
+ "mvp": true,
113
+ "description": "Content-driven static pages (contact, legal, about) with per-page metadata",
114
+ "route": "/contacto",
115
+ "loader": "static",
116
+ "requiredDataKeys": [],
117
+ "defaultVariants": {},
118
+ "allowedVariants": {},
119
+ "optionalSections": [],
120
+ "registeredSlots": [],
121
+ "allowedSections": [],
122
+ "maxSections": 0
72
123
  },
73
124
  {
74
125
  "name": "user-panel",
126
+ "mvp": false,
75
127
  "description": "Customer account area with order history and account navigation",
76
128
  "route": "/cuenta",
77
129
  "loader": "userPanel",
@@ -84,7 +136,10 @@
84
136
  "shell": ["AppShell1"],
85
137
  "orderHistory": ["OrderHistory1", "OrderHistory2"]
86
138
  },
87
- "optionalSections": []
139
+ "optionalSections": [],
140
+ "registeredSlots": ["shell", "orderHistory"],
141
+ "allowedSections": [],
142
+ "maxSections": 4
88
143
  }
89
144
  ]
90
145
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "$schema": "./section.schema.json",
3
3
  "version": "1.0.0",
4
+ "description": "Editorial section registry. Each entry declares a `status`: `executable` (shipped, with a verified presentation bridge/provenance and no backend data source required) or `held` (bounded curation: no verified presentation bridge/provenance in the current composition or no verified backend/data source for the MVP; not an intrinsic exclusion). A held entry declares a non-empty `reason`. The status is the single source of truth that `recipes/validate.mjs` and the CLI introspection read; there is no separate held list.",
4
5
  "sections": [
5
6
  {
6
7
  "name": "testimonials",
@@ -8,7 +9,9 @@
8
9
  "elementType": "Testimonials",
9
10
  "variants": ["Testimonial1", "Testimonial2"],
10
11
  "defaultVariant": "Testimonial1",
11
- "propsHint": "title, badge, items[] (quote, author, role, avatar)"
12
+ "propsHint": "title, badge, items[] (quote, author, role, avatar)",
13
+ "status": "held",
14
+ "reason": "bounded curation: no verified presentation bridge/provenance in the current composition and no verified backend/data source for the MVP"
12
15
  },
13
16
  {
14
17
  "name": "faq",
@@ -16,7 +19,17 @@
16
19
  "elementType": "Faq",
17
20
  "variants": ["FaqBlock1"],
18
21
  "defaultVariant": "FaqBlock1",
19
- "propsHint": "title, description, items[] (question, answer)"
22
+ "propsHint": "title, description, items[] (question, answer)",
23
+ "status": "executable"
24
+ },
25
+ {
26
+ "name": "cta",
27
+ "description": "Call-to-action banner with primary and secondary links",
28
+ "elementType": "Cta",
29
+ "variants": ["CtaSection1"],
30
+ "defaultVariant": "CtaSection1",
31
+ "propsHint": "badgeText, title, description, primaryCta/secondaryCta (label, href)",
32
+ "status": "executable"
20
33
  },
21
34
  {
22
35
  "name": "statistics",
@@ -24,7 +37,9 @@
24
37
  "elementType": "Statistics",
25
38
  "variants": ["Statistic1"],
26
39
  "defaultVariant": "Statistic1",
27
- "propsHint": "title, items[] (value, label)"
40
+ "propsHint": "title, items[] (value, label)",
41
+ "status": "held",
42
+ "reason": "bounded curation: no verified backend/data source for the metrics and no verified presentation bridge/provenance in the current composition"
28
43
  },
29
44
  {
30
45
  "name": "logoCloud",
@@ -32,7 +47,9 @@
32
47
  "elementType": "LogoCloud",
33
48
  "variants": ["LogoCloud1"],
34
49
  "defaultVariant": "LogoCloud1",
35
- "propsHint": "title, platforms[]/logos[] (name, src)"
50
+ "propsHint": "title, platforms[]/logos[] (name, src)",
51
+ "status": "held",
52
+ "reason": "bounded curation: no verified backend/data source for partner/brand logos and no verified presentation bridge/provenance in the current composition"
36
53
  },
37
54
  {
38
55
  "name": "pricing",
@@ -40,7 +57,9 @@
40
57
  "elementType": "Pricing",
41
58
  "variants": ["PricingSection1"],
42
59
  "defaultVariant": "PricingSection1",
43
- "propsHint": "title, description, tiers[] (name, price, features, cta)"
60
+ "propsHint": "title, description, tiers[] (name, price, features, cta)",
61
+ "status": "held",
62
+ "reason": "bounded curation: no verified backend/data source for pricing tiers and no verified presentation bridge/provenance in the current composition"
44
63
  }
45
64
  ]
46
65
  }
@@ -0,0 +1,25 @@
1
+ 'use client'
2
+
3
+ // Optional section fragment: cta (mirrors recipes/sections.json) — client registry.
4
+ // The matching server-side catalog lives in cta.tsx. @thorprovider/blocks ships a
5
+ // 'use client' boundary, but this fragment keeps the explicit client boundary so
6
+ // the section composes identically in any consumer graph.
7
+
8
+ import { CtaSection1 as CtaBlock } from '@thorprovider/blocks'
9
+ import { ctaSection1PropsSchema } from './schema.cta'
10
+ import type { ViewRegistry } from '../types'
11
+
12
+ export const ctaRegistry: ViewRegistry = {
13
+ Cta: ({ props }) => {
14
+ const parsed = ctaSection1PropsSchema.parse(props)
15
+ return (
16
+ <CtaBlock
17
+ badgeText={parsed.badgeText}
18
+ title={parsed.title}
19
+ description={parsed.description}
20
+ primaryCta={parsed.primaryCta}
21
+ secondaryCta={parsed.secondaryCta}
22
+ />
23
+ )
24
+ },
25
+ }
@@ -0,0 +1,16 @@
1
+ // Optional section fragment: cta (mirrors recipes/sections.json)
2
+ // Server-side part: element type + zod catalog entry. The component mappings
3
+ // live in cta.client.tsx ('use client') and are composed via composeSections.
4
+
5
+ import { ctaSection1PropsSchema } from './schema.cta'
6
+
7
+ export const ctaSection = {
8
+ elementType: 'Cta',
9
+ catalog: {
10
+ Cta: {
11
+ props: ctaSection1PropsSchema,
12
+ description: 'Call-to-action banner with optional primary and secondary links.',
13
+ },
14
+ },
15
+ registryModule: './cta.client',
16
+ }
@@ -6,15 +6,19 @@
6
6
  // the section composes identically in any consumer graph.
7
7
 
8
8
  import { FaqBlock1 as FaqBlock } from '@thorprovider/blocks'
9
+ import { faqPropsSchema } from './schema.faq'
9
10
  import type { ViewRegistry } from '../types'
10
11
 
11
- export const registry: ViewRegistry = {
12
- Faq: ({ props }) => (
13
- <FaqBlock
14
- title={(props.title as string) ?? 'Preguntas frecuentes'}
15
- description={props.description as string}
16
- items={props.items as never}
17
- accordionType={(props.accordionType as 'single' | 'multiple') ?? 'single'}
18
- />
19
- ),
12
+ export const faqRegistry: ViewRegistry = {
13
+ Faq: ({ props }) => {
14
+ const parsed = faqPropsSchema.parse(props)
15
+ return (
16
+ <FaqBlock
17
+ title={parsed.title ?? 'Preguntas frecuentes'}
18
+ description={parsed.description}
19
+ items={parsed.items}
20
+ accordionType={parsed.accordionType ?? 'single'}
21
+ />
22
+ )
23
+ },
20
24
  }
@@ -2,18 +2,13 @@
2
2
  // Server-side part: element type + zod catalog entry. The component mappings
3
3
  // live in faq.client.tsx ('use client') and are composed via composeSections.
4
4
 
5
- import { z } from 'zod'
5
+ import { faqPropsSchema } from './schema.faq'
6
6
 
7
7
  export const faqSection = {
8
8
  elementType: 'Faq',
9
9
  catalog: {
10
10
  Faq: {
11
- props: z.object({
12
- title: z.string().optional(),
13
- description: z.string().optional(),
14
- items: z.array(z.record(z.string(), z.unknown())),
15
- accordionType: z.enum(['single', 'multiple']).optional(),
16
- }),
11
+ props: faqPropsSchema,
17
12
  description: 'Frequently asked questions accordion.',
18
13
  },
19
14
  },
@@ -4,15 +4,19 @@
4
4
  // The matching server-side catalog entry lives in logoCloud.tsx.
5
5
 
6
6
  import { LogoCloud1 as LogoCloudBlock } from '@thorprovider/blocks'
7
+ import { logoCloudPropsSchema } from './schema.logoCloud'
7
8
  import type { ViewRegistry } from '../types'
8
9
 
9
10
  export const registry: ViewRegistry = {
10
- LogoCloud: ({ props }) => (
11
- <LogoCloudBlock
12
- title={(props.title as string) ?? 'Marcas que confían en nosotros'}
13
- description={props.description as string}
14
- moreLabel={props.moreLabel as string}
15
- platforms={props.platforms as never}
16
- />
17
- ),
11
+ LogoCloud: ({ props }) => {
12
+ const parsed = logoCloudPropsSchema.parse(props)
13
+ return (
14
+ <LogoCloudBlock
15
+ title={parsed.title ?? 'Marcas que confían en nosotros'}
16
+ description={parsed.description}
17
+ moreLabel={parsed.moreLabel}
18
+ platforms={parsed.platforms}
19
+ />
20
+ )
21
+ },
18
22
  }
@@ -2,18 +2,13 @@
2
2
  // Server-side part: element type + zod catalog entry. The component mappings
3
3
  // live in logoCloud.client.tsx ('use client').
4
4
 
5
- import { z } from 'zod'
5
+ import { logoCloudPropsSchema } from './schema.logoCloud'
6
6
 
7
7
  export const logoCloudSection = {
8
8
  elementType: 'LogoCloud',
9
9
  catalog: {
10
10
  LogoCloud: {
11
- props: z.object({
12
- title: z.string().optional(),
13
- description: z.string().optional(),
14
- moreLabel: z.string().optional(),
15
- platforms: z.array(z.record(z.string(), z.unknown())),
16
- }),
11
+ props: logoCloudPropsSchema,
17
12
  description: 'Brand or partner logo strip.',
18
13
  },
19
14
  },
@@ -4,17 +4,21 @@
4
4
  // The matching server-side catalog entry lives in pricing.tsx.
5
5
 
6
6
  import { PricingSection1 as PricingBlock } from '@thorprovider/blocks'
7
+ import { pricingPropsSchema } from './schema.pricing'
7
8
  import type { ViewRegistry } from '../types'
8
9
 
9
10
  export const registry: ViewRegistry = {
10
- Pricing: ({ props }) => (
11
- <PricingBlock
12
- sectionTitle={(props.sectionTitle as string) ?? 'Planes y precios'}
13
- sectionLabel={props.sectionLabel as string}
14
- currencySymbol={props.currencySymbol as string}
15
- popularBadgeLabel={(props.popularBadgeLabel as string) ?? 'Popular'}
16
- getStartedLabel={(props.getStartedLabel as string) ?? 'Empezar'}
17
- tiers={props.tiers as never}
18
- />
19
- ),
11
+ Pricing: ({ props }) => {
12
+ const parsed = pricingPropsSchema.parse(props)
13
+ return (
14
+ <PricingBlock
15
+ sectionTitle={parsed.sectionTitle ?? 'Planes y precios'}
16
+ sectionLabel={parsed.sectionLabel}
17
+ currencySymbol={parsed.currencySymbol}
18
+ popularBadgeLabel={parsed.popularBadgeLabel ?? 'Popular'}
19
+ getStartedLabel={parsed.getStartedLabel ?? 'Empezar'}
20
+ tiers={parsed.tiers}
21
+ />
22
+ )
23
+ },
20
24
  }
@@ -2,20 +2,13 @@
2
2
  // Server-side part: element type + zod catalog entry. The component mappings
3
3
  // live in pricing.client.tsx ('use client').
4
4
 
5
- import { z } from 'zod'
5
+ import { pricingPropsSchema } from './schema.pricing'
6
6
 
7
7
  export const pricingSection = {
8
8
  elementType: 'Pricing',
9
9
  catalog: {
10
10
  Pricing: {
11
- props: z.object({
12
- sectionTitle: z.string().optional(),
13
- sectionLabel: z.string().optional(),
14
- currencySymbol: z.string().optional(),
15
- popularBadgeLabel: z.string().optional(),
16
- getStartedLabel: z.string().optional(),
17
- tiers: z.array(z.record(z.string(), z.unknown())),
18
- }),
11
+ props: pricingPropsSchema,
19
12
  description: 'Pricing tiers or plans.',
20
13
  },
21
14
  },
@@ -0,0 +1,19 @@
1
+ // Optional section fragment: cta (mirrors recipes/sections.json) — shared contract.
2
+ // Zod mirror of the Cta/CtaSection1Props block contracts, shared by the server
3
+ // catalog (cta.tsx) and the client registry (cta.client.tsx). The React-only
4
+ // callbacks (onPrimaryClick/onSecondaryClick) are intentionally excluded.
5
+
6
+ import { z } from 'zod'
7
+
8
+ export const ctaSchema = z.object({
9
+ label: z.string(),
10
+ href: z.string().optional(),
11
+ })
12
+
13
+ export const ctaSection1PropsSchema = z.object({
14
+ badgeText: z.string().optional(),
15
+ title: z.string().optional(),
16
+ description: z.string().optional(),
17
+ primaryCta: ctaSchema.optional(),
18
+ secondaryCta: ctaSchema.optional(),
19
+ })
@@ -0,0 +1,18 @@
1
+ // Optional section fragment: faq (mirrors recipes/sections.json) — shared contract.
2
+ // Zod mirror of the FaqItem/FaqBlock1Props block contracts, shared by the server
3
+ // catalog (faq.tsx) and the client registry (faq.client.tsx).
4
+
5
+ import { z } from 'zod'
6
+
7
+ export const faqItemSchema = z.object({
8
+ value: z.string(),
9
+ question: z.string(),
10
+ answer: z.string(),
11
+ })
12
+
13
+ export const faqPropsSchema = z.object({
14
+ title: z.string().optional(),
15
+ description: z.string().optional(),
16
+ items: z.array(faqItemSchema),
17
+ accordionType: z.enum(['single', 'multiple']).optional(),
18
+ })
@@ -0,0 +1,19 @@
1
+ // Optional section fragment: logoCloud (mirrors recipes/sections.json) — shared contract.
2
+ // Zod mirror of the LogoCloud1Platform/LogoCloud1Props block contracts, shared
3
+ // by the server catalog (logoCloud.tsx) and the client registry
4
+ // (logoCloud.client.tsx). `id` is a simple-icons slug resolved internally by
5
+ // the block.
6
+
7
+ import { z } from 'zod'
8
+
9
+ export const logoCloudPlatformSchema = z.object({
10
+ name: z.string(),
11
+ id: z.string(),
12
+ })
13
+
14
+ export const logoCloudPropsSchema = z.object({
15
+ title: z.string().optional(),
16
+ description: z.string().optional(),
17
+ moreLabel: z.string().optional(),
18
+ platforms: z.array(logoCloudPlatformSchema),
19
+ })