@wix/agent-skills 1.7.0 → 1.9.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 (126) hide show
  1. package/README.md +3 -7
  2. package/package.json +1 -1
  3. package/skills/wix-app/SKILL.md +2 -0
  4. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +2 -1
  5. package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
  6. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +14 -0
  7. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +11 -6
  8. package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +3 -17
  9. package/skills/wix-app/references/editor-react-component/SSR.md +119 -0
  10. package/skills/wix-design-system/SKILL.md +5 -1
  11. package/skills/wix-headless/SKILL.md +55 -32
  12. package/skills/wix-headless/references/BUILD-astro.md +244 -0
  13. package/skills/wix-headless/references/BUILD-own-build.md +167 -0
  14. package/skills/wix-headless/references/BUILD.md +50 -252
  15. package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
  16. package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
  17. package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
  18. package/skills/wix-headless/references/DISCOVERY.md +41 -422
  19. package/skills/wix-headless/references/PLAN-connect.md +19 -0
  20. package/skills/wix-headless/references/PLAN-create.md +17 -0
  21. package/skills/wix-headless/references/PLAN.md +37 -27
  22. package/skills/wix-headless/references/SEED.md +27 -18
  23. package/skills/wix-headless/references/SETUP.md +32 -213
  24. package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
  25. package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +45 -0
  26. package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +58 -0
  27. package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
  28. package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
  29. package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
  30. package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
  31. package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
  32. package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +47 -27
  33. package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
  34. package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
  35. package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
  36. package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
  37. package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
  38. package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
  39. package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
  40. package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
  41. package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
  42. package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
  43. package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
  44. package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
  45. package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
  46. package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
  47. package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
  48. package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
  49. package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +233 -0
  50. package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +136 -0
  51. package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
  52. package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +54 -0
  53. package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
  54. package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +49 -0
  55. package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +245 -0
  56. package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +356 -0
  57. package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +133 -0
  58. package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +73 -0
  59. package/skills/wix-headless/references/astro/templates/global.css +2 -2
  60. package/skills/wix-headless/references/astro/templates/index.astro +1 -1
  61. package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
  62. package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
  63. package/skills/wix-headless/references/bookings/FLOW.md +185 -0
  64. package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +90 -0
  65. package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
  66. package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
  67. package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
  68. package/skills/wix-headless/references/commands/known-apps.json +2 -1
  69. package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
  70. package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
  71. package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
  72. package/skills/wix-headless/references/custom/bookings/WIRING.md +146 -0
  73. package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
  74. package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
  75. package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
  76. package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
  77. package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
  78. package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
  79. package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
  80. package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
  81. package/skills/wix-headless/references/seed-recipes.md +2 -2
  82. package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
  83. package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
  84. package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
  85. package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
  86. package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
  87. package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
  88. package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
  89. package/skills/wix-headless/references/shared/STYLING.md +8 -18
  90. package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
  91. package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
  92. package/skills/wix-headless/references/verticals/_schema.json +1 -1
  93. package/skills/wix-headless/references/verticals/_schema.md +3 -3
  94. package/skills/wix-headless/references/verticals/blog.md +1 -1
  95. package/skills/wix-headless/references/verticals/bookings.md +39 -0
  96. package/skills/wix-headless/references/verticals/cms.md +1 -1
  97. package/skills/wix-headless/references/verticals/ecom.md +2 -2
  98. package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
  99. package/skills/wix-headless/references/verticals/stores.md +1 -1
  100. package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
  101. package/skills/wix-headless/scripts/compose.mjs +540 -0
  102. package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
  103. package/skills/wix-headless/scripts/flatten.sh +61 -0
  104. package/skills/wix-headless/scripts/generate-images.mjs +242 -0
  105. package/skills/wix-headless/scripts/scaffold.sh +47 -16
  106. package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
  107. package/skills/wix-manage/SKILL.md +13 -1
  108. package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -27
  109. package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
  110. package/skills/wix-manage/references/bookings/create-appointment-service.md +266 -0
  111. package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +37 -0
  112. package/skills/wix-manage/references/bookings/create-class-service.md +245 -0
  113. package/skills/wix-manage/references/bookings/create-course-service.md +249 -0
  114. package/skills/wix-manage/references/cms/cms-data-items-crud.md +3 -2
  115. package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
  116. package/skills/wix-manage/references/cms/cms-schema-management.md +2 -2
  117. package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
  118. package/skills/wix-manage/references/forms/create-form.md +49 -1
  119. package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
  120. package/skills/wix-manage/references/stores/create-product-from-image.md +154 -2
  121. package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
  122. package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
  123. package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
  124. package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
  125. package/skills/wix-headless/scripts/preview.sh +0 -45
  126. package/skills/wix-headless/scripts/release.sh +0 -73
