@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
@@ -67,7 +67,7 @@ function dedicatedPageFor(types) {
67
67
  label: `Full ${plugin.pluralLabel} filters →`,
68
68
  };
69
69
  }
70
- export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODUCT_LISTING_TYPES, typeTabs = PRODUCT_TYPE_FILTER_TABS, searchPlaceholder = "Search products...", }) {
70
+ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODUCT_LISTING_TYPES, typeTabs = PRODUCT_TYPE_FILTER_TABS, searchPlaceholder = "Search products...", storeId: scopedStoreId, }) {
71
71
  const router = useRouter();
72
72
  const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
73
73
  const { showToast } = useToast();
@@ -155,7 +155,7 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
155
155
  maxPrice: table.get(TABLE_KEYS.MAX_PRICE) ? Number(table.get(TABLE_KEYS.MAX_PRICE)) : undefined,
156
156
  condition: table.get(TABLE_KEYS.CONDITION) || undefined,
157
157
  brand: table.get(TABLE_KEYS.BRAND) || undefined,
158
- storeId: table.get(TABLE_KEYS.STORE_ID) || undefined,
158
+ storeId: scopedStoreId || table.get(TABLE_KEYS.STORE_ID) || undefined,
159
159
  freeShipping: table.get(TABLE_KEYS.FREE_SHIPPING) === "true" ? true : undefined,
160
160
  // These three are in FILTER_KEYS (so they count toward the filter badge)
161
161
  // but were never put on the wire — the drawer sections were inert.
@@ -345,5 +345,5 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
345
345
  ] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Grid, { cols: "cards", gap: "md", children: Array.from({ length: 10 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-1/3", surface: "subtle", rounded: "default" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => pluginFor(p.listingType ?? "standard").detailRoute(p.slug || p.id), view: view === "grid" ? "card" : "list", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow, selectionMode: selection.isSelecting, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle })) }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "product", onClose: () => {
346
346
  setCompareIds([]);
347
347
  selection.clearSelection();
348
- }, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", listingTypeOptions: listingTypeOptions, listingTypeFooter: dedicatedPage ? (_jsx(Div, { padding: "x-md", paddingY: "b-sm", children: _jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }) })) : null, categoryOptions: categoryOptions, brandOptions: brandOptions, featureOptions: featureOptions }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
348
+ }, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)), onAdd: (id) => setCompareIds((ids) => (ids.includes(id) ? ids : [...ids, id])) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", listingTypeOptions: listingTypeOptions, listingTypeFooter: dedicatedPage ? (_jsx(Div, { padding: "x-md", paddingY: "b-sm", children: _jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }) })) : null, categoryOptions: categoryOptions, brandOptions: brandOptions, featureOptions: featureOptions }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
349
349
  }
@@ -5,6 +5,8 @@ import Link from "next/link.js";
5
5
  import { Div, PricesOnly, Row, Span, Text, Modal, SideDrawer, Button } from "../../../ui/index.js";
6
6
  import { MediaImage } from "../../media/MediaImage.js";
7
7
  import { GroupMemberPicker } from "./GroupMemberPicker.js";
8
+ import { resolveGroupMemberImage } from "../utils/group-member-image.js";
9
+ import { readGroupRules } from "../../../_internal/shared/features/products/group-rules.js";
8
10
  import { formatCurrency } from "../../../utils/number.formatter.js";
9
11
  import { normalizeListingType } from "../utils/listing-type.js";
10
12
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
@@ -18,7 +20,7 @@ function memberHref(m) {
18
20
  return pluginFor(normalizeListingType(m)).detailRoute(slug);
19
21
  }
