@jay-framework/wix-stores 0.15.0 → 0.15.4

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 CHANGED
@@ -4,11 +4,13 @@ Wix Stores integration for Jay Framework using the Catalog V3 API. Provides head
4
4
 
5
5
  ## Features
6
6
 
7
- - **Catalog V3 API** - Uses `productsV3` and `@wix/categories` for product and category data
8
- - **Three-phase rendering** - Slow (build/SSG), Fast (request/SSR), Interactive (client)
9
- - **Category-prefixed routes** - Optional URL prefixes per product line (e.g., `/products/polgat/shirt-name`)
10
- - **Unified search + category** - Single `product-search` component handles both search pages and category listings
11
- - **Shared cart** - Delegates cart operations to `@jay-framework/wix-cart`
7
+ - **Catalog V3 API** Uses `productsV3` and `@wix/categories` for product and category data
8
+ - **Three-phase rendering** Slow (build/SSG), Fast (request/SSR), Interactive (client)
9
+ - **Flexible URL patterns** Configurable URL templates with `{prefix}`, `{category}`, `{slug}` placeholders
10
+ - **Category header** Automatic category metadata (name, description, image, breadcrumbs, SEO)
11
+ - **Filter URL persistence** Filters saved to query params for sharable, bookmarkable URLs
12
+ - **Canonical redirects** — 301 redirects for non-canonical product/category URLs
13
+ - **Shared cart** — Delegates cart operations to `@jay-framework/wix-cart`
12
14
 
13
15
  ## Headless Components
14
16
 
@@ -20,21 +22,19 @@ Complete product detail page with variant selection and add-to-cart.
20
22
  - **Fast phase**: Inventory status, pricing, variant availability
21
23
  - **Interactive**: Option/modifier selection, quantity, add to cart
22
24
 
23
- Route: `/products/[slug]` or `/products/[category]/[slug]` (when category prefixes are configured)
24
-
25
25
  ### Product Search (`product-search`)
26
26
 
27
- Unified search and category listing component. Replaces the previous separate `category-page` component.
27
+ Unified search, category listing, and product browsing component.
28
28
 
29
- - **Slow phase**: Available categories for filtering
30
- - **Fast phase**: Initial product results with price aggregations
31
- - **Interactive**: Search input, category/price/stock filters, sorting, load more
29
+ - **Slow phase**: Category header (name, description, image, breadcrumbs, SEO), available categories for filtering
30
+ - **Fast phase**: Initial product results with price aggregations, filters restored from URL query params
31
+ - **Interactive**: Search input, category/price/stock filters, sorting, load more, filter URL persistence
32
32
 
33
- When a `category` URL parameter is provided (e.g., from a route like `/products/polgat/`):
33
+ Supports three roles depending on route params:
34
34
 
35
- - Scopes all searches to products within that category hierarchy
36
- - Shows only child categories of the root as filter options (root category is hidden)
37
- - Enables per-category template designs via separate jay-html files
35
+ - **Search page** no category context, shows all products
36
+ - **Top-level category page** scoped to a root category, shows child categories as filters
37
+ - **Sub-category page** scoped to a sub-category, shows header with breadcrumbs
38
38
 
39
39
  ### Category List (`category-list`)
40
40
 
@@ -42,142 +42,203 @@ Lists all store categories for navigation.
42
42
 
43
43
  ## Configuration
44
44
 
45
- ### Basic Setup
45
+ ### Setup
46
46
 
47
47
  The plugin requires `@jay-framework/wix-server-client` to be configured with Wix API credentials.
48
48
 
49
- Run setup to create the config template:
50
-
51
49
  ```bash
52
50
  jay-stack setup wix-stores
53
51
  ```
54
52
 
55
- ### Category Prefixes (Optional)
53
+ This creates `config/.wix-stores.yaml` and generates `agent-kit/references/wix-stores/categories.yaml` with the full category tree.
56
54
 
57
- To enable category-prefixed product routes, create `config/.wix-stores.yaml`:
55
+ ### Config File (`config/.wix-stores.yaml`)
58
56
 
