@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
@@ -2,30 +2,38 @@
2
2
 
3
3
  The commerce machinery ships as files — data layer, hooks, cart, checkout, SEO plumbing,
4
4
  typed end-to-end. **The presentation doesn't ship — you build it** on the shipped hooks/DTOs:
5
- the shop and product (PDP) pages with their islands, the home page, and the brand. The
6
- skeletons below carry each page's contract — including the SSR and SEO machinery, which must
5
+ the shop, category, and product (PDP) pages with their islands, the home page, and the brand.
6
+ The skeletons below carry each page's contract — including the SSR and SEO machinery, which must
7
7
  be exact. You never write commerce code; you never skip designing the store.
8
8
 
9
9
  ## The file map (deployed into `src/`)
10
10
 
11
- **Don't read the shipped files** — this table and the contracts below are everything you
12
- need. Open a shipped file's source **only** on a real fallback: a runtime error, or a field
13
- this playbook doesn't cover. Files you edit: `SiteLayout.astro` and `styles/global.css`.
14
- Files you **create** (skeletons below): the shop and PDP pages with their island components,
15
- plus your home page.
11
+ **On Astro and React the shipped files are tested and work as they are** — this table and the
12
+ contracts below are everything you need to use them, so don't spend the run reading their source;
13
+ wire them and build your surfaces. Reading them is the right move when something is off (a runtime
14
+ error, a field this playbook doesn't cover) or when the brief wants a behaviour they don't offer —
15
+ then read the file that owns it and change or extend it. On `lib`, `static`, and a port the
16
+ components don't deploy at all, and each wiring section below opens with the files to read before
17
+ writing their equivalents. Files you edit: `SiteLayout.astro` and `styles/global.css`.
18
+ Files you **create** (skeletons below): the shop, category, and PDP pages with their island
19
+ components, plus your home page.
16
20
 
17
21
  | file | what it is |
18
22
  |---|---|
19
23
  | `wix/config.ts` · `wix/sdk.ts` | shared auth seam (deploy configures it — nothing to set by hand) |
20
- | `wix/media.ts` · `wix/money.ts` | `imgSrc()` / `formatMoney()` — already used by everything shipped |
21
- | `wix/storefront/types.ts` | the DTOs (`ProductSummary`, `ProductDetail`, `Cart`, `Category`) — contracts inlined below |
22
- | `wix/storefront/catalog.ts` | `searchCatalog` (sort/filter/search + cursor paging, all server-side), `fetchProducts`, `fetchProductsByCategory`, `fetchProductBySlug`, `fetchCategories`, `resolveVariant` |
24
+ | `wix/media.ts` · `wix/money.ts` | `imgAttrs(url, sizes)` — every `<img>` attribute for a DTO image (`src`, `srcSet`, `sizes`, lazy): `<img {...imgAttrs(p.imageUrl, "25vw")} alt={p.name} />`; `imgSrc()` / `imgSrcSet()` / `formatMoney()` underneath, already used by everything shipped |
25
+ | `wix/storefront/types.ts` | the DTOs (`ProductSummary`, `ProductDetail`, `Cart`, `Category`, `Facet`) — contracts inlined below |
26
+ | `wix/storefront/catalog.ts` | `searchCatalog` (sort/filter/facets/search + cursor paging + result count, all server-side), `fetchFacets`, `fetchProducts`, `fetchProductsByCategory`, `fetchProductBySlug`, `fetchCategories`, `fetchCategoryBySlug`, `resolveVariant` — the transport; the rules and DTO mappers are in `catalog-core.ts` / `cart-core.ts` beside it (shared with the REST layer) |
23
27
  | `wix/storefront/cart.ts` · `cart-store.ts` | Cart V2 + shared cart state (module store — spans Astro islands) |
28
+ | `wix/storefront/shop-store.ts` · `product-detail-store.ts` | the listing and product-detail state machines, framework-free (`createShopStore()`, `createProductDetailStore()` — `getState`/`subscribe` + actions, one instance per surface); the hooks below bind them to React, every other stack uses them directly |
24
29
  | `hooks/storefront/useCart.ts` | cart state + actions — contract below |
25
- | `hooks/storefront/useShop.ts` | listing + live category filter — contract below |
26
- | `hooks/storefront/useProductDetail.ts` | option selection → variant resolution → add-to-cart — contract below |
30
+ | `hooks/storefront/useShop.ts` | React binding of `shop-store.ts`: category scope, sort, filters, option facets, result count, paging — contract below |
31
+ | `hooks/storefront/useProductDetail.ts` | React binding of `product-detail-store.ts`: option selection → variant resolution → add-to-cart — contract below |
27
32
  | `components/storefront/CartButton.tsx` · `CartDrawer.tsx` | header badge + slide-over cart — **wire as-is** (drawer once per page) |
28
- | `components/storefront/ShopView.tsx` · `ProductDetailView.tsx` | **don't ship — YOU create them** (skeletons below): the client islands your shop and PDP pages mount |
33
+ | `components/storefront/FilterPanel.tsx` | the gallery's filter LAYOUT — toolbar (result count, sort), active chips, then a 16rem sidebar of collapsible groups (price as a two-handle slider bounded by the catalog's real prices, availability, one group per option facet with swatches/pills) beside YOUR results; a bottom sheet under `md` — **wire as-is** in your `ShopView`, your grid as its children: `<FilterPanel shop={shop}>…grid…</FilterPanel>` |
34
+ | `components/storefront/QuickAdd.tsx` | the tile's purchase control — one click for a product with no options, a picker anchored to the tile (bottom sheet on small screens) for one with options, the product page for free-text customization — **wire as-is** as the last row of every tile's text block (`<QuickAdd product={p} />`) |
35
+ | `components/storefront/OptionPicker.tsx` | the purchase controls for one product — option groups (swatches/pills, sold-out choices disabled), choice and text modifiers, an optional quantity stepper, the buy button gated by `useProductDetail` with its plain reason, "Pre-order" when pre-orderable — **wire as-is** in your PDP (`<OptionPicker detail={d} showQuantity />`); QuickAdd's picker is this same component |
36
+ | `components/storefront/ShopView.tsx` · `ProductDetailView.tsx` | **don't ship — YOU create them** (skeletons below): the client islands your shop, category, and PDP pages mount |
29
37
  | `styles/global.css` | **the design system**: Tailwind v4 + the `@theme` token block (colors, radii, fonts — same token family as the official Wix templates). Everything, shipped and yours, styles from these tokens |
