@mohasinac/appkit 4.34.1 → 4.35.0

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 (232) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +32 -2
  2. package/dist/features/tester/components/TesterChecklistStepRow.d.ts +9 -1
  3. package/dist/features/tester/components/TesterChecklistStepRow.js +31 -2
  4. package/dist/features/tester/components/TesterHubView.js +8 -0
  5. package/dist/features/tester/schemas/firestore.d.ts +109 -0
  6. package/dist/features/tester/schemas/firestore.js +14 -0
  7. package/dist/features/tester/seed-data/_money-flows.d.ts +23 -0
  8. package/dist/features/tester/seed-data/_money-flows.js +430 -0
  9. package/dist/features/tester/seed-data/authored/_types.d.ts +33 -0
  10. package/dist/features/tester/seed-data/authored/_types.js +35 -0
  11. package/dist/features/tester/seed-data/authored/account-auth__profile-settings.d.ts +2 -0
  12. package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +207 -0
  13. package/dist/features/tester/seed-data/authored/account-auth__signup-login.d.ts +2 -0
  14. package/dist/features/tester/seed-data/authored/account-auth__signup-login.js +298 -0
  15. package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.d.ts +2 -0
  16. package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.js +33 -0
  17. package/dist/features/tester/seed-data/authored/account-auth__testing-program.d.ts +2 -0
  18. package/dist/features/tester/seed-data/authored/account-auth__testing-program.js +138 -0
  19. package/dist/features/tester/seed-data/authored/addresses__address-filters.d.ts +2 -0
  20. package/dist/features/tester/seed-data/authored/addresses__address-filters.js +36 -0
  21. package/dist/features/tester/seed-data/authored/addresses__postal-lookup.d.ts +2 -0
  22. package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +75 -0
  23. package/dist/features/tester/seed-data/authored/addresses__postal-validation.d.ts +2 -0
  24. package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +72 -0
  25. package/dist/features/tester/seed-data/authored/addresses__state-picker.d.ts +2 -0
  26. package/dist/features/tester/seed-data/authored/addresses__state-picker.js +54 -0
  27. package/dist/features/tester/seed-data/authored/addresses__unban-request.d.ts +2 -0
  28. package/dist/features/tester/seed-data/authored/addresses__unban-request.js +39 -0
  29. package/dist/features/tester/seed-data/authored/admin__bans-and-trust.d.ts +2 -0
  30. package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +226 -0
  31. package/dist/features/tester/seed-data/authored/admin__blog-faqs.d.ts +2 -0
  32. package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +82 -0
  33. package/dist/features/tester/seed-data/authored/admin__bulk-actions.d.ts +2 -0
  34. package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +177 -0
  35. package/dist/features/tester/seed-data/authored/admin__bundles.d.ts +2 -0
  36. package/dist/features/tester/seed-data/authored/admin__bundles.js +97 -0
  37. package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.d.ts +2 -0
  38. package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +232 -0
  39. package/dist/features/tester/seed-data/authored/admin__catalog-listings.d.ts +2 -0
  40. package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +306 -0
  41. package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.d.ts +2 -0
  42. package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +158 -0
  43. package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.d.ts +2 -0
  44. package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +78 -0
  45. package/dist/features/tester/seed-data/authored/admin__content-deletes.d.ts +2 -0
  46. package/dist/features/tester/seed-data/authored/admin__content-deletes.js +261 -0
  47. package/dist/features/tester/seed-data/authored/admin__content-marketing.d.ts +2 -0
  48. package/dist/features/tester/seed-data/authored/admin__content-marketing.js +151 -0
  49. package/dist/features/tester/seed-data/authored/admin__coupons.d.ts +2 -0
  50. package/dist/features/tester/seed-data/authored/admin__coupons.js +214 -0
  51. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.d.ts +2 -0
  52. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +160 -0
  53. package/dist/features/tester/seed-data/authored/admin__media-watermark.d.ts +2 -0
  54. package/dist/features/tester/seed-data/authored/admin__media-watermark.js +104 -0
  55. package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.d.ts +2 -0
  56. package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +350 -0
  57. package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.d.ts +2 -0
  58. package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +105 -0
  59. package/dist/features/tester/seed-data/authored/admin__site-system.d.ts +2 -0
  60. package/dist/features/tester/seed-data/authored/admin__site-system.js +590 -0
  61. package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.d.ts +2 -0
  62. package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +235 -0
  63. package/dist/features/tester/seed-data/authored/admin__users-trust.d.ts +2 -0
  64. package/dist/features/tester/seed-data/authored/admin__users-trust.js +569 -0
  65. package/dist/features/tester/seed-data/authored/buying__bidding.d.ts +2 -0
  66. package/dist/features/tester/seed-data/authored/buying__bidding.js +417 -0
  67. package/dist/features/tester/seed-data/authored/buying__browsing-search.d.ts +2 -0
  68. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +653 -0
  69. package/dist/features/tester/seed-data/authored/buying__buying-checkout.d.ts +2 -0
  70. package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +533 -0
  71. package/dist/features/tester/seed-data/authored/buying__buying-coupons.d.ts +2 -0
  72. package/dist/features/tester/seed-data/authored/buying__buying-coupons.js +379 -0
  73. package/dist/features/tester/seed-data/authored/buying__cart.d.ts +2 -0
  74. package/dist/features/tester/seed-data/authored/buying__cart.js +1171 -0
  75. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.d.ts +2 -0
  76. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +140 -0
  77. package/dist/features/tester/seed-data/authored/buying__my-orders.d.ts +2 -0
  78. package/dist/features/tester/seed-data/authored/buying__my-orders.js +257 -0
  79. package/dist/features/tester/seed-data/authored/buying__offers.d.ts +2 -0
  80. package/dist/features/tester/seed-data/authored/buying__offers.js +1111 -0
  81. package/dist/features/tester/seed-data/authored/buying__order-status-lifecycle.d.ts +2 -0
  82. package/dist/features/tester/seed-data/authored/buying__order-status-lifecycle.js +252 -0
  83. package/dist/features/tester/seed-data/authored/buying__product-detail.d.ts +2 -0
  84. package/dist/features/tester/seed-data/authored/buying__product-detail.js +387 -0
  85. package/dist/features/tester/seed-data/authored/buying__return-request.d.ts +2 -0
  86. package/dist/features/tester/seed-data/authored/buying__return-request.js +152 -0
  87. package/dist/features/tester/seed-data/authored/buying__reviews-pagination.d.ts +2 -0
  88. package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +242 -0
  89. package/dist/features/tester/seed-data/authored/buying__reviews.d.ts +2 -0
  90. package/dist/features/tester/seed-data/authored/buying__reviews.js +113 -0
  91. package/dist/features/tester/seed-data/authored/buying__user-dashboard-extras.d.ts +2 -0
  92. package/dist/features/tester/seed-data/authored/buying__user-dashboard-extras.js +283 -0
  93. package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.d.ts +2 -0
  94. package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +167 -0
  95. package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.d.ts +2 -0
  96. package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +90 -0
  97. package/dist/features/tester/seed-data/authored/buying__wishlist-history.d.ts +2 -0
  98. package/dist/features/tester/seed-data/authored/buying__wishlist-history.js +344 -0
  99. package/dist/features/tester/seed-data/authored/community-support__public-profile.d.ts +2 -0
  100. package/dist/features/tester/seed-data/authored/community-support__public-profile.js +53 -0
  101. package/dist/features/tester/seed-data/authored/community-support__support-tickets.d.ts +2 -0
  102. package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +76 -0
  103. package/dist/features/tester/seed-data/authored/content-discovery__blog.d.ts +2 -0
  104. package/dist/features/tester/seed-data/authored/content-discovery__blog.js +93 -0
  105. package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.d.ts +2 -0
  106. package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +149 -0
  107. package/dist/features/tester/seed-data/authored/content-discovery__coupons.d.ts +2 -0
  108. package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +121 -0
  109. package/dist/features/tester/seed-data/authored/content-discovery__event-participation.d.ts +2 -0
  110. package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +207 -0
  111. package/dist/features/tester/seed-data/authored/content-discovery__events.d.ts +2 -0
  112. package/dist/features/tester/seed-data/authored/content-discovery__events.js +318 -0
  113. package/dist/features/tester/seed-data/authored/content-discovery__faq-help.d.ts +2 -0
  114. package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +65 -0
  115. package/dist/features/tester/seed-data/authored/content-discovery__notifications.d.ts +2 -0
  116. package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +147 -0
  117. package/dist/features/tester/seed-data/authored/content-discovery__search.d.ts +2 -0
  118. package/dist/features/tester/seed-data/authored/content-discovery__search.js +332 -0
  119. package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.d.ts +2 -0
  120. package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +53 -0
  121. package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.d.ts +2 -0
  122. package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +52 -0
  123. package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.d.ts +2 -0
  124. package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -0
  125. package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.d.ts +2 -0
  126. package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +141 -0
  127. package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.d.ts +2 -0
  128. package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +35 -0
  129. package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.d.ts +2 -0
  130. package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +95 -0
  131. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.d.ts +2 -0
  132. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +191 -0
  133. package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.d.ts +2 -0
  134. package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +158 -0
  135. package/dist/features/tester/seed-data/authored/design-ux__footer-theme.d.ts +2 -0
  136. package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +106 -0
  137. package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.d.ts +2 -0
  138. package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +88 -0
  139. package/dist/features/tester/seed-data/authored/design-ux__general-design.d.ts +2 -0
  140. package/dist/features/tester/seed-data/authored/design-ux__general-design.js +204 -0
  141. package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.d.ts +2 -0
  142. package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +301 -0
  143. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.d.ts +2 -0
  144. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +407 -0
  145. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.d.ts +2 -0
  146. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +214 -0
  147. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.d.ts +2 -0
  148. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +275 -0
  149. package/dist/features/tester/seed-data/authored/index.d.ts +2 -0
  150. package/dist/features/tester/seed-data/authored/index.js +237 -0
  151. package/dist/features/tester/seed-data/authored/page-wiring__data-loss.d.ts +2 -0
  152. package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +102 -0
  153. package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.d.ts +2 -0
  154. package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +50 -0
  155. package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.d.ts +2 -0
  156. package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +66 -0
  157. package/dist/features/tester/seed-data/authored/page-wiring__reachability.d.ts +2 -0
  158. package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +115 -0
  159. package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.d.ts +2 -0
  160. package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +136 -0
  161. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +2 -0
  162. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +69 -0
  163. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.d.ts +2 -0
  164. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +263 -0
  165. package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.d.ts +2 -0
  166. package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +102 -0
  167. package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.d.ts +2 -0
  168. package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +234 -0
  169. package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.d.ts +2 -0
  170. package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +126 -0
  171. package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.d.ts +2 -0
  172. package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +255 -0
  173. package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.d.ts +2 -0
  174. package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +59 -0
  175. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +2 -0
  176. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +88 -0
  177. package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.d.ts +2 -0
  178. package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +51 -0
  179. package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.d.ts +2 -0
  180. package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +35 -0
  181. package/dist/features/tester/seed-data/authored/selling__become-seller.d.ts +2 -0
  182. package/dist/features/tester/seed-data/authored/selling__become-seller.js +94 -0
  183. package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.d.ts +2 -0
  184. package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +107 -0
  185. package/dist/features/tester/seed-data/authored/selling__listing-a-product.d.ts +2 -0
  186. package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +285 -0
  187. package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.d.ts +2 -0
  188. package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +326 -0
  189. package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.d.ts +2 -0
  190. package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +136 -0
  191. package/dist/features/tester/seed-data/authored/selling__media-limits.d.ts +2 -0
  192. package/dist/features/tester/seed-data/authored/selling__media-limits.js +128 -0
  193. package/dist/features/tester/seed-data/authored/selling__sectionised-forms.d.ts +2 -0
  194. package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +167 -0
  195. package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.d.ts +2 -0
  196. package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +117 -0
  197. package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.d.ts +2 -0
  198. package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +88 -0
  199. package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.d.ts +2 -0
  200. package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +151 -0
  201. package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.d.ts +2 -0
  202. package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +105 -0
  203. package/dist/features/tester/seed-data/authored/selling__seller-guide.d.ts +2 -0
  204. package/dist/features/tester/seed-data/authored/selling__seller-guide.js +33 -0
  205. package/dist/features/tester/seed-data/authored/selling__seller-listing-types.d.ts +2 -0
  206. package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +347 -0
  207. package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.d.ts +2 -0
  208. package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +166 -0
  209. package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.d.ts +2 -0
  210. package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +89 -0
  211. package/dist/features/tester/seed-data/authored/selling__seller-orders.d.ts +2 -0
  212. package/dist/features/tester/seed-data/authored/selling__seller-orders.js +195 -0
  213. package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.d.ts +2 -0
  214. package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +180 -0
  215. package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.d.ts +2 -0
  216. package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +164 -0
  217. package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.d.ts +2 -0
  218. package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +116 -0
  219. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.d.ts +2 -0
  220. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +111 -0
  221. package/dist/features/tester/seed-data/authored/seo__og-images.d.ts +2 -0
  222. package/dist/features/tester/seed-data/authored/seo__og-images.js +91 -0
  223. package/dist/features/tester/seed-data/authored/seo__page-metadata.d.ts +2 -0
  224. package/dist/features/tester/seed-data/authored/seo__page-metadata.js +122 -0
  225. package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.d.ts +2 -0
  226. package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +105 -0
  227. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +744 -1
  228. package/dist/seed/carousel-slides-seed-data.js +1 -1
  229. package/dist/seed/products-standard-seed-data.js +2 -2
  230. package/dist/seed/reviews-seed-data.js +6 -1
  231. package/package.json +1 -1
  232. package/scripts/seed-cli.mjs +6 -0
