@wix/agent-skills 1.19.2 → 1.20.1

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 (215) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/references/SERVICE_PLUGIN.md +14 -1
  3. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +7 -6
  4. package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
  5. package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
  6. package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
  7. package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
  8. package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
  9. package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
  10. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
  11. package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
  12. package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
  13. package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
  14. package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
  15. package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
  16. package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
  17. package/skills/wix-headless/references/CAPABILITIES.md +2 -2
  18. package/skills/wix-headless/references/inline-recipes/experience-store.md +4 -3
  19. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +98 -18
  20. package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
  21. package/skills/wix-headless-fast/SKILL.md +200 -34
  22. package/skills/wix-headless-fast/entry/skill.md +10 -2
  23. package/skills/wix-headless-fast/install/attach.mjs +253 -0
  24. package/skills/wix-headless-fast/install/deploy.mjs +109 -9
  25. package/skills/wix-headless-fast/install/fast-path.mjs +28 -31
  26. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
  27. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
  28. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
  29. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
  30. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
  31. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
  32. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
  33. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
  34. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
  35. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
  36. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
  37. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
  38. package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
  39. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
  40. package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
  41. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
  42. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
  43. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
  44. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
  45. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
  46. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
  47. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
  48. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
  49. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
  50. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
  51. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
  52. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
  53. package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
  54. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
  55. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
  56. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
  57. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
  58. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
  59. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
  60. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
  61. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
  62. package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
  63. package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
  64. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
  65. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
  66. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
  67. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
  68. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
  69. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
  70. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
  71. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
  72. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
  73. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
  74. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
  75. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
  76. package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
  77. package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
  78. package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
  79. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
  80. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
  81. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
  82. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
  83. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
  84. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
  85. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
  86. package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
  87. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
  88. package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
  89. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
  90. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
  91. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
  92. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
  93. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
  94. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
  95. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
  96. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
  97. package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
  98. package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
  99. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
  100. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
  101. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
  102. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
  103. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
  104. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
  105. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
  106. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
  107. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
  108. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
  109. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
  110. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
  111. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
  112. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
  113. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
  114. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
  115. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
  116. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
  117. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
  118. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
  119. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
  120. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
  121. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
  122. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
  123. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
  124. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
  125. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
  126. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
  127. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
  128. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
  129. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
  130. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
  131. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
  132. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
  133. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
  134. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
  135. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
  136. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
  137. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
  138. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
  139. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
  140. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
  141. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
  142. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
  143. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
  144. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
  145. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
  146. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
  147. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +55 -1
  148. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
  149. package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
  150. package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
  151. package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
  152. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +371 -81
  153. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
  154. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
  155. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
  156. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +136 -0
  157. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -130
  158. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -182
  159. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
  160. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +53 -136
  161. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
  162. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +73 -230
  163. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
  164. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
  165. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
  166. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
  167. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
  168. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
  169. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -5
  170. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +24 -0
  171. package/skills/wix-manage/SKILL.md +4 -1
  172. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  173. package/skills/wix-manage/references/events/create-wix-event.md +46 -0
  174. package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
  175. package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
  176. package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
  177. package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
  178. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
  179. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
  180. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
  181. package/skills/wix-vibe-headless/platforms/base44.md +19 -4
  182. package/skills/wix-vibe-headless/platforms/generic.md +6 -0
  183. package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
  184. package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
  185. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
  186. package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
  187. package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
  188. package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
  189. package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
  190. package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
  191. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
  192. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
  193. package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
  194. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
  195. package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
  196. package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
  197. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
  198. package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
  199. package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
  200. package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
  201. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
  202. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
  203. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
  204. package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
  205. package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
  206. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
  207. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
  208. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
  209. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
  210. package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
  211. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +42 -41
  212. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +7 -1
  213. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
  214. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
  215. package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
@@ -0,0 +1,45 @@
1
+ # Realtime Permissions Provider Service Plugin Reference
2
+
3
+ ## Overview
4
+
5
+ The Realtime Permissions Provider SPI controls who can receive messages on your app's [Wix Realtime](https://dev.wix.com/docs/sdk/core-modules/realtime/realtime/introduction) channels. When a subscriber from another app attempts a cross-app subscription to one of your channels, Wix calls your `checkSubscriberPermissions` handler and waits synchronously for the answer. Without a registered provider, Wix denies all cross-app subscription attempts by default.
6
+
7
+ This is the only SPI outside eCommerce and Bookings — it belongs to the Realtime module.
8
+
9
+ ## Request and Response Schema
10
+
11
+ Before implementing, call `ReadFullDocsMethodSchema` on the docs URL to get the full request/response types.
12
+
13
+ | Handler | Docs URL |
14
+ | --- | --- |
15
+ | `checkSubscriberPermissions` | https://dev.wix.com/docs/sdk/core-modules/realtime/extensions/realtime-permissions-provider/check-subscriber-permissions |
16
+
17
+ The request gives you the `channel` (`name` + optional `resourceId`), the `subscriber` (`type`: `ADMIN` / `MEMBER` / `VISITOR`, and `_id`), and `requestingAppId` (the app attempting the subscription, may be empty). Return `{ read: true }` to allow, `{ read: false }` to deny.
18
+
19
+ ## Example: Allow Members and Admins, Deny Visitors
20
+
21
+ ```typescript
22
+ import { realtimePermissionsProvider } from "@wix/realtime/service-plugins";
23
+
24
+ realtimePermissionsProvider.provideHandlers({
25
+ checkSubscriberPermissions: async (payload) => {
26
+ const { request } = payload;
27
+ const { subscriber, channel } = request;
28
+
29
+ if (channel?.name === "public-updates") {
30
+ return { read: true };
31
+ }
32
+
33
+ return { read: subscriber?.type !== "VISITOR" };
34
+ },
35
+ });
36
+ ```
37
+
38
+ ## Key Implementation Notes
39
+
40
+ 1. **The generated stub may include a `write` field — remove it.** `wix generate` for this `pluginType` has been observed scaffolding a response of `{ read: true, write: false }`, but `CheckSubscriberPermissionsResponse` only has `read?: boolean`. Confirm against the installed `@wix/auto_sdk_duplexer_realtime-permissions-provider` types if `tsc` doesn't catch it for you.
41
+ 2. **`channel` and `subscriber` are both optional on the request** — `channel?: Channel`, `subscriber?: Subscriber`. Accessing `.name`/`.type` without the `?.` above fails `tsc` with "possibly undefined."
42
+ 3. **Deny with data, not errors** — return `{ read: false }` for normal policy denials. Only throw `InvalidArgumentError` when the request itself is invalid or missing data; any other thrown error, timeout, or unreachable service makes Wix deny the subscription with a 503.
43
+ 4. **Respond quickly** — the call is synchronous and blocks the subscription attempt; a slow handler delays it, and a non-responsive one causes a 503 denial.
44
+ 5. **Be consistent** — the same channel, subscriber, and context should always yield the same decision.
45
+ 6. **No provider means no cross-app access** — until you implement this SPI, all cross-app subscription attempts to your channels are denied by default.
@@ -53,6 +53,21 @@ shippingRates.provideHandlers({
53
53
  });
