@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,73 @@
1
+ /*
2
+ * WHY: Authored six-part procedures for the two Wave-11 adoptions —
3
+ * price-index local resolution and the compare overlay's new
4
+ * "add another" typeahead.
5
+ * WHAT: 4 case(s), keyed by full checklist id.
6
+ *
7
+ * @tag domain:tester
8
+ * @tag layer:seed
9
+ * @tag pattern:none
10
+ * @tag access:server-only
11
+ * @tag consumers:seed-data/authored/index.ts
12
+ * @tag sideEffects:none
13
+ */
14
+ export const authored = {
15
+ "checklist-content-discovery-price-index-tool-price-index-resolves-locally": {
16
+ roles: ["guest"],
17
+ startPage: "/tools/price-index",
18
+ steps: [
19
+ "Open /tools/price-index in a private window and open DevTools → Network.",
20
+ "Clear the network log.",
21
+ "Type a real model name into the search box and submit.",
22
+ "Read the network log for what fired.",
23
+ "Read the results on the page.",
24
+ ],
25
+ inputs: { query: "Dragoon G" },
26
+ expectedBehaviour: "The query-to-leaf resolution step runs in the browser against the local search artifact, at zero Firestore reads — before this, it was a searchTxt push-down query on every submit. The server is now asked only for the sale bands of the already-resolved id(s).",
27
+ expectedUiState: "The network log shows the one-time artifact fetch (a .json, if not already cached from an earlier visit) plus exactly one request to a bands endpoint carrying resolved id(s) — not a raw free-text query parameter. The page shows real min/median/mean/max figures for a model with recorded sales.",
28
+ endResult: "Read-only; nothing persists.",
29
+ },
30
+ "checklist-content-discovery-price-index-tool-price-index-falls-back-when-index-off": {
31
+ roles: ["admin"],
32
+ startPage: "/tools/price-index",
33
+ steps: [
34
+ "Sign in as admin and flip siteSettings.search.indexEnabled to false (via Firestore console or an admin settings surface, if one exists for it).",
35
+ "Open /tools/price-index in a private window.",
36
+ "Search a real model name and submit.",
37
+ "Read the results.",
38
+ "Restore the flag to true afterwards.",
39
+ ],
40
+ needsReview: true,
41
+ reviewNote: "Needs the kill switch flipped off to observe the fallback path, which an ordinary run never exercises — flagged for a human or an admin-tooling pass.",
42
+ expectedBehaviour: "With the index unavailable, the tool must fall back to the original query-based endpoint rather than going blank. The fallback pays a real Firestore query, which is the accepted cost of the flag being off.",
43
+ expectedUiState: "The same real sale figures appear as when the index is on — slower, possibly, but not absent.",
44
+ endResult: "Restore siteSettings.search.indexEnabled to true before ending the session.",
45
+ },
46
+ "checklist-content-discovery-compare-overlay-add-item-compare-add-another-via-typeahead": {
47
+ roles: ["guest"],
48
+ startPage: "/products",
49
+ steps: [
50
+ "Open /products and select one item for comparison (checkbox).",
51
+ "Open the compare overlay.",
52
+ "Find the 'Add another to compare' box inside the overlay.",
53
+ "Type a different product's name and read the suggestions.",
54
+ "Click one.",
55
+ "Read the overlay's columns.",
56
+ ],
57
+ expectedBehaviour: "Before this, the only way to add an item was selecting it via a listing-page checkbox BEFORE opening the overlay — there was no way to add a further item once inside it. The typeahead resolves a slug locally and the overlay fetches that product the same way it already fetches checkbox-selected ones.",
58
+ expectedUiState: "A new column appears for the picked item — its image, price and specs — without closing the overlay or returning to the grid. The originally-selected item's column is still there.",
59
+ endResult: "Close the overlay; nothing persists.",
60
+ },
61
+ "checklist-content-discovery-compare-overlay-add-item-compare-add-hidden-at-column-limit": {
62
+ roles: ["guest"],
63
+ startPage: "/products",
64
+ steps: [
65
+ "Open /products and select items for comparison until the compare overlay's maximum column count is reached (checkboxes, then the typeahead if room remains).",
66
+ "Open or stay in the overlay once full.",
67
+ "Look for the 'Add another to compare' box.",
68
+ ],
69
+ expectedBehaviour: "A picker that cannot actually add anything because the overlay is full is worse than an absent one — it invites a click that does nothing.",
70
+ expectedUiState: "The 'Add another' box is completely ABSENT once the column limit is reached, not merely disabled or greyed out.",
71
+ endResult: "Close the overlay; nothing persists.",
72
+ },
73
+ };
@@ -0,0 +1,2 @@
1
+ import type { AuthoredCase } from "./_types";
2
+ export declare const authored: Record<string, AuthoredCase>;
@@ -0,0 +1,340 @@
1
+ /*
2
+ * WHY: Authored six-part procedures for the site-wide search index cases
3
+ * added 2026-10-19 to the content-discovery/search page.
4
+ * WHAT: 17 case(s), keyed by full checklist id.
5
+ *
6
+ * 🛑 EVERY CASE CARRIES A CONTROL. A search box that has stopped filtering
7
+ * returns plausible rows for any real term and looks identical to one that
8
+ * works — "zzzznope" returning zero is the only kind of assertion here that
9
+ * cannot pass by accident. That is this directory's standing rule and it is
10
+ * why the control is a STEP, not a footnote.
11
+ *
12
+ * 🛑 Several of these check the NETWORK PANEL rather than the page, and that
13
+ * is deliberate. "Zero Firestore reads per keystroke" and "a guest issues no
14
+ * authenticated request" are the two claims the whole design rests on, and
15
+ * neither is visible on screen — a page that fetches per keystroke looks
16
+ * exactly like one that does not.
17
+ *
18
+ * @tag domain:tester
19
+ * @tag layer:seed
20
+ * @tag pattern:none
21
+ * @tag access:server-only
22
+ * @tag consumers:seed-data/authored/index.ts
23
+ * @tag sideEffects:none
24
+ */
25
+ export const authored = {
26
+ "checklist-content-discovery-search-search-finds-every-type": {
27
+ roles: ["guest"],
28
+ startPage: "/",
29
+ steps: [
30
+ "Open / in a private window with no session.",
31
+ "Click the header search and type dranzer.",
32
+ "Press Enter.",
33
+ "Read the URL in the address bar.",
34
+ "Count how many distinct section headings the results page shows.",
35
+ "Go back, type zzzznope, and press Enter.",
36
+ "Read what the page says.",
37
+ ],
38
+ inputs: { query: "dranzer", control: "zzzznope" },
39
+ expectedBehaviour: "Submitting navigates to the cross-resource results page and searches every indexed type at once, rather than redirecting to one browse route chosen by the type dropdown.",
40
+ expectedUiState: "The URL is /search?q=dranzer, not /products?q=dranzer. Results appear under several headings (for example Products, Categories, Parts, Articles), each with its own 'See all … →' link. For zzzznope the page says nothing matched and shows ZERO result rows — that control is the only assertion here that cannot pass by accident.",
41
+ endResult: "Read-only; nothing persists.",
42
+ },
43
+ "checklist-content-discovery-search-search-finds-a-part": {
44
+ roles: ["guest"],
45
+ startPage: "/search",
46
+ steps: [
47
+ "Open /search?q=Rubber Flat in a private window.",
48
+ "Read every result row and note which section each sits under.",
49
+ "Change the query to RF and search again.",
50
+ "Compare the two result sets.",
51
+ "Click the part row and read the page it lands on.",
52
+ ],
53
+ inputs: { byName: "Rubber Flat", byCode: "RF" },
54
+ expectedBehaviour: "Parts are searchable by full name and by code. Before this they had no search tokens at all — all 752 were reachable only by drilling /parts into a classification and then a code.",
55
+ expectedUiState: "Both queries return the part under a Parts heading, and the row shows its classification on a second line underneath the name. Clicking it lands on a real /parts/<classification>/<code> page, not a 404.",
56
+ endResult: "Read-only; nothing persists.",
57
+ },
58
+ "checklist-content-discovery-search-search-ambiguous-part-code": {
59
+ roles: ["guest"],
60
+ startPage: "/search",
61
+ steps: [
62
+ "Open /search?q=105 in a private window.",
63
+ "Count the rows under the Parts heading.",
64
+ "Read the second line of each row and write down the classification it names.",
65
+ "Click two different rows in turn and write down the URL each lands on.",
66
+ ],
67
+ inputs: { query: "105" },
68
+ expectedBehaviour: "A bare code is ambiguous across generations — 105 is a Metal Fight spin-track height while a bare number in Burst is a disc — so the engine must return all of them rather than picking one.",
69
+ expectedUiState: "SEVERAL rows, each naming a DIFFERENT classification, each linking to a different /parts/<classification>/105 URL. Exactly one row is the FAILURE: it means something chose a winner for the user.",
70
+ endResult: "Read-only; nothing persists.",
71
+ },
72
+ "checklist-content-discovery-search-search-midword": {
73
+ roles: ["guest"],
74
+ startPage: "/search",
75
+ steps: [
76
+ "Open /search?q=ariza in a private window.",
77
+ "Read the result rows.",
78
+ "Change the query to zzzz and search again.",
79
+ "Read what the page says.",
80
+ ],
81
+ inputs: { midword: "ariza", control: "zzzz" },
82
+ expectedBehaviour: "The browser scans the whole index, so a fragment from the MIDDLE of a word matches. The Firestore path structurally cannot do this — its tokens are word-prefix only.",
83
+ expectedUiState: "'ariza' returns Charizard rows. 'zzzz' returns nothing — without that control a matcher returning everything for any fragment would also pass the first half.",
84
+ endResult: "Read-only; nothing persists.",
85
+ },
86
+ "checklist-content-discovery-search-search-typo": {
87
+ roles: ["guest"],
88
+ startPage: "/search",
89
+ steps: [
90
+ "Open /search?q=dragon g in a private window.",
91
+ "Read the result rows.",
92
+ "Search again for Pegasis (note the spelling: 'sis', not 'sus').",
93
+ "Read the result rows.",
94
+ ],
95
+ inputs: { typo1: "dragon g", typo2: "Pegasis" },
96
+ expectedBehaviour: "Misspellings are expanded at INDEX time from a crawl of a counterfeit seller's real titles, on the measured insight that an evasion spelling is the same string a genuine buyer mistypes.",
97
+ expectedUiState: "'dragon g' returns Dragoon G. 'Pegasis' returns Storm Pegasus. Neither returns an empty state.",
98
+ endResult: "Read-only; nothing persists.",
99
+ },
100
+ "checklist-content-discovery-search-search-normalised-away": {
101
+ roles: ["guest"],
102
+ startPage: "/search",
103
+ steps: [
104
+ "Open /search?q=!!! in a private window.",
105
+ "Read the message on the page word for word.",
106
+ "Count the result rows.",
107
+ "Now search zzzznope and read that message too.",
108
+ "Compare the two messages.",
109
+ ],
110
+ inputs: { punctuation: "!!!", nonsense: "zzzznope" },
111
+ expectedBehaviour: "A query that normalises to no searchable words must return NOTHING, never everything. The inverse of this bug shipped on /api/products once: a punctuation-only query produced no filter clause and returned the entire published catalogue as search results.",
112
+ expectedUiState: "'!!!' says the search had no searchable words in it. 'zzzznope' says nothing matched. The two messages are DIFFERENT — one means we could not form a query, the other means we ran one and found none. Any rows at all for '!!!' is the failure.",
113
+ endResult: "Read-only; nothing persists.",
114
+ },
115
+ "checklist-content-discovery-search-search-zero-reads": {
116
+ roles: ["guest"],
117
+ startPage: "/",
118
+ steps: [
119
+ "Open / in a private window and open DevTools → Network.",
120
+ "Clear the network log.",
121
+ "Click into the header search and type d.",
122
+ "Wait for suggestions to appear, then type r, a, n, z, e, r one character at a time.",
123
+ "Read the network log and count the requests made after the first one.",
124
+ ],
125
+ inputs: { query: "dranzer" },
126
+ expectedBehaviour: "The index is fetched ONCE per page session and searched locally thereafter. Before this, every keystroke was a request to /api/search/suggestions, which was four parallel Firestore reads.",
127
+ expectedUiState: "The log shows ONE large .json fetch from firebasestorage (a few hundred KB) on the first focus or keystroke, and NOTHING further as you keep typing. A request per character is the failure even though the suggestions look identical either way.",
128
+ endResult: "Read-only; nothing persists.",
129
+ },
130
+ "checklist-content-discovery-search-search-no-prices": {
131
+ roles: ["guest"],
132
+ startPage: "/",
133
+ steps: [
134
+ "Open / in a private window with no session.",
135
+ "Click the header search and type dranzer.",
136
+ "Read every suggestion row, including the small second line.",
137
+ "Press Enter and read every row on the results page.",
138
+ "Search the rendered page for the ₹ character.",
139
+ ],
140
+ inputs: { query: "dranzer" },
141
+ expectedBehaviour: "Prices are hidden from signed-out visitors so the catalogue cannot be read at scale without an account. The suggestion subtitle used to be the product's price, served from a route cached PUBLIC for 60 seconds — a live leak on every page of the site.",
142
+ expectedUiState: "Suggestion subtitles show the brand and category, e.g. 'Takara-Tomy · Attack Rings'. No ₹ appears anywhere in the dropdown or in the results, signed out.",
143
+ endResult: "Read-only; nothing persists.",
144
+ },
145
+ "checklist-content-discovery-search-search-no-sandbox": {
146
+ roles: ["guest"],
147
+ startPage: "/search",
148
+ steps: [
149
+ "Open /search?q=sandbox in a private window with no session.",
150
+ "Read every result row.",
151
+ "Search again for tester and read every row.",
152
+ "Note any row whose title or detail names a tester or sandbox fixture.",
153
+ ],
154
+ inputs: { query1: "sandbox", query2: "tester" },
155
+ expectedBehaviour: "The index is ONE shared file every browser downloads, with no per-viewer filter anywhere after it is built — so a test-data row in it is published to the world for that version's whole lifetime, and an immutable object cannot be recalled.",
156
+ expectedUiState: "No row seeded as test data appears. Finding even one is a disclosure, not a cosmetic issue.",
157
+ endResult: "Read-only; nothing persists.",
158
+ },
159
+ "checklist-content-discovery-search-search-freshness": {
160
+ roles: ["seller"],
161
+ startPage: "/store/products/new",
162
+ steps: [
163
+ "Create and PUBLISH a listing with a distinctive title, e.g. Freshness Probe Dranzer.",
164
+ "Write down the time.",
165
+ "Open /search in a private window and search that exact title.",
166
+ "If it is absent, wait and retry every few minutes for up to 20 minutes.",
167
+ "Once it appears, go back and UNPUBLISH the listing.",
168
+ "Search the same title again, retrying for up to 20 minutes.",
169
+ ],
170
+ inputs: { title: "Freshness Probe Dranzer" },
171
+ expectedBehaviour: "A content write marks the index stale and the scheduled job rebuilds within about 15 minutes. An unpublished listing still has a fresh updatedAt, so the incremental pass re-reads it, sees it fail the published filter, and removes it.",
172
+ expectedUiState: "The listing becomes findable within ~15 minutes of publishing and disappears within ~15 minutes of unpublishing. A row that NEVER appears and a row that never LEAVES are both failures, and both mean the rebuild has stopped — note which one you saw.",
173
+ endResult: "Delete the probe listing afterwards. Its absence from search within ~15 minutes is itself the final check.",
174
+ },
175
+ "checklist-content-discovery-search-search-facet-counts": {
176
+ roles: ["guest"],
177
+ startPage: "/search",
178
+ steps: [
179
+ "Open /search?q=beyblade in a private window.",
180
+ "Find the facet chips above the results and write down each chip's label and number.",
181
+ "Count the result rows actually shown for one type and compare it with that chip's number.",
182
+ "Click one chip.",
183
+ "Read the result rows again, and read the other chips' numbers again.",
184
+ "Click the same chip a second time to clear it.",
185
+ ],
186
+ inputs: { query: "beyblade" },
187
+ expectedBehaviour: "Facet counts are computed over the hits the current query produced, in memory. The Firestore path cannot afford this — it is one aggregation query per facet value, each needing its own composite index, and it returns no count at all when an index is missing.",
188
+ expectedUiState: "Chips show a number each. Clicking one narrows the results AND lowers the other chips' numbers to match. Clicking again restores both. A chip that does not change the results, or a number that disagrees with the rows you can count, is the failure.",
189
+ endResult: "Read-only; the selection is not persisted and is gone on reload.",
190
+ },
191
+ "checklist-content-discovery-search-search-guest-no-private": {
192
+ roles: ["guest"],
193
+ startPage: "/search",
194
+ steps: [
195
+ "Open a private window with no session and open DevTools → Network.",
196
+ "Clear the network log.",
197
+ "Go to /search?q=order.",
198
+ "Read every request in the log.",
199
+ "Look specifically for any request whose path contains /api/search/scoped.",
200
+ "Read the page for any section headed 'Your …' or 'Admin'.",
201
+ ],
202
+ inputs: { query: "order" },
203
+ expectedBehaviour: "A signed-out visitor must make NO authenticated request at all — not a 401, not an empty 200. The client gate, the route's auth requirement and the empty source list are three independent gates, because the expensive failure is not a guest seeing an error, it is a guest silently receiving someone's data.",
204
+ expectedUiState: "No /api/search/scoped request appears in the log in any form. No account or admin section renders on the page. A 401 in the log is still a FAIL — it means the request was issued.",
205
+ endResult: "Read-only; nothing persists.",
206
+ },
207
+ "checklist-content-discovery-search-search-buyer-isolation": {
208
+ roles: ["buyer"],
209
+ startPage: "/search",
210
+ steps: [
211
+ "Sign in as one buyer.",
212
+ "From an admin session in another browser, find a DIFFERENT buyer's order and note a distinctive word from its product title.",
213
+ "Back in the first buyer's session, search that word on /search.",
214
+ "Read every row, including any under 'Your orders'.",
215
+ "Now open /api/search/scoped?q=<that word> directly in the same signed-in session.",
216
+ "Read the raw JSON response end to end.",
217
+ ],
218
+ expectedBehaviour: "Every scoped source carries an owner clause built from the verified session, so the QUERY itself cannot return another viewer's rows. The API half of this check is the half that matters.",
219
+ expectedUiState: "Neither the page nor the JSON contains the other buyer's order. A page that hides the row while the endpoint returns it is precisely the failure this case exists for, and only the second half can see it.",
220
+ endResult: "Read-only; nothing persists.",
221
+ },
222
+ "checklist-content-discovery-search-search-seller-own-store": {
223
+ roles: ["seller"],
224
+ startPage: "/store/products/new",
225
+ steps: [
226
+ "Signed in as a seller, create a listing with a distinctive title and leave it as a DRAFT.",
227
+ "Go to /search and search that title.",
228
+ "Read which section the row appears under.",
229
+ "Note a draft title belonging to a DIFFERENT store (ask an admin for one).",
230
+ "Search that second title and read every row.",
231
+ ],
232
+ inputs: { ownDraft: "Isolation Probe Draft" },
233
+ expectedBehaviour: "A seller's own drafts are returned by the scoped tier. Published listings are deduped away because the public index already has them, so what survives in that section is exactly the drafts — which is why it is labelled as such.",
234
+ expectedUiState: "Your own draft appears under 'Your listings (incl. drafts)'. The other store's draft returns nothing at all.",
235
+ endResult: "Delete the probe draft afterwards.",
236
+ },
237
+ "checklist-content-discovery-search-search-listing-boxes-still-filter": {
238
+ roles: ["guest", "seller", "admin"],
239
+ startPage: "/stores",
240
+ steps: [
241
+ "Open /stores and type a real store name into the page's own search box.",
242
+ "Confirm the list narrows, then type zzzznope and confirm it empties.",
243
+ "Sign in as a seller, open /store/orders, and repeat both searches there.",
244
+ "Check the pager still works and the filter-drawer badge still shows the right count.",
245
+ "Sign in as an admin, open /admin/users, and repeat both searches there.",
246
+ ],
247
+ inputs: { control: "zzzznope" },
248
+ expectedBehaviour: "The per-collection boxes are a DIFFERENT system from site-wide search — they filter one table, carry URL state and feed real pagination — and nothing in the index work touches them. This case exists because that is a promise, and a promise with no check is a hope.",
249
+ expectedUiState: "All three boxes narrow on a real term and return zero on the control. Pagination still pages, and the filter-drawer badge still counts only drawer filters.",
250
+ endResult: "Read-only; the URL carries the query but nothing persists server-side.",
251
+ },
252
+ "checklist-content-discovery-search-search-support-ticket-two-words": {
253
+ roles: ["admin"],
254
+ startPage: "/admin/support-tickets",
255
+ steps: [
256
+ "Open /admin/support-tickets and read several ticket subjects.",
257
+ "Pick TWO words that appear together in exactly one subject, where the longer word also appears in others.",
258
+ "Type just the longer word into the search box and count the rows.",
259
+ "Now type both words and count the rows again.",
260
+ "Compare the two counts.",
261
+ ],
262
+ expectedBehaviour: "Both words must narrow the result. Before this the second word was discarded entirely — only the longest term reached the query — so a two-word search returned everything matching the longer word while looking like it was working.",
263
+ expectedUiState: "The two-word search returns FEWER rows than the one-word search, and every row it returns contains both words. Identical counts mean the second word is still being thrown away.",
264
+ endResult: "Read-only; nothing persists.",
265
+ },
266
+ "checklist-content-discovery-search-search-parts-page-has-a-box": {
267
+ roles: ["guest"],
268
+ startPage: "/parts",
269
+ steps: [
270
+ "Open /parts in a private window.",
271
+ "Look above the grid of classifications for a search input.",
272
+ "Type RF and read the rows that appear.",
273
+ "Read the second line of each row.",
274
+ "Clear it and type zzzznope.",
275
+ "Scroll down and confirm the classification grid is still present.",
276
+ ],
277
+ inputs: { query: "RF", control: "zzzznope" },
278
+ expectedBehaviour: "Before this the page was browse-only: 752 parts reachable only by clicking a classification and then a code, with no search input anywhere on the surface.",
279
+ expectedUiState: "A search box sits above the grid. 'RF' returns the part with its classification on a second line; 'zzzznope' says no part matches. The classification grid is still there underneath — the box adds a way in, it does not replace browsing.",
280
+ endResult: "Read-only; nothing persists.",
281
+ },
282
+ "checklist-content-discovery-search-search-facet-carries-through": {
283
+ roles: ["guest"],
284
+ startPage: "/search",
285
+ steps: [
286
+ "Open /search?q=beyblade in a private window.",
287
+ "Tick one facet chip, e.g. a brand.",
288
+ "Find that group's own 'See all →' link and read its href before clicking.",
289
+ "Click it.",
290
+ "Read the URL you land on and whether the listing page shows the same filter applied.",
291
+ ],
292
+ inputs: { query: "beyblade" },
293
+ expectedBehaviour: "The refinement a visitor builds on /search must survive the jump to the real browse page — losing it means re-doing work they already did.",
294
+ expectedUiState: "The destination URL carries the same filter (e.g. a brand query parameter), and the browse page opens already narrowed to it, not showing the unfiltered full list.",
295
+ endResult: "Read-only; nothing persists.",
296
+ },
297
+ "checklist-content-discovery-search-search-admin-person-exact": {
298
+ roles: ["admin"],
299
+ startPage: "/admin/users",
300
+ steps: [
301
+ "Sign in as admin and open /admin/users.",
302
+ "Pick a real user and note their first name only.",
303
+ "Type just that first name into the search box.",
304
+ "Read the results.",
305
+ "Clear the box and type only the first 3-4 letters of that same first name — a prefix, not the whole word.",
306
+ "Read the results again.",
307
+ ],
308
+ expectedBehaviour: "The admin search resolves through a whole-WORD index, not a whole-STRING exact match. Typing a first name alone must find a user whose displayName is 'First Last' — before this it required the exact full string. A PREFIX of the word must find nobody: the index is whole-word only on purpose, so it cannot be unpicked by counting partial-match frequencies.",
309
+ expectedUiState: "The full first name returns the user. The 3-4 letter prefix returns an empty list — not the same user, not anybody else.",
310
+ endResult: "Read-only; nothing persists.",
311
+ },
312
+ "checklist-content-discovery-search-search-no-plaintext-pii": {
313
+ roles: ["admin"],
314
+ startPage: "/admin/users",
315
+ steps: [
316
+ "With Firestore console access, open any `users` document.",
317
+ "Find the `nameTokensIndex` field and read its values.",
318
+ "Confirm every entry is an opaque hex string with an `hmac:` prefix, never a readable name fragment.",
319
+ "Separately, fetch the public search artifact's .json file (the one the header search downloads) and search its raw text for the word 'TokensIndex'.",
320
+ ],
321
+ needsReview: true,
322
+ reviewNote: "Needs Firestore console access, which an automated browser session does not have — flagged for a human or an admin-tooling pass rather than answered null.",
323
+ expectedBehaviour: "A blind index lets admin search without ever decrypting anyone's name. If the stored values are readable, or if the field reaches the public artifact (a deterministic per-person identifier published to every browser, cross-referenceable even unbroken), the whole design has failed regardless of what the UI shows.",
324
+ expectedUiState: "Every `nameTokensIndex` entry is an `hmac:`-prefixed digest. The public artifact's raw text contains no occurrence of 'TokensIndex' at all.",
325
+ endResult: "Read-only; nothing persists.",
326
+ },
327
+ "checklist-content-discovery-search-faq-punctuation-search": {
328
+ roles: ["guest"],
329
+ startPage: "/faqs",
330
+ steps: [
331
+ "Open /faqs in a private window.",
332
+ "Type !!! into the FAQ search box.",
333
+ "Read the message shown and count the FAQs displayed.",
334
+ ],
335
+ inputs: { punctuation: "!!!" },
336
+ expectedBehaviour: "GUARD CASE — pins behaviour that is already correct. A punctuation-only query has no searchable words, and must say so rather than silently falling through to 'no filter', which would return the entire FAQ catalogue disguised as search results.",
337
+ expectedUiState: "The page says the search had no searchable words. Zero FAQs are shown — not the full list.",
338
+ endResult: "Read-only; nothing persists.",
339
+ },
340
+ };
@@ -78,10 +78,14 @@ import { authored as a_content_discovery__coupons } from "./content-discovery__c
78
78
  import { authored as a_content_discovery__events } from "./content-discovery__events.js";
