@mohasinac/appkit 4.53.0 → 4.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/scaffolds/AppShell.js +1 -1
  3. package/dist/_internal/server/features/auctions/og.d.ts +3 -1
  4. package/dist/_internal/server/features/auctions/og.js +3 -1
  5. package/dist/_internal/server/features/bundles/actions.js +1 -1
  6. package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
  7. package/dist/_internal/server/features/business-documents/download.js +91 -0
  8. package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
  9. package/dist/_internal/server/features/categories/adapters.js +10 -0
  10. package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
  11. package/dist/_internal/server/features/checkout/actions.js +106 -12
  12. package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
  13. package/dist/_internal/server/features/checkout/data.d.ts +22 -0
  14. package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
  15. package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
  16. package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
  17. package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
  18. package/dist/_internal/server/features/classified/metadata.js +1 -1
  19. package/dist/_internal/server/features/classified/og.d.ts +3 -1
  20. package/dist/_internal/server/features/classified/og.js +3 -1
  21. package/dist/_internal/server/features/digital-code/metadata.js +1 -1
  22. package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
  23. package/dist/_internal/server/features/digital-code/og.js +3 -1
  24. package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
  25. package/dist/_internal/server/features/digital-code/pool.js +19 -4
  26. package/dist/_internal/server/features/live/metadata.js +1 -1
  27. package/dist/_internal/server/features/live/og.d.ts +3 -1
  28. package/dist/_internal/server/features/live/og.js +3 -1
  29. package/dist/_internal/server/features/messages/actions.js +1 -1
  30. package/dist/_internal/server/features/parts/og.js +1 -1
  31. package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
  32. package/dist/_internal/server/features/pre-orders/og.js +3 -1
  33. package/dist/_internal/server/features/price-index/search.d.ts +18 -0
  34. package/dist/_internal/server/features/price-index/search.js +47 -7
  35. package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
  36. package/dist/_internal/server/features/prize-draws/og.js +3 -1
  37. package/dist/_internal/server/features/products/group-members.d.ts +26 -0
  38. package/dist/_internal/server/features/products/group-members.js +7 -1
  39. package/dist/_internal/server/features/products/og.d.ts +3 -1
  40. package/dist/_internal/server/features/products/og.js +3 -1
  41. package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
  42. package/dist/_internal/server/features/search/scoped-sources.js +252 -0
  43. package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
  44. package/dist/_internal/server/features/search/search-everything.js +70 -20
  45. package/dist/_internal/server/features/seo/sitemap.js +6 -0
  46. package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
  47. package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
  48. package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
  49. package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
  50. package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
  51. package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
  52. package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
  53. package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
  54. package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
  55. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  56. package/dist/_internal/server/features/stores/adapters.js +16 -0
  57. package/dist/_internal/server/features/stores/index.d.ts +1 -0
  58. package/dist/_internal/server/features/stores/index.js +1 -0
  59. package/dist/_internal/server/features/stores/tabs.d.ts +25 -0
  60. package/dist/_internal/server/features/stores/tabs.js +122 -0
  61. package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
  62. package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
  63. package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
  64. package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
  65. package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
  66. package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
  67. package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
  68. package/dist/_internal/shared/checkout/rules/index.js +9 -0
  69. package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
  70. package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
  71. package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
  72. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
  73. package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
  74. package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
  75. package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
  76. package/dist/_internal/shared/config/schema.d.ts +122 -0
  77. package/dist/_internal/shared/features/cart/schema.js +3 -2
  78. package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
  79. package/dist/_internal/shared/features/products/group-rules.js +142 -0
  80. package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
  81. package/dist/_internal/shared/listing-types/_registry.js +39 -1
  82. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  83. package/dist/_internal/shared/listing-types/art/config.js +2 -0
  84. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  85. package/dist/_internal/shared/listing-types/auction/config.js +2 -0
  86. package/dist/_internal/shared/listing-types/availability.js +48 -0
  87. package/dist/_internal/shared/listing-types/capabilities.js +19 -0
  88. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  89. package/dist/_internal/shared/listing-types/classified/config.js +2 -0
  90. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  91. package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
  92. package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
  93. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  94. package/dist/_internal/shared/listing-types/live/config.js +2 -0
  95. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  96. package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
  97. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  98. package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
  99. package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
  100. package/dist/_internal/shared/listing-types/spares/config.js +60 -0
  101. package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
  102. package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
  103. package/dist/_internal/shared/listing-types/spares/og.js +1 -0
  104. package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
  105. package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
  106. package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
  107. package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
  108. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  109. package/dist/_internal/shared/listing-types/standard/config.js +2 -0
  110. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  111. package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
  112. package/dist/_internal/shared/search/match.d.ts +11 -0
  113. package/dist/_internal/shared/search/match.js +2 -1
  114. package/dist/_internal/shared/search/to-groups.d.ts +52 -0
  115. package/dist/_internal/shared/search/to-groups.js +141 -0
  116. package/dist/client.d.ts +8 -2
  117. package/dist/client.js +4 -1
  118. package/dist/constants/api-endpoints.d.ts +39 -15
  119. package/dist/constants/api-endpoints.js +28 -5
  120. package/dist/constants/cache-invalidation.js +3 -1
  121. package/dist/constants/field-names.d.ts +6 -0
  122. package/dist/constants/field-names.js +6 -0
  123. package/dist/features/about/components/PolicyPageView.d.ts +22 -2
  124. package/dist/features/about/components/PolicyPageView.js +14 -2
  125. package/dist/features/about/components/PublicProfileView.js +2 -1
  126. package/dist/features/about/components/ShippingPolicyView.js +42 -2
  127. package/dist/features/account/components/NotificationBell.js +1 -1
  128. package/dist/features/account/hooks/useProfileStats.js +5 -1
  129. package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
  130. package/dist/features/addresses/repository/addresses.repository.js +21 -0
  131. package/dist/features/admin/actions/admin-actions.js +16 -1
  132. package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
  133. package/dist/features/admin/components/AdminNavigationView.js +1 -1
  134. package/dist/features/admin/components/AdminPageHeader.js +1 -1
  135. package/dist/features/admin/components/AdminProductEditorView.js +17 -2
  136. package/dist/features/admin/components/AdminSidebar.js +1 -1
  137. package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
  138. package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
  139. package/dist/features/admin/components/AdminSparesView.js +30 -0
  140. package/dist/features/admin/components/AdminUsersView.js +9 -7
  141. package/dist/features/admin/components/index.d.ts +2 -0
  142. package/dist/features/admin/components/index.js +1 -0
  143. package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
  144. package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
  145. package/dist/features/admin/constants/filter-tabs.js +1 -0
  146. package/dist/features/admin/schemas/firestore.d.ts +26 -2
  147. package/dist/features/auctions/components/AuctionDetailPageView.js +4 -11
  148. package/dist/features/auth/repository/user.repository.d.ts +13 -0
  149. package/dist/features/auth/repository/user.repository.js +28 -0
  150. package/dist/features/auth/schemas/firestore.d.ts +2 -0
  151. package/dist/features/auth/schemas/firestore.js +2 -0
  152. package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
  153. package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
  154. package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
  155. package/dist/features/business-documents/schemas/firestore.js +75 -0
  156. package/dist/features/cart/actions/cart-actions.js +83 -6
  157. package/dist/features/cart/repository/cart.repository.js +32 -4
  158. package/dist/features/cart/schemas/firestore.d.ts +20 -0
  159. package/dist/features/cart/schemas/index.d.ts +2 -2
  160. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
  161. package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
  162. package/dist/features/categories/api/[id]/price-guidance/route.js +113 -0
  163. package/dist/features/categories/components/BrandDetailPageView.js +19 -3
  164. package/dist/features/categories/components/BrandDetailTabs.js +10 -0
  165. package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
  166. package/dist/features/categories/components/CategoryDetailPageView.js +141 -14
  167. package/dist/features/categories/components/CategoryDetailTabs.js +12 -0
  168. package/dist/features/categories/components/CategoryGrid.js +10 -1
  169. package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
  170. package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
  171. package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
  172. package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
  173. package/dist/features/categories/constants/category-tabs.d.ts +20 -25
  174. package/dist/features/categories/constants/category-tabs.js +22 -39
  175. package/dist/features/categories/repository/categories.repository.d.ts +12 -0
  176. package/dist/features/categories/repository/categories.repository.js +20 -0
  177. package/dist/features/categories/schemas/firestore.d.ts +22 -1
  178. package/dist/features/categories/schemas/firestore.js +10 -0
  179. package/dist/features/categories/schemas/index.d.ts +6 -0
  180. package/dist/features/categories/schemas/index.js +2 -0
  181. package/dist/features/categories/types/index.d.ts +8 -0
  182. package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
  183. package/dist/features/categories/utils/taxonomy-visual.js +26 -10
  184. package/dist/features/classified/components/ClassifiedDetailPageView.js +4 -9
  185. package/dist/features/contact/components/ContactPageView.d.ts +33 -1
  186. package/dist/features/contact/components/ContactPageView.js +58 -28
  187. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +4 -9
  188. package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
  189. package/dist/features/grouped/schemas/firestore.d.ts +5 -0
  190. package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
  191. package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
  192. package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
  193. package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
  194. package/dist/features/homepage/components/HowItWorksSection.js +4 -4
  195. package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
  196. package/dist/features/homepage/components/PromoGrid.js +12 -3
  197. package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
  198. package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
  199. package/dist/features/homepage/components/index.d.ts +0 -20
  200. package/dist/features/homepage/components/index.js +0 -11
  201. package/dist/features/homepage/index.d.ts +0 -2
  202. package/dist/features/homepage/index.js +0 -2
  203. package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
  204. package/dist/features/homepage/lib/section-renderer.js +202 -58
  205. package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
  206. package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
  207. package/dist/features/homepage/schemas/firestore.d.ts +106 -3
  208. package/dist/features/homepage/schemas/firestore.js +32 -2
  209. package/dist/features/homepage/schemas/index.d.ts +36 -7
  210. package/dist/features/homepage/schemas/index.js +39 -27
  211. package/dist/features/homepage/server.d.ts +0 -3
  212. package/dist/features/homepage/server.js +0 -3
  213. package/dist/features/homepage/types/index.d.ts +0 -47
  214. package/dist/features/homepage/types/index.js +49 -0
  215. package/dist/features/layout/AppLayoutShell.js +1 -1
  216. package/dist/features/layout/BottomNavItem.js +1 -1
  217. package/dist/features/layout/BottomSheet.js +1 -1
  218. package/dist/features/layout/TitleBarLayout.js +1 -1
  219. package/dist/features/live/components/LiveItemDetailPageView.js +4 -9
  220. package/dist/features/media/hooks/useMedia.js +17 -0
  221. package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
  222. package/dist/features/media/repository/media-assets.repository.js +31 -0
  223. package/dist/features/media/trust-signals.d.ts +85 -0
  224. package/dist/features/media/trust-signals.js +126 -0
  225. package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
  226. package/dist/features/parts/components/PartDetailPageView.js +2 -2
  227. package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
  228. package/dist/features/parts/components/PartsIndexPageView.js +2 -2
  229. package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
  230. package/dist/features/parts/components/PartsSearchBox.js +57 -0
  231. package/dist/features/parts/schemas/firestore.d.ts +149 -3
  232. package/dist/features/parts/schemas/firestore.js +184 -0
  233. package/dist/features/payments/repository/payout.repository.d.ts +15 -0
  234. package/dist/features/payments/repository/payout.repository.js +21 -0
  235. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -11
  236. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
  237. package/dist/features/price-index/components/PriceIndexToolView.d.ts +3 -1
  238. package/dist/features/price-index/components/PriceIndexToolView.js +92 -13
  239. package/dist/features/products/components/CompareOverlay.d.ts +18 -1
  240. package/dist/features/products/components/CompareOverlay.js +21 -2
  241. package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
  242. package/dist/features/products/components/CustomSectionTabContent.js +29 -3
  243. package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
  244. package/dist/features/products/components/GroupMemberPicker.js +46 -8
  245. package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
  246. package/dist/features/products/components/GroupSettingsPanel.js +91 -3
  247. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  248. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
  249. package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
  250. package/dist/features/products/components/ProductDetailPageView.js +74 -15
  251. package/dist/features/products/components/ProductForm.d.ts +13 -1
  252. package/dist/features/products/components/ProductForm.js +16 -2
  253. package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
  254. package/dist/features/products/components/ProductsIndexListing.js +3 -3
  255. package/dist/features/products/components/ShowGroupSection.js +15 -2
  256. package/dist/features/products/components/SparesListView.d.ts +7 -0
  257. package/dist/features/products/components/SparesListView.js +39 -0
  258. package/dist/features/products/constants/action-defs.d.ts +1 -0
  259. package/dist/features/products/constants/action-defs.js +1 -0
  260. package/dist/features/products/repository/products.repository.d.ts +98 -0
  261. package/dist/features/products/repository/products.repository.js +235 -17
  262. package/dist/features/products/schemas/firestore.d.ts +97 -4
  263. package/dist/features/products/schemas/firestore.js +104 -0
  264. package/dist/features/products/schemas/index.d.ts +10 -19
  265. package/dist/features/products/schemas/index.js +23 -6
  266. package/dist/features/products/types/index.d.ts +45 -1
  267. package/dist/features/products/utils/group-member-image.d.ts +39 -0
  268. package/dist/features/products/utils/group-member-image.js +3 -0
  269. package/dist/features/products/utils/listing-badge-variant.js +4 -0
  270. package/dist/features/reviews/components/ReviewsList.js +1 -1
  271. package/dist/features/scams/components/ScamProfileView.js +1 -1
  272. package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
  273. package/dist/features/search/components/EntityTypeahead.js +92 -0
  274. package/dist/features/search/components/Search.d.ts +12 -1
  275. package/dist/features/search/components/Search.js +47 -3
  276. package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
  277. package/dist/features/search/components/SearchFacetRail.js +92 -0
  278. package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
  279. package/dist/features/search/components/SearchResultsClient.js +103 -0
  280. package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
  281. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  282. package/dist/features/search/components/index.d.ts +0 -6
  283. package/dist/features/search/components/index.js +0 -3
  284. package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
  285. package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
  286. package/dist/features/search/hooks/useScopedSearch.js +114 -0
  287. package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
  288. package/dist/features/search/hooks/useSiteSearch.js +149 -0
  289. package/dist/features/search/index.d.ts +0 -4
  290. package/dist/features/search/index.js +0 -4
  291. package/dist/features/search/manifest.d.ts +12 -0
  292. package/dist/features/search/manifest.js +17 -2
  293. package/dist/features/search/server.d.ts +0 -3
  294. package/dist/features/search/server.js +0 -3
  295. package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
  296. package/dist/features/seller/components/SellerListingTypeView.js +20 -0
  297. package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
  298. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  299. package/dist/features/seller/components/SellerProductShell.js +2 -1
  300. package/dist/features/seller/components/SellerSidebar.js +1 -1
  301. package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
  302. package/dist/features/seller/components/SellerSparesView.js +6 -0
  303. package/dist/features/seller/components/index.d.ts +2 -0
  304. package/dist/features/seller/components/index.js +1 -0
  305. package/dist/features/shell/CommandPalette.js +1 -1
  306. package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
  307. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
  308. package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
  309. package/dist/features/stores/components/StoreHeader.js +1 -2
  310. package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
  311. package/dist/features/stores/components/StoreSparesPageView.js +48 -0
  312. package/dist/features/stores/schemas/firestore.d.ts +19 -0
  313. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
  314. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
  315. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
  316. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +280 -45
  317. package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
  318. package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
  319. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
  320. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
  321. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
  322. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
  323. package/dist/features/tester/seed-data/authored/index.js +16 -0
  324. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
  325. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
  326. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
  327. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
  328. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
  329. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
  330. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
  331. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +372 -18
  332. package/dist/index.d.ts +17 -57
  333. package/dist/index.js +35 -96
  334. package/dist/next/routing/route-map.d.ts +42 -1
  335. package/dist/next/routing/route-map.js +45 -0
  336. package/dist/providers/db-firebase/base.repository.d.ts +22 -0
  337. package/dist/providers/db-firebase/base.repository.js +36 -0
  338. package/dist/schemas/registry.d.ts +15 -6
  339. package/dist/security/blind-index-tokens.d.ts +54 -0
  340. package/dist/security/blind-index-tokens.js +152 -0
  341. package/dist/security/index.d.ts +1 -1
  342. package/dist/security/index.js +1 -1
  343. package/dist/security/settings-encryption.d.ts +35 -0
  344. package/dist/security/settings-encryption.js +91 -0
  345. package/dist/seed/_helpers/brand-rows.js +38 -14
  346. package/dist/seed/_helpers/category-forest.js +73 -7
  347. package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
  348. package/dist/seed/_helpers/category-images-generated.js +81 -6
  349. package/dist/seed/_helpers/category-models.d.ts +1 -1
  350. package/dist/seed/_helpers/category-models.js +7 -1
  351. package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
  352. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  353. package/dist/seed/_helpers/category-parts.js +50 -2
  354. package/dist/seed/_helpers/character-features.d.ts +1 -1
  355. package/dist/seed/_helpers/character-features.js +2 -1
  356. package/dist/seed/_helpers/franchise-rows.js +10 -2
  357. package/dist/seed/_helpers/media.d.ts +47 -0
  358. package/dist/seed/_helpers/media.js +34 -0
  359. package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
  360. package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
  361. package/dist/seed/categories-seed-data.js +124 -20
  362. package/dist/seed/grouped-listings-seed-data.js +16 -0
  363. package/dist/seed/homepage-sections-seed-data.js +94 -5
  364. package/dist/seed/index.d.ts +2 -3
  365. package/dist/seed/index.js +2 -2
  366. package/dist/seed/manifest.js +3 -0
  367. package/dist/seed/media-assets-seed-data.d.ts +1 -1
  368. package/dist/seed/media-assets-seed-data.js +746 -196
  369. package/dist/seed/model-composition-seed-data.js +122 -6
  370. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  371. package/dist/seed/part-glossary-seed-data.js +37219 -463
  372. package/dist/seed/products-art-seed-data.js +7 -18
  373. package/dist/seed/products-auctions-seed-data.js +56 -47
  374. package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
  375. package/dist/seed/products-beyblade-x-seed-data.js +441 -0
  376. package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
  377. package/dist/seed/products-catalogue-seed-data.js +1500 -1240
  378. package/dist/seed/products-classifieds-seed-data.js +9 -27
  379. package/dist/seed/products-digital-codes-seed-data.js +9 -18
  380. package/dist/seed/products-live-items-seed-data.js +5 -13
  381. package/dist/seed/products-preorders-seed-data.js +67 -33
  382. package/dist/seed/products-prize-draws-seed-data.js +8 -17
  383. package/dist/seed/products-spares-seed-data.d.ts +2 -0
  384. package/dist/seed/products-spares-seed-data.js +368 -0
  385. package/dist/seed/products-standard-seed-data.js +39 -126
  386. package/dist/seed/products-stickers-seed-data.js +59 -15
  387. package/dist/seed/site-settings-seed-data.js +8 -0
  388. package/dist/seo/json-ld.d.ts +25 -0
  389. package/dist/seo/json-ld.js +28 -0
  390. package/dist/server-entry.d.ts +3 -1
  391. package/dist/server-entry.js +2 -2
  392. package/dist/server.d.ts +9 -9
  393. package/dist/server.js +27 -25
  394. package/dist/styles.css +202 -1
  395. package/dist/tailwind-input.css +31 -17
  396. package/dist/tailwind-utilities.css +1 -1
  397. package/dist/ui/components/Accordion.js +1 -1
  398. package/dist/ui/components/Breadcrumb.js +1 -1
  399. package/dist/ui/components/DetailPageGallery.js +0 -2
  400. package/dist/ui/components/DetailPageHero.js +0 -1
  401. package/dist/ui/components/DetailPageTabs.js +1 -1
  402. package/dist/ui/components/Divider.js +3 -3
  403. package/dist/ui/components/GatedPrice.js +5 -1
  404. package/dist/ui/components/HorizontalScroller.js +1 -1
  405. package/dist/ui/components/ListingLayout.js +3 -3
  406. package/dist/ui/components/MediaTabs.style.css +81 -0
  407. package/dist/ui/components/Pagination.js +1 -1
  408. package/dist/ui/components/PendingValue.d.ts +45 -0
  409. package/dist/ui/components/PendingValue.js +7 -0
  410. package/dist/ui/components/Radio.style.css +119 -0
  411. package/dist/ui/components/RatingDisplay.js +2 -2
  412. package/dist/ui/components/SettingsSection.js +0 -2
  413. package/dist/ui/components/SiteLogo.js +3 -0
  414. package/dist/ui/components/SiteMark.js +3 -0
  415. package/dist/ui/components/SortDropdown.js +1 -1
  416. package/dist/ui/components/SpecTable.d.ts +58 -0
  417. package/dist/ui/components/SpecTable.js +8 -0
  418. package/dist/ui/components/StarRating.js +1 -3
  419. package/dist/ui/components/SummaryCard.js +1 -1
  420. package/dist/ui/components/index.style.css +1 -0
  421. package/dist/ui/icons/icon-registry.d.ts +1 -0
  422. package/dist/ui/icons/icon-registry.js +9 -1
  423. package/dist/ui/index.d.ts +4 -0
  424. package/dist/ui/index.js +2 -0
  425. package/dist/utils/search-txt-query.d.ts +12 -0
  426. package/dist/utils/search-txt-query.js +12 -0
  427. package/package.json +1 -1
  428. package/scripts/dedupe-peer-deps.mjs +18 -0
  429. package/scripts/seed-cli.mjs +135 -26
  430. package/scripts/verify-css-build.mjs +118 -37
  431. package/dist/_internal/server/features/search/actions.d.ts +0 -7
  432. package/dist/_internal/server/features/search/actions.js +0 -21
  433. package/dist/_internal/server/features/search/data.d.ts +0 -15
  434. package/dist/_internal/server/features/search/data.js +0 -18
  435. package/dist/_internal/server/features/search/index.d.ts +0 -3
  436. package/dist/_internal/server/features/search/index.js +0 -2
  437. package/dist/features/homepage/api/[id]/route.d.ts +0 -23
  438. package/dist/features/homepage/api/[id]/route.js +0 -103
  439. package/dist/features/homepage/api/route.d.ts +0 -14
  440. package/dist/features/homepage/api/route.js +0 -140
  441. package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
  442. package/dist/features/homepage/api/sections/[id]/route.js +0 -95
  443. package/dist/features/homepage/api/sections/route.d.ts +0 -13
  444. package/dist/features/homepage/api/sections/route.js +0 -82
  445. package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
  446. package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
  447. package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
  448. package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
  449. package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
  450. package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
  451. package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
  452. package/dist/features/homepage/components/HeroBanner.js +0 -43
  453. package/dist/features/homepage/components/HeroSection.d.ts +0 -7
  454. package/dist/features/homepage/components/HeroSection.js +0 -6
  455. package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
  456. package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
  457. package/dist/features/homepage/components/HomepageView.d.ts +0 -25
  458. package/dist/features/homepage/components/HomepageView.js +0 -5
  459. package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
  460. package/dist/features/homepage/components/NewsletterBanner.js +0 -37
  461. package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
  462. package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
  463. package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
  464. package/dist/features/homepage/components/TrustBadges.js +0 -46
  465. package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
  466. package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
  467. package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
  468. package/dist/features/homepage/hooks/useHomepage.js +0 -17
  469. package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
  470. package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
  471. package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
  472. package/dist/features/homepage/repository/homepage.repository.js +0 -25
  473. package/dist/features/search/actions/index.d.ts +0 -1
  474. package/dist/features/search/actions/index.js +0 -1
  475. package/dist/features/search/actions/search-actions.d.ts +0 -18
  476. package/dist/features/search/actions/search-actions.js +0 -43
  477. package/dist/features/search/api/route.d.ts +0 -22
  478. package/dist/features/search/api/route.js +0 -128
  479. package/dist/features/search/columns/index.d.ts +0 -17
  480. package/dist/features/search/columns/index.js +0 -49
  481. package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
  482. package/dist/features/search/components/SearchFiltersRow.js +0 -21
  483. package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
  484. package/dist/features/search/components/SearchResultsSection.js +0 -35
  485. package/dist/features/search/components/SearchView.d.ts +0 -27
  486. package/dist/features/search/components/SearchView.js +0 -8
  487. package/dist/features/search/hooks/useSearch.d.ts +0 -21
  488. package/dist/features/search/hooks/useSearch.js +0 -33
  489. package/dist/features/search/repository/search.repository.d.ts +0 -14
  490. package/dist/features/search/repository/search.repository.js +0 -59
  491. package/dist/features/search/schemas/index.d.ts +0 -70
  492. package/dist/features/search/schemas/index.js +0 -36
  493. package/dist/features/search/types/index.d.ts +0 -48
  494. package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
  495. package/dist/features/stores/components/StoreScopedSearch.js +0 -27
  496. package/dist/seed/defaults/homepage-sections.d.ts +0 -2
  497. package/dist/seed/defaults/homepage-sections.js +0 -60
  498. package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
  499. package/dist/seed/factories/homepage-section.factory.js +0 -13
  500. /package/dist/{features/search/types/index.js → _internal/shared/listing-types/spares/og.d.ts} +0 -0
