@mohasinac/appkit 4.42.1 → 4.42.2

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 (181) 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/constants/filter-tabs.d.ts +25 -0
  13. package/dist/features/admin/constants/filter-tabs.js +30 -0
  14. package/dist/features/admin/hooks/useAdminListingData.d.ts +35 -0
  15. package/dist/features/admin/hooks/useAdminListingData.js +45 -9
  16. package/dist/features/cart/schemas/index.d.ts +15 -0
  17. package/dist/features/cart/schemas/index.js +2 -0
  18. package/dist/features/cart/types/index.d.ts +22 -0
  19. package/dist/features/categories/components/CategoriesIndexListing.js +19 -1
  20. package/dist/features/categories/components/CategoriesIndexPageView.js +14 -1
  21. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +8 -0
  22. package/dist/features/classified/components/ClassifiedDetailPageView.js +1 -0
  23. package/dist/features/events/actions/event-actions.d.ts +13 -0
  24. package/dist/features/events/actions/event-actions.js +75 -1
  25. package/dist/features/events/repository/event-entry.repository.d.ts +17 -0
  26. package/dist/features/events/repository/event-entry.repository.js +36 -0
  27. package/dist/features/homepage/components/BrandsSection.js +1 -1
  28. package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
  29. package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
  30. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  31. package/dist/features/media/upload/MediaUploadField.js +21 -3
  32. package/dist/features/orders/components/OrdersList.js +2 -1
  33. package/dist/features/orders/index.d.ts +1 -0
  34. package/dist/features/orders/index.js +1 -0
  35. package/dist/features/orders/types/index.d.ts +2 -0
  36. package/dist/features/orders/utils/order-ref.d.ts +45 -0
  37. package/dist/features/orders/utils/order-ref.js +58 -0
  38. package/dist/features/products/components/MakeOfferButton.d.ts +18 -1
  39. package/dist/features/products/components/MakeOfferButton.js +23 -1
  40. package/dist/features/products/components/ProductDetailPageView.d.ts +5 -0
  41. package/dist/features/products/components/ProductDetailPageView.js +3 -2
  42. package/dist/features/products/repository/products.repository.js +29 -2
  43. package/dist/features/seller/actions/offer-actions.js +32 -0
  44. package/dist/features/seller/components/QuickProductForm.js +15 -1
  45. package/dist/features/seller/components/SellerBidsView.js +3 -1
  46. package/dist/features/seller/components/SellerOffersView.js +2 -2
  47. package/dist/features/seller/components/SellerOrdersView.js +49 -6
  48. package/dist/features/seller/components/SellerProductsCards.js +23 -1
  49. package/dist/features/seller/components/SellerProductsView.js +27 -2
  50. package/dist/features/seller/hooks/useSellerListingData.d.ts +0 -1
  51. package/dist/features/seller/hooks/useSellerListingData.js +17 -19
  52. package/dist/features/shell/SectionForm.js +33 -2
  53. package/dist/features/tester/seed-data/_happy-path.d.ts +23 -0
  54. package/dist/features/tester/seed-data/_happy-path.js +710 -0
  55. package/dist/features/tester/seed-data/_money-flows.js +34 -23
  56. package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +22 -3
  57. package/dist/features/tester/seed-data/authored/addresses__address-filters.js +17 -12
  58. package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +7 -6
  59. package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +10 -4
  60. package/dist/features/tester/seed-data/authored/addresses__state-picker.js +13 -8
  61. package/dist/features/tester/seed-data/authored/addresses__unban-request.js +49 -11
  62. package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +10 -6
  63. package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +71 -32
  64. package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +80 -31
  65. package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.js +62 -25
  66. package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +61 -31
  67. package/dist/features/tester/seed-data/authored/admin__bundles.js +84 -37
  68. package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +90 -57
  69. package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +38 -26
  70. package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +47 -28
  71. package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +118 -31
  72. package/dist/features/tester/seed-data/authored/admin__content-deletes.js +30 -24
  73. package/dist/features/tester/seed-data/authored/admin__content-marketing.js +83 -46
  74. package/dist/features/tester/seed-data/authored/admin__coupons.js +8 -8
  75. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +9 -9
  76. package/dist/features/tester/seed-data/authored/admin__firebase-function-effects.js +19 -4
  77. package/dist/features/tester/seed-data/authored/admin__media-watermark.js +77 -33
  78. package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +5 -5
  79. package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +29 -5
  80. package/dist/features/tester/seed-data/authored/admin__site-system.js +45 -23
  81. package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +53 -28
  82. package/dist/features/tester/seed-data/authored/admin__users-trust.js +20 -20
  83. package/dist/features/tester/seed-data/authored/buying__bidding.js +19 -2
  84. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +20 -5
  85. package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +28 -10
  86. package/dist/features/tester/seed-data/authored/buying__cart.js +10 -3
  87. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +0 -16
  88. package/dist/features/tester/seed-data/authored/buying__my-orders.js +27 -5
  89. package/dist/features/tester/seed-data/authored/buying__offers.js +22 -7
  90. package/dist/features/tester/seed-data/authored/buying__order-detail-actions.js +3 -3
  91. package/dist/features/tester/seed-data/authored/buying__product-detail.js +35 -4
  92. package/dist/features/tester/seed-data/authored/buying__return-request.js +19 -6
  93. package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +36 -18
  94. package/dist/features/tester/seed-data/authored/buying__reviews.js +16 -1
  95. package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +2 -1
  96. package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +16 -1
  97. package/dist/features/tester/seed-data/authored/community-support__public-profile.js +3 -2
  98. package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +4 -4
  99. package/dist/features/tester/seed-data/authored/content-discovery__blog.js +19 -2
  100. package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +4 -4
  101. package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +5 -5
  102. package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +41 -6
  103. package/dist/features/tester/seed-data/authored/content-discovery__event-detail-subroutes.js +63 -8
  104. package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +29 -29
  105. package/dist/features/tester/seed-data/authored/content-discovery__events.js +2 -2
  106. package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +23 -1
  107. package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +6 -6
  108. package/dist/features/tester/seed-data/authored/content-discovery__search.js +1 -1
  109. package/dist/features/tester/seed-data/authored/content-discovery__store-reviews-aggregate.js +1 -1
  110. package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +31 -6
  111. package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +34 -10
  112. package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -8
  113. package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +31 -9
  114. package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +29 -3
  115. package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +1 -1
  116. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +10 -10
  117. package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +3 -3
  118. package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +2 -2
  119. package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +2 -2
  120. package/dist/features/tester/seed-data/authored/design-ux__general-design.js +4 -4
  121. package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +1 -1
  122. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +8 -8
  123. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +2 -2
  124. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +2 -2
  125. package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +39 -16
  126. package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +43 -8
  127. package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +40 -14
  128. package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +31 -14
  129. package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +7 -6
  130. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +14 -5
  131. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +3 -3
  132. package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +1 -1
  133. package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +4 -2
  134. package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +3 -3
  135. package/dist/features/tester/seed-data/authored/public-pages__newly-wired-browse-indexes.js +5 -5
  136. package/dist/features/tester/seed-data/authored/public-pages__store-tabs.js +5 -5
  137. package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +3 -3
  138. package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +45 -11
  139. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -17
  140. package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +36 -5
  141. package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +29 -4
  142. package/dist/features/tester/seed-data/authored/selling__become-seller.js +23 -0
  143. package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +14 -13
  144. package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +14 -9
  145. package/dist/features/tester/seed-data/authored/selling__listing-edit-roundtrip.js +34 -7
  146. package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +17 -7
  147. package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +9 -9
  148. package/dist/features/tester/seed-data/authored/selling__media-limits.js +9 -4
  149. package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +3 -3
  150. package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +3 -3
  151. package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +25 -1
  152. package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +27 -6
  153. package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +19 -1
  154. package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +63 -22
  155. package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +9 -5
  156. package/dist/features/tester/seed-data/authored/selling__seller-guide.js +7 -6
  157. package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +3 -2
  158. package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +18 -18
  159. package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +13 -12
  160. package/dist/features/tester/seed-data/authored/selling__seller-orders-at-scale.js +19 -3
  161. package/dist/features/tester/seed-data/authored/selling__seller-orders.js +22 -4
  162. package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +20 -8
  163. package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +4 -4
  164. package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +2 -2
  165. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +5 -5
  166. package/dist/features/tester/seed-data/authored/seo__og-images.js +1 -1
  167. package/dist/features/tester/seed-data/authored/seo__page-metadata.js +7 -7
  168. package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +3 -3
  169. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +123 -21
  170. package/dist/features/tester/utils/phases.d.ts +17 -0
  171. package/dist/features/tester/utils/phases.js +29 -2
  172. package/dist/index.d.ts +1 -0
  173. package/dist/index.js +2 -0
  174. package/dist/seo/json-ld.d.ts +23 -0
  175. package/dist/seo/json-ld.js +3 -2
  176. package/dist/server.d.ts +1 -0
  177. package/dist/server.js +2 -0
  178. package/dist/styles.css +1 -1
  179. package/dist/tailwind-utilities.css +1 -1
  180. package/dist/ui/components/PaginatedSelect.js +0 -0
  181. package/package.json +1 -1