20
22
  function MemberThumb({ member, isCurrent }) {
21
- const image = member.images?.[0] ?? "";
23
+ const image = resolveGroupMemberImage(member);
22
24
  const href = memberHref(member);
23
25
  const price = formatCurrency(member.price, member.currency ?? "INR");
24
26
  return (_jsxs(Link, { href: href, "aria-label": member.title, className: "flex flex-col items-center gap-[var(--appkit-space-1-5)] flex-shrink-0 w-16 group", children: [_jsxs(Div, { rounded: "full", className: `relative w-14 h-14 overflow-hidden border-2 transition-all ${isCurrent
@@ -48,7 +50,18 @@ export function ShowGroupSection({ groupId, currentSlug, isParent, groupTitle })
48
50
  // The overlay is now a PICKER, not a read-only table: pick how many of each
49
51
  // member you want and add the whole selection as one cart line. The picker
50
52
  // stands itself down (read-only, no CTA) when the group spans sellers.
51
- const tableContent = (_jsx(GroupMemberPicker, { groupId: groupId, groupSource: "product-group", members: members, onAdded: () => setShowAll(false), onAuthRequired: () => setShowAll(false) }));
53
+ const tableContent = (_jsx(GroupMemberPicker, { groupId: groupId, groupSource: "product-group", members: members,
54
+ /*
55
+ * A product group's rules live on the PARENT, and the parent is one of
56
+ * the members this component already fetched — found by `isGroupParent`
57
+ * rather than by position, because the endpoint makes no ordering
58
+ * promise and the parent is not reliably first.
59
+ *
60
+ * 🛑 This is why the five rule fields are in `PRODUCT_PUBLIC_FIELDS`: they
61
+ * reach `toPublicGroupMember` through that allow-list, and without it the
62
+ * picker would silently see no rules and permit what the server refuses.
63
+ */
64
+ rules: readGroupRules(members.find((m) => m.isGroupParent)), onAdded: () => setShowAll(false), onAuthRequired: () => setShowAll(false) }));
52
65
  return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: `${__O.hidden}`, surface: "muted", rounded: "xl", border: "default", children: [_jsxs(Row, { role: "button", tabIndex: 0, onClick: () => setOpen((v) => !v), onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") {
53
66
  e.preventDefault();
54
67
  setOpen((v) => !v);
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ type SearchParams = Record<string, string | string[]>;
3
+ export interface SparesListViewProps {
4
+ searchParams?: SearchParams;
5
+ }
6
+ export declare function SparesListView({ searchParams }: SparesListViewProps): Promise<React.JSX.Element>;
7
+ export {};
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Container, Heading, Main, Section, Text, TextLink } from "../../../ui/index.js";
3
+ import { AdSlot } from "../../homepage/components/AdSlot.js";
4
+ import { ProductsIndexListing } from "./ProductsIndexListing.js";
5
+ import { PRODUCT_FIELDS } from "../../../constants/field-names.js";
6
+ import { sortBy } from "../../../constants/sort.js";
7
+ import { ROUTES } from "../../../next/routing/route-map.js";
8
+ import { listPublicProducts, parsePublicProductParams, defaultAvailabilityForListingTypes, } from "../../../_internal/server/features/products/list-public.js";
9
+ const DEFAULT_PAGE_SIZE = 24;
10
+ const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
11
+ /** The one type this page spans. Named so the two call sites cannot disagree. */
12
+ const SPARES_LISTING_TYPES = ["spares"];
13
+ export async function SparesListView({ searchParams = {} }) {
14
+ /*
15
+ * Filter semantics live in `listPublicProducts`, shared verbatim with
16
+ * `/api/products`, so the SSR first paint and the client's refetch cannot
17
+ * disagree about what the default view means. That matters more here than it
18
+ * looks: every public listing hook sets `staleTime: Infinity` once given SSR
19
+ * `initialData`, so a disagreement is FROZEN for that query key and no
20
+ * refetch ever repairs it (Root Cause #30).
21
+ */
22
+ const result = await listPublicProducts(parsePublicProductParams(searchParams, {
23
+ listingTypes: SPARES_LISTING_TYPES,
24
+ pageSize: DEFAULT_PAGE_SIZE,
25
+ sorts: DEFAULT_SORT,
26
+ // Derived from the plugin's own `hideDefault`, so this page and
27
+ // `ProductsIndexListing` cannot mirror two different literals.
28
+ ...defaultAvailabilityForListingTypes(SPARES_LISTING_TYPES),
29
+ }));
30
+ return (_jsxs(Main, { children: [_jsx(Section, { border: "bottom-subtle", paddingY: "b-md", padding: "t-xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "bold", color: "primary", children: "Spares" }), _jsx(Text, { className: "mt-1", color: "muted", size: "sm", children: "Single components off disassembled tops \u2014 attack rings, weight disks, drivers, bits. Sellers list what they have spare; most are one of a kind." }), _jsxs(Text, { className: "mt-2", size: "sm", children: ["Not sure which part you need?", " ", _jsx(TextLink, { href: String(ROUTES.PUBLIC.PARTS), children: "Browse the parts glossary" }), " ", "\u2014 every component, what it does, and which tops use it."] })] }) }), _jsxs(Container, { size: "xl", padding: "x-md", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing
31
+ // `undefined`, not `null` — React Query treats `null` as defined
32
+ // data, so a failed query would freeze an empty grid forever under
33
+ // `staleTime: Infinity`. Same reasoning as ProductsIndexPageView.
34
+ , {
35
+ // `undefined`, not `null` — React Query treats `null` as defined
36
+ // data, so a failed query would freeze an empty grid forever under
37
+ // `staleTime: Infinity`. Same reasoning as ProductsIndexPageView.
38
+ initialData: result ?? undefined, listingTypes: SPARES_LISTING_TYPES, searchPlaceholder: "Search spares by part, colour or model..." }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
39
+ }
@@ -411,6 +411,7 @@ export declare const SELLER_BULK_ACTIONS: {
411
411
  readonly live: readonly ["row-delete"];
412
412
  readonly art: readonly ["row-delete"];
413
413
  readonly stickers: readonly ["row-delete"];
414
+ readonly spares: readonly ["row-delete"];
414
415
  readonly auctions: readonly ["row-delete"];
415
416
  readonly preOrders: readonly ["row-delete"];
416
417
  readonly prizeDraws: readonly ["row-delete"];
@@ -566,6 +566,7 @@ export const SELLER_BULK_ACTIONS = {
566
566
  live: [ROW_ACTION_ID.DELETE],
567
567
  art: [ROW_ACTION_ID.DELETE],
568
568
  stickers: [ROW_ACTION_ID.DELETE],
569
+ spares: [ROW_ACTION_ID.DELETE],
569
570
  auctions: [ROW_ACTION_ID.DELETE],
570
571
  preOrders: [ROW_ACTION_ID.DELETE],
571
572
  prizeDraws: [ROW_ACTION_ID.DELETE],
@@ -267,16 +267,106 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
267
267
  updateAvailableQuantity(productId: string, quantity: number): Promise<void>;
268
268
  updateBid(productId: string, bidAmount: number, bidCount: number): Promise<void>;
269
269
  findAvailable(): Promise<ProductDocument[]>;
270
+ /**
271
+ * Every member of a group, UNFILTERED — drafts, and every listing type.
272
+ *
273
+ * 🛑 This is the scope `dissolveGroup` needs and the ONLY one that may be
274
+ * unfiltered. A filtered dissolve leaves the excluded members carrying a
275
+ * `groupId` pointing at a group that no longer exists, which nothing ever
276
+ * clears — and that orphan is invisible until someone wonders why a product
277
+ * claims to be in a group with no other members.
278
+ */
279
+ findAllByGroupId(groupId: string): Promise<ProductDocument[]>;
280
+ /**
281
+ * Group members a BUYER may see: eligible types, published only.
282
+ *
283
+ * 🛑 The type filter is DERIVED from `canJoinProductGroup`, not hard-coded.
284
+ * It used to be a literal `["standard", "pre-order"]`, which silently dropped
285
+ * every `stickers` / `art` member from the query — so `ShowGroupSection` saw
286
+ * one member and returned null, and the picker was unreachable with nothing
287
+ * erroring anywhere (Root Cause #58).
288
+ *
289
+ * 🛑 The `status` filter is NEW and is a leak fix, not a tightening. There
290
+ * was no status filter at all, so DRAFT members were reaching the public
291
+ * `/api/products/group/[groupId]` payload; widening the type list without
292
+ * this would have widened that leak at the same time.
293
+ */
294
+ findGroupMembersForPublic(groupId: string): Promise<ProductDocument[]>;
295
+ /**
296
+ * Union the child-side query with the PARENT's own `groupChildSlugs`.
297
+ *
298
+ * Mirrors `resolveBundleMemberIds`, and exists for the same reason: Root
299
+ * Cause #42 documented a parent carrying `groupChildSlugs` while its children
300
+ * had no `groupId`, so a child-side query resolved only the parent and the
301
+ * group rendered as one member. The repository's six mutating methods all
302
+ * write both sides in one batch, so code cannot produce that state — but a
303
+ * seeded fixture or a direct Firestore edit still can, and the READER was
304
+ * never made resilient. `listBundleMembers` got this fix; this did not.
305
+ */
306
+ private withParentSideFallback;
307
+ /**
308
+ * @deprecated Ambiguous about scope — say which one you mean.
309
+ * Kept so no caller silently changes behaviour; it resolves to the PUBLIC
310
+ * scope, which is what every existing caller wanted.
311
+ */
270
312
  findByGroupId(groupId: string): Promise<ProductDocument[]>;
271
313
  startGroup(productId: string, slug: string): Promise<void>;
272
314
  updateGroupTitle(productId: string, groupTitle: string): Promise<void>;
273
315
  dissolveGroup(groupId: string): Promise<void>;
316
+ /**
317
+ * Link ONE child into a parent's group.
318
+ *
319
+ * 🛑 `arrayUnion`, not a spread of the in-memory `parent.groupChildSlugs`.
320
+ *
321
+ * The spread read a snapshot the CALLER was holding, so linking N children in
322
+ * a loop produced exactly ONE entry: every iteration computed
323
+ * `[...(stale ?? []), child]` from the same pre-loop document and the last
324
+ * write won. Nothing errored — each child got its own `groupId` correctly, so
325
+ * `findAllByGroupId` found them all while the parent's own
326
+ * `groupChildSlugs` claimed one member, which is what the group picker's
327
+ * fallback path and `GroupSettingsPanel` read.
328
+ *
329
+ * `arrayUnion` is also idempotent, so re-linking an existing child is a
330
+ * no-op instead of a duplicate id.
331
+ *
332
+ * For a known set, prefer `linkChildrenToGroup` below — one batch instead of
333
+ * N, which matters against Rule #6's round-trip budget when promoting a
334
+ * 60-part sticker sheet.
335
+ */
274
336
  linkChildToGroup(parent: ProductDocument, child: ProductDocument): Promise<void>;
337
+ /**
338
+ * Link SEVERAL children into a parent's group in one batch.
339
+ *
340
+ * Built for `promoteStickerSheetToListings`, which links every unique part of
341
+ * a sheet at once — N calls to `linkChildToGroup` would be N batch commits
342
+ * for work that is one logical change.
343
+ */
344
+ linkChildrenToGroup(parent: ProductDocument, children: readonly ProductDocument[]): Promise<void>;
275
345
  unlinkChildFromGroup(parent: ProductDocument, child: ProductDocument): Promise<void>;
276
346
  leaveGroup(child: ProductDocument, parent: ProductDocument | null): Promise<void>;
277
347
  addChildProduct(parent: ProductDocument, child: Omit<import("../schemas/firestore").ProductCreateInput, "groupId" | "isGroupParent" | "groupParentSlug" | "groupTitle">): Promise<ProductDocument>;
278
348
  deleteByStore(storeId: string): Promise<number>;
279
349
  delete(id: string): Promise<void>;
350
+ /**
351
+ * Every live listing OF one glossary part, across all stores.
352
+ *
353
+ * 🛑 NOT filtered by `listingType`, deliberately. Both `spares` and the
354
+ * 972 generated per-leaf catalogue rows (which are `standard`) carry
355
+ * `partGlossaryId`, and a buyer on `/parts/{slot}/{code}` wants any listing
356
+ * of that part — not only the ones that happen to be filed under the newer
357
+ * type. Narrowing to `spares` would hide most of the real inventory.
358
+ *
359
+ * 🛑 `orderBy` is `createdAt`, which EVERY document carries
360
+ * (`BaseRepository.create` writes it). Ordering by an optional field silently
361
+ * excludes every row lacking it — Root Cause #100, where `itemsSold` on zero
362
+ * of 95 products made a store`s whole review endpoint return nothing.
363
+ *
364
+ * Availability is filtered IN MEMORY rather than in the query: "available" is
365
+ * negation-shaped and no query expresses it, which is the asymmetry
366
+ * `baseAvailable` exists for. The window is bounded, so the cost is a few
367
+ * rows read and discarded.
368
+ */
369
+ findByPartGlossaryId(partGlossaryId: string, limit?: number): Promise<ProductDocument[]>;
280
370
  static readonly SIEVE_FIELDS: {
281
371
  id: {
282
372
  canFilter: boolean;
@@ -330,6 +420,14 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
330
420
  canFilter: boolean;
331
421
  canSort: boolean;
332
422
  };
423
+ partGlossaryId: {
424
+ canFilter: boolean;
425
+ canSort: boolean;
426
+ };
427
+ colourway: {
428
+ canFilter: boolean;
429
+ canSort: boolean;
430
+ };
333
431
  isPromoted: {
334
432
  canFilter: boolean;
335
433
  canSort: boolean;
@@ -1,5 +1,5 @@
1
1
  import { normalizeError } from "../../../errors/normalize.js";
2
- import { increment, serverTimestamp } from "../../../contracts/field-ops.js";
2
+ import { arrayUnion, increment, serverTimestamp } from "../../../contracts/field-ops.js";
3
3
  // 🛑 `../../../errors` re-exports the REAL ValidationError (errors/validation-error),
4
4
  // which is the one `mapToHttpError` instanceof-checks to produce a 400. The
5
5
  // unrelated same-named class under `_internal/shared/errors` falls through every
@@ -24,7 +24,7 @@ import { planSearchTxt, beginSearchWindow, emptySearchResult, } from "../../../u
24
24
  // One definition of "which fields feed searchTxt", shared with the seed
25
25
  // wrapper. Three copies existed and two were missing the same three fields.
26
26
  import { buildProductSearchTxt } from "../../../utils/search-txt-builders.js";
27
- import { PRODUCT_COLLECTION, ProductStatusValues } from "../schemas/index.js";
27
+ import { PRODUCT_COLLECTION, ProductStatusValues, toProductImages } from "../schemas/index.js";
28
28
  // Collection NAMES only — deliberately not `categoriesRepository` or
29
29
  // `taxCodesRepository`. See deriveTaxonomy()'s header for why these are raw
30
30
  // reads: no repository in this codebase imports another feature's, because of
@@ -45,6 +45,12 @@ import { MAX_FEATURES_PER_PRODUCT } from "../schemas/product-features.js";
45
45
  // The ONE `{{placeholder}}` implementation, promoted out of the FAQ schema in
46
46
  // B3. A second regex here would be Root Cause #75's shape.
47
47
  import { interpolate } from "../../../_internal/shared/templating/placeholders.js";
48
+ /*
49
+ * From their DEFINING modules, not a barrel — a repository reaching the wide
50
+ * entry is how the Turbopack client-bundle trap gets re-opened (Root Cause #18).
51
+ */
52
+ import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags.js";
53
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
48
54
  import { PRODUCT_FIELDS } from "../../../constants/field-names.js";
49
55
  /**
50
56
  * Canonical listing-kind tokens used in `listingType`. Note: the public Sieve
@@ -73,6 +79,21 @@ const LISTING_KIND_ALIAS_MAP = {
73
79
  live: LISTING_TYPE_VALUES.LIVE,
74
80
  art: LISTING_TYPE_VALUES.ART,
75
81
  stickers: LISTING_TYPE_VALUES.STICKERS,
82
+ /*
83
+ * 🛑 Root Cause #58 lives here, and it is the single most expensive line to
84
+ * omit in this whole change. The alias returns `""` for an unrecognised
85
+ * token and `expandFilterAliases` then DROPS the empty clause — so a missing
86
+ * entry does not error, it deletes the `listingType` filter from the query.
87
+ * `art` and `stickers` were added to the union, the registry and the
88
+ * capability map and not to this map, and every art/sticker browse page
89
+ * silently returned the entire catalogue until someone reported it.
90
+ *
91
+ * `Record<ProductListingKind, string>` makes it compile-checked against THAT
92
+ * union — but `ProductListingKind` is itself hand-written above and is not
93
+ * checked against `ListingType`, which is why this is still on the unchecked
94
+ * list.
95
+ */
96
+ spares: LISTING_TYPE_VALUES.SPARES,
76
97
  // Legacy → canonical.
77
98
  product: LISTING_TYPE_VALUES.STANDARD,
78
99
  preorder: LISTING_TYPE_VALUES.PRE_ORDER,
@@ -232,6 +253,17 @@ export class ProductRepository extends BaseRepository {
232
253
  if (!doc.categorySlugs || doc.categorySlugs.length === 0) {
233
254
  doc.categorySlugs = doc.category ? [doc.category] : [];
234
255
  }
256
+ /*
257
+ * A6 — widen a legacy `images: string[]` document to `ProductImage[]`.
258
+ *
259
+ * 🛑 This is HALF the normalisation and must stay paired with the write
260
+ * half in `create`/`update` below. Read-only widening is Root Cause #101
261
+ * exactly: the field would be the object form on every object anyone
262
+ * inspects and still a bare string array in Firestore, so the next write
263
+ * of an untouched document would persist whichever shape the caller
264
+ * happened to hold.
265
+ */
266
+ doc.images = toProductImages(doc.images);
235
267
  return doc;
236
268
  }
237
269
  /**
@@ -833,6 +865,13 @@ export class ProductRepository extends BaseRepository {
833
865
  ...data,
834
866
  ...derived,
835
867
  ...stock,
868
+ /*
869
+ * A6 — only when the write NAMES `images`. An unconditional
870
+ * `toProductImages(data.images)` would write `[]` over a real gallery on
871
+ * every stock decrement and status flip, because `data` does not carry
872
+ * the field at all on those paths.
873
+ */
874
+ ...(data.images !== undefined ? { images: toProductImages(data.images) } : {}),
836
875
  searchTxt: buildProductSearchTxt(merged),
837
876
  });
838
877
  this.cacheSet(updated);
@@ -907,7 +946,17 @@ export class ProductRepository extends BaseRepository {
907
946
  // all. A seed row already carrying the correct chain derives to the same
908
947
  // value, so this is a no-op for every existing fixture.
909
948
  const derived = await this.deriveTaxonomy(input, true);
910
- const withTaxonomy = { ...input, ...derived };
949
+ /*
950
+ * A6 — the WRITE half of the `images` normalisation (the read half is in
951
+ * `mapDoc`). Every create path funnels through here, so a caller handing
952
+ * us `string[]` — a seed row, an older client, the shipment-link route —
953
+ * stores the object form rather than leaving the collection mixed.
954
+ */
955
+ const withTaxonomy = {
956
+ ...input,
957
+ ...derived,
958
+ ...(input.images !== undefined ? { images: toProductImages(input.images) } : {}),
959
+ };
911
960
  const productData = {
912
961
  ...withTaxonomy,
913
962
  barcodeId,
@@ -1053,17 +1102,89 @@ export class ProductRepository extends BaseRepository {
1053
1102
  .get();
1054
1103
  return snapshot.docs.map((doc) => this.mapDoc(doc));
1055
1104
  }
1056
- async findByGroupId(groupId) {
1057
- // Tier GP: groups may mix standard products + pre-orders (never auctions).
1105
+ /**
1106
+ * Every member of a group, UNFILTERED — drafts, and every listing type.
1107
+ *
1108
+ * 🛑 This is the scope `dissolveGroup` needs and the ONLY one that may be
1109
+ * unfiltered. A filtered dissolve leaves the excluded members carrying a
1110
+ * `groupId` pointing at a group that no longer exists, which nothing ever
1111
+ * clears — and that orphan is invisible until someone wonders why a product
1112
+ * claims to be in a group with no other members.
1113
+ */
1114
+ async findAllByGroupId(groupId) {
1058
1115
  const snapshot = await this.db
1059
1116
  .collection(this.collection)
1060
1117
  .where("groupId", "==", groupId)
1061
- .where(PRODUCT_FIELDS.LISTING_TYPE, "in", [
1062
- LISTING_TYPE_VALUES.STANDARD,
1063
- LISTING_TYPE_VALUES.PRE_ORDER,
1064
- ])
1065
1118
  .get();
1066
- return snapshot.docs.map((doc) => this.mapDoc(doc));
1119
+ const byQuery = snapshot.docs.map((doc) => this.mapDoc(doc));
1120
+ return this.withParentSideFallback(groupId, byQuery);
1121
+ }
1122
+ /**
1123
+ * Group members a BUYER may see: eligible types, published only.
1124
+ *
1125
+ * 🛑 The type filter is DERIVED from `canJoinProductGroup`, not hard-coded.
1126
+ * It used to be a literal `["standard", "pre-order"]`, which silently dropped
1127
+ * every `stickers` / `art` member from the query — so `ShowGroupSection` saw
1128
+ * one member and returned null, and the picker was unreachable with nothing
1129
+ * erroring anywhere (Root Cause #58).
1130
+ *
1131
+ * 🛑 The `status` filter is NEW and is a leak fix, not a tightening. There
1132
+ * was no status filter at all, so DRAFT members were reaching the public
1133
+ * `/api/products/group/[groupId]` payload; widening the type list without
1134
+ * this would have widened that leak at the same time.
1135
+ */
1136
+ async findGroupMembersForPublic(groupId) {
1137
+ const eligible = ALL_LISTING_TYPES.filter((t) => pluginFor(t).canJoinProductGroup);
1138
+ const snapshot = await this.db
1139
+ .collection(this.collection)
1140
+ .where("groupId", "==", groupId)
1141
+ // `in` caps at 30 values; this is 4 of 9 today and cannot approach it.
1142
+ .where(PRODUCT_FIELDS.LISTING_TYPE, "in", eligible)
1143
+ .where(PRODUCT_FIELDS.STATUS, "==", ProductStatusValues.PUBLISHED)
1144
+ .get();
1145
+ const byQuery = snapshot.docs.map((doc) => this.mapDoc(doc));
1146
+ const withFallback = await this.withParentSideFallback(groupId, byQuery);
1147
+ /*
1148
+ * 🛑 The fallback results are filtered TOO. Without this the fallback
1149
+ * becomes the hole the query just closed — a draft or an ineligible member
1150
+ * would reach the public payload through the parent's own list.
1151
+ */
1152
+ const eligibleSet = new Set(eligible);
1153
+ return withFallback.filter((p) => eligibleSet.has(String(p.listingType ?? LISTING_TYPE_VALUES.STANDARD)) &&
1154
+ p.status === ProductStatusValues.PUBLISHED);
1155
+ }
1156
+ /**
1157
+ * Union the child-side query with the PARENT's own `groupChildSlugs`.
1158
+ *
1159
+ * Mirrors `resolveBundleMemberIds`, and exists for the same reason: Root
1160
+ * Cause #42 documented a parent carrying `groupChildSlugs` while its children
1161
+ * had no `groupId`, so a child-side query resolved only the parent and the
1162
+ * group rendered as one member. The repository's six mutating methods all
1163
+ * write both sides in one batch, so code cannot produce that state — but a
1164
+ * seeded fixture or a direct Firestore edit still can, and the READER was
1165
+ * never made resilient. `listBundleMembers` got this fix; this did not.
1166
+ */
1167
+ async withParentSideFallback(groupId, byQuery) {
1168
+ const seen = new Set(byQuery.map((p) => p.id));
1169
+ /*
1170
+ * `startGroup` sets `groupId = parent.slug`, so the group id IS the
1171
+ * parent's slug and one lookup finds it. If that ever stops being true the
1172
+ * fallback simply finds nothing, which is the same behaviour as today.
1173
+ */
1174
+ const parent = byQuery.find((p) => p.isGroupParent) ?? (await this.findById(groupId));
1175
+ const missing = (parent?.groupChildSlugs ?? []).filter((id) => !seen.has(id));
1176
+ if (missing.length === 0)
1177
+ return byQuery;
1178
+ const extra = await Promise.all(missing.map((id) => this.findById(id)));
1179
+ return [...byQuery, ...extra.filter((p) => p !== null)];
1180
+ }
1181
+ /**
1182
+ * @deprecated Ambiguous about scope — say which one you mean.
1183
+ * Kept so no caller silently changes behaviour; it resolves to the PUBLIC
1184
+ * scope, which is what every existing caller wanted.
1185
+ */
1186
+ async findByGroupId(groupId) {
1187
+ return this.findGroupMembersForPublic(groupId);
1067
1188
  }
1068
1189
  async startGroup(productId, slug) {
1069
1190
  await this.getCollection().doc(productId).update({
@@ -1076,7 +1197,10 @@ export class ProductRepository extends BaseRepository {
1076
1197
  await this.getCollection().doc(productId).update({ groupTitle });
1077
1198
  }
1078
1199
  async dissolveGroup(groupId) {
1079
- const members = await this.findByGroupId(groupId);
1200
+ // UNFILTERED on purpose — see findAllByGroupId. Dissolving must clear every
1201
+ // member's fields, including drafts and ineligible types, or they keep a
1202
+ // groupId pointing at nothing.
1203
+ const members = await this.findAllByGroupId(groupId);
1080
1204
  const batch = this.db.batch();
1081
1205
  const clearFields = {
1082
1206
  groupId: null,
@@ -1090,15 +1214,62 @@ export class ProductRepository extends BaseRepository {
1090
1214
  }
1091
1215
  await batch.commit();
1092
1216
  }
1217
+ /**
1218
+ * Link ONE child into a parent's group.
1219
+ *
1220
+ * 🛑 `arrayUnion`, not a spread of the in-memory `parent.groupChildSlugs`.
1221
+ *
1222
+ * The spread read a snapshot the CALLER was holding, so linking N children in
1223
+ * a loop produced exactly ONE entry: every iteration computed
1224
+ * `[...(stale ?? []), child]` from the same pre-loop document and the last
1225
+ * write won. Nothing errored — each child got its own `groupId` correctly, so
1226
+ * `findAllByGroupId` found them all while the parent's own
1227
+ * `groupChildSlugs` claimed one member, which is what the group picker's
1228
+ * fallback path and `GroupSettingsPanel` read.
1229
+ *
1230
+ * `arrayUnion` is also idempotent, so re-linking an existing child is a
1231
+ * no-op instead of a duplicate id.
1232
+ *
1233
+ * For a known set, prefer `linkChildrenToGroup` below — one batch instead of
1234
+ * N, which matters against Rule #6's round-trip budget when promoting a
1235
+ * 60-part sticker sheet.
1236
+ */
1093
1237
  async linkChildToGroup(parent, child) {
1238
+ await this.linkChildrenToGroup(parent, [child]);
1239
+ }
1240
+ /**
1241
+ * Link SEVERAL children into a parent's group in one batch.
1242
+ *
1243
+ * Built for `promoteStickerSheetToListings`, which links every unique part of
1244
+ * a sheet at once — N calls to `linkChildToGroup` would be N batch commits
1245
+ * for work that is one logical change.
1246
+ */
1247
+ async linkChildrenToGroup(parent, children) {
1248
+ if (children.length === 0)
1249
+ return;
1094
1250
  const batch = this.db.batch();
1095
- batch.update(this.getCollection().doc(child.id), {
1096
- groupId: parent.groupId,
1097
- groupParentSlug: parent.slug ?? parent.id,
1098
- groupTitle: parent.groupTitle ?? null,
1099
- });
1251
+ for (const child of children) {
1252
+ batch.update(this.getCollection().doc(child.id), {
1253
+ groupId: parent.groupId,
1254
+ groupParentSlug: parent.slug ?? parent.id,
1255
+ groupTitle: parent.groupTitle ?? null,
1256
+ });
1257
+ }
1100
1258
  batch.update(this.getCollection().doc(parent.id), {
1101
- groupChildSlugs: [...(parent.groupChildSlugs ?? []), child.id],
1259
+ /*
1260
+ * Ids, matching `unlinkChildFromGroup`'s filter, which removes by id OR
1261
+ * slug. These are pure slugs for products (`id === slug`), so the two
1262
+ * agree — but the field name says "slugs" and the stored values are ids,
1263
+ * which is worth knowing before writing a reader that assumes either.
1264
+ */
1265
+ /*
1266
+ * 🛑 `arrayUnion` from the CONTRACTS layer, not `firebase-admin`
1267
+ * directly. Two sibling repositories import `FieldValue` from
1268
+ * firebase-admin and this one deliberately does not — its own imports go
1269
+ * through `contracts/field-ops`, which is what keeps the admin SDK out of
1270
+ * this module's static import graph (Root Cause #24/#6).
1271
+ */
1272
+ groupChildSlugs: arrayUnion(...children.map((c) => c.id)),
1102
1273
  });
1103
1274
  await batch.commit();
1104
1275
  }
@@ -1185,6 +1356,39 @@ export class ProductRepository extends BaseRepository {
1185
1356
  await super.delete(id);
1186
1357
  await this.syncCategoryCounts(prior, null);
1187
1358
  }
1359
+ /**
1360
+ * Every live listing OF one glossary part, across all stores.
1361
+ *
1362
+ * 🛑 NOT filtered by `listingType`, deliberately. Both `spares` and the
1363
+ * 972 generated per-leaf catalogue rows (which are `standard`) carry
1364
+ * `partGlossaryId`, and a buyer on `/parts/{slot}/{code}` wants any listing
1365
+ * of that part — not only the ones that happen to be filed under the newer
1366
+ * type. Narrowing to `spares` would hide most of the real inventory.
1367
+ *
1368
+ * 🛑 `orderBy` is `createdAt`, which EVERY document carries
1369
+ * (`BaseRepository.create` writes it). Ordering by an optional field silently
1370
+ * excludes every row lacking it — Root Cause #100, where `itemsSold` on zero
1371
+ * of 95 products made a store`s whole review endpoint return nothing.
1372
+ *
1373
+ * Availability is filtered IN MEMORY rather than in the query: "available" is
1374
+ * negation-shaped and no query expresses it, which is the asymmetry
1375
+ * `baseAvailable` exists for. The window is bounded, so the cost is a few
1376
+ * rows read and discarded.
1377
+ */
1378
+ async findByPartGlossaryId(partGlossaryId, limit = 12) {
1379
+ if (!partGlossaryId)
1380
+ return [];
1381
+ const snap = await this.getCollection()
1382
+ .where(PRODUCT_FIELDS.PART_GLOSSARY_ID, "==", partGlossaryId)
1383
+ .where(PRODUCT_FIELDS.STATUS, "==", PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED)
1384
+ .orderBy(PRODUCT_FIELDS.CREATED_AT, "desc")
1385
+ // Over-fetch, because the availability filter below removes rows. Taking
1386
+ // exactly `limit` here would return fewer than `limit` buyable listings
1387
+ // whenever any of them were sold.
1388
+ .limit(Math.min(60, limit * 3))
1389
+ .get();
1390
+ return snap.docs.map((d) => this.mapDoc(d)).slice(0, limit);
1391
+ }
1188
1392
  /**
1189
1393
  * Shared scope construction for `list()` and `countBy*()` — both must narrow
1190
1394
  * by exactly the same store/status/category/search predicates or a tab's
@@ -1481,6 +1685,20 @@ ProductRepository.SIEVE_FIELDS = {
1481
1685
  // admin and seller product lists.
1482
1686
  featured: { canFilter: true, canSort: true },
1483
1687
  listingType: { canFilter: true, canSort: false },
1688
+ /*
1689
+ * 🛑 Filterable so a `partGlossaryId` filter is not SILENTLY DROPPED.
1690
+ * `sieve.ts` sets `throwExceptions: false`, so a field absent from this map
1691
+ * does not raise — `findField` returns undefined and the clause vanishes,
1692
+ * which on a page scoped to one part means the ENTIRE catalogue comes back
1693
+ * (Root Cause #62). Both of these are real `ProductDocument` fields, so
1694
+ * neither is the orphan half of that pattern.
1695
+ *
1696
+ * `canSort: false` on both: they are identity, not an ordering anyone wants
1697
+ * — and a sort option whose field is `canSort: false` is dropped just as
1698
+ * silently (Root Cause #63).
1699
+ */
1700
+ partGlossaryId: { canFilter: true, canSort: false },
1701
+ colourway: { canFilter: true, canSort: false },
1484
1702
  isPromoted: { canFilter: true, canSort: true },
1485
1703
  isOnSale: { canFilter: true, canSort: false },
1486
1704
  price: { canFilter: true, canSort: true },