79
79
  import { authored as a_content_discovery__faq_help } from "./content-discovery__faq-help.js";
80
80
  import { authored as a_content_discovery__authenticity_checker } from "./content-discovery__authenticity-checker.js";
81
+ import { authoredNearMiss as a_content_discovery__authenticity_near_miss } from "./content-discovery__authenticity-checker.js";
82
+ import { authoredTypeahead as a_content_discovery__authenticity_typeahead } from "./content-discovery__authenticity-checker.js";
81
83
  import { authored as a_content_discovery__parts_database } from "./content-discovery__parts-database.js";
82
84
  import { authored as a_content_discovery__category_tabs_franchise_bundles } from "./content-discovery__category-tabs-franchise-bundles.js";
83
85
  import { authored as a_content_discovery__notifications } from "./content-discovery__notifications.js";
84
86
  import { authored as a_content_discovery__search } from "./content-discovery__search.js";
87
+ import { authored as a_content_discovery__site_wide_index } from "./content-discovery__site-wide-index.js";
88
+ import { authored as a_content_discovery__price_index_and_compare } from "./content-discovery__price-index-and-compare.js";
85
89
  import { authored as a_cta_layout__checkout_bottom_bar } from "./cta-layout__checkout-bottom-bar.js";
86
90
  import { authored as a_cta_layout__dialog_footers } from "./cta-layout__dialog-footers.js";
