@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,2 @@
1
+ import type { AuthoredCase } from "./_types";
2
+ export declare const authored: Record<string, AuthoredCase>;
@@ -0,0 +1,58 @@
1
+ /*
2
+ * WHY: Authored six-part procedures for promoting a traced sticker sheet into
3
+ * real marketplace listings — distinct from the tracer's own "promote"
4
+ * (run -> sheet). This is sheet -> marketplace.
5
+ * WHAT: 3 case(s), keyed by full checklist id.
6
+ *
7
+ * 🛑 These depend on the sticker-tracer's own happy path having already
8
+ * produced an accepted, full-resolution-rendered run on a sheet — they are
9
+ * not reachable standing alone. Run the tracer's own cases (checklist-tools-
10
+ * sticker-tracer-*) up through "Accept & render" first.
11
+ *
12
+ * @tag domain:tester
13
+ * @tag layer:seed
14
+ * @tag pattern:none
15
+ * @tag access:server-only
16
+ * @tag consumers:seed-data/authored/index.ts
17
+ * @tag sideEffects:none
18
+ */
19
+ const ADMIN_SIGNIN = "Sign in as admin@letitrip.in / TempPass123!.";
20
+ export const authored = {
21
+ "checklist-tools-sticker-promote-to-listings-promote-creates-one-group": {
22
+ roles: ["admin"],
23
+ startPage: "/tools/sticker-tracer",
24
+ steps: [
25
+ ADMIN_SIGNIN,
26
+ "Open a sheet whose active run has already been accepted and rendered to full resolution, with at least one part marked a duplicate of another.",
27
+ "Click \"Promote to listings\". Pick \"Beyblade Arena\" as the store, enter 499 for the sheet price and 99 for the flat part price, then confirm.",
28
+ "Open the newly created parent listing's product page and view its group/\"Pick what you want\" section.",
29
+ ],
30
+ expectedBehaviour: "The promoter creates one parent (the whole sheet) plus one child per UNIQUE part only — a part marked `duplicateOf` another must NOT get its own listing. All children are linked into ONE group via `linkChildrenToGroup`, a single batch call rather than N separate `linkChildToGroup` calls in a loop.",
31
+ expectedUiState: "The group picker on the parent's page shows every UNIQUE child — if the sheet had, say, 8 parts with 3 confirmed duplicates, the picker shows 5 members, not 1 and not 8. This is exactly the `linkChildToGroup` stale-append bug this phase fixed first: looping that call used to leave only the LAST child linked, so the picker showed just one member no matter how many parts existed.",
32
+ endResult: "BEFORE the fix, promoting an 8-part sheet silently produced a group whose parent claimed only 1 child, even though all 8 children individually had the correct `groupId` set. AFTER: the group's member list and the children's own `groupId`s agree.",
33
+ },
34
+ "checklist-tools-sticker-promote-to-listings-promoted-part-image-watermarked": {
35
+ roles: ["admin"],
36
+ startPage: "/tools/sticker-tracer",
37
+ steps: [
38
+ ADMIN_SIGNIN,
39
+ "After promoting a sheet, open one of the new CHILD listings' public product page and inspect its main image.",
40
+ "Separately, from the tracer's own sheet-review UI, download that same part's original full-resolution file (via the signed URL, not the public page).",
41
+ ],
42
+ expectedBehaviour: "The marketplace copy is served through `/media/{shortId}`, which the proxy watermarks on read because the `mediaAssets` row for it was written with `watermarkedAt` deliberately absent. The admin's own download goes through a signed GCS URL pointing at the SAME `assetPath` — the clean original — with zero bytes duplicated between the two.",
43
+ expectedUiState: "The public listing's image carries a visible watermark. The admin's direct download of the same part is clean — no watermark at all. Both are visibly the same artwork at the same crop.",
44
+ endResult: "BEFORE this phase, there was no public listing for a traced part at all. AFTER: the same pixels serve two audiences two different ways from one stored object — if the public copy is clean, or the admin's download is watermarked, the `watermarkedAt` wiring has inverted.",
45
+ },
46
+ "checklist-tools-sticker-promote-to-listings-promote-refuses-before-full-res-render": {
47
+ roles: ["admin"],
48
+ startPage: "/tools/sticker-tracer",
49
+ steps: [
50
+ "Sign in as admin@letitrip.in / TempPass123!.",
51
+ "Open a sheet that has been traced but whose run has NOT had \"Accept & render\" run on it (a fresh trace, or one accepted but not yet rendered to full resolution).",
52
+ "Attempt to promote it to listings.",
53
+ ],
54
+ expectedBehaviour: "`promoteStickerSheetToListings` checks every precondition UP FRONT — an active run, at least one unique part, and every unique part actually carrying an `assetPath` — and refuses the WHOLE action if any is missing, rather than creating a handful of listings and silently skipping the rest.",
55
+ expectedUiState: "The action is refused with a message naming the missing step (e.g. \"accept and render the run before promoting it to listings\"). NO parent or child listing is created — checking the store's product list afterward shows nothing new.",
56
+ endResult: "BEFORE this check existed, promoting too early would have been the exact 'wrong data, no error' failure this whole phase exists to avoid: some parts listed, others silently missing, with nothing anywhere saying so. AFTER: the refusal happens before any write, so a half-promoted sheet cannot exist.",
57
+ },
58
+ };
@@ -248,8 +248,8 @@ export const authored = {
248
248
  "Open the Tools entry in the main navigation and read its children.",
249
249
  "Visit /tools and read the cards.",
250
250
  ],
