@mohasinac/appkit 4.37.0 → 4.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/_internal/server/features/bundles/og.d.ts +0 -1
  2. package/dist/_internal/server/features/bundles/og.js +2 -18
  3. package/dist/_internal/server/features/catalogue/og.d.ts +0 -1
  4. package/dist/_internal/server/features/catalogue/og.js +1 -9
  5. package/dist/_internal/server/features/classified/og.d.ts +0 -1
  6. package/dist/_internal/server/features/classified/og.js +1 -9
  7. package/dist/_internal/server/features/digital-code/og.d.ts +0 -1
  8. package/dist/_internal/server/features/digital-code/og.js +1 -9
  9. package/dist/_internal/server/features/grouped/og.d.ts +7 -4
  10. package/dist/_internal/server/features/grouped/og.js +7 -6
  11. package/dist/_internal/server/features/item-requests/og.d.ts +0 -1
  12. package/dist/_internal/server/features/item-requests/og.js +2 -18
  13. package/dist/_internal/server/features/live/og.d.ts +0 -1
  14. package/dist/_internal/server/features/live/og.js +1 -9
  15. package/dist/_internal/server/features/prize-draws/og.js +5 -10
  16. package/dist/_internal/server/features/products/og.js +5 -2
  17. package/dist/_internal/server/features/seo/sitemap.js +10 -2
  18. package/dist/_internal/server/features/shared/listing-adapters.d.ts +1 -2
  19. package/dist/_internal/server/features/shared/listing-adapters.js +5 -3
  20. package/dist/_internal/shared/listing-types/_registry.d.ts +31 -4
  21. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  22. package/dist/_internal/shared/listing-types/art/config.js +2 -0
  23. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  24. package/dist/_internal/shared/listing-types/auction/config.js +3 -0
  25. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  26. package/dist/_internal/shared/listing-types/classified/config.js +3 -0
  27. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  28. package/dist/_internal/shared/listing-types/digital-code/config.js +3 -0
  29. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  30. package/dist/_internal/shared/listing-types/live/config.js +3 -0
  31. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  32. package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
  33. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  34. package/dist/_internal/shared/listing-types/prize-draw/config.js +3 -0
  35. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  36. package/dist/_internal/shared/listing-types/standard/config.js +2 -0
  37. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  38. package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
  39. package/dist/client/observability/reportClientError.js +11 -1
  40. package/dist/client.d.ts +4 -0
  41. package/dist/client.js +4 -0
  42. package/dist/core/hooks/useSyncManager.js +11 -1
  43. package/dist/features/account/hooks/useNotifications.js +25 -5
  44. package/dist/features/auctions/components/AuctionBottomActions.js +10 -2
  45. package/dist/features/auctions/components/AuctionDetailPageView.js +12 -3
  46. package/dist/features/auctions/components/LiveBidPrice.js +2 -2
  47. package/dist/features/auctions/components/LiveMinIncrement.d.ts +1 -1
  48. package/dist/features/auctions/components/LiveMinIncrement.js +8 -0
  49. package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
  50. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  51. package/dist/features/auctions/hooks/useAuctions.js +12 -1
  52. package/dist/features/auctions/hooks/useRealtimeBids.d.ts +3 -0
  53. package/dist/features/auctions/hooks/useRealtimeBids.js +138 -36
  54. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +2 -2
  55. package/dist/features/catalogue/components/PublicCatalogueView.js +2 -2
  56. package/dist/features/categories/components/BundleCollage.js +10 -5
  57. package/dist/features/categories/components/BundleDetailView.js +7 -3
  58. package/dist/features/categories/components/CategoryProductsListing.js +4 -1
  59. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -2
  60. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -2
  61. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -4
  62. package/dist/features/live/components/LiveItemDetailPageView.js +2 -2
  63. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  64. package/dist/features/pre-orders/components/PreOrderActionsClient.js +2 -2
  65. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -2
  66. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +4 -1
  67. package/dist/features/products/actions/product-actions.d.ts +9 -0
  68. package/dist/features/products/actions/product-actions.js +11 -0
  69. package/dist/features/products/columns/productTableColumns.js +2 -2
  70. package/dist/features/products/components/AuctionsIndexListing.js +4 -1
  71. package/dist/features/products/components/BidHistory.js +2 -2
  72. package/dist/features/products/components/CompareOverlay.js +2 -2
  73. package/dist/features/products/components/GroupMemberPicker.js +5 -5
  74. package/dist/features/products/components/ListingBottomActions.js +12 -3
  75. package/dist/features/products/components/MakeOfferButton.js +7 -1
  76. package/dist/features/products/components/MarketplaceBundleCard.js +4 -4
  77. package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
  78. package/dist/features/products/components/PrizeDrawCollage.js +16 -5
  79. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  80. package/dist/features/products/components/PrizeDrawsIndexListing.js +4 -1
  81. package/dist/features/products/components/PrizeRevealModal.js +2 -2
  82. package/dist/features/products/components/ProductDetailActions.js +10 -3
  83. package/dist/features/products/components/ProductFilters.js +2 -2
  84. package/dist/features/products/components/ProductGrid.js +5 -2
  85. package/dist/features/products/components/ProductsIndexListing.js +12 -1
  86. package/dist/features/products/components/ProductsIndexPageView.js +26 -0
  87. package/dist/features/products/components/ShowGroupSection.js +2 -2
  88. package/dist/features/products/components/SublistingCarouselSection.js +2 -2
  89. package/dist/features/products/constants/listing-tabs.d.ts +36 -12
  90. package/dist/features/products/constants/listing-tabs.js +38 -14
  91. package/dist/features/products/constants/sieve.d.ts +14 -0
  92. package/dist/features/products/constants/sieve.js +39 -0
  93. package/dist/features/tester/seed-data/authored/buying__bidding.js +5 -4
  94. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +85 -85
  95. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +2 -2
  96. package/dist/features/tester/seed-data/authored/buying__product-detail.js +53 -15
  97. package/dist/features/tester/seed-data/authored/content-discovery__search.js +6 -4
  98. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +4 -4
  99. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +2 -2
  100. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +4 -4
  101. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +8 -8
  102. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +8 -8
  103. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +16 -0
  104. package/dist/features/tester/seed-data/authored/seo__og-images.js +17 -0
  105. package/dist/features/tester/seed-data/stores-tester-seed-data.js +41 -2
  106. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +36 -6
  107. package/dist/features/wishlist/components/WishlistPage.js +2 -2
  108. package/dist/index.d.ts +3 -1
  109. package/dist/index.js +6 -1
  110. package/dist/react/contexts/SessionContext.js +40 -1
  111. package/dist/react/hooks/useCanSeePrices.d.ts +37 -0
  112. package/dist/react/hooks/useCanSeePrices.js +40 -0
  113. package/dist/seed/addresses-seed-data.js +36 -2
  114. package/dist/seed/index.d.ts +1 -1
  115. package/dist/seed/index.js +6 -1
  116. package/dist/seed/users-seed-data.js +10 -2
  117. package/dist/seo/json-ld.d.ts +21 -0
  118. package/dist/seo/json-ld.js +45 -0
  119. package/dist/styles.css +41 -0
  120. package/dist/ui/components/GatedPrice.d.ts +66 -0
  121. package/dist/ui/components/GatedPrice.js +80 -0
  122. package/dist/ui/components/GatedPrice.style.css +40 -0
  123. package/dist/ui/components/PriceDisplay.d.ts +12 -0
  124. package/dist/ui/components/PriceDisplay.js +17 -17
  125. package/dist/ui/components/index.style.css +1 -0
  126. package/dist/ui/index.d.ts +2 -0
  127. package/dist/ui/index.js +1 -0
  128. package/package.json +1 -1
