@mohasinac/appkit 4.53.0 โ†’ 4.53.2

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 (520) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/scaffolds/AppShell.js +1 -1
  3. package/dist/_internal/client/theme/ThemeProvider.js +15 -1
  4. package/dist/_internal/server/features/auctions/og.d.ts +3 -1
  5. package/dist/_internal/server/features/auctions/og.js +3 -1
  6. package/dist/_internal/server/features/bundles/actions.js +1 -1
  7. package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
  8. package/dist/_internal/server/features/business-documents/download.js +91 -0
  9. package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
  10. package/dist/_internal/server/features/categories/adapters.js +10 -0
  11. package/dist/_internal/server/features/categories/price-guidance-resolve.d.ts +66 -0
  12. package/dist/_internal/server/features/categories/price-guidance-resolve.js +138 -0
  13. package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
  14. package/dist/_internal/server/features/checkout/actions.js +106 -12
  15. package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
  16. package/dist/_internal/server/features/checkout/data.d.ts +22 -0
  17. package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
  18. package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
  19. package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
  20. package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
  21. package/dist/_internal/server/features/classified/metadata.js +1 -1
  22. package/dist/_internal/server/features/classified/og.d.ts +3 -1
  23. package/dist/_internal/server/features/classified/og.js +3 -1
  24. package/dist/_internal/server/features/digital-code/metadata.js +1 -1
  25. package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
  26. package/dist/_internal/server/features/digital-code/og.js +3 -1
  27. package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
  28. package/dist/_internal/server/features/digital-code/pool.js +19 -4
  29. package/dist/_internal/server/features/live/metadata.js +1 -1
  30. package/dist/_internal/server/features/live/og.d.ts +3 -1
  31. package/dist/_internal/server/features/live/og.js +3 -1
  32. package/dist/_internal/server/features/messages/actions.js +1 -1
  33. package/dist/_internal/server/features/parts/og.js +1 -1
  34. package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
  35. package/dist/_internal/server/features/pre-orders/og.js +3 -1
  36. package/dist/_internal/server/features/price-index/market.d.ts +57 -0
  37. package/dist/_internal/server/features/price-index/market.js +120 -0
  38. package/dist/_internal/server/features/price-index/search.d.ts +18 -0
  39. package/dist/_internal/server/features/price-index/search.js +47 -7
  40. package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
  41. package/dist/_internal/server/features/prize-draws/og.js +3 -1
  42. package/dist/_internal/server/features/products/group-members.d.ts +26 -0
  43. package/dist/_internal/server/features/products/group-members.js +7 -1
  44. package/dist/_internal/server/features/products/list-public.d.ts +12 -0
  45. package/dist/_internal/server/features/products/list-public.js +10 -1
  46. package/dist/_internal/server/features/products/listing-tab-counts.d.ts +25 -1
  47. package/dist/_internal/server/features/products/listing-tab-counts.js +128 -10
  48. package/dist/_internal/server/features/products/og.d.ts +3 -1
  49. package/dist/_internal/server/features/products/og.js +3 -1
  50. package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
  51. package/dist/_internal/server/features/search/scoped-sources.js +252 -0
  52. package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
  53. package/dist/_internal/server/features/search/search-everything.js +70 -20
  54. package/dist/_internal/server/features/seo/sitemap.js +6 -0
  55. package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
  56. package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
  57. package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
  58. package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
  59. package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
  60. package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
  61. package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
  62. package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
  63. package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
  64. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  65. package/dist/_internal/server/features/stores/adapters.js +16 -0
  66. package/dist/_internal/server/features/stores/index.d.ts +1 -0
  67. package/dist/_internal/server/features/stores/index.js +1 -0
  68. package/dist/_internal/server/features/stores/tabs.d.ts +30 -0
  69. package/dist/_internal/server/features/stores/tabs.js +124 -0
  70. package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
  71. package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
  72. package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
  73. package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
  74. package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
  75. package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
  76. package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
  77. package/dist/_internal/shared/checkout/rules/index.js +9 -0
  78. package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
  79. package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
  80. package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
  81. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
  82. package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
  83. package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
  84. package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
  85. package/dist/_internal/shared/config/schema.d.ts +122 -0
  86. package/dist/_internal/shared/features/cart/schema.js +3 -2
  87. package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
  88. package/dist/_internal/shared/features/products/group-rules.js +142 -0
  89. package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
  90. package/dist/_internal/shared/listing-types/_registry.js +39 -1
  91. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  92. package/dist/_internal/shared/listing-types/art/config.js +2 -0
  93. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  94. package/dist/_internal/shared/listing-types/auction/config.js +2 -0
  95. package/dist/_internal/shared/listing-types/availability.js +48 -0
  96. package/dist/_internal/shared/listing-types/capabilities.js +19 -0
  97. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  98. package/dist/_internal/shared/listing-types/classified/config.js +2 -0
  99. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  100. package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
  101. package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
  102. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  103. package/dist/_internal/shared/listing-types/live/config.js +2 -0
  104. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  105. package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
  106. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  107. package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
  108. package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
  109. package/dist/_internal/shared/listing-types/spares/config.js +60 -0
  110. package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
  111. package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
  112. package/dist/_internal/shared/listing-types/spares/og.js +1 -0
  113. package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
  114. package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
  115. package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
  116. package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
  117. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  118. package/dist/_internal/shared/listing-types/standard/config.js +2 -0
  119. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  120. package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
  121. package/dist/_internal/shared/search/match.d.ts +11 -0
  122. package/dist/_internal/shared/search/match.js +30 -3
  123. package/dist/_internal/shared/search/to-groups.d.ts +52 -0
  124. package/dist/_internal/shared/search/to-groups.js +141 -0
  125. package/dist/client.d.ts +8 -2
  126. package/dist/client.js +4 -1
  127. package/dist/constants/api-endpoints.d.ts +46 -15
  128. package/dist/constants/api-endpoints.js +35 -5
  129. package/dist/constants/cache-invalidation.js +3 -1
  130. package/dist/constants/field-names.d.ts +6 -0
  131. package/dist/constants/field-names.js +6 -0
  132. package/dist/features/about/components/PolicyPageView.d.ts +22 -2
  133. package/dist/features/about/components/PolicyPageView.js +14 -2
  134. package/dist/features/about/components/PublicProfileView.js +2 -1
  135. package/dist/features/about/components/ShippingPolicyView.js +42 -2
  136. package/dist/features/account/components/NotificationBell.js +1 -1
  137. package/dist/features/account/hooks/useProfileStats.js +5 -1
  138. package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
  139. package/dist/features/addresses/repository/addresses.repository.js +21 -0
  140. package/dist/features/admin/actions/admin-actions.js +16 -1
  141. package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
  142. package/dist/features/admin/components/AdminNavigationView.js +1 -1
  143. package/dist/features/admin/components/AdminPageHeader.js +1 -1
  144. package/dist/features/admin/components/AdminProductEditorView.js +17 -2
  145. package/dist/features/admin/components/AdminSidebar.js +1 -1
  146. package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
  147. package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
  148. package/dist/features/admin/components/AdminSparesView.js +30 -0
  149. package/dist/features/admin/components/AdminUsersView.js +9 -7
  150. package/dist/features/admin/components/index.d.ts +2 -0
  151. package/dist/features/admin/components/index.js +1 -0
  152. package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
  153. package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
  154. package/dist/features/admin/constants/filter-tabs.js +1 -0
  155. package/dist/features/admin/schemas/firestore.d.ts +26 -2
  156. package/dist/features/auctions/components/AuctionDetailPageView.js +12 -18
  157. package/dist/features/auth/repository/user.repository.d.ts +13 -0
  158. package/dist/features/auth/repository/user.repository.js +28 -0
  159. package/dist/features/auth/schemas/firestore.d.ts +2 -0
  160. package/dist/features/auth/schemas/firestore.js +2 -0
  161. package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
  162. package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
  163. package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
  164. package/dist/features/business-documents/schemas/firestore.js +75 -0
  165. package/dist/features/cart/actions/cart-actions.js +83 -6
  166. package/dist/features/cart/repository/cart.repository.js +32 -4
  167. package/dist/features/cart/schemas/firestore.d.ts +20 -0
  168. package/dist/features/cart/schemas/index.d.ts +2 -2
  169. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
  170. package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
  171. package/dist/features/categories/api/[id]/price-guidance/route.js +90 -0
  172. package/dist/features/categories/components/BrandDetailPageView.js +32 -8
  173. package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
  174. package/dist/features/categories/components/BrandDetailTabs.js +28 -3
  175. package/dist/features/categories/components/BundleCollage.js +3 -2
  176. package/dist/features/categories/components/BundleItemsPicker.js +3 -4
  177. package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
  178. package/dist/features/categories/components/CategoryDetailPageView.js +157 -20
  179. package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
  180. package/dist/features/categories/components/CategoryDetailTabs.js +41 -7
  181. package/dist/features/categories/components/CategoryGrid.js +10 -1
  182. package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
  183. package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
  184. package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
  185. package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
  186. package/dist/features/categories/constants/category-tabs.d.ts +20 -25
  187. package/dist/features/categories/constants/category-tabs.js +22 -39
  188. package/dist/features/categories/repository/categories.repository.d.ts +12 -0
  189. package/dist/features/categories/repository/categories.repository.js +20 -0
  190. package/dist/features/categories/schemas/firestore.d.ts +22 -1
  191. package/dist/features/categories/schemas/firestore.js +10 -0
  192. package/dist/features/categories/schemas/index.d.ts +6 -0
  193. package/dist/features/categories/schemas/index.js +2 -0
  194. package/dist/features/categories/types/index.d.ts +8 -0
  195. package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
  196. package/dist/features/categories/utils/taxonomy-visual.js +26 -10
  197. package/dist/features/classified/components/ClassifiedDetailPageView.js +14 -10
  198. package/dist/features/contact/components/ContactPageView.d.ts +33 -1
  199. package/dist/features/contact/components/ContactPageView.js +58 -28
  200. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +14 -10
  201. package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
  202. package/dist/features/grouped/schemas/firestore.d.ts +5 -0
  203. package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
  204. package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
  205. package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
  206. package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
  207. package/dist/features/homepage/components/HowItWorksSection.js +4 -4
  208. package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
  209. package/dist/features/homepage/components/PromoGrid.js +12 -3
  210. package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
  211. package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
  212. package/dist/features/homepage/components/index.d.ts +0 -20
  213. package/dist/features/homepage/components/index.js +0 -11
  214. package/dist/features/homepage/index.d.ts +0 -2
  215. package/dist/features/homepage/index.js +0 -2
  216. package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
  217. package/dist/features/homepage/lib/section-renderer.js +202 -58
  218. package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
  219. package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
  220. package/dist/features/homepage/schemas/firestore.d.ts +106 -3
  221. package/dist/features/homepage/schemas/firestore.js +32 -2
  222. package/dist/features/homepage/schemas/index.d.ts +36 -7
  223. package/dist/features/homepage/schemas/index.js +39 -27
  224. package/dist/features/homepage/server.d.ts +0 -3
  225. package/dist/features/homepage/server.js +0 -3
  226. package/dist/features/homepage/types/index.d.ts +0 -47
  227. package/dist/features/homepage/types/index.js +49 -0
  228. package/dist/features/layout/AppLayoutShell.js +1 -1
  229. package/dist/features/layout/BottomNavItem.js +1 -1
  230. package/dist/features/layout/BottomSheet.js +1 -1
  231. package/dist/features/layout/TitleBarLayout.js +1 -1
  232. package/dist/features/live/components/LiveItemDetailPageView.js +14 -10
  233. package/dist/features/media/hooks/useMedia.js +17 -0
  234. package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
  235. package/dist/features/media/repository/media-assets.repository.js +31 -0
  236. package/dist/features/media/trust-signals.d.ts +85 -0
  237. package/dist/features/media/trust-signals.js +126 -0
  238. package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
  239. package/dist/features/parts/components/PartDetailPageView.js +2 -2
  240. package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
  241. package/dist/features/parts/components/PartsIndexPageView.js +2 -2
  242. package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
  243. package/dist/features/parts/components/PartsSearchBox.js +57 -0
  244. package/dist/features/parts/schemas/firestore.d.ts +149 -3
  245. package/dist/features/parts/schemas/firestore.js +184 -0
  246. package/dist/features/payments/repository/payout.repository.d.ts +15 -0
  247. package/dist/features/payments/repository/payout.repository.js +21 -0
  248. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +12 -18
  249. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
  250. package/dist/features/price-index/components/PriceIndexToolView.d.ts +24 -1
  251. package/dist/features/price-index/components/PriceIndexToolView.js +299 -17
  252. package/dist/features/price-index/lookup-target.d.ts +48 -0
  253. package/dist/features/price-index/lookup-target.js +107 -0
  254. package/dist/features/products/components/AvailabilityScopeHint.d.ts +22 -0
  255. package/dist/features/products/components/AvailabilityScopeHint.js +27 -0
  256. package/dist/features/products/components/CompareOverlay.d.ts +20 -2
  257. package/dist/features/products/components/CompareOverlay.js +23 -3
  258. package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
  259. package/dist/features/products/components/CustomSectionTabContent.js +29 -3
  260. package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
  261. package/dist/features/products/components/GroupMemberPicker.js +46 -8
  262. package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
  263. package/dist/features/products/components/GroupSettingsPanel.js +92 -3
  264. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  265. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
  266. package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
  267. package/dist/features/products/components/ProductDetailPageView.js +81 -17
  268. package/dist/features/products/components/ProductForm.d.ts +13 -1
  269. package/dist/features/products/components/ProductForm.js +16 -2
  270. package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
  271. package/dist/features/products/components/ProductsIndexListing.js +3 -3
  272. package/dist/features/products/components/ShowGroupSection.js +15 -2
  273. package/dist/features/products/components/SparesListView.d.ts +7 -0
  274. package/dist/features/products/components/SparesListView.js +39 -0
  275. package/dist/features/products/components/SublistingCarouselSection.js +2 -1
  276. package/dist/features/products/constants/action-defs.d.ts +1 -0
  277. package/dist/features/products/constants/action-defs.js +1 -0
  278. package/dist/features/products/repository/products.repository.d.ts +99 -0
  279. package/dist/features/products/repository/products.repository.js +240 -18
  280. package/dist/features/products/schemas/firestore.d.ts +97 -4
  281. package/dist/features/products/schemas/firestore.js +104 -0
  282. package/dist/features/products/schemas/index.d.ts +10 -19
  283. package/dist/features/products/schemas/index.js +23 -6
  284. package/dist/features/products/types/index.d.ts +45 -1
  285. package/dist/features/products/utils/group-member-image.d.ts +46 -0
  286. package/dist/features/products/utils/group-member-image.js +4 -0
  287. package/dist/features/products/utils/listing-badge-variant.js +4 -0
  288. package/dist/features/reviews/components/ReviewsList.js +1 -1
  289. package/dist/features/scams/components/ScamProfileView.js +1 -1
  290. package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
  291. package/dist/features/search/components/EntityTypeahead.js +92 -0
  292. package/dist/features/search/components/Search.d.ts +12 -1
  293. package/dist/features/search/components/Search.js +47 -3
  294. package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
  295. package/dist/features/search/components/SearchFacetRail.js +92 -0
  296. package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
  297. package/dist/features/search/components/SearchResultsClient.js +103 -0
  298. package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
  299. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  300. package/dist/features/search/components/index.d.ts +0 -6
  301. package/dist/features/search/components/index.js +0 -3
  302. package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
  303. package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
  304. package/dist/features/search/hooks/useScopedSearch.js +114 -0
  305. package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
  306. package/dist/features/search/hooks/useSiteSearch.js +149 -0
  307. package/dist/features/search/index.d.ts +0 -4
  308. package/dist/features/search/index.js +0 -4
  309. package/dist/features/search/manifest.d.ts +12 -0
  310. package/dist/features/search/manifest.js +17 -2
  311. package/dist/features/search/server.d.ts +0 -3
  312. package/dist/features/search/server.js +0 -3
  313. package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
  314. package/dist/features/seller/components/SellerListingTypeView.js +20 -0
  315. package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
  316. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  317. package/dist/features/seller/components/SellerProductShell.js +2 -1
  318. package/dist/features/seller/components/SellerProductsView.js +3 -2
  319. package/dist/features/seller/components/SellerSidebar.js +1 -1
  320. package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
  321. package/dist/features/seller/components/SellerSparesView.js +6 -0
  322. package/dist/features/seller/components/index.d.ts +2 -0
  323. package/dist/features/seller/components/index.js +1 -0
  324. package/dist/features/shell/CommandPalette.js +1 -1
  325. package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
  326. package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
  327. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
  328. package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
  329. package/dist/features/stores/components/StoreHeader.js +1 -2
  330. package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
  331. package/dist/features/stores/components/StoreSparesPageView.js +48 -0
  332. package/dist/features/stores/schemas/firestore.d.ts +19 -0
  333. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
  334. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
  335. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
  336. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +392 -45
  337. package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
  338. package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
  339. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
  340. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
  341. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
  342. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
  343. package/dist/features/tester/seed-data/authored/index.js +16 -0
  344. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
  345. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
  346. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
  347. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
  348. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
  349. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
  350. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
  351. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +384 -18
  352. package/dist/index.d.ts +17 -57
  353. package/dist/index.js +35 -96
  354. package/dist/next/routing/route-map.d.ts +42 -1
  355. package/dist/next/routing/route-map.js +45 -0
  356. package/dist/providers/db-firebase/base.repository.d.ts +22 -0
  357. package/dist/providers/db-firebase/base.repository.js +36 -0
  358. package/dist/schemas/registry.d.ts +15 -6
  359. package/dist/security/blind-index-tokens.d.ts +54 -0
  360. package/dist/security/blind-index-tokens.js +152 -0
  361. package/dist/security/index.d.ts +1 -1
  362. package/dist/security/index.js +1 -1
  363. package/dist/security/settings-encryption.d.ts +35 -0
  364. package/dist/security/settings-encryption.js +91 -0
  365. package/dist/seed/_helpers/brand-rows.js +38 -14
  366. package/dist/seed/_helpers/category-forest.js +73 -7
  367. package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
  368. package/dist/seed/_helpers/category-images-generated.js +81 -6
  369. package/dist/seed/_helpers/category-models.d.ts +1 -1
  370. package/dist/seed/_helpers/category-models.js +7 -1
  371. package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
  372. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  373. package/dist/seed/_helpers/category-parts.js +50 -2
  374. package/dist/seed/_helpers/character-features.d.ts +1 -1
  375. package/dist/seed/_helpers/character-features.js +2 -1
  376. package/dist/seed/_helpers/franchise-rows.js +10 -2
  377. package/dist/seed/_helpers/media.d.ts +47 -0
  378. package/dist/seed/_helpers/media.js +34 -0
  379. package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
  380. package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
  381. package/dist/seed/categories-seed-data.js +124 -20
  382. package/dist/seed/grouped-listings-seed-data.js +16 -0
  383. package/dist/seed/homepage-sections-seed-data.js +94 -5
  384. package/dist/seed/index.d.ts +2 -3
  385. package/dist/seed/index.js +2 -2
  386. package/dist/seed/manifest.js +3 -0
  387. package/dist/seed/media-assets-seed-data.d.ts +1 -1
  388. package/dist/seed/media-assets-seed-data.js +746 -196
  389. package/dist/seed/model-composition-seed-data.js +122 -6
  390. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  391. package/dist/seed/part-glossary-seed-data.js +37219 -463
  392. package/dist/seed/products-art-seed-data.js +7 -18
  393. package/dist/seed/products-auctions-seed-data.js +56 -47
  394. package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
  395. package/dist/seed/products-beyblade-x-seed-data.js +476 -0
  396. package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
  397. package/dist/seed/products-catalogue-seed-data.js +1500 -1240
  398. package/dist/seed/products-classifieds-seed-data.js +9 -27
  399. package/dist/seed/products-digital-codes-seed-data.js +9 -18
  400. package/dist/seed/products-live-items-seed-data.js +5 -13
  401. package/dist/seed/products-preorders-seed-data.js +67 -33
  402. package/dist/seed/products-prize-draws-seed-data.js +8 -17
  403. package/dist/seed/products-spares-seed-data.d.ts +2 -0
  404. package/dist/seed/products-spares-seed-data.js +368 -0
  405. package/dist/seed/products-standard-seed-data.js +39 -126
  406. package/dist/seed/products-stickers-seed-data.js +59 -15
  407. package/dist/seed/site-settings-seed-data.js +8 -0
  408. package/dist/seo/json-ld.d.ts +25 -0
  409. package/dist/seo/json-ld.js +28 -0
  410. package/dist/server-entry.d.ts +3 -1
  411. package/dist/server-entry.js +2 -2
  412. package/dist/server.d.ts +11 -9
  413. package/dist/server.js +35 -25
  414. package/dist/styles.css +202 -1
  415. package/dist/tailwind-input.css +31 -17
  416. package/dist/tailwind-utilities.css +1 -1
  417. package/dist/ui/components/Accordion.js +1 -1
  418. package/dist/ui/components/Breadcrumb.js +1 -1
  419. package/dist/ui/components/DetailPageGallery.js +0 -2
  420. package/dist/ui/components/DetailPageHero.js +0 -1
  421. package/dist/ui/components/DetailPageTabs.js +1 -1
  422. package/dist/ui/components/Divider.js +3 -3
  423. package/dist/ui/components/GatedPrice.js +5 -1
  424. package/dist/ui/components/HorizontalScroller.js +1 -1
  425. package/dist/ui/components/ListingLayout.js +3 -3
  426. package/dist/ui/components/MediaTabs.style.css +81 -0
  427. package/dist/ui/components/Pagination.js +1 -1
  428. package/dist/ui/components/PendingValue.d.ts +45 -0
  429. package/dist/ui/components/PendingValue.js +7 -0
  430. package/dist/ui/components/Radio.style.css +119 -0
  431. package/dist/ui/components/RatingDisplay.js +2 -2
  432. package/dist/ui/components/SettingsSection.js +0 -2
  433. package/dist/ui/components/SiteLogo.js +3 -0
  434. package/dist/ui/components/SiteMark.js +3 -0
  435. package/dist/ui/components/SortDropdown.js +1 -1
  436. package/dist/ui/components/SpecTable.d.ts +58 -0
  437. package/dist/ui/components/SpecTable.js +8 -0
  438. package/dist/ui/components/StarRating.js +1 -3
  439. package/dist/ui/components/SummaryCard.js +1 -1
  440. package/dist/ui/components/index.style.css +1 -0
  441. package/dist/ui/icons/icon-registry.d.ts +1 -0
  442. package/dist/ui/icons/icon-registry.js +9 -1
  443. package/dist/ui/index.d.ts +4 -0
  444. package/dist/ui/index.js +2 -0
  445. package/dist/utils/search-txt-query.d.ts +12 -0
  446. package/dist/utils/search-txt-query.js +12 -0
  447. package/package.json +1 -1
  448. package/scripts/dedupe-peer-deps.mjs +18 -0
  449. package/scripts/seed-cli.mjs +250 -35
  450. package/scripts/verify-css-build.mjs +118 -37
  451. package/dist/_internal/server/features/search/actions.d.ts +0 -7
  452. package/dist/_internal/server/features/search/actions.js +0 -21
  453. package/dist/_internal/server/features/search/data.d.ts +0 -15
  454. package/dist/_internal/server/features/search/data.js +0 -18
  455. package/dist/_internal/server/features/search/index.d.ts +0 -3
  456. package/dist/_internal/server/features/search/index.js +0 -2
  457. package/dist/features/homepage/api/[id]/route.d.ts +0 -23
  458. package/dist/features/homepage/api/[id]/route.js +0 -103
  459. package/dist/features/homepage/api/route.d.ts +0 -14
  460. package/dist/features/homepage/api/route.js +0 -140
  461. package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
  462. package/dist/features/homepage/api/sections/[id]/route.js +0 -95
  463. package/dist/features/homepage/api/sections/route.d.ts +0 -13
  464. package/dist/features/homepage/api/sections/route.js +0 -82
  465. package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
  466. package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
  467. package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
  468. package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
  469. package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
  470. package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
  471. package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
  472. package/dist/features/homepage/components/HeroBanner.js +0 -43
  473. package/dist/features/homepage/components/HeroSection.d.ts +0 -7
  474. package/dist/features/homepage/components/HeroSection.js +0 -6
  475. package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
  476. package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
  477. package/dist/features/homepage/components/HomepageView.d.ts +0 -25
  478. package/dist/features/homepage/components/HomepageView.js +0 -5
  479. package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
  480. package/dist/features/homepage/components/NewsletterBanner.js +0 -37
  481. package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
  482. package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
  483. package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
  484. package/dist/features/homepage/components/TrustBadges.js +0 -46
  485. package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
  486. package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
  487. package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
  488. package/dist/features/homepage/hooks/useHomepage.js +0 -17
  489. package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
  490. package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
  491. package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
  492. package/dist/features/homepage/repository/homepage.repository.js +0 -25
  493. package/dist/features/search/actions/index.d.ts +0 -1
  494. package/dist/features/search/actions/index.js +0 -1
  495. package/dist/features/search/actions/search-actions.d.ts +0 -18
  496. package/dist/features/search/actions/search-actions.js +0 -43
  497. package/dist/features/search/api/route.d.ts +0 -22
  498. package/dist/features/search/api/route.js +0 -128
  499. package/dist/features/search/columns/index.d.ts +0 -17
  500. package/dist/features/search/columns/index.js +0 -49
  501. package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
  502. package/dist/features/search/components/SearchFiltersRow.js +0 -21
  503. package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
  504. package/dist/features/search/components/SearchResultsSection.js +0 -35
  505. package/dist/features/search/components/SearchView.d.ts +0 -27
  506. package/dist/features/search/components/SearchView.js +0 -8
  507. package/dist/features/search/hooks/useSearch.d.ts +0 -21
  508. package/dist/features/search/hooks/useSearch.js +0 -33
  509. package/dist/features/search/repository/search.repository.d.ts +0 -14
  510. package/dist/features/search/repository/search.repository.js +0 -59
  511. package/dist/features/search/schemas/index.d.ts +0 -70
  512. package/dist/features/search/schemas/index.js +0 -36
  513. package/dist/features/search/types/index.d.ts +0 -48
  514. package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
  515. package/dist/features/stores/components/StoreScopedSearch.js +0 -27
  516. package/dist/seed/defaults/homepage-sections.d.ts +0 -2
  517. package/dist/seed/defaults/homepage-sections.js +0 -60
  518. package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
  519. package/dist/seed/factories/homepage-section.factory.js +0 -13
  520. /package/dist/{features/search/types/index.js โ†’ _internal/shared/listing-types/spares/og.d.ts} +0 -0