30
38
 
31
39
  Astro stack additionally gets:
@@ -33,7 +41,7 @@ Astro stack additionally gets:
33
41
  | file | what it is |
34
42
  |---|---|
35
43
  | `layouts/SiteLayout.astro` | the site chrome — **yours to brand**: header, footer, nav. Keep the `<slot name="seo-tags" />`, the global.css import, and the CartButton/CartDrawer mounts |
36
- | `pages/shop.astro` · `pages/products/[slug].astro` | **don't ship — YOU create them** (skeletons below): thin SSR pages that fetch server-side and mount your islands; the PDP page carries the owner-editable SEO machinery, exactly as its skeleton shows |
44
+ | `pages/shop.astro` · `pages/category/[slug].astro` · `pages/products/[slug].astro` | **don't ship — YOU create them** (skeletons below): thin SSR pages that fetch server-side and mount your islands; the category and PDP pages carry the owner-editable SEO machinery, exactly as their skeletons show |
37
45
 
38
46
  ## What you build — this is the design job, not optional polish
39
47
 
@@ -42,11 +50,13 @@ skeletons below — styled with Tailwind utilities on the `@theme` tokens, desig
42
50
  brief (the business, the tone, the audience — a toy brand and a jewelry house should not get
43
51
  the same store):
44
52
 
45
- 1. **The shop page + `ShopView`** (skeletons below) — category filter + your grid of your
46
- tiles: image treatment, badges, price/sale presentation, hover behavior, grid rhythm
47
- (columns, density, maybe an editorial featured tile). `products === null` → skeleton tiles,
53
+ 1. **The shop page + `ShopView`** (skeletons below) — category links, the shipped
54
+ `FilterPanel`, and your grid of your tiles: image treatment, badges, price/sale
55
+ presentation, hover behavior, grid rhythm (columns, density, maybe an editorial featured
56
+ tile), each tile carrying the shipped `QuickAdd`. `products === null` → skeleton tiles,
48
57
  `[]` → an honest empty state. Decompose into `ProductCard`/`ProductGrid` files if you like —
49
- your call, nothing prescribes it.
58
+ your call, nothing prescribes it. **The category page** (`/category/[slug]`) mounts the same
59
+ `ShopView` scoped to one category — one island, two pages.
50
60
  2. **The PDP page + `ProductDetailView`** (skeletons below) — gallery, price/sale,
51
61
  description, your option-selection UI (color options = real swatches), quantity,
52
62
  add-to-cart — on `useProductDetail`, which owns ALL selection/variant logic; you own how it
@@ -58,22 +68,78 @@ Plus the **theme** (edit the `@theme` block in `styles/global.css` — one edit;
58
68
  flipped token values; add brand fonts as extra tokens) and the **chrome** (header/footer in
59
69
  `SiteLayout.astro`, one edit pass; mount the shipped `CartButton` in your header).
60
70
 
71
+ ### What a complete storefront shows (recommended defaults)
72
+
73
+ These are the recommended defaults for a store whose brief says nothing about them. They are
74
+ not requirements: when the user's prompt asks for something different — a cart page instead of a
75
+ drawer, no shop page, a particular layout or look — the prompt wins, and the item here that
76
+ conflicts with it is dropped without discussion. Look at the catalog before designing (categories,
77
+ assortment size, media, options, sales, ribbons) and design for this store, not for a stereotype
78
+ of its category. Then, by default:
79
+
80
+ - **Home:** what the store sells and one shopping action in the first screen; real products under
81
+ truthful headings ("Best Sellers" needs data behind it); not a repeat of the shop page. A
82
+ category tile or link on the home page goes to that category's page (`/category/<slug>`) —
83
+ never to an anchor on the shop page that every tile shares — and its image is a product from
84
+ THAT category (fetched with the category filter) or the category's own image, never a
85
+ positional guess into the all-products list.
86
+ - **Shop:** a real product card — image, name, price, link — in the first screen; the shipped
87
+ `FilterPanel` around the grid (toolbar, then a filter sidebar beside the results on desktop and
88
+ a sheet on phones) — a store with any filterable catalog ships it, not "when it fits"; the
89
+ shipped `QuickAdd` as the last row of every tile; each
90
+ category reachable by a real link (`/category/<slug>`) — from the nav, the home page, or the
91
+ shop's category row; loading, empty, no-results, and error states that look different.
92
+ - **Product page:** image, name, price, the first choice, and the buy button with `blockedReason`
93
+ in the first screen **at 390px wide too** — on a phone the image is a bounded band
94
+ (`max-h-[45vh]`), not a full-screen hero that pushes the price below the fold; every ribbon;
95
+ every image reachable in the gallery.
96
+ - **Cart:** the shipped drawer — it opens after every add, and checkout is a button in it.
97
+ - **Overlays you build** (quick-add, mobile nav, filters): mount at the document root (a fixed
98
+ panel inside the `backdrop-blur` header gets clipped), lock background scroll, close on Escape,
99
+ return focus on close — as the shipped `CartDrawer` does. An anchored picker is positioned
100
+ inside its tile, not `fixed` with computed offsets. If you add dismiss-on-outside-click, decide
101
+ inside/outside before anything re-renders (a capture-phase listener), or a click on a swatch
102
+ that re-renders the panel reads as "outside" and closes it.
103
+ - **A page whose slug resolves to nothing** (category, product) shows only the not-found state —
104
+ no heading, toolbar, or empty grid rendered around it.
105
+ - **Copy:** nothing the merchant didn't supply — no invented reviews, scarcity, or delivery
106
+ promises; no Wix IDs or technical words in visible text.
107
+
108
+ Pre-order ships (`isPreorder`). Subscriptions, product groups, promotions, and notify-me are built
109
+ only when the catalog has them — never fabricated; `wix-docs` has their contracts.
110
+
61
111
  ### The contracts your components consume (everything you need — don't read the source)