54
54
  ```
55
55
 
56
+ ## ⚠️ Manual setup required — installing the app isn't enough
57
+
58
+ Like Additional Fees and Payment Settings, this plugin only supplies rates when Wix actually asks it to. Unlike those two, **it isn't asked automatically** — the merchant must explicitly turn your app on as a delivery carrier for a shipping region before Wix calls `getShippingRates` for it. Tell the merchant to do this in the dashboard (confirmed live, click-by-click):
59
+
60
+ 1. Go to **Settings** (left sidebar) → under **Business solutions**, click **Shipping, delivery & fulfillment**.
61
+ 2. Find the region to enable the carrier for (e.g. **Domestic** or **International**) and click **Manage Your Apps** in that region's header.
62
+ 3. In the **"Manage your installed apps"** dialog, check the box next to your app's name.
63
+ 4. A **backup rate** section appears (checked by default) with a **Shipping name** and **Rate at checkout** field — this is what Wix falls back to if your plugin errors or times out. Fill these in (or leave the defaults) — Wix requires a backup rate here, same as the API's `backupRate`.
64
+ 5. Optionally check **Add a handling fee to every order**.
65
+ 6. Click **Save**.
66
+
67
+ Confirmed live: enabling the carrier this way registers it with no errors. Whichever specific rate a given customer sees selected by default (this carrier's vs. another installed one's) is resolved at checkout-page render time, not by the Cart `calculate`/`refresh` endpoints — verify the actual customer-facing rate on the live checkout page, not from a raw API response alone.
68
+
69
+ (The same thing can be done via the [Add Delivery Carrier](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/shipping-delivery/delivery-profiles/add-delivery-carrier) API if you're scripting site setup yourself, but for a merchant using your app, send them to the dashboard steps above — not a raw API call.)
70
+
56
71
  ## Singular Constraint
57
72
 
58
73
  `ECOM_SHIPPING_RATES` is **singular** — only one component of this type is allowed per app. Do not scaffold or include two Shipping Rates service plugins in the same app.
@@ -45,6 +45,10 @@ toolsProvider.provideHandlers({
45
45
  });
46
46
  ```
47
47
 
48
+ ## Manual Setup Required
49
+
50
+ This handler is only invoked by the real Wix AI assistant deciding a tool is relevant — there's no direct API to force-invoke a specific tool for testing. Two things must both be true before it can fire at all: (1) a paired `APP_TOOLS` extension declares the tool with `activated: true` (see [APP_TOOLS.md](../APP_TOOLS.md)), and (2) a user actually asks the AI assistant something the tool's `description` matches. Verify by asking the assistant a matching question on the live site, not by calling this handler directly.
51
+
48
52
  ## Key Implementation Notes
49
53
 
50
54
  1. **Route on `methodName`** — use a `switch` or map to dispatch to the right handler logic
@@ -43,6 +43,10 @@ validations.provideHandlers({
43
43
  });