@@ -13,8 +13,12 @@
13
13
  * chips against the real union rather than trusting the labels.
14
14
  *
15
15
  * Categories are the other theme: a product carries its FULL ancestor chain so a
16
- * parent page can match on its own id alone, and nothing on the write side
17
- * derives that chain yet.
16
+ * parent page can match on its own id alone. That chain IS derived on write, by
17
+ * `ProductRepository.deriveTaxonomy()`, from create and update alike — this header
18
+ * said the opposite until 2026-09-29 and told the tester a one-element chain was
19
+ * expected. A form cannot supply the chain itself (the create schema strips it), so
20
+ * if the derivation stops running there is no fallback and the product silently
21
+ * reaches only its leaf page.
18
22
  *
19
23
  * @tag domain:tester
20
24
  * @tag layer:seed
@@ -26,10 +30,10 @@
26
30
  export const authored = {
27
31
  "checklist-admin-catalog-listings-brands-crud": {
28
32
  roles: ["admin", "guest"],
29
- startPage: "/admin/categories",
33
+ startPage: "/admin/brands",
30
34
  steps: [
31
35
  "Sign in as admin@letitrip.in / TempPass123!.",
32
- "Open the admin brands surface and create one named 'QA Brand admin-crud' with a website, a country and a founding year.",
36
+ "Open /admin/brands and create one named 'QA Brand admin-crud' with a website, a country and a founding year.",
33
37
  "Upload public/test-media/sample-image.png to the field labelled for the hero or cover image.",
34
38
  "Save, RELOAD, and read every field.",
35
39
  "Open /brands/{slug} in a private window and read the hero banner and the 'About this brand' panel.",
@@ -64,17 +68,24 @@ export const authored = {
64
68
  startPage: "/admin/products",
65
69
  steps: [
66
70
  "Sign in as admin@letitrip.in / TempPass123!.",
67
- "Open /admin/products and create a product titled 'QA Product admin-crud' at 600 with a category, a brand, stock 3 and an image.",
71
+ "Open /admin/products and create a product titled 'QA Product admin-crud' at 600 with stock 3, an image, the brand Beyblade, and the category Burst Parts — which sits two levels below the root.",
68
72
  "Save, RELOAD, and read every field.",
69
73
  "Open its public page and read the title, price, stock badge, category links and brand link.",
70
- "Count the category links shown.",
74
+ "Count the category links shown and read their names.",
71
75
  "Edit ONLY the price to 700, save, RELOAD, and compare every other field.",
72
76
  "Delete the product and confirm the public page no longer resolves.",
73
77
  ],
74
- inputs: { title: "QA Product admin-crud", priceBefore: 600, priceAfter: 700, stock: 3 },
75
- expectedBehaviour: "An admin create and edit round-trip, and an edit writes back only what changed. The category link count is worth recording: a product is supposed to carry its full ancestor chain so every ancestor page can find it, and nothing on the write side derives that chain — so a product created through a form against a deep category gets ONE slug and is invisible on every ancestor page.",
76
- expectedUiState: "After each reload the values hold and only the price differs after the edit. Record how many category links the public page shows — one, where the chosen category has ancestors, is the known gap rather than a surprise. After the delete the public URL no longer resolves.",
77
- expectedData: { unintendedFieldChanges: 0 },
78
+ inputs: {
79
+ title: "QA Product admin-crud",
80
+ priceBefore: 600,
81
+ priceAfter: 700,
82
+ stock: 3,
83
+ categoryId: "category-burst-parts",
84
+ brand: "Beyblade",
85
+ },
86
+ expectedBehaviour: "An admin create and edit round-trip, and an edit writes back only what changed. The category links are the second assertion: the repository derives the full ancestor chain on write, so a product filed under Burst Parts carries Beyblade Burst and Spinning Tops with it. The brand link is the same shape — products are matched to a brand by DISPLAY NAME while the picker's value is the brand row's id, so that too is resolved on write rather than stored as picked.",
87
+ expectedUiState: "After each reload the values hold and only the price differs after the edit. THREE category links: Burst Parts, Beyblade Burst, Spinning Tops. The brand link resolves to a page that lists this product rather than an empty one. One category link, or a brand page that does not list it, are both write-path failures and both look entirely normal on the product's own page. After the delete the public URL no longer resolves.",
88
+ expectedData: { unintendedFieldChanges: 0, categoryLinksShown: 3 },
78
89
  endResult: "The product is deleted. Report the category link count either way.",
79
90
  },
80
91
  "checklist-admin-catalog-listings-products-default-listing-not-empty": {
@@ -161,14 +172,15 @@ export const authored = {
161
172
  startPage: "/admin/products",
162
173
  steps: [
163
174
  "Sign in as admin@letitrip.in / TempPass123!.",
164
- "Open each per-type admin listing in turn — auctions, pre-orders, prize draws, classifieds, digital codes, live items, art and stickers.",
175
+ "Open each per-type admin listing in turn, BY URL — there are exactly six and only /admin/classified is linked from the sidebar: /admin/prize-draws, /admin/classified, /admin/digital-codes, /admin/live, /admin/art, /admin/stickers.",
165
176
  "On each, read the filters offered and note whether any are specific to that type.",
166
177
  "Use each type-specific filter and confirm it changes the rows.",
167
178
  "Write down any page offering no filters at all.",
168
179
  "Write down any filter that changes nothing.",
180
+ "Now confirm auctions and pre-orders have NO page of their own: /admin/auctions and /admin/pre-orders should not resolve, and both types must instead be reachable as chips on /admin/products.",
169
181
  ],
170
- expectedBehaviour: "Each type's page offers the facets that type actually has — a classified's city, a live item's species, an auction's bid range — on top of the shared ones. A facet that renders and matches nothing is inert, which happens when it emits a field the query allowlist does not carry, and nothing raises.",
171
- expectedUiState: "Every per-type page offers filters including its own, and each visibly changes the rows. A page with no filters, or a filter that leaves the count identical while incrementing the badge, are both findings named by page.",
182
+ expectedBehaviour: "Each type's page offers the facets that type actually has — a classified's city, a live item's species — on top of the shared ones. A facet that renders and matches nothing is inert, which happens when it emits a field the query allowlist does not carry, and nothing raises. Auctions and pre-orders deliberately have no dedicated admin page; the chips on /admin/products are their only admin surface, and that is why those chips must cover all nine types.",
183
+ expectedUiState: "All six per-type pages resolve and offer filters including their own, each visibly changing the rows. /admin/auctions and /admin/pre-orders do not resolve, and both types filter correctly from the chips instead — a 404 there is the expected result, not a finding. A page with no filters, or a filter leaving the count identical while incrementing the badge, are findings named by page.",
172
184
  endResult: "Read-only; nothing persists beyond the URL.",
173
185
  },
174
186
  "checklist-admin-catalog-listings-admin-listing-reset-restores-defaults": {
@@ -196,7 +208,7 @@ export const authored = {
196
208
  "Read the URL and check it encodes the selection.",
197
209
  "Copy the URL, open it in a new tab, and read which chip is active on arrival.",
198
210
  "Press the browser back button and read which chip is active.",
199
- "Repeat all of this on the admin payment-methods listing.",
211
+ "Repeat all of this on /admin/payment-methods, which has the same chip row.",
200
212
  ],
201
213
  expectedBehaviour: "Chip state lives in the URL on both listings, so a filtered view is shareable and the back button walks the selections. A chip held in component state alone makes the URL describe a different page from the one on screen.",
202
214
  expectedUiState: "Both listings encode the chip in the URL, the copied URL arrives with the same chip active, and back returns to the previous selection. One press per selection — two presses to undo one chip is the double-navigation failure.",
@@ -210,7 +222,7 @@ export const authored = {
210
222
  "Open /admin/products with no query parameters and read the sort dropdown's selection.",
211
223
  "Check something is selected rather than the control being blank.",
212
224
  "Confirm the selected option is one of the options the dropdown offers.",
213
- "Repeat on /admin/orders, /admin/addresses and the admin payment-methods listing.",
225
+ "Repeat on /admin/orders, /admin/addresses and /admin/payment-methods.",
214
226
  "Write down any listing whose dropdown opens with nothing selected.",
215
227
  ],
216
228
  expectedBehaviour: "A listing's default sort is one of the options it offers. Where the default names a field the dropdown does not list, the control opens blank — the list IS sorted, but nothing on screen says how, and two of these listings shipped exactly that way.",
@@ -219,10 +231,10 @@ export const authored = {
219
231
  },
220
232
  "checklist-admin-catalog-listings-sublisting-categories-crud": {
221
233
  roles: ["admin", "guest"],
222
- startPage: "/admin/categories",
234
+ startPage: "/admin/sublisting-categories",
223
235
  steps: [
224
236
  "Sign in as admin@letitrip.in / TempPass123!.",
225
- "Open the admin sublisting-categories surface and read the existing rows.",
237
+ "Open /admin/sublisting-categories and read the existing rows.",
226
238
  "Create one named 'QA Sublisting admin-crud' with a description and save.",
227
239
  "RELOAD and read every field.",
228
240
  "Rename it, save, RELOAD, and read the derived page title and description.",
@@ -244,20 +256,20 @@ export const authored = {
244
256
  "Create a carousel named 'QA Carousel admin-crud' and add a slide with a title, an image and a link.",
245
257
  "Save, RELOAD, and read the carousel's name, status and its slide.",
246
258
  "Set it active and open / in a private window to find it.",
247
- "Count the active carousels and check the cap is respected.",
259
+ "Add slides to it until a sixth ACTIVE slide is refused. The cap of five is on active SLIDES, not on carousels, and the seed already ships five active ones — so the refusal may arrive on the very first slide you activate.",
248
260
  "Delete the carousel.",
249
261
  ],
250
262
  inputs: { name: "QA Carousel admin-crud", maxActive: 5 },
251
- expectedBehaviour: "The list page lists named carousels — it once rendered the flat slide editor instead, so there was no list at all. At most five may be active at once, and a slide's background may be an image, a video, a colour or a gradient; a video background renders through a real video element, so its URL must be directly playable rather than routed through the image proxy, which rejects video outright.",
252
- expectedUiState: "The page lists named carousels. After the reload the carousel holds its name, status and slide. Activating it shows it publicly. Attempting a sixth active carousel is refused rather than silently accepted.",
263
+ expectedBehaviour: "The list page lists named carousels — it once rendered the flat slide editor instead, so there was no list at all. At most five SLIDES may be active at once — refused at write with a 409 naming the limit, not silently truncated, and there is deliberately no cap on the number of named carousels. A slide's background may be an image, a video, a colour or a gradient; a video background renders through a real video element, so its URL must be directly playable rather than routed through the image proxy, which rejects video outright.",
264
+ expectedUiState: "The page lists named carousels. After the reload the carousel holds its name, status and slide. Activating it shows it publicly. A sixth active SLIDE is refused with a readable message rather than silently accepted or silently dropped from the public carousel. Refusing to create a sixth CAROUSEL would be the opposite failure — there is no such cap.",
253
265
  endResult: "The carousel is deleted by the final step.",
254
266
  },
255
267
  "checklist-admin-catalog-listings-sections-crud": {
256
268
  roles: ["admin", "guest"],
257
- startPage: "/admin",
269
+ startPage: "/admin/sections",
258
270
  steps: [
259
271
  "Sign in as admin@letitrip.in / TempPass123!.",
260
- "Open the homepage sections editor and read every section, its type and its order.",
272
+ "Open /admin/sections and read every section, its type and its order. It is the homepage sections editor; /admin is only a redirect to the dashboard.",
261
273
  "Create a section, choosing a type, and configure every field it offers.",
262
274
  "Save, RELOAD, and read every field back.",
263
275
  "Open / in a private window and find the new section in the right position.",
@@ -276,9 +288,9 @@ export const authored = {
276
288
  "Sign in as admin@letitrip.in / TempPass123!.",
277
289
  "Create an art listing titled 'QA Art Print admin-crud' at 700 with a size, material, finish and edition size.",
278
290
  "Save, RELOAD, and read all four print fields.",
279
- "Open the admin listing filtered to art and check the listing appears there.",
291
+ "Open /admin/art and check the listing appears there.",
280
292
  "Read every other row in that filtered view and check none is an ordinary product.",
281
- "Open the public art tab and check the listing appears there too.",
293
+ "Open /art publicly and check the listing appears there too. Art and stickers share that one browse route — there is no /stickers page.",
282
294
  "Delete the listing.",
283
295
  ],
284
296
  inputs: { title: "QA Art Print admin-crud", price: 700 },
@@ -296,8 +308,8 @@ export const authored = {
296
308
  "Set its featured flag on and save.",
297
309
  "RELOAD and confirm the flag is still on.",
298
310
  "Open / in a private window and find the product in a featured section.",
299
- "Set the promoted or on-sale flag on, save, reload, and check the public surface that flag drives.",
300
- "Turn both flags back off, save, and confirm the product leaves those surfaces.",
311
+ "Set the promoted flag on, save, reload, and open /admin/featured and /admin/deals — both are real listings of flagged products and the product must appear in the matching one.",
312
+ "Turn both flags back off, save, and confirm the product leaves the homepage section AND leaves /admin/featured and /admin/deals.",
301
313
  ],
302
314
  expectedBehaviour: "Featured, promoted and on-sale flags reach the public surfaces they drive, in both directions. Turning a flag off must remove the product from its section — a flag that only ever adds is half a flag, and the homepage sections that read them filter by availability too, so a sold-out featured product should not appear.",
303
315
  expectedUiState: "Each flag survives its reload and its public surface follows it on and off. The product appears in the featured section when flagged and leaves when unflagged.",
@@ -5,12 +5,20 @@
5
5
  * The WRITE side of the tree. Its read side is content-discovery/category-brand-relations;
6
6
  * these are the operations that can corrupt what that page then renders.
7
7
  *
8
- * 🛑 ONE OF THESE IS A KNOWN OPEN GAP RATHER THAN A REGRESSION. Nothing on the
9
- * write path derives a product's ancestor chain from the category it was filed
10
- * under — the convention is maintained by hand in the seed. So a seller picking a
11
- * tier-3 category through the form today produces a one-element chain and a
12
- * product invisible on every ancestor page. The case says so and asks for what
13
- * actually happens, because a case that assumes the answer teaches nothing.
8
+ * 🛑 THE ANCESTOR-CHAIN CASE WAS INVERTED AND IS NOW CORRECTED. This header used to
9
+ * say "nothing on the write path derives a product's ancestor chain", and the case
10
+ * encoded that as `categoryPagesListingIt: 1` — so once the gap was actually closed,
11
+ * the case DEMANDED the bug: correct behaviour scored a fail and a tester following
12
+ * it would have filed the fix as a regression.
13
+ *
14
+ * `ProductRepository.deriveTaxonomy()` has derived the chain from both `create` and
15
+ * `update` since 2026-09-14. It reads the leaf's `parentIds`, reverses them onto the
16
+ * front, and writes `categorySlugs = [leaf, ...ancestors]` — byte-identical to what
17
+ * the seed hand-writes. The expected answer is now ALL THREE pages.
18
+ *
19
+ * It stays a valuable case for a reason worth keeping: every SEEDED product already
20
+ * carries a correct chain, so a regression here is invisible to any check that reads
21
+ * existing data. Only a product created through a form can catch it.
14
22
  *
15
23
  * @tag domain:tester
16
24
  * @tag layer:seed
@@ -79,16 +87,14 @@ export const authored = {
79
87
  "Open /categories/category-burst-parts publicly and check the product is listed.",
80
88
  "Open /categories/category-beyblade-burst and search for it.",
81
89
  "Open /categories/category-spinning-tops and search for it.",
82
- "Record on which of the three pages it appears.",
90
+ "Record on which of the three pages it appears — Burst Parts sits two levels below the root, so all three are expected.",
83
91
  "Delete the product afterwards.",
84
92
  ],
85
93
  inputs: { title: "QA Ancestor Chain Probe", price: 499, categoryId: "category-burst-parts" },
86
- expectedBehaviour: "A category page matches on its own id alone, which only works if the product carries its full ancestor chain — and nothing on the write path derives that chain today. So a product created here is expected to be visible on Burst Parts and NOT on its ancestors. That is a known open gap, not a fresh regression, and the value of the case is the exact answer rather than the verdict.",
87
- expectedUiState: "Record exactly which of the three category pages list the product. Visible on all three means the chain is now derived on write and the gap is closed; visible only on Burst Parts is today's expected state.",
88
- expectedData: { categoryPagesListingIt: 1 },
94
+ expectedBehaviour: "A category page matches on its own id alone, which works only if the product carries its full ancestor chain — and the repository derives that chain on write, from create and update alike. A form cannot supply it: the create schema strips any categorySlugs it is sent, so if the derivation stops running there is no fallback and the product silently reaches its leaf page only.",
95
+ expectedUiState: "All THREE pages list it: Burst Parts, Beyblade Burst and Spinning Tops. Visible only on Burst Parts is the failure — and it is a silent one, because the product's own page looks entirely normal and every seeded product around it still shows a correct chain.",
96
+ expectedData: { categoryPagesListingIt: 3 },
89
97
  endResult: "The QA product is deleted.",
90
- needsReview: true,
91
- reviewNote: "Open gap by design — the write side does not append ancestors. The case exists to measure it, and its answer should change once that lands.",
92
98
  },
93
99
  "checklist-admin-category-brand-authoring-brand-rename-orphan-check": {
94
100
  roles: ["admin"],
@@ -130,29 +136,42 @@ export const authored = {
130
136
  steps: [
131
137
  "Sign in as admin@letitrip.in / TempPass123!.",
132
138
  "Open /admin/categories and start creating a category.",
133
- "Find where the row's kind is chosen and read the options offered.",
134
- "Check the options match the kinds the site actually renders — a listing category, a brand, a sub-listing group and a bundle.",
135
- "Check no option is offered that no page renders.",
136
- "Save nothing and open an existing brand row for edit, checking its kind is shown.",
139
+ "Read every field the create form offers and confirm there is NO dropdown for the row's kind or type — the fields are name, slug, description, parent, order, active and show-in-menu.",
140
+ "Create one named 'QA Kind Probe' with no parent and save it.",
141
+ "Open /admin/brands and confirm 'QA Kind Probe' is NOT listed there.",
142
+ "Create a brand named 'QA Brand Probe' on /admin/brands and save it.",
143
+ "Return to /admin/categories and confirm 'QA Brand Probe' is NOT listed among the categories.",
144
+ "Open /brands publicly and confirm QA Brand Probe appears; open /categories and confirm QA Kind Probe appears and QA Brand Probe does not.",
145
+ "Delete both QA rows.",
137
146
  ],
138
- expectedBehaviour: "Brands, bundles, sub-listing groups and ordinary categories are all rows in one collection distinguished by a kind field, so the author has to be able to see and set it. An option offered that no page renders creates rows nothing will ever display.",
139
- expectedUiState: "The kind is an explicit, readable choice on create and visible on edit, and every option corresponds to a kind the site renders.",
140
- endResult: "Nothing is saved.",
147
+ inputs: { categoryName: "QA Kind Probe", brandName: "QA Brand Probe" },
148
+ expectedBehaviour: "Brands, bundles, sub-listing groups and ordinary categories are all rows in ONE collection distinguished by a kind field — but the author never picks it. The surface determines it: /admin/brands lists and writes kind=brand, /admin/categories writes an ordinary category. That is deliberate, so a missing kind dropdown is correct rather than a gap. What must hold is that the two surfaces stay disjoint.",
149
+ expectedUiState: "No kind dropdown on either form. Each row appears on exactly ONE admin surface and exactly one public surface. A brand leaking into the category tree — or a category into the brand list — means the kind was not set from the surface, and that row will render in a place built for a different shape.",
150
+ endResult: "Both QA rows are deleted. If a row appeared on both surfaces, record which, and delete it — a mistyped row is worse left in place than removed.",
141
151
  },
142
152
  "checklist-admin-category-brand-authoring-delete-category-with-children-refused": {
143
153
  roles: ["admin"],
144
154
  startPage: "/admin/categories",
145
155
  steps: [
146
156
  "Sign in as admin@letitrip.in / TempPass123!.",
147
- "Open /admin/categories and attempt to delete Beyblade Burst, which has children and products beneath it.",
148
- "Read the confirmation or refusal.",
149
- "Check the message names how many children or products depend on it.",
150
- "If it refuses, cancel and confirm the category is untouched.",
151
- "If it succeeds, open the public categories tree and record what happened to its children.",
157
+ "Create a category 'QA Doomed Parent' under Beyblade Burst, then a category 'QA Orphan Child' under QA Doomed Parent.",
158
+ "Create a product 'QA Cascade Probe' priced at 499 filed directly under QA Doomed Parent, and publish it.",
159
+ "Open /admin/categories and delete QA Doomed Parent.",
160
+ "Open /categories/category-beyblade-burst publicly and look for QA Orphan Child — it should now sit directly under Beyblade Burst.",
161
+ "Open the QA Cascade Probe product's public page and read its category links.",
162
+ "Open /categories/category-beyblade-burst and confirm the product is listed there.",
163
+ "Delete QA Orphan Child and the product afterwards.",
152
164
  ],
153
- inputs: { categoryId: "category-beyblade-burst" },
154
- expectedBehaviour: "A delete that orphans a subtree is unrecoverable, so it is refused with a count of what depends on it. Deletes are the one operation whose bug cannot be undone, which is why the refusal has to be structural rather than a warning the admin can click past.",
155
- expectedUiState: "The delete is refused with a message naming the dependent children and products. If it succeeds instead, that is the finding and the state of the orphaned subtree is the evidence.",
156
- endResult: "The category still exists. If the delete succeeded, re-seed rather than attempting a manual repair.",
165
+ inputs: {
166
+ doomedParent: "QA Doomed Parent",
167
+ orphanChild: "QA Orphan Child",
168
+ productTitle: "QA Cascade Probe",
169
+ price: 499,
170
+ grandparentId: "category-beyblade-burst",
171
+ },
172
+ expectedBehaviour: "Deleting a category CASCADES TO ITS PARENT rather than refusing: the children re-parent to the grandparent and the products are re-filed there, through the repository so each product's ancestor chain is rebuilt from its new leaf. This replaced an older flat refusal. The order is load-bearing — products must move while the document still exists — so the failure mode to watch for is a product left pointing at a category that is gone.",
173
+ expectedUiState: "QA Orphan Child now sits directly under Beyblade Burst. QA Cascade Probe is filed under Beyblade Burst and its category links show the rebuilt chain, with no link naming the deleted QA Doomed Parent. A product with a dead category link, a one-element chain, or one missing from Beyblade Burst entirely is the failure.",
174
+ expectedData: { childReparentedToGrandparent: true, deadCategoryLinks: 0 },
175
+ endResult: "Only the two QA rows and the QA product were touched; the seeded tree is unchanged. Do NOT test this by deleting a seeded category — the whole Burst subtree would re-parent and every other case reading it in this run would then be testing a different tree.",
157
176
  },
158
177
  };
@@ -11,6 +11,45 @@
11
11
  * as a rejection that leaves the item live — both look correct from the admin's
12
12
  * seat, and only the public view can tell them apart.
13
13
  *
14
+ * 🛑 STATUS IS CHANGED FROM THE LIST, NOT THE EDITOR (corrected 2026-09-29).
15
+ *
16
+ * All three cases said "open its detail" and then "change its status and save".
17
+ * `AdminProductEditorView` has exactly three sections — Listing type, Details,
18
+ * Inventory — and `status` appears in it only as a default and as read-only
19
+ * header text. There is no status control to use. The real ones are on
20
+ * /admin/products' row menu: 'Approve' (writes published), 'Reject', and a
21
+ * 'Quick edit' drawer whose Status select is the one place an admin picks a
22
+ * value. So the procedure now names the row menu.
23
+ *
24
+ * 🛑 AND THE TYPE TABS IN THAT EDITOR RENDER NOTHING. Every `<TabsContent>` is
25
+ * self-closing: the strip flips the `listingType` discriminator and shows no
26
+ * type-specific fields at all. So a moderator opening a live listing sees no
27
+ * species, no jurisdiction list and no video — which is precisely the
28
+ * "deciding blind" failure the live case was written to catch, and it is real
29
+ * today. The case now says where to look and what the finding is, rather than
30
+ * sending a tester to hunt a panel that does not exist. Recorded in OUTOFSCOPE.
31
+ *
32
+ * 🛑 THE CODE POOL IS EDIT-MODE ONLY. `renderDigitalContentPool` is gated on
33
+ * `mode === "edit" && listingType === "digital-code" && productId`, so a
34
+ * digital-code listing cannot be created WITH codes in one pass — it is created,
35
+ * saved, and reopened. "Code Pool Size (optional)" on the create form is a
36
+ * seller-typed number that the server recounts from the real subcollection; it
37
+ * is not the pool.
38
+ *
39
+ * 🛑 /admin/classified, /admin/digital-codes AND /admin/live ARE BROWSE-ONLY.
40
+ * All three are thin wrappers over `buildListingTypeListingConfig`, which
41
+ * supplies a search box, a sort and a `status` filter — and no row actions, no
42
+ * bulk actions, no Approve/Reject and no Quick edit. The catalogue `href`s
43
+ * pointed at them, which is a real page where the case's central action cannot
44
+ * be performed; they now point at /admin/products, and `startPage` says the
45
+ * same. Both Status and Type on that page are chip groups inside the FILTER
46
+ * DRAWER, not an always-visible row, and Type is multi-select.
47
+ *
48
+ * Field labels used below are the real ones: 'City' / 'Preferred Contact Method'
49
+ * (section "Meetup Details"), 'Code Pool Size (optional)' /
50
+ * 'Redemption Instructions (optional)', 'Species / Common Name' /
51
+ * 'Jurisdictions where sale is permitted'.
52
+ *
14
53
  * @tag domain:tester
15
54
  * @tag layer:seed
16
55
  * @tag pattern:none
@@ -23,56 +62,104 @@ export const authored = {
23
62
  roles: ["admin", "seller", "guest"],
24
63
  startPage: "/admin/products",
25
64
  steps: [
26
- "Sign in as tyson@beybladearena.in / TempPass123! and create a classified titled 'QA Classified admin-moderate' at 900, city Pune.",
27
- "Sign out and sign in as admin@letitrip.in / TempPass123!.",
28
- "Open the admin listing that holds classifieds and find it.",
29
- "Read every status chip offered and select each in turn, noting which returns this listing.",
30
- "Change its status to a non-published one and save.",
31
- "Open the listing's public URL in a private window and read what is shown.",
32
- "Restore it to published, then sign back in as tyson and delete it.",
65
+ "Sign in as tyson@beybladearena.in / TempPass123!, open /store/classified/new, and create a listing titled 'QA Classified admin-moderate' at 900 with City 'Pune' under Meetup Details. Publish it and note the slug on its public URL.",
66
+ "Sign in as admin@letitrip.in / TempPass123! and open /admin/products.",
67
+ "Open the filter drawer, tick Classified in its multi-select Type chip group, apply, and find the listing.",
68
+ "In that same filter drawer read the Status chip group — All, Pending, Published, Draft, Archived — and select each in turn, noting which one returns this listing.",
69
+ "Open the row menu, choose 'Quick edit', set Status to Archived, and save.",
70
+ "Open the listing's public URL and read what is shown.",
71
+ "Quick-edit it back to Published, then sign in as tyson@beybladearena.in and delete it from /store/classified.",
33
72
  ],
34
- inputs: { title: "QA Classified admin-moderate", price: 900, city: "Pune" },
73
+ inputs: { title: "QA Classified admin-moderate", price: 900, city: "Pune", archivedVia: "Quick edit" },
35
74
  expectedBehaviour: "An admin status change reaches the public read path. Every status chip must also name a value the documents actually hold — a chip whose id is a display label rather than the stored value matches nothing forever and returns an empty list with no error.",
36
- expectedUiState: "The listing is findable under its own status chip. After the status change the public URL no longer renders it as a live listing — a public page still showing it is the failure, and the admin row will look correct either way.",
37
- endResult: "The listing is restored to published and then deleted. A chip returning nothing while the unfiltered list holds rows of that status is a separate finding worth naming.",
75
+ expectedUiState: "While published the listing is returned by the Published chip and by no other. After the archive the public URL no longer renders it as a live listing, and the Archived chip returns it — the admin row will look correct either way, so the public page is the oracle. A chip returning nothing while All holds rows of that status is a separate finding worth naming.",
76
+ expectedData: { statusAfterArchive: "archived", chipReturningIt: "Archived" },
77
+ endResult: "The listing is Published again and then deleted. Re-open the public URL after the delete: a page still rendering it is a cache or projection failure.",
38
78
  },
39
79
  "checklist-admin-classifieds-digitalcodes-live-digitalcode-create-moderate": {
40
80
  roles: ["admin", "seller", "guest"],
41
81
  startPage: "/admin/products",
42
82
  steps: [
43
- "Sign in as tyson@beybladearena.in / TempPass123! and create a digital-code listing titled 'QA Digital Code admin-moderate' at 350 with three codes.",
44
- "Sign out and sign in as admin@letitrip.in / TempPass123!.",
45
- "Find it in the admin listing and open its detail.",
46
- "Read whether the actual CODE STRINGS are visible to the admin.",
47
- "Change its status to a non-published one and save.",
48
- "Open the public URL in a private window and read what is shown.",
49
- "Restore it, then sign back in as tyson and delete it.",
83
+ "Sign in as tyson@beybladearena.in / TempPass123!, open /store/digital-codes/new, and create a listing titled 'QA Digital Code admin-moderate' at 350. Publish it.",
84
+ "REOPEN it from /store/digital-codes for editing — the code pool is only mounted in edit mode — and add three codes QA-CODE-1, QA-CODE-2, QA-CODE-3. Note the slug on its public URL.",
85
+ "Sign in as admin@letitrip.in / TempPass123!, open /admin/products, open the filter drawer, tick Digital Code in its Type chip group, apply, and find the listing.",
86
+ "Read the row and every column: are the actual CODE STRINGS visible anywhere in the list?",
87
+ "Open the row menu, choose 'Quick edit', set Status to Archived, and save.",
88
+ "Open the listing's public URL and read what is shown.",
89
+ "Quick-edit it back to Published, then sign in as tyson@beybladearena.in and delete it from /store/digital-codes.",
50
90
  ],
51
- inputs: { title: "QA Digital Code admin-moderate", price: 350, codeCount: 3 },
52
- expectedBehaviour: "Moderation works the same for this type, and the code pool is the thing worth watching: an unsold code is inventory a buyer will pay for, so it must not be readable from a list view. Availability here reads the nested pool count rather than a stock figure.",
53
- expectedUiState: "The listing is findable and its status change takes effect publicly. Unsold code strings are not displayed in the admin list; if the detail view shows them, record exactly where — that is a finding rather than a pass.",
54
- endResult: "The listing is restored and deleted. Note whether an admin can read unredeemed codes at all.",
91
+ inputs: {
92
+ title: "QA Digital Code admin-moderate",
93
+ price: 350,
94
+ codeCount: 3,
95
+ codes: "QA-CODE-1, QA-CODE-2, QA-CODE-3",
96
+ },
97
+ expectedBehaviour: "Moderation works the same for this type, and the code pool is the thing worth watching: an unsold code is inventory a buyer will pay for, so it must not be readable from a list view. Availability for this type reads the nested pool count rather than a stock figure, which is why the pool has to exist before the listing can be judged available at all.",
98
+ expectedUiState: "The listing is findable under the Digital Code chip and its archive takes effect publicly. NO unredeemed code string appears in the admin list — not in a column, not in a tooltip, not in the quick-edit drawer. If any is visible, quote it and say exactly where; that is a finding, not a pass.",
99
+ expectedData: { codesVisibleInAdminList: false, statusAfterArchive: "archived" },
100
+ endResult: "The listing is Published again and then deleted. Record whether an admin can read an unredeemed code anywhere in the product — a 'no' here is a leak of paid inventory.",
55
101
  },
56
102
  "checklist-admin-classifieds-digitalcodes-live-live-create-moderate": {
57
103
  roles: ["admin", "seller", "guest"],
58
104
  startPage: "/admin/products",
59
105
  steps: [
60
- "Sign in as tyson@beybladearena.in / TempPass123! and create a live listing titled 'QA Live Item admin-moderate' at 3000, species Dog, jurisdictions Maharashtra only, with public/test-media/sample-video.mp4 attached.",
61
- "Sign out and sign in as admin@letitrip.in / TempPass123!.",
62
- "Find it in the admin listing and open its detail.",
63
- "Read whether the species, jurisdiction list and video are all shown to the moderator.",
64
- "Change its status to a non-published one and save.",
65
- "Open the public URL in a private window and read what is shown.",
66
- "Restore it, then sign back in as tyson and delete it.",
106
+ "Sign in as tyson@beybladearena.in / TempPass123!, open /store/live/new, and create a listing titled 'QA Live Item admin-moderate' at 3000 with 'Species / Common Name' set to Dog and 'Jurisdictions where sale is permitted' set to Maharashtra only.",
107
+ "Attach /test-media/sample-video.mp4 as its video and publish it. Note the slug on its public URL.",
108
+ "Sign in as admin@letitrip.in / TempPass123!, open /admin/products, open the filter drawer, tick Live Item in its Type chip group, apply, and find the listing.",
109
+ "Open the row menu and then the editor at /admin/products/{its id}/edit. Read every section and record whether the species, the jurisdiction list and the video appear ANYWHERE.",
110
+ "Return to /admin/products, open the row menu, choose 'Quick edit', set Status to Archived, and save.",
111
+ "Open the listing's public URL and read what is shown.",
112
+ "Quick-edit it back to Published, then sign in as tyson@beybladearena.in and delete it from /store/live.",
67
113
  ],
68
114
  inputs: {
69
115
  title: "QA Live Item admin-moderate",
70
116
  price: 3000,
71
117
  species: "Dog",
72
118
  jurisdictions: "Maharashtra",
119
+ video: "/test-media/sample-video.mp4",
73
120
  },
74
- expectedBehaviour: "A live listing is the one type where moderation has a duty beyond commerce — the jurisdiction list decides who may lawfully receive the animal or plant, and the mandatory video is what the moderator judges the listing on. Both must be visible in the admin view or the moderator is deciding blind.",
75
- expectedUiState: "The admin detail shows the species line, the jurisdiction list AND the video. The status change takes effect on the public page. A moderation row offering approve and reject without showing the video is the acting-blind failure this page family exists to catch.",
76
- endResult: "The listing is restored and deleted. Record specifically whether the video was viewable from the moderation surface.",
121
+ expectedBehaviour: "A live listing is the one type where moderation has a duty beyond commerce — the jurisdiction list decides who may lawfully receive the animal or plant, and the video is what the moderator judges the listing on. Both must be visible to the admin, or the moderator is approving and rejecting blind.",
122
+ expectedUiState: "🛑 EXPECT THIS TO FAIL ON THE ADMIN HALF, AND RECORD IT AS A FAILURE. The admin editor has three sections — Listing type, Details, Inventory — and its listing-type tabs render EMPTY content, so no species, no jurisdiction list and no video is shown. Confirm that by reading the sections and say so, naming the three sections you found. The archive half does hold: it takes effect on the public page. Keep the two apart in the comment — the moderation mechanics work and the information the moderator needs is absent.",
123
+ expectedData: { adminSectionsSeen: 3, speciesShownToAdmin: false, videoShownToAdmin: false },
124
+ endResult: "The listing is Published again and then deleted. Record specifically whether the video was viewable from any admin surface — that is the finding, not the status change.",
125
+ },
126
+ /*
127
+ * 🛑 ADDED 2026-09-29. The three cases above all archive; none of them used
128
+ * the 'Reject' action, and it is the one that writes a value the rest of the
129
+ * product cannot represent.
130
+ *
131
+ * `ProductStatus` is draft | published | in_review | archived — four values,
132
+ * no 'rejected'. `ADMIN_PRODUCT_STATUS_TABS` mirrors those four plus All.
133
+ * But AdminProductsView's Reject row action writes `{ status: "rejected" }`,
134
+ * and the PATCH schema types status as a bare `z.string().optional()`, so it
135
+ * is persisted verbatim with no validation.
136
+ *
137
+ * The effect looks right — every public query filters `status == published`,
138
+ * so the listing does disappear — which is exactly why nobody noticed. The
139
+ * cost is on the way back: the row now matches no status chip, so an admin
140
+ * can only reach it under All, and the seller's own status filters cannot
141
+ * isolate it either. Root Cause #33's shape on the WRITE side.
142
+ *
143
+ * Written as a case rather than fixed: choosing between adding 'rejected' to
144
+ * the union and making Reject write 'archived' is a product decision.
145
+ */
146
+ "checklist-admin-classifieds-digitalcodes-live-reject-status-has-no-chip": {
147
+ roles: ["admin", "seller"],
148
+ startPage: "/admin/products",
149
+ steps: [
150
+ "Sign in as tyson@beybladearena.in / TempPass123!, open /store/classified/new, and create a listing titled 'QA Classified reject-status' at 700 with City 'Pune'. Publish it.",
151
+ "Sign in as admin@letitrip.in / TempPass123! and open /admin/products.",
152
+ "Open the filter drawer, tick Classified in its Type chip group, apply, then find the listing, open its row menu and click 'Reject'.",
153
+ "Read the status shown on the row.",
154
+ "In the filter drawer select each Status chip in turn — Pending, Published, Draft, Archived — and record whether ANY of them returns the listing.",
155
+ "Select the All status chip and confirm it is still there.",
156
+ "Open the listing's public URL and read what is shown.",
157
+ "Quick-edit it back to Published, then sign in as tyson@beybladearena.in and delete it from /store/classified.",
158
+ ],
159
+ inputs: { title: "QA Classified reject-status", price: 700, city: "Pune", action: "Reject" },
160
+ expectedBehaviour: "A rejected listing must stay reachable by the admin who rejected it. Hiding it from buyers is only half the job — a moderator needs to find it again to explain, reverse or escalate the decision, and the status filters are the only tool for that.",
161
+ expectedUiState: "🛑 EXPECT THE FILTER HALF TO FAIL. The row reads 'rejected', a value ProductStatus does not have, so none of the four status chips returns it and it is reachable only under All. Record which chips you tried and that each came back without it. The public half should hold — the listing is gone from its public URL, because every public query filters on published.",
162
+ expectedData: { statusOnRow: "rejected", chipsReturningIt: 0 },
163
+ endResult: "The listing is Published again and then deleted. Two findings to keep separate: a status no chip matches, and — if the public page still renders it — a far worse one.",
77
164
  },
78
165
  };