62
112
 
63
113
  ```ts
64
114
  // ProductSummary (grid tiles) — all display-ready: prices formatted, images https URLs:
65
- // { id, slug, name, price, maxPrice, compareAtPrice|null, ribbon|null,
66
- // availability: "IN_STOCK"|"OUT_OF_STOCK"|"PARTIALLY_OUT_OF_STOCK", preorder: boolean,
67
- // imageUrl, hoverImageUrl, optionsSummary /* "2 colors · 3 sizes" */, quickAddable: boolean }
68
- // price vs maxPrice differ → show a range. quickAddable → useCart().addToCart(product.id).
69
-
70
- // useShop({ initialProducts?, initialCategories?, pageSize? /* 24 */ }) →
71
- // { products: ProductSummary[]|null /* null = loading → skeletons */, categories: Category[],
72
- // activeCategoryId: string|null, setActiveCategoryId(id|null),
115
+ // { id, slug, name, price, maxPrice, compareAtPrice|null, ribbon|null, ribbons: string[],
116
+ // minPriceVariantId|null, availability: "IN_STOCK"|"OUT_OF_STOCK"|"PARTIALLY_OUT_OF_STOCK",
117
+ // preorder: boolean, imageUrl, hoverImageUrl, optionsSummary /* "2 colors · 3 sizes" */,
118
+ // swatches: string[] /* hex colors of a color option's choices — dots on the tile, not a picker */,
119
+ // quickAddable: boolean }
120
+ // price !== maxPrice → the product is a RANGE: render "price – maxPrice" (compareAtPrice is null
121
+ // then — never a struck price beside a range). Otherwise price is what the buyer pays (a discount
122
+ // already applied) and compareAtPrice, when present, is the labelled "was".
123
+ // ribbons = EVERY merchant ribbon, primary first — render all, one shared style; a ribbon is a
124
+ // label, never proof of a discount. The tile's buy control is the shipped <QuickAdd product={p} />:
125
+ // it reads quickAddable / minPriceVariantId / preorder itself and routes to a direct add, an
126
+ // anchored option picker, or the product page — don't rebuild that decision in the tile.
127
+
128
+ // useShop({ initialProducts?, initialCategories?, initialCategoryId?, pageSize? /* 24 */ }) →
129
+ // { products: ProductSummary[]|null /* null = loading → skeletons */, total: number|null,
130
+ // categories: Category[], activeCategoryId: string|null, setActiveCategoryId(id|null),
73
131
  // sort: keyof SORTS, setSort(sort), filters, setFilters({ minPrice?, maxPrice?, inStockOnly?, search? }),
132
+ // facets: Facet[], selectedChoiceIds, toggleChoice(choiceId), clearFilters(), hasActiveFilters,
74
133
  // loading, error, retry(), hasMore, loadMore(), loadingMore }
75
- // Category = { id, slug, name }. Render the filter bar only when categories.length > 1.
76
- // Sort/filter/search/paging run on Wix across the WHOLE catalog (a change restarts the list).
134
+ // Category = { id, slug, name, description }. Facet = { name, isColor, choices: [{ id, name, colorCode|null }] }.
135
+ // The shipped <FilterPanel shop={shop}>{…your grid…}</FilterPanel> renders total/sort/chips, the
136
+ // filter sidebar (md+) or sheet, and lays your results out beside it — hand it the whole hook result
137
+ // and put your grid + states inside it; don't build a second filter UI or a second sort control. Categories are LINKS (`/category/${c.slug}`) — a category page is a URL
138
+ // a shopper can share and a search engine can index; setActiveCategoryId is for a live scope
139
+ // switch on /shop, not a substitute for the links.
140
+ // Sort/filter/facets/search/paging run on Wix across the WHOLE catalog (a change restarts the list).
141
+ // The selection is mirrored into the query string (?sort=&min=&max=&stock=1&q=&choice=…) and read
142
+ // back on load — a filtered gallery is a shareable link; nothing for you to wire.
77
143
  // SORTS (exported next to useShop) is Record<sortKey, { label: string }> — the value is an
78
144
  // OBJECT, so render entry.label, never the entry itself:
79
145
  // Object.entries(SORTS).map(([key, { label }]) => <option value={key}>{label}</option>)
@@ -84,14 +150,20 @@ flipped token values; add brand fonts as extra tokens) and the **chrome** (heade
84
150
  // optionGroups: [{ id, name, isColor, choices: [{ choiceId, name, colorCode|null, inStock, selected }] }],
85
151
  // selectOption(optionName, choiceName),
86
152
  // modifierValues, setModifier(key, value), // product.modifiers: pills or text input; "*" = mandatory
87
- // price, compareAtPrice, // live: variant price once resolved
88
- // canAdd, // gate the button; false until every option picked & in stock
89
- // quantity, setQuantity, add(), adding, error }
90
- // ProductDetail adds: descriptionHtml (render as HTML), gallery: string[] (urls, main first),
91
- // options, modifiers, variants — but selection ALWAYS goes through the hook above.
153
+ // price, compareAtPrice, // the RANGE until every option is picked, then the variant's price (+ labelled "was" when real)
154
+ // isPreorder, // resolved variant is out of stock but pre-orderable → label the action "Pre-order"
155
+ // canAdd, // gate the button; false until every option picked & in stock (or pre-orderable)
156
+ // blockedReason, // "Choose Size" / "Out of stock" / "Add Engraving" — render beside the button as
157
+ // // neutral guidance (not error styling) while it's disabled; null when addable
158
+ // quantity, setQuantity, add(), adding, error } // quantity resets to 1 when an option changes
159
+ // ProductDetail adds: descriptionHtml (render as HTML), infoSections: [{ title, html }] (sections
160
+ // or accordions), gallery: string[] (urls, main first), options, modifiers, variants — but
161
+ // selection ALWAYS goes through the hook above.
92
162
 
93
163
  // useCart() →
94
- // { cart: { lines, itemCount, subtotal, currency }|null, busy, error, open,
164
+ // { cart: { lines, itemCount, subtotal, discount /* "" when none */, currency }|null, busy, error, open,
165
+ // // a line: { lineItemId, productName, quantity, unitPrice, linePrice, imageUrl, descriptionLines,
166
+ // // status, subscription /* "Monthly plan · every month · 12 payments" or "" */ }
95
167
  // addToCart(productId, variantId?, qty?, extras?), updateQuantity(lineItemId, qty),
96
168
  // removeLine(lineItemId), checkout(), openCart(), closeCart(), refresh() }
97
169
  // addToCart rejects on refusal (out of stock, digital product with no file) AND records
@@ -101,6 +173,10 @@ flipped token values; add brand fonts as extra tokens) and the **chrome** (heade
101
173
 
102
174
  ### The pages and islands you create — skeletons
103
175
 
176
+ The class names in these skeletons are the Astro/React spelling of layout rules that hold on
177
+ every stack, and each rule is also named in words beside its classes. On a stack where the shipped
178
+ components don't deploy (`lib`, `static`, a port), keep the rule and write it in your own CSS.
179
+
104
180
  Nothing renders until you write these — the store IS your work. Each page is a thin SSR shell
105
181
  (fetch → DTO props → island); each island is a thin view over a hook. The pages' frontmatter
106
182
  is **machinery, not design** — reproduce it as the skeletons show, exactly. Hooks first,
@@ -135,6 +211,60 @@ try {
135
211
  </SiteLayout>
136
212
  ```