87
91
  import { authored as a_cta_layout__editor_action_bar } from "./cta-layout__editor-action-bar.js";
@@ -117,6 +121,9 @@ import { authored as a_selling__listing_type_fields_roundtrip } from "./selling_
117
121
  import { authored as a_selling__media_limits } from "./selling__media-limits.js";
118
122
  import { authored as a_selling__seller_bids_bundles_filters } from "./selling__seller-bids-bundles-filters.js";
119
123
  import { authored as a_tools__sticker_tracer } from "./tools__sticker-tracer.js";
124
+ import { authored as a_tools__sticker_promote_to_listings } from "./tools__sticker-promote-to-listings.js";
125
+ import { authored as a_buying__spares_and_groups } from "./buying__spares-and-groups.js";
126
+ import { authored as a_selling__spares_and_group_editing } from "./selling__spares-and-group-editing.js";
120
127
  import { authored as a_selling__seller_catalog_org } from "./selling__seller-catalog-org.js";
121
128
  import { authored as a_selling__seller_custom_brands } from "./selling__seller-custom-brands.js";
122
129
  import { authored as a_selling__seller_guide } from "./selling__seller-guide.js";
@@ -137,6 +144,7 @@ import { authored as a_seo__page_metadata } from "./seo__page-metadata.js";
137
144
  import { authored as a_seo__og_images } from "./seo__og-images.js";
