@wix/agent-skills 1.18.1 → 1.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +4 -4
  3. package/skills/wix-app/references/APP_VALIDATION.md +7 -33
  4. package/skills/wix-app/references/DASHBOARD_PAGE.md +18 -14
  5. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +6 -3
  6. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +23 -26
  7. package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +7 -6
  8. package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +71 -4
  9. package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +67 -7
  10. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -1
  11. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +9 -6
  12. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +59 -89
  13. package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +23 -4
  14. package/skills/wix-base44-connector/SKILL.md +168 -48
  15. package/skills/wix-base44-connector/scripts/{utils.js → utils.cjs} +69 -48
  16. package/skills/wix-docs/SKILL.md +109 -111
  17. package/skills/wix-docs/references/API_SPEC_SEARCH.md +35 -23
  18. package/skills/wix-docs/references/CALLING.md +91 -0
  19. package/skills/wix-docs/references/EXTRACTING.md +5 -3
  20. package/skills/wix-headless/references/DOC_DISCOVERY.md +9 -3
  21. package/skills/wix-manage/SKILL.md +7 -1
  22. package/skills/wix-manage/references/seo/manage-content-plan.md +247 -0
  23. package/skills/wix-manage/references/seo/manage-seo-tags.md +59 -6
  24. package/skills/wix-manage/references/stores/change-store-currency.md +10 -0
  25. package/skills/wix-manage/references/stores/update-product-with-options.md +9 -0
  26. package/skills/wix-vibe-headless/SKILL.md +17 -18
  27. package/skills/wix-vibe-headless/install/pin-agents-md.cjs +1 -1
  28. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +118 -0
  29. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +55 -62
  30. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +31 -29
  31. package/skills/wix-vibe-headless/platforms/base44.md +8 -5
  32. package/skills/wix-vibe-headless/platforms/generic.md +13 -12
  33. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +4 -13
  34. package/skills/wix-vibe-headless/references/blog/seed/SEED.md +4 -8
  35. package/skills/wix-vibe-headless/references/blog/seed/{seed-blog.js → seed-blog.cjs} +2 -3
  36. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +5 -14
  37. package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +5 -9
  38. package/skills/wix-vibe-headless/references/bookings/seed/{seed-bookings.js → seed-bookings.cjs} +2 -2
  39. package/skills/wix-vibe-headless/references/bookings/seed/{seed-rentals.js → seed-rentals.cjs} +3 -3
  40. package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +1 -1
  41. package/skills/wix-vibe-headless/references/cms/seed/SEED.md +2 -2
  42. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +4 -13
  43. package/skills/wix-vibe-headless/references/events/seed/SEED.md +3 -7
  44. package/skills/wix-vibe-headless/references/events/seed/{seed-events.js → seed-events.cjs} +2 -2
  45. package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +2 -1
  46. package/skills/wix-vibe-headless/references/forms/seed/SEED.md +1 -1
  47. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +4 -13
  48. package/skills/wix-vibe-headless/references/members/seed/SEED.md +2 -3
  49. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +4 -13
  50. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectMedia.jsx +1 -1
  51. package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +3 -7
  52. package/skills/wix-vibe-headless/references/portfolio/seed/{seed-portfolio.js → seed-portfolio.cjs} +2 -2
  53. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +5 -14
  54. package/skills/wix-vibe-headless/references/pricing-plans/app/components/OrderCard.jsx +1 -1
  55. package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +3 -7
  56. package/skills/wix-vibe-headless/references/pricing-plans/seed/{seed-pricing-plans.js → seed-pricing-plans.cjs} +2 -2
  57. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +5 -15
  58. package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +3 -7
  59. package/skills/wix-vibe-headless/references/restaurants/seed/{seed-restaurants.js → seed-restaurants.cjs} +2 -2
  60. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +359 -327
  61. package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +1 -0
  62. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +2 -2
  63. package/skills/wix-vibe-headless/references/storefront/app/hooks/useShop.js +63 -78
  64. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +59 -43
  65. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +46 -10
  66. package/skills/wix-vibe-headless/references/storefront/seed/{seed-store.js → seed-store.cjs} +163 -25
  67. package/skills/wix-vibe-headless/references/storefront/app/pages/Shop.jsx +0 -75