44
44
  ```
45
45
 
46
+ ## Manual Setup Required
47
+
48
+ None — confirmed live. Once the app is installed and released, Wix calls this plugin automatically on every cart/checkout calculation; no dashboard configuration or extra API call is needed to "turn it on."
49
+
46
50
  ## Key Implementation Notes
47
51
 
48
52
  1. **Return empty array when valid** - Return `{ violations: [] }` when no validation issues
@@ -26,10 +26,10 @@ The verticals the skill operates end-to-end today: **stores · blog · cms · fo
26
26
  - **Required site features:** a homepage that sells (what the store offers, real products, one clear shopping action); a shop / category gallery with sort, the filters the catalog supports, and paging; a page per product; categories to browse by; a cart the shopper can review and edit, opening as a side drawer after every add; checkout reachable directly from that cart.
27
27
  - **Implementation checklist:**
28
28
  - *Homepage:* say what the store sells in the first screen; real products from the catalog under truthful headings — never a repeat of the shop page, never a heading like "Best Sellers" without data behind it.
29
- - *Gallery:* a real product — image, name, price, link — visible in the first screen before any scrolling; a result count; loading, empty-catalog, no-results-for-these-filters, and error states that look different from each other.
29
+ - *Gallery:* a real product — image, name, price, link — visible in the first screen before any scrolling; a result count; a filter panel with price, stock, and the option facets the catalog has (required whenever there is something to filter); a way to buy on every card (one click without options, a quick-add picker with options); loading, empty-catalog, no-results-for-these-filters, and error states that look different from each other.
30
30
  - *Product page:* in the first screen, the product image, name, price, the first choice to make, and the buy button — disabled with a plain reason until every choice is made; every ribbon the merchant set; the current price and, when there is one, the labelled original price; a gallery of every product image; options with unavailable choices marked; quantity; the full description and information sections; pre-order, subscriptions, and notify-me only when the catalog has them.
31
31
  - *Cart:* each line's image, name, chosen options, quantity (editable) and price; subtotal and discounts as Wix calculates them; "shipping and taxes calculated at checkout" rather than invented numbers; survives a reload; checkout from the drawer.
32
- - *Everywhere:* availability / out-of-stock shown truthfully; each product linked to its category; no reviews, ratings, scarcity, or delivery promises the merchant didn't supply; no Wix IDs or technical words in shopper-facing text.
32
+ - *Everywhere:* availability / out-of-stock shown truthfully; each product linked to its category, and each category a page at its own URL linked from the navigation; no reviews, ratings, scarcity, or delivery promises the merchant didn't supply; no Wix IDs or technical words in shopper-facing text.
33
33
 
34
34
  ### blog — publish posts
35
35
  - **Intent:** blog / posts / articles / publication / news.
@@ -26,9 +26,10 @@ Two stores with the same vertical and different catalogs should look nothing ali
26
26
 
27
27
  - **Global shell:** category navigation from the live tree (a curated menu is fine, but every product-bearing category stays reachable); a persistent cart control with a live count; a footer sized to the real destinations. Announcement, shipping, and returns text only from the merchant.
28
28
  - **Homepage — a merchandising surface, not the shop page again.** Say what the store sells and give one dominant shopping action in the first viewport; real products from live queries under truthful headings; category discovery when the taxonomy is meaningful. Don't reuse the same first product as hero, first card, and feature. No section exists to fill space — a claim-dependent module with no truthful source is omitted.
29
- - **Gallery — a discovery surface.** Title, result count, sort, the filters the catalog supports, paging; in its default state a **real product card — image, name, price, link — is inside the first viewport**, also on a short desktop window. Skeletons while loading, a distinct "no results for these filters" with a reset, an honest empty catalog, a recoverable error. Filters that stay visible with the results commit immediately; filters in a sheet stage changes until Apply.
30
- - **Product page — a complete purchase decision.** In the first viewport at mobile, tablet, and desktop: a recognizable product image, name, price, the first required choice (or a control that jumps to it), and the buy action with its neutral disabled reason. Then every ribbon, the full description and info sections, a gallery of **every** image, quantity, and — when the catalog has them — subscriptions, preorder, group navigation, notify-me. A sticky buy region must never cover a control.
31
- - **Cart — a side drawer by default**, opened after every successful add and from the header: image, name, the chosen options and text, quantity editing with pending and failure states, line prices, the calculated subtotal and discount, "shipping and taxes calculated at checkout", and checkout **from the drawer**. Persists across navigation and refresh. A separate cart page is optional, never a mandatory step before checkout.
29
+ - **Gallery — a discovery surface.** Title, result count, sort, a filter panel, paging, and a buy path on every card. The filter panel — price, stock, and the option facets the catalog has (Color as swatches) — is **required whenever the catalog has anything to filter**; it is not polish to add if the brief mentions it. Every card offers a way to buy: one click for a product with no options, a quick-add picker for one with options, the product page only when the product needs free text. In its default state a **real product card — image, name, price, link — is inside the first viewport**, also on a short desktop window. Skeletons while loading, a distinct "no results for these filters" with a reset, an honest empty catalog, a recoverable error. Filters that stay visible with the results commit immediately; filters in a sheet stage changes until Apply. Cards hold their hierarchy under the catalog's longest name and a ranged price: name and price on separate lines, the price never clipped, checked at phone width; a color option may show as swatches on the card when there is room.
30
+ - **Categories are pages.** Every product-bearing category has its own URL (`/category/<slug>`), linked from the navigation — a shopper can share it and a search engine can index it. A category that exists only as a filter toggle on the shop page is not enough.
31
+ - **Product page — a complete purchase decision.** In the first viewport at mobile, tablet, and desktop: a recognizable product image, name, price, the first required choice (or a control that jumps to it), and the buy action with its neutral disabled reason. On a phone this decides the layout: the image is a bounded band, not a full-screen hero that pushes the price and the button below the fold; description and details come after the action. Then every ribbon, the full description and info sections, a gallery of **every** image, quantity, and — when the catalog has them — subscriptions, preorder, group navigation, notify-me. A sticky buy region must never cover a control.
32
+ - **Cart — a side drawer by default**, opened after every successful add and from the header: image, name, the chosen options and text, the subscription plan and its billing terms when the line is one, quantity editing with pending and failure states, line prices, the calculated subtotal and discount, "shipping and taxes calculated at checkout", and checkout **from the drawer**. Persists across navigation and refresh. A separate cart page is optional, never a mandatory step before checkout.
32
33
  - **Quick add** anchors to its card (an inline or attached panel; a bottom sheet on mobile); a centered dialog is the exception for a demonstrably large configuration, not the default. Its first view shows identity, price, the first required choice, and the action.
33
34
 
34
35
  ## 3 · Overlays (cart drawer, quick add, mobile nav, filter sheet)
@@ -107,7 +107,8 @@ variant = {
107
107
  }
108
108
 
109
109
  // currentCartV2.getCurrentCart() → { cart: { _id, lineItems: [...] } } // NOTE: returns { cart } — destructure it
110
- lineItem = { _id, name: { original }, quantityInfo: { confirmedQuantity, availableQuantity }, pricing: { unitPrice: { amount, convertedAmount }, totalPrice: {...} }, attributes: { image, descriptionLines }, status }
110
+ lineItem = { _id, name: { original }, quantityInfo: { confirmedQuantity, availableQuantity }, pricing: { unitPrice: { amount, convertedAmount }, totalPrice: {...} }, attributes: { image, descriptionLines }, status,
111
+ subscriptionInfo: { title: { original }, subscriptionSettings: { frequency /* DAY|WEEK|MONTH|YEAR */, interval, billingCycles, autoRenewal } } } // present on a subscription line only
111
112
  // price → pricing.unitPrice (ConvertedMoney, NO formatted string in V2 — format it yourself); qty → quantityInfo.confirmedQuantity; image → attributes.image (wix:image:// → resolve)
112
113
 
113
114
  // the cart's _id is the checkout id → pass to the redirect session:
@@ -150,6 +151,8 @@ if (categoryId) conditions.push({ 'allCategoriesInfo.categories': { $matchItems:
150
151
  if (minPrice != null) conditions.push({ 'actualPriceRange.minValue.amount': { $gte: String(minPrice) } });
151
152
  if (maxPrice != null) conditions.push({ 'actualPriceRange.minValue.amount': { $lte: String(maxPrice) } });
152
153
  if (inStockOnly) conditions.push({ 'inventory.availabilityStatus': { $eq: 'IN_STOCK' } });
154
+ // Option facets (Color, Size…) — selected choice IDs; matches products carrying ANY of them:
155
+ if (choiceIds.length) conditions.push({ 'options.choicesSettings.choices.choiceId': { $hasSome: choiceIds } });
153
156
  const filter = { $and: conditions };
154
157
 
155
158
  const SORTS = {
@@ -175,6 +178,7 @@ const { count } = await productsV3.countProducts({ filter }); // the "N produc
175
178
  ```