138
145
  import { authored as a_search_and_nav__employee_permissions } from "./search-and-nav__employee-permissions.js";
139
146
  import { authored as a_search_and_nav__header_search } from "./search-and-nav__header-search.js";
147
+ import { authoredIndexEra as a_search_and_nav__header_search_index_era } from "./search-and-nav__header-search.js";
140
148
  import { authored as a_search_and_nav__settings_deep_links } from "./search-and-nav__settings-deep-links.js";
141
149
  import { authored as a_search_and_nav__sidebar_search } from "./search-and-nav__sidebar-search.js";
142
150
  import { authored as a_selling__listing_edit_roundtrip } from "./selling__listing-edit-roundtrip.js";
@@ -209,10 +217,14 @@ export const AUTHORED_CASES = {
209
217
  ...a_content_discovery__events,
210
218
  ...a_content_discovery__faq_help,
211
219
  ...a_content_discovery__authenticity_checker,
220
+ ...a_content_discovery__authenticity_near_miss,
221
+ ...a_content_discovery__authenticity_typeahead,
212
222
  ...a_content_discovery__parts_database,
213
223
  ...a_content_discovery__category_tabs_franchise_bundles,
214
224
  ...a_content_discovery__notifications,
215
225
  ...a_content_discovery__search,
226
+ ...a_content_discovery__site_wide_index,
227
+ ...a_content_discovery__price_index_and_compare,
216
228
  ...a_cta_layout__checkout_bottom_bar,
217
229
  ...a_cta_layout__dialog_footers,
218
230
  ...a_cta_layout__editor_action_bar,
@@ -248,6 +260,9 @@ export const AUTHORED_CASES = {
248
260
  ...a_selling__media_limits,
249
261
  ...a_selling__seller_bids_bundles_filters,
250
262
  ...a_tools__sticker_tracer,
263
+ ...a_tools__sticker_promote_to_listings,
264
+ ...a_buying__spares_and_groups,
265
+ ...a_selling__spares_and_group_editing,
251
266
  ...a_selling__seller_catalog_org,
252
267
  ...a_selling__seller_custom_brands,
253
268
  ...a_selling__seller_guide,
@@ -268,6 +283,7 @@ export const AUTHORED_CASES = {
268
283
  ...a_seo__og_images,
269
284
  ...a_search_and_nav__employee_permissions,
270
285
  ...a_search_and_nav__header_search,
286
+ ...a_search_and_nav__header_search_index_era,
271
287
  ...a_search_and_nav__settings_deep_links,
272
288
  ...a_search_and_nav__sidebar_search,
273
289
  ...a_selling__listing_edit_roundtrip,
@@ -1,2 +1,3 @@
1
1
  import type { AuthoredCase } from "./_types";
2
2
  export declare const authored: Record<string, AuthoredCase>;
3
+ export declare const authoredIndexEra: Record<string, AuthoredCase>;
@@ -126,3 +126,60 @@ export const authored = {
126
126
  endResult: "Read-only; nothing persists. The fourth query is the control — a search that returned nothing for everything would pass the first three for the wrong reason.",
127
127
  },
128
128
  };
129
+ /*
130
+ * ── The header now reaches /search (2026-10-19) ─────────────────────────
131
+ *
132
+ * One `onSearch` handler was the entire reason the cross-resource results
133
+ * page had no users: it mapped the type dropdown to a browse route and
134
+ * redirected there, so a buyer searching "dranzer" never learned there is
135
+ * also a category, a part, a blog post and an FAQ about it.
136
+ */
137
+ export const authoredIndexEra = {
138
+ "checklist-search-and-nav-header-search-header-search-submit-lands-on-search": {
139
+ roles: ["guest"],
140
+ startPage: "/",
141
+ steps: [
142
+ "Open / in a private window.",
143
+ "Click the header search and type dranzer.",
144
+ "Press Enter.",
145
+ "Read the URL in the address bar.",
146
+ "Count the section headings on the page.",
147
+ "Find the 'See all … →' link on one group and read where it points.",
148
+ ],
149
+ inputs: { query: "dranzer" },
150
+ expectedBehaviour: "Submitting goes to the cross-resource page. The nine per-type browse routes are not lost — each is its own group's 'See all' link, which is where a per-type destination belongs once results are grouped.",
151
+ expectedUiState: "The URL is /search?q=dranzer. Results appear under several headings. Any URL of the form /products?q=… or /stores?q=… means the old redirect is still in place.",
152
+ endResult: "Read-only; nothing persists.",
153
+ },
154
+ "checklist-search-and-nav-header-search-header-search-type-is-a-filter": {
155
+ roles: ["guest"],
156
+ startPage: "/",
157
+ steps: [
158
+ "Open / in a private window.",
159
+ "Open the header search and set the type dropdown to Stores.",
160
+ "Type beyblade and press Enter.",
161
+ "Read the URL and the sections shown.",
162
+ "Find the Stores group and click its 'See all stores →' link.",
163
+ "Go back, clear the query completely, and press Enter with an EMPTY box.",
164
+ "Note whether the page navigated anywhere.",
165
+ ],
166
+ inputs: { type: "Stores", query: "beyblade" },
167
+ expectedBehaviour: "The dropdown narrows the result set; it no longer decides the destination. An empty query does nothing, because landing on a browse page you did not ask for is worse than staying put.",
168
+ expectedUiState: "You land on /search, not /stores. The Stores group's 'See all' link is what goes to /stores. Submitting an empty box leaves you exactly where you were.",
169
+ endResult: "Read-only; nothing persists.",
170
+ },
171
+ "checklist-search-and-nav-header-search-header-search-no-price-for-guest": {
172
+ roles: ["guest"],
173
+ startPage: "/",
174
+ steps: [
175
+ "Open / in a private window with no session.",
176
+ "Click the header search and type dranzer.",
177
+ "Read every suggestion row, including the small grey second line.",
178
+ "Look specifically for a ₹ figure anywhere in the dropdown.",
179
+ ],
180
+ inputs: { query: "dranzer" },
181
+ expectedBehaviour: "The suggestion subtitle used to be the product's price, from an unauthenticated route cached publicly for 60 seconds — so every signed-out visitor saw prices in the header on every page of the site.",
182
+ expectedUiState: "Subtitles read as brand and category, e.g. 'Takara-Tomy · Attack Rings'. Any ₹ in the dropdown while signed out is a FAIL.",
183
+ endResult: "Read-only; nothing persists.",
184
+ },
185
+ };
@@ -0,0 +1,2 @@
1
+ import type { AuthoredCase } from "./_types";
2
+ export declare const authored: Record<string, AuthoredCase>;
@@ -0,0 +1,77 @@
1
+ /*
2
+ * WHY: Authored six-part procedures for the seller side of Part Two —
3
+ * listing a spare, and the shared group-stock/rules editor.
4
+ * WHAT: 4 case(s), keyed by full checklist id.
5
+ *
6
+ * 🛑 "Stock is independent per listing" is the whole premise of the shared
7
+ * editor, so the round trip that proves it (edit one, reload, check the
8
+ * OTHER member) is the case that actually matters here — not whether the
9
+ * form submits.
10
+ *
11
+ * @tag domain:tester
12
+ * @tag layer:seed
13
+ * @tag pattern:none
14
+ * @tag access:server-only
15
+ * @tag consumers:seed-data/authored/index.ts
16
+ * @tag sideEffects:none
17
+ */
18
+ const SELLER_SIGNIN = "Sign in as a seller account that owns store-beyblade-arena.";
19
+ export const authored = {
20
+ "checklist-selling-spares-seller-and-group-editing-seller-can-list-a-spare": {
21
+ roles: ["seller"],
22
+ startPage: "/store/spares",
23
+ steps: [
24
+ SELLER_SIGNIN,
25
+ "Click \"List a Spare\".",
26
+ "Fill in title, price, colourway, and a stock quantity, then Publish.",
27
+ "Return to /store/spares.",
28
+ ],
29
+ expectedBehaviour: "The seller product-shell accepts `listingType: \"spares\"` the same way it accepts the other nine types — this is the one new entry in `ProductListingMode`, so a missing wiring here would show as a form that silently fails to save or a blank listing-type dropdown.",
30
+ expectedUiState: "The new spare appears in the /store/spares list with the colourway shown as its distinguishing column (not a size column, which the other print types use), the price entered, and status Published.",
31
+ endResult: "BEFORE spares existed, there was no seller-facing way to list one at all. AFTER: creating one is the same shape as creating a sticker or an art print, just under a different type.",
32
+ },
33
+ "checklist-selling-spares-seller-and-group-editing-group-stock-edit-is-independent": {
34
+ roles: ["seller"],
35
+ startPage: "/store/products",
36
+ steps: [
37
+ SELLER_SIGNIN,
38
+ "Open the editor for a group PARENT product (one with \"isGroupParent\").",
39
+ "In the Members list, note the current stock figures for two different children, A and B.",
40
+ "In the shared stock editor, change ONLY child A's stock to a new number. Leave B's input untouched.",
41
+ "Click \"Save group stock\".",
42
+ "Reload the editor and re-read both children's stock figures.",
43
+ ],
44
+ expectedBehaviour: "`PATCH .../group/stock` writes ONLY the member rows named in the request body — `saveGroupStock()` sends just the touched ids via `stockDraft`/`motDraft`, never a blanket broadcast across every child. Stock is independent per listing by design: a spare is one physical object a seller happens to have, and selling or restocking one must not touch another's figure.",
45
+ expectedUiState: "After reload, child A shows the NEW figure you entered. Child B shows EXACTLY the figure it had before — unchanged to the unit. This is the single most important assertion in this group: a shared pool bug would show up as B silently changing when only A was edited.",
46
+ endResult: "BEFORE this editor existed, each child's stock could only be edited by opening its own separate product page — tedious but safe. AFTER: editing is faster AND must remain exactly as safe. If B's figure moved at all, the editor is pooling stock rather than editing independent listings, which contradicts the feature's entire premise.",
47
+ },
48
+ "checklist-selling-spares-seller-and-group-editing-group-rules-save-and-reload": {
49
+ roles: ["seller"],
50
+ startPage: "/store/products",
51
+ steps: [
52
+ SELLER_SIGNIN,
53
+ "Open a group parent's editor and find the \"Selection rules\" form.",
54
+ "Change the min, max, per-item cap, and discount fields to new values different from what they currently show.",
55
+ "Click \"Save rules\".",
56
+ "Reload the editor page.",
57
+ "As a buyer (different tab/session), open that group's public picker.",
58
+ ],
59
+ expectedBehaviour: "`PATCH .../group` writes only the keys present in the body, via the `in` test rather than a spread — so saving rules must not blank the group title, and reloading must show exactly what was saved, not a stale cached value. The public picker reads the SAME stored fields through `readGroupRules`, so it must reflect the new numbers on its very next load, with no separate publish step.",
60
+ expectedUiState: "After reload, the editor's four rule fields show the NEW values you typed, not the old ones and not blank. The buyer-facing picker, opened fresh, states the NEW rules in its rule-summary text — if it still shows the old numbers, something is caching the group document past this write.",
61
+ endResult: "BEFORE: a group had no rules to edit at all. AFTER: a seller's edit is live on the storefront immediately, and a reload of the editor proves the write actually persisted rather than only updating client-side state.",
62
+ },
63
+ "checklist-selling-spares-seller-and-group-editing-group-stock-partial-failure-reported": {
64
+ roles: ["seller"],
65
+ startPage: "/store/products",
66
+ steps: [
67
+ SELLER_SIGNIN,
68
+ "Open a group parent's editor in TWO browser tabs.",
69
+ "In tab 1, unlink one child from the group and confirm it.",
70
+ "Switch to tab 2 (which still shows the now-unlinked child in its member list) and, without reloading, edit that same child's stock PLUS a different, still-valid child's stock.",
71
+ "Click \"Save group stock\" in tab 2.",
72
+ ],
73
+ expectedBehaviour: "The route re-fetches the CURRENT member list server-side before writing, so the unlinked child's id is no longer a member and is rejected — but the request must not fail as a whole. The still-valid child's stock edit must still be written. This is the 207-style partial result the route is built for.",
74
+ expectedUiState: "A toast or message reports a partial result (e.g. \"Updated 1; 1 could not be updated.\"). Reloading the editor shows the still-valid child's stock at its NEW value — the save for that row was not discarded just because the other row was stale.",
75
+ endResult: "BEFORE this endpoint existed, there was no shared editor and so no partial-failure case to handle. AFTER: a stale row (the common case when two admins or two tabs edit the same group near-simultaneously) costs nothing to the rows that were still valid.",
76
+ },
77
+ };