137
213
 
214
+ ```astro
215
+ ---
216
+ // src/pages/category/[slug].astro — YOU create it. A category is a Wix ITEM PAGE like a product:
217
+ // its own URL, its SEO owner-editable in the dashboard, registered in the sitemap. Same SEO
218
+ // machinery as the PDP with the CATEGORY identifiers. It mounts the SAME ShopView, scoped.
219
+ import SiteLayout from "../../layouts/SiteLayout.astro";
220
+ import ShopView from "../../components/storefront/ShopView";
221
+ import { fetchCategoryBySlug, fetchProductsByCategory, fetchCategories } from "../../wix/storefront/catalog";
222
+ import type { Category, ProductSummary } from "../../wix/storefront/types";
223
+ import { WIX_APPS } from "@wix/essentials";
224
+ import { SEO } from "@wix/seo/components";
225
+ import { loadSEOTagsServiceConfig } from "@wix/seo/services";
226
+ import { seoTags } from "@wix/seo";
227
+
228
+ export const wixMetadata = {
229
+ appDefId: WIX_APPS.checkoutAndOrders.id,
230
+ pageIdentifier: WIX_APPS.checkoutAndOrders.categoryPageMetadata.pageIdentifier,
231
+ identifiers: { slug: WIX_APPS.checkoutAndOrders.categoryPageMetadata.identifiers.handle },
232
+ };
233
+
234
+ const slug = Astro.params.slug!;
235
+ const forwardedUrl = Astro.request.headers.get("x-wix-forwarded-url");
236
+ const pageUrl =
237
+ forwardedUrl && URL.canParse(forwardedUrl) && /^https?:$/.test(new URL(forwardedUrl).protocol)
238
+ ? forwardedUrl
239
+ : Astro.url.href;
240
+
241
+ let category: Category | null = null;
242
+ let products: ProductSummary[] = [];
243
+ let categories: Category[] = [];
244
+ let seoTagsServiceConfig = null;
245
+ try {
246
+ [category, categories, seoTagsServiceConfig] = await Promise.all([
247
+ fetchCategoryBySlug(slug),
248
+ fetchCategories(),
249
+ loadSEOTagsServiceConfig({ pageUrl, itemType: seoTags.ItemType.STORES_CATEGORY, itemData: { slug } }),
250
+ ]);
251
+ if (category) products = await fetchProductsByCategory(category.id, { limit: 24 });
252
+ } catch {
253
+ // Guarded: an unhandled SSR throw truncates the response mid-stream.
254
+ }
255
+
256
+ // A missing or hidden category is a real 404 — never a fallback to all products.
257
+ if (!category) {
258
+ return new Response(null, { status: 404 });
259
+ }
260
+ ---
261
+ <SiteLayout title={category.name}>
262
+ <SEO.Tags seoTagsServiceConfig={seoTagsServiceConfig} slot="seo-tags" />
263
+ <!-- your heading: category.name, category.description when present, then: -->
264
+ <ShopView client:load initialProducts={products} initialCategories={categories} initialCategoryId={category.id} />
265
+ </SiteLayout>
266
+ ```
267
+
138
268
  ```astro
139
269
  ---
140
270
  // src/pages/products/[slug].astro — YOU create it. This is a Wix ITEM PAGE: the wixMetadata
@@ -191,26 +321,63 @@ if (!product) {
191
321
  ```
192
322
 