@@ -96,8 +96,38 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
96
96
  * `audit-code-quality`'s DEEP_NESTING rule exists to stop.
97
97
  */
98
98
  function SlotTable({ slots, showPrice, onSlotsChange, }) {
99
- const addSlot = () => onSlotsChange([...slots, { slotNumber: slots.length + 1, name: "", price: 0 }]);
100
- const removeSlot = (idx) => onSlotsChange(slots.filter((_, i) => i !== idx).map((s, i) => ({ ...s, slotNumber: i + 1 })));
99
+ /**
100
+ * Next number is max+1, not length+1.
101
+ *
102
+ * Now that removeSlot preserves numbering, `slots.length + 1` collides: delete
103
+ * slot 1 of 25 and you hold 24 slots numbered 2..25, so length+1 is 25 — a
104
+ * duplicate, which the write schema rejects outright (slot numbers must be
105
+ * unique). Deriving from the highest number in use is stable under any gaps.
106
+ */
107
+ const addSlot = () => onSlotsChange([
108
+ ...slots,
109
+ { slotNumber: slots.reduce((max, s) => Math.max(max, s.slotNumber), 0) + 1, name: "", price: 0 },
110
+ ]);
111
+ /**
112
+ * 🛑 Remove the slot. Do NOT renumber the survivors.
113
+ *
114
+ * `slotNumber` is the stable identity a booking is attached to, and renumbering
115
+ * silently defeated the server's own protection. `mergeLotteryConfig` refuses a
116
+ * save that drops a BOOKED slot by checking `isBooked && !incoming.has(slotNumber)`
117
+ * — but this function used to renumber the remaining slots to `i + 1`, so deleting
118
+ * slot 1 of 25 sent back numbers 1..24. Every booked number (1–5) still *appeared*
119
+ * in that set, the check found nothing to refuse, and the save went through.
120
+ *
121
+ * The damage was worse than the deletion: the merge re-attaches bookings by
122
+ * slotNumber, so the buyer of the deleted slot 1 had their booking moved onto
123
+ * whatever prize now occupied number 1. That is precisely the hazard the schema
124
+ * header describes — "hands slot 7's buyer the prize that used to be slot 8".
125
+ *
126
+ * Numbers may therefore be non-contiguous after a delete (2..25), which the write
127
+ * schema allows: it requires slot numbers to be UNIQUE, not gapless, and totalSlots
128
+ * is derived from slots.length rather than from the highest number.
129
+ */
130
+ const removeSlot = (idx) => onSlotsChange(slots.filter((_, i) => i !== idx));
101
131
  const updateSlot = (idx, field, value) => onSlotsChange(slots.map((s, i) => i === idx
102
132
  ? {
103
133
  ...s,
@@ -1,10 +1,18 @@
1
1
  import React from "react";
2
- import type { TesterAnswer } from "../schemas/firestore";
2
+ import type { TesterAnswer, TesterCaseRole } from "../schemas/firestore";
3
3
  export interface TesterChecklistStepItem {
4
4
  checklistItemId: string;
5
5
  label: string;
6
6
  description?: string;
7
7
  href?: string;
8
+ roles?: TesterCaseRole[];
9
+ startPage?: string;
10
+ steps?: string[];
11
+ inputs?: Record<string, string | number | boolean>;
12
+ expectedBehaviour?: string;
13
+ expectedUiState?: string;
14
+ expectedData?: Record<string, string | number | boolean>;
15
+ endResult?: string;
8
16
  answer: TesterAnswer | null;
9
17
  comment?: string;
10
18
  screenshotUrl?: string;
@@ -1,10 +1,34 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
- import { Anchor, Button, Div, FieldTextarea, Row, Stack, Text } from "../../../ui";
4
+ import { Anchor, Button, Details, Div, FieldTextarea, Li, Ol, Row, Stack, Summary, Text } from "../../../ui";
5
5
  import { MediaUploadField } from "../../media/upload/MediaUploadField";
6
6
  import { useMediaUpload } from "../../media/hooks/useMedia";
7
7
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
8
+ /** Human-facing labels for the role chip. `seller` is specifically the listing's OWNER. */
9
+ const ROLE_LABEL = {
10
+ guest: "Signed out",
11
+ buyer: "As a buyer",
12
+ seller: "As the owning seller",
13
+ admin: "As an admin",
14
+ employee: "As an employee",
15
+ };
16
+ /**
17
+ * `inputs` / `expectedData` as a two-column list.
18
+ *
19
+ * Rendered as key/value rather than pretty-printed JSON because the reader is a
20
+ * person about to type these into a form — braces and quotes are noise, and a
21
+ * value they have to mentally unescape is a value they will get wrong.
22
+ *
23
+ * Renders nothing at all when the case has no such data, which is the common case:
24
+ * a read-only page enters nothing and has nothing checkable behind the screen.
25
+ */
26
+ function KeyValueBlock({ label, data, }) {
27
+ const entries = Object.entries(data ?? {});
28
+ if (!entries.length)
29
+ return null;
30
+ return (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: label }), entries.map(([k, v]) => (_jsxs(Row, { gap: "xs", align: "baseline", wrap: true, children: [_jsx(Text, { size: "sm", color: "muted", children: k }), _jsx(Text, { size: "sm", weight: "medium", numeric: typeof v === "number", children: String(v) })] }, k)))] }));
31
+ }
8
32
  export function TesterChecklistStepRow({ item, testerDisplayName, onAnswer, onSaveNote, }) {
9
33
  const [expanded, setExpanded] = React.useState(false);
10
34
  const [comment, setComment] = React.useState(item.comment ?? "");
@@ -25,5 +49,10 @@ export function TesterChecklistStepRow({ item, testerDisplayName, onAnswer, onSa
25
49
  setSaving(false);
26
50
  }
27
51
  };
28
- return (_jsxs(Stack, { gap: "xs", padding: "sm", border: "default", rounded: "md", children: [_jsxs(Row, { gap: "sm", justify: "between", align: "center", wrap: true, children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { weight: "medium", children: item.label }), item.description && (_jsx(Text, { size: "xs", color: "muted", children: item.description })), item.href && (_jsx(Anchor, { href: item.href, tone: "brand", underline: "hover", target: "_blank", rel: "noopener noreferrer", children: "Go test this \u2192" }))] }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", size: "sm", variant: item.answer === "yes" ? "primary" : "outline", onClick: () => onAnswer(item.checklistItemId, "yes"), children: "Yes" }), _jsx(Button, { type: "button", size: "sm", variant: item.answer === "no" ? "danger" : "outline", onClick: () => onAnswer(item.checklistItemId, "no"), children: "No" }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setExpanded((v) => !v), children: expanded ? "Hide note" : "Add note" })] })] }), expanded && (_jsx(Div, { padding: "t-sm", border: "default", className: "border-t", children: _jsxs(Stack, { gap: "sm", children: [_jsx(FieldTextarea, { name: `comment-${item.checklistItemId}`, label: "Comment", value: comment, onChange: setComment, placeholder: "What did you notice? Colors, styles, readability, bugs..." }), _jsx(MediaUploadField, { label: "Screenshot (optional)", value: screenshotUrl, onChange: setScreenshotUrl, onUpload: handleUpload, kind: "image" }), _jsx(Row, { children: _jsx(Button, { type: "button", action: ACTIONS.TESTER["save-note"], isLoading: saving, onClick: handleSave }) })] }) }))] }));
52
+ return (_jsxs(Stack, { gap: "xs", padding: "sm", border: "default", rounded: "md", children: [_jsxs(Row, { gap: "sm", justify: "between", align: "center", wrap: true, children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [item.roles?.map((r) => (_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: ROLE_LABEL[r] }, r))), _jsx(Text, { weight: "medium", children: item.label })] }), item.description && (_jsx(Text, { size: "xs", color: "muted", children: item.description })), item.href && (_jsx(Anchor, { href: item.href, tone: "brand", underline: "hover", target: "_blank", rel: "noopener noreferrer", children: "Go test this \u2192" }))] }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", size: "sm", variant: item.answer === "yes" ? "primary" : "outline", onClick: () => onAnswer(item.checklistItemId, "yes"), children: "Yes" }), _jsx(Button, { type: "button", size: "sm", variant: item.answer === "no" ? "danger" : "outline", onClick: () => onAnswer(item.checklistItemId, "no"), children: "No" }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setExpanded((v) => !v), children: expanded ? "Hide note" : "Add note" })] })] }), (item.steps?.length ||
53
+ item.expectedBehaviour ||
54
+ item.expectedUiState ||
55
+ item.endResult ||
56
+ item.inputs ||
57
+ item.expectedData) && (_jsxs(Details, { tone: "card", padding: "sm", defaultOpen: false, children: [_jsx(Summary, { children: item.steps?.length ? `Steps (${item.steps.length})` : "Expected result" }), _jsxs(Stack, { gap: "sm", children: [item.startPage && (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "Start" }), _jsx(Text, { size: "sm", children: item.startPage })] })), item.steps?.length ? (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "Steps" }), _jsx(Ol, { marker: "decimal", indent: "md", spacing: "tight", size: "sm", children: item.steps.map((step, i) => (_jsx(Li, { children: step }, i))) })] })) : null, _jsx(KeyValueBlock, { label: "Enter exactly", data: item.inputs }), item.expectedBehaviour && (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "Expected behaviour" }), _jsx(Text, { size: "sm", children: item.expectedBehaviour })] })), item.expectedUiState && (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "Expected UI state" }), _jsx(Text, { size: "sm", children: item.expectedUiState })] })), _jsx(KeyValueBlock, { label: "Values that must be correct", data: item.expectedData }), item.endResult && (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "After reload" }), _jsx(Text, { size: "sm", children: item.endResult })] }))] })] })), expanded && (_jsx(Div, { padding: "t-sm", border: "default", className: "border-t", children: _jsxs(Stack, { gap: "sm", children: [_jsx(FieldTextarea, { name: `comment-${item.checklistItemId}`, label: "Comment", value: comment, onChange: setComment, placeholder: "What did you notice? Colors, styles, readability, bugs..." }), _jsx(MediaUploadField, { label: "Screenshot (optional)", value: screenshotUrl, onChange: setScreenshotUrl, onUpload: handleUpload, kind: "image" }), _jsx(Row, { children: _jsx(Button, { type: "button", action: ACTIONS.TESTER["save-note"], isLoading: saving, onClick: handleSave }) })] }) }))] }));
29
58
  }
