@wix/agent-skills 1.19.1 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +1 -1
  3. package/skills/wix-app/references/SERVICE_PLUGIN.md +14 -1
  4. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +7 -2
  5. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +53 -28
  6. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +17 -4
  7. package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +71 -31
  8. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +3 -4
  9. package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
  10. package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
  11. package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
  12. package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
  13. package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
  14. package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
  15. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
  16. package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
  17. package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
  18. package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
  19. package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
  20. package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
  21. package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
  22. package/skills/wix-base44-connector/SKILL.md +46 -24
  23. package/skills/wix-headless/SKILL.md +1 -0
  24. package/skills/wix-headless/references/CAPABILITIES.md +7 -2
  25. package/skills/wix-headless/references/SDK_HANDOFF.md +2 -1
  26. package/skills/wix-headless/references/inline-recipes/experience-store.md +49 -0
  27. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +431 -115
  28. package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +1 -1
  29. package/skills/wix-headless/references/managed/CREATE.md +5 -0
  30. package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
  31. package/skills/wix-headless-fast/install/fast-path.mjs +3 -1
  32. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +13 -0
  33. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +227 -56
  34. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
  35. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
  36. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +165 -0
  37. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +48 -15
  38. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +129 -11
  39. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +36 -6
  40. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +190 -24
  41. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
  42. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
  43. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +5 -1
  44. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +59 -6
  45. package/skills/wix-manage/SKILL.md +8 -5
  46. package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +4 -1
  47. package/skills/wix-manage/references/bookings/create-appointment-service.md +3 -1
  48. package/skills/wix-manage/references/bookings/create-class-service.md +3 -1
  49. package/skills/wix-manage/references/bookings/create-course-service.md +4 -2
  50. package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +68 -3
  51. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-seasonal-promotion.md +1 -1
  52. package/skills/wix-manage/references/events/create-wix-event.md +46 -0
  53. package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
  54. package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
  55. package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +108 -14
  56. package/skills/wix-manage/references/seo/manage-content-plan.md +1 -1
  57. package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
  58. package/skills/wix-manage/references/sites/upload-static-site.md +264 -34
  59. package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
  60. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
  61. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
  62. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
  63. package/skills/wix-vibe-headless/platforms/base44.md +19 -4
  64. package/skills/wix-vibe-headless/platforms/generic.md +6 -0
  65. package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
  66. package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
  67. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
  68. package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
  69. package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
  70. package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
  71. package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
  72. package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
  73. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
  74. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
  75. package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
  76. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
  77. package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
  78. package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
  79. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
  80. package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
  81. package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
  82. package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
  83. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
  84. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
  85. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
  86. package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
  87. package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
  88. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
  89. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
  90. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
  91. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
  92. package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
  93. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +110 -27
  94. package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +32 -14
  95. package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +14 -3
  96. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +47 -35
  97. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +77 -30
  98. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +31 -2
  99. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +31 -6
