@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.
- package/README.md +321 -152
- package/VENDOR.md +69 -0
- package/assets/openspec/config.yaml +38 -0
- package/assets/templates/AGENTS.md +76 -0
- package/assets/templates/app/app/[[...slug]]/page.tsx +54 -2
- package/assets/templates/app/app/[[...slug]]/renderer.tsx +13 -2
- package/assets/templates/app/app/globals.css +5 -8
- package/assets/templates/app/app/layout.tsx +17 -7
- package/assets/templates/app/app/robots.ts +19 -0
- package/assets/templates/app/app/sitemap.ts +71 -0
- package/assets/templates/app/lib/__STOREFRONT__/actions.ts +612 -0
- package/assets/templates/app/lib/__STOREFRONT__/cart-state.ts +66 -0
- package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +50 -57
- package/assets/templates/app/lib/__STOREFRONT__/commerce.ts +679 -0
- package/assets/templates/app/lib/__STOREFRONT__/composition.ts +26 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.core.ts +197 -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 +264 -51
- package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +6 -3
- package/assets/templates/app/lib/__STOREFRONT__/schema.ts +136 -0
- package/assets/templates/app/lib/__STOREFRONT__/section-observation.ts +182 -0
- package/assets/templates/app/lib/__STOREFRONT__/section-sources.ts +130 -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 +120 -0
- package/assets/templates/app/lib/__STOREFRONT__/shell/props.ts +38 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +129 -10
- package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +5 -1
- package/assets/templates/app/lib/__STOREFRONT__/spec/seo.ts +191 -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 +161 -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 +18 -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/products.client.tsx +100 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/products.tsx +19 -0
- 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/schema.products.ts +39 -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 +159 -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 +45 -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 +439 -0
- package/assets/templates/views/account/schema.ts +53 -0
- package/assets/templates/views/cart/content.ts +11 -0
- package/assets/templates/views/cart/data.ts +23 -0
- package/assets/templates/views/cart/index.tsx +53 -0
- package/assets/templates/views/cart/props.ts +12 -0
- package/assets/templates/views/cart/registry.tsx +81 -0
- package/assets/templates/views/cart/schema.ts +52 -0
- package/assets/templates/views/category/assembly/assembly.json +29 -0
- package/assets/templates/views/category/assembly/index.skeleton.tsx +251 -0
- package/assets/templates/views/category/assembly/registry.skeleton.tsx +34 -0
- package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/binding.json +5 -0
- package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/catalog.part +4 -0
- package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/element.part +7 -0
- package/assets/templates/views/category/assembly/slots/breadcrumb/Breadcrumb/registry.part +18 -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 +299 -0
- package/assets/templates/views/category/props.ts +23 -0
- package/assets/templates/views/category/registry.tsx +122 -0
- package/assets/templates/views/category/schema.ts +91 -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 +33 -0
- package/assets/templates/views/home/assembly/index.skeleton.tsx +90 -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 +24 -0
- package/assets/templates/views/home/index.tsx +57 -85
- 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 +50 -0
- package/assets/templates/views/product/index.tsx +148 -0
- package/assets/templates/views/product/props.ts +27 -0
- package/assets/templates/views/product/registry.tsx +103 -0
- package/assets/templates/views/product/schema.ts +121 -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/bin/storefront.js +2424 -148
- package/examples/merchandising/category-led.json +37 -0
- package/examples/merchandising/collection-led.json +37 -0
- package/package.json +15 -9
- package/recipes/archetypes.json +28 -14
- package/recipes/catalog.json +400 -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 +162 -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 +76 -19
- package/recipes/sections.json +34 -5
- package/recipes/validate.mjs +838 -32
- package/scripts/candidate.mjs +236 -0
- package/scripts/composers.mjs +160 -0
- package/scripts/env-file.mjs +86 -0
- package/scripts/file-classes.mjs +121 -0
- package/scripts/logical-controls.mjs +61 -0
- package/scripts/manifest.mjs +298 -0
- package/scripts/migrate-english-routes.mjs +276 -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 +573 -0
- package/scripts/plan-apply.mjs +987 -0
- package/scripts/preflight.mjs +51 -0
- package/scripts/reconcile.mjs +549 -0
- package/scripts/render-templates.mjs +338 -68
- package/scripts/schema-validate.mjs +132 -0
- package/scripts/section-compat.mjs +300 -0
- package/scripts/section-config.mjs +253 -0
- package/scripts/section-operations.mjs +508 -0
- package/scripts/starter.mjs +17 -0
- package/scripts/sync-assets.mjs +22 -15
- package/scripts/theme/color.mjs +185 -0
- package/scripts/theme/css-theme.mjs +233 -0
- package/scripts/theme/fonts.mjs +123 -0
- package/scripts/theme/import-export.mjs +125 -0
- package/scripts/theme/preset.mjs +93 -0
- package/scripts/theme/theme-plan.mjs +292 -0
- package/scripts/theme/tokens.mjs +137 -0
- package/scripts/view-assembly.mjs +337 -0
- package/scripts/view-selection.mjs +211 -0
- package/skills/sf-feature/SKILL.md +80 -0
- package/skills/sf-setup/SKILL.md +79 -0
- package/skills/sf-theme-gen/SKILL.md +100 -30
- package/skills/sf-theme-gen/references/shadcn-theming.md +87 -0
- package/skills/{shadcn-theming/references/templates → sf-theme-gen/references}/theme-setup.md +15 -19
- package/skills/{shadcn-theming → sf-theme-gen}/references/theming-guide.md +6 -11
- package/skills/sf-view-gen/SKILL.md +146 -93
- package/skills/sf-view-gen/assets/recipes/archetypes.json +28 -14
- package/skills/sf-view-gen/assets/recipes/catalog.json +400 -0
- package/skills/sf-view-gen/assets/recipes/catalog.schema.json +64 -0
- package/skills/sf-view-gen/assets/recipes/features.json +162 -0
- package/skills/sf-view-gen/assets/recipes/recipes.json +76 -19
- package/skills/sf-view-gen/assets/recipes/sections.json +34 -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/products.client.tsx +100 -0
- package/skills/sf-view-gen/assets/templates/views/sections/products.tsx +19 -0
- 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.products.ts +39 -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/{json-render-core/SKILL.md → sf-view-gen/references/json-render-core.md} +4 -27
- package/skills/{json-render-next/SKILL.md → sf-view-gen/references/json-render-next.md} +5 -8
- package/skills/{json-render-react/SKILL.md → sf-view-gen/references/json-render-react.md} +3 -5
- package/skills/{json-render-shadcn/SKILL.md → sf-view-gen/references/json-render-shadcn.md} +3 -5
- package/skills/sf-view-gen/references/views.md +58 -33
- 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 +838 -32
- package/skills/storefront-workflow/SKILL.md +179 -0
- package/skills/{storefront-best-practices/reference/design.md → storefront-workflow/references/design-consistency.md} +13 -29
- package/CHANGELOG.md +0 -61
- package/commands/sf-add-view.md +0 -26
- package/commands/sf-theme.md +0 -20
- package/commands/sf-view.md +0 -25
- package/skills/building-storefronts/SKILL.md +0 -178
- package/skills/building-storefronts/references/frontend-integration.md +0 -229
- package/skills/json-render-remotion/SKILL.md +0 -111
- package/skills/json-render-solid/SKILL.md +0 -204
- package/skills/nextjs-shadcn/SKILL.md +0 -367
- package/skills/nextjs-shadcn/references/architecture.md +0 -499
- package/skills/nextjs-shadcn/references/project-setup.md +0 -162
- package/skills/nextjs-shadcn/references/shadcn-platform.md +0 -261
- package/skills/nextjs-shadcn/references/sidebar.md +0 -274
- package/skills/nextjs-shadcn/references/styling.md +0 -555
- 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
- package/skills/sf-view-gen/assets/templates/views/user-panel/index.tsx +0 -168
- package/skills/sf-view-gen/assets/templates/views/user-panel/registry.tsx +0 -49
- package/skills/shadcn-component-discovery/SKILL.md +0 -273
- package/skills/shadcn-component-discovery/references/registries.md +0 -226
- package/skills/shadcn-theming/SKILL.md +0 -104
- package/skills/storefront-best-practices/SKILL.md +0 -421
- package/skills/storefront-best-practices/reference/components/breadcrumbs.md +0 -123
- package/skills/storefront-best-practices/reference/components/cart-popup.md +0 -189
- package/skills/storefront-best-practices/reference/components/country-selector.md +0 -298
- package/skills/storefront-best-practices/reference/components/footer.md +0 -112
- package/skills/storefront-best-practices/reference/components/hero.md +0 -241
- package/skills/storefront-best-practices/reference/components/megamenu.md +0 -239
- package/skills/storefront-best-practices/reference/components/navbar.md +0 -397
- package/skills/storefront-best-practices/reference/components/popups.md +0 -221
- package/skills/storefront-best-practices/reference/components/product-card.md +0 -125
- package/skills/storefront-best-practices/reference/components/product-reviews.md +0 -217
- package/skills/storefront-best-practices/reference/components/product-slider.md +0 -174
- package/skills/storefront-best-practices/reference/components/search.md +0 -101
- package/skills/storefront-best-practices/reference/connecting-to-backend.md +0 -391
- package/skills/storefront-best-practices/reference/features/promotions.md +0 -307
- package/skills/storefront-best-practices/reference/features/wishlist.md +0 -230
- package/skills/storefront-best-practices/reference/layouts/account.md +0 -380
- package/skills/storefront-best-practices/reference/layouts/cart.md +0 -316
- package/skills/storefront-best-practices/reference/layouts/checkout.md +0 -486
- package/skills/storefront-best-practices/reference/layouts/home-page.md +0 -264
- package/skills/storefront-best-practices/reference/layouts/order-confirmation.md +0 -231
- package/skills/storefront-best-practices/reference/layouts/product-details.md +0 -527
- package/skills/storefront-best-practices/reference/layouts/product-listing.md +0 -520
- package/skills/storefront-best-practices/reference/layouts/static-pages.md +0 -356
- package/skills/storefront-best-practices/reference/medusa.md +0 -307
- /package/skills/{storefront-best-practices/reference → storefront-workflow/references}/mobile-responsiveness.md +0 -0
- /package/skills/{storefront-best-practices/reference → storefront-workflow/references}/seo.md +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
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": "/account",
|
|
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": "/contact",
|
|
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": "navigability",
|
|
127
|
+
"mvp": true,
|
|
128
|
+
"description": "Cross-cutting navigability feature: entity-derived breadcrumbs with BreadcrumbList structured data on the product and category surfaces, plus skip-to-content, current-page indication, back-to-top, and a mobile bottom navigation in the shared shell.",
|
|
129
|
+
"views": [],
|
|
130
|
+
"state": [],
|
|
131
|
+
"handlers": [],
|
|
132
|
+
"adapter": { "needs": [] },
|
|
133
|
+
"config": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"additionalProperties": false,
|
|
136
|
+
"properties": {
|
|
137
|
+
"presentation": { "type": "string", "enum": ["default"], "default": "default" },
|
|
138
|
+
"showBackToTop": { "type": "boolean", "default": true }
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"presentations": { "default": "Breadcrumb" },
|
|
142
|
+
"mounts": [
|
|
143
|
+
{ "id": "category-breadcrumb", "host": "category", "slot": "breadcrumb" },
|
|
144
|
+
{ "id": "product-breadcrumb", "host": "product", "slot": "breadcrumb" }
|
|
145
|
+
],
|
|
146
|
+
"surfaces": []
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "recommendations",
|
|
150
|
+
"mvp": false,
|
|
151
|
+
"description": "Related-products surface injected into the product view.",
|
|
152
|
+
"views": [],
|
|
153
|
+
"state": ["recommendedProducts"],
|
|
154
|
+
"handlers": [],
|
|
155
|
+
"adapter": { "needs": ["getRelatedProducts"], "fallback": "catalog" },
|
|
156
|
+
"config": { "type": "object", "additionalProperties": false, "properties": {} },
|
|
157
|
+
"surfaces": [
|
|
158
|
+
{ "host": "product", "slot": "product.related", "element": "ProductCard" }
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://thorcommerce.dev/storefront/manifest.schema.json",
|
|
4
|
+
"title": "Thor Commerce Storefront Composition Manifest",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["version", "name", "theme", "views", "features"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": { "type": "string" },
|
|
9
|
+
"version": { "type": "string", "minLength": 1 },
|
|
10
|
+
"name": { "type": "string", "minLength": 1 },
|
|
11
|
+
"siteName": { "type": "string" },
|
|
12
|
+
"theme": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"required": ["mode"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"mode": { "type": "string", "enum": ["light", "dark", "system"] }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"views": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": ["name"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"name": { "type": "string", "minLength": 1 },
|
|
26
|
+
"intents": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"additionalProperties": { "type": "string" }
|
|
29
|
+
},
|
|
30
|
+
"variants": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": { "type": "string" }
|
|
33
|
+
},
|
|
34
|
+
"sections": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"properties": {
|
|
40
|
+
"id": { "type": "string", "minLength": 1 },
|
|
41
|
+
"type": { "type": "string", "minLength": 1 },
|
|
42
|
+
"name": { "type": "string", "minLength": 1 },
|
|
43
|
+
"variant": { "type": "string" }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"preservedSectionIds": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": { "type": "string", "minLength": 1 }
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"features": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"required": ["name"],
|
|
62
|
+
"properties": {
|
|
63
|
+
"name": { "type": "string", "minLength": 1 },
|
|
64
|
+
"config": { "type": "object" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://thorcommerce.dev/storefront/operations.schema.json",
|
|
4
|
+
"title": "Thor Commerce Storefront Operations Envelope",
|
|
5
|
+
"description": "The versioned input envelope for `storefront plan --operations`. `formatVersion` must equal 1, aligned with the catalog `catalogVersion` (both 1 in E1). E1 accepts exactly six namespaced operations: view.add, view.remove, view.configure, feature.add, feature.remove, feature.configure. The per-operation field contract (which fields are required, which optional, and the exactly-one `intent` / `variant` / `reset: true` rule) is declared by OPERATION_SCHEMAS in scripts/operations.mjs, which is the runtime authority; this published schema is the closed structural contract. Unknown operation names, unknown fields, and a `force` field are not part of version 1 (`force` is an internal add/refresh control used by the legacy `view add --force` alias and by plan/apply, never a public operation field).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["formatVersion", "operations"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": { "type": "string" },
|
|
11
|
+
"formatVersion": { "type": "number", "enum": [1] },
|
|
12
|
+
"operations": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"items": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"required": ["op"],
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"properties": {
|
|
19
|
+
"op": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"enum": [
|
|
22
|
+
"view.add",
|
|
23
|
+
"view.remove",
|
|
24
|
+
"view.configure",
|
|
25
|
+
"feature.add",
|
|
26
|
+
"feature.remove",
|
|
27
|
+
"feature.configure"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"view": { "type": "string", "minLength": 1 },
|
|
31
|
+
"slot": { "type": "string", "minLength": 1 },
|
|
32
|
+
"intent": { "type": "string", "minLength": 1 },
|
|
33
|
+
"variant": { "type": "string", "minLength": 1 },
|
|
34
|
+
"reset": { "type": "boolean" },
|
|
35
|
+
"intents": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
36
|
+
"variants": { "type": "object", "additionalProperties": { "type": "string" } },
|
|
37
|
+
"feature": { "type": "string", "minLength": 1 },
|
|
38
|
+
"config": { "type": "object" }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"properties": {
|
|
24
24
|
"name": {
|
|
25
25
|
"type": "string",
|
|
26
|
-
"enum": ["home", "product", "category", "checkout", "
|
|
26
|
+
"enum": ["home", "product", "cart", "category", "checkout", "account", "static"]
|
|
27
27
|
},
|
|
28
28
|
"description": { "type": "string" },
|
|
29
29
|
"route": { "type": "string" },
|
|
@@ -51,7 +51,16 @@
|
|
|
51
51
|
"type": "string",
|
|
52
52
|
"enum": ["testimonials", "faq", "statistics", "logoCloud", "pricing"]
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
},
|
|
55
|
+
"registeredSlots": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": { "type": "string" }
|
|
58
|
+
},
|
|
59
|
+
"allowedSections": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": { "type": "string", "enum": ["faq", "cta"] }
|
|
62
|
+
},
|
|
63
|
+
"maxSections": { "type": "number" }
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
}
|