59
57
  ```yaml
60
- categoryPrefixes:
61
- - categoryId: '024a9fff-77de-4508-b82c-5fce24f74757'
62
- prefix: 'polgat'
63
- name: 'פולגת'
64
- - categoryId: 'eac4db24-04cc-4f36-86cf-c9da6e873421'
65
- prefix: 'kitan'
66
- name: 'כיתן'
58
+ # URL templates — how the component builds canonical links
59
+ urls:
60
+ product: '/products/{slug}' # default
61
+ category: null # no category pages by default
62
+
63
+ # Fallback category for pages without category context
64
+ defaultCategory: 'all-products' # slug of the fallback category
67
65
  ```
68
66
 
69
- Each entry maps a root Wix category to a URL prefix. Products belonging to any child of the root category get URLs like `/products/{prefix}/{product-slug}`.
67
+ URL templates use three placeholders:
70
68
 
71
- To find category IDs, run setup to generate the category tree reference:
69
+ | Placeholder | Source | Description |
70
+ | ------------ | ------------------------------------------ | ------------------------- |
71
+ | `{slug}` | Wix product slug | Product identifier in URL |
72
+ | `{category}` | Wix category slug (from `mainCategoryId`) | Sub-category in URL |
73
+ | `{prefix}` | Wix root category slug (from parent chain) | Top-level category in URL |
72
74
 
73
- ```bash
74
- jay-stack setup wix-stores
75
+ ## URL Patterns
76
+
77
+ ### Pattern 1: Simple Store (No Categories)
78
+
79
+ ```yaml
80
+ urls:
81
+ product: '/products/{slug}'
75
82
  ```
76
83
 
77
- This creates `agent-kit/references/wix-stores/categories.yaml` with all category IDs, names, product counts, and parent-child relationships as a tree.
84
+ ```
85
+ src/pages/products/
86
+ ├── page.jay-html → /products (search page)
87
+ └── [slug]/page.jay-html → /products/:slug (product page)
88
+ ```
78
89
 
79
- ### Route Structure with Category Prefixes
90
+ ### Pattern 2: Categories (No Top-Level Prefixes)
91
+
92
+ ```yaml
93
+ urls:
94
+ product: '/products/{category}/{slug}'
95
+ category: '/products/{category}'
96
+ ```
80
97
 
81
98
  ```
82
99
  src/pages/products/
83
- ├── page.jay-html # /products (default search, optional)
84
- ├── [slug]/page.jay-html # /products/:slug (default product page, optional)
100
+ ├── page.jay-html /products (all products)
101
+ └── [category]/
102
+ ├── page.jay-html → /products/:category (category listing)
103
+ └── [slug]/page.jay-html → /products/:category/:slug (product)
104
+ ```
105
+
106
+ ### Pattern 3: Top-Level Prefixes + Sub-Categories
107
+
108
+ ```yaml
109
+ urls:
110
+ product: '/products/{prefix}/{category}/{slug}'
111
+ category: '/products/{prefix}/{category}'
112
+ defaultCategory: 'all-products'
113
+ ```
114
+
115
+ ```
116
+ src/pages/products/
117
+ ├── page.jay-html → /products (all products)
118
+ ├── [prefix]/
119
+ │ ├── page.jay-html → /products/:prefix (generic prefix page)
120
+ │ └── [category]/
121
+ │ ├── page.jay-html → /products/:prefix/:category (sub-category)
122
+ │ └── [slug]/page.jay-html → /products/:prefix/:category/:slug (product)
85
123
  ├── polgat/
86
- │ ├── page.jay-html # /products/polgat (polgat-design search)
87
- │ └── [slug]/page.jay-html # /products/polgat/:slug (polgat product page)
124
+ │ ├── page.jay-html /products/polgat (custom design)
125
+ │ └── [category]/
126
+ │ ├── page.jay-html → /products/polgat/:category
127
+ │ └── [slug]/page.jay-html → /products/polgat/:category/:slug
88
128
  └── kitan/
