@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
@@ -10,6 +10,16 @@ const CLS_ICON_ROW = "w-4 h-4 flex-shrink-0 text-[var(--appkit-color-text-muted)
10
10
  const CLS_TRUNCATE = "font-medium truncate";
11
11
  import { Button, Div, Input, Li, Row, Select, Span, Stack, Text, Ul } from "../../../ui/index.js";
12
12
  import { useNavSuggestions } from "../hooks/useNavSuggestions.js";
13
+ import { useSiteSearch } from "../hooks/useSiteSearch.js";
14
+ /**
15
+ * The dropdown renders no group headings, so it needs no labels — but
16
+ * `useSiteSearch` requires one because `/search` does.
17
+ *
18
+ * 🛑 Declared at MODULE scope, not inline. It is a `useMemo` dependency
19
+ * inside the hook, and an inline arrow is a new identity on every render,
20
+ * which would re-run the scan on every keystroke of an unchanged query.
21
+ */
22
+ const identityLabel = (typeKey) => typeKey;
13
23
  const DEFAULT_TYPE_ICONS = {
14
24
  page: "📄",
15
25
  category: "🗂️",
@@ -28,7 +38,7 @@ function SuggestionItem({ suggestion, itemIndex, activeIndex, onSelect, onHover,
28
38
  const isActive = activeIndex === itemIndex;
29
39
  return (_jsx(Li, { children: _jsxs(Button, { type: "button", variant: "ghost", onClick: () => onSelect(suggestion), onMouseEnter: () => onHover(itemIndex), className: `w-full flex items-center gap-[var(--appkit-space-3)] px-[var(--appkit-space-4)] py-[var(--appkit-space-3)] text-left transition-colors border-b border-[var(--appkit-color-border)] last:border-b-0 ${isActive ? "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)]" : CLS_HOVER_ROW}`, children: [_jsx(Span, { size: "sm", children: typeIcons[suggestion.type] }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { size: "sm", className: CLS_TRUNCATE, children: suggestion.title }), suggestion.subtitle && (_jsx(Text, { variant: "secondary", size: "xs", className: "truncate", children: suggestion.subtitle }))] }), _jsx(Span, { size: "xs", className: `${typeBadges[suggestion.type]}`, rounded: "full", padding: "pill-xs", children: suggestion.type })] }) }));
30
40
  }
31
- export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, placeholder, deferred = true, debounceMs = 300, onClear, className, router, quickLinks = [], labels, suggestionTypeIcons, suggestionTypeBadges, resourceTypes, defaultResourceType = "products", storageKey = "letitrip_search_type", }) {
41
+ export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, placeholder, deferred = true, debounceMs = 300, onClear, className, router, quickLinks = [], labels, suggestionTypeIcons, suggestionTypeBadges, resourceTypes, defaultResourceType = "products", storageKey = "letitrip_search_type", searchIndexEnabled, }) {
32
42
  const isInlineMode = value !== undefined;
33
43
  const [query, setQuery] = useState(isInlineMode ? value : "");
34
44
  const [activeIndex, setActiveIndex] = useState(-1);
@@ -37,7 +47,33 @@ export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, pla
37
47
  const inputRef = useRef(null);
38
48
  const debounceRef = useRef(null);
39
49
  const inlineBlurRef = useRef(null);
40
- const { suggestions, isLoading: suggestionsLoading } = useNavSuggestions(query, selectedType);
50
+ /*
51
+ * 🛑 BOTH hooks are called unconditionally — React's rules leave no choice,
52
+ * and it costs nothing here because each one is self-gating.
53
+ *
54
+ * `useSiteSearch` is gated on `active`: it issues no request until the user
55
+ * focuses or types, and then exactly one, for the whole page session.
56
+ * `useNavSuggestions` is gated on its own 3-character minimum AND on
57
+ * `remoteQuery` below, which is blank whenever the index is answering — so
58
+ * the fallback path makes no network request at all in the normal case.
59
+ */
60
+ const [searchIntent, setSearchIntent] = useState(false);
61
+ const local = useSiteSearch(query, {
62
+ active: searchIntent,
63
+ indexEnabled: searchIndexEnabled,
64
+ label: identityLabel,
65
+ });
66
+ /*
67
+ * The index is authoritative the moment it is `ready`. `loading` must NOT
68
+ * fall through to the remote path: that would fire the very round trips
69
+ * this replaces, for the one second before the local answer arrives, on
70
+ * every first keystroke.
71
+ */
72
+ const useLocal = local.status === "ready";
73
+ const remoteQuery = useLocal || local.status === "loading" ? "" : query;
74
+ const { suggestions: remoteSuggestions, isLoading: remoteLoading } = useNavSuggestions(remoteQuery, selectedType);
75
+ const suggestions = useLocal ? local.suggestions : remoteSuggestions;
76
+ const suggestionsLoading = useLocal ? false : local.status === "loading" || remoteLoading;
41
77
  const typeIcons = { ...DEFAULT_TYPE_ICONS, ...suggestionTypeIcons };
42
78
  const typeBadges = { ...DEFAULT_TYPE_BADGES, ...suggestionTypeBadges };
43
79
  // Restore persisted type from localStorage after mount
@@ -90,6 +126,7 @@ export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, pla
90
126
  setActiveIndex(-1);
91
127
  }, [isOpen, isInlineMode, query, suggestions.length]);