@@ -25,6 +25,14 @@ function TesterChecklistPageSection({ page, testerDisplayName, onAnswer, onSaveN
25
25
  label: item.label,
26
26
  description: item.description,
27
27
  href: item.href,
28
+ roles: item.roles,
29
+ startPage: item.startPage,
30
+ steps: item.steps,
31
+ inputs: item.inputs,
32
+ expectedBehaviour: item.expectedBehaviour,
33
+ expectedUiState: item.expectedUiState,
34
+ expectedData: item.expectedData,
35
+ endResult: item.endResult,
28
36
  answer: item.answer,
29
37
  comment: item.comment,
30
38
  screenshotUrl: item.screenshotUrl,
@@ -13,6 +13,101 @@ export interface TesterChecklistItemDocument extends BaseDocument {
13
13
  pageLabel: string;
14
14
  label: string;
15
15
  description?: string;
16
+ /**
17
+ * WHICH ROLES this case affects. Not decoration — it changes what "correct" means.
18
+ *
19
+ * The same page is a different page per role: /products/<slug> shows a buyer
20
+ * Add to Cart and Make Offer, shows the OWNING seller Edit listing and no Make
21
+ * Offer (you cannot bid on your own listing), shows an admin moderation
22
+ * controls, and shows a guest a sign-in prompt. So `expectedUiState` is
23
+ * meaningless without this, and a case omitting it is really three untested
24
+ * cases wearing one label.
25
+ *
26
+ * 🛑 A LIST, because the interesting content is usually the DIFFERENCE between
27
+ * roles — "the owning seller must NOT see Make Offer where a buyer does" is one
28
+ * case, not two, and splitting it loses the comparison that is the whole point.
29
+ * Where roles differ, `expectedBehaviour` and `expectedUiState` say so in prose
30
+ * ("Buyer: … Owning seller: …"). Deliberately NOT a parallel per-role structure:
31
+ * that would be four more fields to keep in sync for something a sentence
32
+ * expresses perfectly well.
33
+ *
34
+ * Also drives which session the automated harness browses with.
35
+ */
36
+ roles?: TesterCaseRole[];
37
+ /** WHERE the tester begins. One unambiguous URL, unprefixed (/products, not /en/products). */
38
+ startPage?: string;
39
+ /**
40
+ * WHAT the tester does. One action per entry, concrete fixtures named, literal
41
+ * values ("Enter 780", not "enter an amount below the listed price") so two
42
+ * runs are comparable and a regression can be spotted by diffing.
43
+ *
44
+ * 🛑 No assertions in here — they belong in the three expectation fields. A
45
+ * step reading "click Save and confirm it worked" is two things wearing one
46
+ * number, and the confirming half silently goes unchecked.
47
+ *
48
+ * Length varies with the case. "This page renders" is one honest step; a
49
+ * multi-role negotiation is fifteen. There is no target, and padding toward one
50
+ * produces the mechanical "Open X. Verify Y." shape that fills the field while
51
+ * encoding nothing the label did not already say.
52
+ */
53
+ steps?: string[];
54
+ /**
55
+ * The exact data the tester enters or selects, as flat key/value pairs.
56
+ *
57
+ * 🛑 WHY THIS IS NOT JUST PROSE
58
+ *
59
+ * "Enter 780, the listed price is 999" reads fine and still lets two testers type
60
+ * two different numbers, and gives an automated runner nothing to consume but a
61
+ * sentence. The steps say WHAT TO DO; this says WITH WHAT — so the procedure is
62
+ * repeatable by construction and a runner can fill a form from it directly.
63
+ *
64
+ * Flat and scalar on purpose: nested objects invite a shape nobody validates, and
65
+ * every real value here is a string, number or boolean.
66
+ *
67
+ * Omitted entirely by a case that enters nothing — a read-only page has no inputs.
68
+ */
69
+ inputs?: Record<string, string | number | boolean>;
70
+ /**
71
+ * What the system must DO — the functional outcome, including side effects no
72
+ * screen shows (a notification sent, stock decremented, an order created).
73
+ *
74
+ * Kept apart from `expectedUiState` because the gap between them is a real bug
75
+ * class: the write succeeds and the screen never updates.
76
+ */
77
+ expectedBehaviour?: string;
78
+ /**
79
+ * What the tester must SEE — exact on-screen text and control states.
80
+ *
81
+ * Quote the screen ("the button reads 'Offer sent'"), never "the UI updates
82
+ * correctly", which cannot be checked against a screenshot. This is what makes
83
+ * the mandatory screenshot reviewable: the image is compared to a written
84
+ * claim instead of to a guess about what "works" looked like.
85
+ */
86
+ expectedUiState?: string;
87
+ /**
88
+ * The values that must be CORRECT afterwards, as flat key/value pairs.
89
+ *
90
+ * `expectedUiState` settles what the screen looks like; this settles whether the
91
+ * data behind it is right. The two are different questions, and a screen can be
92
+ * entirely plausible over a wrong number — which is most of this codebase's bug
93
+ * history, where a defect returns HTTP 200 with believable rows.
94
+ *
95
+ * Machine-readable so a runner asserts against it rather than re-reading English,
96
+ * and so "it looked right" becomes a comparison.
97
+ *
98
+ * Omitted by a case with nothing checkable behind the screen.
99
+ */
100
+ expectedData?: Record<string, string | number | boolean>;
101
+ /**
102
+ * What is still true after a RELOAD — the persistence oracle.
103
+ *
104
+ * Several of the worst bugs here returned a normal 200, rendered
105
+ * optimistically, and wrote nothing (the grouped-listing rename, the dropped
106
+ * address landmark). Reloading is the only thing that catches them. Where a
107
+ * case genuinely persists nothing, say so explicitly rather than leaving the
108
+ * field to mean two different things.
109
+ */
110
+ endResult?: string;
16
111
  href?: string;
17
112
  order: number;
18
113
  phase: number;
@@ -48,6 +143,20 @@ export interface BugHunterLeaderboardEntry {
48
143
  }
49
144
  export declare function slugifyChecklistLabel(label: string): string;
50
145
  export declare function createChecklistItemId(groupKey: string, pageKey: string, label: string): string;
146
+ /**
147
+ * Whose point of view a checklist case is written from.
148
+ *
149
+ * `guest` is a real role, not the absence of one — a signed-out visitor sees a
150
+ * sign-in prompt where a buyer sees a wishlist heart, and that path had never
151
+ * been exercised before this field existed.
152
+ *
153
+ * `seller` means the seller who OWNS the listing under test. A different
154
+ * seller looking at someone else's listing is a `buyer` for that case — the
155
+ * distinction matters because the owning seller is precisely who must NOT see
156
+ * Make Offer.
157
+ */
158
+ export declare const TESTER_CASE_ROLES: readonly ["guest", "buyer", "seller", "admin", "employee"];
159
+ export type TesterCaseRole = (typeof TESTER_CASE_ROLES)[number];
51
160
  export type TesterAnswer = "yes" | "no";
52
161
  export type TesterFeedbackStatus = "new" | "reviewed";
53
162
  export interface TesterChecklistResponseDocument extends BaseDocument {
@@ -42,6 +42,20 @@ export function slugifyChecklistLabel(label) {
42
42
  export function createChecklistItemId(groupKey, pageKey, label) {
43
43
  return generateChecklistItemId({ groupKey, pageKey, label });
44
44
  }
45
+ // --- Checklist response (tester answers) --------------------------------------
46
+ /**
47
+ * Whose point of view a checklist case is written from.
48
+ *
49
+ * `guest` is a real role, not the absence of one — a signed-out visitor sees a
50
+ * sign-in prompt where a buyer sees a wishlist heart, and that path had never
51
+ * been exercised before this field existed.
52
+ *
53
+ * `seller` means the seller who OWNS the listing under test. A different
54
+ * seller looking at someone else's listing is a `buyer` for that case — the
55
+ * distinction matters because the owning seller is precisely who must NOT see
56
+ * Make Offer.
57
+ */
58
+ export const TESTER_CASE_ROLES = ["guest", "buyer", "seller", "admin", "employee"];
45
59
  export const TESTER_CHECKLIST_RESPONSE_COLLECTION = "testerChecklistResponses";
46
60
  export const TESTER_CHECKLIST_RESPONSE_INDEXED_FIELDS = [
47
61
  "testerId",
@@ -0,0 +1,23 @@
1
+ import type { TesterCaseRole } from "../schemas";
2
+ interface MoneyFlowCase {
3
+ key: string;
4
+ label: string;
5
+ /** Every role the case affects; per-role detail goes inline in the expectations. */
6
+ roles: TesterCaseRole[];
7
+ startPage?: string;
8
+ steps: string[];
9
+ inputs?: Record<string, string | number | boolean>;
10
+ expectedBehaviour: string;
11
+ expectedUiState: string;
12
+ expectedData?: Record<string, string | number | boolean>;
13
+ endResult: string;
14
+ href?: string;
15
+ }
16
+ interface MoneyFlowPage {
17
+ pageKey: string;
18
+ pageLabel: string;
19
+ href?: string;
20
+ cases: MoneyFlowCase[];
21
+ }
22
+ export declare const moneyFlowsPages: MoneyFlowPage[];
23
+ export {};