@@ -11,9 +11,10 @@
11
11
  * the tab bar renders, so a tenth listing type is counted automatically.
12
12
  *
13
13
  * EXPORTS:
14
- * ListingTabScope โ€” the surface's scope (category / brand / store)
15
- * ListingTabCounts โ€” tabSlug -> count, `undefined` meaning "query failed"
16
- * listingTabCounts โ€” resolve every tab's count in one parallel batch
14
+ * ListingTabScope โ€” the surface's scope (category / brand / store)
15
+ * ListingTabCounts โ€” tabSlug -> count, `undefined` meaning "unknown"
16
+ * ListingTabCountResult โ€” the AVAILABLE and TOTAL maps, see below
17
+ * listingTabCounts โ€” resolve every tab's counts in one parallel batch
17
18
  *
18
19
  * @tag domain:products,categories,stores
19
20
  * @tag layer:server
@@ -25,6 +26,11 @@
25
26
  import { categoriesRepository, productRepository } from "../../../../repositories/index.js";
26
27
  import { serverLogger } from "../../../../monitoring/index.js";
27
28
  import { normalizeError } from "../../../../errors/normalize.js";
29
+ import { sieveAnd, sieveFilter, SIEVE_OP, sortBy } from "../../../../utils/sieve-builder.js";
30
+ import { PRODUCT_FIELDS } from "../../../../constants/field-names.js";
31
+ import { isListingRowAvailable, unavailableClausesFor, } from "../../../shared/listing-types/_registry.js";
32
+ import { normalizeListingType } from "../../../../features/products/utils/listing-type.js";
33
+ import { clauseToSieve, PUBLIC_PRODUCT_MAX_PAGE_SIZE } from "./list-public.js";
28
34
  const DEFAULT_STATUS = "published";