89
- ├── page.jay-html # /products/kitan (kitan-design search)
90
- └── [slug]/page.jay-html # /products/kitan/:slug (kitan product page)
129
+ ├── page.jay-html /products/kitan (custom design)
130
+ └── [category]/
131
+ ├── page.jay-html → /products/kitan/:category
132
+ └── [slug]/page.jay-html → /products/kitan/:category/:slug
91
133
  ```
92
134
 
93
- Each prefix directory gets its own jay-html templates, enabling different visual designs per product line. Jay's routing precedence ensures static segments (`polgat`, `kitan`) take priority over the dynamic `[slug]` parameter.
135
+ Static directories (`polgat/`, `kitan/`) override the dynamic `[prefix]/` route, enabling different template designs per category. Any other prefix falls through to `[prefix]/`.
94
136
 
95
- Products without a matching category prefix fall back to `/products/[slug]` if that route exists, or return 404 if it doesn't.
137
+ ### Static Overrides
96
138
 
97
- ## Server Actions
98
-
99
- ### `searchProducts`
139
+ Jay's routing supports static segments overriding dynamic params at any level:
100
140
 
101
- Search products with filtering, sorting, and price aggregations.
141
+ ```
142
+ src/pages/products/polgat/
143
+ ├── [category]/page.jay-html → default sub-category design
144
+ ├── sale/page.jay-html → custom design for "sale" sub-category
145
+ └── [category]/
146
+ ├── [slug]/page.jay-html → default product page
147
+ └── premium-oxford/page.jay-html → custom page for one specific product
148
+ ```
102
149
 
103
- ```typescript
104
- const results = await searchProducts({
105
- query: 'shoes',
106
- filters: { inStockOnly: true, categoryIds: ['cat-123'] },
107
- sortBy: 'price_asc',
108
- pageSize: 12,
109
- });
150
+ ### Setting Params for Static Routes
151
+
152
+ Static route directories use `jay-params` to tell the component which category they represent:
153
+
154
+ ```html
155
+ <!-- src/pages/products/polgat/page.jay-html -->
156
+ <script type="application/jay-params">
157
+ category: polgat
158
+ </script>
159
+ <script
160
+ type="application/jay-headless"
161
+ plugin="@jay-framework/wix-stores"
162
+ contract="product-search"
163
+ key="search"
164
+ ></script>
110
165
  ```
111
166
 
112
- CLI:
167
+ ## Category Header
113
168
 
114
- ```bash
115
- jay-stack action wix-stores/searchProducts
116
- jay-stack action wix-stores/searchProducts --input '{"query": "shoes", "pageSize": 5}'
117
- ```
169
+ The category header is always loaded. The component resolves category metadata using a fallback chain:
118
170
 
119
- ### `getProductBySlug`
171
+ 1. **`subcategory` param** → load sub-category
172
+ 2. **`category` / `prefix` param** → load root category
173
+ 3. **Neither** → load `defaultCategory` from config
120
174
 
121
- Fetch a single product by its URL slug.
175
+ If the resolved category is missing image, description, or SEO data, the component walks up the parent chain until it finds the data. Each field inherits independently.
122
176
 
123
- ```typescript
124
- const product = await getProductBySlug({ slug: 'blue-sneakers' });
125
- ```
177
+ The header includes:
126
178
 
127
- CLI:
179
+ - Category name and description
180
+ - Category image
181
+ - Product count
182
+ - Breadcrumb trail with navigation URLs
183
+ - SEO metadata (title tags, meta description, keywords)
128
184
 
129
- ```bash
130
- jay-stack action wix-stores/getProductBySlug --input '{"slug": "blue-sneakers"}'
131
- ```
185
+ ## Filter URL Persistence
132
186
 
133
- ### `getCategories`
187
+ Filters are persisted in URL query parameters for sharable, bookmarkable URLs:
134
188
 
135
- Get all visible categories for filtering.
189
+ | Filter | Query Param | Example |
190
+ | --------------- | ----------- | ------------------- |
191
+ | Search term | `q` | `?q=cotton` |
192
+ | Category filter | `cat` | `?cat=shirts,pants` |
193
+ | Min price | `min` | `?min=50` |
194
+ | Max price | `max` | `?max=200` |
195
+ | In stock only | `inStock` | `?inStock=1` |
196
+ | Sort | `sort` | `?sort=priceAsc` |
136
197
 