251
- expectedBehaviour: "The guard is the layout's server-side session read, so a non-admin is redirected before any HTML is produced. The nav child is filtered out client-side, which is presentation only — the redirect is the real boundary.",
252
- expectedUiState: "The tracer URL does not render; the browser lands elsewhere. The Tools dropdown does not list 'Sticker sheet tracer'. /tools itself renders normally with its other cards.",
251
+ expectedBehaviour: "A non-admin is refused. MEASURED on production 2026-10-19: the response is a 200 carrying an Unauthorized state, NOT a 307 — but no tracer UI is served, which is the property that matters. The nav child is filtered client-side, which is presentation only.",
252
+ expectedUiState: "An unauthorized / sign-in state, not the tracer. The phrase 'Choose a scan' appears nowhere on the page. The Tools dropdown does not list 'Sticker sheet tracer'. /tools itself renders normally with its other cards.",
253
253
  endResult: "BEFORE/AFTER is unchanged for a buyer — this case exists to prove the gate holds. Nothing is created or persisted.",
254
254
  },
255
255
  "checklist-tools-sticker-tracer-guest-redirected": {
@@ -260,9 +260,10 @@ export const authored = {
260
260
  "Navigate directly to /tools/sticker-tracer.",
261
261
  "Then visit /tools.",
262
262
  "View source on /tools/sticker-tracer's response and search for 'Choose a scan'.",
263
+ "In the same source, confirm a <meta name=\"robots\" content=\"noindex, nofollow\"> tag is present.",
263
264
  ],
264
- expectedBehaviour: "A signed-out visitor is redirected to login. No part of the tracer UI is sent to the browser, because the layout redirects before rendering rather than hiding controls after.",
265
- expectedUiState: "The login page, with a redirect back to the tracer. /tools renders normally and is unaffected. The phrase 'Choose a scan' appears nowhere in the response body.",
265
+ expectedBehaviour: "A signed-out visitor gets no tracer UI. 🛑 MEASURED on production: a 200 with a sign-in state and `<title>Sticker sheet tracer</title>`, NOT a redirect — so the plan's premise that 'a crawler gets a redirect and there is no indexable content at that URL' is FALSE. What actually keeps it out of the index is the `noindex, nofollow` meta plus the robots.txt Disallow, both verified live. That is exactly why all three protections were specified as non-redundant.",
266
+ expectedUiState: "A sign-in state rather than the tracer. `Choose a scan` appears NOWHERE in the response body — that absence is the real assertion. /tools renders normally and is unaffected.",
266
267
  endResult: "BEFORE/AFTER unchanged for a guest — this proves the gate is server-side rather than a hidden control. Nothing is persisted.",
267
268
  },
268
269
  "checklist-tools-sticker-tracer-storage-meter-honest": {
@@ -218,6 +218,49 @@ const rawTesterChecklistItems = [
218
218
  },
219
219
  ]),