176
179
 
177
180
  - **Any change to category, sort, filters, or search starts over without a cursor.** Reset paging; ignore responses from a superseded query (version or abort in-flight requests so a late older response can't roll back a newer selection).
181
+ - **The filter panel is part of the gallery, not an option** — whenever the catalog has anything to filter (more than a handful of products, prices that differ, an option like Color or Size), the gallery ships sort, price bounds, in-stock, and the option facets, plus the result `count`, active-filter chips, and a clear-all. Build the facets from the scope's own products (one 100-product `searchProducts` with the same category condition; collect each `options[].name` with its visible `choicesSettings.choices[]` — `choiceId`, `name`, `colorCode`; keep facets with more than one choice) so the panel never offers a filter nothing matches; a `SWATCH_CHOICES`/`COLOR_CHOICES` facet renders as swatches. Filters that stay visible beside the results commit immediately; filters in a sheet (small screens) stage changes until Apply.
178
182
  - Keep filter/sort state in the URL so back/forward and refresh preserve it — but translate back to the IDs Wix expects before querying.
179
183
  - Page size **24**. Offer "load more" or prev/next from the cursor; never raise the limit to avoid paging.
180
184
  - Docs: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/supported-filters-and-sorting.md?apiView=SDK>
@@ -194,7 +198,10 @@ const { count } = await productsV3.countProducts({ filter }); // the "N produc
194
198
  import { categories } from '@wix/categories';
195
199
  const res = await categories.queryCategories({
196
200
  treeReference: { appNamespace: '@wix/stores' },
197
- }).exists('name', true).find(); // items: { _id, name, slug, visible, parentCategory? }; link each to /category/<slug>
201
+ }).exists('name', true).find(); // items: { _id, name, slug, visible, parentCategory? }
202
+ // Every category is a PAGE at /category/<slug> — link each one from the nav (or a menu/footer when
203
+ // the tree is large). A category that exists only as a filter toggle on the shop page has no URL a
204
+ // shopper can share or a search engine can index.
198
205
  ```
199
206
 
200
207
  **⚠️ The query MUST carry a filter condition — chain `.exists('name', true)` (as above), do NOT call a bare `.find()`.** A `.find()` with no chained filter serializes an empty `"filter": {}`, which `categories/v1/categories/query` rejects with `400 INVALID_FILTER` — **fatal on the visitor/manual-client (non-Astro) path** (managed-Astro's server-side transport happens to tolerate it, so a bare `.find()` *looks* fine there and breaks the moment the same code runs client-side). `.exists('name', true)` is a tautology, so it matches **all** categories and is accepted on **both** paths. **Do NOT filter on `visible`** (`.eq('visible', true)`) — `visible` is **not declared filterable** on `queryCategories`, so it triggers a silently-swallowed `400` and the nav renders blank; filter `visible === false` out of the returned array **client-side**. Skip the auto-created `all-products` system category. Page through when the store has more than one page. Index by `_id`, rebuild parent/child from `parentCategory`, and **keep the `_id` for product queries — the slug is only for URLs and route matching.** This is a **distinct API from bookings' `categoriesV2`** — don't copy that module's query shape here: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/introduction.md?apiView=SDK>.
@@ -213,7 +220,7 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v
213
220
 
214
221
  **⚠️ `variantsInfo` is `null` unless `VARIANT_OPTION_CHOICE_NAMES` is in `fields`** — the docs say variants "aren't returned" and that's true of a bare read; with the field, `product.variantsInfo.variants` carries every variant with its `optionChoiceIds`, `price`, and `inventoryStatus`. There is no second call to make. (`readOnlyVariantsV3` still exists for bulk variant queries; the product page doesn't need it.)
215
222
 
216
- Render the description as HTML (see *Rendering product descriptions*), the info sections as sections/accordions matching their length, breadcrumbs from `breadcrumbsInfo`, and **every** image in `media.itemsInfo.items` as a browsable gallery (main first, de-duplicated) — not just `media.main`.
223
+ Render the description as HTML (see *Rendering product descriptions*), the info sections as sections/accordions matching their length, breadcrumbs from `breadcrumbsInfo`, and **every** image in `media.itemsInfo.items` as a browsable gallery (main first, de-duplicated) — not just `media.main`. **De-duplicate on the media identity** (the item's `_id`, or the raw `wix:image://` id before its `#` suffix), never on a resolved URL: `media.main` and its copy in `itemsInfo.items` scaled to two sizes are two URLs of one photo, and the gallery then shows two thumbnails that open the same image.
217
224
 
218
225
  **How the page is split — one price, on the page in the HTML:**