92
128
  const handleInlineChange = (nextValue) => {
129
+ setSearchIntent(true);
93
130
  setQuery(nextValue);
94
131
  if (!deferred) {
95
132
  if (debounceRef.current)
@@ -209,6 +246,10 @@ export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, pla
209
246
  }
210
247
  };
211
248
  return (_jsxs(Row, { className: `relative ${className ?? ""}`, gap: "sm", children: [_jsxs(Row, { className: "relative flex-1 min-w-0", children: [_jsx("svg", { className: "absolute left-3 w-4 h-4 text-zinc-400 pointer-events-none", "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }), _jsx(Input, { ref: inputRef, bare: true, type: "search", value: query, onChange: (event) => handleInlineChange(event.target.value), onKeyDown: handleInlineKeyDown, onFocus: () => {
249
+ // Focus is the earliest honest signal of intent, and it buys
250
+ // the ~900 ms the artifact takes to land before the first
251
+ // keystroke needs an answer.
252
+ setSearchIntent(true);
212
253
  if (inlineBlurRef.current)
213
254
  clearTimeout(inlineBlurRef.current);
214
255
  setIsInlineOpen(true);
@@ -292,7 +333,10 @@ export function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, pla
292
333
  };
293
334
  if (!isOpen)
294
335
  return null;
295
- return (_jsx(Div, { border: "bottom-subtle", className: "animate-[fadeIn_150ms_ease-out]", surface: "default", shadow: "md", children: _jsxs(Div, { className: "container mx-auto md:py-[1rem] relative", padding: "inline", children: [_jsxs(Row, { gap: "sm", className: "md:gap-[0.75rem]", children: [_jsx(Div, { className: "flex-1 relative min-w-0", children: _jsx(Input, { ref: inputRef, bare: true, type: "search", placeholder: placeholder ?? labels.placeholder, value: query, onChange: (event) => setQuery(event.target.value), onKeyDown: handleOverlayKeyDown, className: "w-full rounded-lg border bg-[var(--appkit-color-surface)] text-zinc-900 placeholder:text-zinc-400 focus:border-primary focus:ring-2 focus:ring-primary/20 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] dark:text-white dark:placeholder:text-slate-500" }) }), resourceTypes && resourceTypes.length > 0 && (_jsx(Select, { options: resourceTypes, value: selectedType, onValueChange: handleTypeChange, "aria-label": labels.resourceTypeLabel ?? "Search in", wrapperClassName: "flex-shrink-0 max-w-[92px] sm:max-w-[150px]" })), _jsxs(Button, { gap: "md", onClick: handleOverlaySearch, variant: "primary", size: "md", className: "hidden sm:", children: [_jsx("svg", { className: "w-5 h-5 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }), labels.title] }), _jsx(Button, { rounded: "xl", variant: "ghost", onClick: () => onClose?.(), className: "p-[var(--appkit-space-2-5)] md:p-[var(--appkit-space-3)] transition-colors text-[var(--appkit-color-text-muted)] hover:text-zinc-700 text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-[var(--appkit-color-text-muted)] flex-shrink-0", "aria-label": labels.closeAriaLabel, children: _jsx("svg", { className: "w-6 h-6 md:w-7 md:h-7 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), (filteredSiteLinks.length > 0 || query) && (_jsxs(Stack, { className: "absolute top-full left-0 right-0 z-50 pt-[0.5rem] pb-[1rem]", padding: "x-md", gap: "sm", children: [filteredSiteLinks.length > 0 && !suggestionsLoading && (_jsxs(Div, { className: `${__O.hidden}`, rounded: "xl", shadow: "lg", surface: "default", border: "default", children: [_jsx(Div, { border: "default", className: "border-b", paddingY: "y-xs", paddingX: "x-md", children: _jsx(Text, { variant: "secondary", size: "xs", className: "uppercase tracking-wider", weight: "semibold", children: labels.quickLinks }) }), _jsx(Ul, { children: filteredSiteLinks.map((link) => {
336
+ return (_jsx(Div, { border: "bottom-subtle", className: "animate-[fadeIn_150ms_ease-out]", surface: "default", shadow: "md", children: _jsxs(Div, { className: "container mx-auto md:py-[1rem] relative", padding: "inline", children: [_jsxs(Row, { gap: "sm", className: "md:gap-[0.75rem]", children: [_jsx(Div, { className: "flex-1 relative min-w-0", children: _jsx(Input, { ref: inputRef, bare: true, type: "search", placeholder: placeholder ?? labels.placeholder, value: query, onFocus: () => setSearchIntent(true), onChange: (event) => {
337
+ setSearchIntent(true);
338
+ setQuery(event.target.value);
339
+ }, onKeyDown: handleOverlayKeyDown, className: "w-full rounded-lg border bg-[var(--appkit-color-surface)] text-zinc-900 placeholder:text-zinc-400 focus:border-primary focus:ring-2 focus:ring-primary/20 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] dark:text-white dark:placeholder:text-slate-500" }) }), resourceTypes && resourceTypes.length > 0 && (_jsx(Select, { options: resourceTypes, value: selectedType, onValueChange: handleTypeChange, "aria-label": labels.resourceTypeLabel ?? "Search in", wrapperClassName: "flex-shrink-0 max-w-[92px] sm:max-w-[150px]" })), _jsxs(Button, { gap: "md", onClick: handleOverlaySearch, variant: "primary", size: "md", className: "hidden sm:", children: [_jsx("svg", { className: "w-5 h-5 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: SVG_PATH_SEARCH }) }), labels.title] }), _jsx(Button, { rounded: "xl", variant: "ghost", onClick: () => onClose?.(), className: "p-[var(--appkit-space-2-5)] md:p-[var(--appkit-space-3)] transition-colors text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-[var(--appkit-color-text)] flex-shrink-0", "aria-label": labels.closeAriaLabel, children: _jsx("svg", { className: "w-6 h-6 md:w-7 md:h-7 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), (filteredSiteLinks.length > 0 || query) && (_jsxs(Stack, { className: "absolute top-full left-0 right-0 z-50 pt-[0.5rem] pb-[1rem]", padding: "x-md", gap: "sm", children: [filteredSiteLinks.length > 0 && !suggestionsLoading && (_jsxs(Div, { className: `${__O.hidden}`, rounded: "xl", shadow: "lg", surface: "default", border: "default", children: [_jsx(Div, { border: "default", className: "border-b", paddingY: "y-xs", paddingX: "x-md", children: _jsx(Text, { variant: "secondary", size: "xs", className: "uppercase tracking-wider", weight: "semibold", children: labels.quickLinks }) }), _jsx(Ul, { children: filteredSiteLinks.map((link) => {
296
340
  const Icon = link.icon;
297
341
  const itemIndex = quickLinkItems.findIndex((item) => item.link.href === link.href);
298
342
  const isActive = activeIndex === itemIndex;
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import type { FacetCount } from "../../../_internal/shared/search/to-groups";
3
+ export interface SearchFacetRailProps {
4
+ facets: readonly FacetCount[];
5
+ /** Currently applied `k:v` pairs. */
6
+ selected: readonly string[];
7
+ onToggle: (pair: string) => void;
8
+ /** Values shown per key before the rest are hidden. */
9
+ maxPerKey?: number;
10
+ }
11
+ export declare function SearchFacetRail({ facets, selected, onToggle, maxPerKey, }: SearchFacetRailProps): React.JSX.Element | null;
@@ -0,0 +1,92 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /*
4
+ * WHY: Refining a 3,700-record local result set is free; refining a Firestore
5
+ * one is an aggregation query per facet value plus a composite index per
6
+ * shape. This rail only exists because Tier 1 made it cost nothing.
7
+ * WHAT: Toggleable `k:v` facet chips with live counts over the CURRENT hits.
8
+ *
9
+ * ## 🛑 Counts are over the current result set, not the catalogue
10
+ *
11
+ * Tick one chip and the others shrink, truthfully, because every number is a
12
+ * reduce over the hits the query just produced. `BaseRepository.facetCounts`
13
+ * cannot do this — it counts the collection, one aggregation query per value,
14
+ * and returns `undefined` on a missing index so the chip renders with no
15
+ * number at all. A count that can silently vanish is worse than no rail.
16
+ *
17
+ * ## 🛑 A chip whose count would be zero is not rendered
18
+ *
19
+ * It cannot be: the counts come from the hits, so a value with no hits has no
20
+ * entry. That is the structural version of Root Cause #72's rule — there, a
21
+ * hide-when-empty tab survived because its count map returned `undefined`
22
+ * ("unknown") rather than `0`, so four tabs were unhideable. Here there is no
23
+ * count map to be incomplete.
24
+ *
25
+ * @tag domain:search
26
+ * @tag layer:feature
27
+ * @tag pattern:none
28
+ * @tag access:client
29
+ * @tag consumers:SearchResultsClient
30
+ * @tag sideEffects:none
31
+ */
32
+ import React from "react";
33
+ import { Button, Div, Row, Span, Stack, Text } from "../../../ui/index.js";
34
+ /**
35
+ * Short code -> heading.
36
+ *
37
+ * 🛑 `Partial`, and an unknown code is SKIPPED rather than shown raw. The
38
+ * codes are two letters to keep the artifact small (`lt:auction` over
39
+ * `listingType:auction`, ×3,700), which means a new one added on the builder
40
+ * side would otherwise surface to a visitor as a heading reading "sz". A
41
+ * facet nobody can name is not a refinement, and dropping it loses nothing
42
+ * the groups above do not already show.
43
+ */
44
+ const FACET_KEY_LABEL = {
45
+ lt: "Type",
46
+ br: "Brand",
47
+ cn: "Condition",
48
+ fr: "Franchise",
49
+ ct: "Category",
50
+ };
51
+ /** Slug -> words. The artifact stores slugs; nobody wants to read one. */
52
+ function humanise(value) {
53
+ const spaced = value.replace(/[-_]/g, " ");
54
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
55
+ }
56
+ export function SearchFacetRail({ facets, selected, onToggle, maxPerKey = 6, }) {
57
+ const selectedSet = React.useMemo(() => new Set(selected), [selected]);
58
+ const byKey = React.useMemo(() => {
59
+ const map = new Map();
60
+ for (const f of facets) {
61
+ if (!FACET_KEY_LABEL[f.key])
62
+ continue; // see FACET_KEY_LABEL's 🛑
63
+ const bucket = map.get(f.key);
64
+ if (bucket)
65
+ bucket.push(f);
66
+ else
67
+ map.set(f.key, [f]);
68
+ }
69
+ return map;
70
+ }, [facets]);
71
+ /*
72
+ * A key offering ONE value is not a refinement — every result already has
73
+ * it, so ticking it changes nothing and the chip is pure noise. The
74
+ * exception is a value already selected: hiding the chip you used to narrow
75
+ * the page would leave no way to un-narrow it.
76
+ */
77
+ const keys = [...byKey.keys()].filter((k) => {
78
+ const values = byKey.get(k) ?? [];
79
+ return values.length > 1 || values.some((v) => selectedSet.has(`${v.key}:${v.value}`));
80
+ });
81
+ if (keys.length === 0)
82
+ return null;
83
+ return (_jsx(Stack, { gap: "sm", children: keys.map((key) => {
84
+ const values = byKey.get(key) ?? [];
85
+ const shown = values.slice(0, maxPerKey);
86
+ return (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", weight: "semibold", transform: "uppercase", children: FACET_KEY_LABEL[key] }), _jsxs(Row, { wrap: true, gap: "xs", children: [shown.map((f) => {
87
+ const pair = `${f.key}:${f.value}`;
88
+ const isOn = selectedSet.has(pair);
89
+ return (_jsxs(Button, { size: "sm", variant: isOn ? "primary" : "outline", "aria-pressed": isOn, onClick: () => onToggle(pair), children: [humanise(f.value), _jsxs(Span, { size: "xs", color: isOn ? "inherit" : "muted", numeric: true, children: [" ", f.count] })] }, pair));
90
+ }), values.length > shown.length && (_jsx(Div, { padding: "xs", children: _jsxs(Text, { size: "xs", color: "muted", children: ["+", values.length - shown.length, " more"] }) }))] })] }, key));
91
+ }) }));
92
+ }
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import type { SearchEverythingResult } from "../../../_internal/server/features/search/search-everything";
3
+ export interface SearchResultsClientProps {
4
+ /** The query, resolved server-side from `?q=`. */
5
+ q: string;
6
+ /**
7
+ * The Tier 2 result. Empty (and unread) whenever `indexEnabled` is true —
8
+ * see the header.
9
+ */
10
+ initialResult: SearchEverythingResult;
11
+ indexEnabled: boolean;
12
+ /**
13
+ * Whether a session exists. FALSE for a guest, and that is what makes
14
+ * "signed out issues zero authenticated requests" verifiable in the
15
+ * Network panel rather than merely true on the server.
16
+ */
17
+ isSignedIn?: boolean;
18
+ /**
19
+ * `typeKey` -> heading.
20
+ *
21
+ * 🛑 A plain OBJECT, not a `(key: string) => string`. This component is
22
+ * rendered from a Server Component, and a function prop is not
23
+ * serialisable across the RSC boundary — it throws at render, not at
24
+ * compile. Same constraint `HomepageSectionBoundary` documents for its
25
+ * `sectionId`/`sectionType` strings.
26
+ *
27
+ * A key with no entry falls back to the key itself rather than being
28
+ * dropped: a group headed `scammer` is ugly, a group that silently
29
+ * disappears is a bug.
30
+ */
31
+ labels: Readonly<Record<string, string>>;
32
+ }
33
+ export declare function SearchResultsClient({ q, initialResult, indexEnabled, isSignedIn, labels, }: SearchResultsClientProps): React.JSX.Element;
@@ -0,0 +1,103 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /*
4
+ * WHY: `/search` should answer from the local artifact — every type, mid-word
5
+ * matching, exact counts, zero Firestore reads — while still rendering
6
+ * something when the artifact is off or unreachable.
7
+ * WHAT: The client wrapper around `SearchResultsPageView`. Picks the tier,
8
+ * owns the facet selection, and renders the rail.
9
+ *
10
+ * ## 🛑 The server result is a FALLBACK, not a first paint to be replaced
11
+ *
12
+ * `/search/page.tsx` does NOT run the Firestore fan-out when the index is
13
+ * enabled — it passes an empty envelope and 0 reads. So `initialResult` is
14
+ * meaningful only in the kill-switch case, and swapping a populated server
15
+ * result out from under the user is a flicker that cannot happen here.
16
+ *
17
+ * That is also why `loading` renders a waiting line rather than the empty
18
+ * state: with no server result behind it, an empty state would say "nothing
19
+ * matched" about a query nobody has finished asking (the `GatedPrice`
20
+ * three-state discipline, one surface over).
21
+ *
22
+ * ## 🛑 Facet selection lives in component state, NOT the URL
23
+ *
24
+ * `/search` is `noindex` and every result links out to a real indexable
25
+ * listing page, so a shareable faceted search URL has no SEO value and would
26
+ * multiply the crawl trap the page is already careful about. It also keeps
27
+ * `useUrlTable` out of a page that has no pagination — the one thing the
28
+ * listing views need it for.
29
+ *
30
+ * @tag domain:search
31
+ * @tag layer:feature
32
+ * @tag pattern:none
33
+ * @tag access:client
34
+ * @tag consumers:app/[locale]/search/page.tsx
35
+ * @tag sideEffects:network
36
+ */
37
+ import React from "react";
38
+ import { Text } from "../../../ui/index.js";
39
+ import { useSiteSearch } from "../hooks/useSiteSearch.js";
40
+ import { mergeSearchTiers, useScopedSearch } from "../hooks/useScopedSearch.js";
41
+ import { SearchFacetRail } from "./SearchFacetRail.js";
42
+ import { SearchResultsPageView } from "./SearchResultsPageView.js";
43
+ export function SearchResultsClient({ q, initialResult, indexEnabled, isSignedIn = false, labels, }) {
44
+ const [selectedFacets, setSelectedFacets] = React.useState([]);
45
+ // Stable across renders — it is a `useMemo` dependency inside the hook.
46
+ const label = React.useCallback((typeKey) => labels[typeKey] ?? typeKey, [labels]);
47
+ /*
48
+ * `active` is unconditionally true here, unlike the header. Arriving on
49
+ * `/search?q=…` IS the intent — there is no cheaper signal to wait for,
50
+ * and gating on focus would mean a page whose entire purpose is results
51
+ * sits idle until somebody clicks the box.
52
+ */
53
+ const local = useSiteSearch(q, {
54
+ active: true,
55
+ indexEnabled,
56
+ facets: selectedFacets,
57
+ label,
58
+ });
59
+ const toggleFacet = React.useCallback((pair) => {
60
+ setSelectedFacets((prev) => prev.includes(pair) ? prev.filter((p) => p !== pair) : [...prev, pair]);
61
+ }, []);
62
+ /*
63
+ * Tier 2. `submittedQuery` is `q` — the URL parameter — so it changes when
64
+ * the user SUBMITS, not as they type. That is the whole gating story: the
65
+ * local tier runs per keystroke because it is free, this one runs per
66
+ * navigation because it is a bounded Firestore fan-out.
67
+ */
68
+ const scoped = useScopedSearch({ enabled: isSignedIn, submittedQuery: q });
69
+ const usingLocal = local.status === "ready" && local.result !== null;
70
+ const baseResult = usingLocal && local.result ? local.result : initialResult;
71
+ const result = React.useMemo(() => scoped.groups.length > 0
72
+ ? { ...baseResult, groups: mergeSearchTiers(baseResult.groups, scoped.groups) }
73
+ : baseResult, [baseResult, scoped.groups]);
74
+ /*
75
+ * 🛑 A dropped facet selection must not silently keep filtering.
76
+ *
77
+ * If the index fails after the user has ticked chips, the fallback result
78
+ * is unfiltered — so the chips would be lit while doing nothing. Clearing
79
+ * them is the honest state, and it is cheap because the rail is derived
80
+ * rather than stored.
81
+ */
82
+ React.useEffect(() => {
83
+ if (!usingLocal && selectedFacets.length > 0)
84
+ setSelectedFacets([]);
85
+ }, [usingLocal, selectedFacets.length]);
86
+ const waiting = local.status === "loading" && q.trim().length > 0;
87
+ return (_jsx(SearchResultsPageView, { result: waiting ? { ...result, groups: [], totalHits: 0 } : result, footnote: usingLocal && local.builtAt
88
+ ? `Catalogue index updated ${new Date(local.builtAt).toLocaleString()}.`
89
+ : undefined, renderAboveGroups: () => (_jsxs(_Fragment, { children: [waiting && _jsx(Text, { color: "muted", children: "Searching\u2026" }), usingLocal && (_jsx(SearchFacetRail, { facets: local.facets, selected: selectedFacets, onToggle: toggleFacet })), scoped.status === "failed" && (
90
+ /*
91
+ * Said out loud, for the same reason as the line below it. A
92
+ * seller whose draft is missing because the scoped fetch failed
93
+ * will read the silence as "that draft does not exist".
94
+ */
95
+ _jsx(Text, { size: "sm", color: "muted", children: "Couldn't search your account \u2014 results below are public listings only." })), local.status === "failed" && (
96
+ /*
97
+ * Said out loud rather than degraded silently. `searchEverything`'s
98
+ * own header records that a degraded source "is indistinguishable
99
+ * from 'nothing matched'" — and on a page that otherwise rendered
100
+ * fine, silence tells someone we have none of what they asked for.
101
+ */
102
+ _jsx(Text, { size: "sm", color: "muted", children: "Showing a reduced set \u2014 the full catalogue index could not be loaded." }))] })) }));
103
+ }
@@ -29,5 +29,19 @@ export interface SearchResultsPageViewProps {
29
29
  result: SearchEverythingResult;
30
30
  /** Rendered above the groups — the consumer owns the input control. */
31
31
  renderSearchInput?: () => React.ReactNode;
32
+ /**
33
+ * Rendered between the input and the groups. The facet rail lives here.
34
+ *
35
+ * A SLOT rather than a `facets` prop, because this component is also
36
+ * rendered from a Server Component with no facets at all (the kill-switch
37
+ * fallback), and a required-looking prop that is absent half the time
38
+ * invites a `?? []` that silently renders an empty rail.
39
+ */
40
+ renderAboveGroups?: () => React.ReactNode;
41
+ /**
42
+ * A short line under the heading — "results as of 10:51". Optional because
43
+ * the server tier has no build time to report.
44
+ */
45
+ footnote?: string;
32
46
  }
33
- export declare function SearchResultsPageView({ result, renderSearchInput, }: SearchResultsPageViewProps): React.JSX.Element;
47
+ export declare function SearchResultsPageView({ result, renderSearchInput, renderAboveGroups, footnote, }: SearchResultsPageViewProps): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ROUTES } from "../../../next/routing/route-map.js";
3
3
  import { Section, Container, Stack, Row, Div, Heading, Text, Span, TextLink } from "../../../ui/index.js";
4
- export function SearchResultsPageView({ result, renderSearchInput, }) {
4
+ export function SearchResultsPageView({ result, renderSearchInput, renderAboveGroups, footnote, }) {
5
5
  const { q, terms, groups, totalHits } = result;
6
6
  const hasQuery = q.trim().length > 0;
7
7
  /*
@@ -18,5 +18,5 @@ export function SearchResultsPageView({ result, renderSearchInput, }) {
18
18
  const normalisedAway = hasQuery && terms.length === 0;
19
19
  return (_jsx(Section, { padding: "page", children: _jsx(Container, { children: _jsxs(Stack, { gap: "loose", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 1, children: hasQuery ? `Search: ${q}` : "Search" }), hasQuery && !normalisedAway && (_jsx(Text, { color: "muted", size: "sm", children: totalHits === 0
20
20
  ? "Nothing matched."
21
- : `${totalHits}${groups.some((g) => g.truncated) ? "+" : ""} result${totalHits === 1 ? "" : "s"} across ${groups.length} section${groups.length === 1 ? "" : "s"}.` }))] }), renderSearchInput?.(), !hasQuery && (_jsx(Text, { color: "muted", children: "Search listings, categories, brands, stores, articles and help pages at once." })), normalisedAway && (_jsx(Text, { color: "muted", children: "That search had no searchable words in it. Try a product, part or category name." })), groups.map((group) => (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", wrap: true, gap: "sm", children: [_jsxs(Heading, { level: 2, size: "lg", children: [group.label, group.truncated && (_jsxs(Span, { size: "sm", color: "muted", children: [" ", "(", group.hits.length, "+)"] }))] }), _jsxs(TextLink, { href: group.moreHref, children: ["See all ", group.label.toLowerCase(), " \u2192"] })] }), _jsx(Stack, { gap: "xs", children: group.hits.map((hit) => (_jsxs(Div, { surface: "card", padding: "sm", rounded: "md", children: [_jsx(TextLink, { href: hit.href, children: hit.title }), hit.detail && (_jsx(Text, { size: "xs", color: "muted", children: hit.detail }))] }, `${group.key}-${hit.id}`))) })] }, group.key))), hasQuery && !normalisedAway && totalHits === 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { color: "muted", children: ["No listings, categories, brands, stores, articles or help pages matched \u201C", q, "\u201D."] }), _jsx(TextLink, { href: ROUTES.PUBLIC.PRODUCTS, children: "Browse all listings \u2192" })] }))] }) }) }));
21
+ : `${totalHits}${groups.some((g) => g.truncated) ? "+" : ""} result${totalHits === 1 ? "" : "s"} across ${groups.length} section${groups.length === 1 ? "" : "s"}.` }))] }), footnote && (_jsx(Text, { size: "xs", color: "muted", children: footnote })), renderSearchInput?.(), renderAboveGroups?.(), !hasQuery && (_jsx(Text, { color: "muted", children: "Search listings, categories, brands, stores, articles and help pages at once." })), normalisedAway && (_jsx(Text, { color: "muted", children: "That search had no searchable words in it. Try a product, part or category name." })), groups.map((group) => (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", wrap: true, gap: "sm", children: [_jsxs(Heading, { level: 2, size: "lg", children: [group.label, group.truncated && (_jsxs(Span, { size: "sm", color: "muted", children: [" ", "(", group.hits.length, "+)"] }))] }), _jsxs(TextLink, { href: group.moreHref, children: ["See all ", group.label.toLowerCase(), " \u2192"] })] }), _jsx(Stack, { gap: "xs", children: group.hits.map((hit) => (_jsxs(Div, { surface: "card", padding: "sm", rounded: "md", children: [_jsx(TextLink, { href: hit.href, children: hit.title }), hit.detail && (_jsx(Text, { size: "xs", color: "muted", children: hit.detail }))] }, `${group.key}-${hit.id}`))) })] }, group.key))), hasQuery && !normalisedAway && totalHits === 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { color: "muted", children: ["No listings, categories, brands, stores, articles or help pages matched \u201C", q, "\u201D."] }), _jsx(TextLink, { href: ROUTES.PUBLIC.PRODUCTS, children: "Browse all listings \u2192" })] }))] }) }) }));
22
22
  }
@@ -1,8 +1,2 @@
1
1
  export { Search } from "./Search";
2
2
  export type { SearchLabels, SearchProps, SearchQuickLink, SearchRouterAdapter, SearchResourceType, SearchResourceTypeOption, } from "./Search";
3
- export { SearchFiltersRow } from "./SearchFiltersRow";
4
- export type {} from "./SearchFiltersRow";
5
- export { SearchResultsSection } from "./SearchResultsSection";
6
- export type { SearchResultsSectionProps } from "./SearchResultsSection";
7
- export { SearchView } from "./SearchView";
8
- export type { SearchViewProps } from "./SearchView";
@@ -1,4 +1 @@
1
1
  export { Search } from "./Search.js";
2
- export { SearchFiltersRow } from "./SearchFiltersRow.js";
3
- export { SearchResultsSection } from "./SearchResultsSection.js";
4
- export { SearchView } from "./SearchView.js";
@@ -1,6 +1,19 @@
1
1
  export interface NavSuggestionRecord {
2
2
  objectID: string;
3
3
  type: "page" | "category" | "blog" | "event" | "product";
4
+ /**
5
+ * The real index type key (`auction`, `part`, `faq`, `scammer`, …) when the
6
+ * row came from the local search artifact, which spans 21 types.
7
+ *
8
+ * 🛑 `type` above stays a 5-value union on purpose. It is what the
9
+ * dropdown's `DEFAULT_TYPE_ICONS` / `DEFAULT_TYPE_BADGES` maps switch on,
10
+ * so widening it would compile everywhere and render 16 types unstyled
11
+ * (Root Cause #20). `useSiteSearch.suggestionTypeFor` collapses the key
12
+ * into that union for presentation and puts the precise one here, so a
13
+ * badge can be specific where a map entry exists without any caller
14
+ * breaking where one does not.
15
+ */
16
+ typeKey?: string;
4
17
  title: string;
5
18
  subtitle?: string;
6
19
  url: string;
@@ -0,0 +1,34 @@
1
+ import type { SearchGroup } from "../../../_internal/server/features/search/search-everything";
2
+ export type ScopedSearchStatus = "idle" | "loading" | "ready" | "failed";
3
+ export interface UseScopedSearchResult {
4
+ readonly status: ScopedSearchStatus;
5
+ readonly groups: readonly SearchGroup[];
6
+ }
7
+ export interface UseScopedSearchOptions {
8
+ /** False for a guest. No request is issued at all. */
9
+ enabled: boolean;
10
+ /** The committed query. Changing it re-fetches; typing does not. */
11
+ submittedQuery: string;
12
+ }
13
+ export declare function useScopedSearch({ enabled, submittedQuery, }: UseScopedSearchOptions): UseScopedSearchResult;
14
+ /**
15
+ * Merge the two tiers.
16
+ *
17
+ * ## 🛑 Dedupe by DESTINATION, and local wins
18
+ *
19
+ * The tiers are NOT disjoint: a seller's published listing is in the public
20
+ * artifact AND in `my-listings`. Showing both is a duplicate-results bug;
21
+ * dropping the public one would lose the row a buyer would see.
22
+ *
23
+ * Local wins, so what survives in the scoped group is precisely the rows the
24
+ * artifact does not have — a seller's DRAFTS. Labelling that group "Your
25
+ * listings (incl. drafts)" is what makes the dedupe legible rather than
26
+ * mysterious.
27
+ *
28
+ * 🛑 The key is the HREF, not `${typeKey}:${id}`. The artifact keys products
29
+ * by SLUG and Firestore by document id, so a composite key would never
30
+ * collide and the dedupe would silently do nothing — the failure mode being
31
+ * a page that looks fine and shows everything twice. Both tiers already
32
+ * agree on where a row goes, which is the only identity they share.
33
+ */
34
+ export declare function mergeSearchTiers(local: readonly SearchGroup[], scoped: readonly SearchGroup[]): SearchGroup[];
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ /*
3
+ * WHY: A signed-in viewer's own records belong in the same box as the public
4
+ * catalogue — but they cost real reads and must not be fetched the way
5
+ * the free tier is.
6
+ * WHAT: Tier 2's client half, plus the merge with Tier 1.
7
+ *
8
+ * ## 🛑 ON SUBMIT, never per keystroke
9
+ *
10
+ * Tier 1 is a local scan and costs nothing, so it runs on every character.
11
+ * This one is a bounded Firestore fan-out — up to ~320 documents for an
12
+ * admin — so it runs when the user commits to a query. A debounced
13
+ * per-keystroke version of this endpoint is how a 50k/day read tier
14
+ * disappears in an afternoon.
15
+ *
16
+ * ## 🛑 A guest issues ZERO requests
17
+ *
18
+ * Gated on `enabled`, which the caller sets from the session. The server
19
+ * refuses too (`auth: true`, and `scopedSourcesFor(null)` is `[]`), but the
20
+ * client gate is what makes "signed out = no authenticated request in the
21
+ * Network panel" true, which is the only form of that claim anyone can
22
+ * verify.
23
+ *
24
+ * @tag domain:search
25
+ * @tag layer:feature
26
+ * @tag pattern:hook
27
+ * @tag access:client
28
+ * @tag consumers:SearchResultsClient
29
+ * @tag sideEffects:network
30
+ */
31
+ import * as React from "react";
32
+ import { apiClient } from "../../../http/index.js";
33
+ import { normalizeError } from "../../../errors/normalize.js";
34
+ import { SEARCH_ENDPOINTS } from "../../../constants/api-endpoints.js";
35
+ /** Matches the server-side gate, so the two cannot disagree about what is worth asking. */
36
+ const MIN_CHARS = 3;
37
+ export function useScopedSearch({ enabled, submittedQuery, }) {
38
+ const [status, setStatus] = React.useState("idle");
39
+ const [groups, setGroups] = React.useState([]);
40
+ React.useEffect(() => {
41
+ const q = submittedQuery.trim();
42
+ if (!enabled || q.length < MIN_CHARS) {
43
+ setStatus("idle");
44
+ setGroups([]);
45
+ return;
46
+ }
47
+ /*
48
+ * An `AbortController` per query, mirroring `useNavSuggestions`. Without
49
+ * it, a slow response to an earlier query can land after a faster
50
+ * response to a later one and overwrite it — the user sees results for
51
+ * something they already moved on from, which looks like the search
52
+ * returning the wrong thing rather than like a race.
53
+ */
54
+ const controller = new AbortController();
55
+ setStatus("loading");
56
+ apiClient
57
+ .get(SEARCH_ENDPOINTS.SCOPED(q), { signal: controller.signal })
58
+ .then((res) => {
59
+ if (controller.signal.aborted)
60
+ return;
61
+ setGroups(res?.data?.groups ?? []);
62
+ setStatus("ready");
63
+ })
64
+ .catch((err) => {
65
+ if (controller.signal.aborted)
66
+ return;
67
+ const e = normalizeError(err);
68
+ /*
69
+ * `failed`, and the CALLER must say so out loud.
70
+ * `searchEverything`'s own header records that a degraded source "is
71
+ * indistinguishable from 'nothing matched'" — and on a page where
72
+ * the public half rendered fine, silence tells a seller their draft
73
+ * does not exist.
74
+ */
75
+ void e;
76
+ setGroups([]);
77
+ setStatus("failed");
78
+ });
79
+ return () => controller.abort();
80
+ }, [enabled, submittedQuery]);
81
+ return { status, groups };
82
+ }
83
+ /**
84
+ * Merge the two tiers.
85
+ *
86
+ * ## 🛑 Dedupe by DESTINATION, and local wins
87
+ *
88
+ * The tiers are NOT disjoint: a seller's published listing is in the public
89
+ * artifact AND in `my-listings`. Showing both is a duplicate-results bug;
90
+ * dropping the public one would lose the row a buyer would see.
91
+ *
92
+ * Local wins, so what survives in the scoped group is precisely the rows the
93
+ * artifact does not have — a seller's DRAFTS. Labelling that group "Your
94
+ * listings (incl. drafts)" is what makes the dedupe legible rather than
95
+ * mysterious.
96
+ *
97
+ * 🛑 The key is the HREF, not `${typeKey}:${id}`. The artifact keys products
98
+ * by SLUG and Firestore by document id, so a composite key would never
99
+ * collide and the dedupe would silently do nothing — the failure mode being
100
+ * a page that looks fine and shows everything twice. Both tiers already
101
+ * agree on where a row goes, which is the only identity they share.
102
+ */
103
+ export function mergeSearchTiers(local, scoped) {
104
+ const seen = new Set();
105
+ for (const g of local)
106
+ for (const h of g.hits)
107
+ seen.add(h.href);
108
+ const deduped = scoped
109
+ .map((g) => ({ ...g, hits: g.hits.filter((h) => !seen.has(h.href)) }))
110
+ // A scoped group that is now empty is dropped entirely rather than
111
+ // rendered as a heading over nothing, which reads as a broken section.
112
+ .filter((g) => g.hits.length > 0);
113
+ return [...local, ...deduped];
114
+ }
@@ -0,0 +1,26 @@
1
+ import { type FacetCount } from "../../../_internal/shared/search/to-groups";
2
+ import { type SearchIndexStatus } from "./useSearchIndex";
3
+ import type { SearchEverythingResult } from "../../../_internal/server/features/search/search-everything";
4
+ import type { NavSuggestionRecord } from "./useNavSuggestions";
5
+ export interface UseSiteSearchOptions {
6
+ /** Flip true on focus or first keypress. Gates the artifact fetch. */
7
+ active: boolean;
8
+ /** `siteSettings.search?.indexEnabled` — read as `!== false`. */
9
+ indexEnabled?: boolean;
10
+ /** Restrict to these index type keys. Absent means every type. */
11
+ types?: readonly string[];
12
+ /** Require every one of these `k:v` pairs. The facet rail's selection. */
13
+ facets?: readonly string[];
14
+ /** `typeKey` -> heading. See `ToGroupsOptions.label`. */
15
+ label: (typeKey: string) => string;
16
+ }
17
+ export interface UseSiteSearchResult {
18
+ readonly status: SearchIndexStatus;
19
+ /** Null until the index is ready; never a fabricated empty result. */
20
+ readonly result: SearchEverythingResult | null;
21
+ readonly suggestions: readonly NavSuggestionRecord[];
22
+ readonly facets: readonly FacetCount[];
23
+ /** The index build this answered from, for a "results as of" line. */
24
+ readonly builtAt: string | null;
25
+ }
26
+ export declare function useSiteSearch(query: string, { active, indexEnabled, types, facets, label }: UseSiteSearchOptions): UseSiteSearchResult;