@mohasinac/appkit 4.42.1 → 4.42.3

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 (182) hide show
  1. package/dist/_internal/server/features/offers/adapters.d.ts +29 -7
  2. package/dist/_internal/server/features/offers/adapters.js +14 -3
  3. package/dist/_internal/server/features/orders/adapters.js +54 -1
  4. package/dist/client.d.ts +1 -0
  5. package/dist/client.js +1 -0
  6. package/dist/features/account/components/UserOrdersView.js +30 -2
  7. package/dist/features/account/hooks/useAddresses.d.ts +21 -6
  8. package/dist/features/account/hooks/useAddresses.js +1 -1
  9. package/dist/features/addresses/hooks/usePostalLookup.js +84 -12
  10. package/dist/features/admin/components/AdminCategoriesView.js +37 -2
  11. package/dist/features/admin/components/AdminOffersView.js +2 -2
  12. package/dist/features/admin/components/AdminSupportTicketsView.js +27 -1
  13. package/dist/features/admin/constants/filter-tabs.d.ts +25 -0
  14. package/dist/features/admin/constants/filter-tabs.js +30 -0
  15. package/dist/features/admin/hooks/useAdminListingData.d.ts +35 -0
  16. package/dist/features/admin/hooks/useAdminListingData.js +45 -9
  17. package/dist/features/cart/schemas/index.d.ts +15 -0
  18. package/dist/features/cart/schemas/index.js +2 -0
  19. package/dist/features/cart/types/index.d.ts +22 -0
  20. package/dist/features/categories/components/CategoriesIndexListing.js +19 -1
  21. package/dist/features/categories/components/CategoriesIndexPageView.js +14 -1
  22. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +8 -0
  23. package/dist/features/classified/components/ClassifiedDetailPageView.js +1 -0
  24. package/dist/features/events/actions/event-actions.d.ts +13 -0
  25. package/dist/features/events/actions/event-actions.js +75 -1
  26. package/dist/features/events/repository/event-entry.repository.d.ts +17 -0
  27. package/dist/features/events/repository/event-entry.repository.js +36 -0
  28. package/dist/features/homepage/components/BrandsSection.js +1 -1
  29. package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
  30. package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
  31. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  32. package/dist/features/media/upload/MediaUploadField.js +21 -3
  33. package/dist/features/orders/components/OrdersList.js +2 -1
  34. package/dist/features/orders/index.d.ts +1 -0
  35. package/dist/features/orders/index.js +1 -0
  36. package/dist/features/orders/types/index.d.ts +2 -0
  37. package/dist/features/orders/utils/order-ref.d.ts +45 -0
  38. package/dist/features/orders/utils/order-ref.js +58 -0
  39. package/dist/features/products/components/MakeOfferButton.d.ts +18 -1
  40. package/dist/features/products/components/MakeOfferButton.js +23 -1
  41. package/dist/features/products/components/ProductDetailPageView.d.ts +5 -0
  42. package/dist/features/products/components/ProductDetailPageView.js +3 -2
  43. package/dist/features/products/repository/products.repository.js +29 -2
  44. package/dist/features/seller/actions/offer-actions.js +32 -0
  45. package/dist/features/seller/components/QuickProductForm.js +15 -1
  46. package/dist/features/seller/components/SellerBidsView.js +3 -1
  47. package/dist/features/seller/components/SellerOffersView.js +2 -2
  48. package/dist/features/seller/components/SellerOrdersView.js +49 -6
  49. package/dist/features/seller/components/SellerProductsCards.js +23 -1
  50. package/dist/features/seller/components/SellerProductsView.js +27 -2
  51. package/dist/features/seller/hooks/useSellerListingData.d.ts +0 -1
  52. package/dist/features/seller/hooks/useSellerListingData.js +17 -19
  53. package/dist/features/shell/SectionForm.js +33 -2
  54. package/dist/features/tester/seed-data/_happy-path.d.ts +23 -0
  55. package/dist/features/tester/seed-data/_happy-path.js +710 -0
  56. package/dist/features/tester/seed-data/_money-flows.js +34 -23
  57. package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +22 -3
  58. package/dist/features/tester/seed-data/authored/addresses__address-filters.js +17 -12
  59. package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +7 -6
  60. package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +10 -4
  61. package/dist/features/tester/seed-data/authored/addresses__state-picker.js +13 -8
  62. package/dist/features/tester/seed-data/authored/addresses__unban-request.js +49 -11
  63. package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +10 -6
  64. package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +71 -32
  65. package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +80 -31
  66. package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.js +62 -25
  67. package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +61 -31
  68. package/dist/features/tester/seed-data/authored/admin__bundles.js +84 -37
  69. package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +90 -57
  70. package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +38 -26
  71. package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +47 -28
  72. package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +118 -31
  73. package/dist/features/tester/seed-data/authored/admin__content-deletes.js +30 -24
  74. package/dist/features/tester/seed-data/authored/admin__content-marketing.js +83 -46
  75. package/dist/features/tester/seed-data/authored/admin__coupons.js +8 -8
  76. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +9 -9
  77. package/dist/features/tester/seed-data/authored/admin__firebase-function-effects.js +19 -4
  78. package/dist/features/tester/seed-data/authored/admin__media-watermark.js +77 -33
  79. package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +5 -5
  80. package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +29 -5
  81. package/dist/features/tester/seed-data/authored/admin__site-system.js +45 -23
  82. package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +53 -28
  83. package/dist/features/tester/seed-data/authored/admin__users-trust.js +20 -20
  84. package/dist/features/tester/seed-data/authored/buying__bidding.js +19 -2
  85. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +20 -5
  86. package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +28 -10
  87. package/dist/features/tester/seed-data/authored/buying__cart.js +10 -3
  88. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +0 -16
  89. package/dist/features/tester/seed-data/authored/buying__my-orders.js +27 -5
  90. package/dist/features/tester/seed-data/authored/buying__offers.js +22 -7
  91. package/dist/features/tester/seed-data/authored/buying__order-detail-actions.js +3 -3
  92. package/dist/features/tester/seed-data/authored/buying__product-detail.js +35 -4
  93. package/dist/features/tester/seed-data/authored/buying__return-request.js +19 -6
  94. package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +36 -18
  95. package/dist/features/tester/seed-data/authored/buying__reviews.js +16 -1
  96. package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +2 -1
  97. package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +16 -1
  98. package/dist/features/tester/seed-data/authored/community-support__public-profile.js +3 -2
  99. package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +4 -4
  100. package/dist/features/tester/seed-data/authored/content-discovery__blog.js +19 -2
  101. package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +4 -4
  102. package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +5 -5
  103. package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +41 -6
  104. package/dist/features/tester/seed-data/authored/content-discovery__event-detail-subroutes.js +63 -8
  105. package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +29 -29
  106. package/dist/features/tester/seed-data/authored/content-discovery__events.js +2 -2
  107. package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +23 -1
  108. package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +6 -6
  109. package/dist/features/tester/seed-data/authored/content-discovery__search.js +1 -1
  110. package/dist/features/tester/seed-data/authored/content-discovery__store-reviews-aggregate.js +1 -1
  111. package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +31 -6
  112. package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +34 -10
  113. package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -8
  114. package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +31 -9
  115. package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +29 -3
  116. package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +1 -1
  117. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +10 -10
  118. package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +3 -3
  119. package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +2 -2
  120. package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +2 -2
  121. package/dist/features/tester/seed-data/authored/design-ux__general-design.js +4 -4
  122. package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +1 -1
  123. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +8 -8
  124. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +2 -2
  125. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +2 -2
  126. package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +39 -16
  127. package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +43 -8
  128. package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +40 -14
  129. package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +31 -14
  130. package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +7 -6
  131. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +14 -5
  132. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +3 -3
  133. package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +1 -1
  134. package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +4 -2
  135. package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +3 -3
  136. package/dist/features/tester/seed-data/authored/public-pages__newly-wired-browse-indexes.js +5 -5
  137. package/dist/features/tester/seed-data/authored/public-pages__store-tabs.js +5 -5
  138. package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +3 -3
  139. package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +45 -11
  140. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -17
  141. package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +36 -5
  142. package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +29 -4
  143. package/dist/features/tester/seed-data/authored/selling__become-seller.js +23 -0
  144. package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +14 -13
  145. package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +14 -9
  146. package/dist/features/tester/seed-data/authored/selling__listing-edit-roundtrip.js +34 -7
  147. package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +17 -7
  148. package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +9 -9
  149. package/dist/features/tester/seed-data/authored/selling__media-limits.js +9 -4
  150. package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +3 -3
  151. package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +3 -3
  152. package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +25 -1
  153. package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +27 -6
  154. package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +19 -1
  155. package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +63 -22
  156. package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +9 -5
  157. package/dist/features/tester/seed-data/authored/selling__seller-guide.js +7 -6
  158. package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +3 -2
  159. package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +18 -18
  160. package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +13 -12
  161. package/dist/features/tester/seed-data/authored/selling__seller-orders-at-scale.js +19 -3
  162. package/dist/features/tester/seed-data/authored/selling__seller-orders.js +22 -4
  163. package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +20 -8
  164. package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +4 -4
  165. package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +2 -2
  166. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +5 -5
  167. package/dist/features/tester/seed-data/authored/seo__og-images.js +1 -1
  168. package/dist/features/tester/seed-data/authored/seo__page-metadata.js +7 -7
  169. package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +3 -3
  170. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +123 -21
  171. package/dist/features/tester/utils/phases.d.ts +17 -0
  172. package/dist/features/tester/utils/phases.js +29 -2
  173. package/dist/index.d.ts +1 -0
  174. package/dist/index.js +2 -0
  175. package/dist/seo/json-ld.d.ts +23 -0
  176. package/dist/seo/json-ld.js +3 -2
  177. package/dist/server.d.ts +1 -0
  178. package/dist/server.js +2 -0
  179. package/dist/styles.css +1 -1
  180. package/dist/tailwind-utilities.css +1 -1
  181. package/dist/ui/components/PaginatedSelect.js +0 -0
  182. package/package.json +1 -1