@@ -10,14 +10,24 @@ export interface ProductSummary {
10
10
  id: string;
11
11
  slug: string;
12
12
  name: string;
13
- /** Lowest price, formatted with currency symbol (e.g. "€34.99"). */
13
+ /**
14
+ * The price the buyer pays for the cheapest variant, formatted (an automatic discount already
15
+ * applied). When `price !== maxPrice` the product is a RANGE — render "price – maxPrice".
16
+ */
14
17
  price: string;
15
- /** Highest price, formatted — differs from `price` when variants are priced differently. */
18
+ /** Highest variant price, formatted — differs from `price` when variants are priced differently. */
16
19
  maxPrice: string;
17
- /** Strikethrough "was" price, formatted; null when not on sale. */
20
+ /**
21
+ * Struck "was" price, formatted; null when not on sale — and ALWAYS null for a range (a lone
22
+ * struck minimum beside a range claims a saving that may not apply to the variant picked).
23
+ */
18
24
  compareAtPrice: string | null;
19
- /** Merchant-set badge ("New", "Best Seller"); null when none. */
25
+ /** The primary merchant ribbon ("New", "Best Seller"); null when none. Same as ribbons[0]. */
20
26
  ribbon: string | null;
27
+ /** EVERY merchant ribbon, primary first ("New", "Sale") — render all of them, one shared style. */
28
+ ribbons: string[];
29
+ /** The cheapest variant's id — what a direct add sends for a product with no options; null when unknown. */
30
+ minPriceVariantId: string | null;
21
31
  availability: Availability;
22
32
  /** OUT_OF_STOCK but pre-orderable — label "Pre-order", not "Sold out". */
23
33
  preorder: boolean;
@@ -27,6 +37,11 @@ export interface ProductSummary {
27
37
  hoverImageUrl: string;
28
38
  /** e.g. "2 colors · 3 sizes"; "" for a single-variant product. */
29
39
  optionsSummary: string;
40
+ /**
41
+ * Hex colors of a color option's visible choices, catalog order — render as small dots on the
42
+ * tile (a preview, not a picker: selection happens in QuickAdd or on the PDP). [] when none.
43
+ */
44
+ swatches: string[];
30
45
  /** True when the product can be added to the cart with no choices (single variant, in stock). */
31
46
  quickAddable: boolean;
32
47
  }
@@ -60,15 +75,21 @@ export interface ProductVariant {
60
75
  variantId: string;
61
76
  /** The option selections this variant answers to: optionName -> choiceName. */
62
77
  choices: Record<string, string>;
78
+ /** The price the buyer pays, formatted — an automatic discount beats the regular price. */
63
79
  price: string;
80
+ /** Struck "was" price, formatted; null unless it is real and higher than `price`. */
64
81
  compareAtPrice: string | null;
65
82
  inStock: boolean;
83
+ /** Out of stock but pre-orderable — still buyable (the add carries preOrderRequested). */
84
+ preorderEnabled: boolean;
66
85
  }
67
86
 
68
87
  /** A product as the detail page needs it. */
69
88
  export interface ProductDetail extends ProductSummary {
70
89
  /** Product description as an HTML string — render with innerHTML, not as text. */
71
90
  descriptionHtml: string;
91
+ /** Merchant info sections (materials, shipping, care…) — title + HTML; render as sections or accordions. */
92
+ infoSections: { title: string; html: string }[];
72
93
  /** Every gallery image as a resolved https URL, main image first, de-duplicated. */
73
94
  gallery: string[];
74
95
  options: ProductOption[];
@@ -80,6 +101,36 @@ export interface Category {
80
101
  id: string;
81
102
  slug: string;
82
103
  name: string;
104
+ /** Plain-text description when the merchant wrote one; "" otherwise. */
105
+ description: string;
106
+ }
107
+
108
+ export interface FacetChoice {
109
+ /** The option choice id — what searchCatalog({ choiceIds }) filters on. */
110
+ id: string;
111
+ name: string;
112
+ /** Hex color for a swatch facet; null for a text facet. */
113
+ colorCode: string | null;
114
+ }
115
+
116
+ /** One filterable option across the catalog (or a category): "Color" with its choices. */
117
+ export interface Facet {
118
+ name: string;
119
+ isColor: boolean;
120
+ choices: FacetChoice[];
121
+ }
122
+
123
+ /** The catalog's (or category's) lowest and highest product price, as numbers in site currency — the slider's bounds. */
124
+ export interface PriceRange {
125
+ min: number;
126
+ max: number;
127
+ currency: string;
128
+ }
129
+
130
+ /** What the filter panel needs beyond the product page: the facets and the price bounds of the scope. */
131
+ export interface FacetData {
132
+ facets: Facet[];
133
+ priceRange: PriceRange | null;
83
134
  }
84
135
 
85
136
  export interface CartLine {
@@ -97,6 +148,11 @@ export interface CartLine {
97
148
  descriptionLines: string[];
98
149
  /** Not IN_STOCK → the line can't be checked out as-is. */
99
150
  status: string;
151
+ /**
152
+ * The recurring plan's terms for a subscription line — "Monthly plan · every month · 12 payments";
153
+ * "" for a one-time purchase. A subscription line must read as one in the cart.
154
+ */
155
+ subscription: string;
100
156
  }
101
157
 
102
158
  export interface Cart {
@@ -105,5 +161,7 @@ export interface Cart {
105
161
  itemCount: number;
106
162
  /** Formatted subtotal (after discounts) — from the cart estimate, not hand-summed. */
107
163
  subtotal: string;
164
+ /** Formatted CART-level discount (a coupon or cart rule) — "" when none; item discounts are already in subtotal. */
165
+ discount: string;
108
166
  currency: string;
109
167
  }
@@ -1,12 +1,18 @@
1
1
  ---
2
2
  // The site chrome for every storefront page — THIS file (header/footer/nav) plus the @theme
3
3
  // token block in src/styles/global.css are what you brand. Keep:
4
- // - the <slot name="seo-tags" /> in <head> (the product page's owner-editable SEO lands there)
4
+ // - the <slot name="seo-tags" /> in <head> (the product page's owner-editable SEO lands there,
5
+ // including the <title> — which is why ours is printed only when the slot is empty)
5
6
  // - the global.css import (Tailwind + the design tokens every shipped component reads)
6
7
  // - one <CartButton> in the header and one <CartDrawer> per page (both client:only —
7
8
  // they read browser cart state and must not server-render)
9
+ // - category links in the nav (or your own menu/footer) — every category page reachable by
10
+ // a real link (`/category/<slug>`), from the live tree, so a category the owner adds later
11
+ // shows up with no code change
8
12
  import CartButton from "../components/storefront/CartButton";
9
13
  import CartDrawer from "../components/storefront/CartDrawer";
14
+ import { fetchCategories } from "../wix/storefront/catalog";
15
+ import type { Category } from "../wix/storefront/types";
10
16
  import "../styles/global.css";
11
17
 
12
18
  interface Props {
@@ -14,6 +20,15 @@ interface Props {
14
20
  description?: string;
15
21
  }
16
22
  const { title, description } = Astro.props;
23
+
24
+ // Non-fatal: the chrome renders without the links if the read fails. Six in the header keeps a
25
+ // large tree from crowding it — put the rest in a menu or the footer, but keep them linked.
26
+ let navCategories: Category[] = [];
27
+ try {
28
+ navCategories = (await fetchCategories()).slice(0, 6);
29
+ } catch {
30
+ /* the shop link still reaches everything */
31
+ }
17
32
  ---
18
33
 
19
34
  <!doctype html>
@@ -21,7 +36,8 @@ const { title, description } = Astro.props;
21
36
  <head>
22
37
  <meta charset="UTF-8" />
23
38
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
24
- <title>{title}</title>
39
+ {/* The product page's <SEO.Tags> renders the <title>; print ours only when no page filled the slot — two titles otherwise. */}
40
+ {!Astro.slots.has("seo-tags") && <title>{title}</title>}
25
41
  {description && <meta name="description" content={description} />}
26
42
  <slot name="seo-tags" />
27
43
  </head>
@@ -31,6 +47,9 @@ const { title, description } = Astro.props;
31
47
  <a href="/" class="text-base font-bold tracking-tight text-foreground no-underline">Brand Name</a>
32
48
  <nav class="flex items-center gap-6 text-sm">
33
49
  <a href="/shop" class="text-foreground no-underline transition-colors hover:text-muted-foreground">Shop</a>
50
+ {navCategories.map((c) => (
51
+ <a href={`/category/${c.slug}`} class="hidden text-foreground no-underline transition-colors hover:text-muted-foreground md:inline">{c.name}</a>
52
+ ))}
34
53
  <CartButton client:only="react" />
35
54
  </nav>
36
55
  </div>
@@ -75,7 +75,11 @@ the shipped UI**: give at least one product a color option and put one product o
75
75
  truthfully to the business (a ceramics studio has glaze colors; a bakery doesn't).
76
76
 
77
77
  **Seeding is additive — never delete or overwrite existing content.** No cleanup, no removing
78
- "sample" data, no resets. If a cleanup genuinely seems needed, ask the user first.
78
+ "sample" data, no resets. If a cleanup genuinely seems needed, ask the user first. The one
79
+ exception is built in: the fast path passes `--fresh-site` because it created the site seconds
80
+ earlier, and the seed then deletes the Stores install's sample products before creating yours
81
+ (`samplesDeleted` in the result). Never pass that flag by hand against a site that existed before.
82
+ Categories are idempotent by name too — a re-run reuses "Donuts" instead of creating a second one.
79
83
 
80
84
  **A bulk create can partially succeed.** The result carries `failures: [{ name, error }]` next
81
85
  to `products` — read it. A non-empty `failures` means those products are genuinely absent, not
@@ -1,7 +1,7 @@
1
1
  // Storefront seed — a BUILD-TIME script, never shipped in the app. Run it from the project
2
2
  // root (where wix.config.json lives) with a plan file:
3
3
  //
4
- // node <SKILL_ROOT>/references/storefront/seed/seed-store.mjs plan.json
4
+ // node <SKILL_ROOT>/references/storefront/seed/seed-store.mjs plan.json [--fresh-site]
5
5
  //
6
6
  // It mints its own site token via the Wix CLI (the token never leaves this process), installs
7
7
  // the Wix Stores app if needed, waits for the V3 catalog, bulk-creates products (variants
@@ -331,11 +331,34 @@ async function stockOptionlessProducts(ctx, created) {
331
331
  }
332
332
  }
333
333
 
334
+ // Existing categories by name (for idempotent reruns) — a re-run of the seed must reuse
335
+ // "Donuts", not create a second one. Empty map on any failure (falls back to create).
336
+ export async function queryCategoriesByNames(ctx, names) {
337
+ const out = new Map();
338
+ if (!names.length) return out;
339
+ try {
340
+ const r = await req(ctx, "/categories/v1/categories/query", {
341
+ body: { treeReference: { appNamespace: "@wix/stores", treeKey: null }, query: { cursorPaging: { limit: 100 } } },
342
+ });
343
+ const wanted = new Set(names);
344
+ for (const c of r.categories ?? []) if (wanted.has(c.name) && !out.has(c.name)) out.set(c.name, c.id);
345
+ } catch (e) {
346
+ console.error(`category name pre-check failed (creating everything): ${String(e.message).slice(0, 120)}`);
347
+ }
348
+ return out;
349
+ }
350
+
334
351
  // Categories share the @wix/stores tree revision — concurrent creates 409, so: sequential.
352
+ // Idempotent by name: a name that already exists is reused, never duplicated.
335
353
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/create-category.md
336
354
  export async function createCategories(ctx, names) {
355
+ const existing = await queryCategoriesByNames(ctx, names);
337
356
  const out = [];
338
357
  for (const name of names) {
358
+ if (existing.has(name)) {
359
+ out.push({ id: existing.get(name), name });
360
+ continue;
361
+ }
339
362
  const r = await req(ctx, "/categories/v1/categories", {
340
363
  body: { category: { name, visible: true }, treeReference: { appNamespace: "@wix/stores", treeKey: null } },
341
364
  });
@@ -344,6 +367,24 @@ export async function createCategories(ctx, names) {
344
367
  return out;
345
368
  }
346
369
 
370
+ // The Stores install drops sample products ("I'm a product") into every new catalog. On a site the
371
+ // fast path JUST created nothing else can exist, so they are deleted before seeding — otherwise the
372
+ // storefront ships with the samples between the real products. Only called with --fresh-site; a
373
+ // seed against an existing site never deletes anything (SEED.md: seeding is additive).
374
+ // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/bulk-delete-products.md
375
+ export async function deleteAllProducts(ctx) {
376
+ let deleted = 0;
377
+ for (let pass = 0; pass < 10; pass++) {
378
+ const r = await req(ctx, "/stores/v3/products/query", { body: { query: { cursorPaging: { limit: 100 } } } });
379
+ const ids = (r.products ?? []).map((p) => p.id).filter(Boolean);
380
+ if (!ids.length) break;
381
+ await req(ctx, "/stores/v3/bulk/products/delete", { body: { productIds: ids } });
382
+ deleted += ids.length;
383
+ if (ids.length < 100) break;
384
+ }
385
+ return deleted;
386
+ }
387
+
347
388
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/bulk-add-items-to-category.md
348
389
  export async function addProductsToCategories(ctx, mapping) {
349
390
  for (const [categoryId, productIds] of Object.entries(mapping)) {
@@ -421,12 +462,22 @@ async function setSiteCurrency(ctx, currency) {
421
462
  * threaded in memory. This is the default path — call it once instead of the individual
422
463
  * functions.
423
464
  */
424
- export async function setupStore(ctx, { products = [], categories = {}, currency } = {}) {
465
+ export async function setupStore(ctx, { products = [], categories = {}, currency } = {}, { freshSite = false } = {}) {
425
466
  validateProducts(products);
426
467
  await installStoresApp(ctx);
427
468
  // Before any product exists: a product's price is stored in the site currency at create time,
428
469
  // so switching afterwards leaves the catalog priced in the old one.
429
470
  if (currency) await setSiteCurrency(ctx, currency);
471
+ // A just-created site holds only the install's sample products — clear them (never on an
472
+ // existing site: the flag is the fast path's, set only when it created the site itself).
473
+ let samplesDeleted = 0;
474
+ if (freshSite) {
475
+ try {
476
+ samplesDeleted = await deleteAllProducts(ctx);
477
+ } catch (e) {
478
+ console.error(`sample cleanup failed (seeding on top): ${String(e.message).slice(0, 120)}`);
479
+ }
480
+ }
430
481
 
431
482
  // Idempotent by name: an errored bulk create (429/5xx) may still have applied server-side,
432
483
  // and SKILL.md tells the agent to re-run a failed seed — creating only the names that don't
@@ -478,21 +529,23 @@ export async function setupStore(ctx, { products = [], categories = {}, currency
478
529
  // failures is part of the result, not an exception: a partial seed still leaves a usable
479
530
  // store, and the agent needs the names to report rather than silently shipping a short
480
531
  // catalog. Re-run the seed to retry them — existing names are skipped, not duplicated.
481
- return { products: withNames, categories: cats, imagesAttached, failures };
532
+ return { products: withNames, categories: cats, imagesAttached, samplesDeleted, failures };
482
533
  }
483
534
 
484
535
  // ---- CLI entry ----------------------------------------------------------------------------------
485
536
 
486
537
  const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
487
538
  if (invokedDirectly) {
488
- const planPath = process.argv[2];
539
+ const args = process.argv.slice(2);
540
+ const freshSite = args.includes("--fresh-site");
541
+ const planPath = args.find((a) => !a.startsWith("--"));
489
542
  if (!planPath) {
490
- console.error("usage: node seed-store.mjs <plan.json> (run from the project root)");
543
+ console.error("usage: node seed-store.mjs <plan.json> [--fresh-site] (run from the project root)");
491
544
  process.exit(1);
492
545
  }
493
546
  const plan = JSON.parse(readFileSync(planPath, "utf8"));
494
547
  const ctx = makeCtx();
495
- setupStore(ctx, plan)
548
+ setupStore(ctx, plan, { freshSite })
496
549
  .then((result) => console.log(JSON.stringify(result, null, 2)))
497
550
  .catch((e) => {
498
551
  console.error(e.message);
@@ -48,6 +48,9 @@ Generate an SEO content plan and read its blog post topics, or troubleshoot an e
48
48
  ### [Manage Google Search Console for a Wix Site](references/seo/manage-google-search-console.md)
49
49
  "Connect a Wix site to Google Search Console and drive its setup through the public GSC Connection and Site Readiness APIs: check connection and readiness, start the Google authorization, verify ownership, add the property, submit the sitemap, request indexing, read search performance, run URL inspection, recover a stale connection, or disconnect. The site owner authorizes in their own browser via a single-use connect URL."
50
50
 
51
+ ### [Generate AI SEO Suggestions for a Wix Site](references/seo/manage-seo-suggestions.md)
52
+ "Generate AI-written SEO text for a Wix site through the public Tag Suggestions and Page Optimization APIs: title tag and meta description options for a page, alt text for the images of a page, store product, or blog post (one image or up to 20 per call), corrected heading levels for a page's headings, and a whole-page rewrite of title, description, headings, and body text aimed at the page's focus keyword, returned as before/after pairs. Use it when the user wants to improve, optimize, audit, or write the SEO text of a page or the homepage. Suggestions are returned for review; applying one is a separate SEO tags write."
53
+
51
54
  ---
52
55
 
53
56
  ## Accessibility
@@ -107,7 +110,7 @@ Full CRUD operations for Wix Bookings services using Services API. Covers servic
107
110
  "Answers whether an appointment-based Wix Bookings service currently has bookable availability — the primary question — and diagnoses the cause only when there's no availability or the owner asks why. To diagnose, first rules out service-level blockers the availability endpoint can't see (service hidden, online booking off), then runs DiagnoseAvailability for ordered, machine-readable staff/setup reasons, with a manual fallback for booking-policy and capacity causes. Use when someone asks whether a service has availability, or why a service shows no times / customers can't book it."
108
111
 
109
112
  ### [End-to-End Booking Flow](references/bookings/end-to-end-booking-flow.md)
110
- Complete booking flow from service discovery to payment. Query services, check availability with Time Slots V2, create bookings, and process payment via eCommerce checkout.
113
+ Books and settles appointments, classes and courses with the site owner's credentials — an operator managing bookings, or server-side code booking as the owner. Covers service discovery, availability with Time Slots V2, creating the booking, and settling it by direct confirmation or eCommerce checkout. A visitor booking for themselves needs a visitor token instead; this recipe links that path.
111
114
 
112
115
  ### [External Calendar Integration](references/bookings/external-calendar-integration.md)
113
116
  OAuth-based integration with Google Calendar, Microsoft Outlook, and Apple Calendar. Covers authentication flows, sync configuration, and bidirectional event management.
@@ -251,7 +254,7 @@ Buy a domain through Wix or connect one the user already owns — intent, availa
251
254
  "Creates an event with the Wix Events V3 API — the required request body, ISO-8601 date and time settings, venue/online/TBD location, RSVP vs ticketed registration, guest capacity, short vs rich-text descriptions, ticket tiers and pricing, and recurring series. Covers the exact field shapes and the API's misleading validation messages. Use when the user wants to create an event, set its date, location, description, guest limit or ticket prices, or set up a repeating event."
252
255
 
253
256
  ### [Manage Wix Events — Publishing, Cancelling, Cloning and Counting](references/events/manage-wix-events.md)
254
- "Operates on events that already exist with the Wix Events V3 API — publishing a draft, cancelling, deleting, cloning, updating an event's date or details, and counting events. Use when the user wants to publish or cancel an event, duplicate one, move an event's date, or count their events. Creating an event, its tickets or a recurring series is a separate recipe."
257
+ "Operates on events that already exist with the Wix Events V3 API — finding an event by title, publishing a draft, cancelling, deleting (one or by filter), cloning, updating an event's date or details, and counting events. Use when the user wants to find, publish or cancel an event, duplicate one, move an event's date, delete events in bulk, or count their events. Creating an event, its tickets or a recurring series is a separate recipe."
255
258
 
256
259
  ---
257
260
 
@@ -289,7 +292,7 @@ Creates payment links for unpaid bookings using Payment Links API. Links booking
289
292
  **Routing — Google paid-advertising campaigns for a site (Smart & Performance Max).** All flows require a Google Ads account, created once via the setup recipe. Budgets are in micros (1,000,000 = 1 currency unit). REST base: `https://www.wixapis.com/_serverless/pa-google/v1`.
290
293
  - **First-time setup / "connect Google Ads" / `ACCOUNT_NOT_FOUND`** → [Install and Create an Account](references/google-ads/install-and-create-account.md) (do this before anything else).
291
294
  - **Suggested keywords / geo / budget / ad copy / images** → [Get AI Campaign Suggestions](references/google-ads/get-campaign-suggestions.md).
292
- - **Offer a Success Guide after creating a PMAX Leads campaign (a proactive offer requires approval) / improve it / success guide / says they completed or fixed a guide recommendation / mark or reopen a recommendation** → [Manage a Campaign Success Guide](references/google-ads/manage-campaign-success-guide.md) (a direct improvement/guide request is already approval; works while `LEARNING` and before metrics exist).
295
+ - **Improve an existing campaign / see what to fix next / Campaign Success Guide / mark or reopen a recommendation** → [Manage a Campaign Success Guide](references/google-ads/manage-campaign-success-guide.md).
293
296
  - **Create a multi-channel / lead-gen / Shopping campaign** → [Create a Performance Max Campaign](references/google-ads/create-performance-max-campaign.md).
294
297
  - **Pause / resume / launch / update budget / delete / history** → [Manage Campaign Lifecycle](references/google-ads/manage-campaign-lifecycle.md).
295
298
  - **Performance, conversions, search terms, per-product / per-asset metrics** → [Query Campaign Performance Analytics](references/google-ads/query-campaign-analytics.md).
@@ -308,7 +311,7 @@ Creates payment links for unpaid bookings using Payment Links API. Links booking
308
311
  "Manages existing Google Ads campaigns on a Wix site: list/get, launch (first activation) vs resume (reactivate after a pause), pause a running campaign — optionally with a scheduled auto-resume date — update name/budget/targeting, change the daily budget, delete permanently, and read status history / change log. Use when the user wants to 'pause my Google ad', 'resume my campaign', 'stop the campaign', 'change my daily budget', 'rename the campaign', 'delete this campaign', 'list my Google Ads campaigns', or 'why did my campaign status change'. Requires an existing Google Ads account and campaign. REST base https://www.wixapis.com/_serverless/pa-google/v1."
309
312
 
310
313
  ### [Manage a Campaign Success Guide](references/google-ads/manage-campaign-success-guide.md)
311
- "Retrieves and manages the campaign success guide for an existing Wix Google Ads Performance Max Leads campaign. Offer it after creating a supported campaign, even while it is learning or has no metrics; that proactive offer requires approval before retrieval. A direct request to improve a campaign, see what to fix next, or show the guide is already approval to retrieve it. Also use when a user reports completing or wants to reopen a guide item. Treat a clear completion report as an actionable update request even when phrased as a statement. Covers campaign selection, prioritized guide retrieval, and suggestion status updates without redundant confirmation. REST base https://www.wixapis.com/pa-platform/suggestions/v1."
314
+ "Campaign Success Guide for existing Wix Google Ads Performance Max Leads campaigns. Use after creating a supported campaign, even while it is learning or has no metrics, and whenever users ask how to improve a campaign, what to fix next, to view the guide, or to mark or reopen a recommendation. Covers campaign and site selection; prioritized actionable recommendations; deduplicated, destination-specific Editor and Google Ads navigation; offers to perform supported work after approval; Merchant Center and Business Profile connection follow-ups; and suggestion-status tracking."
312
315
 
313
316
  ### [Query Campaign Performance Analytics](references/google-ads/query-campaign-analytics.md)
314
317
  "Reads performance analytics for a Google Ads campaign on a Wix site: daily performance metrics (impressions, clicks, CTR, cost, leads, phone calls) with optional previous-period comparison and trends; conversion metrics from Wix Analytics (orders, revenue, leads, CPL, ROAS); the search terms that triggered a campaign's ads; per-product shopping performance for retail campaigns; and per-asset performance (headlines, descriptions, images) for PMAX Leads. Explains when to use campaignResourceName vs the Wix campaignId, the dateRange shape, field enums, sorting, and paging. Use when the user asks 'how is my campaign doing', 'show ad performance', 'what search terms triggered my ads', 'which products/assets perform best', 'campaign ROI/ROAS', or 'conversions from my Google ads'. REST base https://www.wixapis.com/_serverless/pa-google/v1."
@@ -425,7 +428,7 @@ Drive the Wix Site Import agent to migrate an existing store or site from anothe
425
428
  "Builds direct links to the account-level sites pages on manage.wix.com — the My Sites list (all sites in the account) and each site's own dashboard. Pairs the site list with the Query Sites read API. Use when the user asks where something is in the Wix dashboard, wants a direct link to a dashboard page, or you need a dashboard URL to include with the result of an API operation."
426
429
 
427
430
  ### [Upload a Website or HTML Files](references/sites/upload-static-site.md)
428
- Get a user's ready-made website live on Wix when they already have the site files — an index.html, a zip, or a static build exported from an AI builder or any other tool — by sending them to the Wix Headless drop page, where they upload the files and instantly get a live hosted site they can claim into their account. Use whenever the user wants to upload, publish, or host their own HTML/CSS/JS files as a NEW site. Not for migrating a live store/site from another platform by URL or from CSV exports (use Site Import), not for adding HTML or custom code into an existing Wix site, and not for uploading images or documents to a site's media files.
431
+ Publish a user's ready-made website — an index.html, a static build, or a zip exported from an AI builder or any other tool — as a new live Wix site. Covers both ways to get there — handing the user the Wix Headless drop page, and calling the Wix Headless instant-site REST API yourself when you can reach the files and make outbound HTTPS requests. When you also hold the user's identity, covers putting the published site straight into their Wix account and reading back its final live URL. Use whenever the user wants to upload, publish, deploy, or host their own HTML/CSS/JS as a NEW site, including files generated for them earlier in the conversation, or to update a site already published this way (iterate on the same site instead of creating another). Not for migrating a live store/site from another platform by URL or from CSV exports (use Site Import), not for adding HTML or custom code into an existing Wix site, and not for uploading images or documents to a site's media files.
429
432
 
430
433
  ---
431
434
 
@@ -201,6 +201,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
201
201
  -H 'Authorization: <AUTH>' \
202
202
  -H 'Content-Type: application/json' \
203
203
  -d '{
204
+ "returnEntity": true,
204
205
  "services": [{
205
206
  "name": "Consultation",
206
207
  "type": "APPOINTMENT",
@@ -234,6 +235,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
234
235
  -H 'Authorization: <AUTH>' \
235
236
  -H 'Content-Type: application/json' \
236
237
  -d '{
238
+ "returnEntity": true,
237
239
  "services": [{
238
240
  "name": "Free Consultation",
239
241
  "type": "APPOINTMENT",
@@ -264,6 +266,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
264
266
  -H 'Authorization: <AUTH>' \
265
267
  -H 'Content-Type: application/json' \
266
268
  -d '{
269
+ "returnEntity": true,
267
270
  "services": [{
268
271
  "name": "Yoga Class",
269
272
  "type": "CLASS",
@@ -283,7 +286,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
283
286
  }'
284
287
  ```
285
288
 
286
- After creation, use the `service.schedule.id` from the response to create class events with `bulkCreateEvents` (see Step 3).
289
+ After creation, use `results[0].item.schedule.id` from the response to create class events with `bulkCreateEvents` (see Step 3). This requires `returnEntity: true` on the request — without it the response carries only `results[0].itemMetadata.id`, which has no `schedule.id`; and the created service is directly under `item` (there is no `item.service`).
287
290
 
288
291
  **Required Fields:**
289
292
 
@@ -109,6 +109,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
109
109
  -H 'Authorization: <AUTH>' \
110
110
  -H 'Content-Type: application/json' \
111
111
  -d '{
112
+ "returnEntity": true,
112
113
  "services": [{
113
114
  "name": "<SERVICE_NAME>",
114
115
  "description": "<GENERATED_DESCRIPTION>",
@@ -142,6 +143,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
142
143
  -H 'Authorization: <AUTH>' \
143
144
  -H 'Content-Type: application/json' \
144
145
  -d '{
146
+ "returnEntity": true,
145
147
  "services": [{
146
148
  "name": "<SERVICE_NAME>",
147
149
  "description": "<GENERATED_DESCRIPTION>",
@@ -171,7 +173,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
171
173
  - `schedule.availabilityConstraints.sessionDurations` sets the appointment length
172
174
  - Availability is based on the assigned staff member's working hours schedule
173
175
 
174
- Save the `serviceId` from the response: `results[0].item.service.id`
176
+ Save the `serviceId` from the response: `results[0].item.id` (the created service is directly under `item` — there is no `item.service`). This requires `returnEntity: true` on the request; without it the response carries only `results[0].itemMetadata.id`.
175
177
 
176
178
  ---
177
179
 
@@ -96,6 +96,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
96
96
  -H 'Authorization: <AUTH>' \
97
97
  -H 'Content-Type: application/json' \
98
98
  -d '{
99
+ "returnEntity": true,
99
100
  "services": [{
100
101
  "name": "<SERVICE_NAME>",
101
102
  "description": "<GENERATED_DESCRIPTION>",
@@ -124,6 +125,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
124
125
  -H 'Authorization: <AUTH>' \
125
126
  -H 'Content-Type: application/json' \
126
127
  -d '{
128
+ "returnEntity": true,
127
129
  "services": [{
128
130
  "name": "<SERVICE_NAME>",
129
131
  "description": "<GENERATED_DESCRIPTION>",
@@ -149,7 +151,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
149
151
  - `defaultCapacity` is **required** — sets max participants per session
150
152
  - After creation, class sessions must be scheduled separately via `bulkCreateEvents` using the returned `service.schedule.id` (see [Create and Update Booking Services](./create-and-update-booking-services.md))
151
153
 
152
- **Response:** Save the service ID from `results[0].itemMetadata.id`
154
+ **Response:** Save the service ID from `results[0].item.id` and the schedule ID for `bulkCreateEvents` from `results[0].item.schedule.id` (the created service is directly under `item`). This requires `returnEntity: true` on the request — without it the response carries only `results[0].itemMetadata.id`, which has no `schedule.id`.
153
155
 
154
156
  ---
155
157
 
@@ -101,6 +101,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
101
101
  -H 'Authorization: <AUTH>' \
102
102
  -H 'Content-Type: application/json' \
103
103
  -d '{
104
+ "returnEntity": true,
104
105
  "services": [{
105
106
  "name": "<SERVICE_NAME>",
106
107
  "description": "<GENERATED_DESCRIPTION>",
@@ -129,6 +130,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
129
130
  -H 'Authorization: <AUTH>' \
130
131
  -H 'Content-Type: application/json' \
131
132
  -d '{
133
+ "returnEntity": true,
132
134
  "services": [{
133
135
  "name": "<SERVICE_NAME>",
134
136
  "description": "<GENERATED_DESCRIPTION>",
@@ -157,8 +159,8 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
157
159
  - After creation, course sessions must be scheduled separately via `bulkCreateEvents` using the returned `service.schedule.id` (see [Create and Update Booking Services](./create-and-update-booking-services.md))
158
160
  - If the user asked you to create the course and gave the session count/times, the job is not complete after `bulkCreateServices`; continue by creating the Calendar events, then verify the service has future events before telling the user it is ready
159
161
 
160
- Save the `serviceId` from the response: `results[0].item.service.id`
161
- Save the `service.schedule.id` from the response for the follow-up `bulkCreateEvents` request.
162
+ Save the `serviceId` from the response: `results[0].item.id` (the created service is directly under `item` — there is no `item.service`). This requires `returnEntity: true` on the request; without it the response carries only `results[0].itemMetadata.id`.
163
+ Save the `service.schedule.id` from the response (`results[0].item.schedule.id`) for the follow-up `bulkCreateEvents` request.
162
164
 
163
165
  ---
164
166
 
@@ -1,11 +1,68 @@
1
1
  ---
2
2
  name: "End-to-End Booking Flow"
3
- description: Complete booking flow from service discovery to payment. Query services, check availability with Time Slots V2, create bookings, and process payment via eCommerce checkout.
3
+ description: Books and settles appointments, classes and courses with the site owner's credentials — an operator managing bookings, or server-side code booking as the owner. Covers service discovery, availability with Time Slots V2, creating the booking, and settling it by direct confirmation or eCommerce checkout. A visitor booking for themselves needs a visitor token instead; this recipe links that path.
4
4
  ---
5
5
 
6
6
  # End-to-End Booking Flow (REST)
7
7
 
8
- Step-by-step flow for implementing a complete booking experience using REST APIs.
8
+ Step-by-step flow for creating and settling bookings **with the site owner's credentials** —
9
+ see who that covers, and where visitor-facing code goes instead, immediately below.
10
+
11
+ > ## ⚠️ These are owner-side calls, not visitor-facing code
12
+ >
13
+ > Every call below runs with the **site owner's credentials** — an API key or the site's admin
14
+ > token. That covers two situations, and both are this recipe:
15
+ >
16
+ > - an **operator managing bookings**, calling these APIs from a script, a management tool, or a
17
+ > back-office screen
18
+ > - **server-side code in a site or app acting as the owner** — a backend function booking a
19
+ > client in, a scheduled job, a webhook handler
20
+ >
21
+ > Either way the booking belongs to the site, not to the person being booked.
22
+ >
23
+ > **Building a site or app where a visitor books for themselves? These payloads are right, the
24
+ > identity is not.** A visitor's booking and checkout run on an **anonymous visitor token** minted
25
+ > from the site's OAuth app client id. Using the owner's credentials for a visitor's purchase
26
+ > produces a checkout that belongs to the site: no cart persistence, no abandoned-checkout
27
+ > recovery, and no attribution to the customer. Read these first:
28
+ >
29
+ > - [Book an Appointment](https://dev.wix.com/docs/api-reference/business-solutions/bookings/skills/book-an-appointment)
30
+ > — the same flow from the visitor's side: visitor token, slot re-validation, booking form
31
+ > fields, checkout hand-off, and eight pitfalls from real conversations
32
+ > - [Retrieve Tokens](https://dev.wix.com/docs/api-reference/business-management/headless/authentication/retrieve-tokens)
33
+ > — minting and refreshing the anonymous visitor token
34
+ > - [Allow Redirect URIs and Domains](https://dev.wix.com/docs/go-headless/authentication/setup/allow-redirect-uris-and-domains)
35
+ > — registering your URLs so the visitor returns to your app after the Wix-hosted checkout
36
+ > - [Manage OAuth Apps](../sites/manage-oauth-apps.md) — creating the OAuth app, whose id is the
37
+ > `client_id` the frontend mints visitor tokens from
38
+ > - [Bookings Quick Start](https://dev.wix.com/docs/go-headless/self-managed-headless/tutorials/java-script-sdk-tutorials/bookings-quick-start)
39
+ > — the same flow through the JavaScript SDK on a headless site
40
+ >
41
+ > Mixed apps are normal: the owner's credentials manage the catalog, staff, policies and coupons
42
+ > (the recipes here), while the visitor's own token books and pays.
43
+
44
+ ## Contents
45
+
46
+ 1. [Query Available Services](#step-1-query-available-services) — `POST /bookings/v2/services/query`.
47
+ The three service types and how each one changes the rest of the flow, plus the four fields to
48
+ carry forward: `id`, `schedule.id`, `type`, `staffMemberIds`.
49
+ 2. [Check Availability](#step-2-check-availability) — `POST /_api/service-availability/v2/time-slots`.
50
+ Time Slots V2 replaces the deprecated Availability Calendar. Dates must be full local
51
+ datetimes or the call 400s; `availableResources` stays empty unless you ask for it by resource
52
+ type; the `location.locationType` a slot returns is **not** the value Create Booking accepts.
53
+ Classes come from a different endpoint and carry an `eventId`.
54
+ 3. [Create the Booking](#step-3-create-the-booking) — `POST /_api/bookings-service/v2/bookings`.
55
+ One payload shape per service type: appointments need every slot field spelled out, classes
56
+ need only the service and event ids and derive the rest, courses book a whole schedule.
57
+ Participants are `totalParticipants` **or** `participantsChoices`, never both. The booking
58
+ lands as `CREATED` and is not on the calendar until step 4.
59
+ 4. [Confirm or Process Payment](#step-4-confirm-or-process-payment) — two branches. Free and
60
+ pay-at-location bookings are confirmed directly with a payment status. Paid bookings go into a
61
+ cart that references the booking id, and whoever is paying is sent to its checkout URL; a
62
+ server-to-server place-order path skips the payment page entirely, which only fits an operator
63
+ or backend job booking someone in.
64
+ 5. [Service Type Summary](#service-type-summary) — appointment, class and course side by side:
65
+ which `bookedEntity` each uses, which availability API feeds it, and what makes it different.
9
66
 
10
67
  ## Prerequisites
11
68
 
@@ -256,7 +313,15 @@ Use the booking ID as `catalogItemId` with the Wix Bookings app ID. Save `cart.i
256
313
 
257
314
  Redirect the user to the returned `checkoutUrl`. After payment, the booking is automatically confirmed.
258
315
 
259
- **4c. Place Order (alternative, server-to-server)**
316
+ > When the person paying is a visitor, create the cart and this URL **with their visitor token**,
317
+ > not the owner's credentials — see the note at the top. The call succeeds either way, which is
318
+ > what makes it easy to get wrong.
319
+
320
+ **4c. Place Order — no payment page (alternative, server-to-server)**
321
+
322
+ > This one only makes sense on the owner's side: it creates the order outright, with nobody
323
+ > visiting a payment page. Right for an operator or backend job booking someone in; not a way to
324
+ > charge a visitor, who still has to be sent to checkout.
260
325
 
261
326
  First calculate the cart to get a price-verification token:
262
327
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: "Flow: Seasonal Promotion"
3
- description: SEASONAL sub-flow — load [Goal: Seasonal Revenue] FIRST (it owns classification and routing); this is a sub-step, NOT a direct entry from README.
3
+ description: "SEASONAL sub-flow — load [Goal: Seasonal Revenue] FIRST (it owns classification and routing); this is a sub-step, NOT a direct entry from README."
4
4
  ---
5
5
  # Flow: Seasonal Promotion
6
6