220
220
  ...group("buying", "Buying", [
221
+ {
222
+ /*
223
+ * Part Two B/C/D/E — spares, made-to-order, promoted sticker parts and
224
+ * the group discount, all together on the one page a buyer actually
225
+ * uses. Each case is an explicit BEFORE -> AFTER: a spare page returning
226
+ * 200 with plausible cards is not evidence any of this works.
227
+ */
228
+ pageKey: "spares-and-groups",
229
+ pageLabel: "Spares browse, made-to-order, and group selections",
230
+ href: "/spares",
231
+ cases: [
232
+ {
233
+ key: "spares-not-in-general-catalogue",
234
+ label: "A spare does NOT appear on /products, but does appear on /spares",
235
+ description: "BEFORE: there was no tenth listing type. AFTER: open /products and search for a known spare's title (e.g. \"Cross Dranzer\") — it must NOT appear, because `spares` sets `inGeneralCatalogue: false`. The same title DOES appear on /spares. If it appears on both, the exclusion broke; if it appears on neither, the nav/page wiring broke.",
236
+ },
237
+ {
238
+ key: "spares-nav-entry-present",
239
+ label: "The main nav's Products dropdown has a \"Spares\" child linking to /spares",
240
+ },
241
+ {
242
+ key: "made-to-order-buyable-at-zero-stock",
243
+ label: "A made-to-order listing at zero stock is buyable and says \"Made to order\", never \"Out of Stock\"",
244
+ description: "Open the seeded \"Custom Beyblade Sticker Sheet — Print on Demand\" (stockQuantity 0, madeToOrder true). It must show \"Made to order — printed when you order\" rather than an Out of Stock badge, Add to Cart must work, and checkout must complete. This is the exact case C exists for: stock must gate nothing on this listing.",
245
+ href: "/products/sticker-beyblade-custom-print-on-demand",
246
+ },
247
+ {
248
+ key: "group-picker-shows-rules-before-choosing",
249
+ label: "A group with selection rules states them BEFORE the buyer picks anything",
250
+ description: "Open the seeded \"Burst & X Attackers You Might Like\" group (via one of its member products' Group section). The picker must show its rules (min 2, max 3, max 2 of any one, 10% off) as text near the top — not only as a refusal after submitting.",
251
+ },
252
+ {
253
+ key: "group-picker-blocks-and-explains-invalid-selection",
254
+ label: "Picking an invalid combination (e.g. only 1 item, or 3 of one item) shows the reason and disables Add",
255
+ description: "On the same group, select only 1 item: the Add button must be disabled and a message naming the rule must appear (\"Pick at least 2…\"). Select 3 of one item: a different message naming the per-item cap must appear. Fixing the selection must re-enable Add without a page reload.",
256
+ },
257
+ {
258
+ key: "group-discount-applied-and-shown",
259
+ label: "The group discount is shown on the picker AND the cart line is actually discounted by that percentage",
260
+ description: "Select a valid combination (e.g. 2 different items) on the 10%-off group and note the total shown in the picker. Add to cart, then open the cart and the checkout review: the line total must be 10% below the sum of the two items' individual prices, to the paisa, in all three places (picker, cart, checkout).",
261
+ },
262
+ ],
263
+ },
221
264
  {
222
265
  /*
223
266
  * The post-purchase money surface. `/user/orders/[id]/` has five
@@ -2212,6 +2255,39 @@ const rawTesterChecklistItems = [
2212
2255
  },
2213
2256
  ]),
2214
2257
  ...group("selling", "Selling", [
2258
+ {
2259
+ /*
2260
+ * Part Two B/E — the seller side of spares, and the shared group-stock
2261
+ * editor. "Stock is independent per listing" is the whole premise, so
2262
+ * the editor-save-and-reload round trip is what actually proves that —
2263
+ * a shared pool bug would show up exactly here, as one listing's stock
2264
+ * silently changing another's.
2265
+ */
2266
+ pageKey: "spares-seller-and-group-editing",
2267
+ pageLabel: "Seller spares dashboard and group stock/rules editing",
2268
+ href: "/store/spares",
2269
+ cases: [
2270
+ {
2271
+ key: "seller-can-list-a-spare",
2272
+ label: "A seller can create, publish and see a spare on their own /store/spares list",
2273
+ },
2274
+ {
2275
+ key: "group-stock-edit-is-independent",
2276
+ label: "Editing one group member's stock does NOT change any other member's stock",
2277
+ description: "Open a group parent's editor, note two children's stock figures, change ONLY one of them in the shared stock editor, save, reload. The edited child shows the new figure; the other child is UNCHANGED. This is the premise the whole feature rests on — a shared pool would fail exactly this check.",
2278
+ },
2279
+ {
2280
+ key: "group-rules-save-and-reload",
2281
+ label: "Saved group rules (min/max/per-item cap/discount) survive a reload and reach the public picker",
2282
+ description: "In the group parent's editor, set min/max/per-item/discount to new values, save, reload the editor — the same values must still be there. Then open the group's public picker (as a buyer) and confirm the NEW rule text is what shows, not the old one.",
2283
+ },
2284
+ {
2285
+ key: "group-stock-partial-failure-reported",
2286
+ label: "Saving group stock for a member no longer in the group reports it, without losing the other saved rows",
2287
+ description: "Unlink a child from a group in one tab, then (without reloading) try to save that same child's stock from a second tab holding the stale member list. The response must name that row as rejected while the OTHER rows in the same save still succeed — not a blanket failure.",
2288
+ },
2289
+ ],
2290
+ },
2215
2291
  {
2216
2292
  /*
2217
2293
  * The UPDATE path. The create path has 57 cases on /store/products/new
@@ -3160,28 +3236,123 @@ const rawTesterChecklistItems = [
3160
3236
  * romanisation and a licensed Indian importer's name.
3161
3237
  */
3162
3238
  pageKey: "authenticity-checker",
3163
- pageLabel: "Authenticity checker — box codes, per-generation checks and the sign-in gate",
3239
+ pageLabel: "Authenticity checker — box codes, per-generation checks and the regional code tables",
3164
3240
  href: "/tools/authenticity",
3165
3241
  cases: [
3166
- { key: "guest-sees-reference-not-a-wall", label: "Signed out, the generation table and universal checks still render — only the lookup is gated" },
3167
- { key: "no-signin-flash-when-authenticated", label: "A signed-in user never sees 'Sign in to run a lookup' during a hard reload" },
3242
+ /* ── Guards for the near-miss work (2026-10-19) ──────────────────
3243
+ * All three protect behaviour that is ALREADY CORRECT. They exist
3244
+ * because a "did you mean" feature is precisely the change that
3245
+ * could turn an absent code into an accusation, and this tool's
3246
+ * third rule is that a false counterfeit accusation against a real
3247
+ * seller is worse than a missed one.
3248
+ */
3249
+ {
3250
+ key: "authenticity-unknown-code-is-not-fake",
3251
+ label: "An invented but well-formed box code returns NEUTRAL + inconclusive — never a counterfeit verdict",
3252
+ description: "Type a code that is well-formed (one letter, four digits) but not in any table — try Z9999. The verdict must be \"Inconclusive\", the signal must read neutral, and the text must say the code is NOT RECORDED rather than fake. Any wording that implies the item is counterfeit is the failure, and so is any verdict more severe than inconclusive. New genuine codes appear regularly, and counterfeiters use real codes anyway, so an absent code proves nothing in either direction.",
3253
+ href: "/tools/authenticity",
3254
+ },
3255
+ {
3256
+ key: "authenticity-near-miss-suggests",
3257
+ label: "A one-character-off code offers \"did you mean …?\" as a SUGGESTION, with no change to the verdict",
3258
+ description: "BEFORE: a transposed code said only \"not in either table\". AFTER: enter A0831 when A0813 is a known code — you get a neutral \"did you mean A0813?\" line. It must sit OUTSIDE the \"What I checked\" list, must not appear as a signal, must not fire any evasion or counterfeit warning, and the verdict must stay exactly what it was without the suggestion. A near-miss is ambiguous between a typo and a batch mimicking a real code; it is input correction, never evidence.",
3259
+ href: "/tools/authenticity",
3260
+ },
3261
+ {
3262
+ key: "authenticity-inconclusive-asks-for-more",
3263
+ label: "Code-only input prompts for the model name and offers the ask-the-seller next step",
3264
+ description: "Enter a code and a generation but NO model name. The inconclusive result must now say that supplying the name lets it cross-check name against code, and must offer asking the seller for a photo of the box bottom and the screws. BEFORE: you got the physical checklist and no indication that a second field would unlock a real check.",
3265
+ href: "/tools/authenticity",
3266
+ },
3267
+ {
3268
+ key: "authenticity-evasion-warning-survives",
3269
+ label: "Typing \"Pegasis\" still reaches the tool's own verdict with the evasion warning intact",
3270
+ description: "SECURITY-SHAPED, and it exists because of a specific hazard. The site search matcher deliberately treats \"Pegasis\" as a match for \"Storm Pegasus\" — its alias table is built FROM a counterfeit seller's spellings. This tool treats that same spelling as EVIDENCE of trademark evasion. Type Pegasis here: the fuzzy/evasion signal must still fire. If a name typeahead is offered and you pick a canonical name from it, the tool must STILL analyse what you typed — a case asserting only \"the typeahead found it\" would pass against exactly the bug this guards.",
3271
+ href: "/tools/authenticity",
3272
+ },
3273
+ {
3274
+ key: "authenticity-name-typeahead",
3275
+ label: "Typing a partial model name offers canonical releases, and picking one does NOT change what gets checked",
3276
+ description: "BEFORE: you had to already know the exact release name. AFTER: type a partial name (e.g. \"Drago\") and canonical releases appear beneath the field as suggestions. Click one — a confirmation line appears (\"We have a record close to this: …\"), but the TEXT YOU TYPED in the field must stay exactly as you typed it. Submit the check and confirm the result is consistent with what you typed, not with the suggestion you clicked.",
3277
+ href: "/tools/authenticity",
3278
+ },
3279
+ {
3280
+ key: "authenticity-box-code-still-instant",
3281
+ label: "A box date code (e.g. A0813) resolves instantly with no network request — it is NOT in the search index",
3282
+ description: "GUARD CASE. Open DevTools -> Network, clear it, and submit a known box date code. No fetch to the search artifact or any search endpoint should appear — the date-code table is bundled locally and always was. This case exists because the site-wide index work added a name typeahead to this same page, and the two must stay on separate code paths.",
3283
+ href: "/tools/authenticity",
3284
+ },
3285
+ { key: "guest-sees-reference-not-a-wall", label: "Signed out, the LOOKUP ITSELF runs — no sign-in card anywhere on the page" },
3286
+ { key: "no-signin-flash-when-authenticated", label: "No session placeholder and no sign-in card during a hard reload, signed in or out" },
3168
3287
  { key: "midfake-code-reads-as-batch-not-accusation", label: "A matched midfake code reports a batch, never a counterfeit accusation against the seller" },
3169
3288
  { key: "date-code-shows-both-readings", label: "A box date code shows BOTH circulating readings and says neither is official" },
3170
3289
  { key: "real-product-code-is-not-evidence", label: "A well-formed product code is marked neutral and states that real codes appear on fakes" },
3171
- { key: "name-code-mismatch-asks-rather-than-accuses", label: "A name/code mismatch is a question, and ticking Random Booster re-reads it as expected" },
3290
+ { key: "name-code-mismatch-asks-rather-than-accuses", label: "A name/code mismatch is a question, and answering 'How it was sold' re-reads it as expected" },
3172
3291
  { key: "ambiguous-name-asks-which-model", label: "An ambiguous name asks which release instead of confidently naming one" },
3173
3292
  { key: "takara-spelling-is-not-flagged", label: "'Storm Pegasis' (the Takara Tomy spelling) is not flagged, while 'Scythee' is" },
3174
3293
  { key: "funskool-is-not-a-fake-signal", label: "Funskool reads as a licensed importer; Rapidity reads as an unlicensed producer" },
3175
3294
  { key: "unknown-code-is-not-called-fake", label: "An unrecorded code says 'not recorded' and explicitly not 'fake'" },
3176
3295
  { key: "asks-generation-when-nothing-inferable", label: "With no generation inferable the tool asks instead of returning a verdict" },
3177
- { key: "api-refuses-signed-out", label: "POST /api/tools/authenticity returns 401/403 signed out and leaks no assessment" },
3178
- { key: "json-ld-declares-the-gate", label: "The WebApplication JSON-LD declares the gated region, and its cssSelector matches a real element" },
3296
+ { key: "api-refuses-signed-out", label: "POST /api/tools/authenticity answers a signed-out caller, and still 400s an undeclared key" },
3297
+ { key: "json-ld-declares-no-false-gate", label: "The WebApplication JSON-LD declares NO paywalled region, now that there is none" },
3298
+ { key: "defaults-to-metal-fight", label: "Generation opens on Metal Fight, and a Burst code against it raises a mismatch signal rather than passing silently" },
3299
+ { key: "contested-code-reports-the-disagreement", label: "A code two sources disagree about reports the disagreement instead of picking a side" },
3300
+ { key: "font-midfake-does-not-accuse", label: "A midfake row whose own source calls the code authentic does NOT reach 'likely reproduction'" },
3301
+ { key: "regional-code-reads-as-regional", label: "A Korean or Middle-East code names its region instead of coming back unrecorded" },
3302
+ { key: "series-window-names-the-production-run", label: "A derivable date names its Metal-series production window, or names none outside all four" },
3303
+ { key: "temporal-context-for-an-unmatched-code", label: "An unmatched code reports what else the tables record from the same month, framed as context not a match" },
3304
+ ],
3305
+ },
3306
+ {
3307
+ pageKey: "price-index-tool",
3308
+ pageLabel: "What it sold for — realised prices",
3309
+ href: "/tools/price-index",
3310
+ cases: [
3311
+ {
3312
+ key: "price-index-resolves-locally",
3313
+ label: "Candidate names appear with no network delay while typing; only the bands themselves come from the server",
3314
+ description: "BEFORE: resolving a free-text query to a category leaf was a Firestore searchTxt query on every lookup. AFTER: open /tools/price-index, open DevTools -> Network, clear the log, and search a real model name. The suggestion/candidate step must feel instant and correspond to the local search artifact (a .json fetched once, not per search); only ONE further request — to the bands endpoint — should fire, carrying just the resolved id(s), and it must return real min/median/mean/max figures for a model with recorded sales.",
3315
+ href: "/tools/price-index",
3316
+ },
3317
+ {
3318
+ key: "price-index-falls-back-when-index-off",
3319
+ label: "Price lookup still works end-to-end if the search index is unavailable",
3320
+ description: "GUARD CASE for the fallback path. If the admin search-index kill switch is off (or the artifact fails to load), the price lookup must still return real results via the original query-based endpoint — the feature must not go blank just because the faster path is unavailable.",
3321
+ href: "/tools/price-index",
3322
+ needsReview: true,
3323
+ reviewNote: "Needs the kill switch flipped off to observe, which an ordinary run does not do — flagged for a human or admin-tooling pass.",
3324
+ },
3325
+ ],
3326
+ },
3327
+ {
3328
+ pageKey: "compare-overlay-add-item",
3329
+ pageLabel: "Compare overlay — add another item",
3330
+ href: "/products",
3331
+ cases: [
3332
+ {
3333
+ key: "compare-add-another-via-typeahead",
3334
+ label: "The compare overlay's \"Add another\" typeahead adds a real item without leaving the page",
3335
+ description: "BEFORE: the compare overlay only ever showed items selected via listing-page checkboxes before opening it — there was no way to add a further item once the overlay was open. AFTER: open the compare overlay with at least one item selected, find the 'Add another to compare' box, type a product name, and pick a suggestion. The picked item's full column (image, price, specs) appears in the overlay without closing it or returning to the listing grid.",
3336
+ href: "/products",
3337
+ },
3338
+ {
3339
+ key: "compare-add-hidden-at-column-limit",
3340
+ label: "The \"Add another\" box disappears once the compare overlay is full",
3341
+ description: "Add items via checkboxes (or the typeahead) until the overlay holds its maximum number of columns. The 'Add another' box must then be absent entirely — not merely disabled — since a picker that cannot add anything is worse than none.",
3342
+ href: "/products",
3343
+ },
3179
3344
  ],
3180
3345
  },
3181
3346
  {
3182
3347
  /*
3183
- * D12 — the parts database, the composition panel, and the part line in a
3184
- * seller's auto-filled description.
3348
+ * D12 — the Beyblade parts gallery, the composition panel, and the part
3349
+ * line in a seller's auto-filled description.
3350
+ *
3351
+ * 🛑 `pageKey` stays `parts-database` even though the surface is now
3352
+ * called the Beyblade parts gallery. It keys all nine case ids, every
3353
+ * `testerChecklistResponses` row already recorded against them, and
3354
+ * `audit-tester-checklist-hrefs`. A label is a label; a key is an
3355
+ * identifier.
3185
3356
  *
3186
3357
  * 🛑 Four defects this feature has already produced, every one of them a
3187
3358
  * 200 with plausible content: a read against a field the collection does
@@ -3192,7 +3363,7 @@ const rawTesterChecklistItems = [
3192
3363
  * ABSENT panel — indistinguishable from a model we have no data for.
3193
3364
  */
3194
3365
  pageKey: "parts-database",
3195
- pageLabel: "Parts database, composition panel and the part line in a description",
3366
+ pageLabel: "Beyblade parts gallery, composition panel and the part line in a description",
3196
3367
  href: "/parts",
3197
3368
  cases: [
3198
3369
  { key: "index-lists-every-classification", label: "The parts index lists all 20 classifications, labelled as names and not as URL slugs" },
@@ -3217,30 +3388,33 @@ const rawTesterChecklistItems = [
3217
3388
  *
3218
3389
  * 1. The index rendered all 1,185 rows flat. Nothing errored; it just
3219
3390
  * buried the six roots under 752 part leaves.
3220
- * 2. The Parts tab first matched 772 rows — it also caught the 20
3221
- * classification NODES, whose own parentIds pass through a
3222
- * `*-parts` node. A tab mixing "Attack Rings" with the attack rings
3223
- * inside it looks entirely reasonable.
3224
- * 3. Seven franchises have NO stock. Seeded active they would each be
3391
+ * 2. Seven franchises have NO stock. Seeded active they would each be
3225
3392
  * a card opening an empty shop; omitted entirely, seventeen
3226
3393
  * CharacterHotspot links stay 404. Both failure modes look fine
3227
3394
  * from one page.
3228
- * 4. Every bundle carries a placeholder cover image, so a collage
3395
+ * 3. Every bundle carries a placeholder cover image, so a collage
3229
3396
  * checked AFTER the cover would never render — and a card showing
3230
3397
  * one grey tile is not visibly broken.
3398
+ *
3399
+ * 🛑 There was a fifth tab, Parts, removed 2026-10-20 — parts belong to
3400
+ * `/parts` and the spares listing type, not to a flat tab over 752
3401
+ * leaves. Its own case went with it. The product-detail Parts
3402
+ * COMPOSITION tab is a different surface and still has cases, on the
3403
+ * `parts-database` page above.
3231
3404
  */
3232
3405
  pageKey: "category-tabs-franchise-bundles",
3233
3406
  pageLabel: "Categories index tabs, the franchise unit and the bundle collage",
3234
3407
  href: "/categories",
3235
3408
  cases: [
3236
3409
  { key: "index-shows-roots-only", label: "The Categories tab lists the six roots and NO deeper category" },
3237
- { key: "five-tabs-no-all", label: "There are exactly five tabs and none of them is 'All'" },
3238
- { key: "parts-tab-is-leaves-only", label: "The Parts tab lists part leaves and NOT the 20 classification nodes" },
3410
+ { key: "four-tabs-no-all-no-parts", label: "There are exactly four tabs — no 'All', and no 'Parts'" },
3239
3411
  { key: "tabs-do-not-overlap", label: "No single row appears on two tabs" },
3240
3412
  { key: "franchise-index-hides-unstocked", label: "The franchise index shows only stocked franchises, not all eight" },
3241
3413
  { key: "unstocked-franchise-page-resolves", label: "An unstocked franchise page loads and says so, rather than 404ing or showing an empty grid" },
3242
3414
  { key: "franchise-detail-lists-its-products", label: "The Beyblade franchise page lists its listings, with a nonsense slug as the control" },
3243
3415
  { key: "bundle-card-shows-a-collage", label: "A bundle card shows a collage of its members, not its placeholder cover" },
3416
+ { key: "tabs-collapse-on-narrow-viewport", label: "On a phone-width viewport the four tabs collapse into a searchable dropdown, not a row running off-screen" },
3417
+ { key: "tabs-hover-changes-ink", label: "On desktop, hovering an inactive tab visibly changes its text colour" },
3244
3418
  ],
3245
3419
  },
3246
3420
  {
@@ -3450,7 +3624,7 @@ const rawTesterChecklistItems = [
3450
3624
  {
3451
3625
  key: "search-typeahead-differs",
3452
3626
  label: "Typing \"dranzer\" and then \"dragoon\" in the header search gives DIFFERENT suggestions",
3453
- description: "WAS: the same 5 newest rows appeared for every keystroke, because no search term reached the query at all. If both terms give an identical list, the term is still being ignored.",
3627
+ description: "WAS: the same 5 newest rows appeared for every keystroke, because no search term reached the query at all. If both terms give an identical list, the term is still being ignored. UPDATED 2026-10-19: suggestions now come from the local index and span all 21 content types rather than the four the old endpoint could produce — so expect categories, parts and FAQs among the rows, not only products. ALSO CHECK, in DevTools -> Network: after the first index download there must be NO request per keystroke.",
3454
3628
  },
3455
3629
  {
3456
3630
  key: "search-typeahead-no-drafts",
@@ -3565,6 +3739,138 @@ const rawTesterChecklistItems = [
3565
3739
  description: "Write this one carefully. Every other case here passes on freshly seeded data while real production documents stay invisible, because search tokens are only written when a record is saved. Pick a listing that has NOT been edited recently and confirm it is findable; if it is not, the backfill did not cover it.",
3566
3740
  href: "/products",
3567
3741
  },
3742
+ /* ── The site-wide index (2026-10-19) ─────────────────────────────
3743
+ * Every case below is written as an explicit BEFORE -> AFTER,
3744
+ * because almost all of them describe behaviour that previously
3745
+ * returned HTTP 200 with plausible rows. A case reading "check
3746
+ * search works" passes AGAINST the bug it is meant to catch.
3747
+ */
3748
+ {
3749
+ key: "search-finds-every-type",
3750
+ label: "Searching \"dranzer\" from the header lands on /search and returns hits in SEVERAL sections",
3751
+ description: "BEFORE: the header sent you to /products?q=dranzer — one browse page — so you never learned there is also a category, a part, a blog post and an FAQ about it. AFTER: you land on /search and see grouped results across several sections. CONTROL, and it is the only assertion here that cannot pass by accident: search \"zzzznope\" and the page must say nothing matched, with ZERO result rows.",
3752
+ href: "/search",
3753
+ },
3754
+ {
3755
+ key: "search-finds-a-part",
3756
+ label: "Searching \"Rubber Flat\" and then \"RF\" both find the part",
3757
+ description: "BEFORE: zero results for either — all 752 parts had no search tokens at all, so they were reachable only by drilling /parts -> classification -> code. AFTER: both queries return the part, and the row shows its classification underneath the name.",
3758
+ href: "/search",
3759
+ },
3760
+ {
3761
+ key: "search-ambiguous-part-code",
3762
+ label: "Searching \"105\" returns SEVERAL parts, each showing a DIFFERENT classification",
3763
+ description: "BEFORE: there was no way to search parts at all. AFTER: \"105\" is deliberately ambiguous — it is a Metal Fight spin-track height, while a bare number in Burst is a disc — so the right answer is several rows, each naming its own classification and each linking to a different two-segment /parts/<classification>/<code> page. ONE row is the failure: it means something picked a winner.",
3764
+ href: "/search",
3765
+ },
3766
+ {
3767
+ key: "search-midword",
3768
+ label: "Searching \"ariza\" finds Charizard",
3769
+ description: "BEFORE: impossible. Firestore token search is word-PREFIX only, so a mid-word fragment matched nothing. AFTER: it matches, because the browser scans the whole index. CONTROL: \"zzzz\" must still return nothing — a matcher that returns everything for any fragment would also pass the first half.",
3770
+ href: "/search",
3771
+ },
3772
+ {
3773
+ key: "search-typo",
3774
+ label: "Searching \"dragon g\" finds Dragoon G, and \"Pegasis\" finds Storm Pegasus",
3775
+ description: "Misspellings are expanded at index time from a crawl of a counterfeit seller's real titles, on the basis that an evasion spelling is the same string a buyer mistypes. BEFORE: neither matched. AFTER: both do.",
3776
+ href: "/search",
3777
+ },
3778
+ {
3779
+ key: "search-normalised-away",
3780
+ label: "Searching \"!!!\" shows the \"no searchable words\" state — NOT every listing",
3781
+ description: "BEFORE (the inverse bug, on /api/products): a punctuation-only query produced no filter clause, so it returned the ENTIRE published catalogue as search results. AFTER: the page says the query had no searchable words in it. Note this is a different message from \"nothing matched\" and both must be distinguishable.",
3782
+ href: "/search",
3783
+ },
3784
+ {
3785
+ key: "search-zero-reads",
3786
+ label: "After the first search, typing produces NO further network requests",
3787
+ description: "Open DevTools -> Network, signed out. Type a query: you should see ONE fetch of the index artifact (a .json from firebasestorage, a few hundred KB) and nothing per keystroke thereafter. BEFORE: every keystroke hit /api/search/suggestions. A request per character is the failure even though the results look identical.",
3788
+ href: "/search",
3789
+ },
3790
+ {
3791
+ key: "search-no-prices",
3792
+ label: "No rupee amount appears anywhere in the header suggestions or /search results, signed out",
3793
+ description: "BEFORE: the header dropdown printed the product price as the suggestion subtitle, from a route cached PUBLIC for 60s — a live guest-price leak on every page. AFTER: suggestions show the brand and category instead, and no ₹ appears anywhere for a signed-out visitor.",
3794
+ href: "/search",
3795
+ },
3796
+ {
3797
+ key: "search-no-sandbox",
3798
+ label: "No tester-sandbox row appears in search results for a signed-out visitor",
3799
+ description: "The index is ONE shared file every browser downloads, with no per-viewer filter anywhere after it is built — so a sandbox row in it is published to the world for that version's lifetime. Search \"sandbox\" and \"tester\" signed out; nothing seeded as test data may appear.",
3800
+ href: "/search",
3801
+ },
3802
+ {
3803
+ key: "search-freshness",
3804
+ label: "A newly published listing becomes findable within ~15 minutes, and unpublishing removes it",
3805
+ description: "Publish a listing, note the time, and search its exact title. It should appear within about 15 minutes. Then unpublish it and search again — it must disappear within the same window. BEFORE: there was no index to go stale. A row that never appears, or never leaves, both mean the rebuild stopped.",
3806
+ href: "/search",
3807
+ },
3808
+ {
3809
+ key: "search-facet-counts",
3810
+ label: "Facet chips on /search show counts that match the rows actually displayed, and ticking one narrows BOTH",
3811
+ description: "Search a broad term. The rail shows chips like Type/Brand/Condition with a number on each. Tick one: the results narrow AND the other chips' numbers drop to match. A chip whose count does not change the result set, or a count that disagrees with the rows you can see, is the failure.",
3812
+ href: "/search",
3813
+ },
3814
+ {
3815
+ key: "search-guest-no-private",
3816
+ label: "Signed out, /search shows no account sections and makes ZERO authenticated requests",
3817
+ description: "SECURITY. In a private window with no session, search a broad term and watch DevTools -> Network. There must be no request to /api/search/scoped at all — not a 401, not an empty 200. An authenticated request firing for a guest means a session the page did not expect.",
3818
+ href: "/search",
3819
+ },
3820
+ {
3821
+ key: "search-buyer-isolation",
3822
+ label: "Buyer A cannot find Buyer B's order — by the UI AND by calling the API directly",
3823
+ description: "SECURITY, and the API half is the half that matters. Signed in as one buyer, search a term from another buyer's order. Then call /api/search/scoped?q=<term> directly in the same session and read the JSON. Neither may contain the other buyer's row. A UI that hides it while the API returns it is the failure this case exists for.",
3824
+ href: "/search",
3825
+ },
3826
+ {
3827
+ key: "search-seller-own-store",
3828
+ label: "A seller finds their own DRAFT listing; another seller's draft is absent",
3829
+ description: "Signed in as a seller, search a draft listing's title. It appears under \"Your listings (incl. drafts)\" — that section exists precisely because published listings are deduped away into the public results, so what is left is your drafts. Then confirm a different store's draft title returns nothing.",
3830
+ href: "/search",
3831
+ },
3832
+ {
3833
+ key: "search-listing-boxes-still-filter",
3834
+ label: "The per-collection search boxes still filter — one public, one seller, one admin",
3835
+ description: "REGRESSION GUARD. The box on /stores, the one on /store/orders and the one on /admin/users are a DIFFERENT system from site-wide search and must be untouched. For each: a real term narrows the list, a nonsense term returns zero, pagination still works, and the filter-drawer badge still counts correctly.",
3836
+ href: "/stores",
3837
+ },
3838
+ {
3839
+ key: "search-support-ticket-two-words",
3840
+ label: "A TWO-WORD support-ticket search narrows to tickets matching BOTH words",
3841
+ description: "BEFORE: the second word was discarded entirely, so the search returned everything matching the longer word and looked like it was working. AFTER: search two words you know appear in one ticket's subject but not together in others — only the matching ticket should remain.",
3842
+ href: "/admin/support-tickets",
3843
+ },
3844
+ {
3845
+ key: "search-parts-page-has-a-box",
3846
+ label: "/parts has a search box that finds a part by name or by code",
3847
+ description: "BEFORE: /parts was browse-only — 752 parts reachable only by clicking a classification then a code. AFTER: there is a search input above the classification grid. Type \"RF\" and the part appears with its classification; type \"zzzznope\" and it says nothing matches. The classification grid must still be there below it.",
3848
+ href: "/parts",
3849
+ },
3850
+ {
3851
+ key: "search-facet-carries-through",
3852
+ label: "\"See all →\" on a group with a facet ticked lands on the browse page WITH that facet still applied",
3853
+ description: "Search a broad term and tick a facet chip (e.g. a brand). Click the group's own \"See all →\" link. BEFORE: the link always went to the plain, unfiltered browse page, so refining on /search and then following through threw the refinement away. AFTER: the destination URL carries the same filter (e.g. ?f=brand==Takara-Tomy) and the browse page opens already narrowed.",
3854
+ href: "/search",
3855
+ },
3856
+ {
3857
+ key: "search-admin-person-exact",
3858
+ label: "Admin can find a user by typing just their FIRST name — but a prefix of it finds nothing",
3859
+ description: "Signed in as admin, open /admin/users and type a real user's first name only (not their full \"First Last\"). BEFORE: the search was an exact match on the WHOLE displayName string, so typing only the first name found nobody. AFTER: it is a whole-WORD match and finds them. Then type only the first 3-4 letters of that name (a prefix, not the whole word) — this must find NOTHING. That second half is the design, not a bug: the index is whole-word only so it cannot be unpicked by counting partial-match frequencies.",
3860
+ href: "/admin/users",
3861
+ },
3862
+ {
3863
+ key: "search-no-plaintext-pii",
3864
+ label: "The stored search-index values are HMAC digests, never a readable name",
3865
+ description: "For an admin or engineer with Firestore console access: open a `users` document and find `nameTokensIndex`. Every entry must be an opaque hex digest (prefixed `hmac:`), never the person's actual name or email in any form. Also confirm no such field exists on, or is reachable from, the public search artifact (the .json file fetched by a signed-out visitor) — a deterministic per-person identifier published to every browser is a disclosure even though it cannot be reversed.",
3866
+ href: "/admin/users",
3867
+ },
3868
+ {
3869
+ key: "faq-punctuation-search",
3870
+ label: "Searching \"!!!\" in the FAQ box shows the normalised-away state, not every FAQ",
3871
+ description: "GUARD CASE — this behaviour is already correct and this case exists to pin it so it cannot regress while the site-wide index work is in motion elsewhere. Open /faqs and search \"!!!\". It must say the search had no searchable words, and show zero FAQs — not the entire FAQ list.",
3872
+ href: "/faqs",
3873
+ },
3568
3874
  ],
3569
3875
  },
3570
3876
  {
@@ -6756,6 +7062,24 @@ const rawTesterChecklistItems = [
6756
7062
  pageLabel: "Header search",
6757
7063
  href: "/",
6758
7064
  cases: [
7065
+ {
7066
+ key: "header-search-submit-lands-on-search",
7067
+ label: "Submitting the header search lands on /search with GROUPED results — not on one browse page",
7068
+ description: "BEFORE: submitting sent you to the browse route for whichever type was selected in the dropdown — /products?q=dranzer — so a cross-resource search was unreachable and the /search page it should have used had no users at all. AFTER: you land on /search?q=dranzer and see results grouped by type, each group with its own \"See all … →\" link pointing at that browse page. The per-type destinations are not lost; they moved to where they belong.",
7069
+ href: "/",
7070
+ },
7071
+ {
7072
+ key: "header-search-type-is-a-filter",
7073
+ label: "The type dropdown NARROWS the results — it no longer decides where you land",
7074
+ description: "BEFORE: choosing \"Stores\" meant submitting took you to /stores. AFTER: choosing \"Stores\" narrows what you see, and the Stores group's own \"See all stores →\" link is what takes you to /stores. Also confirm an EMPTY query does nothing at all on submit — landing on a bare browse page you did not ask for is worse than staying put.",
7075
+ href: "/",
7076
+ },
7077
+ {
7078
+ key: "header-search-no-price-for-guest",
7079
+ label: "Signed out, no suggestion row shows a price",
7080
+ description: "BEFORE: the suggestion subtitle was the product's price, served from a route cached PUBLIC for 60 seconds — so every signed-out visitor saw prices in the header on every page. AFTER: the subtitle is the brand and category. Any ₹ in the dropdown while signed out is a FAIL.",
7081
+ href: "/",
7082
+ },
6759
7083
  {
6760
7084
  key: "search-finds-maintenance-toggle",
6761
7085
  label: "Searching 'maintenance' reaches the maintenance TOGGLE, not just Site Settings",
@@ -6880,6 +7204,36 @@ const rawTesterChecklistItems = [
6880
7204
  { key: "storage-meter-honest", label: "The storage meter reports object COUNT beside bytes, and says when it truncated" },
6881
7205
  ],
6882
7206
  },
7207
+ {
7208
+ /*
7209
+ * Part Two D — promoting a traced, accepted, promoted-to-full-res
7210
+ * sheet into real marketplace listings. Distinct from the tracer's
7211
+ * own "promote" (run -> sheet, full-res pixels); this is sheet ->
7212
+ * marketplace (real products). Needs a real traced sheet to reach, so
7213
+ * these cases follow the tracer's own happy path rather than
7214
+ * standing alone.
7215
+ */
7216
+ pageKey: "sticker-promote-to-listings",
7217
+ pageLabel: "Promoting a sheet to real listings",
7218
+ href: "/tools/sticker-tracer",
7219
+ cases: [
7220
+ {
7221
+ key: "promote-creates-one-group",
7222
+ label: "Promoting a sheet creates one parent listing plus one child per UNIQUE part, all in one group",
7223
+ description: "On a sheet with at least one part marked a duplicate of another, promote it to listings. The group must contain the parent plus exactly the UNIQUE parts — a duplicate part must NOT get its own listing — and the picker on the parent's product page must show every member, not just one (this is exactly the `linkChildToGroup` stale-append bug: linking N children in a loop used to leave only one).",
7224
+ },
7225
+ {
7226
+ key: "promoted-part-image-watermarked",
7227
+ label: "A promoted part's marketplace image is watermarked; the admin's original download is NOT",
7228
+ description: "Open the promoted CHILD listing's public page and note its image carries a visible watermark. Then, from the tracer's own UI, download that same part's original — it must be clean, with no watermark. Same pixels, same object, two different responses: that is the point.",
7229
+ },
7230
+ {
7231
+ key: "promote-refuses-before-full-res-render",
7232
+ label: "Promoting a sheet that has not been accepted-and-rendered is refused, naming the fix",
7233
+ description: "Attempt to promote a sheet whose active run has not had \"Accept & render\" run on it (or try on a fresh trace with no promoted run). The action must refuse with a message naming the required step (accept/render the run first) — not create partial listings.",
7234
+ },
7235
+ ],
7236
+ },
6883
7237
  ], { adminOnly: true }),
6884
7238
  ];
6885
7239
  const defaultPhases = assignDefaultPhases(rawTesterChecklistItems);