@@ -22,18 +22,35 @@
22
22
  import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags";
23
23
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
24
24
  /**
25
- * The generic `/products` browse page spans EVERY listing type (2026-08-21).
25
+ * The types the generic `/products` browse page spans — the GENERAL CATALOGUE,
26
+ * not an index of everything.
26
27
  *
27
- * It used to span only `standard|classified|digital-code|live` on the theory
28
- * that auctions/pre-orders/prize-draws/art each had a dedicated page — but
29
- * that made five of nine types unreachable from the main catalogue, broke
30
- * seven legacy `/search/.../tab/<type>` permanent redirects that all point at
31
- * `/products`, and made the cross-type Compare + bulk-cart features (which
32
- * only exist on this page) unusable for those types. The dedicated pages
33
- * remain, and `/products` links to them when a single time-boxed type is
34
- * selected.
28
+ * DERIVED from `ListingTypePlugin.inGeneralCatalogue`, never hand-written: a
29
+ * literal array here is how ten enumerations of one union drifted apart in
30
+ * Root Cause #61, and `audit-listing-type-tab-coverage`'s STAYS_DERIVED rule
31
+ * blocks one.
32
+ *
33
+ * ## History — this spanned all nine types from 2026-08-21 until now
34
+ *
35
+ * The widening was a fix for Root Cause #61 and cited three reasons. Two have
36
+ * since been solved elsewhere, and the third is an accepted cost:
37
+ *
38
+ * 1. "Five of nine types unreachable from the main catalogue." Still true for
39
+ * `classified`/`digital-code`/`live`, which have dedicated pages but no
40
+ * `MAIN_NAV_ITEMS` entry — which is exactly why `inGeneralCatalogue` keeps
41
+ * those three here. The other five are all in the main nav.
42
+ * 2. "Seven legacy `/search/.../tab/<type>` redirects all point at /products."
43
+ * No longer true: that route derives `tabSlug -> browseRoute` from this
44
+ * same registry, and `/search` maps every resource type to its own page.
45
+ * Neither falls through to `/products` for a typed request.
46
+ * 3. "Cross-type Compare + bulk-cart only exist on this page." Still true, and
47
+ * accepted: you can no longer compare an auction against a standard product.
48
+ *
49
+ * A type appearing both here and in the main nav is the bug this fixes — the
50
+ * same item reachable from two places with two different chromes (an auction
51
+ * card in the general grid renders Buy-Now chrome, not bid affordances).
35
52
  */
36
- export const GENERIC_PRODUCT_LISTING_TYPES = ALL_LISTING_TYPES;
53
+ export const GENERIC_PRODUCT_LISTING_TYPES = ALL_LISTING_TYPES.filter((t) => pluginFor(t).inGeneralCatalogue);
37
54
  /** The listing types the combined `/art` page spans. */
38
55
  export const ART_STICKERS_LISTING_TYPES = ["art", "stickers"];
39
56
  /**
@@ -87,10 +104,17 @@ export const SELLER_LISTING_TABS = [
87
104
  * In-page type-filter chips on the generic `/products` browse page.
88
105
  *
89
106
  * Multi-select (checkbox semantics) — no "All" sentinel chip, because "none
90
- * checked" already means "every type". A sentinel would be a second way to
91
- * express the same state and would have to be cleared whenever a real chip
92
- * was ticked.
107
+ * checked" already means "every type IN THE GENERAL CATALOGUE". A sentinel
108
+ * would be a second way to express the same state and would have to be cleared
109
+ * whenever a real chip was ticked.
110
+ *
111
+ * 🛑 Derived from `GENERIC_PRODUCT_LISTING_TYPES`, NOT `ALL_LISTING_TYPES`. The
112
+ * chips must offer exactly the types the page can actually return: a chip for a
113
+ * type outside the span intersects to empty, and `listPublicProducts` then falls
114
+ * back to the full span — i.e. ticking it would silently widen the results
115
+ * instead of narrowing them. `ADMIN_PRODUCT_LISTING_TYPE_TABS` is a separate
116
+ * constant and correctly stays all-nine.
93
117
  */
94
- export const PRODUCT_TYPE_FILTER_TABS = ALL_LISTING_TYPES.map(chipTab);
118
+ export const PRODUCT_TYPE_FILTER_TABS = GENERIC_PRODUCT_LISTING_TYPES.map(chipTab);
95
119
  /** In-page type-filter chips on the combined `/art` (Art & Stickers) page. */
96
120
  export const ART_STICKERS_TYPE_FILTER_TABS = ART_STICKERS_LISTING_TYPES.map(chipTab);
@@ -16,6 +16,20 @@ export declare const BASE_SORT_OPTIONS: readonly [{
16
16
  readonly value: string;
17
17
  readonly label: "Price: High to Low";
18
18
  }];