@@ -0,0 +1,92 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /*
4
+ * WHY: Three tools need "turn what the user typed into a canonical entity",
5
+ * and each wants a different slice. `resolveEntity`'s `types` parameter
6
+ * exists for exactly this; this is its UI.
7
+ * WHAT: A generic index-backed typeahead. Offers canonical names, reports
8
+ * BOTH the chosen entity and the raw string the user typed.
9
+ *
10
+ * ## 🛑 `onPick` hands back the RAW query as well as the entity
11
+ *
12
+ * This is the one thing about the component that is not a convenience.
13
+ *
14
+ * `/tools/authenticity` treats a fuzzy name match as EVIDENCE — a near-miss
15
+ * fires a trademark-evasion warning, and its own source records the case:
16
+ * "`Pegasis` … resolves as an `alias` hit, and the evasion-spelling warning
17
+ * (which only fires on a `fuzzy` hit) stays silent." Meanwhile this
18
+ * component's matcher derives its alias table from a crawl of a counterfeit
19
+ * seller's 1,277 titles, on the stated insight that "a counterfeiter's
20
+ * evasion spellings are the same strings a real buyer mistypes."
21
+ *
22
+ * Both systems consume the same corpus with OPPOSITE intent: search treats a
23
+ * counterfeiter's spelling as something to match through, authenticity
24
+ * treats it as the signal. So if someone types `Pegasis`, picks "Storm
25
+ * Pegasus" from this dropdown, and the consumer forwards only the resolved
26
+ * slug, the engine never sees `Pegasis` and the evasion warning is silenced
27
+ * BY CONSTRUCTION — the tool would confirm a fake using the exact spelling
28
+ * that indicates one.
29
+ *
30
+ * Hence: this component PROPOSES an entity; the consumer must keep passing
31
+ * what the user actually typed. Enforced from the other side by
32
+ * `audit-entity-resolver-consumers` R2, which forbids any module under
33
+ * `features/authenticity/` from importing the matcher at all — which is why
34
+ * this lives here and reaches that tool as a slot.
35
+ *
36
+ * @tag domain:search
37
+ * @tag layer:feature
38
+ * @tag pattern:none
39
+ * @tag access:client
40
+ * @tag consumers:/tools/authenticity,/tools/price-index
41
+ * @tag sideEffects:network
42
+ */
43
+ import React from "react";
44
+ import { Button, Div, Input, Stack, Text } from "../../../ui/index.js";
45
+ import { useSiteSearch } from "../hooks/useSiteSearch.js";
46
+ /** Stable identity — a `useMemo` dependency inside the hook. */
47
+ const passthroughLabel = (typeKey) => typeKey;
48
+ export function EntityTypeahead({ types, onPick, indexEnabled, label, placeholder, value, onChange, limit = 6, fillOnPick = true, }) {
49
+ const controlled = value !== undefined;
50
+ const [internal, setInternal] = React.useState("");
51
+ const query = controlled ? value : internal;
52
+ const [active, setActive] = React.useState(false);
53
+ const [dismissed, setDismissed] = React.useState(false);
54
+ const { status, result } = useSiteSearch(query, {
55
+ active,
56
+ indexEnabled,
57
+ types,
58
+ label: passthroughLabel,
59
+ });
60
+ const hits = result?.groups.flatMap((g) => g.hits) ?? [];
61
+ const show = !dismissed && status === "ready" && hits.length > 0 && query.trim().length > 0;
62
+ const setQuery = (next) => {
63
+ setDismissed(false);
64
+ if (controlled)
65
+ onChange?.(next);
66
+ else
67
+ setInternal(next);
68
+ };
69
+ return (_jsxs(Stack, { gap: "xs", className: "min-w-0", children: [_jsx(Input, { type: "search", value: query, "aria-label": label, placeholder: placeholder, onFocus: () => setActive(true), onChange: (event) => {
70
+ setActive(true);
71
+ setQuery(event.target.value);
72
+ } }), show && (_jsxs(Stack, { gap: "xs", className: "min-w-0", children: [_jsx(Text, { size: "xs", color: "muted", children: "Did you mean one of these?" }), hits.slice(0, limit).map((hit) => (_jsxs(Div, { surface: "card", padding: "xs", rounded: "md", border: "subtle", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
73
+ /*
74
+ * 🛑 `rawQuery: query` — BEFORE the field is overwritten
75
+ * with the canonical title. Reading it after would hand the
76
+ * consumer the canonical name as "what the user typed",
77
+ * which is the silent version of the bug this whole
78
+ * component's header is about.
79
+ */
80
+ onPick({
81
+ title: hit.title,
82
+ href: hit.href,
83
+ typeKey: result?.groups.find((g) => g.hits.includes(hit))?.key ?? "",
84
+ rawQuery: query,
85
+ });
86
+ // See `fillOnPick`'s 🛑 — skipping this is the whole
87
+ // reason the prop exists, not an inconsistency.
88
+ if (fillOnPick)
89
+ setQuery(hit.title);
90
+ setDismissed(true);
91
+ }, children: hit.title }), hit.detail && (_jsx(Text, { size: "xs", color: "muted", children: hit.detail }))] }, hit.id)))] }))] }));
92
+ }
@@ -47,5 +47,16 @@ export interface SearchProps {
47
47
  resourceTypes?: SearchResourceTypeOption[];
48
48
  defaultResourceType?: SearchResourceType;
49
49
  storageKey?: string;
50
+ /**
51
+ * `siteSettings.search?.indexEnabled`. Read as `!== false`, so an absent
52
+ * settings object leaves the local index in play.
53
+ *
54
+ * When the index answers, the typeahead spends ZERO network requests per
55
+ * keystroke and spans all 21 indexed types instead of the four
56
+ * `/api/search/suggestions` can produce. When it is off or has failed, the
57
+ * component falls back to that endpoint unchanged — which is why
58
+ * `useNavSuggestions` is still called rather than deleted.
59
+ */
60
+ searchIndexEnabled?: boolean;
50
61
  }
51
- export declare function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, placeholder, deferred, debounceMs, onClear, className, router, quickLinks, labels, suggestionTypeIcons, suggestionTypeBadges, resourceTypes, defaultResourceType, storageKey, }: SearchProps): React.JSX.Element | null;
62
+ export declare function Search({ isOpen, onClose, onSearch, onOpen, value, onChange, placeholder, deferred, debounceMs, onClear, className, router, quickLinks, labels, suggestionTypeIcons, suggestionTypeBadges, resourceTypes, defaultResourceType, storageKey, searchIndexEnabled, }: SearchProps): React.JSX.Element | null;
@@ -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[];