@mohasinac/appkit 4.53.0 → 4.53.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 (500) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/scaffolds/AppShell.js +1 -1
  3. package/dist/_internal/server/features/auctions/og.d.ts +3 -1
  4. package/dist/_internal/server/features/auctions/og.js +3 -1
  5. package/dist/_internal/server/features/bundles/actions.js +1 -1
  6. package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
  7. package/dist/_internal/server/features/business-documents/download.js +91 -0
  8. package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
  9. package/dist/_internal/server/features/categories/adapters.js +10 -0
  10. package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
  11. package/dist/_internal/server/features/checkout/actions.js +106 -12
  12. package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
  13. package/dist/_internal/server/features/checkout/data.d.ts +22 -0
  14. package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
  15. package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
  16. package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
  17. package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
  18. package/dist/_internal/server/features/classified/metadata.js +1 -1
  19. package/dist/_internal/server/features/classified/og.d.ts +3 -1
  20. package/dist/_internal/server/features/classified/og.js +3 -1
  21. package/dist/_internal/server/features/digital-code/metadata.js +1 -1
  22. package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
  23. package/dist/_internal/server/features/digital-code/og.js +3 -1
  24. package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
  25. package/dist/_internal/server/features/digital-code/pool.js +19 -4
  26. package/dist/_internal/server/features/live/metadata.js +1 -1
  27. package/dist/_internal/server/features/live/og.d.ts +3 -1
  28. package/dist/_internal/server/features/live/og.js +3 -1
  29. package/dist/_internal/server/features/messages/actions.js +1 -1
  30. package/dist/_internal/server/features/parts/og.js +1 -1
  31. package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
  32. package/dist/_internal/server/features/pre-orders/og.js +3 -1
  33. package/dist/_internal/server/features/price-index/search.d.ts +18 -0
  34. package/dist/_internal/server/features/price-index/search.js +47 -7
  35. package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
  36. package/dist/_internal/server/features/prize-draws/og.js +3 -1
  37. package/dist/_internal/server/features/products/group-members.d.ts +26 -0
  38. package/dist/_internal/server/features/products/group-members.js +7 -1
  39. package/dist/_internal/server/features/products/og.d.ts +3 -1
  40. package/dist/_internal/server/features/products/og.js +3 -1
  41. package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
  42. package/dist/_internal/server/features/search/scoped-sources.js +252 -0
  43. package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
  44. package/dist/_internal/server/features/search/search-everything.js +70 -20
  45. package/dist/_internal/server/features/seo/sitemap.js +6 -0
  46. package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
  47. package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
  48. package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
  49. package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
  50. package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
  51. package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
  52. package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
  53. package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
  54. package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
  55. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  56. package/dist/_internal/server/features/stores/adapters.js +16 -0
  57. package/dist/_internal/server/features/stores/index.d.ts +1 -0
  58. package/dist/_internal/server/features/stores/index.js +1 -0
  59. package/dist/_internal/server/features/stores/tabs.d.ts +25 -0
  60. package/dist/_internal/server/features/stores/tabs.js +122 -0
  61. package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
  62. package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
  63. package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
  64. package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
  65. package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
  66. package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
  67. package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
  68. package/dist/_internal/shared/checkout/rules/index.js +9 -0
  69. package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
  70. package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
  71. package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
  72. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
  73. package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
  74. package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
  75. package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
  76. package/dist/_internal/shared/config/schema.d.ts +122 -0
  77. package/dist/_internal/shared/features/cart/schema.js +3 -2
  78. package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
  79. package/dist/_internal/shared/features/products/group-rules.js +142 -0
  80. package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
  81. package/dist/_internal/shared/listing-types/_registry.js +39 -1
  82. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  83. package/dist/_internal/shared/listing-types/art/config.js +2 -0
  84. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  85. package/dist/_internal/shared/listing-types/auction/config.js +2 -0
  86. package/dist/_internal/shared/listing-types/availability.js +48 -0
  87. package/dist/_internal/shared/listing-types/capabilities.js +19 -0
  88. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  89. package/dist/_internal/shared/listing-types/classified/config.js +2 -0
  90. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  91. package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
  92. package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
  93. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  94. package/dist/_internal/shared/listing-types/live/config.js +2 -0
  95. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  96. package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
  97. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  98. package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
  99. package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
  100. package/dist/_internal/shared/listing-types/spares/config.js +60 -0
  101. package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
  102. package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
  103. package/dist/_internal/shared/listing-types/spares/og.js +1 -0
  104. package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
  105. package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
  106. package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
  107. package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
  108. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  109. package/dist/_internal/shared/listing-types/standard/config.js +2 -0
  110. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  111. package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
  112. package/dist/_internal/shared/search/match.d.ts +11 -0
  113. package/dist/_internal/shared/search/match.js +2 -1
  114. package/dist/_internal/shared/search/to-groups.d.ts +52 -0
  115. package/dist/_internal/shared/search/to-groups.js +141 -0
  116. package/dist/client.d.ts +8 -2
  117. package/dist/client.js +4 -1
  118. package/dist/constants/api-endpoints.d.ts +39 -15
  119. package/dist/constants/api-endpoints.js +28 -5
  120. package/dist/constants/cache-invalidation.js +3 -1
  121. package/dist/constants/field-names.d.ts +6 -0
  122. package/dist/constants/field-names.js +6 -0
  123. package/dist/features/about/components/PolicyPageView.d.ts +22 -2
  124. package/dist/features/about/components/PolicyPageView.js +14 -2
  125. package/dist/features/about/components/PublicProfileView.js +2 -1
  126. package/dist/features/about/components/ShippingPolicyView.js +42 -2
  127. package/dist/features/account/components/NotificationBell.js +1 -1
  128. package/dist/features/account/hooks/useProfileStats.js +5 -1
  129. package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
  130. package/dist/features/addresses/repository/addresses.repository.js +21 -0
  131. package/dist/features/admin/actions/admin-actions.js +16 -1
  132. package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
  133. package/dist/features/admin/components/AdminNavigationView.js +1 -1
  134. package/dist/features/admin/components/AdminPageHeader.js +1 -1
  135. package/dist/features/admin/components/AdminProductEditorView.js +17 -2
  136. package/dist/features/admin/components/AdminSidebar.js +1 -1
  137. package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
  138. package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
  139. package/dist/features/admin/components/AdminSparesView.js +30 -0
  140. package/dist/features/admin/components/AdminUsersView.js +9 -7
  141. package/dist/features/admin/components/index.d.ts +2 -0
  142. package/dist/features/admin/components/index.js +1 -0
  143. package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
  144. package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
  145. package/dist/features/admin/constants/filter-tabs.js +1 -0
  146. package/dist/features/admin/schemas/firestore.d.ts +26 -2
  147. package/dist/features/auctions/components/AuctionDetailPageView.js +4 -11
  148. package/dist/features/auth/repository/user.repository.d.ts +13 -0
  149. package/dist/features/auth/repository/user.repository.js +28 -0
  150. package/dist/features/auth/schemas/firestore.d.ts +2 -0
  151. package/dist/features/auth/schemas/firestore.js +2 -0
  152. package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
  153. package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
  154. package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
  155. package/dist/features/business-documents/schemas/firestore.js +75 -0
  156. package/dist/features/cart/actions/cart-actions.js +83 -6
  157. package/dist/features/cart/repository/cart.repository.js +32 -4
  158. package/dist/features/cart/schemas/firestore.d.ts +20 -0
  159. package/dist/features/cart/schemas/index.d.ts +2 -2
  160. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
  161. package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
  162. package/dist/features/categories/api/[id]/price-guidance/route.js +113 -0
  163. package/dist/features/categories/components/BrandDetailPageView.js +19 -3
  164. package/dist/features/categories/components/BrandDetailTabs.js +10 -0
  165. package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
  166. package/dist/features/categories/components/CategoryDetailPageView.js +141 -14
  167. package/dist/features/categories/components/CategoryDetailTabs.js +12 -0
  168. package/dist/features/categories/components/CategoryGrid.js +10 -1
  169. package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
  170. package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
  171. package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
  172. package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
  173. package/dist/features/categories/constants/category-tabs.d.ts +20 -25
  174. package/dist/features/categories/constants/category-tabs.js +22 -39
  175. package/dist/features/categories/repository/categories.repository.d.ts +12 -0
  176. package/dist/features/categories/repository/categories.repository.js +20 -0
  177. package/dist/features/categories/schemas/firestore.d.ts +22 -1
  178. package/dist/features/categories/schemas/firestore.js +10 -0
  179. package/dist/features/categories/schemas/index.d.ts +6 -0
  180. package/dist/features/categories/schemas/index.js +2 -0
  181. package/dist/features/categories/types/index.d.ts +8 -0
  182. package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
  183. package/dist/features/categories/utils/taxonomy-visual.js +26 -10
  184. package/dist/features/classified/components/ClassifiedDetailPageView.js +4 -9
  185. package/dist/features/contact/components/ContactPageView.d.ts +33 -1
  186. package/dist/features/contact/components/ContactPageView.js +58 -28
  187. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +4 -9
  188. package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
  189. package/dist/features/grouped/schemas/firestore.d.ts +5 -0
  190. package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
  191. package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
  192. package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
  193. package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
  194. package/dist/features/homepage/components/HowItWorksSection.js +4 -4
  195. package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
  196. package/dist/features/homepage/components/PromoGrid.js +12 -3
  197. package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
  198. package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
  199. package/dist/features/homepage/components/index.d.ts +0 -20
  200. package/dist/features/homepage/components/index.js +0 -11
  201. package/dist/features/homepage/index.d.ts +0 -2
  202. package/dist/features/homepage/index.js +0 -2
  203. package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
  204. package/dist/features/homepage/lib/section-renderer.js +202 -58
  205. package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
  206. package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
  207. package/dist/features/homepage/schemas/firestore.d.ts +106 -3
  208. package/dist/features/homepage/schemas/firestore.js +32 -2
  209. package/dist/features/homepage/schemas/index.d.ts +36 -7
  210. package/dist/features/homepage/schemas/index.js +39 -27
  211. package/dist/features/homepage/server.d.ts +0 -3
  212. package/dist/features/homepage/server.js +0 -3
  213. package/dist/features/homepage/types/index.d.ts +0 -47
  214. package/dist/features/homepage/types/index.js +49 -0
  215. package/dist/features/layout/AppLayoutShell.js +1 -1
  216. package/dist/features/layout/BottomNavItem.js +1 -1
  217. package/dist/features/layout/BottomSheet.js +1 -1
  218. package/dist/features/layout/TitleBarLayout.js +1 -1
  219. package/dist/features/live/components/LiveItemDetailPageView.js +4 -9
  220. package/dist/features/media/hooks/useMedia.js +17 -0
  221. package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
  222. package/dist/features/media/repository/media-assets.repository.js +31 -0
  223. package/dist/features/media/trust-signals.d.ts +85 -0
  224. package/dist/features/media/trust-signals.js +126 -0
  225. package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
  226. package/dist/features/parts/components/PartDetailPageView.js +2 -2
  227. package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
  228. package/dist/features/parts/components/PartsIndexPageView.js +2 -2
  229. package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
  230. package/dist/features/parts/components/PartsSearchBox.js +57 -0
  231. package/dist/features/parts/schemas/firestore.d.ts +149 -3
  232. package/dist/features/parts/schemas/firestore.js +184 -0
  233. package/dist/features/payments/repository/payout.repository.d.ts +15 -0
  234. package/dist/features/payments/repository/payout.repository.js +21 -0
  235. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -11
  236. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
  237. package/dist/features/price-index/components/PriceIndexToolView.d.ts +3 -1
  238. package/dist/features/price-index/components/PriceIndexToolView.js +92 -13
  239. package/dist/features/products/components/CompareOverlay.d.ts +18 -1
  240. package/dist/features/products/components/CompareOverlay.js +21 -2
  241. package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
  242. package/dist/features/products/components/CustomSectionTabContent.js +29 -3
  243. package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
  244. package/dist/features/products/components/GroupMemberPicker.js +46 -8
  245. package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
  246. package/dist/features/products/components/GroupSettingsPanel.js +91 -3
  247. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  248. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
  249. package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
  250. package/dist/features/products/components/ProductDetailPageView.js +74 -15
  251. package/dist/features/products/components/ProductForm.d.ts +13 -1
  252. package/dist/features/products/components/ProductForm.js +16 -2
  253. package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
  254. package/dist/features/products/components/ProductsIndexListing.js +3 -3
  255. package/dist/features/products/components/ShowGroupSection.js +15 -2
  256. package/dist/features/products/components/SparesListView.d.ts +7 -0
  257. package/dist/features/products/components/SparesListView.js +39 -0
  258. package/dist/features/products/constants/action-defs.d.ts +1 -0
  259. package/dist/features/products/constants/action-defs.js +1 -0
  260. package/dist/features/products/repository/products.repository.d.ts +98 -0
  261. package/dist/features/products/repository/products.repository.js +235 -17
  262. package/dist/features/products/schemas/firestore.d.ts +97 -4
  263. package/dist/features/products/schemas/firestore.js +104 -0
  264. package/dist/features/products/schemas/index.d.ts +10 -19
  265. package/dist/features/products/schemas/index.js +23 -6
  266. package/dist/features/products/types/index.d.ts +45 -1
  267. package/dist/features/products/utils/group-member-image.d.ts +39 -0
  268. package/dist/features/products/utils/group-member-image.js +3 -0
  269. package/dist/features/products/utils/listing-badge-variant.js +4 -0
  270. package/dist/features/reviews/components/ReviewsList.js +1 -1
  271. package/dist/features/scams/components/ScamProfileView.js +1 -1
  272. package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
  273. package/dist/features/search/components/EntityTypeahead.js +92 -0
  274. package/dist/features/search/components/Search.d.ts +12 -1
  275. package/dist/features/search/components/Search.js +47 -3
  276. package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
  277. package/dist/features/search/components/SearchFacetRail.js +92 -0
  278. package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
  279. package/dist/features/search/components/SearchResultsClient.js +103 -0
  280. package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
  281. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  282. package/dist/features/search/components/index.d.ts +0 -6
  283. package/dist/features/search/components/index.js +0 -3
  284. package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
  285. package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
  286. package/dist/features/search/hooks/useScopedSearch.js +114 -0
  287. package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
  288. package/dist/features/search/hooks/useSiteSearch.js +149 -0
  289. package/dist/features/search/index.d.ts +0 -4
  290. package/dist/features/search/index.js +0 -4
  291. package/dist/features/search/manifest.d.ts +12 -0
  292. package/dist/features/search/manifest.js +17 -2
  293. package/dist/features/search/server.d.ts +0 -3
  294. package/dist/features/search/server.js +0 -3
  295. package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
  296. package/dist/features/seller/components/SellerListingTypeView.js +20 -0
  297. package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
  298. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  299. package/dist/features/seller/components/SellerProductShell.js +2 -1
  300. package/dist/features/seller/components/SellerSidebar.js +1 -1
  301. package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
  302. package/dist/features/seller/components/SellerSparesView.js +6 -0
  303. package/dist/features/seller/components/index.d.ts +2 -0
  304. package/dist/features/seller/components/index.js +1 -0
  305. package/dist/features/shell/CommandPalette.js +1 -1
  306. package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
  307. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
  308. package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
  309. package/dist/features/stores/components/StoreHeader.js +1 -2
  310. package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
  311. package/dist/features/stores/components/StoreSparesPageView.js +48 -0
  312. package/dist/features/stores/schemas/firestore.d.ts +19 -0
  313. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
  314. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
  315. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
  316. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +280 -45
  317. package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
  318. package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
  319. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
  320. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
  321. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
  322. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
  323. package/dist/features/tester/seed-data/authored/index.js +16 -0
  324. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
  325. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
  326. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
  327. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
  328. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
  329. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
  330. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
  331. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +372 -18
  332. package/dist/index.d.ts +17 -57
  333. package/dist/index.js +35 -96
  334. package/dist/next/routing/route-map.d.ts +42 -1
  335. package/dist/next/routing/route-map.js +45 -0
  336. package/dist/providers/db-firebase/base.repository.d.ts +22 -0
  337. package/dist/providers/db-firebase/base.repository.js +36 -0
  338. package/dist/schemas/registry.d.ts +15 -6
  339. package/dist/security/blind-index-tokens.d.ts +54 -0
  340. package/dist/security/blind-index-tokens.js +152 -0
  341. package/dist/security/index.d.ts +1 -1
  342. package/dist/security/index.js +1 -1
  343. package/dist/security/settings-encryption.d.ts +35 -0
  344. package/dist/security/settings-encryption.js +91 -0
  345. package/dist/seed/_helpers/brand-rows.js +38 -14
  346. package/dist/seed/_helpers/category-forest.js +73 -7
  347. package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
  348. package/dist/seed/_helpers/category-images-generated.js +81 -6
  349. package/dist/seed/_helpers/category-models.d.ts +1 -1
  350. package/dist/seed/_helpers/category-models.js +7 -1
  351. package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
  352. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  353. package/dist/seed/_helpers/category-parts.js +50 -2
  354. package/dist/seed/_helpers/character-features.d.ts +1 -1
  355. package/dist/seed/_helpers/character-features.js +2 -1
  356. package/dist/seed/_helpers/franchise-rows.js +10 -2
  357. package/dist/seed/_helpers/media.d.ts +47 -0
  358. package/dist/seed/_helpers/media.js +34 -0
  359. package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
  360. package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
  361. package/dist/seed/categories-seed-data.js +124 -20
  362. package/dist/seed/grouped-listings-seed-data.js +16 -0
  363. package/dist/seed/homepage-sections-seed-data.js +94 -5
  364. package/dist/seed/index.d.ts +2 -3
  365. package/dist/seed/index.js +2 -2
  366. package/dist/seed/manifest.js +3 -0
  367. package/dist/seed/media-assets-seed-data.d.ts +1 -1
  368. package/dist/seed/media-assets-seed-data.js +746 -196
  369. package/dist/seed/model-composition-seed-data.js +122 -6
  370. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  371. package/dist/seed/part-glossary-seed-data.js +37219 -463
  372. package/dist/seed/products-art-seed-data.js +7 -18
  373. package/dist/seed/products-auctions-seed-data.js +56 -47
  374. package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
  375. package/dist/seed/products-beyblade-x-seed-data.js +441 -0
  376. package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
  377. package/dist/seed/products-catalogue-seed-data.js +1500 -1240
  378. package/dist/seed/products-classifieds-seed-data.js +9 -27
  379. package/dist/seed/products-digital-codes-seed-data.js +9 -18
  380. package/dist/seed/products-live-items-seed-data.js +5 -13
  381. package/dist/seed/products-preorders-seed-data.js +67 -33
  382. package/dist/seed/products-prize-draws-seed-data.js +8 -17
  383. package/dist/seed/products-spares-seed-data.d.ts +2 -0
  384. package/dist/seed/products-spares-seed-data.js +368 -0
  385. package/dist/seed/products-standard-seed-data.js +39 -126
  386. package/dist/seed/products-stickers-seed-data.js +59 -15
  387. package/dist/seed/site-settings-seed-data.js +8 -0
  388. package/dist/seo/json-ld.d.ts +25 -0
  389. package/dist/seo/json-ld.js +28 -0
  390. package/dist/server-entry.d.ts +3 -1
  391. package/dist/server-entry.js +2 -2
  392. package/dist/server.d.ts +9 -9
  393. package/dist/server.js +27 -25
  394. package/dist/styles.css +202 -1
  395. package/dist/tailwind-input.css +31 -17
  396. package/dist/tailwind-utilities.css +1 -1
  397. package/dist/ui/components/Accordion.js +1 -1
  398. package/dist/ui/components/Breadcrumb.js +1 -1
  399. package/dist/ui/components/DetailPageGallery.js +0 -2
  400. package/dist/ui/components/DetailPageHero.js +0 -1
  401. package/dist/ui/components/DetailPageTabs.js +1 -1
  402. package/dist/ui/components/Divider.js +3 -3
  403. package/dist/ui/components/GatedPrice.js +5 -1
  404. package/dist/ui/components/HorizontalScroller.js +1 -1
  405. package/dist/ui/components/ListingLayout.js +3 -3
  406. package/dist/ui/components/MediaTabs.style.css +81 -0
  407. package/dist/ui/components/Pagination.js +1 -1
  408. package/dist/ui/components/PendingValue.d.ts +45 -0
  409. package/dist/ui/components/PendingValue.js +7 -0
  410. package/dist/ui/components/Radio.style.css +119 -0
  411. package/dist/ui/components/RatingDisplay.js +2 -2
  412. package/dist/ui/components/SettingsSection.js +0 -2
  413. package/dist/ui/components/SiteLogo.js +3 -0
  414. package/dist/ui/components/SiteMark.js +3 -0
  415. package/dist/ui/components/SortDropdown.js +1 -1
  416. package/dist/ui/components/SpecTable.d.ts +58 -0
  417. package/dist/ui/components/SpecTable.js +8 -0
  418. package/dist/ui/components/StarRating.js +1 -3
  419. package/dist/ui/components/SummaryCard.js +1 -1
  420. package/dist/ui/components/index.style.css +1 -0
  421. package/dist/ui/icons/icon-registry.d.ts +1 -0
  422. package/dist/ui/icons/icon-registry.js +9 -1
  423. package/dist/ui/index.d.ts +4 -0
  424. package/dist/ui/index.js +2 -0
  425. package/dist/utils/search-txt-query.d.ts +12 -0
  426. package/dist/utils/search-txt-query.js +12 -0
  427. package/package.json +1 -1
  428. package/scripts/dedupe-peer-deps.mjs +18 -0
  429. package/scripts/seed-cli.mjs +135 -26
  430. package/scripts/verify-css-build.mjs +118 -37
  431. package/dist/_internal/server/features/search/actions.d.ts +0 -7
  432. package/dist/_internal/server/features/search/actions.js +0 -21
  433. package/dist/_internal/server/features/search/data.d.ts +0 -15
  434. package/dist/_internal/server/features/search/data.js +0 -18
  435. package/dist/_internal/server/features/search/index.d.ts +0 -3
  436. package/dist/_internal/server/features/search/index.js +0 -2
  437. package/dist/features/homepage/api/[id]/route.d.ts +0 -23
  438. package/dist/features/homepage/api/[id]/route.js +0 -103
  439. package/dist/features/homepage/api/route.d.ts +0 -14
  440. package/dist/features/homepage/api/route.js +0 -140
  441. package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
  442. package/dist/features/homepage/api/sections/[id]/route.js +0 -95
  443. package/dist/features/homepage/api/sections/route.d.ts +0 -13
  444. package/dist/features/homepage/api/sections/route.js +0 -82
  445. package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
  446. package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
  447. package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
  448. package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
  449. package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
  450. package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
  451. package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
  452. package/dist/features/homepage/components/HeroBanner.js +0 -43
  453. package/dist/features/homepage/components/HeroSection.d.ts +0 -7
  454. package/dist/features/homepage/components/HeroSection.js +0 -6
  455. package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
  456. package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
  457. package/dist/features/homepage/components/HomepageView.d.ts +0 -25
  458. package/dist/features/homepage/components/HomepageView.js +0 -5
  459. package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
  460. package/dist/features/homepage/components/NewsletterBanner.js +0 -37
  461. package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
  462. package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
  463. package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
  464. package/dist/features/homepage/components/TrustBadges.js +0 -46
  465. package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
  466. package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
  467. package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
  468. package/dist/features/homepage/hooks/useHomepage.js +0 -17
  469. package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
  470. package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
  471. package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
  472. package/dist/features/homepage/repository/homepage.repository.js +0 -25
  473. package/dist/features/search/actions/index.d.ts +0 -1
  474. package/dist/features/search/actions/index.js +0 -1
  475. package/dist/features/search/actions/search-actions.d.ts +0 -18
  476. package/dist/features/search/actions/search-actions.js +0 -43
  477. package/dist/features/search/api/route.d.ts +0 -22
  478. package/dist/features/search/api/route.js +0 -128
  479. package/dist/features/search/columns/index.d.ts +0 -17
  480. package/dist/features/search/columns/index.js +0 -49
  481. package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
  482. package/dist/features/search/components/SearchFiltersRow.js +0 -21
  483. package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
  484. package/dist/features/search/components/SearchResultsSection.js +0 -35
  485. package/dist/features/search/components/SearchView.d.ts +0 -27
  486. package/dist/features/search/components/SearchView.js +0 -8
  487. package/dist/features/search/hooks/useSearch.d.ts +0 -21
  488. package/dist/features/search/hooks/useSearch.js +0 -33
  489. package/dist/features/search/repository/search.repository.d.ts +0 -14
  490. package/dist/features/search/repository/search.repository.js +0 -59
  491. package/dist/features/search/schemas/index.d.ts +0 -70
  492. package/dist/features/search/schemas/index.js +0 -36
  493. package/dist/features/search/types/index.d.ts +0 -48
  494. package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
  495. package/dist/features/stores/components/StoreScopedSearch.js +0 -27
  496. package/dist/seed/defaults/homepage-sections.d.ts +0 -2
  497. package/dist/seed/defaults/homepage-sections.js +0 -60
  498. package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
  499. package/dist/seed/factories/homepage-section.factory.js +0 -13
  500. /package/dist/{features/search/types/index.js → _internal/shared/listing-types/spares/og.d.ts} +0 -0
