@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
@@ -13,6 +13,37 @@
13
13
  * @tag consumers:seed-data/authored/index.ts
14
14
  * @tag sideEffects:none
15
15
  */
16
+ /*
17
+ * Verified on 2026-09-29, and both cases are right, including the detail most
18
+ * likely to be wrong: the default really IS branding. `activeTab` is
19
+ *
20
+ * useState(() => { if (typeof window === "undefined") return "branding";
21
+ * const requested = new URLSearchParams(...).get("tab");
22
+ * return isSiteSettingsTabId(requested) ? requested : "branding"; })
23
+ *
24
+ * so an unknown or empty value falls through to branding with no complaint,
25
+ * which is exactly what the second case asserts. The tab id is validated
26
+ * against the union rather than trusted.
27
+ *
28
+ * 🛑 DO NOT infer the default from the JSX. `about` is the first
29
+ * SettingsTabForm in the file, so reading the markup suggests the page opens
30
+ * there; it does not, because the default is explicit state. The 20 valid ids
31
+ * in declaration order are:
32
+ *
33
+ * about branding appearance themes announcement seo contact watermark fees
34
+ * integrations shipping auction limits legal whatsapp notifications
35
+ * procurement emi gst listings
36
+ *
37
+ * 🛑 AND THE FIRST PAINT IS ALWAYS BRANDING. The initialiser returns branding
38
+ * when `window` is undefined, so the server-rendered tab is branding and the
39
+ * URL's tab is applied on hydration. On a slow load a tester can briefly see
40
+ * Branding before Fees appears. That is the deep link working, not failing,
41
+ * and the case now says so rather than leaving it to be reported.
42
+ *
43
+ * Note `ADMIN_SETTINGS_TABS` is a DIFFERENT registry {actions, navigation} for
44
+ * /admin/settings/*. The guard that matters here is `isSiteSettingsTabId` in
45
+ * site-settings-tabs.ts; chasing the wrong constant leads to a two-tab list.
46
+ */
16
47
  export const authored = {
17
48
  "checklist-search-and-nav-settings-deep-links-tab-query-param-opens-that-tab": {
18
49
  roles: ["admin"],
@@ -20,7 +51,7 @@ export const authored = {
20
51
  steps: [
21
52
  "Sign in as admin@letitrip.in / TempPass123!.",
22
53
  "Open /admin/site?tab=fees directly in the address bar.",
23
- "Read which tab is selected.",
54
+ "Read which tab is selected, waiting for hydration before judging — the first paint is always Branding.",
24
55
  "Open /admin/site?tab=themes directly.",
25
56
  "Read which tab is selected.",
26
57
  "Open /admin/site with no query parameter.",
@@ -28,8 +59,8 @@ export const authored = {
28
59
  ],
29
60
  inputs: { tab1: "fees", tab2: "themes" },
30
61
  expectedBehaviour: "The open tab is read from the URL, so a deep link — from search, from a guide, from a colleague's message — lands where it says it will. A page that hard-codes its default ignores the URL entirely and every such link arrives on tab one.",
31
- expectedUiState: "?tab=fees opens the Fees tab with its fields on screen. ?tab=themes opens Themes. With no parameter the page opens on Branding. Two different parameters producing the same tab means the URL is not being read.",
32
- expectedData: { defaultTab: "branding" },
62
+ expectedUiState: "?tab=fees opens the Fees tab with its fields on screen. ?tab=themes opens Themes. With no parameter the page opens on Branding. Two different parameters producing the same tab means the URL is not being read — but a MOMENT of Branding before the requested tab is hydration, not a failure: the server-rendered default is always Branding because the initialiser cannot read the URL without a window.",
63
+ expectedData: { defaultTab: "branding", validTabCount: 20 },
33
64
  endResult: "Read-only; do not save anything. Nothing persists from opening a tab.",
34
65
  },
35
66
  "checklist-search-and-nav-settings-deep-links-unknown-tab-falls-back-quietly": {
@@ -43,8 +74,8 @@ export const authored = {
43
74
  "Read which tab is selected.",
44
75
  ],
45
76
  inputs: { unknownTab: "nonsense" },
46
- expectedBehaviour: "An unrecognised tab value falls back to the default without complaint. The realistic cause is a stale bookmark from a tab that has since been renamed, and that is not an error condition — it is a page whose shape changed.",
47
- expectedUiState: "Both URLs open Branding. No error banner, no toast, no empty tab strip and no blank panel. A rejected value must not look like a broken page.",
77
+ expectedBehaviour: "An unrecognised tab value falls back to the default without complaint, because the id is validated against the 20-value union rather than trusted. The realistic cause is a stale bookmark from a tab that has since been renamed, and that is not an error condition — it is a page whose shape changed.",
78
+ expectedUiState: "Both URLs open Branding. No error banner, no toast, no empty tab strip and no blank panel. A rejected value must not look like a broken page. Worth trying one more: ?tab=Fees with a capital F, since the guard is an exact match against the union and so should also fall back rather than open Fees.",
48
79
  expectedData: { fallbackTab: "branding" },
49
80
  endResult: "Read-only; nothing persists.",
50
81
  },
@@ -13,6 +13,26 @@
13
13
  * @tag consumers:seed-data/authored/index.ts
14
14
  * @tag sideEffects:none
15
15
  */
16
+ /*
17
+ * Counted against ADMIN_NAV_GROUPS on 2026-09-29, because the case named three
18
+ * entries and the query returns five, in TWO groups:
19
+ *
20
+ * Trust & Safety Banned Addresses keywords: blocklist, fraud, ban
21
+ * Address Clusters description AND keywords
22
+ * Payment Clusters keywords: fraud, duplicates, linked accounts
23
+ * Scam Registry keywords: fraud, blocklist, report
24
+ * Guides Trust & Safety Guide description AND keywords
25
+ *
26
+ * 🛑 The two the case omitted are the ones a tester would have to judge.
27
+ * Seeing Banned Addresses and a GUIDE come back for 'fraud' looks like
28
+ * over-matching when the case promised three specific entries, and the honest
29
+ * reading is the opposite: the guide is a description-and-keyword hit, which is
30
+ * exactly the behaviour under test. Note Payment METHODS does not match and
31
+ * Payment CLUSTERS does; they are adjacent in the sidebar and easy to confuse.
32
+ *
33
+ * Groups keep their declared order and only their contents narrow, so both
34
+ * headings stay and Trust & Safety stays above Guides.
35
+ */
16
36
  export const authored = {
17
37
  "checklist-search-and-nav-sidebar-search-sidebar-search-matches-description": {
18
38
  roles: ["admin"],
@@ -21,15 +41,20 @@ export const authored = {
21
41
  "Sign in as admin@letitrip.in / TempPass123!.",
22
42
  "Open /admin and read the sidebar's group headings in order.",
23
43
  "Type fraud in the sidebar search box.",
24
- "Read which entries remain and which groups they sit under.",
44
+ "Read which entries remain, count them, and note which group heading each sits under.",
25
45
  "Clear the box and type zzzznope.",
26
46
  "Read the sidebar.",
27
47
  "Clear the box and read the group order again.",
28
48
  ],
29
- inputs: { query: "fraud", nonsenseQuery: "zzzznope" },
49
+ inputs: {
50
+ query: "fraud",
51
+ nonsenseQuery: "zzzznope",
52
+ expectedMatches: 5,
53
+ expectedGroups: 2,
54
+ },
30
55
  expectedBehaviour: "The filter matches a screen's description and keywords, not only its label. Matching on the label alone means only a screen with 'fraud' in its name survives the query — and none is named that.",
31
- expectedUiState: "'fraud' leaves Scam Registry, Address Clusters and Payment Clusters visible, each still under its own group heading. Groups keep their normal order; only their contents narrow. 'zzzznope' empties the sidebar rather than leaving it unfiltered. Clearing the box restores every group in the original order.",
32
- expectedData: { nonsenseVisibleCount: 0 },
56
+ expectedUiState: "'fraud' leaves FIVE entries across TWO groups: Banned Addresses, Address Clusters, Payment Clusters and Scam Registry under Trust & Safety, and Trust & Safety Guide under Guides. The guide is a legitimate hit — it matches on description and keywords, which is the behaviour under test, not over-matching. Payment Methods must NOT appear; only Payment Clusters carries the keyword, and the two sit next to each other. Groups keep their normal order and only their contents narrow. 'zzzznope' empties the sidebar rather than leaving it unfiltered. Clearing the box restores every group in the original order.",
57
+ expectedData: { nonsenseVisibleCount: 0, fraudMatchCount: 5, fraudGroupCount: 2 },
33
58
  endResult: "Nothing persists; the filter is local to the sidebar and resets on reload.",
34
59
  },
35
60
  };
@@ -11,6 +11,29 @@
11
11
  * classic half-fix: the seller sees an approved dashboard while every public
12
12
  * visibility check reads the store's, so the store never appears anywhere.
13
13
  *
14
+ * 🛑 THIS CHAIN IS ONE-SHOT AND LEAVES karthik.new@gmail.com A SELLER FOREVER.
15
+ * Read this before running it a second time, because the second run does not
16
+ * fail — it tests something else while looking the same.
17
+ *
18
+ * `users` is PRESERVE tier, so the tester lifecycle never wipes it. `stores` is
19
+ * SEED_OWNED, so it IS wiped. After one run karthik therefore holds
20
+ * `role: seller` and a `storeStatus` with NO STORE — a half-state neither
21
+ * `sell-redirect` (which needs "a buyer with no store") nor `apply-seller`
22
+ * (which needs someone who has not applied) is written for.
23
+ *
24
+ * A users reseed only half-fixes it: the seed declares `role`, so that is
25
+ * restored — but it does NOT declare `storeStatus`, and `appkit-seed load` is a
26
+ * merge write that cannot REMOVE a key it does not carry. The leftover status
27
+ * survives every reseed. Clearing it needs a deliberate field delete.
28
+ *
29
+ * So before re-running this page:
30
+ * 1. delete `storeStatus` (and `storeId`, if set) from user-karthik-new
31
+ * 2. npx appkit-seed load --collections users (restores role)
32
+ *
33
+ * The honest alternative is a fresh QA signup per run, as
34
+ * account-auth/signup-login does with its run-stamped address — but that leaks
35
+ * a permanent account each time, which is its own cost.
36
+ *
14
37
  * @tag domain:tester
15
38
  * @tag layer:seed
16
39
  * @tag pattern:none
@@ -24,13 +24,14 @@ export const authored = {
24
24
  startPage: "/store/products/new",
25
25
  steps: [
26
26
  "Sign in as tyson@beybladearena.in / TempPass123!.",
27
- "Open /store/products/new and find the final-sale control without touching it.",
28
- "Read its state.",
29
- "Open the auction creation form and read the same control's state.",
30
- "Open the pre-order and classified creation forms and read it in each.",
27
+ "Open /store/products/new and go to the Returns step WITHOUT touching anything. The control is a toggle labelled 'Accept change-of-mind returns' — there is none named 'final sale' — and it stores the INVERSE: toggle OFF means finalSale, which is the default.",
28
+ "Click 'Show all fields (advanced)' first — the quick form has no Returns step, so the toggle below is not on screen until you switch.",
29
+ "Read its state. Expect it OFF, meaning final sale is on.",
30
+ "Open /store/auctions/new and read the same toggle's state.",
31
+ "Open /store/pre-orders/new and /store/classified/new and read it in each.",
31
32
  ],
32
33
  expectedBehaviour: "Final sale is on by default on every listing type, so a seller who never opens the setting is protected rather than silently exposed to returns they did not agree to.",
33
- expectedUiState: "The control reads as on in every creation form checked, before any interaction. A type whose default differs is the finding, named by type.",
34
+ expectedUiState: "The 'Accept change-of-mind returns' toggle reads OFF in all four creation forms before any interaction — OFF is what makes the listing final sale. A type whose default differs is the finding, named by type. Remember the polarity when recording: a toggle that reads ON here means returns ARE accepted, which is the opposite of the expected default.",
34
35
  expectedData: { defaultFinalSale: true },
35
36
  endResult: "Leave every editor without saving. This case cannot distinguish a real default from a field that fails to load — that is what the next case is for.",
36
37
  },
@@ -40,11 +41,11 @@ export const authored = {
40
41
  steps: [
41
42
  "Sign in as tyson@beybladearena.in / TempPass123!.",
42
43
  "Open /store/products and open the editor for 'Beyblade Burst B-01 Valkyrie'.",
43
- "Turn the final-sale control OFF.",
44
+ "Turn final sale OFF. 🛑 THE CONTROL IS INVERTED AND RENAMED: there is no switch labelled 'Final sale'. It is a toggle reading 'Accept change-of-mind returns', in the Returns step, and turning final sale OFF means turning that toggle ON. Its OFF state is the default and stores finalSale as final-sale.",
44
45
  "Save and read the confirmation.",
45
46
  "RELOAD the editor and read the control's state.",
46
- "Open the product's public page and read whether a final-sale badge is shown.",
47
- "Turn the control back on, save, and reload to confirm.",
47
+ "Open /products/product-beyblade-burst-valkyrie and read whether a 'Final Sale' badge is shown — that is the badge's exact label.",
48
+ "Turn the 'Accept change-of-mind returns' toggle back OFF — restoring final sale, which is the seeded default — then save and reload to confirm.",
48
49
  ],
49
50
  inputs: { productId: "product-beyblade-burst-valkyrie", finalSale: false },
50
51
  expectedBehaviour: "Turning the flag off persists. Off is the only testable direction: a field that never saves reads back as its on default, so a case that only ever turns it ON passes against a completely broken save.",
@@ -58,11 +59,11 @@ export const authored = {
58
59
  steps: [
59
60
  "Sign in as tyson@beybladearena.in / TempPass123!.",
60
61
  "Open the editor for 'Beyblade Burst B-01 Valkyrie' and find the return-policy field.",
61
- "Turn final sale off so returns are permitted.",
62
+ "Turn ON the toggle labelled 'Accept change-of-mind returns' in the Returns step — that is what turns final sale off. There is no control named 'final sale'; the switch is deliberately phrased positively so its ON state never means fewer buyer rights.",
62
63
  "Type 'QA Return Policy authorable — 7 days, unopened only.' into the policy field and save.",
63
64
  "RELOAD the editor and read the field.",
64
- "Open the product's public page in a private window and find the return policy.",
65
- "Restore the original policy and turn final sale back on.",
65
+ "Open /products/product-beyblade-burst-valkyrie in a private window and find the return policy.",
66
+ "Restore the original policy and turn the 'Accept change-of-mind returns' toggle back OFF, which restores final sale.",
66
67
  ],
67
68
  inputs: {
68
69
  productId: "product-beyblade-burst-valkyrie",
@@ -81,8 +82,8 @@ export const authored = {
81
82
  "Open /admin/products, open the same product, and turn final sale OFF.",
82
83
  "Save and reload the admin editor to confirm.",
83
84
  "Sign out and sign in as tyson@beybladearena.in / TempPass123!.",
84
- "Open the seller editor for the same product and read the control.",
85
- "Open the public page and read whether the badge is shown.",
85
+ "Open that product's SELLER editor at /store/products/{id}/edit and read the 'Accept change-of-mind returns' toggle — the admin form names the field directly while the seller form inverts and renames it, which is the disagreement this case is for.",
86
+ "Open that product's public page at /products/{its slug} and read whether the 'Final Sale' badge is shown.",
86
87
  ],
87
88
  inputs: { productId: "product-beyblade-burst-valkyrie" },
88
89
  expectedBehaviour: "An admin override reaches the same stored field the seller edits, so both dashboards and the public page agree afterwards. An override held somewhere else would leave the seller's editor showing one answer and the buyer seeing another — and the buyer's is the one that binds.",
@@ -24,11 +24,12 @@ export const authored = {
24
24
  steps: [
25
25
  "Sign in as tyson@beybladearena.in / TempPass123!.",
26
26
  "Open /store/products/new and publish with every field empty, reading where the errors land.",
27
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create: its image control is a single-value field labelled 'Product Image' — no gallery, no multi-select, no crop editor, no video field and no brand. Anything beyond one plain image needs the advanced form.",
27
28
  "Type 'QA Product list-standard' as the title and 750 as the price.",
28
29
  "Select a category and a brand, set the stock to 5, and write a description.",
29
30
  "Upload public/test-media/sample-image.png as the main image.",
30
31
  "Publish.",
31
- "Open the product's public page and read its title, price, stock badge and images.",
32
+ "Open the product's public page at /products/{its slug} and read its title, price, stock badge and images.",
32
33
  ],
33
34
  inputs: { title: "QA Product list-standard", price: 750, stock: 5, image: "public/test-media/sample-image.png" },
34
35
  expectedBehaviour: "A standard listing is created and becomes publicly visible. The slug is derived from the title at creation and is then immutable — the update schema does not accept it, which is the deliberate convention across categories, brands and bundles and is what keeps existing links working.",
@@ -40,11 +41,11 @@ export const authored = {
40
41
  startPage: "/store/auctions/new",
41
42
  steps: [
42
43
  "Sign in as tyson@beybladearena.in / TempPass123!.",
43
- "Open the auction creation form and type 'QA Auction list-auction' as the title.",
44
+ "Open /store/auctions/new and type 'QA Auction list-auction' as the title.",
44
45
  "Set the starting bid to 1000, the minimum increment to 100, and an end date two days out.",
45
46
  "Upload public/test-media/sample-image.png and fill every other required field.",
46
47
  "Publish.",
47
- "Open the auction's public page and read the starting bid, increment, countdown and purchase controls.",
48
+ "Open the auction's public page at /auctions/{its slug} and read the starting bid, increment, countdown and purchase controls.",
48
49
  "Delete the auction.",
49
50
  ],
50
51
  inputs: {
@@ -62,11 +63,11 @@ export const authored = {
62
63
  startPage: "/store/pre-orders/new",
63
64
  steps: [
64
65
  "Sign in as tyson@beybladearena.in / TempPass123!.",
65
- "Open the pre-order creation form and type 'QA Pre-order list-preorder' as the title.",
66
+ "Open /store/pre-orders/new and type 'QA Pre-order list-preorder' as the title.",
66
67
  "Set the price to 1200, the deposit percentage to 25, an estimated delivery date a month out, and the production status.",
67
68
  "Upload public/test-media/sample-image.png and fill every other required field.",
68
69
  "Publish.",
69
- "Open the pre-order's public page and read the price, deposit note, delivery date and status badge.",
70
+ "Open the pre-order's public page at /pre-orders/{its slug} and read the price, deposit note, delivery date and status badge.",
70
71
  "Delete the pre-order.",
71
72
  ],
72
73
  inputs: {
@@ -89,7 +90,7 @@ export const authored = {
89
90
  "Write down every field's current value.",
90
91
  "Change the price to 850 and nothing else.",
91
92
  "Save, then RELOAD the editor and compare every field against what was written down.",
92
- "Open the public page and read the price and everything else.",
93
+ "Open the public page at /products/{its slug} and read the price and everything else.",
93
94
  "Delete the listing.",
94
95
  ],
95
96
  inputs: { priceBefore: 750, priceAfter: 850 },
@@ -103,8 +104,8 @@ export const authored = {
103
104
  startPage: "/store/products/new",
104
105
  steps: [
105
106
  "Sign in as tyson@beybladearena.in / TempPass123!.",
106
- "Open /store/products/new and publish a listing titled 'QA Product belongs-to-store' at 300.",
107
- "Open its public page and read the seller line at the bottom of the info panel.",
107
+ "Open /store/products/new and publish a listing titled 'QA Product belongs-to-store' at 300, with Category 'Beyblade Burst' and a Product Image — the quick form requires title, category, price AND image, so a publish naming only two of them cannot succeed.",
108
+ "Open its public page at /products/{its slug} and read the seller line at the bottom of the info panel.",
108
109
  "Click the 'Visit Store' link and read where it lands.",
109
110
  "Open /stores/store-beyblade-arena and find the listing in the Products tab.",
110
111
  "Sign out and sign in as admin@letitrip.in / TempPass123!, a different seller who owns store-letitrip-official.",
@@ -121,7 +122,7 @@ export const authored = {
121
122
  startPage: "/store",
122
123
  steps: [
123
124
  "Sign in as tyson@beybladearena.in / TempPass123!.",
124
- "Open the store's settings and change the store name to 'QA Store rename-updates-cards'.",
125
+ "Open /store/storefront and change the store name to 'QA Store rename-updates-cards'.",
125
126
  "Save and reload to confirm.",
126
127
  "Open /products in a private window and find a card from that store.",
127
128
  "Read the seller name on the card and on the product's detail page.",
@@ -174,6 +175,7 @@ export const authored = {
174
175
  steps: [
175
176
  "Sign in as tyson@beybladearena.in / TempPass123!.",
176
177
  "Open /store/products/new and read any stated video duration or size limit.",
178
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create: its image control is a single-value field labelled 'Product Image' — no gallery, no multi-select, no crop editor, no video field and no brand. Anything beyond one plain image needs the advanced form.",
177
179
  "Upload public/test-media/sample-video.mp4 to the video field.",
178
180
  "Wait for it to finish and read the preview and any duration shown.",
179
181
  "Read whether a poster frame was captured.",
@@ -190,6 +192,7 @@ export const authored = {
190
192
  steps: [
191
193
  "Sign in as tyson@beybladearena.in / TempPass123!.",
192
194
  "Open /store/products/new and select public/test-media/sample-image.png, sample-image.jpg and sample-image.webp together in one file dialog.",
195
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create: its image control is a single-value field labelled 'Product Image' — no gallery, no multi-select, no crop editor, no video field and no brand. Anything beyond one plain image needs the advanced form.",
193
196
  "Watch the upload progress and read whether each file reports its own.",
194
197
  "Read the previews once they finish and count them.",
195
198
  "Check each preview shows its own file rather than three copies of one.",
@@ -206,6 +209,7 @@ export const authored = {
206
209
  steps: [
207
210
  "Sign in as tyson@beybladearena.in / TempPass123!.",
208
211
  "Open /store/products/new and upload sample-image.png, sample-image.jpg and sample-image.webp.",
212
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create: its image control is a single-value field labelled 'Product Image' — no gallery, no multi-select, no crop editor, no video field and no brand. Anything beyond one plain image needs the advanced form.",
209
213
  "Note which image is in which position.",
210
214
  "Remove the MIDDLE one and read the remaining previews.",
211
215
  "Check the remaining two are the ones expected and are in their original relative order.",
@@ -222,6 +226,7 @@ export const authored = {
222
226
  steps: [
223
227
  "Sign in as tyson@beybladearena.in / TempPass123!.",
224
228
  "Open /store/products/new and upload public/test-media/sample-image.png as the main image.",
229
+ "Click 'Show all fields (advanced)' first. 🛑 /store/products/new opens the QUICK form for a standard create: its image control is a single-value field labelled 'Product Image' — no gallery, no multi-select, no crop editor, no video field and no brand. Anything beyond one plain image needs the advanced form.",
225
230
  "Open the crop editor and read the aspect-ratio presets offered.",
226
231
  "Choose a preset, rotate the image, zoom in, and apply.",
227
232
  "Read the resulting preview.",
@@ -88,10 +88,26 @@ export const authored = {
88
88
  roles: ["seller"],
89
89
  startPage: "/store/products",
90
90
  steps: [
91
- "Sign in as admin@letitrip.in / TempPass123!, who owns store-letitrip-official — a different store from Beyblade Arena.",
91
+ /*
92
+ * 🛑 NOT THE ADMIN. `getSellerProductAction` reads
93
+ * `if (!isAdminUser(profile))` before comparing stores, so an admin is
94
+ * EXEMPT by design and will open Beyblade Arena's listing successfully.
95
+ * Signing in as admin@letitrip.in and expecting a 404 tested the
96
+ * exemption while claiming to test the gate — it would have reported a
97
+ * working ownership check as broken.
98
+ *
99
+ * 🛑 AND THE CONFOUND IS REAL: store-blader-bazaar is PENDING, because
100
+ * there is no second ACTIVE non-admin seller in the seed (Beyblade Arena
101
+ * is the only one; Vintage Vault Co. is suspended). So a refusal here may
102
+ * come from the store's status rather than from ownership, and the step
103
+ * below says how to tell them apart rather than pretending the fixture is
104
+ * cleaner than it is.
105
+ */
106
+ "Sign in as meera.blader@gmail.com / TempPass123!, who owns store-blader-bazaar — a different store from Beyblade Arena.",
107
+ "Open /store/products first — if meera's own dashboard is itself blocked, her store's PENDING status is the cause and this case is a null with that reason, not a verdict about ownership.",
92
108
  "In a second tab open /products and copy the slug of any listing belonging to Beyblade Arena, e.g. product-beyblade-burst-valkyrie.",
93
109
  "Back in the first tab, navigate to /store/products/<that slug>/edit.",
94
- "Read what the page shows.",
110
+ "Read what the page shows, and whether the refusal is about OWNERSHIP or about the store being pending.",
95
111
  ],
96
112
  expectedBehaviour: "Ownership is checked against the caller's STORE, and a product's `storeId` is the store SLUG rather than the owner's Auth uid. A check comparing `storeId` to a uid compares two different namespaces and can never pass — which returns null for every legitimate seller too, so a page that 404s here must be verified to still open the seller's OWN listing.",
97
113
  expectedUiState: "A not-found page, with no editable fields.",
@@ -103,12 +119,23 @@ export const authored = {
103
119
  startPage: "/store/products",
104
120
  steps: [
105
121
  "Sign in as tyson@beybladearena.in / TempPass123!.",
106
- "Open /categories/original-plastic-gen — a tier-3 category whose parent is original-tops and whose root is spinning-tops.",
122
+ /*
123
+ * 🛑 The `category-` PREFIX is load-bearing in all three URLs below.
124
+ *
125
+ * These steps named /categories/original-plastic-gen, original-tops and
126
+ * spinning-tops, and every one of them 404s — a category's document id IS
127
+ * its prefixed slug (§ Slug Prefix System), so the real routes are
128
+ * /categories/category-original-plastic-gen and so on. Verified 200 on all
129
+ * three prefixed forms. `audit-tester-checklist-hrefs` did not catch it
130
+ * because it validates `href` and `startPage`, and these are URLs written
131
+ * inside step PROSE, which nothing checks.
132
+ */
133
+ "Open /categories/category-original-plastic-gen — its parent is category-original-tops and its root is category-spinning-tops.",
107
134
  "Open /store/products, pick a published listing, and open its Edit action.",
108
- "Change its Category to Original Plastic Gen and press Save.",
109
- "Open /categories/original-plastic-gen and look for the listing.",
110
- "Open /categories/original-tops and look for the same listing.",
111
- "Open /categories/spinning-tops and look for it again.",
135
+ "Change its Category to Plastic Generation (that is the display name; the id is category-original-plastic-gen) and press Save.",
136
+ "Open /categories/category-original-plastic-gen and look for the listing.",
137
+ "Open /categories/category-original-tops and look for the same listing.",
138
+ "Open /categories/category-spinning-tops and look for it again.",
112
139
  ],
113
140
  expectedBehaviour: "A product stores the FULL ancestor chain, so it is reachable from its own category and from every ancestor with one query. If only the leaf is stored, the ancestor pages match nothing — and the leaf page can fail too, because the stored field and the field the form sends are not the same one.",
114
141
  expectedUiState: "The listing's card appears on all three pages: the deep category, its parent, and its root.",
@@ -21,6 +21,16 @@
21
21
  * @tag sideEffects:none
22
22
  */
23
23
  const SIGN_IN_SELLER = "Sign in as tyson@beybladearena.in / TempPass123!.";
24
+ /*
25
+ * The two MIXED-ROLE cases below list roles [seller, buyer], and identityFor
26
+ * resolves that to the BUYER session — admin wins if present, then all-seller,
27
+ * and everything else falls to the buyer. So those batches open as someone who
28
+ * owns no store, and their first step is a seller action. They say so; the
29
+ * twelve seller-only cases on this page need no such warning and do not carry
30
+ * one.
31
+ */
32
+ const SIGN_IN_SELLER_MIXED = SIGN_IN_SELLER +
33
+ " 🛑 THIS CASE OPENS AS THE BUYER because its roles are [seller, buyer]. Copy tester/.tester-runs/session-seller.json over session.json and CLOSE THE BROWSER before this step; the MCP reads that file only when the context is created.";
24
34
  export const authored = {
25
35
  "checklist-selling-listing-lifecycle-standard-create-publish-sell": {
26
36
  roles: ["seller", "buyer"],
@@ -44,13 +54,13 @@ export const authored = {
44
54
  roles: ["seller", "buyer"],
45
55
  startPage: "/store/products",
46
56
  steps: [
47
- SIGN_IN_SELLER,
57
+ SIGN_IN_SELLER_MIXED,
48
58
  "Create an auction titled 'QA Lifecycle Auction' with a starting bid of 500, an increment of 50 and no reserve, ending within the current test window, and publish it.",
49
- "Sign in as rehan.sheikh@gmail.com / TempPass123! and place a bid of 550.",
50
- "Sign in as vivaan.kapoor@gmail.com / TempPass123! and place a bid of 600.",
59
+ "Swap session: copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER before continuing — the MCP reads that file only when the context is created, so swapping over an open browser changes nothing and the next steps run as the previous person. Then sign in as rehan.sheikh@gmail.com / TempPass123! and place a bid of 550.",
60
+ "Sign out and sign in as vivaan.kapoor@gmail.com / TempPass123! — same browser session file, a different account — and place a bid of 600.",
51
61
  "Wait for the auction to end.",
52
62
  "Read the auction page for the outcome.",
53
- "Sign in as the winning bidder and check the won line is in their cart.",
63
+ "Sign in as the winning bidder (vivaan, at ₹600) and check the won line is in their cart under the Won Auctions tab.",
54
64
  ],
55
65
  inputs: { title: "QA Lifecycle Auction", startingBid: 500, increment: 50, bidA: 550, bidB: 600 },
56
66
  expectedBehaviour: "The auction closes at its end time and the top bidder wins. A win reaches the buyer as a LOCKED cart line rather than as a ready-made order, because that is the only checkout that knows how to collect an address, charge a method and split per store — an order written directly by settlement had no payment method and no way to pay it at all.",
@@ -114,7 +124,7 @@ export const authored = {
114
124
  roles: ["seller", "buyer"],
115
125
  startPage: "/store/products",
116
126
  steps: [
117
- SIGN_IN_SELLER,
127
+ SIGN_IN_SELLER_MIXED,
118
128
  "Create a prize draw titled 'QA Lifecycle Draw' with 3 entries at 100 each and a reveal window inside the current test window, and publish it.",
119
129
  "Sign in as rehan.sheikh@gmail.com / TempPass123! and buy two entries.",
120
130
  "Sign in as vivaan.kapoor@gmail.com / TempPass123! and buy the last one.",
@@ -169,11 +179,11 @@ export const authored = {
169
179
  startPage: "/digital-codes",
170
180
  steps: [
171
181
  "Sign in as rehan.sheikh@gmail.com / TempPass123!.",
172
- "Open /digital-codes on the All scope and find digitalcode-beyblade-x-app-launch-codes-depleted, whose code pool is empty while its stock field still reads 5.",
182
+ "Open /digital-codes — it REDIRECTS to /products?listingType=digital-code, which is expected: the per-type browse pages were folded into one catalogue with an in-page type filter — then set the All scope and find digitalcode-beyblade-x-app-launch-codes-depleted, whose code pool is empty while its stock field still reads 5.",
173
183
  "Open it and read whether it can be bought.",
174
184
  "Read its stock number.",
175
185
  "Compare that stock number against the pool count.",
176
- "Open /digital-codes on the Available scope and check the listing is not there.",
186
+ "Open /digital-codes — it REDIRECTS to /products?listingType=digital-code, which is expected: the per-type browse pages were folded into one catalogue with an in-page type filter — then set the Available scope and check the listing is not there.",
177
187
  ],
178
188
  expectedBehaviour: "Availability for this type is the NESTED codes-available count, not the stock field — and the two disagree on purpose in the seed, so a check reading stock alone passes while the listing is unbuyable. The branch that reads the pool once looked at a top-level field the record does not have, so it had never fired even once.",
179
189
  expectedUiState: "The listing is not buyable and is absent from the Available scope, even though its stock number is above zero. Finding it buyable is the finding.",
@@ -26,12 +26,12 @@ export const authored = {
26
26
  startPage: "/store/classified/new",
27
27
  steps: [
28
28
  "Sign in as tyson@beybladearena.in / TempPass123!.",
29
- "Open the classified creation form and type 'QA Classified roundtrip-meetup' as the title.",
29
+ "Open /store/classified/new and type 'QA Classified roundtrip-meetup' as the title.",
30
30
  "Set the city to Mumbai, the contact method to chat, negotiable to on, and accepts-shipping to off.",
31
31
  "Type 1500 as the price and fill every other required field.",
32
32
  "Save.",
33
33
  "RELOAD the editor for that listing and read all four type-specific values.",
34
- "Open the listing's public page and read the same four.",
34
+ "Open the listing's public page at /classified/{its slug} and read the same four.",
35
35
  "Delete the listing.",
36
36
  ],
37
37
  inputs: {
@@ -58,7 +58,7 @@ export const authored = {
58
58
  "Upload public/test-media/sample-video.mp4 and type 2000 as the price.",
59
59
  "Save.",
60
60
  "RELOAD the editor and read every one of those values.",
61
- "Open the public page and read the species line and the delivery restrictions.",
61
+ "Open the public page at /live/{its slug} and read the species line and the delivery restrictions.",
62
62
  "Delete the listing.",
63
63
  ],
64
64
  inputs: {
@@ -80,11 +80,11 @@ export const authored = {
80
80
  startPage: "/store/digital-codes/new",
81
81
  steps: [
82
82
  "Sign in as tyson@beybladearena.in / TempPass123!.",
83
- "Open the digital-code creation form and type 'QA Digital Code roundtrip-delivery' as the title.",
83
+ "Open /store/digital-codes/new and type 'QA Digital Code roundtrip-delivery' as the title.",
84
84
  "Set the delivery mode to auto-claim and type 250 as the price.",
85
85
  "Add three codes to the pool and save.",
86
86
  "RELOAD the editor and read the delivery mode and the pool count.",
87
- "Open the public page and read the availability shown.",
87
+ "Open the public page at /digital-codes/{its slug} and read the availability shown.",
88
88
  "Change the delivery mode to manual email, save, reload and read it again.",
89
89
  "Delete the listing.",
90
90
  ],
@@ -101,14 +101,14 @@ export const authored = {
101
101
  },
102
102
  "checklist-selling-listing-type-fields-roundtrip-roundtrip-print-meta": {
103
103
  roles: ["seller"],
104
- startPage: "/store/products/new",
104
+ startPage: "/store/art/new",
105
105
  steps: [
106
106
  "Sign in as tyson@beybladearena.in / TempPass123!.",
107
- "Open the art or sticker creation form and type 'QA Art Print roundtrip-meta' as the title.",
107
+ "Open /store/art/new — stickers have their own at /store/stickers/new and the same four print fields — and type 'QA Art Print roundtrip-meta' as the title. These per-type routes render the FULL form; /store/products/new would open the six-field quick form, which has none of these fields.",
108
108
  "Set the size, the material, the finish and the edition size, noting each value.",
109
109
  "Type 800 as the price and save.",
110
110
  "RELOAD the editor and read all four print fields.",
111
- "Open the public page and read the same four.",
111
+ "Open the public page at /products/{its slug} — art and stickers deliberately use the STANDARD product route, not a dedicated one — and read the same four.",
112
112
  "Delete the listing.",
113
113
  ],
114
114
  inputs: { title: "QA Art Print roundtrip-meta", price: 800 },
@@ -125,7 +125,7 @@ export const authored = {
125
125
  "Read every field the form offers and note which are empty.",
126
126
  "Compare each against the public page for that product.",
127
127
  "Change nothing and save.",
128
- "RELOAD the public page and check nothing has changed — title, price, description, images, category, brand, stock.",
128
+ "RELOAD the public page and check nothing has changed — title, price, description, images, category, brand, stock AND STATUS. Status is the one to check hardest: a form seeded from an unwrapped result envelope resolves every real field to undefined, and the status ternary then evaluates undefined === 'published' to false and writes 'draft' — which removes a live listing from every public surface while the save reports success and the editor looks merely empty.",
129
129
  ],
130
130
  inputs: { productId: "product-beyblade-burst-valkyrie" },
131
131
  expectedBehaviour: "An edit form opens populated from a read that strips nothing, and a no-op save writes back exactly what was there. This is the single most valuable case on the page: a form seeded from a projection that drops fields will save those drops back, so opening and saving an untouched record quietly deletes data.",
@@ -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 type 'QA Product media-gallery' as the title.",
28
+ "Click 'Show all fields (advanced)' before touching any media. 🛑 /store/products/new opens the QUICK form by default for a create of a standard listing, and it has ONE field — 'Product Image' — with no gallery and no video field at all. Every media step below needs the advanced form.",
28
29
  "Upload public/test-media/sample-image.png to the gallery.",
29
30
  "Upload public/test-media/sample-image.jpg to the gallery.",
30
31
  "Upload public/test-media/sample-image.webp to the gallery.",
@@ -46,6 +47,7 @@ export const authored = {
46
47
  steps: [
47
48
  "Sign in as tyson@beybladearena.in / TempPass123!.",
48
49
  "Open /store/products/new and type 'QA Product media-tenth' as the title.",
50
+ "Click 'Show all fields (advanced)' before touching any media. 🛑 /store/products/new opens the QUICK form by default for a create of a standard listing, and it has ONE field — 'Product Image' — with no gallery and no video field at all. Every media step below needs the advanced form.",
49
51
  "Upload public/test-media/sample-image.png to the gallery nine times, counting the previews after each.",
50
52
  "Upload it a tenth time.",
51
53
  "Read the preview count and any message about the limit.",
@@ -62,6 +64,7 @@ export const authored = {
62
64
  steps: [
63
65
  "Sign in as tyson@beybladearena.in / TempPass123!.",
64
66
  "Open /store/products/new and upload public/test-media/sample-image.png ten times.",
67
+ "Click 'Show all fields (advanced)' before touching any media. 🛑 /store/products/new opens the QUICK form by default for a create of a standard listing, and it has ONE field — 'Product Image' — with no gallery and no video field at all. Every media step below needs the advanced form.",
65
68
  "Open the browser's Network panel.",
66
69
  "Attempt an eleventh upload of the same file.",
67
70
  "Read the message shown and count the previews.",
@@ -79,6 +82,7 @@ export const authored = {
79
82
  steps: [
80
83
  "Sign in as tyson@beybladearena.in / TempPass123!.",
81
84
  "Open /store/products/new and type 'QA Product media-second-video' as the title.",
85
+ "Click 'Show all fields (advanced)' before touching any media. 🛑 /store/products/new opens the QUICK form by default for a create of a standard listing, and it has ONE field — 'Product Image' — with no gallery and no video field at all. Every media step below needs the advanced form.",
82
86
  "Upload public/test-media/sample-video.mp4 to the video field and wait for its poster frame.",
83
87
  "Attempt to upload public/test-media/sample-video.mp4 a second time.",
84
88
  "Read the message shown.",
@@ -95,10 +99,11 @@ export const authored = {
95
99
  startPage: "/store/products/new",
96
100
  steps: [
97
101
  "Sign in as tyson@beybladearena.in / TempPass123!.",
98
- "Open /store/products/new and read the stated image and video limits.",
99
- "Open the auction creation form and read its stated limits.",
100
- "Open the pre-order creation form and read its limits.",
101
- "Open the classified, digital-code and live-item creation forms and read each.",
102
+ "Click 'Show all fields (advanced)' before touching any media. 🛑 /store/products/new opens the QUICK form by default for a create of a standard listing, and it has ONE field — 'Product Image' — with no gallery and no video field at all. Every media step below needs the advanced form.",
103
+ "Read the stated image and video limits on the advanced form.",
104
+ "Open /store/auctions/new and read its stated limits. These per-type creation routes render the full form directly, so no advanced switch is needed on any of them.",
105
+ "Open /store/pre-orders/new and read its limits.",
106
+ "Open /store/classified/new, /store/digital-codes/new and /store/live/new and read each.",
102
107
  "Compare all six.",
103
108
  ],
104
109
  expectedBehaviour: "The caps come from one shared limits module, so every listing type states the same numbers. A type stating a different cap has a local copy, and a local copy is a number that will drift the first time the shared one changes.",
@@ -63,13 +63,13 @@ export const authored = {
63
63
  SIGN_IN_SELLER,
64
64
  "Open /store/products/new and switch to the advanced form.",
65
65
  "Open the Media section and read the size limit stated next to 'Main Image'.",
66
- "Choose public/test-media/oversized.png for the Main Image and start a timer. (The batch generates this file — it is >10 MB of incompressible noise and deliberately not committed.)",
66
+ "Choose public/test-media/oversized.png for the Main Image and start a timer. 🛑 THE BATCH DOES NOT GENERATE THIS FILE — there is no fixture manifest for this page. Run `node tester/scripts/make-media-fixtures.mjs --with-oversized` first; it sizes the fixture by reading the caps out of appkit/src/_internal/shared/media/limits.ts rather than from a remembered number. It is >10 MB of incompressible noise and deliberately not committed.",
67
67
  "Read the message that appears and note how long it took.",
68
68
  `Open the browser network panel and check whether any request to ${SIGN} was made.`,
69
69
  ],
70
70
  inputs: { image: "public/test-media/oversized.png", limitMb: 10 },
71
71
  expectedBehaviour: "A file over the limit is refused BEFORE any bytes leave the browser. The point is the seller's time: a 12 MB upload that travels, then finalises, then fails validation has taken a minute to tell them something the form already knew when they picked the file. The limit is also stated up front rather than only in the refusal.",
72
- expectedUiState: `The refusal is immediate (well under a second) and reads 'File size must be less than 10MB', naming the actual size. No ${SIGN} request appears in the network panel. The field keeps whatever image it had before.`,
72
+ expectedUiState: `The refusal is immediate (well under a second) and reads 'File size must be less than 10MB (current: …)', naming the actual size — 10 is MAX_IMAGE_BYTES from limits.ts. 🛑 EXPECT 50MB INSTEAD, AND RECORD IT: the gallery field passes maxSizeMB={50} explicitly, which is the VIDEO cap applied to a field accepting image/* and video/* alike, so a 12 MB image is accepted client-side. MediaUploadField's own auto-derived defaults (image 25, video 200, pdf 10) disagree with limits.ts (10, 50, 20) on all three. Report the number the message actually states. No ${SIGN} request appears in the network panel. The field keeps whatever image it had before.`,
73
73
  expectedData: { signRequestsMade: 0 },
74
74
  endResult: "Leave without saving; nothing was uploaded.",
75
75
  },
@@ -121,7 +121,7 @@ export const authored = {
121
121
  "Add public/test-media/sample-image-2.png as the first GALLERY image.",
122
122
  "Read both previews and confirm they show different pictures.",
123
123
  "Publish with Title 'Slot Collision Probe', Description 'Main image and first gallery image must not overwrite each other.', Category 'Beyblade Burst', Price 499.",
124
- "Open the product's public page and read the main image and the first gallery thumbnail.",
124
+ "Open the product's public page at /products/{its slug} and read the main image and the first gallery thumbnail.",
125
125
  ],
126
126
  inputs: { mainImage: "public/test-media/sample-image.png", firstGalleryImage: "public/test-media/sample-image-2.png", price: 499 },
127
127
  expectedBehaviour: "The main image occupies index 1 and the gallery starts at index 2. Filenames here are content-derived with no timestamp, so two uploads that computed the same index would write the same storage path and the second would silently overwrite the first — the seller would see one of their two pictures simply vanish.",