@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.
- package/CHANGELOG.md +23 -0
- package/README.md +248 -99
- package/assets/templates/app/app/[[...slug]]/page.tsx +13 -2
- package/assets/templates/app/app/layout.tsx +18 -4
- package/assets/templates/app/app/robots.ts +13 -0
- package/assets/templates/app/app/sitemap.ts +12 -0
- package/assets/templates/app/lib/__STOREFRONT__/actions.ts +542 -0
- package/assets/templates/app/lib/__STOREFRONT__/cart-state.ts +66 -0
- package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +40 -57
- package/assets/templates/app/lib/__STOREFRONT__/commerce.ts +608 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.core.ts +165 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +5 -36
- package/assets/templates/app/lib/__STOREFRONT__/icon-map.ts +27 -0
- package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +13 -11
- package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +232 -51
- package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +3 -3
- package/assets/templates/app/lib/__STOREFRONT__/schema.ts +121 -0
- package/assets/templates/app/lib/__STOREFRONT__/session.core.ts +470 -0
- package/assets/templates/app/lib/__STOREFRONT__/session.ts +169 -0
- package/assets/templates/app/lib/__STOREFRONT__/shell/content.ts +96 -0
- package/assets/templates/app/lib/__STOREFRONT__/shell/props.ts +34 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +100 -11
- package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +5 -1
- package/assets/templates/app/lib/__STOREFRONT__/spec/seo.ts +26 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.core.ts +257 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.ts +7 -31
- package/assets/templates/app/lib/__STOREFRONT__/store-context.ts +142 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.client.tsx +25 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.tsx +16 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.client.tsx +24 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +5 -18
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/index.ts +17 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +26 -14
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +36 -18
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.cta.ts +19 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.faq.ts +18 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +35 -14
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +31 -13
- package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +5 -3
- package/assets/templates/features/checkout/handlers.ts +153 -0
- package/assets/templates/features/checkout/state.ts +61 -0
- package/assets/templates/features/customer-auth/handlers.ts +59 -0
- package/assets/templates/features/customer-auth/state.ts +36 -0
- package/assets/templates/features/recommendations/state.ts +11 -0
- package/assets/templates/views/account/content.ts +35 -0
- package/assets/templates/views/account/index.tsx +114 -0
- package/assets/templates/views/account/registry.tsx +378 -0
- package/assets/templates/views/account/schema.ts +52 -0
- package/assets/templates/views/category/assembly/assembly.json +28 -0
- package/assets/templates/views/category/assembly/index.skeleton.tsx +193 -0
- package/assets/templates/views/category/assembly/registry.skeleton.tsx +33 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/catalog.part +5 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/element.part +34 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/registry.part +71 -0
- package/assets/templates/views/category/content.ts +30 -0
- package/assets/templates/views/category/data.ts +12 -0
- package/assets/templates/views/category/index.tsx +230 -0
- package/assets/templates/views/category/props.ts +16 -0
- package/assets/templates/views/category/registry.tsx +103 -0
- package/assets/templates/views/category/schema.ts +78 -0
- package/assets/templates/views/checkout/content.ts +105 -0
- package/assets/templates/views/checkout/data.ts +23 -0
- package/assets/templates/views/checkout/index.tsx +111 -0
- package/assets/templates/views/checkout/props.ts +49 -0
- package/assets/templates/views/checkout/registry.tsx +217 -0
- package/assets/templates/views/checkout/schema.ts +229 -0
- package/assets/templates/views/home/assembly/assembly.json +24 -0
- package/assets/templates/views/home/assembly/index.skeleton.tsx +72 -0
- package/assets/templates/views/home/assembly/registry.skeleton.tsx +25 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/element.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/registry.part +11 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/element.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/registry.part +10 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/element.part +5 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/registry.part +16 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/element.part +5 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/registry.part +26 -0
- package/assets/templates/views/home/content.ts +70 -0
- package/assets/templates/views/home/data.ts +15 -0
- package/assets/templates/views/home/index.tsx +38 -84
- package/assets/templates/views/home/props.ts +22 -0
- package/assets/templates/views/home/registry.tsx +38 -22
- package/assets/templates/views/home/schema.ts +93 -0
- package/assets/templates/views/product/content.ts +25 -0
- package/assets/templates/views/product/data.ts +48 -0
- package/assets/templates/views/product/index.tsx +104 -0
- package/assets/templates/views/product/props.ts +20 -0
- package/assets/templates/views/product/registry.tsx +78 -0
- package/assets/templates/views/product/schema.ts +108 -0
- package/assets/templates/views/static/content.ts +99 -0
- package/assets/templates/views/static/index.tsx +60 -0
- package/assets/templates/views/static/registry.tsx +66 -0
- package/assets/templates/views/static/schema.ts +45 -0
- package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/index.tsx +8 -70
- package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/registry.tsx +23 -18
- package/assets/templates/views/user-panel/schema.ts +79 -0
- package/bin/storefront.js +1278 -79
- package/commands/sf-add-view.md +44 -13
- package/commands/sf-feature.md +28 -0
- package/commands/sf-theme.md +14 -8
- package/commands/sf-view.md +54 -12
- package/package.json +6 -4
- package/recipes/archetypes.json +28 -1
- package/recipes/catalog.json +361 -0
- package/recipes/catalog.mjs +128 -0
- package/recipes/catalog.schema.json +64 -0
- package/recipes/feature.schema.json +89 -0
- package/recipes/features.json +139 -0
- package/recipes/manifest.schema.json +69 -0
- package/recipes/operations.schema.json +43 -0
- package/recipes/recipe.schema.json +11 -2
- package/recipes/recipes.json +60 -5
- package/recipes/sections.json +24 -5
- package/recipes/validate.mjs +784 -32
- package/scripts/candidate.mjs +236 -0
- package/scripts/composers.mjs +140 -0
- package/scripts/file-classes.mjs +116 -0
- package/scripts/logical-controls.mjs +61 -0
- package/scripts/manifest.mjs +298 -0
- package/scripts/operation-registry.mjs +256 -0
- package/scripts/operations.mjs +720 -0
- package/scripts/ownership-conflict.mjs +356 -0
- package/scripts/pinning.mjs +504 -0
- package/scripts/plan-apply.mjs +843 -0
- package/scripts/preflight.mjs +51 -0
- package/scripts/reconcile.mjs +511 -0
- package/scripts/render-templates.mjs +245 -28
- package/scripts/schema-validate.mjs +132 -0
- package/scripts/section-compat.mjs +300 -0
- package/scripts/section-operations.mjs +508 -0
- package/scripts/sync-assets.mjs +22 -15
- package/scripts/view-assembly.mjs +291 -0
- package/scripts/view-selection.mjs +211 -0
- package/skills/sf-feature/SKILL.md +67 -0
- package/skills/sf-theme-gen/SKILL.md +12 -5
- package/skills/sf-view-gen/SKILL.md +123 -93
- package/skills/sf-view-gen/assets/recipes/archetypes.json +28 -1
- package/skills/sf-view-gen/assets/recipes/catalog.json +361 -0
- package/skills/sf-view-gen/assets/recipes/catalog.schema.json +64 -0
- package/skills/sf-view-gen/assets/recipes/features.json +139 -0
- package/skills/sf-view-gen/assets/recipes/recipes.json +60 -5
- package/skills/sf-view-gen/assets/recipes/sections.json +24 -5
- package/skills/sf-view-gen/assets/templates/views/sections/cta.client.tsx +25 -0
- package/skills/sf-view-gen/assets/templates/views/sections/cta.tsx +16 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +13 -9
- package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +12 -8
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +14 -10
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +2 -9
- package/skills/sf-view-gen/assets/templates/views/sections/schema.cta.ts +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.faq.ts +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.logoCloud.ts +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.pricing.ts +26 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.statistics.ts +25 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.testimonials.ts +23 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +16 -8
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +13 -9
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +2 -5
- package/skills/sf-view-gen/references/views.md +44 -10
- package/skills/sf-view-gen/scripts/catalog.mjs +128 -0
- package/skills/sf-view-gen/scripts/schema-validate.mjs +132 -0
- package/skills/sf-view-gen/scripts/validate.mjs +784 -32
- package/skills/storefront-workflow/SKILL.md +116 -0
- package/skills/sf-view-gen/assets/templates/views/category/index.tsx +0 -86
- package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +0 -48
- package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +0 -192
- package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +0 -45
- package/skills/sf-view-gen/assets/templates/views/product/index.tsx +0 -122
- package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +0 -49
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./catalog.schema.json",
|
|
3
|
+
"catalogVersion": 1,
|
|
4
|
+
"description": "Initial semantic executable catalog (E1 tasks 1.2-1.3), classified by source-shape inspection of the @thorprovider/blocks prop contracts and the view templates. Scope: every slot the curated MVP views (home, product, category, checkout, account) render by default, covering both the selectable archetype slots declared by recipes/archetypes.json and the nested default slots that have no archetype (category.productCard, checkout.confirmation) so no legacy default support is dropped. The account view (change: storefront-customer-auth) declares the login/register slots: LoginPage1/SignUpPage1 are executable while LoginPage2 is held for its divergent callback contract. A variant is `executable` only when the slot's canonical data shape can feed its block props without a prop bridge and without inventing data; every other non-default variant is `held` with the specific reason. The one executable non-default swap is home.benefits Feature1 -> Feature2 (identical item contract, no bridge). user-panel (non-MVP view) is outside this initial catalog scope. E2 task 6.1 declares the base listing slot category.filter CategoryFilter2 as the single controlled listing (dataShape `CatalogQuery & CatalogResults`, state controlled) with the search/filter controls contributed by the logical mounts category.search / category.filters (no second block). Source-shape compatibility is not end-to-end backend behavior proof: the render is executable, but the category.search.apply / category.filter.apply actions remain held under the capability catalog (owner: storefront-catalog-search-filters, E2) until a real runtime proof. The `capabilities` map declares those operational capabilities with their requirements and verification state; E1 declares the held category.filter.apply action, E2 adds the held category.search.apply action and the held category.price.range capability that gates the category-filters showPrice option; no capability is claimed verified.",
|
|
5
|
+
"slots": {
|
|
6
|
+
"home.hero": {
|
|
7
|
+
"default": "StorefrontHero1",
|
|
8
|
+
"variants": {
|
|
9
|
+
"StorefrontHero1": {
|
|
10
|
+
"status": "executable",
|
|
11
|
+
"block": "StorefrontHero",
|
|
12
|
+
"dataShape": "FeaturedProduct[]",
|
|
13
|
+
"events": ["addToCart", "buyNow", "selectColor"],
|
|
14
|
+
"state": "uncontrolled",
|
|
15
|
+
"requirements": ["heroProducts"]
|
|
16
|
+
},
|
|
17
|
+
"StorefrontHero2": {
|
|
18
|
+
"status": "held",
|
|
19
|
+
"reason": "block requires a product with reviews/trending/tag and emits a search event; the canonical FeaturedProduct shape carries rating/reviewCount but not trending/tag, and no verified prop bridge exists",
|
|
20
|
+
"block": "StorefrontHero",
|
|
21
|
+
"dataShape": "StorefrontHero2FeaturedProduct[]",
|
|
22
|
+
"events": ["search", "productClick"],
|
|
23
|
+
"state": "uncontrolled",
|
|
24
|
+
"requirements": ["heroProducts"]
|
|
25
|
+
},
|
|
26
|
+
"StorefrontHero5": {
|
|
27
|
+
"status": "held",
|
|
28
|
+
"reason": "featuredDeals[].description is absent from the canonical product shape and Department.count would require inventing counts; no truthful prop bridge can be derived from source",
|
|
29
|
+
"block": "StorefrontHero",
|
|
30
|
+
"dataShape": "FeaturedDeal[] & Department[]",
|
|
31
|
+
"events": ["departmentClick", "shopNow", "locateStores"],
|
|
32
|
+
"state": "uncontrolled",
|
|
33
|
+
"requirements": ["featuredDeals", "departments"]
|
|
34
|
+
},
|
|
35
|
+
"StorefrontHero6": {
|
|
36
|
+
"status": "held",
|
|
37
|
+
"reason": "block requires deals with discount/endTime and categories with a required count; the canonical shape supplies neither without invention and no verified prop bridge exists",
|
|
38
|
+
"block": "StorefrontHero",
|
|
39
|
+
"dataShape": "HeroDeal[] & HeroCategory[]",
|
|
40
|
+
"events": ["shopNow", "categoryClick"],
|
|
41
|
+
"state": "uncontrolled",
|
|
42
|
+
"requirements": ["deals", "categories"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"home.benefits": {
|
|
47
|
+
"default": "Feature1",
|
|
48
|
+
"variants": {
|
|
49
|
+
"Feature1": {
|
|
50
|
+
"status": "executable",
|
|
51
|
+
"block": "Feature",
|
|
52
|
+
"dataShape": "BenefitsItem[]",
|
|
53
|
+
"events": ["ctaClick", "learnMore"],
|
|
54
|
+
"state": "uncontrolled",
|
|
55
|
+
"requirements": []
|
|
56
|
+
},
|
|
57
|
+
"Feature2": {
|
|
58
|
+
"status": "executable",
|
|
59
|
+
"block": "Feature",
|
|
60
|
+
"dataShape": "BenefitsItem[]",
|
|
61
|
+
"events": [],
|
|
62
|
+
"state": "uncontrolled",
|
|
63
|
+
"requirements": []
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"home.categories": {
|
|
68
|
+
"default": "ProductCategory2",
|
|
69
|
+
"variants": {
|
|
70
|
+
"ProductCategory2": {
|
|
71
|
+
"status": "executable",
|
|
72
|
+
"block": "CategoryCards",
|
|
73
|
+
"dataShape": "CategoryCard[]",
|
|
74
|
+
"events": ["browse"],
|
|
75
|
+
"state": "uncontrolled",
|
|
76
|
+
"requirements": ["categories"]
|
|
77
|
+
},
|
|
78
|
+
"ProductCategory1": {
|
|
79
|
+
"status": "held",
|
|
80
|
+
"reason": "block requires categories with a subtitle and a named icon, which the canonical CategoryCard shape does not supply; no verified prop bridge exists",
|
|
81
|
+
"block": "CategoryCards",
|
|
82
|
+
"dataShape": "ProductCategory1Category[]",
|
|
83
|
+
"events": [],
|
|
84
|
+
"state": "uncontrolled",
|
|
85
|
+
"requirements": ["categories"]
|
|
86
|
+
},
|
|
87
|
+
"ProductCategory4": {
|
|
88
|
+
"status": "held",
|
|
89
|
+
"reason": "block requires categories with title/icon/customers/products/avatarImages, which the canonical CategoryCard shape does not supply; no verified prop bridge exists",
|
|
90
|
+
"block": "CategoryCards",
|
|
91
|
+
"dataShape": "ProductCategory4Category[]",
|
|
92
|
+
"events": ["categoryClick"],
|
|
93
|
+
"state": "uncontrolled",
|
|
94
|
+
"requirements": ["categories"]
|
|
95
|
+
},
|
|
96
|
+
"ProductCategory5": {
|
|
97
|
+
"status": "held",
|
|
98
|
+
"reason": "block requires categories with subcategories and a required itemCount plus a search control, which the canonical CategoryCard shape does not supply; no verified prop bridge exists",
|
|
99
|
+
"block": "CategoryCards",
|
|
100
|
+
"dataShape": "ProductCategory5Category[]",
|
|
101
|
+
"events": ["browse", "viewAll"],
|
|
102
|
+
"state": "uncontrolled",
|
|
103
|
+
"requirements": ["categories"]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"product.overview": {
|
|
108
|
+
"default": "ProductOverview1",
|
|
109
|
+
"variants": {
|
|
110
|
+
"ProductOverview1": {
|
|
111
|
+
"status": "executable",
|
|
112
|
+
"block": "ProductOverview",
|
|
113
|
+
"dataShape": "ProductDetailProduct",
|
|
114
|
+
"events": ["selectVariant", "quantityChange", "addToCart", "toggleWishlist"],
|
|
115
|
+
"state": "controlled",
|
|
116
|
+
"requirements": ["selectedProduct", "selectedVariantId"]
|
|
117
|
+
},
|
|
118
|
+
"ProductOverview2": {
|
|
119
|
+
"status": "held",
|
|
120
|
+
"reason": "block requires a product with brand/sku/sizes/colors and wishlist events, a shape the canonical ProductDetailProduct does not supply; no verified prop bridge exists",
|
|
121
|
+
"block": "ProductOverview",
|
|
122
|
+
"dataShape": "ProductOverview2Product",
|
|
123
|
+
"events": ["addToCart", "toggleWishlist"],
|
|
124
|
+
"state": "uncontrolled",
|
|
125
|
+
"requirements": ["selectedProduct"]
|
|
126
|
+
},
|
|
127
|
+
"ProductOverview5": {
|
|
128
|
+
"status": "held",
|
|
129
|
+
"reason": "block requires specifications/features/mainImages/galleryImages/userReviews, a shape the canonical ProductDetailProduct does not supply; no verified prop bridge exists",
|
|
130
|
+
"block": "ProductOverview",
|
|
131
|
+
"dataShape": "ProductOverview5Product",
|
|
132
|
+
"events": ["addToCart", "toggleWishlist"],
|
|
133
|
+
"state": "uncontrolled",
|
|
134
|
+
"requirements": ["selectedProduct"]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"product.reviews": {
|
|
139
|
+
"default": "ReviewRating1",
|
|
140
|
+
"variants": {
|
|
141
|
+
"ReviewRating1": {
|
|
142
|
+
"status": "executable",
|
|
143
|
+
"block": "Reviews",
|
|
144
|
+
"dataShape": "Review[]",
|
|
145
|
+
"events": ["toggleReviewLike", "submitReview"],
|
|
146
|
+
"state": "uncontrolled",
|
|
147
|
+
"requirements": ["productReviews"]
|
|
148
|
+
},
|
|
149
|
+
"ReviewRating2": {
|
|
150
|
+
"status": "held",
|
|
151
|
+
"reason": "block requires an aggregate averageRating/ratingDistribution and carousel controls the canonical Review[] shape does not supply; no verified prop bridge exists",
|
|
152
|
+
"block": "Reviews",
|
|
153
|
+
"dataShape": "Review[] & RatingSummary",
|
|
154
|
+
"events": ["like", "comment"],
|
|
155
|
+
"state": "uncontrolled",
|
|
156
|
+
"requirements": ["productReviews"]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"category.filter": {
|
|
161
|
+
"default": "CategoryFilter2",
|
|
162
|
+
"variants": {
|
|
163
|
+
"CategoryFilter2": {
|
|
164
|
+
"status": "executable",
|
|
165
|
+
"block": "CategoryFilter",
|
|
166
|
+
"dataShape": "CatalogQuery & CatalogResults",
|
|
167
|
+
"events": [
|
|
168
|
+
"productClick",
|
|
169
|
+
"pageChange",
|
|
170
|
+
"pageSizeChange",
|
|
171
|
+
"sortChange",
|
|
172
|
+
"filterChange",
|
|
173
|
+
"searchChange",
|
|
174
|
+
"clearFilters",
|
|
175
|
+
"repair",
|
|
176
|
+
"retry",
|
|
177
|
+
"removeFilter"
|
|
178
|
+
],
|
|
179
|
+
"state": "controlled",
|
|
180
|
+
"requirements": ["categoryProducts", "categoryQuery", "facetSupport"]
|
|
181
|
+
},
|
|
182
|
+
"CategoryFilter1": {
|
|
183
|
+
"status": "held",
|
|
184
|
+
"reason": "block requires categories/priceRanges/sortOptions with counts the canonical CardProduct[] listing does not supply; no verified prop bridge exists",
|
|
185
|
+
"block": "CategoryFilter",
|
|
186
|
+
"dataShape": "CategoryFilter1Filters",
|
|
187
|
+
"events": ["filterChange"],
|
|
188
|
+
"state": "uncontrolled",
|
|
189
|
+
"requirements": ["categoryProducts"]
|
|
190
|
+
},
|
|
191
|
+
"CategoryFilter4": {
|
|
192
|
+
"status": "held",
|
|
193
|
+
"reason": "block requires an advanced facets contract (modelTypes/rangeBands/frameSizes/colourways/features) the canonical listing does not supply; no verified prop bridge exists",
|
|
194
|
+
"block": "CategoryFilter",
|
|
195
|
+
"dataShape": "CategoryFilter4Facets",
|
|
196
|
+
"events": ["filterChange"],
|
|
197
|
+
"state": "uncontrolled",
|
|
198
|
+
"requirements": ["categoryProducts"]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"category.productCard": {
|
|
203
|
+
"default": "ProductCard1",
|
|
204
|
+
"variants": {
|
|
205
|
+
"ProductCard1": {
|
|
206
|
+
"status": "executable",
|
|
207
|
+
"block": "ProductCard",
|
|
208
|
+
"dataShape": "CardProduct[]",
|
|
209
|
+
"events": ["addToCart", "openProduct"],
|
|
210
|
+
"state": "uncontrolled",
|
|
211
|
+
"requirements": ["categoryProducts"]
|
|
212
|
+
},
|
|
213
|
+
"ProductCard2": {
|
|
214
|
+
"status": "held",
|
|
215
|
+
"reason": "block requires a product with a required rating, a shape the canonical CardProduct listing does not supply; no verified prop bridge exists",
|
|
216
|
+
"block": "ProductCard",
|
|
217
|
+
"dataShape": "ProductCard2Product[]",
|
|
218
|
+
"events": [],
|
|
219
|
+
"state": "uncontrolled",
|
|
220
|
+
"requirements": ["categoryProducts"]
|
|
221
|
+
},
|
|
222
|
+
"ProductCard3": {
|
|
223
|
+
"status": "held",
|
|
224
|
+
"reason": "block requires a product with subtitle/originalPrice/reviewCount/discountPercentage and a badge, a shape the canonical CardProduct listing does not supply; no verified prop bridge exists",
|
|
225
|
+
"block": "ProductCard",
|
|
226
|
+
"dataShape": "ProductCard3Product[]",
|
|
227
|
+
"events": ["toggleWishlist", "viewSimilar", "productClick"],
|
|
228
|
+
"state": "controlled",
|
|
229
|
+
"requirements": ["categoryProducts"]
|
|
230
|
+
},
|
|
231
|
+
"ProductCard5": {
|
|
232
|
+
"status": "held",
|
|
233
|
+
"reason": "block requires a product with name/category/stock/specifications/brand, a shape the canonical CardProduct listing does not supply; no verified prop bridge exists",
|
|
234
|
+
"block": "ProductCard",
|
|
235
|
+
"dataShape": "ProductCard5Product[]",
|
|
236
|
+
"events": ["toggleWishlist", "addToCart", "quickView", "compare", "export"],
|
|
237
|
+
"state": "controlled",
|
|
238
|
+
"requirements": ["categoryProducts"]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
"checkout.checkout": {
|
|
243
|
+
"default": "CheckoutForm1",
|
|
244
|
+
"variants": {
|
|
245
|
+
"CheckoutForm1": {
|
|
246
|
+
"status": "executable",
|
|
247
|
+
"block": "Checkout",
|
|
248
|
+
"dataShape": "CheckoutForm1FormData",
|
|
249
|
+
"events": ["retryTotals", "selectShippingOption", "applyPromo", "submit"],
|
|
250
|
+
"state": "controlled",
|
|
251
|
+
"requirements": ["checkoutCountries", "checkoutPaymentMethods"]
|
|
252
|
+
},
|
|
253
|
+
"CheckoutForm2": {
|
|
254
|
+
"status": "held",
|
|
255
|
+
"reason": "unproven checkout variant: E1 keeps only the default CheckoutForm1, whose behavior is unchanged; no verified prop bridge exists",
|
|
256
|
+
"block": "Checkout",
|
|
257
|
+
"dataShape": "CheckoutForm2Data",
|
|
258
|
+
"events": ["submit"],
|
|
259
|
+
"state": "controlled",
|
|
260
|
+
"requirements": ["checkoutCountries", "checkoutPaymentMethods"]
|
|
261
|
+
},
|
|
262
|
+
"CheckoutForm3": {
|
|
263
|
+
"status": "held",
|
|
264
|
+
"reason": "unproven checkout variant: E1 keeps only the default CheckoutForm1, whose behavior is unchanged; no verified prop bridge exists",
|
|
265
|
+
"block": "Checkout",
|
|
266
|
+
"dataShape": "CheckoutForm3FormData",
|
|
267
|
+
"events": ["submit"],
|
|
268
|
+
"state": "controlled",
|
|
269
|
+
"requirements": ["checkoutCountries", "checkoutPaymentMethods"]
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
"checkout.confirmation": {
|
|
274
|
+
"default": "CheckoutConfirmation",
|
|
275
|
+
"variants": {
|
|
276
|
+
"CheckoutConfirmation": {
|
|
277
|
+
"status": "executable",
|
|
278
|
+
"block": "CheckoutConfirmation",
|
|
279
|
+
"dataShape": "Receipt",
|
|
280
|
+
"events": ["continueShopping"],
|
|
281
|
+
"state": "uncontrolled",
|
|
282
|
+
"requirements": ["lastReceipt"]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"account.login": {
|
|
287
|
+
"default": "LoginPage1",
|
|
288
|
+
"variants": {
|
|
289
|
+
"LoginPage1": {
|
|
290
|
+
"status": "executable",
|
|
291
|
+
"block": "LoginPage1",
|
|
292
|
+
"dataShape": "LoginPage1FormData & AccountAuthResult",
|
|
293
|
+
"events": ["submit", "createAccount"],
|
|
294
|
+
"state": "controlled",
|
|
295
|
+
"requirements": ["accountCustomer", "accountAuthenticated"]
|
|
296
|
+
},
|
|
297
|
+
"LoginPage2": {
|
|
298
|
+
"status": "held",
|
|
299
|
+
"reason": "block's onSubmit takes positional (email, password) with no remember-me field and renders a 2-column social grid; no verified prop bridge from the account element contract exists",
|
|
300
|
+
"block": "LoginPage2",
|
|
301
|
+
"dataShape": "LoginPage2Credentials & AccountAuthResult",
|
|
302
|
+
"events": ["submit", "forgotPassword", "signUp"],
|
|
303
|
+
"state": "controlled",
|
|
304
|
+
"requirements": ["accountCustomer", "accountAuthenticated"]
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
"account.register": {
|
|
309
|
+
"default": "SignUpPage1",
|
|
310
|
+
"variants": {
|
|
311
|
+
"SignUpPage1": {
|
|
312
|
+
"status": "executable",
|
|
313
|
+
"block": "SignUpPage1",
|
|
314
|
+
"dataShape": "SignUpPage1Data & AccountAuthResult",
|
|
315
|
+
"events": ["submit", "signIn"],
|
|
316
|
+
"state": "controlled",
|
|
317
|
+
"requirements": ["accountCustomer", "accountAuthenticated"]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
"account.orders": {
|
|
322
|
+
"default": "OrderHistory2",
|
|
323
|
+
"variants": {
|
|
324
|
+
"OrderHistory2": {
|
|
325
|
+
"status": "executable",
|
|
326
|
+
"block": "OrderHistory2",
|
|
327
|
+
"dataShape": "AccountOrderView[] & OrderHistory2Labels",
|
|
328
|
+
"events": ["viewDetails"],
|
|
329
|
+
"state": "controlled",
|
|
330
|
+
"requirements": ["accountCustomer", "accountAuthenticated"]
|
|
331
|
+
},
|
|
332
|
+
"OrderHistory1": {
|
|
333
|
+
"status": "held",
|
|
334
|
+
"reason": "block models one row per order item (an item-row list), the wrong shape for an order-level history; the canonical order list cannot feed it without inventing an aggregate row",
|
|
335
|
+
"block": "OrderHistory1",
|
|
336
|
+
"dataShape": "OrderHistory1Order[]",
|
|
337
|
+
"events": [],
|
|
338
|
+
"state": "controlled",
|
|
339
|
+
"requirements": ["accountCustomer", "accountAuthenticated"]
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
"capabilities": {
|
|
345
|
+
"category.search.apply": {
|
|
346
|
+
"requirements": ["adapter.getCatalogProducts(query).text"],
|
|
347
|
+
"verification": "unverified",
|
|
348
|
+
"owner": "storefront-catalog-search-filters"
|
|
349
|
+
},
|
|
350
|
+
"category.filter.apply": {
|
|
351
|
+
"requirements": ["adapter.getProducts(query)"],
|
|
352
|
+
"verification": "unverified",
|
|
353
|
+
"owner": "storefront-catalog-search-filters"
|
|
354
|
+
},
|
|
355
|
+
"category.price.range": {
|
|
356
|
+
"requirements": ["adapter.getAvailableFilters(query).priceRange"],
|
|
357
|
+
"verification": "unverified",
|
|
358
|
+
"owner": "storefront-catalog-search-filters"
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://thorcommerce.dev/storefront/feature.schema.json",
|
|
4
|
+
"title": "Storefront feature registry",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["version", "features"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": { "type": "string" },
|
|
9
|
+
"features": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"items": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"required": ["name", "views", "state", "handlers", "adapter", "surfaces", "config"],
|
|
14
|
+
"properties": {
|
|
15
|
+
"name": { "type": "string", "minLength": 1 },
|
|
16
|
+
"description": { "type": "string" },
|
|
17
|
+
"mvp": { "type": "boolean" },
|
|
18
|
+
"route": { "type": "string", "minLength": 1 },
|
|
19
|
+
"views": { "type": "array", "items": { "type": "string" } },
|
|
20
|
+
"state": { "type": "array", "items": { "type": "string" } },
|
|
21
|
+
"handlers": { "type": "array", "items": { "type": "string" } },
|
|
22
|
+
"adapter": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["needs"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"needs": { "type": "array", "items": { "type": "string" } },
|
|
27
|
+
"fallback": { "type": "string" }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"config": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"description": "Per-feature configuration contract: a JSON Schema (the dependency-free subset) that the manifest's features[].config is validated against and whose `default` annotations seed absent fields."
|
|
33
|
+
},
|
|
34
|
+
"configRejection": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"description": "Stable diagnostic for configuration the feature does not accept. With no `when`, any non-empty config key is rejected with this code/message while an empty object stays the no-op (E1 task 3.5, e.g. checkout E_UNPROVEN_CHECKOUT_OPTION). With `when: { field, equals }`, only a config where that field equals the value is rejected (E2 task 4.12); an optional `capability` makes the rejection apply only while that catalog capability is not `verified`, so an unproven option fails loudly instead of being silently coerced to its default.",
|
|
37
|
+
"required": ["code", "message"],
|
|
38
|
+
"properties": {
|
|
39
|
+
"code": { "type": "string", "minLength": 1 },
|
|
40
|
+
"message": { "type": "string", "minLength": 1 },
|
|
41
|
+
"when": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["field", "equals"],
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"properties": {
|
|
46
|
+
"field": { "type": "string", "minLength": 1 },
|
|
47
|
+
"equals": {}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"capability": { "type": "string", "minLength": 1 }
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"presentations": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"description": "Generation-owned block wiring: maps a config value (e.g. config.presentation) to the block variant the feature mount renders.",
|
|
56
|
+
"additionalProperties": { "type": "string" }
|
|
57
|
+
},
|
|
58
|
+
"mounts": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"description": "Feature-instance mounts into a host view's slot. The engine owns the block wiring; a mount never carries an element.",
|
|
61
|
+
"items": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"required": ["id", "host", "slot"],
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"properties": {
|
|
66
|
+
"id": { "type": "string", "minLength": 1 },
|
|
67
|
+
"host": { "type": "string", "minLength": 1 },
|
|
68
|
+
"slot": { "type": "string", "minLength": 1 }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"surfaces": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"description": "Legacy additive alias for existing held registry entries; derived from mounts where both exist.",
|
|
75
|
+
"items": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"required": ["host", "slot", "element"],
|
|
78
|
+
"properties": {
|
|
79
|
+
"host": { "type": "string" },
|
|
80
|
+
"slot": { "type": "string" },
|
|
81
|
+
"element": { "type": "string" }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|