@@ -0,0 +1,142 @@
1
+ /*
2
+ * WHY: "Create your own bundle, with restrictions set by seller/admin" is the
3
+ * feature. The restrictions have TWO homes — a product group is identified
4
+ * by the parent product's slug, while a `groupedListing` is its own
5
+ * document — and the picker, the cart-add and the checkout re-validation
6
+ * all have to agree about them.
7
+ * WHAT: One shape, one resolver, one enforcer. Three readers, zero copies.
8
+ *
9
+ * 🛑 THE SAME RULES, FROM EITHER HOME. Resolving them at each call site is how
10
+ * a picker ends up permitting a selection the server then refuses (or worse,
11
+ * the reverse — a server-only rule reads as a broken button). `resolveGroupRules`
12
+ * is the only place the two homes are read, and `checkGroupRules` is the only
13
+ * place they are applied.
14
+ *
15
+ * 🛑 EVERY FIELD IS OPTIONAL AND ABSENT MEANS UNRESTRICTED. Every group that
16
+ * exists today has none of these, so a default that restricted anything would
17
+ * silently change what is buyable on live records.
18
+ *
19
+ * EXPORTS: GroupRules, GroupRuleSource, GroupRuleViolation, EMPTY_GROUP_RULES,
20
+ * readGroupRules, checkGroupRules, describeGroupRules
21
+ *
22
+ * @tag domain:products
23
+ * @tag layer:shared
24
+ * @tag pattern:none
25
+ * @tag access:isomorphic
26
+ * @tag consumers:GroupMemberPicker,cart-actions,checkout
27
+ * @tag sideEffects:none
28
+ */
29
+ export const EMPTY_GROUP_RULES = {};
30
+ /**
31
+ * Read the rules off either home.
32
+ *
33
+ * Structurally typed on purpose: a product group's rules live on the PARENT
34
+ * product document and a grouped listing's on its own, and both are just
35
+ * objects carrying these optional keys. Taking `ProductDocument |
36
+ * GroupedListingDocument` here would drag two feature schemas into a shared
37
+ * module for no gain — and this module is imported by a client component.
38
+ */
39
+ export function readGroupRules(doc) {
40
+ if (!doc)
41
+ return EMPTY_GROUP_RULES;
42
+ const out = {};
43
+ /*
44
+ * Copied field by field rather than spread. A spread of the whole parent
45
+ * PRODUCT document would carry ~90 unrelated fields into something named
46
+ * "rules", and the next reader would reasonably assume everything on it was
47
+ * a rule.
48
+ */
49
+ if (typeof doc.groupMinMembers === "number")
50
+ out.groupMinMembers = doc.groupMinMembers;
51
+ if (typeof doc.groupMaxMembers === "number")
52
+ out.groupMaxMembers = doc.groupMaxMembers;
53
+ if (typeof doc.groupMaxUnitsPerMember === "number") {
54
+ out.groupMaxUnitsPerMember = doc.groupMaxUnitsPerMember;
55
+ }
56
+ if (Array.isArray(doc.groupRequiredMemberIds) && doc.groupRequiredMemberIds.length > 0) {
57
+ out.groupRequiredMemberIds = [...doc.groupRequiredMemberIds];
58
+ }
59
+ if (typeof doc.groupDiscountPercent === "number") {
60
+ out.groupDiscountPercent = doc.groupDiscountPercent;
61
+ }
62
+ return out;
63
+ }
64
+ /**
65
+ * Check a selection against the rules.
66
+ *
67
+ * Returns ALL violations rather than the first, because a picker shows them
68
+ * together and fixing one at a time is a worse experience than seeing the set.
69
+ * The server throws on the first; that is the caller's choice, not this
70
+ * function's.
71
+ *
72
+ * Entries at quantity 0 are ignored — the picker's stepper has `min={0}` and
73
+ * zero means "not chosen", so counting them would make an untouched member
74
+ * count toward `groupMinMembers`.
75
+ */
76
+ export function checkGroupRules(rules, selection) {
77
+ const chosen = selection.filter((e) => e.quantity > 0);
78
+ const out = [];
79
+ if (rules.groupMinMembers !== undefined && chosen.length < rules.groupMinMembers) {
80
+ out.push({
81
+ code: "GROUP_TOO_FEW_MEMBERS",
82
+ message: `Pick at least ${rules.groupMinMembers} different item${rules.groupMinMembers === 1 ? "" : "s"} from this group — you have ${chosen.length}.`,
83
+ });
84
+ }
85
+ if (rules.groupMaxMembers !== undefined && chosen.length > rules.groupMaxMembers) {
86
+ out.push({
87
+ code: "GROUP_TOO_MANY_MEMBERS",
88
+ message: `Pick at most ${rules.groupMaxMembers} different item${rules.groupMaxMembers === 1 ? "" : "s"} from this group — you have ${chosen.length}.`,
89
+ });
90
+ }
91
+ if (rules.groupMaxUnitsPerMember !== undefined) {
92
+ for (const entry of chosen) {
93
+ if (entry.quantity > rules.groupMaxUnitsPerMember) {
94
+ out.push({
95
+ code: "GROUP_MEMBER_QTY_EXCEEDED",
96
+ message: `At most ${rules.groupMaxUnitsPerMember} of "${entry.title ?? entry.productId}" per order.`,
97
+ });
98
+ }
99
+ }
100
+ }
101
+ if (rules.groupRequiredMemberIds) {
102
+ const present = new Set(chosen.map((e) => e.productId));
103
+ const missing = rules.groupRequiredMemberIds.filter((id) => !present.has(id));
104
+ if (missing.length > 0) {
105
+ /*
106
+ * Named by id when the caller has no title for it — which is the normal
107
+ * case, because a REQUIRED member the buyer has not chosen is not in
108
+ * their selection and so has no entry to read a title from. The picker
109
+ * passes titles it already has; the server does not have them and says
110
+ * so honestly rather than printing "undefined".
111
+ */
112
+ const titles = missing.map((id) => selection.find((e) => e.productId === id)?.title ?? id);
113
+ out.push({
114
+ code: "GROUP_REQUIRED_MEMBER_MISSING",
115
+ message: `This group requires ${titles.join(", ")} to be included.`,
116
+ });
117
+ }
118
+ }
119
+ return out;
120
+ }
121
+ /**
122
+ * The rules in words, for the picker's header.
123
+ *
124
+ * Returns an empty array when nothing is restricted, so the caller renders
125
+ * nothing rather than an empty box saying "Rules:".
126
+ */
127
+ export function describeGroupRules(rules) {
128
+ const out = [];
129
+ if (rules.groupMinMembers !== undefined) {
130
+ out.push(`Pick at least ${rules.groupMinMembers}`);
131
+ }
132
+ if (rules.groupMaxMembers !== undefined) {
133
+ out.push(`Pick at most ${rules.groupMaxMembers}`);
134
+ }
135
+ if (rules.groupMaxUnitsPerMember !== undefined) {
136
+ out.push(`Max ${rules.groupMaxUnitsPerMember} of each`);
137
+ }
138
+ if (rules.groupDiscountPercent !== undefined && rules.groupDiscountPercent > 0) {
139
+ out.push(`${rules.groupDiscountPercent}% off the selection`);
140
+ }
141
+ return out;
142
+ }
@@ -108,6 +108,30 @@ export interface ListingTypePlugin {
108
108
  * reachable only by typing the URL.
109
109
  */
110
110
  inGeneralCatalogue: boolean;
111
+ /**
112
+ * May a listing of this type be a MEMBER of a product group?
113
+ *
114
+ * A product group is "a whole plus its parts, buy any mix" — a sticker sheet
115
+ * with its cut-out stickers, a complete Beyblade with its spares. The buyer
116
+ * picks quantities per member through `GroupMemberPicker`.
117
+ *
118
+ * 🛑 `findByGroupId` used to hard-code `["standard", "pre-order"]`, which
119
+ * made this field's answer invisible and wrong: a `stickers` or `art` member
120
+ * was silently dropped from the query, so `ShowGroupSection` saw one member
121
+ * and returned null — the picker was unreachable with nothing erroring. That
122
+ * is Root Cause #58's shape, a union member missing from the one allowlist
123
+ * that gates it.
124
+ *
125
+ * Required, not optional, for the same reason as `inGeneralCatalogue`: a
126
+ * tenth type must not compile until someone decides.
127
+ *
128
+ * False where the type's own pricing or lifecycle makes "pick N of these"
129
+ * meaningless — an auction is one unique lot, a pre-order is a deposit
130
+ * against a future release, a prize-draw sells entries, a classified has no
131
+ * cart, a digital-code is a fungible pool, and a live item is
132
+ * jurisdiction-gated per buyer.
133
+ */
134
+ canJoinProductGroup: boolean;
111
135
  /**
112
136
  * Leading glyph for this type's chip, tab and dropdown row.
113
137
  *
@@ -32,6 +32,8 @@ import * as live from "./live/config.js";
32
32
  // EMI/art-stickers session — printed-only physical-goods types.
33
33
  import * as art from "./art/config.js";
34
34
  import * as stickers from "./stickers/config.js";
35
+ // Part Two 2026-10-19 — one component off a disassembled item, sold alone.
36
+ import * as spares from "./spares/config.js";
35
37
  import { LISTING_TYPE_CAPABILITIES } from "./capabilities.js";
36
38
  import { baseAvailable, } from "./availability.js";
37
39
  import { AVAILABILITY_VALUES, } from "../../../constants/field-names.js";
@@ -48,9 +50,45 @@ export const LISTING_TYPE_REGISTRY = {
48
50
  live: live.config,
49
51
  art: art.config,
50
52
  stickers: stickers.config,
53
+ spares: spares.config,
51
54
  };
55
+ /*
56
+ * 🛑 TOTAL BY CONSTRUCTION — this was a bare index lookup and it crashed pages.
57
+ *
58
+ * The signature says `(type: ListingType) => ListingTypePlugin`, and for a
59
+ * value the compiler has actually checked that is true. But most callers get
60
+ * their `listingType` from a Firestore document, which crosses into the app
61
+ * as untyped data: a row written before a type existed, a row whose field was
62
+ * stripped by a `z.object()` schema, or a mapper that forgot to copy the
63
+ * field (Root Cause #48) all arrive as `undefined`. The lookup then returned
64
+ * `undefined` while TypeScript still believed it held a plugin, so the very
65
+ * next property access threw.
66
+ *
67
+ * Measured in production over 24h: 18 server-side crashes reading
68
+ * `.detailRoute` of undefined, across FIVE route families
69
+ * (`products/[slug]`, `classified/[slug]`, `pre-orders/[id]`,
70
+ * `digital-codes/[slug]`, `prize-draws/[slug]`), plus client-side copies of
71
+ * the same throw and one on `.badge` from `franchise/[slug]`. Each one is a
72
+ * whole detail page replaced by an error boundary.
73
+ *
74
+ * Falling back to `standard` is right because it is what the rest of the
75
+ * codebase ALREADY assumes: `normalizeListingType` is defined as
76
+ * `p.listingType ?? "standard"`, so a missing field is a standard listing
77
+ * everywhere else. Callers that pass through it were always safe; this makes
78
+ * the ones that do not safe as well.
79
+ *
80
+ * 🛑 It WARNS rather than falling back silently. A silent default is how a
81
+ * tenth listing type goes missing from the catalogue for weeks (Root Cause
82
+ * #58, where an unmapped alias returned "" and the filter simply vanished).
83
+ * The page renders; the gap is still reported.
84
+ */
52
85
  export function pluginFor(type) {
53
- return LISTING_TYPE_REGISTRY[type];
86
+ const plugin = LISTING_TYPE_REGISTRY[type];
87
+ if (plugin)
88
+ return plugin;
89
+ console.warn(`[listing-types] no plugin for listingType ${JSON.stringify(type)} — ` +
90
+ `falling back to "standard". Expected one of: ${Object.keys(LISTING_TYPE_REGISTRY).join(", ")}.`);
91
+ return LISTING_TYPE_REGISTRY.standard;
54
92
  }
55
93
  /**
56
94
  * Infer a listing type from a product slug/id by matching its registered
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "art";
22
23
  hideDefault: "sold";
23
24
  isAvailable: typeof alwaysAvailable;
@@ -22,6 +22,8 @@ export const config = {
22
22
  browseRoute: String(ROUTES.PUBLIC.ART),
23
23
  // /art ("Art & Stickers") is in MAIN_NAV_ITEMS and spans art + stickers.
24
24
  inGeneralCatalogue: false,
25
+ // a print can be one of a set, and a sheet of art can be split
26
+ canJoinProductGroup: true,
25
27
  iconKey: "art",
26
28
  hideDefault: "sold",
27
29
  // Runs out rather than ending — no time dimension, so the shared
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "bid";
22
23
  hideDefault: "ended";
23
24
  isAvailable: (row: AvailabilityRow, now: Date) => boolean;
@@ -22,6 +22,8 @@ export const config = {
22
22
  // /auctions is in MAIN_NAV_ITEMS. An auction card in the general grid renders
23
23
  // Buy-Now chrome rather than bid affordances, so it belongs on its own page.
24
24
  inGeneralCatalogue: false,
25
+ // one unique lot, won by one bidder — there is no "pick 3 of these"
26
+ canJoinProductGroup: false,
25
27
  iconKey: "bid",
26
28
  // An auction is never "sold out" — it ends. Hiding by stock would hide
27
29
  // every live auction, since auctions don't carry meaningful stock.
@@ -67,8 +67,32 @@ export function num(value) {
67
67
  * and must not vanish from the default scope.
68
68
  */
69
69
  export function baseAvailable(row) {
70
+ /*
71
+ * `isSold` stays the kill switch and is tested FIRST, ahead of the
72
+ * made-to-order bypass. A seller who marks a listing sold has made a
73
+ * statement about the listing, not about its stock level, and it must hold
74
+ * whatever the production model is — otherwise a made-to-order row could
75
+ * never be retired at all.
76
+ */
70
77
  if (row[PRODUCT_FIELDS.IS_SOLD] === true)
71
78
  return false;
79
+ /*
80
+ * 🛑 Made to order: stock does not gate availability, so return before the
81
+ * two quantity checks.
82
+ *
83
+ * This ONE branch fixes `isListingRowAvailable`, the dense `available`
84
+ * scope, all three homepage `getFeatured*` helpers and related-items at
85
+ * once, because every one of them resolves to this function — which is the
86
+ * entire reason the four disagreeing copies were collapsed into it (Root
87
+ * Cause #73). It also correctly keeps a made-to-order row OUT of "Sold &
88
+ * Ended": that scope is the complement of this predicate.
89
+ *
90
+ * The counter-case is the point of the flag being per-listing: a finite
91
+ * spare at quantity 0 is genuinely gone and still falls through to the
92
+ * checks below.
93
+ */
94
+ if (row[PRODUCT_FIELDS.MADE_TO_ORDER] === true)
95
+ return true;
72
96
  const available = num(row[PRODUCT_FIELDS.AVAILABLE_QUANTITY]);
73
97
  if (available !== null && available <= 0)
74
98
  return false;
@@ -94,6 +118,30 @@ export const STOCK_ONLY_UNAVAILABLE_CLAUSES = [
94
118
  { field: PRODUCT_FIELDS.AVAILABLE_QUANTITY, op: "eq", value: 0 },
95
119
  { field: PRODUCT_FIELDS.IS_SOLD, op: "eq", value: true },
96
120
  ];
121
+ /*
122
+ * 🛑 WHY `madeToOrder` IS NOT A CLAUSE HERE, written down so nobody
123
+ * "optimises" it into a FAILED_PRECONDITION.
124
+ *
125
+ * A made-to-order row at quantity 0 still matches the first clause above, so it
126
+ * is fetched by the "Sold & Ended" query and then dropped in memory by
127
+ * `baseAvailable`. That is wasteful and it is CORRECT.
128
+ *
129
+ * The apparent fix — adding `madeToOrder != true` to narrow the query — cannot
130
+ * be expressed: `!=` is an INEQUALITY, so `UnavailableClause` would require it
131
+ * to name its own `sortField`, and Firestore implicitly appends an
132
+ * inequality's field to the `orderBy`. Pairing it with the scope's real sort
133
+ * demands a composite index in an order nobody declares (Root Cause #59), and
134
+ * every caller wraps the query in a guard that turns the failure into an empty
135
+ * page rather than an error.
136
+ *
137
+ * Worse, a Firestore inequality EXCLUDES every document that lacks the field —
138
+ * which is every product written before this flag existed, i.e. all of them.
139
+ * `madeToOrder != true` would return almost nothing. Same trap as the
140
+ * `isTestData` one CLAUDE.md documents.
141
+ *
142
+ * The in-memory drop is bounded by the page size, so the cost is a few rows
143
+ * read and discarded. Leave it.
144
+ */
97
145
  /**
98
146
  * `isAvailable` for a type with no rule beyond `baseAvailable`. Named rather
99
147
  * than an inline `() => true` so the five configs that use it read as
@@ -11,6 +11,25 @@
11
11
  * inside the per-type folder (X2 plugin layout).
12
12
  */
13
13
  export const LISTING_TYPE_CAPABILITIES = {
14
+ /*
15
+ * Standard physical-goods behaviour, with ONE deliberate difference:
16
+ * `canMakeOffer` is true.
17
+ *
18
+ * A loose part has no catalogue price — what a spare attack ring is worth
19
+ * depends on the colour, the wear and how badly the buyer needs that exact
20
+ * one. It is the archetypal haggled item here, which is the whole reason the
21
+ * seller is listing it instead of leaving it in a drawer, so the type that
22
+ * most needs Make-an-Offer is this one.
23
+ */
24
+ spares: {
25
+ canAddToCart: true,
26
+ canBid: false,
27
+ supportsShipping: true,
28
+ requiresVendorVerified: false,
29
+ requiresJurisdictionCheck: false,
30
+ hasInstantFulfillment: false,
31
+ canMakeOffer: true,
32
+ },
14
33
  standard: {
15
34
  canAddToCart: true,
16
35
  canBid: false,
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "deals";
22
23
  hideDefault: "sold";
23
24
  isAvailable: typeof alwaysAvailable;
@@ -21,6 +21,8 @@ export const config = {
21
21
  // Has a dedicated page but NO MAIN_NAV_ITEMS entry, so /products is its only
22
22
  // discovery path. Dropping it here would orphan the type (Root Cause #61).
23
23
  inGeneralCatalogue: true,
24
+ // no cart at all (cartLine "blocked") — the purchase path is contact-seller
25
+ canJoinProductGroup: false,
24
26
  iconKey: "deals",
25
27
  hideDefault: "sold",
26
28
  // Runs out rather than ending — no time dimension, so the shared
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "digitalCode";
22
23
  hideDefault: "sold";
23
24
  isAvailable: (row: AvailabilityRow) => boolean;
@@ -34,6 +34,8 @@ export const config = {
34
34
  // Has a dedicated page but NO MAIN_NAV_ITEMS entry, so /products is its only
35
35
  // discovery path. Dropping it here would orphan the type (Root Cause #61).
36
36
  inGeneralCatalogue: true,
37
+ // a fungible pool item; "which 3 of these codes" is not a question a buyer has
38
+ canJoinProductGroup: false,
37
39
  iconKey: "digitalCode",
38
40
  // "Sold out" for a code pool means codesAvailable hit 0. That was ASSUMED to
39
41
  // be mirrored by stockQuantity, and it isn't — seed and production rows exist
@@ -44,6 +44,7 @@ export function isCategoryTypeEnabled(type, settings) {
44
44
  */
45
45
  const ALL_LISTING_TYPES_MAP = {
46
46
  standard: true,
47
+ spares: true,
47
48
  auction: true,
48
49
  "pre-order": true,
49
50
  "prize-draw": true,
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "live";
22
23
  hideDefault: "sold";
23
24
  isAvailable: typeof alwaysAvailable;
@@ -21,6 +21,8 @@ export const config = {
21
21
  // Has a dedicated page but NO MAIN_NAV_ITEMS entry, so /products is its only
22
22
  // discovery path. Dropping it here would orphan the type (Root Cause #61).
23
23
  inGeneralCatalogue: true,
24
+ // jurisdiction-gated per buyer and vendor-verified — not mixable into a group line
25
+ canJoinProductGroup: false,
24
26
  iconKey: "live",
25
27
  hideDefault: "sold",
26
28
  // Runs out rather than ending — no time dimension, so the shared
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "preOrder";
22
23
  hideDefault: "closed";
23
24
  isAvailable: (row: AvailabilityRow) => boolean;
@@ -21,6 +21,8 @@ export const config = {
21
21
  browseRoute: String(ROUTES.PUBLIC.PRE_ORDERS),
22
22
  // /pre-orders is in MAIN_NAV_ITEMS.
23
23
  inGeneralCatalogue: false,
24
+ // a deposit against a future release; members would ship at different times
25
+ canJoinProductGroup: false,
24
26
  iconKey: "preOrder",
25
27
  hideDefault: "closed",
26
28
  // A pre-order closes when its allocation fills, NOT when its delivery date
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "prizeDraw";
22
23
  hideDefault: "closed";
23
24
  isAvailable: (row: AvailabilityRow) => boolean;
@@ -21,6 +21,8 @@ export const config = {
21
21
  // /prize-draws is in MAIN_NAV_ITEMS. Entry-based pricing reads wrong beside
22
22
  // ordinary unit prices in the general grid.
23
23
  inGeneralCatalogue: false,
24
+ // sells ENTRIES at a fixed price; a group line would blur what was entered
25
+ canJoinProductGroup: false,
24
26
  iconKey: "prizeDraw",
25
27
  hideDefault: "closed",
26
28
  // `prizeRevealStatus`, NOT remaining entries: a draw can close with slots
@@ -0,0 +1,74 @@
1
+ import { alwaysAvailable } from "../availability";
2
+ export declare const LISTING_TYPE: "spares";
3
+ export declare const capability: import("../capabilities").ListingTypeCapability;
4
+ export declare const config: {
5
+ listingType: "spares";
6
+ slugPrefix: string;
7
+ cartLine: "single-product";
8
+ detailRoute: (idOrSlug: string) => string;
9
+ badge: {
10
+ label: string;
11
+ className: string;
12
+ };
13
+ priceLabel: string;
14
+ typeLabel: string;
15
+ showsStockQuantity: boolean;
16
+ tabSlug: string;
17
+ pluralLabel: string;
18
+ chipLabel: string;
19
+ browseRoute: string;
20
+ inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
22
+ iconKey: "spares";
23
+ hideDefault: "sold";
24
+ isAvailable: typeof alwaysAvailable;
25
+ unavailableClauses: readonly import("../availability").UnavailableClause[];
26
+ sortOptions: readonly [{
27
+ readonly value: string;
28
+ readonly label: "Newest First";
29
+ }, {
30
+ readonly value: string;
31
+ readonly label: "Oldest First";
32
+ }, {
33
+ readonly value: string;
34
+ readonly label: "Price: Low to High";
35
+ }, {
36
+ readonly value: string;
37
+ readonly label: "Price: High to Low";
38
+ }, {
39
+ readonly value: string;
40
+ readonly label: "Name: A–Z";
41
+ }, {
42
+ readonly value: string;
43
+ readonly label: "Name: Z–A";
44
+ }, {
45
+ readonly value: string;
46
+ readonly label: "Featured First";
47
+ }, {
48
+ readonly value: string;
49
+ readonly label: "Promoted First";
50
+ }, {
51
+ readonly value: string;
52
+ readonly label: "Recently Updated";
53
+ }];
54
+ publicSortOptions: readonly [{
55
+ readonly value: string;
56
+ readonly label: "Newest First";
57
+ }, {
58
+ readonly value: string;
59
+ readonly label: "Oldest First";
60
+ }, {
61
+ readonly value: string;
62
+ readonly label: "Price: Low to High";
63
+ }, {
64
+ readonly value: string;
65
+ readonly label: "Price: High to Low";
66
+ }, {
67
+ readonly value: string;
68
+ readonly label: "Name: A–Z";
69
+ }, {
70
+ readonly value: string;
71
+ readonly label: "Name: Z–A";
72
+ }];
73
+ extraFacetKeys: readonly string[];
74
+ };
@@ -0,0 +1,60 @@
1
+ import { LISTING_TYPE_CAPABILITIES } from "../capabilities.js";
2
+ import { alwaysAvailable, STOCK_ONLY_UNAVAILABLE_CLAUSES, } from "../availability.js";
3
+ import { ROUTES } from "../../../../next/routing/route-map.js";
4
+ import { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, } from "../../../../features/products/constants/sieve.js";
5
+ export const LISTING_TYPE = "spares";
6
+ export const capability = LISTING_TYPE_CAPABILITIES.spares;
7
+ export const config = {
8
+ listingType: LISTING_TYPE,
9
+ /*
10
+ * Singular, matching `sticker-` rather than the plural type name.
11
+ *
12
+ * 🛑 Matched by `startsWith`, so this exact string must open every seeded id
13
+ * or `audit-seed-listing-type`'s R3 fails on every row — that audit calls the
14
+ * shipped `detectListingTypeFromSlug` rather than re-deriving the map, which
15
+ * is how it caught five `stickers-` ids against a registered `sticker-`.
16
+ */
17
+ slugPrefix: "spare-",
18
+ cartLine: "single-product",
19
+ detailRoute: (idOrSlug) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(idOrSlug)),
20
+ badge: { label: "Spare Part", className: "bg-primary-700 text-white" },
21
+ priceLabel: "Price (₹)",
22
+ typeLabel: "Spares",
23
+ showsStockQuantity: true,
24
+ tabSlug: "spares",
25
+ pluralLabel: "Spares",
26
+ chipLabel: "Spares",
27
+ browseRoute: String(ROUTES.PUBLIC.SPARES),
28
+ /*
29
+ * 🛑 `false`, and it obliges the nav entry that lands in the same change.
30
+ *
31
+ * The rule for this flag is nav reachability, not taste: a type kept out of
32
+ * the general catalogue must own a page that `MAIN_NAV_ITEMS` links, or it is
33
+ * reachable from nowhere (Root Cause #61). `/spares` is that page.
34
+ *
35
+ * The reason to keep it out is the catalogue itself. A seller who strips one
36
+ * Beyblade lists eight parts, so a few disassembled items would outnumber
37
+ * every complete listing on `/products` — and a buyer looking for a loose
38
+ * attack ring wants a page where everything is a loose part, not one where
39
+ * they are scattered among bundles and sealed boxes.
40
+ */
41
+ inGeneralCatalogue: false,
42
+ /*
43
+ * A sheet-plus-parts group is the shape this exists for on the sticker side,
44
+ * and the same shape sells a stripped bey: the whole item as the parent, each
45
+ * salvaged part as a child, buyer takes any mix.
46
+ */
47
+ canJoinProductGroup: true,
48
+ iconKey: "spares",
49
+ hideDefault: "sold",
50
+ /*
51
+ * A spare runs out rather than ending — there is no time dimension at all, so
52
+ * the shared stock-only rules are the whole story. Usually quantity 1: it is
53
+ * one physical object the seller happens to have.
54
+ */
55
+ isAvailable: alwaysAvailable,
56
+ unavailableClauses: STOCK_ONLY_UNAVAILABLE_CLAUSES,
57
+ sortOptions: STANDARD_SORT_OPTIONS,
58
+ publicSortOptions: STANDARD_PUBLIC_SORT_OPTIONS,
59
+ extraFacetKeys: [],
60
+ };
@@ -0,0 +1 @@
1
+ export declare const ctas: readonly string[];
@@ -0,0 +1,4 @@
1
+ // Spares reuse the standard "Add to Cart" / "Buy Now" CTAs — plus the shared
2
+ // Make-an-Offer control, which is driven by `capability.canMakeOffer` rather
3
+ // than by anything here (mirrors the art/stickers/live placeholders).
4
+ export const ctas = [];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ProductDocument } from "../../../../features/products/schemas/firestore";
2
+ export declare function isSparesProduct(doc: ProductDocument): boolean;
@@ -0,0 +1,3 @@
1
+ export function isSparesProduct(doc) {
2
+ return doc.listingType === "spares";
3
+ }
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "order";
22
23
  hideDefault: "sold";
23
24
  isAvailable: typeof alwaysAvailable;
@@ -19,6 +19,8 @@ export const config = {
19
19
  browseRoute: String(ROUTES.PUBLIC.PRODUCTS),
20
20
  // The general catalogue IS this type's page.
21
21
  inGeneralCatalogue: true,
22
+ // the ordinary case — a complete item whose parts can be sold beside it
23
+ canJoinProductGroup: true,
22
24
  iconKey: "order",
23
25
  hideDefault: "sold",
24
26
  // Runs out rather than ending — no time dimension, so the shared
@@ -18,6 +18,7 @@ export declare const config: {
18
18
  chipLabel: string;
19
19
  browseRoute: string;
20
20
  inGeneralCatalogue: boolean;
21
+ canJoinProductGroup: boolean;
21
22
  iconKey: "stickers";
22
23
  hideDefault: "sold";
23
24
  isAvailable: typeof alwaysAvailable;