219
226
 
@@ -223,10 +230,12 @@ Render the description as HTML (see *Rendering product descriptions*), the info
223
230
  const { product } = await productsV3.getProductBySlug(slug, { fields: DETAIL_FIELDS });
224
231
  if (!product) return new Response(null, { status: 404 });
225
232
  ---
233
+ <!-- Two columns only from md; on a phone the gallery is a bounded band (e.g. max-h-[45vh]) so the
234
+ name, price, first option, and the action are still in the first screen at 390px wide. -->
226
235
  <ProductPurchase client:load product={product} /> <!-- the island owns price, options, quantity, action -->
227
236
  ```
228
237
 
229
- The purchase island takes the **server-fetched product as a prop** and mounts **`client:load`**, so the price, the option controls, and the buy action are in the initial HTML and hydrate in place. `client:only` is for widgets whose state exists only in the browser (the cart badge, the drawer — `astro.md` A4), not for the purchase area. **Render the price once**, inside the island, where it can follow the selected variant; the page doesn't print its own copy above it.
238
+ The purchase island takes the **server-fetched product as a prop** and mounts **`client:load`**, so the price, the option controls, and the buy action are in the initial HTML and hydrate in place. `client:only` is for widgets whose state exists only in the browser (the cart badge, the drawer — `astro.md` A4), not for the purchase area. **Render the price once**, inside the island, where it can follow the selected variant; the page doesn't print its own copy above it. Description and info sections come **after** the buy action, never between the price and the button.
230
239
 
231
240
  ### Prices — which one the buyer actually pays
232
241
 
@@ -243,7 +252,16 @@ function sellingPrice(price) {
243
252
  }
244
253
  ```
245
254
 
246
- - **Before a variant is selected**, show the product range (`actualPriceRange.minValue`–`maxValue`, one value when equal) — never an empty price and never an arbitrary variant. Once every option is chosen, switch to the selected variant's `sellingPrice(variant.price)` and update the buy button's price with it.
255
+ - **Before a variant is selected, show the range — not the minimum with a struck "was".** A product priced 39.99–44.99 with a 54.99 compare-at must read "₪39.99 – ₪44.99" until the buyer picks; "was ₪54.99 ₪39.99" claims a saving that may not apply to the variant they choose. Once every option is chosen, switch to `sellingPrice(variant.price)` and update the buy button with it:
256
+
257
+ ```js
258
+ const { minValue: min, maxValue: max } = product.actualPriceRange ?? {};
259
+ const rangeDisplay = min && max && min.amount !== max.amount
260
+ ? `${min.formattedAmount} – ${max.formattedAmount}`
261
+ : min?.formattedAmount ?? '';
262
+ const shown = variant ? sellingPrice(variant.price) : { current: null, original: null };
263
+ // render: variant ? shown.current.formattedAmount (+ labelled shown.original when higher) : rangeDisplay (no "was")
264
+ ```
247
265
  - **Strike the original only when it is defined and higher than the current price**, and label it ("was", "regular price") — strikethrough or color alone doesn't carry the meaning for assistive tech.
248
266
  - **On cards, never pair a price *range* with one lone struck "was" minimum** — it implies a saving that may not apply to every variant. Show the "was" only for a single-price product; otherwise omit the comparison until a variant is picked.
249
267
  - `discountInfo.discountRuleNames` (product level) names the automatic discounts — render the names if you like, but **never compute a percentage or a savings claim yourself**; the cart's calculated summary is the only source of applied discounts.
@@ -251,7 +269,15 @@ function sellingPrice(price) {
251
269
 
252
270
  ### Ribbons — render all of them
253
271
 
254
- A product carries a primary `ribbon.name` **and** up to four `additionalRibbons[].name`. Render **every** label, on cards and on the product page, with one shared presentation (same shape, typography, placement; a semantic accent such as a "Sale" color is fine but applied by label, not by which field it came from). Wrap or use a clearly labeled overflow control — never keep only the first.
272
+ A product carries a primary `ribbon.name` **and** up to four `additionalRibbons[].name`. Render **every** label, on cards **and** on the product page, with one shared presentation (same shape, typography, placement; a semantic accent such as a "Sale" color is fine but applied by label, not by which field it came from). Wrap or use a clearly labeled overflow control — never keep only the first. One helper, used by the card and the PDP:
273
+
274
+ ```js
275
+ const ribbons = (p) => [p.ribbon?.name, ...(p.additionalRibbons ?? []).map((r) => r.name)].filter(Boolean);
276
+ ```
277
+
278
+ A card that reads only `product.ribbon.name` drops "Sale" on a product ribboned "New" + "Sale" — the merchant set both.
279
+
280
+ **Cards may preview a color option's choices as swatches** — the list read already carries `options[].choicesSettings.choices[].colorCode` for a `SWATCH_CHOICES`/`COLOR_CHOICES` option, so "3 colors" as text is the weaker choice when the tile has room for three dots. A swatch on a card is a preview, not a picker: selection still happens in Quick Add or on the product page. Keep the tile's hierarchy stable under a long name and a ranged price — name and price on separate lines, the name allowed to wrap, the price never clipped.
255
281
 
256
282
  ### Variants — resolve by choice IDs, from the product itself
257
283
 
@@ -275,6 +301,7 @@ const available = (v) => v.inventoryStatus?.inStock !== false || v.inventoryStat
275
301
  - **Which choices to disable:** before any selection, use each choice's own flags (`choice.inStock ?? true`, `choice.visible ?? true`). After a selection, a choice is selectable when at least one visible variant compatible with the current picks is `available` (in stock **or** preorder-enabled).
276
302
  - **A single-variant product** (no options) resolves to its only variant — `variantsInfo.variants[0]` on the PDP, `variantSummary.minPriceVariant` on a card.
277
303
  - Changing an option **resets quantity to 1**. Modifiers never take part in the key — they're separate inputs (next section).
304
+ - **Color options render as swatches, not text pills.** When `option.optionRenderType` is `SWATCH_CHOICES` or `COLOR_CHOICES`, each choice carries `colorCode` (`#7c3aed`) — render a color swatch with the name as its label. Text options (`TEXT_CHOICES`) are pills.
278
305
  - **A choice image** lives at `choice.media.items[].mediaId` (with `PRODUCT_CHOICES_MEDIA_REFERENCES`); a resolved variant may carry `variant.media`. Make choice media *available* (highlight it in the gallery) rather than replacing the gallery on every option change.
279
306
 
280
307
  **Which action to show** — precedence, top wins: **(1)** the chosen combination doesn't exist → "unavailable"; **(2)** preorder (before selection: `inventory.preorderStatus === 'ENABLED'` and the product is out of stock; after: the variant's `preorderEnabled`) → "Pre-order"; **(3)** back-in-stock notify (see *Catalog-conditional*); **(4)** out of stock; **(5)** purchasable. Until the buyer has picked everything, the button is disabled with a **neutral** reason next to it ("Choose a size") — not error styling; promote to an error only after they try to buy.