137
- ```typescript
138
- const categories = await getCategories();
139
- ```
198
+ All values use slugs/display values, not internal IDs.
140
199
 
141
- CLI:
200
+ Example: `/products/polgat?q=cotton&cat=shirts&min=50&sort=priceAsc`
142
201
 
143
- ```bash
144
- jay-stack action wix-stores/getCategories
145
- ```
202
+ - **Saving**: On filter change, the URL is updated via `replaceState` (no page reload)
203
+ - **Restoring**: On page load (SSR), query params are parsed and applied as the initial filter state. The server-rendered page shows filtered results immediately.
146
204
 
147
- ## Agent Kit References
205
+ ## Canonical URLs & Redirects
148
206
 
149
- Running `jay-stack setup wix-stores` or `jay-stack agent-kit` generates a category tree reference at `agent-kit/references/wix-stores/categories.yaml`. This file contains:
207
+ The `urls` config defines canonical URLs. If a page is accessed at a non-canonical path, the component issues a 301 redirect.
150
208
 
151
- - All visible categories with IDs, names, slugs, and product counts
152
- - Full parent-child hierarchy as a nested tree
153
- - Configured category prefixes (if any) with their mapped category names
209
+ **Products:** Identified by slug. The canonical `{category}` comes from `mainCategoryId`, and `{prefix}` from the root category. Wrong category or prefix in the URL → 301 redirect.
154
210
 
155
- ## Architecture
211
+ **Categories:** Identified by slug. The canonical `{prefix}` comes from the parent chain. Wrong prefix in the URL → 301 redirect.
156
212
 
157
- ### Plugin Initialization
213
+ ## Server Actions
158
214
 
215
+ ### `searchProducts`
216
+
217
+ ```bash
218
+ jay-stack action wix-stores/searchProducts
219
+ jay-stack action wix-stores/searchProducts --input '{"query": "shoes", "pageSize": 5}'
159
220
  ```
160
- wix-server-client (init first)
161
- └── Registers WIX_CLIENT_SERVICE (API key auth)
162
221
 
163
- wix-stores (init second)
164
- ├── Loads config/.wix-stores.yaml (category prefixes)
165
- ├── Registers WIX_STORES_SERVICE_MARKER (products, categories, inventory)
166
- └── Client: Registers WIX_STORES_CONTEXT (delegates cart to wix-cart)
222
+ ### `getProductBySlug`
223
+
224
+ ```bash
225
+ jay-stack action wix-stores/getProductBySlug --input '{"slug": "blue-sneakers"}'
167
226
  ```
168
227
 
169
- ### Category Prefix Resolution
228
+ ### `getCategories`
170
229
 
171
- When category prefixes are configured:
230
+ ```bash
231
+ jay-stack action wix-stores/getCategories
232
+ ```
233
+
234
+ ## Agent Kit References
172
235
 
173
- 1. **`loadProductParams`** fetches all products with `ALL_CATEGORIES_INFO` and resolves each product's prefix
174
- 2. **`searchProducts`** includes `ALL_CATEGORIES_INFO` to generate correct prefixed URLs in results
175
- 3. **`mapProductToCard`** generates URLs like `/products/polgat/shirt-name` based on the product's category ancestry
176
- 4. **Product page** validates that the URL's category prefix matches the product's actual category
236
+ Running `jay-stack setup wix-stores` generates `agent-kit/references/wix-stores/categories.yaml` the full category tree with IDs, names, slugs, product counts, and parent-child hierarchy.
177
237
 
178
- ## Design Log
238
+ ## Design Logs
179
239
 
180
- See [Design Log 10 - Category-Prefixed Product Routes](../../design-log/10%20-%20category-prefixed-product-routes.md) for the full design rationale.
240
+ - [Design Log 10 - Category-Prefixed Product Routes](../../design-log/10%20-%20category-prefixed-product-routes.md)
241
+ - [Design Log 11 - Category Deep-Linking & Header](../../design-log/11%20-%20category-deep-linking-and-header.md)
181
242
 