@@ -6,7 +6,7 @@
6
6
  * what makes this one page rather than two.
7
7
  *
8
8
  * `/store/products/new` does NOT open the sectioned form. SellerProductShell.tsx
9
- * :1339 picks `formMode = "quick"` whenever mode is create and listingType is
9
+ * :1379 picks `formMode = "quick"` whenever mode is create and listingType is
10
10
  * standard, so the default is QuickProductForm — six fields: title*, category*,
11
11
  * price*, mainImage*, description, stockQuantity. The sectioned form is behind
12
12
  * "Show all fields (advanced)".
@@ -67,7 +67,7 @@ export const authored = {
67
67
  "Upload public/test-media/sample-image.png as the Product Image.",
68
68
  "Enter Description 'A short-form listing created with only the required fields present.'",
69
69
  "Click Publish and read what happens.",
70
- "Open the product's public page and read the title, price and image.",
70
+ "Open the product's public page at /products/{its slug} and read the title, price and image.",
71
71
  ],
72
72
  inputs: {
73
73
  title: "Minimum Details Probe",
@@ -122,7 +122,7 @@ export const authored = {
122
122
  SIGN_IN_SELLER,
123
123
  "Open /store/products/new and click 'Show all fields (advanced)'.",
124
124
  "Open the Basic section and read every option in the Condition dropdown, writing them all down.",
125
- "Fill Title 'Condition Probe', Description 'Checking that every condition the form offers can actually be saved.', Category 'Beyblade Burst', Price 499 and a Main Image.",
125
+ "Fill Product Name 'Condition Probe', Description 'Checking that every condition the form offers can actually be saved.', Category 'Beyblade Burst', Price 499 and a Product Image. The six labels are exactly: Product Name, Category, Price (₹), Stock Quantity, Description, Product Image — there is no field labelled Title or Main Image.",
126
126
  "Select Condition 'Like New' and click Publish.",
127
127
  "Read the result — inline error, toast, or success.",
128
128
  "Repeat with 'Good', then with 'Fair', then with 'New', reading the result each time.",
@@ -43,6 +43,7 @@ export const authored = {
43
43
  steps: [
44
44
  "Sign in as tyson@beybladearena.in / TempPass123!.",
45
45
  "Open /store/products/new and read the page before typing — note whether any errors are listed.",
46
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create — six flat fields, NO sections at all — so every section, chevron and error-summary step below needs the advanced form, which is the sectionised one this page is about.",
46
47
  "Leave required fields empty in two DIFFERENT sections.",
47
48
  "Collapse both of those sections.",
48
49
  "Attempt to publish.",
@@ -58,6 +59,7 @@ export const authored = {
58
59
  steps: [
59
60
  "Sign in as tyson@beybladearena.in / TempPass123!.",
60
61
  "Open /store/products/new and look at every section header.",
62
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create — six flat fields, NO sections at all — so every section, chevron and error-summary step below needs the advanced form, which is the sectionised one this page is about.",
61
63
  "Find any section that is always open because it is required.",
62
64
  "Look at whether it still shows an expand chevron.",
63
65
  "Click that chevron and read what happens.",
@@ -73,6 +75,7 @@ export const authored = {
73
75
  steps: [
74
76
  "Sign in as tyson@beybladearena.in / TempPass123!.",
75
77
  "Open /store/products/new and expand a section containing a picker.",
78
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create — six flat fields, NO sections at all — so every section, chevron and error-summary step below needs the advanced form, which is the sectionised one this page is about.",
76
79
  "Open that picker and read whether its full option list is visible.",
77
80
  "Scroll while it is open and check it stays anchored to its field.",
78
81
  "Open a picker in the LAST section of the form, near the bottom of the page.",
@@ -90,9 +93,28 @@ export const authored = {
90
93
  steps: [
91
94
  "Sign in as tyson@beybladearena.in / TempPass123!.",
92
95
  "Open /store/products/new and leave a required field empty in a section far down the form.",
96
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create — six flat fields, NO sections at all — so every section, chevron and error-summary step below needs the advanced form, which is the sectionised one this page is about.",
93
97
  "Collapse that section and attempt to publish.",
94
98
  "Click the error for that field in the summary.",
95
99
  "Read where the page has scrolled to and whether the field is visible.",
100
+ /*
101
+ * 🛑 EXPECT THIS TO FAIL TODAY, AND RECORD IT AS A FAILURE.
102
+ *
103
+ * Measured 2026-09-29: FormErrorList's entire click handler is
104
+ * `onClick={() => ctx.goToStep(stepIndex!)}`. There is no focus() call,
105
+ * no scrollIntoView, and no element lookup — so it opens the owning
106
+ * section and stops, which is exactly what this case calls the failure.
107
+ *
108
+ * Kept as written rather than softened to match. The rationale holds: a
109
+ * section can carry a dozen fields, and an error summary that drops the
110
+ * user at the heading has told them something is wrong without showing
111
+ * them what. Softening it to "lands on the section" would convert a real
112
+ * UX gap into a passing case.
113
+ *
114
+ * So a tester who lands on the heading records a FAILURE with a
115
+ * screenshot — not "could not test", and not a lenient pass because the
116
+ * section did open.
117
+ */
96
118
  "Check whether the field is focused and marked, not merely on screen.",
97
119
  ],
98
120
  expectedBehaviour: "Clicking an error lands ON the field — section expanded, field scrolled into view, focused and marked. Landing on the section heading leaves the user hunting through a section that may hold a dozen fields.",
@@ -105,6 +127,7 @@ export const authored = {
105
127
  steps: [
106
128
  "Sign in as tyson@beybladearena.in / TempPass123!.",
107
129
  "Open /store/products/new and expand every section.",
130
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create — six flat fields, NO sections at all — so every section, chevron and error-summary step below needs the advanced form, which is the sectionised one this page is about.",
108
131
  "Type a long paragraph into the description field at normal speed.",
109
132
  "Watch for characters lagging behind the keystrokes.",
110
133
  "Attempt a publish so validation is running live, then keep typing into another field.",
@@ -119,7 +142,7 @@ export const authored = {
119
142
  startPage: "/store/products/new",
120
143
  steps: [
121
144
  "Sign in as tyson@beybladearena.in / TempPass123!.",
122
- "Resize the browser window to 390 pixels wide and open /store/products/new.",
145
+ "Resize the browser window to 390 pixels wide, open /store/products/new, and click 'Show all fields (advanced)' — the quick form has no sections and therefore no section-scoped error sheet, which is what this case reads.",
123
146
  "Read the action bar at the bottom and every control in it.",
124
147
  "Type into a field so the form becomes dirty and read the bar again.",
125
148
  "Attempt a publish with fields missing and read what the bar shows.",
@@ -137,6 +160,7 @@ export const authored = {
137
160
  steps: [
138
161
  "Sign in as tyson@beybladearena.in / TempPass123!.",
139
162
  "Open /store/products/new and find a control that reveals conditional fields — a listing type or a shipping choice.",
163
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create — six flat fields, NO sections at all — so every section, chevron and error-summary step below needs the advanced form, which is the sectionised one this page is about.",
140
164
  "Set it so the conditional fields appear and fill them in, noting each value.",
141
165
  "Change the controlling field so those conditionals are hidden.",
142
166
  "Change it back so they reappear.",
@@ -87,14 +87,35 @@ export const authored = {
87
87
  startPage: "/store/payouts",
88
88
  steps: [
89
89
  "Sign in as tyson@beybladearena.in / TempPass123!.",
90
- "Open /store/payouts and find the payout reminder toggle.",
91
- "Read its current state.",
92
- "Turn it OFF and save if a save is required.",
93
- "RELOAD the page and read the toggle's state.",
94
- "Turn it back on, save, and reload to confirm.",
90
+ /*
91
+ * 🛑 THE TOGGLE IS PER-PAYOUT, LABELLED 'Remind me', AND SAVES ITSELF.
92
+ *
93
+ * This said "open /store/payouts and find the payout reminder toggle",
94
+ * then "turn it OFF and save if a save is required", then back on. Three
95
+ * things were wrong and each one alone would strand a tester:
96
+ *
97
+ * - it is not on the page. It lives inside a SELECTED payout's detail
98
+ * panel, so a payout has to be opened first.
99
+ * - it is labelled 'Remind me', with the helper text "Flag this payout
100
+ * for a personal follow-up". Nothing reads "reminder".
101
+ * - there is no save. handleToggleReminder PATCHes immediately and
102
+ * toasts 'Reminder set.' or 'Reminder cleared.', so waiting for a save
103
+ * button means never completing the step.
104
+ *
105
+ * And the direction was backwards: no seeded payout sets
106
+ * `sellerReminderFlag`, and the control reads
107
+ * `checked={Boolean(payout.sellerReminderFlag)}` — so it starts OFF.
108
+ * "Turn it OFF" first was a no-op that would have made the reload check
109
+ * pass trivially.
110
+ */
111
+ "Open /store/payouts and open any payout to show its detail panel.",
112
+ "Find the 'Remind me' toggle in that panel and read its current state — it starts OFF, because no seeded payout carries the flag.",
113
+ "Turn it ON. It saves immediately; look for the toast reading 'Reminder set.' rather than a save button.",
114
+ "RELOAD the page, reopen the SAME payout, and read the toggle's state.",
115
+ "Turn it back off, confirming the toast reads 'Reminder cleared.', and reload once more to confirm.",
95
116
  ],
96
117
  expectedBehaviour: "The preference persists server-side. Off is the direction worth testing when the default is on: a toggle that never saves reads back as its default and passes a test that only ever turns it on.",
97
- expectedUiState: "After the reload the toggle is still off. A toggle that has reverted to on is the failure, and the save will have reported success.",
118
+ expectedUiState: "After the reload the reopened payout's 'Remind me' toggle is still ON. A toggle that has reverted to off is the failure — and the PATCH will have reported success and toasted 'Reminder set.', which is exactly what makes it invisible without the reload.",
98
119
  expectedData: { reminderEnabled: false },
99
120
  endResult: "The toggle is back on by the final step, so payout reminders are not left suppressed.",
100
121
  },
@@ -73,16 +73,34 @@ export const authored = {
73
73
  startPage: "/store/bundles",
74
74
  steps: [
75
75
  "Sign in as tyson@beybladearena.in / TempPass123!.",
76
+ /*
77
+ * 🛑 THE SELLER OWNS NO SEEDED BUNDLE, SO THE CASE HAS TO MAKE ONE.
78
+ *
79
+ * /store/bundles filters on `b.createdByStoreId === store.id`, and
80
+ * `createdByStoreId` appears ZERO times across every seed file — all 11
81
+ * seeded bundles are `createdByType: "admin"`, authored by
82
+ * user-admin-letitrip. They are platform bundles and correctly invisible
83
+ * here.
84
+ *
85
+ * So the page is empty for every seller, and a case that opens it and
86
+ * "notes the total row count" could only ever be answered "could not
87
+ * test". Creating one first makes the case self-sufficient, and the
88
+ * created row is disposable: `categories` is SEED_OWNED, so the next
89
+ * lifecycle wipe removes it.
90
+ */
91
+ "Open /store/bundles/new and create a bundle named 'QA Bundle active-filter' with at least two of this store's products as members, leaving it Active.",
92
+ "Create a second one named 'QA Bundle inactive-probe' and set it Inactive.",
76
93
  "Open the store's bundles page and note the total row count.",
77
94
  "Read the active filter's options.",
78
95
  "Select active and read the rows and their states.",
79
96
  "Select inactive and read the rows and their states.",
80
97
  "Confirm the two counts add up to the unfiltered total.",
81
98
  "Open one bundle and check its member list is populated rather than reading zero items.",
99
+ "Delete both QA bundles afterwards.",
82
100
  ],
83
101
  expectedBehaviour: "The two filter states partition the list, so their counts sum to the total. The member check matters separately: a bundle's members are mirrored onto the bundle for index-friendly reads, and a reader trusting only that mirror shows an empty bundle whenever a write path forgot to update it.",
84
102
  expectedUiState: "Active and inactive each return their own rows and the counts sum to the unfiltered total. The opened bundle lists its members with titles and images rather than reading '0 items'.",
85
103
  expectedData: { partitionSumsToTotal: true },
86
- endResult: "Read-only; nothing persists beyond the URL.",
104
+ endResult: "Both QA bundles are deleted, leaving the store's bundle list as it was — empty, since no seeded bundle belongs to a store. If the delete fails, say so: `categories` is SEED_OWNED and the next lifecycle wipe clears them anyway, so a leftover is untidy rather than harmful.",
87
105
  },
88
106
  };
@@ -1,16 +1,34 @@
1
1
  /*
2
2
  * WHY: Authored six-part procedures for the selling/seller-catalog-org page.
3
- * WHAT: 7 case(s), keyed by full checklist id.
3
+ * WHAT: 8 case(s), keyed by full checklist id.
4
4
  *
5
5
  * Written by hand, case by case, against the label each one states. There is no
6
6
  * generator: the authoring scripts were deleted once it was clear they were
7
7
  * scaffolding around work that is simply writing.
8
8
  *
9
+ * 🛑 THREE DIFFERENT THINGS HERE ARE ALL CALLED "CATEGORY", and conflating them is
10
+ * how the first version of this file described the wrong object twice:
11
+ *
12
+ * /store/categories STOREFRONT shelves. Own collection
13
+ * (`storeCategoriesRepository`), scoped to one
14
+ * store, and the field is **Label**, not Name.
15
+ * /store/sublisting-categories the seller's sub-groupings, with DERIVED page
16
+ * metadata that must be recomputed on rename.
17
+ * the product form's picker the GLOBAL taxonomy. Its inline-create posts to
18
+ * the admin categories endpoint — which admits
19
+ * sellers deliberately (ROLES_STORE_WRITE, with a
20
+ * comment saying it is for this picker).
21
+ *
9
22
  * 🛑 A PRODUCT CARRIES ITS FULL ANCESTOR CHAIN so a parent category page can match
10
- * on its own id alone. Nothing on the WRITE side derives that chain yet — the
11
- * convention is maintained by hand in the seed — so a product filed through a form
12
- * against a deep category gets a single-slug array and is invisible on every
13
- * ancestor page. The first category case checks exactly that.
23
+ * on its own id alone — and since 2026-09-14 that chain IS derived on write, by
24
+ * `ProductRepository.deriveTaxonomy()`, from both `create` and `update`. This file
25
+ * used to say the opposite and instruct the tester to REPORT a one-element chain as
26
+ * a known gap; that is now a false bug report. `deep-category-chain-derived` is the
27
+ * case that can actually fail if the derivation regresses.
28
+ *
29
+ * The inline-create form sends only name/slug/description/isActive — there is **no
30
+ * parent field** — so a category created that way is always a root and correctly
31
+ * shows exactly one link. One link there is arithmetic, not a defect.
14
32
  *
15
33
  * @tag domain:tester
16
34
  * @tag layer:seed
@@ -25,19 +43,19 @@ export const authored = {
25
43
  startPage: "/store/categories",
26
44
  steps: [
27
45
  "Sign in as tyson@beybladearena.in / TempPass123!.",
28
- "Open the store's categories page and read the list.",
29
- "Create one named 'QA Category catalog-org' and save.",
30
- "RELOAD and confirm it is listed.",
31
- "Rename it to 'QA Category catalog-org renamed' and save, then RELOAD and read the name.",
46
+ "Open the store's categories page and read the list. These are STOREFRONT shelves scoped to this store — not the global taxonomy the product form's category picker offers.",
47
+ "Create one and save. The field is labelled 'Label', not 'Name' — type 'QA Shelf catalog-org' into it. Leave the slug blank, it is optional on create.",
48
+ "RELOAD and confirm it is listed under that label.",
49
+ "Change the Label to 'QA Shelf catalog-org renamed' WITHOUT touching the slug field, save, then RELOAD and read both the label and the slug.",
32
50
  "Delete it and RELOAD to confirm it is gone.",
33
51
  ],
34
52
  inputs: {
35
- name: "QA Category catalog-org",
36
- renamed: "QA Category catalog-org renamed",
53
+ label: "QA Shelf catalog-org",
54
+ renamed: "QA Shelf catalog-org renamed",
37
55
  },
38
- expectedBehaviour: "Create, rename and delete all persist. The rename is the one worth watching: a create handler that derives a slug from the name while the update handler leaves the slug alone is the deliberate convention here, so the NAME must change while the URL stays stable.",
39
- expectedUiState: "Each operation survives its reload. After the rename the new name is shown; the category's own URL is unchanged, which is correct rather than a bug — an auto-recomputed slug would break every existing link.",
40
- endResult: "The category is deleted by the final step.",
56
+ expectedBehaviour: "Create, rename and delete all persist against this store. The rename is the one worth watching: slug is its own editable field here rather than something derived from the label, so editing the label alone must leave the slug exactly as it was — a slug that silently re-derives would break every link to the shelf.",
57
+ expectedUiState: "Each operation survives its reload. After the rename the new label is shown and the slug is byte-identical to what it was before. A validation error naming a field the form does not show is also a failure — the create schema requires only the label.",
58
+ endResult: "The shelf is deleted by the final step. Nothing in the global category taxonomy is touched by this case.",
41
59
  },
42
60
  "checklist-selling-seller-catalog-org-seller-sublisting-categories-crud": {
43
61
  roles: ["seller"],
@@ -118,7 +136,7 @@ export const authored = {
118
136
  "Type 'QA Product inline-category' as the title, select that category, type 400 as the price, and publish.",
119
137
  "Open the product's public page and read its category links below the title.",
120
138
  "Count how many category links are shown.",
121
- "Click the one naming QA Category inline-create.",
139
+ "Click the one naming QA Category inline-create and confirm the product is listed on the page it opens.",
122
140
  "Delete the product and the category afterwards.",
123
141
  ],
124
142
  inputs: {
@@ -126,10 +144,33 @@ export const authored = {
126
144
  title: "QA Product inline-category",
127
145
  price: 400,
128
146
  },
129
- expectedBehaviour: "The category persists and the product is filed under it. The product's category list is supposed to be the full ancestor chain, and nothing on the write side derives that chain today — so a product created through this form gets a single slug and is unreachable from every ancestor page.",
130
- expectedUiState: "The category is offered after a reload and the product's public page links it. Record how many category links the product shows: ONE, where the chosen category has ancestors, is the known gap this case is here to make visible rather than a surprise.",
147
+ expectedBehaviour: "The category persists across a reload and the product is genuinely filed under it — reachable from the category's own page, not merely named on the product.",
148
+ expectedUiState: "The category is offered after a reload and the product's public page links it. EXACTLY ONE link is correct here and is not a defect: the inline-create form sends only name, slug, description and active — it has no parent field — so the category it makes is a root with no ancestors to list. A product under a DEEP category is a different assertion and has its own case.",
131
149
  expectedData: { categoryLinksShown: 1 },
132
- endResult: "The product and the category are deleted by the final step. If only one link appears, report it — the ancestor chain being seed-only is a documented outstanding follow-up.",
150
+ endResult: "The product and the category are deleted by the final step. Do not report the single link as a missing ancestor chain — that was this case's own stale claim and it is wrong.",
151
+ },
152
+ "checklist-selling-seller-catalog-org-deep-category-chain-derived": {
153
+ roles: ["seller", "guest"],
154
+ startPage: "/store/products/new",
155
+ steps: [
156
+ "Sign in as tyson@beybladearena.in / TempPass123!.",
157
+ "Open /store/products/new and search the category picker for 'Heavy Metal System' — a seeded tier-3 category whose ancestors are Original Tops, Beyblade Original and Spinning Tops.",
158
+ "Select it, type 'QA Product deep-chain' as the title and 500 as the price, and publish.",
159
+ "Open the product's public page and count the category links below the title.",
160
+ "Read the names in order and compare them against the four expected.",
161
+ "Open /categories/category-spinning-tops — the ROOT, three levels above the category chosen — and look for this product.",
162
+ "Delete the product afterwards.",
163
+ ],
164
+ inputs: {
165
+ categoryName: "Heavy Metal System",
166
+ categoryId: "category-original-hms",
167
+ title: "QA Product deep-chain",
168
+ price: 500,
169
+ },
170
+ expectedBehaviour: "A product filed against a deep category is written with its FULL ancestor chain, so every ancestor page matches on its own id alone. The repository derives this on create and update; a form payload cannot supply it, because the create schema strips any categorySlugs it is sent. This is the assertion that fails if that derivation regresses — and the failure is silent, because the product's own page looks completely normal with one link.",
171
+ expectedUiState: "FOUR category links: Heavy Metal System, Original Tops, Beyblade Original, Spinning Tops. The product appears on the root Spinning Tops page. One link, or a root page that does not list it, is the failure — and note that every SEEDED product shows four, so a new listing showing one is a write-path defect rather than a display one.",
172
+ expectedData: { categoryLinksShown: 4, foundOnRootCategoryPage: true },
173
+ endResult: "The product is deleted by the final step. Check the root page before deleting — that is the half a tester is most likely to skip, and it is the half that proves the chain is queryable rather than merely printed.",
133
174
  },
134
175
  "checklist-selling-seller-catalog-org-seller-category-inline-create-duplicate-rejected": {
135
176
  roles: ["seller"],
@@ -138,13 +179,13 @@ export const authored = {
138
179
  "Sign in as tyson@beybladearena.in / TempPass123!.",
139
180
  "Open /store/products/new and open the category picker.",
140
181
  "Use the create control and type the name of a category that already exists — Beyblade Burst.",
141
- "Save and read the message.",
182
+ "Save and read the message shown on the form.",
142
183
  "Open the picker and search that name, counting the matching options.",
143
- "Try again with a case variant: beyblade BURST.",
184
+ "Try again with a case variant: beyblade BURST. The form derives the slug by lowercasing and hyphenating, so this collides on the SAME slug and must be refused for the same reason.",
144
185
  ],
145
186
  inputs: { existingCategory: "Beyblade Burst", caseVariant: "beyblade BURST" },
146
- expectedBehaviour: "A duplicate category is refused. Categories are matched by id on the read side rather than by name, so a duplicate is less immediately destructive than a duplicate brand — but it splits a seller's own catalogue across two near-identical entries with no way to merge them.",
147
- expectedUiState: "Both attempts are refused with a readable message, and exactly one option matches the name in the picker.",
187
+ expectedBehaviour: "A duplicate category is refused on its SLUG, which is what makes the case variant collide too. Categories are matched by id on the read side rather than by name, so a duplicate is less immediately destructive than a duplicate brand — but it splits a seller's own catalogue across two near-identical entries with no way to merge them.",
188
+ expectedUiState: "Both attempts are refused with a readable message — the server answers 409 'A category with this slug already exists' — and exactly one option matches the name in the picker. A refusal that surfaces only as a silent no-op, or as a raw 409 with no message on the form, is a failure even though nothing was created.",
148
189
  expectedData: { matchingOptionCount: 1 },
149
190
  endResult: "Leave the editor without saving.",
150
191
  },
@@ -25,6 +25,7 @@ export const authored = {
25
25
  steps: [
26
26
  "Sign in as tyson@beybladearena.in / TempPass123!.",
27
27
  "Open /store/products/new and find the brand picker.",
28
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create, and its six fields are Product Name, Category, Price (₹), Stock Quantity, Description and Product Image — there is NO brand field on it at all. The brand picker exists only on the advanced form.",
28
29
  "Type QA Brand inline-create into its search box.",
29
30
  "Read what the picker offers when nothing matches.",
30
31
  "Use the create control the picker offers and fill the form it opens.",
@@ -41,10 +42,11 @@ export const authored = {
41
42
  steps: [
42
43
  "Sign in as tyson@beybladearena.in / TempPass123!.",
43
44
  "Open /store/products/new and open the brand picker.",
45
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create, and its six fields are Product Name, Category, Price (₹), Stock Quantity, Description and Product Image — there is NO brand field on it at all. The brand picker exists only on the advanced form.",
44
46
  "Search for QA Brand inline-create.",
45
47
  "Read whether it is offered as an existing option.",
46
48
  "Reload the page and search for it again.",
47
- "Open a different creation form — the auction one — and search for it there.",
49
+ "Open /store/auctions/new — a different creation form — and search for it there. Note that route exists even though /store/auctions itself is a redirect-only shim to the products list.",
48
50
  ],
49
51
  inputs: { brandName: "QA Brand inline-create" },
50
52
  expectedBehaviour: "The brand was written to the shared categories collection as a brand-typed row, so it is available to every form that reads brands, not only the one that created it. A value that exists only in the creating form's local state disappears on reload.",
@@ -57,6 +59,7 @@ export const authored = {
57
59
  steps: [
58
60
  "Sign in as tyson@beybladearena.in / TempPass123!.",
59
61
  "Open /store/products/new and open the brand picker.",
62
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create, and its six fields are Product Name, Category, Price (₹), Stock Quantity, Description and Product Image — there is NO brand field on it at all. The brand picker exists only on the advanced form.",
60
63
  "Use the create control and type QA Brand inline-create — the name that already exists.",
61
64
  "Save and read the message.",
62
65
  "Open the picker again and search that name.",
@@ -75,10 +78,11 @@ export const authored = {
75
78
  steps: [
76
79
  "Sign in as tyson@beybladearena.in / TempPass123!.",
77
80
  "Open /store/products/new and type 'QA Product custom-brand' as the title.",
81
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create, and its six fields are Product Name, Category, Price (₹), Stock Quantity, Description and Product Image — there is NO brand field on it at all. The brand picker exists only on the advanced form.",
78
82
  "Select QA Brand inline-create in the brand picker.",
79
83
  "Type 500 as the price, fill every other required field, and publish.",
80
84
  "RELOAD the product's editor and read the brand.",
81
- "Open the product's public page and read the brand link below the title.",
85
+ "Open the product's public page at /products/{its slug} and read the brand link below the title.",
82
86
  "Click that link.",
83
87
  ],
84
88
  inputs: { title: "QA Product custom-brand", brandName: "QA Brand inline-create", price: 500 },
@@ -94,12 +98,12 @@ export const authored = {
94
98
  "Look for QA Brand inline-create in the list.",
95
99
  "Open its brand page.",
96
100
  "Read the product grid and look for 'QA Product custom-brand'.",
97
- "Sign in as tyson@beybladearena.in / TempPass123! and delete the QA product, then the QA brand.",
101
+ "Open the sort control and choose 'Most Products'. ALL brands should be listed, seeded ones included — Takara-Tomy, Beyblade, Hasbro and Independent Keepers. The seed file writes no `metrics` on brand rows, but `onProductWrite` is a documentWritten trigger on products/{productId}: it fires on seed writes too and increments `metrics.productCount` on the brand as well as the category, creating the field. A brand MISSING from this sort means the trigger did not run for its products.",
98
102
  ],
99
103
  inputs: { brandName: "QA Brand inline-create", productTitle: "QA Product custom-brand" },
100
104
  expectedBehaviour: "A seller-created brand becomes a real public brand page listing its products. The match is on display name, so this page is where a name-versus-slug mismatch finally shows — the brand page renders and its grid is empty, which reads as a brand with no stock rather than as a broken link.",
101
- expectedUiState: "The brand is listed on /brands and its page shows 'QA Product custom-brand' in the grid. An empty grid on a brand that demonstrably has a product is the failure this whole page builds toward.",
105
+ expectedUiState: "The brand is listed on /brands and its page shows 'QA Product custom-brand' in the grid. An empty grid on a brand that demonstrably has a product is the failure this whole page builds toward. Under 'Most Products' every brand with products is listed, seeded ones included. An orderBy does silently exclude a document lacking the ordering field, so a brand absent HERE is real evidence that onProductWrite never ran for its products — which is why this sort is worth opening at all.",
102
106
  expectedData: { productsOnBrandPage: 1 },
103
- endResult: "Both the product and the brand are deleted by the final step, so nothing accumulates across runs.",
107
+ endResult: "NOTHING is cleaned up here and that is deliberate: this case is signed out, and the harness gives a guest batch no session at all, so a 'sign in and delete' step could never be performed. The QA product and brand are removed by the seller-side case that created them; if this case runs alone, say so and leave them.",
104
108
  },
105
109
  };
@@ -16,17 +16,18 @@
16
16
  export const authored = {
17
17
  "checklist-selling-seller-guide-seller-guide-pages": {
18
18
  roles: ["seller"],
19
- startPage: "/store",
19
+ startPage: "/store/guide",
20
20
  steps: [
21
21
  "Sign in as tyson@beybladearena.in / TempPass123!.",
22
- "Open /store and find the guide or help entries in the sidebar.",
23
- "Open each guide page in turn and read its content.",
22
+ "Open /store and find the 'Guides' group in the sidebar. It is the LAST group and it defaults CLOSED (defaultOpen: false), so expand it — seven entries: All Guides, Listings Guide, Orders Guide, Finance Guide, Settings, Capabilities, WhatsApp Catalog Sync.",
23
+ "Click 'All Guides' to reach /store/guide. It is a hub and it links the same six sub-guides, so each one has two routes in — check both agree.",
24
+ "Open each of the six in turn and read its content: /store/guide/listings, /store/guide/orders, /store/guide/finance, /store/guide/settings, /store/guide/capabilities, /store/guide/whatsapp.",
24
25
  "Write down any that renders empty, 404s, or repeats another page's text.",
25
- "Check each guide against the feature it describes — open that feature and confirm the guide's instructions match what is on screen.",
26
+ "Check each guide against the feature it describes — open that feature and confirm the guide's instructions match what is on screen. The WhatsApp guide is the one to read hardest: no seeded store carries a whatsappConfig, so a guide describing a configured integration describes a state no tester can reach.",
26
27
  "Follow every link inside the guides and note where each lands.",
27
28
  ],
28
- expectedBehaviour: "Every seller guide renders its own content and every link in it resolves. The fifth step is the one that matters most: a guide describing a screen that has since changed is worse than no guide, because the seller trusts it and then cannot find what it names.",
29
- expectedUiState: "Each guide shows a heading and body specific to its topic, with no two identical. Every internal link lands on a real page. Instructions match the current screens — a guide naming a control that no longer exists is the finding, quoted exactly.",
29
+ expectedBehaviour: "Every seller guide renders its own content and every link in it resolves. A guide describing a screen that has since changed is worse than no guide, because the seller trusts it and then cannot find what it names. The sidebar group defaulting closed is deliberate — guides are reference, not daily work — so a tester who does not expand it will wrongly conclude they are missing.",
30
+ expectedUiState: "All six guides show a heading and body specific to their topic, with no two identical, and both the sidebar group and the hub reach all of them. Every internal link lands on a real page. Instructions match the current screens — a guide naming a control that no longer exists is the finding, quoted exactly.",
30
31
  expectedData: { brokenGuideLinks: 0 },
31
32
  endResult: "Read-only; nothing persists. Report stale instructions with the guide's name and the sentence that is wrong, not just that the page loads.",
32
33
  },
@@ -88,7 +88,8 @@ export const authored = {
88
88
  startPage: "/store/auctions",
89
89
  steps: [
90
90
  "Sign in as tyson@beybladearena.in / TempPass123!.",
91
- "Open the store's auctions listing and read each row.",
91
+ "Open /store/auctions — it redirects to /store/products?listingType=auction, which is the auctions listing; landing there is correct, not a wrong turn.",
92
+ "Read each row.",
92
93
  "Check each shows the current bid, the bid count and time remaining.",
93
94
  "Compare one row's figures against that auction's public page.",
94
95
  "Find an ended auction and read what its row shows in place of time remaining.",
@@ -310,7 +311,7 @@ export const authored = {
310
311
  startPage: "/store/prize-draws",
311
312
  steps: [
312
313
  "Sign in as tyson@beybladearena.in / TempPass123!.",
313
- "Open the store's prize-draws page and create one titled 'QA Prize Draw seller-crud' at 75 per entry, 20 entries, instant-reveal mode.",
314
+ "Open /store/prize-draws — it redirects to /store/products?listingType=prize-draw, which is the prize-draws listing. Create one titled 'QA Prize Draw seller-crud' at 75 per entry, 20 entries, instant-reveal mode.",
314
315
  "Save, RELOAD, and read the mode, the per-entry price and the entry count.",
315
316
  "Open the public page and read the purchase panel's wording.",
316
317
  "Check the panel describes buying an ENTRY rather than buying the prize.",
@@ -25,7 +25,7 @@ export const authored = {
25
25
  startPage: "/store/offers",
26
26
  steps: [
27
27
  "Sign in as tyson@beybladearena.in / TempPass123!.",
28
- "Open the store's offers list and read every row — product, offered amount, listed price, status and round.",
28
+ "Open /store/offers and read every row — product, offered amount, listed price, status and round.",
29
29
  "Read every status filter offered and select each in turn, noting any that returns nothing.",
30
30
  "For each empty filter, check the unfiltered list for rows carrying that status.",
31
31
  "Open one offer and read its detail, including any earlier rounds.",
@@ -40,11 +40,11 @@ export const authored = {
40
40
  startPage: "/store/features",
41
41
  steps: [
42
42
  "Sign in as tyson@beybladearena.in / TempPass123!.",
43
- "Open the store's product-features page and read the existing features.",
43
+ "Open /store/features and read the existing features.",
44
44
  "Create one named 'QA Feature marketing-extras' with an icon and a description, and save.",
45
45
  "RELOAD and read every field.",
46
- "Open a product editor and attach that feature to 'Beyblade Burst B-01 Valkyrie', then save.",
47
- "Open the product's public page and read whether the feature is shown.",
46
+ "Open /store/products, find 'Beyblade Burst B-01 Valkyrie', open its editor, attach that feature and save.",
47
+ "Open the product's public page at /products/{its slug} and read whether the feature is shown.",
48
48
  "Detach it, then delete the feature.",
49
49
  ],
50
50
  inputs: { name: "QA Feature marketing-extras", productId: "product-beyblade-burst-valkyrie" },
@@ -54,10 +54,10 @@ export const authored = {
54
54
  },
55
55
  "checklist-selling-seller-marketing-extras-seller-google-reviews-sync": {
56
56
  roles: ["seller"],
57
- startPage: "/store",
57
+ startPage: "/store/google-reviews",
58
58
  steps: [
59
59
  "Sign in as tyson@beybladearena.in / TempPass123!.",
60
- "Open the store's Google reviews sync surface and read what it asks for.",
60
+ "Open /store/google-reviews and read what it asks for.",
61
61
  "Read whether it states that credentials are required and which ones.",
62
62
  "Trigger a sync without credentials configured and read the message.",
63
63
  "Read whether any error mentions a provider, a key, or a raw response.",
@@ -69,10 +69,10 @@ export const authored = {
69
69
  },
70
70
  "checklist-selling-seller-marketing-extras-seller-whatsapp-catalog": {
71
71
  roles: ["seller"],
72
- startPage: "/store",
72
+ startPage: "/store/whatsapp",
73
73
  steps: [
74
74
  "Sign in as tyson@beybladearena.in / TempPass123!.",
75
- "Open the store's WhatsApp catalog surface and read every control.",
75
+ "Open /store/whatsapp and read every control.",
76
76
  "Read what it says about the connection state.",
77
77
  "Read whether it names the business account and catalog it would sync with.",
78
78
  "Trigger a catalog action without a token configured and read the message.",
@@ -83,15 +83,15 @@ export const authored = {
83
83
  },
84
84
  "checklist-selling-seller-marketing-extras-seller-whatsapp-token-save": {
85
85
  roles: ["seller", "guest"],
86
- startPage: "/store",
86
+ startPage: "/store/whatsapp",
87
87
  steps: [
88
88
  "Sign in as tyson@beybladearena.in / TempPass123!.",
89
- "Open the store's WhatsApp settings and type QA-WHATSAPP-TOKEN-118427 into the access-token field.",
90
- "Save and RELOAD, then read how the token is displayed.",
89
+ "Open /store/whatsapp and type QA-WHATSAPP-TOKEN-118427 into the access-token field.",
90
+ "Save and RELOAD, then read how the token is displayed — a set token renders as a mask (••••••), never as the value typed.",
91
91
  "Open the browser's View Source on that settings page and search for QA-WHATSAPP-TOKEN-118427.",
92
92
  "Open /stores/store-beyblade-arena in a private window with no session.",
93
93
  "Open View Source there and search for QA-WHATSAPP-TOKEN-118427 and for accessToken.",
94
- "Clear the token field, save, and reload to confirm it is gone.",
94
+ "Clear the token field, save, and reload to confirm it is gone. Clearing is the correct ending here, unusually: no seeded store carries a whatsappConfig, so there is no prior value to restore and the field started empty.",
95
95
  ],
96
96
  inputs: { token: "QA-WHATSAPP-TOKEN-118427" },
97
97
  expectedBehaviour: "The token is stored double-encrypted and shown masked to its owner. Because it is decrypted on every read of the store document, the real risk is a page that passes that document to a client component — which serialises it into the public page HTML. The PUBLIC page source is therefore the assertion, not the masked field.",
@@ -101,10 +101,10 @@ export const authored = {
101
101
  },
102
102
  "checklist-selling-seller-marketing-extras-seller-whatsapp-import-runs-in-background": {
103
103
  roles: ["seller"],
104
- startPage: "/store",
104
+ startPage: "/store/whatsapp",
105
105
  steps: [
106
106
  "Sign in as tyson@beybladearena.in / TempPass123!.",
107
- "Open the store's WhatsApp catalog surface and start an import.",
107
+ "Open /store/whatsapp and start an import.",
108
108
  "Read what the page shows immediately — whether it blocks or returns.",
109
109
  "Navigate to another store page and back.",
110
110
  "Read whether the import's progress is still reported.",
@@ -116,10 +116,10 @@ export const authored = {
116
116
  },
117
117
  "checklist-selling-seller-marketing-extras-seller-whatsapp-import-skips-already-synced": {
118
118
  roles: ["seller"],
119
- startPage: "/store",
119
+ startPage: "/store/whatsapp",
120
120
  steps: [
121
121
  "Sign in as tyson@beybladearena.in / TempPass123!.",
122
- "Open the store's WhatsApp catalog surface and run an import, noting the summary and the store's product count.",
122
+ "Open /store/whatsapp and run an import, noting the summary and the store's product count.",
123
123
  "Run the SAME import a second time without changing anything.",
124
124
  "Read the second summary and compare it against the first.",
125
125
  "Open /store/products and read the product count again.",
@@ -128,7 +128,7 @@ export const authored = {
128
128
  expectedBehaviour: "A second import skips what is already synced rather than creating duplicates. This is the same idempotency the seed data needs: an import keyed on a stable external identifier upserts, while one keyed on anything regenerated per run creates a fresh copy every time and reports success.",
129
129
  expectedUiState: "The second summary reports items skipped rather than a second full import. The product count is unchanged and no title appears twice.",
130
130
  expectedData: { duplicateTitles: 0 },
131
- endResult: "No duplicates exist. A doubled product count after a repeated import is the failure, and the summary will still have read as successful.",
131
+ endResult: "No duplicates exist. A doubled product count after a repeated import is the failure, and the summary will still have read as successful. NOTE: no seeded store carries a whatsappConfig at all, so with no Meta credentials configured BOTH imports will decline and there is nothing to compare — record that and answer null rather than reading two declines as a pass. Two identical declines are not evidence that duplicates are skipped.",
132
132
  },
133
133
  "checklist-selling-seller-marketing-extras-seller-whatsapp-push-product-link-opens": {
134
134
  roles: ["seller"],
@@ -151,7 +151,7 @@ export const authored = {
151
151
  startPage: "/store/reviews",
152
152
  steps: [
153
153
  "Sign in as tyson@beybladearena.in / TempPass123!.",
154
- "Open the store's reviews list and open a review with no seller response.",
154
+ "Open /store/reviews and open a review with no seller response.",
155
155
  "Type 'QA Response seller-reviews-response — thanks for the feedback.' as the response and save.",
156
156
  "RELOAD and read the response.",
157
157
  "Open that review on the product's public page in a private window.",