193
323
  ```tsx
194
- // src/components/storefront/ShopView.tsx — YOU build it; your shop.astro mounts it.
324
+ // src/components/storefront/ShopView.tsx — YOU build it; shop.astro AND category/[slug].astro mount it.
195
325
  import { useShop } from "../../hooks/storefront/useShop";
326
+ import FilterPanel from "./FilterPanel";
327
+ import QuickAdd from "./QuickAdd";
328
+ import { imgAttrs } from "../../wix/media";
196
329
  import type { Category, ProductSummary } from "../../wix/storefront/types";
197
330
 
198
331
  export default function ShopView(props: {
199
- initialProducts?: ProductSummary[]; // SSR props from your shop.astro — pass straight
200
- initialCategories?: Category[]; // to useShop; omitted in a SPA (client fetch)
332
+ initialProducts?: ProductSummary[]; // SSR props from your page — pass straight to useShop;
333
+ initialCategories?: Category[]; // omitted in a SPA (client fetch)
334
+ initialCategoryId?: string | null; // the category page passes its category's id
201
335
  }) {
202
- const { products, categories, activeCategoryId, setActiveCategoryId, loading, error } =
203
- useShop(props);
336
+ const shop = useShop(props);
337
+ const { products, categories, activeCategoryId, loading, error } = shop;
204
338
  // …you implement the render:
205
- // • category pills when categories.length > 1 — All + one per category,
206
- // driving setActiveCategoryId(id | null)
207
- // • a sort control over SORTS driving setSort; filters (price bounds, in-stock,
208
- // name search) via setFilters — include the ones that fit the brief, not all of them
339
+ // • a category row when categories.length > 1: LINKS to `/category/${c.slug}` (plus "All" →
340
+ // /shop), the active one marked by activeCategoryId — real URLs, not only pills that
341
+ // swap state (setActiveCategoryId is fine for an additional live switch on /shop)
342
+ // • <FilterPanel shop={shop}> … </FilterPanel> WRAPS your results — shipped: the toolbar
343
+ // (result count, sort), active chips, a filter sidebar on md+ (price slider, availability,
344
+ // the option facets, collapsible) beside your grid, a sheet under md. Your loading / empty /
345
+ // error states and your grid go inside it as children. Always mounted; it shows only the
346
+ // facets this catalog actually has. No sort control or filter UI of your own.
209
347
  // • error → a short inline message (retry() re-runs the query)
210
- // • products === null (or loading) → skeleton tiles; [] → your honest empty state
211
- // • else YOUR grid of YOUR tiles (ProductSummary contract above): image
212
- // (hoverImageUrl on hover), name, price/compareAtPrice, ribbon, optionsSummary;
213
- // tile links to `/products/${p.slug}`; quickAddable → useCart().addToCart(p.id)
348
+ // • products === null (or loading) → skeleton tiles; [] → your honest empty state, and a
349
+ // distinct "no products match these filters" with clearFilters() when hasActiveFilters
350
+ // • else YOUR grid of YOUR tiles (ProductSummary contract above): image via
351
+ // <img {...imgAttrs(p.imageUrl, "(min-width: 768px) 25vw, 50vw")} alt={p.name} /> — src,
352
+ // srcSet, sizes and lazy loading in one spread, so a tile never ships srcSet without src;
353
+ // an empty imageUrl gives {} — render your placeholder then; hoverImageUrl
354
+ // on hover — name, price — a range when
355
+ // price !== maxPrice, else price + labelled compareAtPrice — EVERY ribbon from ribbons,
356
+ // swatches as small color dots when present (else optionsSummary as text); tile links to
357
+ // `/products/${p.slug}`; and <QuickAdd product={p} /> as the LAST ROW of the tile's text
358
+ // block, under name and price, full width — the shipped buy control (direct add / option
359
+ // picker / product page, decided from the product). It is a direct child of the tile root,
360
+ // which carries `relative`: the picker anchors to that root and takes the tile's width.
361
+ // THE TILE ROOT IS A <div className="relative flex flex-col">, NOT THE LINK: the <a> wraps
362
+ // the image and the name/price, and <QuickAdd> sits beside it as the flex column's last child
363
+ // (it pins itself to the bottom with mt-auto). A button inside an <a> is invalid HTML and its
364
+ // click navigates to the product page instead of adding.
365
+ // • ROW RHYTHM: the grid stretches every tile in a row to the tallest; with the tile a flex
366
+ // column and the control pinned to the bottom, the buy buttons share one baseline across the
367
+ // row even when one tile has swatches, a struck price, or a two-line name and its neighbours
368
+ // don't. Put the swatches between price and control; never let them push only that tile's
369
+ // button down. Never overlay the control on the image or float it
370
+ // between image and text, and never wrap it in a smaller positioned box (the picker would
371
+ // inherit that box's width).
372
+ // • the name WRAPS (`min-w-0`, `break-words`) — no `truncate` / `line-clamp-1`: a shopper reads
373
+ // "Red Velvet Cupcake 4-Pack", not "Red Velvet Cupc…"; price on its own line under it.
374
+ // • badges come ONLY from p.ribbons. Do NOT render a "Sale" badge because compareAtPrice
375
+ // is set — the struck price already says it, and a product the merchant ribboned "Sale"
376
+ // would show the badge twice.
377
+ // • name and price on SEPARATE lines — never one flex row where a long name and a price
378
+ // range fight for width and the price gets clipped at 390px. Keep the page intro short
379
+ // enough that a full tile (image, name, price) is in the first screen, also on a short
380
+ // desktop window.
214
381
  // • hasMore → your "load more" control calling loadMore() (disabled while loadingMore)
215
382
  }
216
383
  ```