19
+ /**
20
+ * Drop the price sorts when the viewer may not see prices.
21
+ *
22
+ * Ordering a list by price discloses the ranking of every hidden amount, and
23
+ * with paging that is most of the way to a price list — so the option is not
24
+ * offered to signed-out visitors, matching the hidden price-range facet.
25
+ *
26
+ * 🛑 This removes the CONTROL, not the capability. A hand-written
27
+ * `?sort=price:asc` still sorts, exactly as `?f=price>N` still filters — both
28
+ * are the documented, accepted residue of gating the render rather than
29
+ * withholding the field (see CLAUDE.md § "Public Data Projections"). What it
30
+ * buys is that no casual visitor is handed the lever.
31
+ */
32
+ export declare function withoutPriceSorts(options: readonly SortOption[], canSeePrices: boolean): readonly SortOption[];
19
33
  export declare const STANDARD_SORT_OPTIONS: readonly [{
20
34
  readonly value: string;
21
35
  readonly label: "Newest First";
@@ -19,6 +19,45 @@ const BASE_PRICE_SORTS = [
19
19
  ];
20
20
  /** Time + price sorts, the common subset across most listing-type pickers. */
21
21
  export const BASE_SORT_OPTIONS = [...BASE_TIME_SORTS, ...BASE_PRICE_SORTS];
22
+ /**
23
+ * Drop the price sorts when the viewer may not see prices.
24
+ *
25
+ * Ordering a list by price discloses the ranking of every hidden amount, and
26
+ * with paging that is most of the way to a price list — so the option is not
27
+ * offered to signed-out visitors, matching the hidden price-range facet.
28
+ *
29
+ * 🛑 This removes the CONTROL, not the capability. A hand-written
30
+ * `?sort=price:asc` still sorts, exactly as `?f=price>N` still filters — both
31
+ * are the documented, accepted residue of gating the render rather than
32
+ * withholding the field (see CLAUDE.md § "Public Data Projections"). What it
33
+ * buys is that no casual visitor is handed the lever.
34
+ */
35
+ export function withoutPriceSorts(options, canSeePrices) {
36
+ if (canSeePrices)
37
+ return options;
38
+ // Exact match on the field, both directions. `sortBy()` encodes direction as
39
+ // a leading "-", so stripping it yields the bare field name — substring
40
+ // matching would catch an unrelated `pricePerEntry` sort by accident.
41
+ return options.filter((o) => !MONEY_SORT_FIELDS.includes(o.value.replace(/^-/, "")));
42
+ }
43
+ /**
44
+ * Every sort field that orders by an AMOUNT, matched as a substring of the
45
+ * sort value (`sortBy()` emits `field` / `-field`, so one entry covers both
46
+ * directions).
47
+ *
48
+ * `currentBid`, `startingBid` and `buyNowPrice` are here for the same reason
49
+ * `price` is: "Highest Current Bid" ranks every hidden bid on the page, which
50
+ * is the disclosure the gate exists to prevent. `bidCount` is deliberately
51
+ * ABSENT — the number of bids is not an amount, it is already displayed
52
+ * ungated beside every auction, and hiding it would remove a real signal for
53
+ * no gain.
54
+ */
55
+ const MONEY_SORT_FIELDS = [
56
+ PRODUCT_FIELDS.PRICE,
57
+ PRODUCT_FIELDS.CURRENT_BID,
58
+ PRODUCT_FIELDS.STARTING_BID,
59
+ PRODUCT_FIELDS.BUY_NOW_PRICE,
60
+ ];
22
61
  // ---------------------------------------------------------------------------
23
62
  // Standard Products
24
63
  // ---------------------------------------------------------------------------
@@ -130,9 +130,10 @@ export const authored = {
130
130
  endResult: "After reload the same rows appear in the same order. An empty list here immediately after placing a bid means the bid was written without a buyer reference.",
131
131
  },
132
132
  "checklist-buying-bidding-bid-history-auction-detail-pagination": {
133
- roles: ["guest"],
133
+ roles: ["buyer"],
134
134
  startPage: "/auctions/auction-beyblade-metal-lightning-l-drago",
135
135
  steps: [
136
+ "Sign in as a buyer — bid amounts are hidden from signed-out visitors, so this case cannot read them as a guest.",
136
137
  "Open /auctions/auction-beyblade-metal-lightning-l-drago.",
137
138
  "Click the 'Bid History' control to expand it.",
138
139
  "Read the amounts on the first page, top to bottom.",
@@ -350,16 +351,16 @@ export const authored = {
350
351
  endResult: "Reloading the auction still shows ₹15,000.00 and 1 bid, and the row is still on /user/bids.",
351
352
  },
352
353
  "checklist-buying-bidding-place-bid-live-other-viewer": {
353
- roles: ["buyer", "guest"],
354
+ roles: ["buyer"],
354
355
  startPage: "/auctions/auction-tester-sandbox-cycle-2",
355
356
  steps: [
356
357
  "In window A, sign in as rehan.sheikh@gmail.com / TempPass123! and open /auctions/auction-tester-sandbox-cycle-2.",
357
- "In window B, open the same auction signed out, and read the current bid and bid count.",
358
+ "In window B, sign in as a DIFFERENT buyer (vivaan.kapoor@gmail.com / TempPass123!) and open the same auction; read the current bid and bid count.",
358
359
  "In window A, click 'Place a bid', select the 'Minimum' preset at ₹15,000.00, and click 'Place Bid'.",
359
360
  "Switch to window B and watch for up to 10 seconds without reloading.",
360
361
  ],
361
362
  inputs: { bidAmount: 15000 },
362
- expectedBehaviour: "Bid updates are relayed over SSE from the server, which is why a signed-out viewer sees them at all — the underlying realtime node is not client-readable, so the browser never reads it directly.",
363
+ expectedBehaviour: "Bid updates are relayed over SSE from the server — the underlying realtime node is not client-readable, so the browser never reads it directly. Window B is a SECOND SIGNED-IN BUYER, not a signed-out visitor: bid figures are now hidden from guests, so a signed-out window B would show 'Sign in to see the current bid' before and after and could never demonstrate the stream at all.",
363
364
  expectedUiState: "Window B moves to 'Current bid ₹15,000.00' and '1 bid' within about 10 seconds, with no reload and no interaction. Figures that only change on refresh mean the stream is not connected.",
364
365
  expectedData: { currentBid: 15000, bidCount: 1 },
365
366
  endResult: "Reloading window B shows the same figures it had already updated to.",
@@ -58,25 +58,26 @@ export const authored = {
58
58
  },
59
59
  "checklist-buying-browsing-search-auctions-show-ended-off-shows-live": {
60
60
  roles: ["guest"],
61
- startPage: "/products",
61
+ startPage: "/auctions",
62
62
  steps: [
63
- "Open /products.",
64
- "Click 'Filters', tick only 'Auctions', click 'Apply'.",
63
+ "Open /auctions.",
64
+ "Read the availability tab bar and the grid.",
65
65
  ],
66
- expectedBehaviour: "Live auctions appear on the Available tab without the 'Ended' toggle being turned on. The page is not empty.",
67
- expectedUiState: "At least one auction card marked 'Live' is visible (e.g. 'Beyblade Burst B-100 Cho-Z Achilles' or 'Metal Fight Beyblade BB-99 Lightning L-Drago'). The grid is not empty and no error toast appears.",
66
+ expectedBehaviour: "Live auctions appear on the Available tab, which is selected on arrival. The page is not empty. This case used to start on /products with the Auctions chip ticked; auctions now live only on their own page.",
67
+ expectedUiState: "At least one live auction card is visible (e.g. 'Beyblade Burst B-100 Cho-Z Achilles' or 'Metal Fight Beyblade BB-99 Lightning L-Drago'). The middle tab reads 'Ended' and is NOT selected. The grid is not empty and no error toast appears.",
68
68
  endResult: "Nothing is persisted.",
69
69
  },
70
70
  "checklist-buying-browsing-search-auctions-show-ended-with-nondefault-sort": {
71
- roles: ["guest"],
72
- startPage: "/products",
71
+ roles: ["buyer"],
72
+ startPage: "/auctions",
73
73
  steps: [
74
- "Open /products.",
75
- "Click 'Filters', tick only 'Auctions', click 'Apply'.",
74
+ "Sign in as a buyer.",
75
+ "Open /auctions.",
76
76
  "Open the 'Sort by' dropdown and select 'Highest Current Bid'.",
77
+ "Read the order of the current-bid figures on the cards.",
77
78
  ],
78
- expectedBehaviour: "Sorting by 'Highest Current Bid' while 'Show ended' is off still returns only live auctions, sorted correctly by current bid descending. The grid is not empty.",
79
- expectedUiState: "The grid shows live auction cards sorted from highest current bid to lowest. No ended auction cards are visible. No 'No products found' message appears.",
79
+ expectedBehaviour: "Sorting by 'Highest Current Bid' on the Available tab still returns only live auctions, ordered by current bid descending, and the grid is not empty. The role is BUYER, not guest: 'Highest Current Bid' is a money sort and is not offered to signed-out visitors, because ordering by bid ranks every hidden amount on the page.",
80
+ expectedUiState: "The grid shows live auction cards whose current-bid figures descend from top-left to bottom-right. No ended auction cards are visible. No 'No products found' message appears.",
80
81
  endResult: "Nothing is persisted.",
81
82
  },
82
83
  "checklist-buying-browsing-search-browse-categories": {
@@ -146,9 +147,10 @@ export const authored = {
146
147
  endResult: "Nothing is persisted.",
147
148
  },
148
149
  "checklist-buying-browsing-search-filter-drawer-combines-correctly": {
149
- roles: ["guest"],
150
+ roles: ["buyer"],
150
151
  startPage: "/products",
151
152
  steps: [
153
+ "Sign in as a buyer — the Price Range facet is hidden from signed-out visitors.",
152
154
  "Open /products.",
153
155
  "Note the initial result count.",
154
156
  "Click 'Filters'.",
@@ -199,9 +201,10 @@ export const authored = {
199
201
  endResult: "The Left-hand mode preference persists in the user's profile across sessions.",
200
202
  },
201
203
  "checklist-buying-browsing-search-filters": {
202
- roles: ["guest"],
204
+ roles: ["buyer"],
203
205
  startPage: "/products",
204
206
  steps: [
207
+ "Sign in as a buyer — the Price Range facet is hidden from signed-out visitors.",
205
208
  "Open /products.",
206
209
  "Note the result count shown in pagination.",
207
210
  "Click 'Filters'.",
@@ -369,19 +372,22 @@ export const authored = {
369
372
  expectedUiState: "After clicking Page 2: the pagination bar highlights Page 2 and the URL contains 'page=2'. After Next: the next page loads with different cards. After Previous: the previous page loads. No page shows 'No products found' unless the total results genuinely fit on one page.",
370
373
  endResult: "Nothing is persisted to the server. The page parameter is URL state.",
371
374
  },
372
- "checklist-buying-browsing-search-pre-orders-reachable-from-products": {
375
+ "checklist-buying-browsing-search-every-listing-type-reachable-from-nav-or-products": {
373
376
  roles: ["guest"],
374
377
  startPage: "/products",
375
378
  steps: [
376
- "Open /products.",
377
- "Click 'Filters'.",
378
- "Tick 'Pre-Orders' under Listing type.",
379
- "Click 'Apply'.",
380
- "Scan the grid cards for type badges.",
379
+ "Open /products and read the main navigation bar.",
380
+ "Click 'Auctions' in the nav and confirm at least one auction card is shown.",
381
+ "Click 'Pre-Orders' in the nav and confirm at least one pre-order card is shown.",
382
+ "Click 'Prize Draws' in the nav and confirm at least one prize-draw card is shown.",
383
+ "Click 'Art & Stickers' in the nav and confirm at least one art or sticker card is shown.",
384
+ "Return to /products, click 'Filters', tick 'Classifieds', click 'Apply', and read the grid.",
385
+ "Click 'Filters', untick 'Classifieds', tick 'Digital Codes', click 'Apply', and read the grid.",
386
+ "Click 'Filters', untick 'Digital Codes', tick 'Live Items', click 'Apply', and read the grid.",
381
387
  ],
382
- expectedBehaviour: "The grid is filtered to show only pre-order items. At least one pre-order card with a 'Pre-Order' badge is visible.",
383
- expectedUiState: "Every visible card shows a 'Pre-Order' badge. At least one card is shown — e.g. 'Beyblade X BX-08 Booster — Next Wave'. No card shows a type badge other than 'Pre-Order'.",
384
- endResult: "Nothing is persisted to the server.",
388
+ expectedBehaviour: "All nine listing types are reachable without typing a URL. Five reach their own page from the main nav; the remaining four — Standard, Classifieds, Digital Codes, Live Items — are reached through the /products Listing type filter, because they have no nav entry of their own.",
389
+ expectedUiState: "Each nav click lands on the matching page with at least one real card: /auctions, /pre-orders, /prize-draws, /art. Each /products filter selection narrows the grid to that type with at least one card and no 'No products found' state — Classifieds shows 'Classified' badges, Digital Codes shows 'Digital Code' badges, Live Items shows 'Live Item' badges.",
390
+ endResult: "Nothing is persisted to the server. A type that can be reached from neither the nav nor the /products filter is the regression this case exists for.",
385
391
  },
386
392
  "checklist-buying-browsing-search-product-filter-status-labels": {
387
393
  roles: ["admin"],
@@ -403,48 +409,45 @@ export const authored = {
403
409
  steps: [
404
410
  "Open /products.",
405
411
  "Click 'Filters'.",
406
- "Tick only 'Auctions' under Listing type.",
412
+ "Tick only 'Classifieds' under Listing type.",
407
413
  "Read the contents of the drawer below the Listing type checkboxes, without pressing Apply.",
408
- "Click the 'Full Auctions filters →' link.",
414
+ "Click the 'Full Classifieds filters →' link.",
409
415
  "Navigate back to /products.",
410
- "Click 'Filters', untick 'Auctions', tick only 'Pre-Orders', and read for a similar link.",
416
+ "Click 'Filters', untick 'Classifieds', tick only 'Digital Codes', and read for a similar link.",
411
417
  "Navigate back to /products.",
412
- "Click 'Filters', tick 'Auctions' and 'Pre-Orders' both, and read for a link.",
418
+ "Click 'Filters', tick 'Classifieds' and 'Digital Codes' both, and read for a link.",
413
419
  "Navigate back to /products.",
414
420
  "Click 'Filters', untick all, and read for a link.",
421
+ "Click 'Filters', tick only 'Standard', and read for a link.",
415
422
  ],
416
- expectedBehaviour: "When exactly one type that has its own browse page is ticked, a 'Full <type> filters →' link appears in the drawer below the Listing type section (before pressing Apply). The link navigates to that type's dedicated page. With two types ticked or none ticked, the link does not appear.",
417
- expectedUiState: "Auctions only: 'Full Auctions filters →' link is visible; clicking it lands on /auctions. Pre-Orders only: 'Full Pre-Orders filters →' link is visible. Both ticked: no link visible. None ticked: no link visible.",
423
+ expectedBehaviour: "When exactly one type that has its own browse page is ticked, a 'Full <type> filters →' link appears in the drawer below the Listing type section (before pressing Apply). The link navigates to that type's dedicated page, which carries facets this generic page cannot. With two types ticked or none ticked, no link appears. Standard is the exception: its 'dedicated page' IS /products, so offering a link would point the page at itself.",
424
+ expectedUiState: "Classifieds only: 'Full Classifieds filters →' link is visible; clicking it lands on /classified. Digital Codes only: 'Full Digital Codes filters →' link is visible. Both ticked: no link visible. None ticked: no link visible. Standard only: NO link is visible — in particular no 'Full Products filters →' link pointing back at /products.",
418
425
  endResult: "Nothing is persisted to the server.",
419
426
  },
420
427
  "checklist-buying-browsing-search-product-type-chip-drives-sort-options": {
421
428
  roles: ["guest"],
422
429
  startPage: "/products",
423
430
  steps: [
424
- "Open /products.",
425
- "Click 'Filters', tick only 'Auctions', click 'Apply'.",
426
- "Open the 'Sort by' dropdown and read the available options.",
427
- "Click 'Filters', untick 'Auctions', tick only 'Pre-Orders', click 'Apply'.",
428
- "Open the 'Sort by' dropdown and read the available options.",
429
- "Click 'Filters', tick 'Auctions' as well (both Auctions and Pre-Orders now ticked), click 'Apply'.",
430
- "Open the 'Sort by' dropdown and read the available options.",
431
- ],
432
- expectedBehaviour: "With only Auctions selected, 'Ending Soon' appears in Sort. With only Pre-Orders, 'Earliest Delivery' appears. With both selected, only shared sorts (Newest, Price, Name) remain — 'Ending Soon' and 'Earliest Delivery' are both absent.",
433
- expectedUiState: "Auctions only: Sort dropdown includes 'Ending Soon'. Pre-Orders only: Sort dropdown includes 'Earliest Delivery'; 'Ending Soon' is absent. Both: Sort dropdown contains only generic options like 'Newest First', 'Price low to high', 'Price high to low'.",
431
+ "Sign in as a buyer (price sorts are hidden from signed-out visitors).",
432
+ "Open /auctions and open the 'Sort by' dropdown; read the available options.",
433
+ "Open /pre-orders and open the 'Sort by' dropdown; read the available options.",
434
+ "Open /products and open the 'Sort by' dropdown; read the available options.",
435
+ ],
436
+ expectedBehaviour: "Each listing type offers the sorts that make sense for it, and only those. An auction sorts by how soon it ends; a pre-order by when it is delivered; the general catalogue by neither, because neither field means anything for a standard product. This used to be exercised by ticking Auctions and Pre-Orders on /products; those types now live only on their own pages, so the contrast is read across pages instead.",
437
+ expectedUiState: "/auctions: the Sort dropdown includes 'Ending Soon'. /pre-orders: it includes 'Earliest Delivery' and does NOT include 'Ending Soon'. /products: it includes neither 'Ending Soon' nor 'Earliest Delivery', offering generic options such as 'Newest First' and the two price sorts.",
434
438
  endResult: "Nothing is persisted to the server.",
435
439
  },
436
440
  "checklist-buying-browsing-search-product-type-chip-sort-resets-no-empty-page": {
437
441
  roles: ["guest"],
438
442
  startPage: "/products",
439
443
  steps: [
440
- "Open /products.",
441
- "Click 'Filters', tick only 'Auctions', click 'Apply'.",
442
- "Open the 'Sort by' dropdown and select 'Ending Soon'.",
443
- "Click 'Filters', untick 'Auctions', tick 'Standard', click 'Apply'.",
444
- "Open the 'Sort by' dropdown and read the selected option.",
444
+ "Open /products?sort=auctionEndDate:asc directly in the address bar — an auction-only sort that the general catalogue cannot satisfy.",
445
+ "Read the 'Sort by' dropdown and the grid.",
446
+ "Open /products?listingType=auction directly in the address bar.",
447
+ "Read the URL the browser settles on and the page heading.",
445
448
  ],
446
- expectedBehaviour: "Switching from the Auctions-only selection to Standard resets the sort to a generic option (Newest First). The grid repopulates with Standard products and is not empty.",
447
- expectedUiState: "After switching to Standard: the Sort dropdown reads 'Newest First' (or another generic sort, not 'Ending Soon'). The grid shows Standard product cards. No 'No products found' state and no error is shown.",
449
+ expectedBehaviour: "A sort or a listing type that /products does not offer must degrade visibly, never silently. An unsupported sort falls back to the default rather than running a query Firestore rejects, which would surface as a bare empty grid with no error. A URL naming only out-of-catalogue types redirects to the page that owns them, rather than quietly showing the whole general catalogue as though the filter had been honoured.",
450
+ expectedUiState: "First URL: the Sort dropdown reads 'Newest First', the grid shows product cards, and there is no 'No products found' state and no error. Second URL: the browser lands on /auctions and the page heading reads Auctions — it does NOT stay on /products showing unfiltered results.",
448
451
  endResult: "Nothing is persisted to the server.",
449
452
  },
450
453
  "checklist-buying-browsing-search-product-type-chips-cover-all-types": {
@@ -456,8 +459,8 @@ export const authored = {
456
459
  "Count every checkbox under the 'Listing type' heading.",
457
460
  "Read each checkbox label.",
458
461
  ],
459
- expectedBehaviour: "Exactly nine listing-type checkboxes are present. All nine are: Standard, Auctions, Pre-Orders, Prize Draws, Classifieds, Digital Codes, Live Items, Art, Stickers. No checkbox labelled 'Bundles' is present.",
460
- expectedUiState: "The Listing type section shows nine checkboxes: Standard, Auctions, Pre-Orders, Prize Draws, Classifieds, Digital Codes, Live Items, Art, Stickers. No 'Bundles' checkbox is present.",
462
+ expectedBehaviour: "Exactly four listing-type checkboxes are present: Standard, Classifieds, Digital Codes, Live Items. Those are the four types with no dedicated entry in the main nav, so /products is their only discovery path. Auctions, Pre-Orders, Prize Draws, Art and Stickers are NOT offered here — each owns a dedicated page reachable from the main nav. No checkbox labelled 'Bundles' is present; a bundle is a category, not a listing type.",
463
+ expectedUiState: "BEFORE: nine checkboxes, including Auctions, Pre-Orders, Prize Draws, Art and Stickers. AFTER: the Listing type section shows exactly four — Standard, Classifieds, Digital Codes, Live Items. No 'Auctions', 'Pre-Orders', 'Prize Draws', 'Art', 'Stickers' or 'Bundles' checkbox is present.",
461
464
  endResult: "Nothing is persisted.",
462
465
  },
463
466
  "checklist-buying-browsing-search-product-type-chips-multi-select": {
@@ -466,17 +469,17 @@ export const authored = {
466
469
  steps: [
467
470
  "Open /products.",
468
471
  "Click 'Filters'.",
469
- "Tick 'Auctions'.",
470
- "Tick 'Pre-Orders' without unticking 'Auctions'.",
472
+ "Tick 'Classifieds'.",
473
+ "Tick 'Digital Codes' without unticking 'Classifieds'.",
471
474
  "Click 'Apply'.",
472
475
  "Click 'Filters' again to reopen the drawer.",
473
476
  "Read which boxes are ticked.",
474
477
  "Click 'Close'.",
475
- "Untick both 'Auctions' and 'Pre-Orders' in the drawer.",
478
+ "Untick both 'Classifieds' and 'Digital Codes' in the drawer.",
476
479
  "Click 'Apply'.",
477
480
  ],
478
- expectedBehaviour: "Both checkboxes remain ticked after Apply. The grid shows a mix of Auction and Pre-Order cards. After unticking both and applying, the grid returns to showing all types.",
479
- expectedUiState: "After first Apply: the Filters badge count is 2; the grid shows cards labelled 'Auction' and 'Pre-Order'. Reopening drawer: both Auctions and Pre-Orders are ticked. After second Apply with none ticked: badge count returns to 0 and cards of multiple types reappear.",
481
+ expectedBehaviour: "Both checkboxes remain ticked after Apply. The grid shows a mix of Classified and Digital Code cards. After unticking both and applying, the grid returns to showing every type the general catalogue spans.",
482
+ expectedUiState: "After first Apply: the Filters badge count is 2; the grid shows cards labelled 'Classified' and 'Digital Code'. Reopening drawer: both Classifieds and Digital Codes are ticked. After second Apply with none ticked: badge count returns to 0 and cards of multiple types reappear.",
480
483
  endResult: "Nothing is persisted to the server. Filter state is URL state.",
481
484
  },
482
485
  "checklist-buying-browsing-search-product-type-chips-none-means-all": {
@@ -488,8 +491,8 @@ export const authored = {
488
491
  "Click 'Close'.",
489
492
  "Scan the badges on the first page of cards.",
490
493
  ],
491
- expectedBehaviour: "With no Listing type filter applied the grid shows items of multiple types — at least two different type badges are visible on the first page.",
492
- expectedUiState: "At least two different type badges are visible on page 1, for example an 'Auction' card alongside a 'Digital Code' card or a 'Pre-Order' alongside a 'Classified'.",
494
+ expectedBehaviour: "With no Listing type filter applied the grid shows every type the GENERAL CATALOGUE spans — not every type in the system. At least two different type badges are visible on the first page, and none of them is a type that owns a dedicated nav page.",
495
+ expectedUiState: "At least two different type badges are visible on page 1, drawn only from Classified / Digital Code / Live Item / no-badge (Standard) — for example a 'Classified' card alongside a 'Digital Code' card. NO 'Auction', 'Pre-Order', 'Prize Draw', 'Art Print' or 'Sticker Sheet' badge appears.",
493
496
  endResult: "Nothing is persisted.",
494
497
  },
495
498
  "checklist-buying-browsing-search-product-type-filter-counts-in-badge": {
@@ -498,7 +501,7 @@ export const authored = {
498
501
  steps: [
499
502
  "Open /products.",
500
503
  "Click 'Filters'.",
501
- "Tick 'Auctions'.",
504
+ "Tick 'Classifieds'.",
502
505
  "Click 'Apply'.",
503
506
  "Note the new number on the Filters badge.",
504
507
  "Click 'Filters' again.",
@@ -506,8 +509,8 @@ export const authored = {
506
509
  "Click 'Apply'.",
507
510
  "Note the Filters badge number and the grid content.",
508
511
  ],
509
- expectedBehaviour: "Ticking one Listing type box and pressing Apply increases the Filters badge by 1. 'Reset all' unticks every type box and restores the badge to 0, and the grid repopulates with all listing types.",
510
- expectedUiState: "After ticking Auctions and applying: badge shows 1 (or higher if defaults add to it). After Reset all and Apply: badge shows 0 and the grid contains mixed listing types.",
512
+ expectedBehaviour: "Ticking one Listing type box and pressing Apply increases the Filters badge by 1. 'Reset all' unticks every type box and restores the badge to 0, and the grid repopulates with every type the general catalogue spans.",
513
+ expectedUiState: "After ticking Classifieds and applying: badge shows 1 (or higher if defaults add to it). After Reset all and Apply: badge shows 0 and the grid contains mixed listing types.",
511
514
  endResult: "Nothing is persisted to the server.",
512
515
  },
513
516
  "checklist-buying-browsing-search-product-type-filter-lives-in-drawer": {
@@ -530,16 +533,16 @@ export const authored = {
530
533
  "Open /products with no filters applied.",
531
534
  "Note the types of cards visible in the grid.",
532
535
  "Click 'Filters'.",
533
- "Tick 'Auctions' and watch the grid behind the open drawer.",
536
+ "Tick 'Classifieds' and watch the grid behind the open drawer.",
534
537
  "Click 'Apply'.",
535
538
  "Note the grid now.",
536
539
  "Click 'Filters' again.",
537
- "Tick 'Art'.",
540
+ "Tick 'Live Items'.",
538
541
  "Click the X (Close) button on the drawer — do not click Apply.",
539
542
  "Click 'Filters' again to reopen and read which boxes are ticked.",
540
543
  ],
541
544
  expectedBehaviour: "Ticking a box does not change the grid until Apply is pressed. Closing with X discards pending changes — the grid and the ticked state both revert to what was applied before.",
542
- expectedUiState: "While drawer is open with Auctions ticked: grid behind the drawer still shows mixed types. After Apply: grid shows only Auction cards. After ticking Art and closing with X: grid still shows only Auction cards (Art was discarded). Reopening drawer: only Auctions is ticked; Art is unticked.",
545
+ expectedUiState: "While drawer is open with Classifieds ticked: grid behind the drawer still shows mixed types. After Apply: grid shows only Classified cards. After ticking Live Items and closing with X: grid still shows only Classified cards (Live Items was discarded). Reopening drawer: only Classifieds is ticked; Live Items is unticked.",
543
546
  endResult: "Nothing is persisted to the server.",
544
547
  },
545
548
  "checklist-buying-browsing-search-product-type-selection-survives-reload": {
@@ -547,7 +550,7 @@ export const authored = {
547
550
  startPage: "/products",
548
551
  steps: [
549
552
  "Open /products.",
550
- "Click 'Filters', tick 'Auctions' and 'Art', click 'Apply'.",
553
+ "Click 'Filters', tick 'Classifieds' and 'Live Items', click 'Apply'.",
551
554
  "Copy the current URL from the address bar.",
552
555
  "Open the copied URL in a new browser tab.",
553
556
  "Click 'Filters' in the new tab and read which boxes are ticked.",
@@ -555,24 +558,22 @@ export const authored = {
555
558
  "Press the browser Back button in the original tab.",
556
559
  "Click 'Filters' in the original tab and read which boxes are ticked.",
557
560
  ],
558
- expectedBehaviour: "The applied listing types are encoded in the URL. Opening the URL in a new tab shows the same filtered grid with Auctions and Art ticked. Pressing Back restores the previous selection.",
559
- expectedUiState: "New tab: grid shows Auction and Art cards; Filters drawer shows Auctions and Art ticked. After Back: the previous filter state (before the Auctions + Art selection) is restored.",
561
+ expectedBehaviour: "The applied listing types are encoded in the URL. Opening the URL in a new tab shows the same filtered grid with Classifieds and Live Items ticked. Pressing Back restores the previous selection.",
562
+ expectedUiState: "New tab: grid shows Classified and Live Item cards; Filters drawer shows Classifieds and Live Items ticked. After Back: the previous filter state (before the Classifieds + Live Items selection) is restored.",
560
563
  endResult: "Nothing is persisted to the server. Filter state is URL state.",
561
564
  },
562
565
  "checklist-buying-browsing-search-product-type-toggles-follow-selection": {
563
566
  roles: ["guest"],
564
567
  startPage: "/products",
565
568
  steps: [
566
- "Open /products.",
567
- "Click 'Filters', tick only 'Auctions', click 'Apply'.",
568
- "Read the label of the middle availability tab above the grid.",
569
- "Click that middle tab.",
570
- "Click 'Filters', untick 'Auctions', tick only 'Standard', click 'Apply'.",
571
- "Read the label of the middle availability tab.",
572
- "Click that middle tab.",
573
- ],
574
- expectedBehaviour: "With only Auctions selected, the middle tab is labelled 'Ended'. With only Standard selected, the middle tab is labelled 'Sold'. Clicking the tab in each case shows the respective unavailable items of that type.",
575
- expectedUiState: "Auctions only: middle tab reads 'Ended'; clicking it shows ended auction cards. Standard only: middle tab reads 'Sold'; clicking it shows sold-out product cards.",
569
+ "Open /products and read the label of the middle availability tab above the grid.",
570
+ "Click that middle tab and read the cards.",
571
+ "Open /auctions and read the label of its middle availability tab.",
572
+ "Click that middle tab and read the cards.",
573
+ "Open /prize-draws and read the label of its middle availability tab.",
574
+ ],
575
+ expectedBehaviour: "The middle availability tab is named for how that listing type stops being available: a product runs out, an auction ends, a prize draw closes. This used to be read by ticking Auctions on /products; auctions now live only on their own page, so the three wordings are read across the three pages.",
576
+ expectedUiState: "/products: the middle tab reads 'Sold'; clicking it shows sold-out product cards. /auctions: it reads 'Ended'; clicking it shows ended auction cards. /prize-draws: it reads 'Closed'. In all three the first tab is 'Available' and is selected on arrival.",
576
577
  endResult: "Nothing is persisted to the server.",
577
578
  },
578
579
  "checklist-buying-browsing-search-search": {
@@ -598,15 +599,15 @@ export const authored = {
598
599
  steps: [
599
600
  "Open /products.",
600
601
  "Type 'Beyblade' in the search field and click 'Search'.",
601
- "Click 'Filters', tick 'Auctions', click 'Apply'.",
602
- "Open the 'Sort by' dropdown and select 'Ending Soon'.",
602
+ "Click 'Filters', tick 'Classifieds', click 'Apply'.",
603
+ "Open the 'Sort by' dropdown and select 'Name: A–Z'.",
603
604
  "Read the URL.",
604
605
  "Click 'Page 2' in the pagination bar if available.",
605
606
  "Read the URL and note the result count.",
606
607
  ],
607
- inputs: { query: "Beyblade", listingType: "Auctions", sort: "Ending Soon" },
608
- expectedBehaviour: "All three — search query, listing type filter, and sort — stay applied together. Pagination reflects the combined narrowed result count, not the unfiltered total.",
609
- expectedUiState: "The URL contains the search term, the listing-type parameter, and the sort parameter together. The grid shows only auction cards whose title includes 'Beyblade', sorted by ending soonest. Page 2 (if navigated) continues this filtered/sorted set, not the unfiltered set.",
608
+ inputs: { query: "Beyblade", listingType: "Classifieds", sort: "Name: A–Z" },
609
+ expectedBehaviour: "All three — search query, listing type filter, and sort — stay applied together. Pagination reflects the combined narrowed result count, not the unfiltered total. The sort used here is deliberately a non-money one: price sorts are not offered to signed-out visitors.",
610
+ expectedUiState: "The URL contains the search term, the listing-type parameter, and the sort parameter together. The grid shows only Classified cards whose title includes 'Beyblade', ordered A–Z by title. Page 2 (if navigated) continues this filtered/sorted set, not the unfiltered set.",
610
611
  endResult: "Nothing is persisted to the server. State is URL-encoded.",
611
612
  },
612
613
  "checklist-buying-browsing-search-show-sold-toggle-reveals-items": {
@@ -631,23 +632,22 @@ export const authored = {
631
632
  endResult: "Nothing is persisted. Tab state is URL state only.",
632
633
  },
633
634
  "checklist-buying-browsing-search-sort-options-per-listing-type": {
634
- roles: ["guest"],
635
+ roles: ["buyer"],
635
636
  startPage: "/products",
636
637
  steps: [
638
+ "Sign in as a buyer — price sorts are not offered to signed-out visitors.",
637
639
  "Open /products with no type filter.",
638
640
  "Open the 'Sort by' dropdown and select 'Price low to high'.",
639
641
  "Note the order of cards (cheapest first).",
640
642
  "Open the 'Sort by' dropdown and select 'Price high to low'.",
641
643
  "Note the order of cards (most expensive first).",
642
- "Click 'Filters', tick only 'Auctions', click 'Apply'.",
643
- "Open Sort and select 'Ending Soon'.",
644
+ "Open /auctions, open Sort and select 'Ending Soon'.",
644
645
  "Note the order of auction cards.",
645
- "Click 'Filters', untick 'Auctions', tick only 'Pre-Orders', click 'Apply'.",
646
- "Open Sort and select 'Earliest Delivery'.",
646
+ "Open /pre-orders, open Sort and select 'Earliest Delivery'.",
647
647
  "Note the order of pre-order cards.",
648
648
  ],
649
- expectedBehaviour: "Each sort option genuinely reorders the visible results. Price sorts change the order by price. Ending Soon reorders auctions by end date. Earliest Delivery reorders pre-orders by delivery date.",
650
- expectedUiState: "Price low to high: cheapest card first (e.g. ₹149 before ₹1,299). Price high to low: most expensive first. Ending Soon: auctions with soonest end date appear first. Earliest Delivery: pre-orders with earliest expected delivery appear first.",
649
+ expectedBehaviour: "Each sort option genuinely reorders the visible results. Price sorts change the order by price. Ending Soon reorders auctions by end date. Earliest Delivery reorders pre-orders by delivery date. The auction and pre-order legs run on their own pages, because those types are no longer selectable on /products.",
650
+ expectedUiState: "Price low to high: cheapest card first (e.g. ₹149 before ₹1,299). Price high to low: most expensive first. Ending Soon on /auctions: auctions with soonest end date appear first. Earliest Delivery on /pre-orders: pre-orders with earliest expected delivery appear first.",
651
651
  endResult: "Nothing is persisted.",
652
652
  },
653
653
  };
@@ -49,14 +49,14 @@ export const authored = {
49
49
  steps: [
50
50
  "Open /bundles in a private window at 1280 pixels wide.",
51
51
  "Look at each bundle card's discount badge and where it sits on the card.",
52
- "Check the badge does not cover the bundle's title or price.",
52
+ "Check the badge does not cover the bundle's title or price row.",
53
53
  "Read the badge's text against its background.",
54
54
  "Resize to 390 pixels and look at the badges again.",
55
55
  "Switch to dark mode and read them once more.",
56
56
  ],
57
57
  inputs: { desktopWidth: 1280, mobileWidth: 390 },
58
58
  expectedBehaviour: "The badge overlays the card image, so it takes the solid pairing — a fill with its own on-solid ink — rather than a pale tint. A tint over an arbitrary product photograph is unreadable regardless of theme, and a literal white ink against a theme-inverting background is invisible in exactly one of the two.",
59
- expectedUiState: "Every badge is readable at both widths and in both themes, and none covers the title or price. A visible badge shape with unreadable text inside it is the failure.",
59
+ expectedUiState: "Every badge is readable at both widths and in both themes, and none covers the title or price row. A visible badge shape with unreadable text inside it is the failure.",
60
60
  endResult: "Read-only; return the site to light mode and restore the width.",
61
61
  },
62
62
  "checklist-buying-image-tile-layout-bundle-tile-opens-lightbox": {