29
35
  /** Bundles a category owns, resolved by the same rule the bundles tab renders. */
30
36
  async function countBundles(scope) {
@@ -65,6 +71,101 @@ function bundleMatchesScope(bundle, scope) {
65
71
  }
66
72
  return true;
67
73
  }
74
+ /**
75
+ * How many rows in scope are NOT available, per listing type.
76
+ *
77
+ * โ”€โ”€ ๐Ÿ›‘ WHY THIS IS NOT AN AGGREGATE COUNT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
78
+ *
79
+ * Two independent reasons, either of which alone is disqualifying.
80
+ *
81
+ * 1. Unavailability is an OR across DIFFERENT fields (`availableQuantity == 0`
82
+ * OR `isSold == true` OR `prizeRevealStatus == closed` โ€ฆ). Firestore cannot
83
+ * express that in one query, and counting each clause separately and adding
84
+ * them double-counts every row matching two of them.
85
+ *
86
+ * 2. A clause selects a SUPERSET, deliberately. `madeToOrder` is pointedly NOT
87
+ * a clause (see the note in `availability.ts`), so a print-on-demand sticker
88
+ * sitting at `availableQuantity: 0` matches `availableQuantity == 0` and is
89
+ * nevertheless AVAILABLE. Only `isListingRowAvailable` knows that, and it is
90
+ * a per-row predicate. Measured on the category that prompted this work,
91
+ * a clause-count would have badged "Art & Stickers 2" over three available
92
+ * rows โ€” trading one wrong number for another.
93
+ *
94
+ * So this is Path C of `listPublicProducts` reused in miniature: one bounded
95
+ * query per DISTINCT clause, in parallel, merged and deduped by id, then every
96
+ * surviving row confirmed with the real predicate. Nine listing types collapse
97
+ * to five clauses, so the whole tab bar costs ~5 ร— 50 documents in one parallel
98
+ * round โ€” the same fan-out and the same budget that scope already spends.
99
+ *
100
+ * ๐Ÿ›‘ The queries are NOT narrowed to the tab's listing types. Firestore permits
101
+ * one array operator per query and the category scope already spends it on
102
+ * `categorySlugs array-contains-any`, so adding `listingType in (โ€ฆ)` risks an
103
+ * INVALID_ARGUMENT for no gain: unavailable rows are sparse, and tallying each
104
+ * row by its own `listingType` afterwards answers every tab from one pass.
105
+ */
106
+ async function unavailableByType(scope, types, now) {
107
+ const byType = new Map();
108
+ const clauses = unavailableClausesFor(types);
109
+ if (clauses.length === 0)
110
+ return { byType, truncated: false };
111
+ const status = scope.status ?? DEFAULT_STATUS;
112
+ const results = await Promise.all(clauses.map(async (clause) => {
113
+ // An inequality must be ordered by its own field โ€” Firestore appends it
114
+ // to the `orderBy` implicitly, so any other sort demands a composite
115
+ // index nobody declared (Root Cause #59).
116
+ const sorts = clause.op === "lt" && clause.sortField
117
+ ? sortBy(clause.sortField, "DESC")
118
+ : sortBy(PRODUCT_FIELDS.CREATED_AT, "DESC");
119
+ try {
120
+ return await productRepository.list({
121
+ filters: sieveAnd(sieveFilter(PRODUCT_FIELDS.STATUS, SIEVE_OP.EQ, status), clauseToSieve(clause, now)),
122
+ sorts,
123
+ page: 1,
124
+ pageSize: PUBLIC_PRODUCT_MAX_PAGE_SIZE,
125
+ }, {
126
+ storeId: scope.storeId,
127
+ categoriesIn: scope.categoriesIn,
128
+ brandName: scope.brandName,
129
+ });
130
+ }
131
+ catch (error) {
132
+ const e = normalizeError(error);
133
+ // ERROR, not warn: a clause that cannot run is almost always a missing
134
+ // composite index โ€” a permanent, silent skew of every badge on the
135
+ // page rather than a transient blip.
136
+ serverLogger.error(`listingTabCounts: unavailable clause failed (${clause.field} ${clause.op}); badges fall back to totals`, { field: clause.field, op: clause.op, error: e.message });
137
+ return null;
138
+ }
139
+ }));
140
+ const merged = new Map();
141
+ let truncated = false;
142
+ for (const result of results) {
143
+ if (!result) {
144
+ truncated = true;
145
+ continue;
146
+ }
147
+ const rows = result.items;
148
+ if (rows.length >= PUBLIC_PRODUCT_MAX_PAGE_SIZE)
149
+ truncated = true;
150
+ for (const row of rows) {
151
+ const id = typeof row.id === "string" ? row.id : JSON.stringify(row.id);
152
+ if (!merged.has(id))
153
+ merged.set(id, row);
154
+ }
155
+ }
156
+ for (const row of merged.values()) {
157
+ // The clause matched a superset; this is the real answer.
158
+ if (isListingRowAvailable(row, now))
159
+ continue;
160
+ const type = normalizeListingType(row);
161
+ byType.set(type, (byType.get(type) ?? 0) + 1);
162
+ }
163
+ return { byType, truncated };
164
+ }
165
+ /** The listing types one tab spans โ€” `"art|stickers"` is a pipe OR-group. */
166
+ function typesOf(tab) {
167
+ return String(tab.listingType).split("|").filter(Boolean);
168
+ }
68
169
  /**
69
170
  * Count every tab in `tabs` within `scope`, in one parallel batch.
70
171
  *
@@ -75,10 +176,12 @@ function bundleMatchesScope(bundle, scope) {
75
176
  * `stores` is not counted here: only the category page has that tab and it
76
177
  * already resolves its store list eagerly to render the tab body.
77
178
  */