@@ -0,0 +1,185 @@
1
+ # Bookings — the booking flow (framework-agnostic logic)
2
+
3
+ The booking logic — the step model, the shared state, and the exact `@wix` SDK
4
+ sequence — is the **same on every frontend** (astro, react/vite, vue, plain HTML).
5
+ This file is that shared logic. The per-framework wiring layers on top:
6
+
7
+ - **astro** (`frontendBuild: wix`): `../astro/bookings/SERVICES_PAGES.md` (SSR read pages) + `../astro/bookings/COMPONENTS.md` (the client islands).
8
+ - **own / static** (`frontendBuild: own`/`none`): `../custom/bookings/WIRING.md`.
9
+
10
+ **Code examples are React** (`../astro/templates/bookings/*.tsx` + `bookingDriver.ts`).
11
+ React is directly usable on astro islands and on react/vite. **On any other
12
+ framework, translate the examples** — the SDK calls and payloads are identical;
13
+ only the UI idiom (`useState`/`useEffect` → a store / `onMounted` / signals) and
14
+ the client acquisition differ (see "Client identity" below).
15
+
16
+ The flow is **appointment booking**. CLASS sign-up reuses the same sequence with
17
+ an event-slot branch (noted inline) and is a fast-follow, not v1. **Location and
18
+ staff selection are likewise a fast-follow (a separate PR); add-ons are out of
19
+ scope.** v1 threads a single service selection — the shared state below holds no
20
+ location / staff / add-on choice yet.
21
+
22
+ ---
23
+
24
+ ## 1. The step model
25
+
26
+ A single customer journey, five steps, holding one shared selection:
27
+
28
+ ```
29
+ 1. Catalog list bookable services → pick a service
30
+ 2. Slots week calendar of availability for that service → pick a time slot
31
+ 3. Details the service's booking form (collect contact) → submit
32
+ 4. Book run the SDK sequence (§3)
33
+ 5a. paid → redirect to the Wix-hosted checkout → returns to the confirmation page
34
+ 5b. free/offline → place the order → confirmation page
35
+ ```
36
+
37
+ **The catalog is the entry point but optional** — a visitor can land directly on a
38
+ service page and start at the Slots step. Every step rehydrates from the URL (§2),
39
+ so deep links work.
40
+
41
+ Mapping the steps onto pages/routes is the **target framework's choice** — translate
42
+ the step model to whatever router the framework uses. The skill recommends the routes
43
+ `/services`, `/services/[slug]`, `/booking-confirmation` (advertised in
44
+ `../verticals/bookings.md`). On astro specifically, SSR the catalog + detail pages
45
+ for SEO and run slots + form + book in a `client:only` island.
46
+
47
+ ## 2. Shared booking state
48
+
49
+ Steps 1–4 build up one selection (this is exactly `BookParams` + `SelectedSlot`
50
+ in `../astro/templates/bookings/bookingDriver.ts`):
51
+
52
+ - **`service`** — the chosen service object (the booking step reads `service.payment` + `service.bookingPolicy`).
53
+ - **`slot`** — `{ serviceType, serviceId, localStartDate, localEndDate, timezone, scheduleId?, eventId?, locationId?, locationType? }`.
54
+ - **`formSubmission`** — the booking-form values, **keyed by each field's `target`** (§4).
55
+
56
+ Hold it however the framework prefers (React state lifted to a coordinator, a
57
+ store, route loaders + a provider, query-params). **Two rules that bite:**
58
+
59
+ 1. **Persist the selection before navigating.** When a step lives on its own
60
+ route, write the selection into the shared state (or the URL) *before* moving
61
+ on, and let the next step **rehydrate from the slug/URL** on direct load /
62
+ refresh. A persistent provider that does not re-init on in-app navigation will
63
+ otherwise show an empty next step.
64
+ 2. **The slug is the carry-across key.** `serviceSlug` (the service's
65
+ `mainSlug.name`) identifies the service across the slots + details steps;
66
+ resolve the full service from it when state is lost.
67
+
68
+ ## 3. The booking SDK sequence — `bookingDriver.ts`
69
+
70
+ The whole booking — `createBooking → createCart → calculateCart → checkout-or-place`
71
+ — is in **`../astro/templates/bookings/bookingDriver.ts`**, in plain `@wix` SDK
72
+ calls (no React, no UI). **Use it as-is** (it is framework-agnostic); the booking
73
+ step calls `book(params)` then branches on the result. Do not re-author the
74
+ sequence — the payload shapes are exact and easy to get subtly wrong.
75
+
76
+ ```
77
+ book({ service, slot, formSubmission, timezone }) →
78
+ createBooking(...) // booking lands CREATED — the cart holds the seat
79
+ createCart(...) // one catalog item per bookingId, appId = BOOKING_APP_ID, channel WEB
80
+ calculateCart(cartId) // → { cart, summary }; totals on summary.priceSummary.total.amount
81
+ isCheckoutRequired(...) ?
82
+ → { CheckoutRequired, cartId } // paid → navigateToCheckout(cartId, postFlowUrl) → Wix-hosted checkout
83
+ : placeOrder(cartId) → { CheckoutSkipped, orderId } // free / pay-in-person → confirmation
84
+ ```
85
+
86
+ Key facts the driver encodes (do not deviate):
87
+ - **No `confirmBooking`** — `confirmBooking` is the classic Bookings server-side
88
+ confirm step (it moves a booking from `CREATED` to `CONFIRMED`). Here the **ecom
89
+ cart holds the seat** instead — `placeOrder` (free/offline) or the hosted checkout
90
+ (paid) drives confirmation — so a client-only site completes the whole flow with no
91
+ server elevation.
92
+ - **ANY_RESOURCE fallback (staff)** — v1 has **no staff picker**, so `createBooking`
93
+ always sends `resourceSelections:[{ resourceTypeId:"1cd44cf8-…", selectionMethod:"ANY_RESOURCE" }]`
94
+ and Wix auto-assigns a bookable staff resource (appointment slots return
95
+ `availableResources:[]` yet book fine this way). (Explicit staff selection is a
96
+ fast-follow.)
97
+ - **Checkout decision** — `isCheckoutRequired`: if the service's booking policy charges
98
+ a **cancellation fee** (`service.bookingPolicy.cancellationFeePolicy.enabled`) →
99
+ checkout (a card must be on file); else total 0 → place; else `FULL_PAYMENT_OFFLINE`
100
+ → place; else checkout.
101
+ - **Redirect shape** — paid bookings hand the cart to the Wix-hosted **ecom**
102
+ checkout: `createRedirectSession({ ecomCheckout: { checkoutId: cartId }, callbacks:{ postFlowUrl } })`.
103
+
104
+ ### Browse + availability (the read calls the steps make)
105
+
106
+ Mirror what the Bookings SDK does (`services.queryServices` is the raw SDK, not a component):
107
+
108
+ ```js
109
+ // Catalog (list) — the filter MUST include appId; pass conditionalFields, then .find():
110
+ services.queryServices({
111
+ query: { filter: { appId: BOOKING_APP_ID }, paging: { limit: 100 } },
112
+ conditionalFields: ["STAFF_MEMBER_DETAILS"],
113
+ }).find(); // → result.items ; filter out s.hidden
114
+
115
+ // Single service by slug — the .eq() builder chain:
116
+ services.queryServices({ conditionalFields: ["STAFF_MEMBER_DETAILS"] })
117
+ .eq("mainSlug.name", slug).eq("appId", BOOKING_APP_ID).limit(1).find();
118
+
119
+ // Availability (APPOINTMENT) — serviceId is a single GUID STRING:
120
+ availabilityTimeSlots.listAvailabilityTimeSlots({
121
+ serviceId, fromLocalDate, toLocalDate, timeZone, bookable: true, cursorPaging: { limit: 100 },
122
+ }); // slots: result.timeSlots[] (localStartDate/localEndDate/scheduleId at top level)
123
+
124
+ // Availability (CLASS) — different namespace, PLURAL serviceIds; slots carry eventInfo.eventId, no scheduleId:
125
+ eventTimeSlots.listEventTimeSlots({ serviceIds: [serviceId], fromLocalDate, toLocalDate, timeZone, includeNonBookable: false });
126
+ ```
127
+
128
+ `fromLocalDate`/`toLocalDate` are **local** date strings `YYYY-MM-DDThh:mm:ss`
129
+ (no `Z`) with an explicit `timeZone`.
130
+
131
+ ## 4. The booking form — schema-driven (`@wix/forms`)
132
+
133
+ The booking form is a **`@wix/forms` form** attached to the service
134
+ (`service.form._id`, namespace `wix.bookings.v2.bookings`). Render it
135
+ **schema-driven** — read the field list and render inputs by field type — the
136
+ **same renderer the forms vertical uses** (`../astro/forms/CONTACT_FORM.md`):
137
+
138
+ 1. Fetch the form by `service.form._id` (`@wix/forms` `getForm`; server-side/elevated
139
+ where the framework allows). Read `form.formFields`.
140
+ 2. Keep `fieldType === "INPUT" && !hidden` **and** a recognized string
141
+ `componentType` (`TEXT_INPUT` / `PHONE_INPUT` / `DROPDOWN`). For each, take
142
+ `inputOptions.target`, `inputOptions.required`, `inputOptions.stringOptions.componentType`,
143
+ and the label from `inputOptions.stringOptions.{textInputOptions|dropdownOptions|phoneInputOptions}.label`.
144
+ **Skip complex, object-valued fields** — e.g. a multi-line `ADDRESS` — they carry
145
+ no string `componentType`; rendering them as a text input sends a string and
146
+ `createBooking` rejects it with **"must be object"**. The booking enforces only
147
+ the contact basics (`first_name`/`last_name`/`email`), so omitting an optional
148
+ complex field is safe (do **not** default unknown field types to a text input).
149
+ 3. Render generic inputs by `componentType`: `TEXT_INPUT` → text input,
150
+ `DROPDOWN` → select, `PHONE_INPUT` → `type=tel`; treat `identifier === "TEXT_AREA"`
151
+ (or `target` containing `message`) as a textarea (its `componentType` is still `TEXT_INPUT`).
152
+ 4. Collect the values into an object **keyed by each field's `target`** — that is
153
+ `formSubmission`. Pass it to `book()`. Only include fields the visitor filled;
154
+ never send a value for a field you didn't render.
155
+
156
+ **Do not** submit `contactDetails`, and **do not** hardcode field names — key by
157
+ `target`. The default booking form's targets are snake_case `first_name` /
158
+ `last_name` / `email` / `phone`. Reference example: `../astro/templates/bookings/BookingForm.tsx`.
159
+
160
+ ## 5. The day-calendar (slots step)
161
+
162
+ Show a **week calendar**, not a flat list of every slot: a 7-day strip with
163
+ week navigation → the picked day's times. A flat grid with time-only labels
164
+ leaves the visitor unable to tell which day a slot is on. Fetch availability for
165
+ the visible window (`fromLocalDate`/`toLocalDate` = the week bounds), group slots
166
+ by calendar day, and offer a **"check next availability"** action that probes
167
+ forward when a week is empty. Reference example:
168
+ `../astro/templates/bookings/AvailabilityCalendar.tsx`.
169
+
170
+ ## 6. Client identity (per framework)
171
+
172
+ The SDK calls are identical everywhere; only how you get the client differs:
173
+ - **astro** — SSR read pages use the ambient `@wix/essentials` client
174
+ (`auth.elevate(services.queryServices)(...)`); browser islands call the `@wix`
175
+ modules **ambiently** too (the `@wix/astro` visitor client, like the ecom
176
+ `CartView` island) — no `createClient`/`OAuthStrategy`.
177
+ - **own / static** — acquire a visitor client once:
178
+ `createClient({ modules, auth: OAuthStrategy({ clientId: appId }) })` and call
179
+ the same functions on it. CDN imports for `none`, bundled for `own`.
180
+
181
+ ## 7. Out of v1
182
+
183
+ Waitlist, on-site manage/cancel, location selector, staff filter, payment
184
+ breakdown, and CLASS sign-up are out of v1. Show bookable slots only;
185
+ post-booking self-service is handled by the Wix-hosted flow / member area.
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: bookings-implementer
3
+ description: "Implements the Wix Bookings vertical — services catalog, a service detail page with a week-calendar availability picker, a schema-driven booking form, and confirmation. The booking runs client-side via the @wix SDK (ecom Cart V2 — no confirmBooking). Scopes: seed, components, pages. Extends references/shared/IMPLEMENTER.md."
4
+ ---
5
+
6
+ # Bookings Implementer
7
+
8
+ Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, the `.wix/seeded.json` read pattern, the return contract, style conventions, and common failure modes.
9
+
10
+ ## The logic lives in one place
11
+
12
+ The booking **logic** — the step model, the shared selection state, the exact
13
+ `@wix` SDK sequence, and the schema-driven form — is framework-agnostic and lives
14
+ in **`./FLOW.md`**. Read it first. The astro guides below are the astro *wiring* of
15
+ that logic; the code examples under `<SKILL_ROOT>/references/astro/templates/bookings/`
16
+ are React (the astro islands use them directly). For an own/static build, the wiring guide is
17
+ `../custom/bookings/WIRING.md` (same logic, client-side `@wix/sdk`).
18
+
19
+ ## Scope routing
20
+
21
+ | Scope | Phase | Reference |
22
+ |-------|-------|-----------|
23
+ | `seed` | Seed — create services (and staff) via the Wix Bookings REST API | `./SERVICES_DATA.md` |
24
+ | `components` | Components — the client islands (week calendar, schema-driven form, the flow coordinator) | `./FLOW.md` + `../astro/bookings/COMPONENTS.md` |
25
+ | `pages` | Pages — `/services` listing, `/services/[slug]` detail, `/booking-confirmation`, + nav/home links | `./FLOW.md` + `../astro/bookings/SERVICES_PAGES.md` |
26
+
27
+ ## Templates — read and adapt (don't invent)
28
+
29
+ Canonical examples live at `<SKILL_ROOT>/references/astro/templates/bookings/`.
30
+ Your `components` and `pages` scopes **read these and adapt them** — adapt brand
31
+ copy, headings, and styling; keep the SDK calls, payload shapes, and the data
32
+ flow (re-authoring the SDK wiring from scratch is the main source of API-shape bugs).
33
+
34
+ Components (`components` scope — TSX only, no CSS):
35
+ - `<SKILL_ROOT>/references/astro/templates/bookings/AvailabilityCalendar.tsx` — the week calendar (week strip → the day's slots; APPOINTMENT via `availabilityTimeSlots`, CLASS via `eventTimeSlots`).
36
+ - `<SKILL_ROOT>/references/astro/templates/bookings/BookingForm.tsx` — the schema-driven form (renders the `@wix/forms` field list, keys values by `target`) that drives `bookingDriver.book()`.
37
+ - `<SKILL_ROOT>/references/astro/templates/bookings/ServiceBookingFlow.tsx` — the coordinator island (holds the selected slot; calendar → form → redirect).
38
+
39
+ Pages (`pages` scope):
40
+ - `<SKILL_ROOT>/references/astro/templates/bookings/ServiceCard.astro`
41
+ - `<SKILL_ROOT>/references/astro/templates/bookings/services/index.astro`, `<SKILL_ROOT>/references/astro/templates/bookings/services/[slug].astro`
42
+ - `<SKILL_ROOT>/references/astro/templates/bookings/booking-confirmation.astro`
43
+
44
+ ### Pre-copied by the orchestrator (do NOT write these yourself)
45
+ Mechanical, brand-agnostic — the orchestrator copies them before dispatch (BUILD-astro.md § build wave). Rely on them at the listed paths:
46
+ - `src/components/bookingDriver.ts` ← `<SKILL_ROOT>/references/astro/templates/bookings/bookingDriver.ts` — the booking SDK sequence (`book()`, `navigateToCheckout()`). The islands import it; never re-author it.
47
+ - `src/components/SeoTags.astro` ← `<SKILL_ROOT>/references/astro/templates/bookings/SeoTags.astro` — renders `service.seoData.tags`; imported by `services/[slug].astro`.
48
+ - `src/styles/components-bookings.css` ← `<SKILL_ROOT>/references/astro/templates/bookings/components-bookings.css` — the flow's component classes.
49
+
50
+ If a pre-copied file is missing at runtime, that's an orchestrator-side bug — return `status: "partial"` with `errors: [{code: "UTILITY_TEMPLATE_NOT_PRECOPIED", path: "<missing>"}]`; do not author your own version.
51
+
52
+ ## Pre-return file-existence assertion (pages scope)
53
+
54
+ Before returning `status: "complete"` from `pages`, verify on disk:
55
+ - `src/pages/services/index.astro`
56
+ - `src/pages/services/[slug].astro`
57
+ - `src/pages/booking-confirmation.astro`
58
+
59
+ If any declared file is missing, return `status: "partial"` with `errors: [{ code: "PHASE4_FILE_MISSING", path: "<expected path>" }]`.
60
+
61
+ ## Dependencies (Setup installs these — see SETUP.md)
62
+
63
+ `@wix/bookings @wix/essentials @wix/forms @wix/redirects @wix/auto_sdk_ecom_cart-v-2`.
64
+ `@wix/forms` renders the booking-form schema; `@wix/redirects` + `@wix/auto_sdk_ecom_cart-v-2` run the cart/checkout sequence.
65
+
66
+ ## CSS ownership — bookings pack
67
+
68
+ Bookings-specific CSS lives in `src/styles/components-bookings.css` (pre-copied — see above), NOT in `global.css`. Classes the pack owns: `.service-card*`, `.service-grid`, `.availability-*`, `.time-slot*`, `.booking-*`. If `global.css` ships a partial rule for any of these, flag it (`{code:"GLOBAL_CSS_LEAK", class:"<name>"}`) and override in `components-bookings.css`.
69
+
70
+ ## Bookings-specific failure modes
71
+
72
+ | Wrong | Right |
73
+ |-------|-------|
74
+ | Stop after `createBooking` returns | `createBooking` leaves the booking **`CREATED`**. The seat is held by the **ecom Cart V2**: continue `createCart → calculateCart → isCheckoutRequired ? hosted-checkout redirect : placeOrder`. The whole sequence is in `bookingDriver.ts` — use it. |
75
+ | Hardcode `selectedPaymentOption: "ONLINE"` on `createBooking` | **Derive** it from the service: `online && !inPerson → "ONLINE"`, `!online && inPerson → "OFFLINE"`, else `"ONLINE"`. A free / pay-in-person service is the only kind that reaches `placeOrder`, and booking it `ONLINE` makes the cart reject it with **`INSUFFICIENT_INVENTORY`** (`available_quantity: 0`). `bookingDriver.ts` derives this for you (don't pass `selectedPaymentOption` to `book()`). |
76
+ | Pass `contactDetails` to `createBooking` | Pass **`formSubmission`**, keyed by each field's **`target`** (default booking form: snake_case `first_name`/`last_name`/`email`/`phone`). `contactDetails` is what Wix derives back onto the response. |
77
+ | Hardcode the booking-form fields | Render the form **schema-driven**: read `service.form._id`'s fields via `@wix/forms`, render by `componentType`, key values by `target` (same renderer as `../astro/forms/CONTACT_FORM.md`). |
78
+ | Render every form field as a text input (default unknown types to `TEXT_INPUT`) | Render **only** fields with a recognized string `componentType` (`TEXT_INPUT`/`PHONE_INPUT`/`DROPDOWN`). **Skip complex, object-valued fields** (e.g. the default booking form's multi-line `ADDRESS`) — sending a string for them fails `createBooking` with **"must be object"**. Only `first_name`/`last_name`/`email` are enforced, so skipping optional complex fields is safe. |
79
+ | Call `queryServices` without an `appId` filter | Always include `appId` (`13d21c63-…`) in the filter. List: `queryServices({ query:{ filter:{ appId }, paging }, conditionalFields:["STAFF_MEMBER_DETAILS"] }).find()`. Single: the `.eq("mainSlug.name", slug).eq("appId", …).limit(1).find()` builder chain. |
80
+ | Build `createClient({ auth: OAuthStrategy({ clientId: import.meta.env.* }) })` in `.astro` SSR | The `*_WIX_CLIENT_ID` env var is client-only → `undefined` at server render → 500. SSR reads use the ambient `@wix/essentials`: `auth.elevate(services.queryServices)(...)`. |
81
+ | Build an `OAuthStrategy` client inside an astro browser island | Astro islands call the `@wix` modules **ambiently** (the `@wix/astro` visitor client, like the ecom `CartView`) — no `createClient`. (Own/static builds DO use `OAuthStrategy` — `../custom/bookings/WIRING.md`.) |
82
+ | Pass UTC ISO strings to `listAvailabilityTimeSlots` | Use **local** date strings `YYYY-MM-DDThh:mm:ss` (no `Z`) + a separate `timeZone`. |
83
+ | `availability.queryAvailability` / `import { availability }` | APPOINTMENT → `availabilityTimeSlots.listAvailabilityTimeSlots({ serviceId: <string> })`; CLASS → `eventTimeSlots.listEventTimeSlots({ serviceIds: [<string>] })`. The `availability` namespace does not exist. |
84
+ | Read slot time from `timeSlot.slot.startDate` | Slot fields are at the **top level**: `localStartDate`, `localEndDate`, `scheduleId` (APPOINTMENT). CLASS slots carry `eventInfo.eventId` and have **no** `scheduleId`. |
85
+ | Dump every slot in one flat, time-only grid | Show a **week calendar**: a day strip → the picked day's times. A flat grid hides which day a slot is on. |
86
+ | Read service slug from `service.slug` / name from `service.info.name` | V2 is flat: slug `service.mainSlug.name` (fallback `supportedSlugs[0].name`), `service.name`, `service.description`, `service.tagLine`; duration `service.schedule.availabilityConstraints.sessionDurations[0]`; price `service.payment.fixed.price.value` (string); media `service.media.mainMedia.image.url`. |
87
+ | Use `service.id` / `result.booking.id` | Entity ids are `_id` (underscore): `service._id`, `booking._id`. |
88
+ | Mount the calendar/form without `client:only="react"` | Availability + booking are timezone/session-specific — always `client:only="react"`. SSR only the read pages (catalog/detail) for SEO. |
89
+ | Omit try/catch on the booking | `createBooking` can reject (e.g. a slot taken between fetch and submit, or strict phone validation). Catch and surface a friendly message; don't crash. |
90
+ | Ship a location selector, staff filter, payment breakdown, CLASS sign-up, or waitlist | Out of v1 — appointment booking only. |
@@ -0,0 +1,295 @@
1
+ # Bookings Seed — Services Data
2
+
3
+ You are seeding Wix Bookings services for a headless site. Your job:
4
+ 1. Mint a site-scoped token once.
5
+ 2. **If `intent.bookings.hasStaff` is `true`** — create staff members FIRST (Step 3 below), then pass their `resourceId`s on each service. **Every APPOINTMENT service needs a non-empty `staffMemberIds`** or it's rejected with `MISSING_APPOINTMENT_RESOURCES` — even when `hasStaff` is false you must pass the default **Business Owner** `resourceId` (see § "When hasStaff is false"). (CLASS services don't need a resource at create time.)
6
+ 3. Create `intent.bookings.serviceCount` services via the Bookings V2 REST API. When staff exist, pass their `resourceId` values via `staffMemberIds`.
7
+ 4. Return all created IDs in the standard return contract.
8
+
9
+ > **Order matters.** Steps 3 and 4 in this file are presented in the order you should execute them: staff first (when applicable), then services. The old "services-first then staff-optionally" order fails for APPOINTMENT with `MISSING_APPOINTMENT_RESOURCES`.
10
+
11
+ ---
12
+
13
+ ## Step 1 — Mint the token
14
+
15
+ ```bash
16
+ TOKEN=$(npx @wix/cli@latest token --site "<siteId>")
17
+ ```
18
+
19
+ Cache in subagent scratch. Every subsequent `curl` reuses it. Every call carries:
20
+ ```
21
+ Authorization: Bearer $TOKEN
22
+ wix-site-id: <siteId>
23
+ Content-Type: application/json
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Step 2 — Verify Wix Bookings is installed
29
+
30
+ Query staff members as an installation check. A successful response (even an empty list) confirms Bookings is installed:
31
+
32
+ ```bash
33
+ curl -sS -X POST \
34
+ -H "Authorization: Bearer $TOKEN" \
35
+ -H "wix-site-id: <siteId>" \
36
+ -H "Content-Type: application/json" \
37
+ -d '{"query":{}}' \
38
+ "https://www.wixapis.com/bookings/v1/staff-members/query"
39
+ ```
40
+
41
+ - **200 OK** → proceed.
42
+ - **403 / "Business schedule not found"** → Bookings app is not installed. Return `status: "error"` with the response verbatim. Do not attempt to install it — the orchestrator's Setup phase handles app installation.
43
+
44
+ ---
45
+
46
+ ## Step 3 — Create staff members (when `intent.bookings.hasStaff` is `true`)
47
+
48
+ Execute this step BEFORE Step 4 when staff are required. APPOINTMENT services need at least one resource to exist before they can be created.
49
+
50
+ For each staff member to create (derive names from the brand; default to 2 staff):
51
+
52
+ ```bash
53
+ # Build the payload — only include phone/email when non-empty.
54
+ # The V1 staff-members API rejects "" as an invalid email/phone.
55
+ curl -sS -X POST \
56
+ -H "Authorization: Bearer $TOKEN" \
57
+ -H "wix-site-id: <siteId>" \
58
+ -H "Content-Type: application/json" \
59
+ -d '{
60
+ "staffMember": {
61
+ "name": "<First Last>",
62
+ "description": "<professional bio — 1–2 sentences, brand-appropriate>"
63
+ }
64
+ }' \
65
+ "https://www.wixapis.com/bookings/v1/staff-members"
66
+ ```
67
+
68
+ > **Do NOT send `"phone": ""` or `"email": ""`** — V1 validates them as format-checked fields and rejects empty strings with `is not a valid email/phone`. Omit the keys entirely when you don't have a real value. If the merchant later wants to populate phone/email, they can do it from the dashboard or via a PATCH.
69
+
70
+ **Important:** Staff inherit business working hours at creation time. For a basic seed, this is fine — the merchant can configure custom hours from the dashboard later.
71
+
72
+ Save `staffMember.id` and `staffMember.resourceId` from each response. **You will pass the `resourceId` values into the `staffMemberIds` field on each service in Step 4.**
73
+
74
+ > **Note:** `staffMemberIds` on services expects `resourceId` values (from `staffMember.resourceId`), NOT `staffMember.id`. Using `staffMember.id` here is the most common cause of "service has no provider" runtime errors.
75
+
76
+ > **Do NOT attempt custom working hours setup during seed.** The two-step custom-hours workflow (assignWorkingHoursSchedule + WORKING_HOURS events) is complex and fragile. Default inherited hours are sufficient for an initial build. If you encounter "Business schedule not found", skip staff creation and return a warning in `notes`.
77
+
78
+ ### When `hasStaff` is `false`
79
+
80
+ For `serviceType: "CLASS"`, no staff/resource is needed at service-creation time — skip this step.
81
+
82
+ For `serviceType: "APPOINTMENT"` with `hasStaff: false`, you **still must pass a resource** in `staffMemberIds` — an empty `[]` is **rejected** with `MISSING_APPOINTMENT_RESOURCES` (*"service of type appointment requires at least one staff member or service resource"*). The Wix Bookings app provisions a default **Business Owner** resource at install; query it and pass **its** `resourceId`:
83
+
84
+ ```bash
85
+ # Get the default Business-Owner resourceId (no staff were created).
86
+ curl -sS -X POST -H "Authorization: Bearer $TOKEN" -H "wix-site-id: <siteId>" -H "Content-Type: application/json" \
87
+ -d '{"query":{}}' "https://www.wixapis.com/bookings/v1/staff-members/query"
88
+ # → take the staffMember named "Business Owner" and use its `resourceId` as the single
89
+ # entry in each APPOINTMENT service's `staffMemberIds`.
90
+ ```
91
+
92
+ (Do **not** rely on `[]` — the default resource does not auto-satisfy the requirement at create time. CLASS services don't need this.)
93
+
94
+ ---
95
+
96
+ ## Step 4 — Create services (Services V2 API)
97
+
98
+ **Endpoint:** `POST https://www.wixapis.com/_api/bookings/v2/services`
99
+
100
+ > **V2 payload is flat — NOT nested under `info`.** Fields are `name`, `description`, `tagLine` at the top level of the `service` object. The V1 shape (`info.name`, `info.description`) is rejected by V2. Price uses `value`, not `amount`.
101
+
102
+ For each service to create (derive names, descriptions, and prices from `brand` + `intent.bookings`):
103
+
104
+ ```bash
105
+ curl -sS -X POST \
106
+ -H "Authorization: Bearer $TOKEN" \
107
+ -H "wix-site-id: <siteId>" \
108
+ -H "Content-Type: application/json" \
109
+ -d '{
110
+ "service": {
111
+ "type": "<APPOINTMENT|CLASS>",
112
+ "name": "<service name>",
113
+ "description": "<brand-appropriate description>",
114
+ "tagLine": "<short tagline>",
115
+ "defaultCapacity": 1,
116
+ "onlineBooking": {
117
+ "enabled": true,
118
+ "requireManualApproval": false,
119
+ "allowMultipleRequests": false
120
+ },
121
+ "schedule": {
122
+ "availabilityConstraints": {
123
+ "sessionDurations": [<duration as integer, e.g. 60>]
124
+ }
125
+ },
126
+ "payment": {
127
+ "rateType": "FIXED",
128
+ "fixed": {
129
+ "price": {
130
+ "value": "<price as string, e.g. \"75.00\">",
131
+ "currency": "USD"
132
+ }
133
+ },
134
+ "options": {
135
+ "online": true,
136
+ "inPerson": false,
137
+ "deposit": false,
138
+ "pricingPlan": false
139
+ }
140
+ },
141
+ "locations": [
142
+ {
143
+ "type": "BUSINESS"
144
+ }
145
+ ],
146
+ "staffMemberIds": [<resourceId values — staff from Step 3, OR the default Business-Owner resourceId when hasStaff is false. NEVER [] for APPOINTMENT — it 400s with MISSING_APPOINTMENT_RESOURCES>]
147
+ }
148
+ }' \
149
+ "https://www.wixapis.com/_api/bookings/v2/services"
150
+ ```
151
+
152
+ > **`locations.type` enum:** the valid values are `UNKNOWN_LOCATION_TYPE`, `CUSTOM`, `BUSINESS`, and `CUSTOMER`. Use `"BUSINESS"` for "at the business address" (the seed default). Do NOT use `"OWNER_BUSINESS"` here — that string IS valid for `createBooking.bookedEntity.slot.location.locationType` on the bookings endpoint, but the **services** endpoint rejects it. Same field name, different enum.
153
+
154
+ > **`staffMemberIds`:** pass `resourceId` values (not staffMember `id` values). For APPOINTMENT this array must be **non-empty** — when `hasStaff` is `false`, pass the default **Business Owner** `resourceId` (query it per § "When hasStaff is false"); `[]` is rejected with `MISSING_APPOINTMENT_RESOURCES` (verified live). CLASS services don't require it.
155
+
156
+ **Response shape:**
157
+ ```json
158
+ {
159
+ "service": {
160
+ "id": "<uuid>",
161
+ "name": "<name>",
162
+ "description": "<description>",
163
+ "mainSlug": { "name": "<url-slug>", "custom": false },
164
+ "supportedSlugs": [{ "name": "<url-slug>", "custom": false }],
165
+ "type": "APPOINTMENT",
166
+ "payment": { ... },
167
+ "schedule": { ... }
168
+ }
169
+ }
170
+ ```
171
+
172
+ > **Slug:** Extract from `service.mainSlug.name`. If absent, derive from the service name: lowercase, replace spaces and non-alphanumeric chars with hyphens, deduplicate hyphens.
173
+
174
+ ### Service creation guidelines
175
+
176
+ - **Count**: Create exactly `intent.bookings.serviceCount` services (default 3 when not specified).
177
+ - **Type**: Use `"APPOINTMENT"` for 1-on-1 services (the default); use `"CLASS"` when `intent.bookings.serviceType === "CLASS"`. For `CLASS`, set `defaultCapacity` to the max participants (e.g. `20`) instead of `1`.
178
+
179
+ > **⚠️ CLASS services need scheduled sessions before anyone can sign up.** Creating a CLASS service does **not** create any bookable sessions. The front-end lists bookable sessions via `eventTimeSlots.listEventTimeSlots()`, which returns scheduled **session events** — a freshly created CLASS service has none, so its calendar is permanently empty. **For CLASS services you MUST run Step 4b below** to schedule sessions; otherwise the class calendar is a dead end. (APPOINTMENT services don't need this — their bookable times come from staff working hours + `availabilityTimeSlots`.)
180
+ - **`sessionDurations`**: Required for APPOINTMENT. An array containing one integer (minutes). Do NOT specify for CLASS or COURSE services.
181
+ - **Names + descriptions**: Derive from `brand.description`. Examples: a yoga studio → "60-min Vinyasa Flow" (60 min), "30-min Morning Meditation" (30 min), "90-min Deep Restore" (90 min). A hair salon → "Women's Cut & Style" (60 min, $85), "Men's Haircut" (30 min, $45), "Balayage Color" (120 min, $150). Make them brand-appropriate — not generic.
182
+ - **Duration** (integers in minutes): Brand-appropriate. Consultation → 30; standard service → 60; premium/complex → 90–120.
183
+ - **Price `value`**: A string, not a number. Brand-appropriate, non-trivial. A budget studio might charge `"25.00"`; a premium clinic `"250.00"`. Default to mid-range when unclear.
184
+ - **Currency**: Set `"USD"` unless the brand's locale implies otherwise — **but note the site's business currency wins**: Services V2 silently stores the site-locale currency (e.g. a EUR-locale site stores `EUR` even when you send `USD`). This is not an error; the page templates format from the service's returned `currency`, so display stays correct. Don't fight it.
185
+ - **Fire all service creates as a parallel batch** — they are independent calls.
186
+
187
+ ### Required fields summary (V2)
188
+
189
+ | Field | Required | Notes |
190
+ |-------|----------|-------|
191
+ | `type` | Yes | `APPOINTMENT`, `CLASS`, or `COURSE` |
192
+ | `name` | Yes | Display name |
193
+ | `defaultCapacity` | Yes | `1` for APPOINTMENT; participant count for CLASS |
194
+ | `onlineBooking.enabled` | Yes | Set to `true` for online booking |
195
+ | `payment.rateType` | Yes | `FIXED`, `NO_FEE`, `VARIED`, or `CUSTOM` |
196
+ | `payment.options.online` or `payment.options.inPerson` | Yes | At least one must be `true` |
197
+ | `schedule.availabilityConstraints.sessionDurations` | APPOINTMENT only | Array with one integer (minutes) |
198
+
199
+ ---
200
+
201
+ ## Step 4b — Create class sessions (CLASS services only)
202
+
203
+ A CLASS service is bookable only once its **schedule** has scheduled session events. Sessions are **Calendar Events V3** (not part of `@wix/bookings`), created via REST. Run this for every service you created with `type: "CLASS"`; skip it entirely for APPOINTMENT.
204
+
205
+ **Endpoint:** `POST https://www.wixapis.com/calendar/v3/events`
206
+
207
+ You need two things from earlier steps:
208
+ - the service's **schedule id** — `service.schedule.id` from the Step 4 create response (every service has one);
209
+ - a **resource id** — a CLASS event requires **at least one resource** (`resources` non-empty) or it's rejected. Use a staff `resourceId` from Step 3; if no staff were created, use the default Business-Owner resource's `resourceId` (query `POST /bookings/v1/staff-members/query` with `{"query":{}}` and take the one named `Business Owner`).
210
+
211
+ ```bash
212
+ # One session. Repeat for several upcoming dates per class (e.g. 3 over the next
213
+ # ~2 weeks) so the calendar isn't empty. `localDate` is local time, no `Z`.
214
+ curl -sS -X POST \
215
+ -H "Authorization: Bearer $TOKEN" -H "wix-site-id: <siteId>" -H "Content-Type: application/json" \
216
+ -d '{
217
+ "event": {
218
+ "scheduleId": "<service.schedule.id>",
219
+ "type": "CLASS",
220
+ "start": { "localDate": "2026-06-05T09:00:00" },
221
+ "end": { "localDate": "2026-06-05T10:00:00" },
222
+ "resources": [ { "id": "<staff resourceId>", "permissionRole": "WRITER" } ],
223
+ "totalCapacity": 12
224
+ }
225
+ }' \
226
+ "https://www.wixapis.com/calendar/v3/events"
227
+ ```
228
+
229
+ > **Gotchas (verified against the live API):**
230
+ > - `resources[].permissionRole` **must** be `"WRITER"` (or `"COMMENTER"`). Omitting it defaults to `UNKNOWN_ROLE` → `400 "resources.permissionRole must not be UNKNOWN_ROLE"`.
231
+ > - `resources` must be **non-empty** for CLASS — a session with no resource is rejected.
232
+ > - `start`/`end` use `{ "localDate": "YYYY-MM-DDThh:mm:ss" }` (local, no `Z`); seconds are ignored.
233
+ > - For a recurring weekly schedule instead of one-off sessions, add `event.recurrenceRule` (`{ "frequency": "WEEKLY", "interval": 1, "days": ["MONDAY"], "until": { "localDate": "..." } }` — only `WEEKLY` and a **single** day are supported; this creates a `MASTER` event and Wix auto-generates the instances). One-off sessions are simpler and sufficient for a seed.
234
+ > - `totalCapacity` defaults to the schedule's `defaultCapacity` if omitted; set it explicitly to the class size.
235
+
236
+ Verify by fetching slots the way the front-end does (`eventTimeSlots.listEventTimeSlots({ serviceIds: [id], … })`) or `POST /calendar/v3/events/query` filtered by `scheduleId`. Record each session's event id in your return under `seeded.bookings.services[].sessionEventIds` if you want Phase 4 to deep-link.
237
+
238
+ > **Booking-policy toggles (optional, CLASS).** Both live on the default booking policy — get it once via `POST /bookings/v1/booking-policies/query` with body `{"query":{}}` (an empty `{}` body is rejected with `query must not be empty`), then `PATCH /bookings/v1/booking-policies/<id>` with the relevant block + `fieldMask`:
239
+ > - **Waitlist** (so a full session offers "join waitlist"): `"waitlistPolicy": { "enabled": true, "capacity": 10, "reservationTimeInMinutes": 10 }`, `fieldMask.paths: ["waitlistPolicy"]`.
240
+ > - **Multi-participant** (party size > 1): `"participantsPolicy": { "maxParticipantsPerBooking": <N> }`, `fieldMask.paths: ["participantsPolicy"]`. **Defaults to `1`** — without raising it, the front-end party-size selector lets the user pick >1 but `createBooking` rejects it. Raise it (e.g. `4`) to actually enable multi-participant class booking.
241
+ >
242
+ > Skip whichever the build doesn't surface.
243
+
244
+ ---
245
+
246
+ ## Step 5 — Return contract
247
+
248
+ **Return this JSON inline as your agent return** (per `references/shared/RETURN_CONTRACT.md`) — do **NOT** write a `.wix/seed-returns/` file; the orchestrator aggregates seeder returns and is the sole writer of `.wix/seeded.json`:
249
+
250
+ ```json
251
+ {
252
+ "phase": "seed-bookings",
253
+ "status": "ok",
254
+ "seeded": {
255
+ "services": [
256
+ {
257
+ "id": "<uuid>",
258
+ "slug": "<mainSlug.name>",
259
+ "name": "<name>",
260
+ "type": "APPOINTMENT",
261
+ "durationMinutes": 60,
262
+ "price": "75.00",
263
+ "currency": "USD"
264
+ }
265
+ ],
266
+ "staff": [
267
+ { "id": "<uuid>", "resourceId": "<uuid>", "name": "<name>" }
268
+ ]
269
+ },
270
+ "recipeCalls": [
271
+ { "url": "https://www.wixapis.com/_api/bookings/v2/services", "status": 200 }
272
+ ]
273
+ }
274
+ ```
275
+
276
+ - `staff` is an empty array `[]` when `intent.bookings.hasStaff` is false or Step 3 was skipped.
277
+ - **For `CLASS` services, schedule sessions in Step 4b** and report them (e.g. `seeded.bookings.services[].sessionEventIds`). Only if Step 4b is skipped or fails, add a `notes` entry so the orchestrator surfaces it: `"notes": ["CLASS sessions not scheduled — add session times in the Bookings dashboard before sign-up works."]`
278
+ - On any REST error: set `status: "error"`, include the failing call's response verbatim under `"error"`.
279
+
280
+ ---
281
+
282
+ ## Common failure modes
283
+
284
+ | Failure | Recovery |
285
+ |---------|----------|
286
+ | 403 on service create | Re-mint token and retry once. If still 403, Bookings app was not installed — return `status: "error"`. |
287
+ | 400 `"defaultCapacity is required"` | Add `"defaultCapacity": 1` to the payload (required in V2, not obvious from V1 docs). |
288
+ | 400 `"onlineBooking is required"` | Add `"onlineBooking": { "enabled": true }` — required in V2. |
289
+ | 400 on `payment.options` | At least one of `online` or `inPerson` must be `true`. Set `"inPerson": true` as fallback. |
290
+ | 400 `"sessionDurations is required"` | Add `"schedule": { "availabilityConstraints": { "sessionDurations": [60] } }` for APPOINTMENT types. |
291
+ | 400 `MISSING_APPOINTMENT_RESOURCES` on service create | An APPOINTMENT service's `staffMemberIds` must be **non-empty**. Pass the created staff `resourceId`s (Step 3), or — when `hasStaff` is false — the default **Business Owner** `resourceId` (query `/bookings/v1/staff-members/query` with `{"query":{}}`). An empty `[]` always 400s here. |
292
+ | 400 enum error on `locations.type` | Use `"BUSINESS"`, not `"OWNER_BUSINESS"`. The services endpoint accepts `UNKNOWN_LOCATION_TYPE`, `CUSTOM`, `BUSINESS`, `CUSTOMER`. `OWNER_BUSINESS` is valid on `createBooking.bookedEntity.slot.location.locationType` only. |
293
+ | 400 `is not a valid email/phone` on staff create | Don't send `"phone": ""` or `"email": ""`. Omit the keys entirely when you don't have a real value. |
294
+ | `mainSlug` absent in response | Derive slug from `service.name`: lowercase, replace spaces with hyphens, strip non-alphanumeric. |
295
+ | Staff create returns "Business schedule not found" | Skip staff creation. Return partial seeded data with `notes: ["Staff creation skipped — business schedule not found; configure via Bookings dashboard"]`. |
@@ -50,7 +50,7 @@ body: {
50
50
  }
51
51
  ```
52
52
 
53
- The response returns the created item's `_id` — collect these for the Phase 1 return contract.
53
+ The response returns the created item's `_id` — collect these for the seed return contract.
54
54
 
55
55
  **Every text field must be populated in the `data` object.** Do not create items with empty `data: {}` and expect Phase 2 or the image agent to fill in text fields — they only handle their own scope (pages and images respectively).
56
56
 
@@ -70,6 +70,8 @@ body: {
70
70
  }
71
71
  ```
72
72
 
73
+ > **Bulk insert (N ≥ 2 items):** use the `wix-manage` skill's recipe — `wix-manage/references/cms/cms-data-items-crud.md` § "Bulk Insert Items" (`POST /wix-data/v2/bulk/items/insert`, up to 1000/call, one `dataCollectionId` per call → fire one bulk call per collection as a parallel batch). It also documents bulk update/patch/delete. Do not hand-roll N single inserts for a multi-item seed.
74
+
73
75
  ### Verify inserts with a live query (mandatory)
74
76
 
75
77
  After inserting all items, **query each collection once** and confirm every field you sent is present in the stored `data`. A POST without errors does NOT prove the content persisted — the API has accepted insert bodies with missing fields before, and the failure is invisible until a human opens the page.
@@ -140,7 +142,7 @@ Emit a structured JSON block at the end of your completion message per `../share
140
142
  {
141
143
  "status": "complete",
142
144
  "phase": "cms-seed",
143
- "scope": "seed-seed",
145
+ "scope": "seed",
144
146
  "summary": "Created {N} collections; seeded {M} items; verified every field persisted",
145
147
  "data": {
146
148
  "collections": [
@@ -5,7 +5,7 @@ description: "Implements structured content pages (About, FAQ, portfolios, team
5
5
 
6
6
  # CMS Implementer
7
7
 
8
- Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, prompt-inlined inputs (no site.json reads), return contract, style conventions, and common failure modes.
8
+ Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, prompt-inlined inputs (read only your `.wix/seeded.json` slice), return contract, style conventions, and common failure modes.
9
9
 
10
10
  ## Scope routing
11
11
 
@@ -29,6 +29,32 @@ Pick based on the business type (the orchestrator names one in your prompt):
29
29
 
30
30
  See `<SKILL_ROOT>/references/verticals/cms.md` frontmatter.
31
31
 
32
+ ## Seed return
33
+
34
+ The `seed` scope emits this `data` shape in its return JSON (envelope rules in `../shared/RETURN_CONTRACT.md`). Phase 4 CMS page agents reference these collection names; the image agent attaches entity images to CMS items by ID.
35
+
36
+ ```json
37
+ {
38
+ "status": "complete",
39
+ "phase": "cms-seed",
40
+ "scope": "seed",
41
+ "data": {
42
+ "collections": [
43
+ {
44
+ "name": "about-content",
45
+ "itemIds": ["665f3363-..."],
46
+ "fields": ["heading", "body", "image"]
47
+ },
48
+ {
49
+ "name": "faq",
50
+ "itemIds": ["abc", "def", "ghi", "jkl", "mno", "pqr"],
51
+ "fields": ["question", "answer", "sortOrder"]
52
+ }
53
+ ]
54
+ }
55
+ }
56
+ ```
57
+
32
58
  ## Page width (FAQ, About, long-form CMS)
33
59
 
34
60
  Read `references/shared/STYLING.md` § "Prose / reading width". **Do not** wrap FAQ/About body copy in `max-w-3xl` unless `--container-3xl` exists in `src/styles/global.css` `@theme`. Prefer `container-reading`, `max-w-6xl` (when `--container-6xl` is defined), or `max-w-[48rem]`. A bare `max-w-3xl` with only a spacing scale ships a ~80px column.