@@ -319,9 +346,14 @@ const { cart } = await currentCartV2.addLineItemsToCurrentCart({ catalogItems: [
319
346
  Both take the **line's `_id`** (`cart.lineItems[]._id`), never the product id. The update body is a `lineItems` array with a nested `quantity` object — a flat `{ _id, quantity }` returns **400**:
320
347
 
321
348
  ```js
322
- const { cart } = await currentCartV2.updateLineItemsInCurrentCart({
323
- lineItems: [{ lineItemId, quantity: { newQuantity } }],
324
- });
349
+ try {
350
+ const { cart } = await currentCartV2.updateLineItemsInCurrentCart({
351
+ lineItems: [{ lineItemId, quantity: { newQuantity } }],
352
+ });
353
+ setCart(cart);
354
+ } catch (e) {
355
+ setLineError(lineItemId, e?.message ?? "Couldn't update the quantity"); // shown next to the line — never `catch {}`
356
+ }
325
357
  const { cart } = await currentCartV2.removeLineItemsFromCurrentCart([lineItemId]);
326
358
  ```
327
359
 
@@ -333,11 +365,29 @@ After either, refresh the estimate (below). **Never swallow the error**: an `awa
333
365
 
334
366
  ### Three ways to buy from the gallery
335
367
 
336
- Route each card by what the product needs from the buyer:
368
+ **Every card carries a buy path** — a gallery whose only action is "view" is incomplete. Route each card by what the product needs from the buyer; one function, used by every card:
369
+
370
+ ```js
371
+ // From LIST_FIELDS data alone — no per-card detail fetch.
372
+ function purchasePath(p) {
373
+ const mods = p.modifiers ?? [];
374
+ if (mods.some((m) => m.modifierRenderType === 'FREE_TEXT') || p.subscriptionPricesInfo) return 'pdp';
375
+ if ((p.options?.length ?? 0) > 0 || mods.some((m) => m.mandatory)) return 'quickAdd';
376
+ if (p.inventory?.availabilityStatus === 'OUT_OF_STOCK' && p.inventory?.preorderStatus !== 'ENABLED') return 'soldOut';
377
+ return 'directAdd';
378
+ }
379
+ ```
337
380
 
338
- 1. **Direct Add** — no options, no modifiers, no subscriptions: add `variantSummary.minPriceVariant._id` with quantity 1, through the same serializer.
339
- 2. **Quick Add** — the product has options or choice modifiers: fetch the full product lazily when the picker opens (`productsV3.getProduct(product._id, { fields: DETAIL_FIELDS })`), require a resolved variant and mandatory modifiers, fix quantity at 1. Anchor the picker **to its card** (an inline or attached panel; a bottom sheet on mobile) — a centered dialog is the exception for a demonstrably large configuration, not the default. Its first view shows identity, price, the first required choice, and the action.
340
- 3. **Go to the product page** — the product has a free-text modifier or subscriptions: the gallery can't collect those inputs.
381
+ 1. **`directAdd`** — no options, no mandatory modifiers, no subscriptions: one click adds `variantSummary.minPriceVariant._id` with quantity 1, through the same serializer.
382
+ 2. **`quickAdd`** — the product has options or a mandatory choice modifier: fetch the full product **only when the picker opens**, then resolve exactly as the PDP does:
383
+ ```js
384
+ const { product: full } = await productsV3.getProduct(p._id, { fields: DETAIL_FIELDS });
385
+ // → the same option controls, variantByKey resolution, and catalogItem() serializer as the PDP;
386
+ // selections start empty; quantity fixed at 1; the action disabled with its neutral reason until
387
+ // a variant resolves and every mandatory modifier has a value.
388
+ ```
389
+ Anchor the picker **to its card** (an inline or attached panel; a bottom sheet on small screens) — a centered dialog is the exception for a demonstrably large configuration, not the default. Its first view shows identity, price, the first required choice, and the action; it follows the overlay contract (`experience-store.md` §3).
390
+ 3. **`pdp`** — a free-text modifier or subscriptions: link to the product page; the gallery can't collect those inputs.
341
391
 
342
392
  ### Cart totals — from Wix, never computed in the client
343
393
 
@@ -351,7 +401,7 @@ const { summary } = await currentCartV2.estimateCurrentCart(); // after every
351
401
  ```
352
402
 
353
403
  - Show **Subtotal**, a **Discount** row only when it's non-zero, and **"Shipping and taxes are calculated at checkout"** — never a hardcoded shipping charge, free-shipping threshold, tax, or delivery date, and never a synthesized `0` for something that wasn't calculated.
354
- - `estimateCurrentCart` is a display nicety — if it fails, the cart is still valid; show lines without the summary row.
404
+ - **Call `estimateCurrentCart` only when the cart has lines** — on an absent or empty cart the endpoint returns **404**, and a drawer that estimates on every page load logs that error for every first-time visitor. It is a display nicety — if it fails, the cart is still valid; show lines without the summary row.
355
405
  - Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/estimate-current-cart.md?apiView=SDK>
356
406
 
357
407
  ### Checkout — redirect to the hosted checkout page
@@ -470,11 +520,40 @@ if (!product) return new Response(null, { status: 404 });
470
520
  ---
471
521
  <Layout title={product.name}>
472
522
  <SEO.Tags seoTagsServiceConfig={seoTagsServiceConfig} slot="seo-tags" /> <!-- Layout renders <slot name="seo-tags" /> in <head> -->
523
+ <!-- SEO.Tags renders the <title>. The layout must NOT print its own when the slot is filled — two titles otherwise:
524
+ in Layout.astro: {!Astro.slots.has("seo-tags") && <title>{title}</title>} <slot name="seo-tags" /> -->
525
+ …
526
+ </Layout>
527
+ ```
528
+
529
+ The **category page** is the same block with the category identifiers — same imports, same `pageUrl`, same guard:
530
+
531
+ ```astro
532
+ ---
533
+ // src/pages/category/[slug].astro
534
+ export const wixMetadata = {
535
+ appDefId: WIX_APPS.checkoutAndOrders.id,
536
+ pageIdentifier: WIX_APPS.checkoutAndOrders.categoryPageMetadata.pageIdentifier,
537
+ identifiers: { slug: WIX_APPS.checkoutAndOrders.categoryPageMetadata.identifiers.handle },
538
+ };
539
+ const slug = Astro.params.slug!;
540
+ let category = null, seoTagsServiceConfig = null;
541
+ try {
542
+ [category, seoTagsServiceConfig] = await Promise.all([
543
+ categories.getCategoryBySlug(slug, { appNamespace: "@wix/stores" }).then((r) => r.category ?? null),
544
+ loadSEOTagsServiceConfig({ pageUrl, itemType: seoTags.ItemType.STORES_CATEGORY, itemData: { slug } }),
545
+ ]);
546
+ } catch {}
547
+ if (!category || category.visible === false) return new Response(null, { status: 404 });
548
+ // category._id → the gallery query's categoryId; category.name / description head the page
549
+ ---
550
+ <Layout title={category.name}>
551
+ <SEO.Tags seoTagsServiceConfig={seoTagsServiceConfig} slot="seo-tags" />
473
552
  …
474
553
  </Layout>
475
554
  ```
476
555
 
477
- A dedicated category route uses `categoryPageMetadata` + `seoTags.ItemType.STORES_CATEGORY` with the same imports. (A category rendered only as a query-string *filter* on the shop page is a main page — automatic SEO, no `wixMetadata`.) Guide: <https://dev.wix.com/docs/go-headless/wix-managed-headless/seo/add-seo-support-to-item-pages.md>.
556
+ (A category rendered only as a query-string *filter* on the shop page is a main page — automatic SEO, no `wixMetadata` — but it is not a substitute for the category pages above.) Guide: <https://dev.wix.com/docs/go-headless/wix-managed-headless/seo/add-seo-support-to-item-pages.md>.
478
557
 
479
558
  **Non-Astro (you render the head yourself):** map `product.seoData` / `category.seoData` tags into the head and fill only what's missing from truthful entity data. Rules that hold on both paths: **one** document title and **one** canonical per page (a merchant override wins over a generated fallback; a tag the merchant disabled stays absent); a missing entity returns a **real 404**, never a rendered shell with another product's tags; clear route tags on client navigation so a product's metadata can't leak into the next page; a `Product` JSON-LD block only from real data — never fabricated reviews, ratings, or availability.
480
559
 
@@ -482,12 +561,12 @@ A dedicated category route uses `categoryPageMetadata` + `seoTags.ItemType.STORE
482
561
 
483
562
  Read the catalog first; then, **only when the data is present**:
484
563
 
485
- - **Subscriptions** (`subscriptionPricesInfo` present): show each plan's price and billing interval as comparable terms; require an **explicit** plan choice (never pre-select the first); offer one-time purchase only when `allowOneTimePurchases` is true; serialize a recurring plan as `subscriptionOptionId`, omit it for one-time. Don't apply the one-time automatic-discount price to a subscription quote.
564
+ - **Subscriptions** (`subscriptionPricesInfo` present): show each plan's price and billing interval as comparable terms; require an **explicit** plan choice (never pre-select the first); offer one-time purchase only when `allowOneTimePurchases` is true; serialize a recurring plan as `subscriptionOptionId`, omit it for one-time. Don't apply the one-time automatic-discount price to a subscription quote. **In the cart, a subscription line says so**: render `lineItem.subscriptionInfo` — the plan's `title.original` and its billing terms from `subscriptionSettings` (every `interval` `frequency`, `billingCycles` unless `autoRenewal`) — under the line, from the supplied fields only, never a generic "recurring".
486
565
  - **Preorder** (`inventory.preorderStatus === 'ENABLED'` / `variant.inventoryStatus.preorderEnabled`): label "Pre-order", include `preOrderRequested: true`. Preorder and a subscription plan may coexist.
487
566
  - **Product groups** (`extendedFields.namespaces['@stores/product-groups'].productGroupId` is a string): the group's members are **navigation between products** (the "same shirt in linen" case), not variants — query the group via the Product Groups API and render members as choices that navigate to the member's slug.
488
567
  - **Positioned promotions**: `promotionsV3.resolvePromotions(categoryId)` returns category-gallery banners with a **one-based** `position` — insert at `position - 1` only once that many products have loaded. They are editorial media, not evidence of a discount, and they belong to that category's gallery, not the homepage. Docs: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/promotions-v3.md>
489
568
  - **Back-in-stock ("Notify me")**: only when the store's back-in-stock settings allow it, the selected variant is out of stock, and you have an implemented, session-safe notification endpoint — a form whose submit goes nowhere is not a graceful placeholder.
490
- - **Filter facets** beyond price/stock (color, size): the option choice fields (`options.choicesSettings.choices.choiceId` with `$hasSome`) discover *products*; Quick Add or the PDP still resolves the actual variant.
569
+ - **Filter facets** beyond price/stock (color, size) are in the gallery query above, not conditional: the option choice fields (`options.choicesSettings.choices.choiceId` with `$hasSome`) discover *products*; Quick Add or the PDP still resolves the actual variant.
491
570
 
492
571
  For the exact shapes of the group, notification, and inventory APIs, find the method pages via `DOC_DISCOVERY.md` (`document_type: SDK`); they aren't pinned here because most catalogs don't use them.
493
572
 
@@ -505,6 +584,7 @@ A correct Catalog V3 storefront frontend:
505
584
  - requests **`LIST_FIELDS` / `DETAIL_FIELDS`** on every read, and uses **`product._id`** (never `product.id`) as the cart's `catalogItemId`;
506
585
  - reads the product page with **`getProductBySlug` + `VARIANT_OPTION_CHOICE_NAMES`** and resolves the variant by **sorted choice IDs** — selections start empty, the mandatory **`variantId`** goes in `options.variantId` (not `options.options`);
507
586
  - prices with **`priceAfterDiscount` → `actualPrice` → `compareAtPrice`** precedence and renders **every ribbon**, never inferring one from the other;
508
- - sorts, filters, and pages **on Wix** (`searchProducts` + `$matchItems: [{ id: categoryId }]` on `allCategoriesInfo.categories`, cursor paging at 24) — never a frozen seed-time list, never `queryProducts` for categories, never `$hasSome`, never V1 `collectionIds`;
587
+ - sorts, filters, and pages **on Wix** (`searchProducts` + `$matchItems: [{ id: categoryId }]` on `allCategoriesInfo.categories`, option facets via `$hasSome` on choice IDs, cursor paging at 24) — never a frozen seed-time list, never `queryProducts` for categories, never V1 `collectionIds`;
588
+ - ships the gallery's filter panel and gives every card its buy path (`purchasePath`: direct add / quick add / product page), and gives every category a page at `/category/<slug>` linked from the chrome;
509
589
  - shows cart totals from **`estimateCurrentCart`**, changes quantity with **`{ lineItems: [{ lineItemId, quantity: { newQuantity } }] }`**, checks out through the **redirect session** with an `https://` origin, and keeps Buy Now on a standalone cart;
510
590
  - builds the homepage, gallery, product page, and side cart to the bar in `experience-store.md` — the store is the surfaces, not the calls.
@@ -28,30 +28,30 @@ Live site: <published-url>
28
28
  Dashboard: https://manage.wix.com/dashboard/<SITE_ID>
29
29
  ```
30
30
 
31
- ## Suggest a custom domain (proactive, after release)
31
+ ## Suggest a custom domain (required, after release)
32
32
 
33
- After delivering the links above, **proactively offer a custom domain** — don't wait for the user to ask. The published URL is a generic Wix subdomain; a custom domain is the obvious next step, and the CLI handles the whole flow.
33
+ The published URL is a generic Wix subdomain, so a custom domain is the obvious next step and the CLI handles the whole flow. **Run step 1 on every managed release and put its result in the same message as the links above** — this is a step you execute, not a judgment call about whether the user seems interested. It applies in a non-interactive run too: there's no one to ask, so just include the suggestions and a checkout link in your final message and let the user act on them.
34
34
 
35
- 1. **Suggest options.** Build a search query from what you know about the site — its business name, topic, or purpose from this run (not a generic placeholder):
35
+ 1. **Run the suggestion command.** Build a search query from what you know about the site — its business name, topic, or purpose from this run (not a generic placeholder):
36
36
 
37
37
  ```bash
38
38
  npx @wix/cli@latest account domain suggest "<business name or site topic>" --limit 5 --json
39
39
  ```
40
40
 
41
- All returned suggestions are already available for purchase. Show the user a few of the best fits, and mention they can explore more options — re-run `suggest` with different keywords, a higher `--limit`, or `--tld com net ...` to filter extensions.
41
+ All returned suggestions are already available for purchase. Name a few of the best fits in your message, and mention they can explore more options — re-run `suggest` with different keywords, a higher `--limit`, or `--tld com net ...` to filter extensions.
42
42
 
43
- 2. **Offer to buy, with a direct purchase link.** For the domain(s) the user likes, generate a site-scoped deep link so the purchase is tied to this site and can be connected after payment:
43
+ 2. **Give a checkout link.** Generate a site-scoped deep link for the domain the user picks — or, if nobody is there to pick, for the best of the suggestions — so the purchase is tied to this site and can be connected after payment:
44
44
 
45
45
  ```bash
46
- npx @wix/cli@latest account --site-id <SITE_ID> domain buy <domain> --json
46
+ npx @wix/cli@latest account --site-id <SITE_ID> domain checkout-link <domain> --json
47
47
  ```
48
48
 
49
- Use the `siteId` from `wix.config.json`. `buy` does **not** charge anything — it returns a checkout URL (`https://manage.wix.com/get-domain?...`); the user completes payment in the browser.
49
+ Use the `siteId` from `wix.config.json`. `checkout-link` only returns a site-scoped checkout URL; it does **not** purchase a domain or charge the user. The user completes payment in the browser.
50
50
 
51
51
  > Your site is live at <published-url>, and you can manage it from your [dashboard](https://manage.wix.com/dashboard/<SITE_ID>).
52
52
  > To build a professional online presence, [connect a custom domain](<checkout-url>) — **mybusiness.com** is available.
53
53
 
54
- If the user isn't interested, drop it — one offer, no upselling.
54
+ Once the suggestions and the link are in front of the user, you're done: if they don't want a domain, drop it — one offer, no upselling.
55
55
 
56
56
  ## Before you sign off — feedback checkpoint
57
57