182
243
  ## License
183
244
 
@@ -78,7 +78,10 @@ export type CategoryPageSlowViewState = Pick<CategoryPageViewState, '_id' | 'nam
78
78
  ribbon: CategoryPageViewState['products'][number]['ribbon'];
79
79
  brand: CategoryPageViewState['products'][number]['brand'];
80
80
  quickOption: Pick<CategoryPageViewState['products'][number]['quickOption'], '_id' | 'name' | 'optionRenderType'> & {
81
- choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode' | 'variantId'>>;
81
+ choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
82
+ };
83
+ secondQuickOption: Pick<CategoryPageViewState['products'][number]['secondQuickOption'], '_id' | 'name' | 'optionRenderType'> & {
84
+ choices: Array<Pick<CategoryPageViewState['products'][number]['secondQuickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
82
85
  };
83
86
  }>;
84
87
  };
@@ -87,6 +90,9 @@ export type CategoryPageFastViewState = Pick<CategoryPageViewState, 'hasMore' |
87
90
  products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
88
91
  quickOption: {
89
92
  choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
93
+ };
94
+ secondQuickOption: {
95
+ choices: Array<Pick<CategoryPageViewState['products'][number]['secondQuickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
90
96
  };
91
97
  }>;
92
98
  loadedProducts: Array<CategoryPageViewState['loadedProducts'][number]>;
@@ -96,6 +102,9 @@ export type CategoryPageInteractiveViewState = Pick<CategoryPageViewState, 'hasM
96
102
  products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
97
103
  quickOption: {
98
104
  choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
105
+ };
106
+ secondQuickOption: {
107
+ choices: Array<Pick<CategoryPageViewState['products'][number]['secondQuickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
99
108
  };
100
109
  }>;
101
110
  loadedProducts: Array<CategoryPageViewState['loadedProducts'][number]>;
@@ -165,15 +165,21 @@ tags:
165
165
  # Quick-add behavior
166
166
  - tag: quickAddType
167
167
  type: variant
168
- dataType: enum (SIMPLE | SINGLE_OPTION | NEEDS_CONFIGURATION)
168
+ dataType: enum (SIMPLE | SINGLE_OPTION | COLOR_AND_TEXT_OPTIONS | NEEDS_CONFIGURATION)
169
169
  description: |
170
170
  SIMPLE = no options, show Add to Cart button
171
171
  SINGLE_OPTION = one option, show choices on hover (click = add to cart)
172
+ COLOR_AND_TEXT_OPTIONS = two options (color + text), color pre-selected, text selection adds to cart
172
173
  NEEDS_CONFIGURATION = multiple options or modifiers, link to product page
173
174
 
174
175
  - tag: quickOption
175
176
  type: sub-contract
176
- description: Primary option for quick selection (only when quickAddType = SINGLE_OPTION)
177
+ description: Primary option for quick selection (color swatches for COLOR_AND_TEXT_OPTIONS, or the single option for SINGLE_OPTION)
178
+ link: ./product-options
179
+
180
+ - tag: secondQuickOption
181
+ type: sub-contract
182
+ description: Secondary option for quick selection (text choices for COLOR_AND_TEXT_OPTIONS). Rendered as buttons or dropdown. Selection adds to cart.
177
183
  link: ./product-options
178
184
 
179
185
  - tag: viewOptionsButton