78
- export async function listingTabCounts(tabs, scope) {
179
+ export async function listingTabCounts(tabs, scope, opts) {
180
+ const now = opts?.now ?? new Date();
79
181
  const listingTabs = tabs.filter((tab) => Boolean(tab.listingType));
80
182
  const wantsBundles = tabs.some((tab) => tab.collection === "bundles");
81
- const [typeCounts, bundleCount] = await Promise.all([
183
+ const allTypes = [...new Set(listingTabs.flatMap(typesOf))];
184
+ const [typeCounts, bundleCount, unavailable] = await Promise.all([
82
185
  productRepository.countByListingTypes(listingTabs.map((tab) => String(tab.listingType)), {
83
186
  status: scope.status ?? DEFAULT_STATUS,
84
187
  storeId: scope.storeId,
@@ -86,12 +189,27 @@ export async function listingTabCounts(tabs, scope) {
86
189
  brandName: scope.brandName,
87
190
  }),
88
191
  wantsBundles ? countBundles(scope) : Promise.resolve(undefined),
192
+ unavailableByType(scope, allTypes, now),
89
193
  ]);
90
- const counts = {};
194
+ const total = {};
195
+ const available = {};
91
196
  for (const tab of listingTabs) {
92
- counts[tab.id] = typeCounts[String(tab.listingType)];
197
+ const totalForTab = typeCounts[String(tab.listingType)];
198
+ total[tab.id] = totalForTab;
199
+ if (totalForTab === undefined || unavailable.truncated) {
200
+ // Unknown beats a number that is quietly too high. Falling back to the
201
+ // total reproduces the pre-2026-10-20 badge rather than inventing one.
202
+ available[tab.id] = totalForTab;
203
+ continue;
204
+ }
205
+ const gone = typesOf(tab).reduce((sum, type) => sum + (unavailable.byType.get(type) ?? 0), 0);
206
+ available[tab.id] = Math.max(0, totalForTab - gone);
207
+ }
208
+ if (wantsBundles) {
209
+ // A bundle has no availability scope of its own โ€” the bundles tab renders
210
+ // active bundle ROWS, not products, so the two numbers are one number.
211
+ total.bundles = bundleCount;
212
+ available.bundles = bundleCount;
93
213
  }
94
- if (wantsBundles)
95
- counts.bundles = bundleCount;
96
- return counts;
214
+ return { available, total };
97
215
  }
@@ -11,7 +11,9 @@ interface ProductDocLike {
11
11
  title?: string | null;
12
12
  price?: number | null;
13
13
  mainImage?: string | null;
14
- images?: (string | null | undefined)[] | null;
14
+ images?: ({
15
+ url: string;
16
+ } | string | null | undefined)[] | null;
15
17
  }
16
18
  /** High-level OG renderer โ€” accepts the raw product document from `getProductForDetail`. */
17
19
  export declare function renderProductOg(doc: ProductDocLike | null | undefined, opts: OgOptions & {
@@ -6,7 +6,9 @@ export function renderProductOg(doc, opts) {
6
6
  return renderProductOgImage({
7
7
  title: doc?.title ?? "Product",
8
8
  price: doc?.price ?? null,
9
- imageUrl: resolveOgImageUrl(doc?.mainImage || doc?.images?.[0] || null, opts.baseUrl),
9
+ imageUrl: resolveOgImageUrl(doc?.mainImage ||
10
+ (typeof doc?.images?.[0] === "string" ? doc.images[0] : doc?.images?.[0]?.url) ||
11
+ null, opts.baseUrl),
10
12
  }, opts.siteName);
11
13
  }
12
14
  export function renderProductOgImage(data, siteName) {
@@ -0,0 +1,26 @@
1
+ import { type SearchSource } from "./search-everything";
2
+ /** The viewer this search is scoped TO. Null means signed out. */
3
+ export interface SearchViewer {
4
+ readonly uid: string;
5
+ readonly role: string;
6
+ /** The store slug this viewer owns, if any. See ยง Store Identity. */
7
+ readonly storeId: string | null;
8
+ readonly isAdmin: boolean;
9
+ readonly isSeller: boolean;
10
+ }
11
+ /**
12
+ * Build the viewer's sources.
13
+ *
14
+ * ๐Ÿ›‘ Returns `[]` for a guest, which is what makes "zero authenticated
15
+ * requests when signed out" structural rather than a caller's discipline.
16
+ */
17
+ export declare function scopedSourcesFor(viewer: SearchViewer | null): SearchSource[];
18
+ /**
19
+ * ฮฃ `scanLimit`, for the budget check.
20
+ *
21
+ * Exported so `audit-scoped-search-ownership` R4 can print the arithmetic
22
+ * rather than just a verdict: "over budget" without the per-source figures
23
+ * tells you nothing about which source to shrink.
24
+ */
25
+ export declare function scopedScanTotal(sources: readonly SearchSource[]): number;
26
+ export declare function assertWithinScopedBudget(sources: readonly SearchSource[]): void;
@@ -0,0 +1,252 @@
1
+ /*
2
+ * WHY: A signed-in viewer should be able to find their own orders, offers
3
+ * and drafts from the same box โ€” without that box ever being able to
4
+ * return somebody else's.
5
+ * WHAT: Tier 2. Viewer-scoped `SearchSource`s, built per request from the
6
+ * session, plus the budget check.
7
+ *
8
+ * ## ๐Ÿ›‘ Filtered SERVER-side, never in the browser
9
+ *
10
+ * The same rule `projectActionIndexForViewer` states for the action index.
11
+ * Every source below carries an OWNER CLAUSE in its `where`, so the query
12
+ * itself cannot return another viewer's rows โ€” a client-side filter over a
13
+ * wider result is one rendering bug away from a disclosure, and the bug is
14
+ * invisible in review because the UI looks right.
15
+ *
16
+ * ## ๐Ÿ›‘ A guest makes ZERO authenticated requests
17
+ *
18
+ * `scopedSourcesFor(null)` returns `[]`, and the route refuses before
19
+ * touching Firestore. This is not an optimisation: a signed-out visitor
20
+ * whose search fires an authenticated request has a session the page does
21
+ * not know about, and the only honest way to prove the gate works is for
22
+ * there to be no request at all.
23
+ *
24
+ * ## The read budget, which is why the split exists
25
+ *
26
+ * guest 0 sources 0 docs 0 round trips
27
+ * buyer 6 ~120 1 (one Promise.all)
28
+ * seller +5 ~220 1
29
+ * admin +5 ~320 1
30
+ *
31
+ * Against `SCOPED_SCAN_BUDGET` = 400. Scoped sources use `scanLimit: 20`
32
+ * rather than the 60 the public tier uses, because an owner clause has
33
+ * already collapsed the candidate set โ€” a 60-doc window would be 40 reads
34
+ * spent proving there was nothing else.
35
+ *
36
+ * @tag domain:search
37
+ * @tag layer:server
38
+ * @tag pattern:none
39
+ * @tag access:server
40
+ * @tag consumers:/api/search/scoped
41
+ * @tag sideEffects:none
42
+ */
43
+ import { ROUTES } from "../../../../next/routing/route-map.js";
44
+ import { SCOPED_SCAN_BUDGET } from "./search-everything.js";
45
+ /** Scoped sources read a narrow window; the owner clause did the work. */
46
+ const SCOPED_SCAN = 20;
47
+ const str = (v) => typeof v === "string" && v.length > 0 ? v : undefined;
48
+ /**
49
+ * Every term must appear somewhere in the named fields.
50
+ *
51
+ * ๐Ÿ›‘ AND, matching the push-down path's `plan.rest.every(...)`. An OR here
52
+ * would make the two modes disagree about what a two-word query means, on
53
+ * the same page, in adjacent groups.
54
+ *
55
+ * ๐Ÿ›‘ Lower-cased comparison only โ€” NOT `normalizeSearchText`. That
56
+ * normaliser exists to make the WRITE path and the token query agree; this
57
+ * path has no tokens, and running it here would strip punctuation out of a
58
+ * tracking number or an order id, which are exactly the strings someone
59
+ * pastes into a scoped search.
60
+ */
61
+ function fieldsContain(fields) {
62
+ return (data, terms) => {
63
+ if (terms.length === 0)
64
+ return false;
65
+ const hay = fields
66
+ .map((f) => str(data[f]) ?? "")
67
+ .join(" ")
68
+ .toLowerCase();
69
+ return terms.every((t) => hay.includes(t.toLowerCase()));
70
+ };
71
+ }
72
+ /**
73
+ * The viewer's own records.
74
+ *
75
+ * ๐Ÿ›‘ `mode: "bounded-memory"` for everything PII-bearing, and `search-txt`
76
+ * only where the collection genuinely carries tokens that are safe to have.
77
+ * `orders` is the interesting case: `buildOrderSearchTxt` already indexes
78
+ * productTitle / storeName / trackingNumber and already EXCLUDES the
79
+ * shipping address, so it keeps the push-down. `addresses` has no
80
+ * `searchTxt` and must never grow one.
81
+ */
82
+ function selfSources(viewer) {
83
+ return [
84
+ {
85
+ key: "my-orders",
86
+ label: "Your orders",
87
+ collection: "orders",
88
+ // ๐Ÿ›‘ `userId`, NOT `buyerId`. The field is `userId` and a query on
89
+ // `buyerId` returns ZERO while raising nothing โ€” indistinguishable
90
+ // from "this buyer has no orders", which is a false-finding generator.
91
+ where: [["userId", viewer.uid]],
92
+ scanLimit: SCOPED_SCAN,
93
+ mode: "search-txt",
94
+ select: ["productTitle", "status", "trackingNumber"],
95
+ toHit: (id, d) => ({
96
+ id,
97
+ title: str(d.productTitle) ?? `Order ${id}`,
98
+ href: String(ROUTES.USER.ORDER_DETAIL(id)),
99
+ detail: str(d.status),
100
+ }),
101
+ moreHref: () => String(ROUTES.USER.ORDERS),
102
+ },
103
+ {
104
+ key: "my-offers",
105
+ label: "Your offers",
106
+ collection: "offers",
107
+ where: [["buyerId", viewer.uid]],
108
+ scanLimit: SCOPED_SCAN,
109
+ mode: "bounded-memory",
110
+ orderBy: ["createdAt", "desc"],
111
+ select: ["productTitle", "status", "productSlug"],
112
+ matches: fieldsContain(["productTitle", "status"]),
113
+ toHit: (id, d) => ({
114
+ id,
115
+ title: str(d.productTitle) ?? "Offer",
116
+ href: String(ROUTES.USER.OFFERS),
117
+ detail: str(d.status),
118
+ }),
119
+ moreHref: () => String(ROUTES.USER.OFFERS),
120
+ },
121
+ {
122
+ key: "my-catalogue",
123
+ label: "Your catalogue",
124
+ collection: "catalogueItems",
125
+ where: [["ownerId", viewer.uid]],
126
+ scanLimit: SCOPED_SCAN,
127
+ mode: "bounded-memory",
128
+ orderBy: ["createdAt", "desc"],
129
+ select: ["title", "listingStatus"],
130
+ matches: fieldsContain(["title"]),
131
+ toHit: (id, d) => ({
132
+ id,
133
+ title: str(d.title) ?? "Catalogue item",
134
+ href: String(ROUTES.USER.CATALOGUE),
135
+ detail: str(d.listingStatus),
136
+ }),
137
+ moreHref: () => String(ROUTES.USER.CATALOGUE),
138
+ },
139
+ {
140
+ /*
141
+ * ๐Ÿ›‘ `bounded-memory`, and this is the source the mode exists for.
142
+ *
143
+ * `addresses` is PII โ€” `fullName`, `phone`, `addressLine1` are all
144
+ * encrypted at rest and decrypted by the repository on read. It has
145
+ * no `searchTxt` and must never get one: a prefix-expanded plaintext
146
+ * token array over a person's name and street is precisely what
147
+ * ยง Searching PII refuses. The owner clause already bounds this to
148
+ * well under 25 rows, so an index would re-derive a bound we have
149
+ * while adding leak surface for nothing.
150
+ */
151
+ key: "my-addresses",
152
+ label: "Your addresses",
153
+ collection: "addresses",
154
+ where: [
155
+ ["ownerType", "user"],
156
+ ["ownerId", viewer.uid],
157
+ ],
158
+ scanLimit: SCOPED_SCAN,
159
+ mode: "bounded-memory",
160
+ select: ["label", "city", "state"],
161
+ // Only the non-sensitive half is matched AND projected. A label and a
162
+ // city are enough to pick an address out of a list of four; the name,
163
+ // phone and street line never leave the server.
164
+ matches: fieldsContain(["label", "city", "state"]),
165
+ toHit: (id, d) => ({
166
+ id,
167
+ title: str(d.label) ?? "Address",
168
+ href: String(ROUTES.USER.ADDRESSES),
169
+ detail: [str(d.city), str(d.state)].filter(Boolean).join(", ") || undefined,
170
+ }),
171
+ moreHref: () => String(ROUTES.USER.ADDRESSES),
172
+ },
173
+ ];
174
+ }
175
+ /** The viewer's store. Requires a resolved `storeId`, never a uid. */
176
+ function sellerSources(viewer) {
177
+ if (!viewer.storeId)
178
+ return [];
179
+ const storeId = viewer.storeId;
180
+ return [
181
+ {
182
+ /*
183
+ * The seller's own listings INCLUDING drafts โ€” which is the whole
184
+ * point, because a published one is already in the public artifact and
185
+ * the merge dedupes it away. What survives in this group is precisely
186
+ * "your drafts", and labelling it so is what makes the dedupe legible.
187
+ */
188
+ key: "my-listings",
189
+ label: "Your listings (incl. drafts)",
190
+ collection: "products",
191
+ where: [["storeId", storeId]],
192
+ scanLimit: SCOPED_SCAN,
193
+ mode: "search-txt",
194
+ select: ["title", "slug", "status"],
195
+ toHit: (id, d) => ({
196
+ id,
197
+ title: str(d.title) ?? "Listing",
198
+ href: String(ROUTES.STORE.PRODUCTS_EDIT(str(d.slug) ?? id)),
199
+ detail: str(d.status),
200
+ }),
201
+ moreHref: () => String(ROUTES.STORE.PRODUCTS),
202
+ },
203
+ {
204
+ key: "store-orders",
205
+ label: "Orders in your store",
206
+ collection: "orders",
207
+ where: [["storeId", storeId]],
208
+ scanLimit: SCOPED_SCAN,
209
+ mode: "search-txt",
210
+ select: ["productTitle", "status", "trackingNumber"],
211
+ toHit: (id, d) => ({
212
+ id,
213
+ title: str(d.productTitle) ?? `Order ${id}`,
214
+ href: String(ROUTES.STORE.ORDER_DETAIL(id)),
215
+ detail: str(d.status),
216
+ }),
217
+ moreHref: () => String(ROUTES.STORE.ORDERS),
218
+ },
219
+ ];
220
+ }
221
+ /**
222
+ * Build the viewer's sources.
223
+ *
224
+ * ๐Ÿ›‘ Returns `[]` for a guest, which is what makes "zero authenticated
225
+ * requests when signed out" structural rather than a caller's discipline.
226
+ */
227
+ export function scopedSourcesFor(viewer) {
228
+ if (!viewer)
229
+ return [];
230
+ const sources = [...selfSources(viewer)];
231
+ if (viewer.isSeller || viewer.isAdmin)
232
+ sources.push(...sellerSources(viewer));
233
+ return sources;
234
+ }
235
+ /**
236
+ * ฮฃ `scanLimit`, for the budget check.
237
+ *
238
+ * Exported so `audit-scoped-search-ownership` R4 can print the arithmetic
239
+ * rather than just a verdict: "over budget" without the per-source figures
240
+ * tells you nothing about which source to shrink.
241
+ */
242
+ export function scopedScanTotal(sources) {
243
+ return sources.reduce((n, s) => n + (s.scanLimit ?? 60), 0);
244
+ }
245
+ export function assertWithinScopedBudget(sources) {
246
+ const total = scopedScanTotal(sources);
247
+ if (total <= SCOPED_SCAN_BUDGET)
248
+ return;
249
+ const breakdown = sources.map((s) => `${s.key}=${s.scanLimit ?? 60}`).join(" + ");
250
+ throw new Error(`Scoped search would read up to ${total} documents, over SCOPED_SCAN_BUDGET ` +
251
+ `(${SCOPED_SCAN_BUDGET}). ${breakdown} = ${total}.`);
252
+ }
@@ -43,7 +43,56 @@ export interface SearchSource {
43
43
  moreHref: (q: string) => string;
44
44
  /** Extra equality narrowing, e.g. `status == published`. */
45
45
  where?: ReadonlyArray<readonly [string, unknown]>;
46
+ /**
47
+ * The bounded window this source reads before the in-memory refine.
48
+ *
49
+ * Defaults to `SCAN` (60). Scoped sources pass 20: an owner clause
50
+ * (`userId == uid`) has already collapsed the candidate set, so a 60-doc
51
+ * window is 40 reads spent proving there was nothing else.
52
+ */
53
+ scanLimit?: number;
54
+ /**
55
+ * How this source FINDS rows. Discriminated so TypeScript forces every
56
+ * source to answer, which is what makes `audit-scoped-search-ownership`
57
+ * R2 checkable rather than advisory.
58
+ *
59
+ * `search-txt` the `array-contains` push-down. Requires the
60
+ * collection to carry `searchTxt`.
61
+ * `bounded-memory` read the owner's bounded window and filter in
62
+ * memory, with no token push-down at all.
63
+ *
64
+ * ๐Ÿ›‘ A PII-bearing collection must be `bounded-memory`. `searchTxt` is
65
+ * plaintext, edge-n-gram-expanded and stored on the document โ€” giving
66
+ * `addresses` a token push-down would write a prefix-expanded index over
67
+ * a person's name and street, which is the precise thing ยง Searching PII
68
+ * refuses (a 1-character prefix has 36 possible values; the frequency
69
+ * distribution unpicks from there). Decrypting a bounded owner-scoped
70
+ * window in memory re-derives a bound ownership already gives, and adds
71
+ * no leak surface.
72
+ */
73
+ mode?: "search-txt" | "bounded-memory";
74
+ /**
75
+ * For `bounded-memory`: does this row match? Receives the DECRYPTED
76
+ * document, so a repository that decrypts on read is the intended caller.
77
+ * Every term must match (AND), consistent with the push-down path.
78
+ */
79
+ matches?: (data: SearchDocData, terms: readonly string[]) => boolean;
80
+ /** Ordering for `bounded-memory`, since there is no token clause to sort by. */
81
+ orderBy?: readonly [string, "asc" | "desc"];
46
82
  }
83
+ /**
84
+ * ๐Ÿ›‘ The ceiling on what ONE viewer's scoped search may read.
85
+ *
86
+ * ฮฃ `scanLimit` across the widest role, checked by
87
+ * `audit-scoped-search-ownership` R4 โ€” which PRINTS THE ARITHMETIC on
88
+ * failure, because "the budget is exceeded" is useless without knowing by
89
+ * which source and how much.
90
+ *
91
+ * 400 against Rule #6's ~3-round-trip guidance is defensible because the
92
+ * sources run in ONE `Promise.all`: it is one round trip of fan-out, not
93
+ * twelve sequential ones. The number that matters is documents read.
94
+ */
95
+ export declare const SCOPED_SCAN_BUDGET = 400;
47
96
  /** A projected Firestore document's fields. */
48
97
  export type SearchDocData = Record<string, FirestoreValue>;
49
98
  /** A projected document โ€” the only shape this module reads. */
@@ -55,6 +104,14 @@ interface SearchQueryRef {
55
104
  where(field: string, op: string, value: unknown): SearchQueryRef;
56
105
  select(...fields: string[]): SearchQueryRef;
57
106
  limit(n: number): SearchQueryRef;
107
+ /**
108
+ * Only `bounded-memory` sources use this. The push-down path deliberately
109
+ * does not order: an `orderBy` beside an `array-contains` demands a
110
+ * composite index per source, and a missing one throws
111
+ * `FAILED_PRECONDITION` into the per-source catch, where it renders as an
112
+ * empty group โ€” i.e. silently (Root Cause #59).
113
+ */
114
+ orderBy(field: string, direction: "asc" | "desc"): SearchQueryRef;
58
115
  get(): Promise<{
59
116
  docs: SearchDoc[];
60
117
  }>;