@@ -40,6 +40,7 @@ export function CartProvider({ children }) {
40
40
  return await fn();
41
41
  } catch (e) {
42
42
  setError(e?.message || "Something went wrong. Please try again.");
43
+ setIsOpen(true); // Surface refusals even when adding from a card or PDP with the drawer closed.
43
44
  if (reread) await refreshCart().catch(() => {});
44
45
  return null;
45
46
  } finally {
@@ -95,10 +95,10 @@ export function useProductDetail(slug) {
95
95
  setAdding(true);
96
96
  try {
97
97
  // addToCart reports its own failures through the cart context (it opens the drawer with the
98
- // reason), so nothing is swallowed by leaving this unguarded beyond resetting the button.
98
+ // reason). Preserve its undefined-on-success / null-on-failure result for chained actions.
99
99
  // Coerce the quantity: the shipped stepper lets the field sit empty mid-edit, and a keyboard
100
100
  // submit from that state would otherwise post "" as the quantity.
101
- await addToCart(product.id, variant?.id, Math.max(1, Number(quantity) || 1), {
101
+ return await addToCart(product.id, variant?.id, Math.max(1, Number(quantity) || 1), {
102
102
  modifierChoices: Object.keys(modifierChoices).length ? modifierChoices : undefined,
103
103
  customTextFields: Object.keys(customTextFields).length ? customTextFields : undefined,
104
104
  });
@@ -1,98 +1,83 @@
1
- // useShop — all catalog-listing logic, no markup: load the category menu, load a page of products for
2
- // the active category, append the next page on demand, sort client-side, and surface a failure instead
3
- // of leaving the page on a spinner. The Shop page only renders what this returns.
4
- //
5
- // Load-bearing:
6
- // • paging is CURSOR-based. Keep the cursor from the previous response and pass it back; a cursor
7
- // follow-up reuses the original filter, so the category is fixed for the life of that cursor.
8
- // Changing category starts a fresh query, never a cursor continuation.
9
- // • queryCategories returns the auto-created "all-products" system category alongside real ones, and
10
- // `visible` does not flag it — filter it by slug or the menu shows a duplicate of everything.
11
- // • sorting is done here on the loaded page, not by the API: with cursor paging the server would
12
- // re-sort per page, so a sort applied across pages already fetched is the honest option. Say so in
13
- // the UI if the catalog is larger than one page.
14
- import { useCallback, useEffect, useMemo, useState } from "react";
15
- import { queryProducts, queryProductsByCategory, queryCategories } from "@/rest/wix-store-catalog";
1
+ // Catalog queries run on Wix before pagination. A changed selection starts a fresh cursor chain.
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { searchProducts, queryCategories, CATALOG_SORTS } from "@/rest/wix-store-catalog";
16
4
 
5
+ export const SORTS = CATALOG_SORTS;
17
6
  const SYSTEM_CATEGORY_SLUG = "all-products";
18
- export const SORTS = {
19
- featured: { label: "Featured" },
20
- priceAsc: { label: "Price: low to high" },
21
- priceHigh: { label: "Price: high to low" },
22
- name: { label: "Name: A–Z" },
23
- };
24
-
25
- // Prices arrive formatted for display ("$12.00"), so read the numeric value for sorting from the
26
- // amount when it's there and fall back to digits in the formatted string.
27
- function priceOf(product) {
28
- const min = product?.actualPriceRange?.minValue;
29
- const n = Number(min?.amount ?? String(min?.formattedAmount || "").replace(/[^\d.]/g, ""));
30
- return Number.isFinite(n) ? n : 0;
31
- }
32
7
 
33
8
  export function useShop({ pageSize = 24 } = {}) {
34
9
  const [categories, setCategories] = useState([]);
35
- const [activeCategory, setActiveCategory] = useState(null); // null = everything
36
- const [products, setProducts] = useState(null); // null = first load
37
- const [cursor, setCursor] = useState(null);
38
- const [loadingMore, setLoadingMore] = useState(false);
39
- const [error, setError] = useState(null);
10
+ const [activeCategory, setActiveCategory] = useState(null);
40
11
  const [sort, setSort] = useState("featured");
12
+ const [filters, setFilters] = useState({});
13
+ const [attempt, setAttempt] = useState(0);
14
+ const [page, setPage] = useState({ key: null, products: null, cursor: null, error: null, loadingMore: false });
15
+ const generation = useRef(0);
16
+ const pendingMore = useRef(null);
17
+ const key = JSON.stringify([pageSize, activeCategory?.id ?? null, sort, filters.minPrice ?? null,
18
+ filters.maxPrice ?? null, !!filters.inStockOnly, filters.search ?? "", attempt]);
19
+ const currentKey = useRef(key);
20
+ currentKey.current = key;
41
21
 
42
22
  useEffect(() => {
23
+ let alive = true;
43
24
  queryCategories({ limit: 100 })
44
- .then(({ categories: list }) =>
45
- setCategories((list || []).filter((c) => c.visible !== false && c.slug !== SYSTEM_CATEGORY_SLUG)))
46
- .catch(() => setCategories([])); // a store without categories is normal, not an error
25
+ .then(({ categories: list }) => {
26
+ if (alive) setCategories((list || []).filter(c => c.visible !== false && c.slug !== SYSTEM_CATEGORY_SLUG));
27
+ })
28
+ .catch(() => { if (alive) setCategories([]); });
29
+ return () => { alive = false; };
47
30
  }, []);
48
31
 
49
- const load = useCallback(async (categoryId) => {
50
- setProducts(null);
51
- setCursor(null);
52
- setError(null);
53
- try {
54
- const res = categoryId
55
- ? await queryProductsByCategory(categoryId, { limit: pageSize })
56
- : await queryProducts({ limit: pageSize });
57
- setProducts(res.products);
58
- setCursor(res.nextCursor);
59
- } catch (e) {
60
- setError(e?.message || "Couldn't load products.");
61
- setProducts([]);
62
- }
63
- }, [pageSize]);
64
-
65
- useEffect(() => { load(activeCategory?.id ?? null); }, [activeCategory, load]);
32
+ useEffect(() => {
33
+ const id = ++generation.current;
34
+ let alive = true;
35
+ pendingMore.current = null;
36
+ setPage({ key, products: null, cursor: null, error: null, loadingMore: false });
37
+ const [limit, categoryId, selectedSort, minPrice, maxPrice, inStockOnly, search] = JSON.parse(key);
38
+ searchProducts({ limit, categoryId, sort: selectedSort, minPrice, maxPrice, inStockOnly, search })
39
+ .then(res => {
40
+ if (alive && generation.current === id && currentKey.current === key) {
41
+ setPage({ key, products: res.products, cursor: res.nextCursor, error: null, loadingMore: false });
42
+ }
43
+ })
44
+ .catch(e => {
45
+ if (alive && generation.current === id && currentKey.current === key) {
46
+ setPage({ key, products: [], cursor: null, error: e?.message || "Couldn't load products.", loadingMore: false });
47
+ }
48
+ });
49
+ return () => { alive = false; generation.current++; };
50
+ }, [key]);
66
51
 
67
- const loadMore = async () => {
68
- if (!cursor || loadingMore) return;
69
- setLoadingMore(true);
52
+ const loadMore = useCallback(async () => {
53
+ if (page.key !== key || !page.cursor || pendingMore.current) return;
54
+ const request = { generation: generation.current, key };
55
+ pendingMore.current = request; // blocks repeated clicks before React rerenders
56
+ const isCurrent = () => generation.current === request.generation && currentKey.current === key;
57
+ setPage(p => ({ ...p, loadingMore: true, error: null }));
70
58
  try {
71
- const res = activeCategory
72
- ? await queryProductsByCategory(activeCategory.id, { limit: pageSize, cursor })
73
- : await queryProducts({ limit: pageSize, cursor });
74
- setProducts((prev) => [...(prev || []), ...res.products]);
75
- setCursor(res.nextCursor);
59
+ const res = await searchProducts({ limit: pageSize, cursor: page.cursor });
60
+ if (isCurrent()) setPage(p => {
61
+ const seen = new Set((p.products || []).map(product => product.id));
62
+ return { ...p, products: [...(p.products || []), ...res.products.filter(product => {
63
+ if (seen.has(product.id)) return false;
64
+ seen.add(product.id); return true;
65
+ })], cursor: res.nextCursor };
66
+ });
76
67
  } catch (e) {
77
- setError(e?.message || "Couldn't load more products.");
68
+ if (isCurrent()) setPage(p => ({ ...p, error: e?.message || "Couldn't load more products." }));
78
69
  } finally {
79
- setLoadingMore(false);
70
+ if (pendingMore.current === request) pendingMore.current = null;
71
+ if (isCurrent()) setPage(p => ({ ...p, loadingMore: false }));
80
72
  }
81
- };
82
-
83
- const sorted = useMemo(() => {
84
- if (!products) return null;
85
- const copy = [...products];
86
- if (sort === "priceAsc") copy.sort((a, b) => priceOf(a) - priceOf(b));
87
- else if (sort === "priceHigh") copy.sort((a, b) => priceOf(b) - priceOf(a));
88
- else if (sort === "name") copy.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
89
- return copy;
90
- }, [products, sort]);
73
+ }, [key, page.key, page.cursor, pageSize]);
91
74
 
75
+ const retry = useCallback(() => setAttempt(n => n + 1), []);
76
+ const current = page.key === key;
92
77
  return {
93
- categories, activeCategory, setActiveCategory,
94
- products: sorted, loading: products === null, error,
95
- hasMore: !!cursor, loadMore, loadingMore,
96
- sort, setSort, retry: () => load(activeCategory?.id ?? null),
78
+ categories, activeCategory, setActiveCategory, sort, setSort, filters, setFilters,
79
+ products: current ? page.products : null, loading: !current || page.products === null,
80
+ error: current ? page.error : null, retry,
81
+ hasMore: current && !!page.cursor, loadMore, loadingMore: current && page.loadingMore,
97
82
  };
98
83
  }
@@ -55,26 +55,67 @@ import { wixApiRequest } from "./wix-client.js";
55
55
  * Full model: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories
56
56
  */
57
57
 
58
+ // Search Products supports server-side sort/filter before cursor paging:
59
+ // https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
60
+ export const CATALOG_SORTS = {
61
+ featured: { label: "Default order" }, // legacy key; no promise of merchant-curated ranking
62
+ priceAsc: { label: "Price: low to high" },
63
+ priceHigh: { label: "Price: high to low" },
64
+ name: { label: "Name: A–Z" },
65
+ newest: { label: "Newest" },
66
+ };
67
+ const SORT_FIELDS = {
68
+ priceAsc: [{ fieldName: "actualPriceRange.minValue.amount", order: "ASC" }, { fieldName: "name", order: "ASC" }],
69
+ priceHigh: [{ fieldName: "actualPriceRange.minValue.amount", order: "DESC" }, { fieldName: "name", order: "ASC" }],
70
+ name: [{ fieldName: "name", order: "ASC" }],
71
+ newest: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "name", order: "ASC" }],
72
+ };
73
+
74
+ function priceBound(value, name) {
75
+ if (value == null || value === "") return undefined;
76
+ if (!["number", "string"].includes(typeof value) || !String(value).trim() || !Number.isFinite(Number(value)) || Number(value) < 0) {
77
+ throw new Error(`${name} must be a non-negative number.`);
78
+ }
79
+ return Number(value);
80
+ }
81
+
58
82
  /**
59
- * Query visible products (one page). Pass nextCursor back as cursor to load the next page.
60
- * @param {{ limit?: number, cursor?: string }} [options]
83
+ * Search visible catalog products, sorted/filtered across the whole catalog.
84
+ * Price bounds and ordering use the product's minimum actual variant price in site currency.
85
+ * A cursor continues the original query; start without one when any selection changes.
86
+ * @param {{ limit?: number, cursor?: string, categoryId?: string, sort?: string,
87
+ * minPrice?: number|string, maxPrice?: number|string, inStockOnly?: boolean, search?: string }} [options]
61
88
  * @returns {Promise<{ products: object[], nextCursor: string|null }>}
62
89
  */
63
- export async function queryProducts({ limit = 100, cursor } = {}) {
64
- const res = await wixApiRequest("/stores/v3/products/query", {
90
+ export async function searchProducts({ limit = 100, cursor, categoryId, sort = "featured", minPrice, maxPrice, inStockOnly = false, search = "" } = {}) {
91
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("limit must be between 1 and 100.");
92
+ let query = { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } };
93
+ if (!cursor) {
94
+ if (!Object.hasOwn(CATALOG_SORTS, sort)) throw new Error("Unsupported catalog sort.");
95
+ const min = priceBound(minPrice, "minPrice"), max = priceBound(maxPrice, "maxPrice");
96
+ if (min !== undefined && max !== undefined && min > max) throw new Error("minPrice must not exceed maxPrice.");
97
+ if (typeof search !== "string" || search.trim().length > 100) throw new Error("Search must be at most 100 characters.");
98
+ const conditions = [{ visible: true }];
99
+ if (categoryId) conditions.push({ "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } });
100
+ // Search rejects two operators in the same field object. Join separate bounds with $and.
101
+ if (min !== undefined) conditions.push({ "actualPriceRange.minValue.amount": { $gte: String(min) } });
102
+ if (max !== undefined) conditions.push({ "actualPriceRange.minValue.amount": { $lte: String(max) } });
103
+ if (inStockOnly) conditions.push({ "inventory.availabilityStatus": { $eq: "IN_STOCK" } });
104
+ query = { ...query, filter: { $and: conditions },
105
+ ...(SORT_FIELDS[sort] ? { sort: SORT_FIELDS[sort] } : {}),
106
+ ...(search.trim() ? { search: { expression: search.trim(), fields: ["name"] } } : {}),
107
+ };
108
+ }
109
+ const res = await wixApiRequest("/stores/v3/products/search", {
65
110
  method: "POST",
66
- body: {
67
- fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"],
68
- query: {
69
- ...(cursor ? {} : { filter: { visible: true } }),
70
- cursorPaging: cursor ? { limit, cursor } : { limit },
71
- },
72
- },
111
+ body: { fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"], search: query },
73
112
  });
74
- return {
75
- products: res?.products ?? [],
76
- nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
77
- };
113
+ return { products: res?.products ?? [], nextCursor: res?.pagingMetadata?.cursors?.next ?? null };
114
+ }
115
+
116
+ /** Visible products; accepts the same sort/filter options as searchProducts. */
117
+ export function queryProducts(options = {}) {
118
+ return searchProducts(options);
78
119
  }
79
120
 
80
121
  /**
@@ -102,34 +143,9 @@ export async function getProductBySlug(slug) {
102
143
  return res?.product ?? null;
103
144
  }
104
145
 
105
- /**
106
- * Query visible products belonging to a category (one page).
107
- * @param {string} categoryId Category GUID from queryCategories / getCategoryBySlug.
108
- * @param {{ limit?: number, cursor?: string }} [options]
109
- * @returns {Promise<{ products: object[], nextCursor: string|null }>}
110
- */
111
- export async function queryProductsByCategory(categoryId, { limit = 100, cursor } = {}) {
112
- const res = await wixApiRequest("/stores/v3/products/search", {
113
- method: "POST",
114
- body: {
115
- fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"],
116
- search: {
117
- ...(cursor
118
- ? { cursorPaging: { limit, cursor } }
119
- : {
120
- cursorPaging: { limit },
121
- filter: {
122
- visible: true,
123
- "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] },
124
- },
125
- }),
126
- },
127
- },
128
- });
129
- return {
130
- products: res?.products ?? [],
131
- nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
132
- };
146
+ /** Category products; accepts the same sort/filter options as searchProducts. */
147
+ export function queryProductsByCategory(categoryId, options = {}) {
148
+ return searchProducts({ ...options, categoryId });
133
149
  }
134
150
 
135
151
  /**
@@ -1,8 +1,10 @@
1
1
  # Storefront — seeding
2
2
 
3
- Seed a Wix Stores catalog by **calling `seed-store.js`** — don't hand-write the REST calls. It's
3
+ Seed a Wix Stores catalog by **calling `seed-store.cjs`** — don't hand-write the REST calls. It's
4
4
  a build-time module (run via `exec_tool`, not shipped in the app) that abstracts every Wix Stores
5
5
  seed operation. Load it and call **`setupStore` — the one-call path** — with plain data.
6
+ Pass only the connector token and catalog data. The module handles site configuration internally;
7
+ do not read the config or supply site/client IDs.
6
8
 
7
9
  **Match each product's type to what the buyer receives** (the example shows both). *Access* — a
8
10
  membership, or an online course/program the buyer enrolls in — isn't a store product at all; that's
@@ -11,12 +13,8 @@ the `pricing-plans` vertical, not here.
11
13
  ```js
12
14
  // build-time exec_tool
13
15
  const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
14
- const fs = require("fs");
15
- // exec_tool's require can return EMPTY exports for these build-time modules — load the file itself:
16
- const seed = (() => { const m = { exports: {} };
17
- new Function("module", "exports", "require", fs.readFileSync("/app/.agents/skills/wix-vibe-headless/references/storefront/seed/seed-store.js", "utf8"))(m, m.exports, require);
18
- return m.exports; })();
19
- const ctx = { token: accessToken, siteId: WIX_METASITE_ID };
16
+ const seed = require(require("path").resolve(".agents/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs"));
17
+ const ctx = { token: accessToken };
20
18
 
21
19
  // ONE call: install (+ wait for V3) → create products → categories → attach images, ids kept
22
20
  // in memory (no hand-threading). Categories map name -> product NAMES. Pass an imageUrl per product
@@ -25,6 +23,7 @@ const ctx = { token: accessToken, siteId: WIX_METASITE_ID };
25
23
  // result — not a still-generating /__generating__/<id>.png placeholder (Wix can't fetch that).
26
24
  // generate_image runs in the background while you build, so the urls are ready by seed time.
27
25
  const result = await seed.setupStore(ctx, {
26
+ currency: "EUR", // Pass only if the user asked for a currency or it's obvious for the store; else omit this line.
28
27
  products: [
29
28
  // physical — a shipped item: carries `quantity` (the default type)
30
29
  { name: "The Glam Rocker", description: "Sequin-studded velvet legend…", price: 49.99, quantity: 12, imageUrl: imageUrls[0] },
@@ -38,12 +37,40 @@ const result = await seed.setupStore(ctx, {
38
37
  ],
39
38
  categories: { "Legends": ["The Glam Rocker"], "Rising Stars": [] }, // omit if the brief names none
40
39
  });
41
- // result: { products:[{id,slug,revision,name}], categories:[{id,name}], imagesAttached }
40
+ // result: { products:[{id,slug,revision,name}], categories:[{id,name}], imagesAttached,
41
+ // currency: { requested, actual, status, warnings } }
42
42
  ```
43
43
 
44
+ The optional `currency` sets the site's payment currency before product creation. Pass it only when
45
+ the user explicitly asked for a currency, or when it's obvious for the store — otherwise omit it. Do
46
+ not infer a currency from the builder's country/region or the brief's language; when in doubt, leave
47
+ it out and the current site currency is preserved. Product prices are numbers in that currency;
48
+ changing currency does not convert existing amounts.
49
+ Currency update or verification failures do not stop seeding: inspect `result.currency.status`
50
+ and `warnings`, report the unresolved setting, and use the connector skill to resolve it. An
51
+ unknown actual currency is `null`; do not replace currency symbols to simulate a successful update.
52
+ Currency changes may take time to appear in existing product responses, even after carts and
53
+ checkout use the new currency. If `result.currency.status` confirms the update succeeded, continue
54
+ without waiting for or verifying the change in product responses or the preview.
55
+
56
+
44
57
  **Seeding is additive — never delete or overwrite existing content.** Don't clean up, don't remove
45
58
  "sample" data, don't reset. Just add.
46
59
 
60
+ ## Stock
61
+
62
+ ```js
63
+ { name: "Limited Print", price: 25, quantity: 12 } // count stock: integer 0–99999
64
+ { name: "Made-to-order Print", price: 25, inStock: true } // available without a quantity counter
65
+ { name: "Unavailable Print", price: 25, inStock: false } // unavailable without a quantity counter
66
+ ```
67
+
68
+ Supply `quantity` **or** `inStock`, never both. Use `inStock: true` for unlimited stock, not a
69
+ large invented quantity. All expanded variants inherit the same setting. Omitting both defaults
70
+ to quantity 0 for physical products and in-stock for downloads. Stock mode doesn't change the
71
+ product type or remove the downloadable-file requirement.
72
+ [Wix inventory tracking](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/inventory-items-v3/inventory-item-object).
73
+
47
74
  ## How many, and exercising the UI
48
75
 
49
76
  **Default to 3 products** unless the brief asks for a specific catalog — the seed shows the shape,
@@ -63,8 +90,12 @@ options: [
63
90
 
64
91
  - `type: "color"` → `SWATCH_CHOICES` with each choice's `colorCode`, which the PDP draws as a swatch.
65
92
  Any other `type` → text pills. Give every colour choice a `colorCode`.
93
+ Within a batch, reuse the same color code for the same option/choice name across products;
94
+ give different shades distinct names (for example, Forest Green and Light Green).
95
+ - Choice names must be unique within each product option, for both text and color choices.
96
+ Reusing a choice name across different products is fine; color choices must follow the consistency rule above.
66
97
  - Variants are expanded for you: the full cross-product of the options, each carrying the product's
67
- `price`, `compareAtPrice` and `quantity`. Two options with 2 and 3 choices means 6 variants — keep
98
+ `price`, `compareAtPrice` and stock setting (`quantity` or `inStock`). Two options with 2 and 3 choices means 6 variants — keep
68
99
  option counts small.
69
100
  - `compareAtPrice` (> `price`) is the "was" price: strikethrough on the PDP and a `−N%` badge on the
70
101
  tile, computed from the two amounts. It works with or without options.
@@ -92,6 +123,11 @@ Two things this module does **not** seed, so don't try:
92
123
  follows automatically — `choiceImage()` reads it back at `media.items[].mediaId`.
93
124
 
94
125
  ## Escape hatch — individual functions
126
+
127
+ If seeding reports a partial failure, keep the reported successful product IDs and correct the
128
+ failed inputs. Do not rerun the whole seed or wrap it in a retry loop: creation may already have
129
+ succeeded for some products. Missing results mean unknown creation status; inspect before creating again.
130
+
95
131
  Reach for the functions below only when the one-call `setupStore` doesn't fit (partial re-seed, custom
96
132
  ordering, mid-flow checks). `setupStore` is built from them, in this order:
97
133
 
@@ -118,7 +154,7 @@ await seed.attachProductImages(ctx, products.map((p, i) => ({ id: p.id, url: ima
118
154
 
119
155
  ## Reference
120
156
  If a call returns a shape you didn't expect, or you need an operation this module doesn't cover,
121
- use the **`wix-docs`** skill to search + read the live Wix API reference — never guess. The
157
+ use the documentation skill available in your environment to search + read the live Wix API reference — never guess. The
122
158
  authoritative source recipe is `wix-headless/references/inline-recipes/setup-online-store.md`.
123
159
 
124
160
  Read a method's page before writing its call: it carries the exact body shape, the required