@@ -54,6 +54,7 @@ export enum ProductType {
54
54
  export enum QuickAddType {
55
55
  SIMPLE,
56
56
  SINGLE_OPTION,
57
+ COLOR_AND_TEXT_OPTIONS,
57
58
  NEEDS_CONFIGURATION
58
59
  }
59
60
 
@@ -75,7 +76,8 @@ export interface ProductCardViewState {
75
76
  productType: ProductType,
76
77
  isAddingToCart: boolean,
77
78
  quickAddType: QuickAddType,
78
- quickOption: ProductOptionsViewState
79
+ quickOption: ProductOptionsViewState,
80
+ secondQuickOption: ProductOptionsViewState
79
81
  }
80
82
 
81
83
  export type ProductCardSlowViewState = Pick<ProductCardViewState, '_id' | 'name' | 'slug' | 'productUrl' | 'categoryPrefix' | 'hasDiscount' | 'hasRibbon' | 'productType' | 'quickAddType'> & {
@@ -85,19 +87,28 @@ export type ProductCardSlowViewState = Pick<ProductCardViewState, '_id' | 'name'
85
87
  ribbon: ProductCardViewState['ribbon'];
86
88
  brand: ProductCardViewState['brand'];
87
89
  quickOption: Pick<ProductCardViewState['quickOption'], '_id' | 'name' | 'optionRenderType'> & {
88
- choices: Array<Pick<ProductCardViewState['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode' | 'variantId'>>;
90
+ choices: Array<Pick<ProductCardViewState['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
91
+ };
92
+ secondQuickOption: Pick<ProductCardViewState['secondQuickOption'], '_id' | 'name' | 'optionRenderType'> & {
93
+ choices: Array<Pick<ProductCardViewState['secondQuickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
89
94
  };
90
95
  };
91
96
 
92
97
  export type ProductCardFastViewState = Pick<ProductCardViewState, 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
93
98
  quickOption: {
94
99
  choices: Array<Pick<ProductCardViewState['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
100
+ };
101
+ secondQuickOption: {
102
+ choices: Array<Pick<ProductCardViewState['secondQuickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
95
103
  };
96
104
  };
97
105
 
98
106
  export type ProductCardInteractiveViewState = Pick<ProductCardViewState, 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
99
107
  quickOption: {
100
108
  choices: Array<Pick<ProductCardViewState['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
109
+ };
110
+ secondQuickOption: {
111
+ choices: Array<Pick<ProductCardViewState['secondQuickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
101
112
  };
102
113
  };
103
114
 
@@ -106,7 +117,8 @@ export interface ProductCardRefs {
106
117
  productLink: HTMLElementProxy<ProductCardViewState, HTMLAnchorElement>,
107
118
  addToCartButton: HTMLElementProxy<ProductCardViewState, HTMLButtonElement>,
108
119
  viewOptionsButton: HTMLElementProxy<ProductCardViewState, HTMLButtonElement>,
109
- quickOption: ProductOptionsRefs
120
+ quickOption: ProductOptionsRefs,
121
+ secondQuickOption: ProductOptionsRefs
110
122
  }
111
123
 
112
124
 
@@ -114,7 +126,8 @@ export interface ProductCardRepeatedRefs {
114
126
  productLink: HTMLElementCollectionProxy<ProductCardViewState, HTMLAnchorElement>,
115
127
  addToCartButton: HTMLElementCollectionProxy<ProductCardViewState, HTMLButtonElement>,
116
128
  viewOptionsButton: HTMLElementCollectionProxy<ProductCardViewState, HTMLButtonElement>,
117
- quickOption: ProductOptionsRepeatedRefs
129
+ quickOption: ProductOptionsRepeatedRefs,
130
+ secondQuickOption: ProductOptionsRepeatedRefs
118
131
  }
119
132
 
120
133
  export type ProductCardContract = JayContract<ProductCardViewState, ProductCardRefs, ProductCardSlowViewState, ProductCardFastViewState, ProductCardInteractiveViewState>
@@ -49,11 +49,6 @@ tags:
49
49
  phase: fast+interactive
50
50
  description: Whether this choice has available stock
51
51
 
52
- - tag: variantId
53
- type: data
54
- dataType: string
55
- description: The variant ID to use when adding to cart (for single-option products)
56
-
57
52
  - tag: isSelected
58
53
  type: variant
59
54
  dataType: boolean
@@ -17,7 +17,6 @@ export interface ChoiceOfProductOptionsViewState {
17
17
  choiceType: ChoiceType,
18
18
  colorCode: string,
19
19
  inStock: boolean,
20
- variantId: string,
21
20
  isSelected: boolean
22
21
  }
23
22
 
@@ -29,7 +28,7 @@ export interface ProductOptionsViewState {
29
28
  }
30
29
 
31
30
  export type ProductOptionsSlowViewState = Pick<ProductOptionsViewState, '_id' | 'name' | 'optionRenderType'> & {
32
- choices: Array<Pick<ProductOptionsViewState['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode' | 'variantId'>>;
31
+ choices: Array<Pick<ProductOptionsViewState['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
33
32
  };
34
33
 
35
34
  export type ProductOptionsFastViewState = {
@@ -168,6 +168,11 @@ tags:
168
168
  dataType: boolean
169
169
  elementType: HTMLInputElement
170
170
  description: Category checkbox
171
+
172
+ - tag: categoryUrl
173
+ type: data
174
+ dataType: string
175
+ description: Deep-link URL to this category's dedicated page
171
176
 
172
177
  # Availability filter (fast+interactive)
173
178
  - tag: inStockOnly
@@ -250,3 +255,69 @@ tags:
250
255
  elementType: HTMLButtonElement
251
256
  description: Button to use this suggestion
252
257
 
258
+ # Category header — always populated via fallback chain (subcategory → category → defaultCategory)
259
+ - tag: categoryHeader
260
+ type: sub-contract
261
+ description: Active category information for the page header
262
+ tags:
263
+ - {tag: name, type: data, dataType: string, description: Category display name}
264
+ - {tag: description, type: data, dataType: string, description: Category plain text description}
265
+ - {tag: imageUrl, type: data, dataType: string, description: Category main image URL}
266
+ - {tag: hasImage, type: variant, dataType: boolean, description: Whether category has an image}
267
+ - {tag: productCount, type: data, dataType: number, description: Number of products in category}
268
+
269
+ - tag: breadcrumbs
270
+ type: sub-contract
271
+ repeated: true
272
+ trackBy: categoryId
273
+ description: Breadcrumb trail from root to current category
274
+ tags:
275
+ - {tag: categoryId, type: data, dataType: string, description: Category GUID}
276
+ - {tag: name, type: data, dataType: string, description: Category name}
277
+ - {tag: slug, type: data, dataType: string, description: Category slug}
278
+ - {tag: url, type: data, dataType: string, description: Full URL path for breadcrumb navigation}
279
+
280
+ - tag: seoData
281
+ type: sub-contract
282
+ description: Category SEO metadata
283
+ tags:
284
+ - tag: tags
285
+ type: sub-contract
286
+ repeated: true
287
+ trackBy: position
288
+ description: SEO tag information
289
+ tags:
290
+ - {tag: position, type: data, dataType: string, description: Tag position as two digit string}
291
+ - {tag: type, type: data, dataType: string, description: SEO tag type}
292
+ - tag: props
293
+ type: sub-contract
294
+ repeated: true
295
+ trackBy: key
296
+ description: Key-value pair of SEO properties
297
+ tags:
298
+ - {tag: key, type: data, dataType: string}
299
+ - {tag: value, type: data, dataType: string}
300
+ - tag: meta
301
+ type: sub-contract
302
+ repeated: true
303
+ trackBy: key
304
+ description: SEO tag metadata
305
+ tags:
306
+ - {tag: key, type: data, dataType: string}
307
+ - {tag: value, type: data, dataType: string}
308
+ - {tag: children, type: data, dataType: string, description: SEO tag inner content}
309
+ - tag: settings
310
+ type: sub-contract
311
+ description: SEO general settings
312
+ tags:
313
+ - {tag: preventAutoRedirect, type: data, dataType: boolean, description: Whether auto-redirect from old URL is enabled}
314
+ - tag: keywords
315
+ type: sub-contract
316
+ repeated: true
317
+ trackBy: term
318
+ description: User-selected keyword terms
319
+ tags:
320
+ - {tag: term, type: data, dataType: string, description: Keyword value}
321
+ - {tag: isMain, type: data, dataType: boolean, description: Whether this is the main focus keyword}
322
+ - {tag: origin, type: data, dataType: string, description: Source that added the keyword}
323
+