@@ -219,6 +386,7 @@ export default function ShopView(props: {
219
386
  // src/components/storefront/ProductDetailView.tsx — YOU build the whole PDP surface;
220
387
  // your [slug].astro mounts it with the server-fetched product.
221
388
  import { useProductDetail } from "../../hooks/storefront/useProductDetail";
389
+ import OptionPicker from "./OptionPicker";
222
390
  import type { ProductDetail } from "../../wix/storefront/types";
223
391
 
224
392
  export default function ProductDetailView(props: {
@@ -236,31 +404,145 @@ export default function ProductDetailView(props: {
236
404
  // to full-width images stacked down the column (products with per-color
237
405
  // linked media carry several gallery urls, so that stacks big duplicates).
238
406
  // A single-image gallery is just the one primary — no empty strip.
239
- // • name, live d.price / d.compareAtPrice (strike only when they differ),
240
- // descriptionHtml rendered as HTML
241
- // • option controls from d.optionGroups → d.selectOption(optionName, choiceName)
242
- // (isColor → real swatches via colorCode; disable out-of-stock choices);
243
- // modifiers from d.product.modifiers → d.setModifier(key, value), "*" = mandatory
244
- // • quantity (d.quantity / d.setQuantity), then the buy button gated by d.canAdd
245
- // ONLY — never resolve variants yourself — calling d.add(); d.adding disables,
246
- // d.error renders inline
407
+ // • name, EVERY ribbon (d.product.ribbons), live d.price (the range until every option
408
+ // is picked) with d.compareAtPrice as a labelled "was" when present — never invent one;
409
+ // descriptionHtml rendered as HTML, then d.product.infoSections as sections/accordions
410
+ // • <OptionPicker detail={d} showQuantity /> right under the price — the shipped option
411
+ // groups (swatches for a color option, sold-out choices disabled), modifiers, quantity,
412
+ // and the buy button gated by the hook with its plain reason, "Pre-order" when it applies,
413
+ // the add error inline. Never resolve variants or gate the button yourself.
414
+ // • in the first screen at mobile AND desktop: the image, name, price, the first choice,
415
+ // and the button with its reason — a shopper decides without scrolling. On a phone that
416
+ // means the primary image is a bounded band, not a full-height hero: e.g. the gallery
417
+ // column `max-h-[45vh] md:max-h-none` with `object-contain`, thumbnails as a row under
418
+ // it, and the two-column split only from md (`md:grid md:grid-cols-2`); description and
419
+ // infoSections come AFTER the buy button, never between the price and the action.
247
420
  }
248
421
  ```
249
422
 
423
+ ### The reference files for stacks where the components don't deploy
424
+
425
+ On `lib`, `static`, and a port, nothing under `components/` or `hooks/` arrives. The state
426
+ machines behind the hooks do arrive — `wix/storefront/shop-store.ts`, `product-detail-store.ts`,
427
+ `cart-store.ts` — so you never rewrite them: create a store per surface, `subscribe`, render from
428
+ `getState()`, call its actions. Their `*State` interfaces are the render contract; read those.
429
+ What you write is the rendering — grid, product page, picker, filter panel, drawer — and for that
430
+ read these first; they are tested code for exactly that behaviour, and rewriting them from the
431
+ prose above is where the bugs come from:
432
+
433
+ 1. `components/storefront/QuickAdd.tsx` — the three purchase paths decided from the summary DTO;
434
+ the panel is positioned inside the tile (the tile is `relative`), a bottom sheet on small
435
+ screens, never `fixed` with computed offsets; it closes on Escape, the close button, a
436
+ successful add, or the scrim — there is NO outside-click handler (one that runs after a
437
+ re-render sees the clicked swatch detached and closes on every pick).
438
+ 2. `components/storefront/OptionPicker.tsx` — the purchase controls as working code: swatches vs
439
+ pills, sold-out choices disabled, quantity, the gated button with its reason and the
440
+ "Pre-order" label; the PDP and the tile picker share it.
441
+ 3. `components/storefront/CartDrawer.tsx` — the overlay contract as working code: root-level,
442
+ scrim, scroll lock, Escape, focus in and back.
443
+ 4. `components/storefront/FilterPanel.tsx` — inline commits at once, the sheet stages until Apply;
444
+ the price pair commits only when valid.
445
+
446
+ All under `references/storefront/app/`.
447
+
250
448
  ### Wiring — Astro (default)
251
449
 
252
450
  1. Set the `@theme` tokens (one edit); brand `SiteLayout.astro` (one pass).
253
- 2. Create the shop and PDP pages and their islands per the skeletons above — frontmatter
254
- machinery exact, presentation yours. Primary-content islands mount `client:load` with the
451
+ 2. Create the shop, category, and PDP pages and their islands per the skeletons above —
452
+ frontmatter machinery exact, presentation yours. Primary-content islands mount `client:load` with the
255
453
  SSR props; browser-state widgets (cart) are `client:only="react"`.
256
454
  3. Write `pages/index.astro` (home) on `SiteLayout`.
257
455
 
456
+ ### Wiring — another JS framework (`--stack lib`: Vue, Svelte, Solid, plain Vite)
457
+
458
+ Read the reference files listed above before writing any surface.
459
+
460
+ `deploy.mjs storefront --stack lib` put the data layer in `src/wix/` and nothing else: `sdk.ts`
461
+ (the visitor client, configured with the public client id), `media.ts`, `money.ts`, and
462
+ `wix/storefront/` — `catalog.ts`, `cart.ts`, `types.ts`, the `*-core.ts` rules, and the three
463
+ stores `shop-store.ts`, `product-detail-store.ts`, `cart-store.ts`. None of it is React. The
464
+ hooks and components don't ship on this stack; the stores replace the hooks, and you write the
465
+ components in your framework to the contracts on this page:
466
+
467
+ - bind the stores with your framework's external-store primitive (Vue: `shallowRef` updated in
468
+ `subscribe`; Svelte: `readable(store.getState(), (set) => store.subscribe(() => set(store.getState())))`;
469
+ Solid: a signal set in `subscribe`). `createShopStore(options)` per listing (`start()` when
470
+ mounted, `stop()` when unmounted), `createProductDetailStore({ initial | slug })` per product
471
+ surface, the cart store as-is (module-level). State in, actions out — exactly the hooks'
472
+ contracts above;
473
+ - your filter panel, quick add, and cart drawer to the contracts in "What a complete storefront
474
+ shows" — the shipped `FilterPanel.tsx`, `QuickAdd.tsx`, `CartDrawer.tsx` are readable as
475
+ behaviour specs (the overlay contract, the three purchase paths, the sidebar/sheet split).
476
+
477
+ Routes `/shop`, `/category/:slug` (via `fetchCategoryBySlug`, null → your 404), `/products/:slug`;
478
+ dev server on 4321; a static build goes through `npx @wix/cli@latest release` with
479
+ `site.outputDirectory` pointing at the build folder, an SSR build is hosted by you. Item-page tags
480
+ from the entity's `seoData`.
481
+
482
+ ### Wiring — static site (`--stack static`, no bundler)
483
+
484
+ Read the reference files listed above before writing any surface.
485
+
486
+ `deploy.mjs storefront --stack static --out site` put the REST layer in `site/js/wix/` (browser
487
+ ESM, the `.ts` beside each `.js` for reading). Everything the visitor loads lives under `site/` —
488
+ pages, styles, `js/` — and `wix.config.json`'s `site.outputDirectory` is `"./site"`; the project
489
+ root (config, plan, seed output) is never the upload. Same function names and DTOs as the table
490
+ above, so the contracts on this page hold unchanged: `searchCatalog`, `fetchFacetData`, `fetchProductBySlug`,
491
+ `fetchCategories`, `fetchCategoryBySlug`, `resolveVariant` from `./js/wix/catalog.js`;
492
+ `fetchCart`, `addToCart`, `updateQuantity`, `removeLine`, `checkoutUrl` from `./js/wix/cart.js`.
493
+ The state machines ship too: `createShopStore` from `./js/wix/shop-store.js` (the listing —
494
+ selection, facets, URL sync, cursor paging; `start()` once the page is up), `createProductDetailStore`
495
+ from `./js/wix/product-detail-store.js` (the PDP and every quick-add picker — selections start
496
+ empty, `resolveVariant`, `canAdd`/`blockedReason`, `add()`), and `./js/wix/cart-store.js` (the
497
+ cart, `subscribeCart`/`getCartState`, `addLine`, `updateLineQuantity`, `removeCartLine`,
498
+ `goToCheckout`, `setCartOpen`). No components ship — you write the rendering in plain JS: one
499
+ render function per surface that reads `getState()`, called from `subscribe`, with the
500
+ surface's controls calling the store's actions. The drawer opens after every add on its own;
501
+ overlays follow the CartDrawer contract (root-level, scroll lock, Escape, focus back). Pages are
502
+ `shop.html`, `category.html?slug=…`,
503
+ `product.html?slug=…`. Set `document.title` and the meta description from the entity's `seoData`
504
+ once it loads, on the product AND category pages. The visitor token persists in `localStorage` on
505
+ its own; never mint per page. `npx @wix/cli@latest release` uploads `site/`.
506
+
507
+ ### Wiring — server-rendered, another language (Flask, Laravel, Rails, …)
508
+
509
+ Read the reference files listed above before writing any surface.
510
+
511
+ Run `deploy.mjs storefront --stack static` in the project folder anyway: `js/wix/` is both the
512
+ browser-side code and the readable spec. Then split by where the call runs. **Reads on the
513
+ server:** port `js/wix/catalog.ts` and `catalog-core.ts` to your language — the same six functions
514
+ returning the same DTO shapes as dicts, one anonymous visitor token per process for these public
515
+ reads (mint and refresh per `client.ts`) — and render shop, category, and PDP in your templates to
516
+ the contracts above, so product names and prices are in the HTML; item-page tags from the entity's
517
+ `seoData`. **Buying in the browser:** the cart drawer on `./js/wix/cart-store.js`, the PDP's
518
+ option picker and the tile's quick add on `./js/wix/product-detail-store.js` (pass the product's
519
+ slug, or the rendered `ProductDetail` as `initial` in a JSON script tag), exactly as the static
520
+ wiring above — the browser owns the shopper's visitor token, so the server never handles
521
+ per-shopper tokens. Routes stay `/shop`, `/category/<slug>`, `/products/<slug>`. Add your public https origin
522
+ to the OAuth app's allowed domains before checkout can return.
523
+
524
+ **Pre-rendered (Frozen-Flask, Pelican, any static-site generator) → Wix-hosted.** Same port for
525
+ the reads, run at build time with one anonymous token; the generator must emit every product and
526
+ category page (a URL generator over `fetchCategories()` plus a full `searchCatalog` walk by
527
+ cursor — never only the first page). Run `deploy.mjs storefront --stack static --out <build dir>`
528
+ so `js/wix/` is inside the output the pages import from, point `site.outputDirectory` at that
529
+ folder, `wix release`. Pages sit at different depths (`/`, `/category/…`, `/products/…`): give
530
+ the templates one base path to `js/wix/` (a template variable, or root-relative `/js/wix/…`),
531
+ never a relative `./js/wix/` — it breaks one level down. The frozen grid is the first paint; the
532
+ shop's sort, filters, facets, search, and load-more still run client-side on it through
533
+ `createShopStore()` from `./js/wix/shop-store.js`, exactly as on a static site, so the gallery
534
+ contract above applies. Close
535
+ with the live URL, the rebuild + release command, and one line for the owner: dashboard edits to
536
+ the catalog reach the site when that command runs; cart and checkout are live regardless.
537
+
258
538
  ### Wiring — React SPA (Vite etc.)
259
539
 
260
540
  Import `./styles/global.css` once at the app entry (needs `@tailwindcss/vite` in the vite
261
541
  config plugins — deploy already added the dep). Write route wrappers in the project's router:
262
- `/shop` → your shop component; `/products/:slug` → your detail component
263
- (`useProductDetail({ slug })` — components fetch client-side when no `initial` is passed).
542
+ `/shop` → your shop component; `/category/:slug` → the same shop component with
543
+ `initialCategoryId` resolved from `fetchCategoryBySlug(slug)` (null → your 404 view);
544
+ `/products/:slug` → your detail component (`useProductDetail({ slug })` — components fetch
545
+ client-side when no `initial` is passed).
264
546
  Mount the shipped `CartButton` in the header and `CartDrawer` once. Deploy wrote the public
265
547
  client id into `wix/config.ts`; nothing else to configure.
266
548
 
@@ -270,12 +552,25 @@ client id into `wix/config.ts`; nothing else to configure.
270
552
  internals or re-derive a request shape. Extend by calling the exports or adding a new
271
553
  function in `wix/storefront/` for what they don't cover (API contracts: the `wix-docs` skill).
272
554
  - **Selection→cart goes through `useProductDetail`** — never add a product with options by
273
- picking `variants[0]`, and never gate `canAdd` yourself.
555
+ picking `variants[0]`, and never gate `canAdd` yourself. On the PDP that is the shipped
556
+ `OptionPicker`; in the gallery the shipped `QuickAdd` — a tile never adds a product with options itself, and never hides the buy path
557
+ behind "go to the product page" for a product that has no options.
558
+ - **Filters are the shipped `FilterPanel`** — mounted in the gallery whenever the store has a
559
+ catalog to filter; not rebuilt with fewer controls, not dropped because the brief didn't ask.
560
+ - **Categories are pages** — `/category/[slug]` with its SEO block, linked from the chrome; a
561
+ category that exists only as a state toggle on `/shop` has no URL to share or index.
274
562
  - Don't wrap shipped calls in your own API routes — they run client-side by design.
275
- - Theme via the `@theme` tokens; your markup uses Tailwind utilities on the same tokens. No
276
- parallel theme files, no hardcoded palette values in components.
563
+ - Where the shipped components deploy (Astro, React): theme via the `@theme` tokens, and your
564
+ markup uses Tailwind utilities on the same tokens — one design system across shipped and written
565
+ code. No parallel theme files, no hardcoded palette values in components. Where they don't
566
+ (`lib`, `static`, a port): style with whatever your stack does well, on one token set of your
567
+ own; the rule that survives is the token set, not Tailwind.
277
568
  - Checkout only through the shipped cart (`checkout()`) — never a hand-built checkout URL.
278
569
  - Live data or an honest empty state — never mock products, prices, reviews, or counts.
570
+ - **Prices and ribbons come from the DTOs as-is** — no computed percent-off, no "Sale" badge
571
+ inferred from `compareAtPrice`, no struck price beside a range (the DTO already withholds it).
572
+ - **Cart totals come from `cart`** — never summed or hardcoded in the client; shipping and tax say
573
+ "calculated at checkout" (the drawer already does).
279
574
  - Your PDP page carries the SEO pieces (`wixMetadata` + `loadSEOTagsServiceConfig` +
280
575
  `<SEO.Tags>`) exactly as the skeleton shows — owners edit those tags in their dashboard.
281
576
  - **Browsing, cart and checkout need no login.** They run on the Wix visitor session the
@@ -291,28 +586,23 @@ client id into `wix/config.ts`; nothing else to configure.
291
586
 
292
587
  ## Point the user to their dashboard
293
588
 
294
- Give the owner the dashboard, products, and categories links — **the deploy step's JSON
295
- output already printed them ready-made** (`dashboardUrl`, `productsUrl`, `categoriesUrl`);
296
- copy, don't re-derive. Real payments additionally need a premium plan + a connected payment
297
- method (dashboard) — mention it, don't treat it as a code failure.
589
+ Hand the owner these links — `{siteId}` is `siteId` in `wix.config.json` (the deploy JSON also prints
590
+ `dashboardUrl`, `productsUrl`, `categoriesUrl`); an entity id fills the placeholder from the seed result.
591
+
592
+ | page | `https://manage.wix.com/dashboard/{siteId}/` + |
593
+ |---|---|
594
+ | Products | `wix-stores/products` |
595
+ | Edit a product | `wix-stores/products/product/{productId}` |
596
+ | Categories | `wix-stores/categories/list` |
597
+ | Inventory | `wix-stores/inventory` |
598
+ | Orders | `ecom-platform/orders-list` |
599
+ | Store settings | `wix-stores/settings` |
600
+
601
+ Real payments additionally need a premium plan + a connected payment method (Settings → Accept
602
+ payments) — mention it, don't treat it as a code failure.
298
603
 
299
604
  ## Seeding
300
605
 
301
606
  Per `seed/SEED.md` — a plain-data `plan.json` into `seed-store.mjs`, run from the project
302
607
  root. Independent of the frontend work; seed a catalog that exercises the UI (≥1 product with
303
608
  a color option, ≥1 on sale, an image per product) unless the brief says otherwise.
304
-
305
- ## Verify (before declaring done)
306
-
307
- - [ ] `/shop` renders live products SSR (view-source shows product names) through **your**
308
- grid/card; category bar filters when categories exist; empty catalog shows your honest
309
- empty state.
310
- - [ ] Your PDP: color options render as swatches, add is disabled until every option is
311
- picked (`canAdd`), the resolved variant's price shows, a sale shows the strikethrough.
312
- - [ ] Cart: add / quantity ± / remove work; badge count is live; subtotal shows; cart survives
313
- a reload (same visitor token).
314
- - [ ] Checkout button redirects to Wix-hosted checkout.
315
- - [ ] PDP view-source carries the SEO tags (Astro).
316
- - [ ] Shop/PDP/home are YOUR designs on the tokens; the data-layer/hook/cart files are
317
- unedited.
318
- - [ ] Dashboard links handed to the owner.