@mohasinac/appkit 4.11.1 → 4.11.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 (212) hide show
  1. package/dist/_internal/server/features/checkout/actions.d.ts +4 -0
  2. package/dist/_internal/server/features/checkout/actions.js +138 -27
  3. package/dist/_internal/server/features/checkout/data.d.ts +10 -0
  4. package/dist/_internal/server/features/checkout/locked-lines.d.ts +30 -0
  5. package/dist/_internal/server/features/checkout/locked-lines.js +116 -0
  6. package/dist/_internal/server/features/orders/adapters.js +7 -0
  7. package/dist/_internal/server/features/products/data.d.ts +12 -3
  8. package/dist/_internal/server/features/products/data.js +55 -4
  9. package/dist/_internal/server/features/products/index.d.ts +2 -1
  10. package/dist/_internal/server/features/products/index.js +2 -1
  11. package/dist/_internal/server/features/products/list-public.d.ts +130 -0
  12. package/dist/_internal/server/features/products/list-public.js +375 -0
  13. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  14. package/dist/_internal/server/features/reviews/data.js +3 -2
  15. package/dist/_internal/server/features/tester/visibility.js +3 -3
  16. package/dist/_internal/server/jobs/core/auctionSettlement.js +89 -16
  17. package/dist/_internal/server/jobs/core/offerExpiry.js +116 -4
  18. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -1
  19. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  20. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +2 -0
  21. package/dist/_internal/server/jobs/handlers/_helpers.js +4 -1
  22. package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -0
  23. package/dist/_internal/server/jobs/handlers/messages.js +5 -0
  24. package/dist/_internal/shared/checkout/lanes.d.ts +41 -0
  25. package/dist/_internal/shared/checkout/lanes.js +106 -0
  26. package/dist/_internal/shared/checkout/order-math.d.ts +19 -0
  27. package/dist/_internal/shared/checkout/order-math.js +30 -6
  28. package/dist/_internal/shared/features/promotions/schema.d.ts +0 -16
  29. package/dist/_internal/shared/features/promotions/schema.js +0 -2
  30. package/dist/_internal/shared/features/reviews/config.d.ts +8 -1
  31. package/dist/_internal/shared/features/reviews/config.js +8 -1
  32. package/dist/_internal/shared/listing-types/_registry.d.ts +88 -1
  33. package/dist/_internal/shared/listing-types/_registry.js +63 -1
  34. package/dist/_internal/shared/listing-types/art/config.d.ts +59 -0
  35. package/dist/_internal/shared/listing-types/art/config.js +12 -0
  36. package/dist/_internal/shared/listing-types/auction/config.d.ts +53 -0
  37. package/dist/_internal/shared/listing-types/auction/config.js +17 -0
  38. package/dist/_internal/shared/listing-types/classified/config.d.ts +59 -0
  39. package/dist/_internal/shared/listing-types/classified/config.js +14 -0
  40. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +59 -0
  41. package/dist/_internal/shared/listing-types/digital-code/config.js +12 -0
  42. package/dist/_internal/shared/listing-types/feature-flags.d.ts +1 -0
  43. package/dist/_internal/shared/listing-types/feature-flags.js +24 -10
  44. package/dist/_internal/shared/listing-types/live/config.d.ts +59 -0
  45. package/dist/_internal/shared/listing-types/live/config.js +14 -0
  46. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +110 -0
  47. package/dist/_internal/shared/listing-types/pre-order/config.js +14 -0
  48. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +44 -0
  49. package/dist/_internal/shared/listing-types/prize-draw/config.js +10 -0
  50. package/dist/_internal/shared/listing-types/standard/config.d.ts +59 -0
  51. package/dist/_internal/shared/listing-types/standard/config.js +9 -0
  52. package/dist/_internal/shared/listing-types/stickers/config.d.ts +59 -0
  53. package/dist/_internal/shared/listing-types/stickers/config.js +12 -0
  54. package/dist/client.d.ts +7 -2
  55. package/dist/client.js +8 -2
  56. package/dist/constants/field-names.d.ts +12 -0
  57. package/dist/constants/field-names.js +12 -0
  58. package/dist/constants/table-keys.d.ts +2 -0
  59. package/dist/constants/table-keys.js +2 -0
  60. package/dist/features/account/components/NotificationBell.js +1 -0
  61. package/dist/features/account/components/UserOffersPanel.js +8 -1
  62. package/dist/features/account/components/UserOrdersView.js +27 -5
  63. package/dist/features/admin/actions/notification-actions.js +1 -1
  64. package/dist/features/admin/components/AdminAddressesView.js +3 -1
  65. package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
  66. package/dist/features/admin/components/AdminPaymentMethodsView.js +3 -1
  67. package/dist/features/admin/components/AdminProductsView.js +34 -18
  68. package/dist/features/admin/constants/filter-tabs.d.ts +26 -9
  69. package/dist/features/admin/constants/filter-tabs.js +22 -9
  70. package/dist/features/admin/schemas/firestore.d.ts +2 -1
  71. package/dist/features/admin/schemas/firestore.js +1 -0
  72. package/dist/features/auctions/actions/bid-actions.d.ts +11 -1
  73. package/dist/features/auctions/actions/bid-actions.js +29 -15
  74. package/dist/features/auctions/components/AuctionBidsTable.js +6 -2
  75. package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
  76. package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
  77. package/dist/features/auctions/components/AuctionsListView.js +12 -53
  78. package/dist/features/auctions/components/PlaceBidFormClient.js +10 -1
  79. package/dist/features/auctions/repository/bid.repository.d.ts +15 -0
  80. package/dist/features/auctions/repository/bid.repository.js +19 -0
  81. package/dist/features/auctions/schemas/firestore.d.ts +11 -1
  82. package/dist/features/auctions/schemas/firestore.js +1 -0
  83. package/dist/features/cart/actions/cart-actions.d.ts +11 -0
  84. package/dist/features/cart/actions/cart-actions.js +24 -0
  85. package/dist/features/cart/repository/cart.repository.d.ts +24 -1
  86. package/dist/features/cart/repository/cart.repository.js +71 -6
  87. package/dist/features/cart/schemas/firestore.d.ts +23 -4
  88. package/dist/features/categories/components/BrandDetailTabs.js +31 -11
  89. package/dist/features/categories/components/CategoryDetailTabs.js +27 -7
  90. package/dist/features/categories/components/CategoryProductsListing.d.ts +9 -1
  91. package/dist/features/categories/components/CategoryProductsListing.js +5 -2
  92. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
  93. package/dist/features/contact/email.js +56 -1
  94. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +17 -16
  95. package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
  96. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  97. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  98. package/dist/features/layout/AppLayoutShell.js +9 -2
  99. package/dist/features/layout/BottomActions.js +26 -3
  100. package/dist/features/layout/BottomActionsContext.d.ts +14 -0
  101. package/dist/features/layout/BottomActionsContext.js +13 -1
  102. package/dist/features/layout/hooks/useBottomActions.d.ts +6 -1
  103. package/dist/features/layout/hooks/useBottomActions.js +4 -1
  104. package/dist/features/layout/index.d.ts +1 -1
  105. package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
  106. package/dist/features/orders/repository/orders.repository.d.ts +4 -15
  107. package/dist/features/orders/repository/orders.repository.js +4 -27
  108. package/dist/features/orders/types/index.d.ts +16 -0
  109. package/dist/features/orders/utils/order-splitter.js +14 -2
  110. package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
  111. package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
  112. package/dist/features/pre-orders/components/PreOrdersListView.js +11 -39
  113. package/dist/features/products/components/ArtStickersListView.js +10 -49
  114. package/dist/features/products/components/AuctionsIndexListing.js +1 -2
  115. package/dist/features/products/components/ListingBottomActions.d.ts +28 -0
  116. package/dist/features/products/components/ListingBottomActions.js +25 -0
  117. package/dist/features/products/components/PrizeDrawBottomActions.d.ts +1 -1
  118. package/dist/features/products/components/PrizeDrawBottomActions.js +3 -21
  119. package/dist/features/products/components/ProductDetailActions.js +4 -0
  120. package/dist/features/products/components/ProductDetailPageView.js +7 -7
  121. package/dist/features/products/components/ProductsIndexPageView.js +9 -62
  122. package/dist/features/products/constants/listing-tabs.d.ts +43 -198
  123. package/dist/features/products/constants/listing-tabs.js +71 -58
  124. package/dist/features/products/constants/sieve.d.ts +104 -1
  125. package/dist/features/products/constants/sieve.js +27 -7
  126. package/dist/features/products/repository/products.repository.d.ts +45 -1
  127. package/dist/features/products/repository/products.repository.js +67 -8
  128. package/dist/features/products/utils/listing-badge-variant.d.ts +20 -1
  129. package/dist/features/products/utils/listing-badge-variant.js +26 -1
  130. package/dist/features/products/utils/listing-type.d.ts +24 -0
  131. package/dist/features/products/utils/listing-type.js +48 -0
  132. package/dist/features/promotions/actions/coupon-actions.d.ts +7 -0
  133. package/dist/features/promotions/actions/coupon-actions.js +12 -1
  134. package/dist/features/promotions/actions/coupon-stacking.d.ts +24 -0
  135. package/dist/features/promotions/actions/coupon-stacking.js +21 -0
  136. package/dist/features/promotions/actions/index.d.ts +1 -0
  137. package/dist/features/promotions/actions/index.js +1 -0
  138. package/dist/features/promotions/actions/seller-coupon-actions.d.ts +0 -1
  139. package/dist/features/promotions/actions/seller-coupon-actions.js +0 -1
  140. package/dist/features/promotions/components/CouponHelpDetails.d.ts +20 -0
  141. package/dist/features/promotions/components/CouponHelpDetails.js +17 -0
  142. package/dist/features/promotions/components/CouponsIndexListing.js +2 -1
  143. package/dist/features/promotions/components/index.d.ts +2 -0
  144. package/dist/features/promotions/components/index.js +1 -0
  145. package/dist/features/promotions/constants/coupon-help.d.ts +27 -0
  146. package/dist/features/promotions/constants/coupon-help.js +42 -0
  147. package/dist/features/promotions/repository/coupons.repository.d.ts +10 -1
  148. package/dist/features/promotions/repository/coupons.repository.js +22 -8
  149. package/dist/features/promotions/schemas/firestore.d.ts +0 -1
  150. package/dist/features/promotions/schemas/firestore.js +1 -1
  151. package/dist/features/promotions/schemas/index.d.ts +0 -20
  152. package/dist/features/promotions/schemas/index.js +0 -1
  153. package/dist/features/reviews/actions/review-actions.js +5 -1
  154. package/dist/features/reviews/components/ReviewDetailPageView.js +2 -1
  155. package/dist/features/reviews/components/ReviewFilters.d.ts +7 -1
  156. package/dist/features/reviews/components/ReviewFilters.js +4 -3
  157. package/dist/features/reviews/components/ReviewsIndexListing.d.ts +6 -0
  158. package/dist/features/reviews/components/ReviewsIndexListing.js +9 -109
  159. package/dist/features/reviews/components/ReviewsListingPanel.d.ts +41 -0
  160. package/dist/features/reviews/components/ReviewsListingPanel.js +226 -0
  161. package/dist/features/reviews/components/index.d.ts +2 -0
  162. package/dist/features/reviews/components/index.js +1 -0
  163. package/dist/features/reviews/hooks/useReviews.d.ts +8 -0
  164. package/dist/features/reviews/hooks/useReviews.js +14 -6
  165. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  166. package/dist/features/reviews/repository/reviews.repository.js +77 -41
  167. package/dist/features/reviews/types/index.d.ts +8 -1
  168. package/dist/features/seller/actions/offer-actions.d.ts +6 -0
  169. package/dist/features/seller/actions/offer-actions.js +8 -9
  170. package/dist/features/seller/components/SellerOffersView.d.ts +6 -1
  171. package/dist/features/seller/components/SellerOffersView.js +59 -8
  172. package/dist/features/seller/components/SellerProductsCards.js +2 -2
  173. package/dist/features/seller/components/SellerProductsView.js +21 -18
  174. package/dist/features/seller/repository/offer.repository.d.ts +18 -2
  175. package/dist/features/seller/repository/offer.repository.js +38 -2
  176. package/dist/features/seller/schemas/firestore.d.ts +7 -2
  177. package/dist/features/seller/schemas/firestore.js +3 -0
  178. package/dist/features/seller/schemas/offer-forms.d.ts +12 -0
  179. package/dist/features/seller/schemas/offer-forms.js +28 -0
  180. package/dist/features/stores/actions/store-query-actions.js +1 -1
  181. package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
  182. package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
  183. package/dist/features/stores/components/StoreReviewsListing.d.ts +4 -0
  184. package/dist/features/stores/components/StoreReviewsListing.js +7 -108
  185. package/dist/features/stores/schemas/index.d.ts +2 -2
  186. package/dist/features/tester/seed-data/coupons-tester-seed-data.js +3 -3
  187. package/dist/features/tester/seed-data/index.d.ts +1 -0
  188. package/dist/features/tester/seed-data/index.js +1 -0
  189. package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +2 -0
  190. package/dist/features/tester/seed-data/offers-tester-seed-data.js +61 -0
  191. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +337 -1
  192. package/dist/index.d.ts +11 -4
  193. package/dist/index.js +15 -3
  194. package/dist/react/hooks/useListingTypeFlags.d.ts +12 -19
  195. package/dist/react/hooks/useListingTypeFlags.js +4 -9
  196. package/dist/schemas/registry.d.ts +0 -12
  197. package/dist/seed/claimed-coupons-seed-data.js +3 -3
  198. package/dist/seed/coupons-seed-data.js +29 -10
  199. package/dist/seed/faq-seed-data.js +2 -2
  200. package/dist/seed/manifest.js +2 -2
  201. package/dist/server-entry.d.ts +1 -0
  202. package/dist/server-entry.js +1 -0
  203. package/dist/server.d.ts +1 -0
  204. package/dist/server.js +3 -0
  205. package/dist/styles.css +44 -39
  206. package/dist/tailwind-utilities.css +1 -1
  207. package/dist/ui/components/FilterChipGroup.d.ts +21 -3
  208. package/dist/ui/components/FilterChipGroup.js +33 -4
  209. package/dist/ui/components/HorizontalScroller.d.ts +1 -2
  210. package/dist/ui/components/HorizontalScroller.js +9 -5
  211. package/dist/ui/components/HorizontalScroller.style.css +43 -38
  212. package/package.json +1 -1
@@ -185,6 +185,22 @@ const rawTesterChecklistItems = [
185
185
  description: "Verify against the seeded fixtures: \"Test Collectible — Sold Out\" (standard, hidden until \"Show sold\" is on), \"Test Auction — Already Won\" (hidden until \"Show ended\" is on), \"Test Prize Draw — Already Closed\" (hidden until \"Show closed\" is on). All three should be genuinely absent by default, not just from an unrelated broken query.",
186
186
  href: "/products",
187
187
  },
188
+ {
189
+ key: "art-stickers-default-not-empty",
190
+ label: "The Art & Stickers listing (/art) shows real items by default — not an empty grid that only populates after clicking \"Show sold\"",
191
+ description: "Fixed 2026-08-21, two independent bugs: (1) \"art\"/\"stickers\" were missing from the repository's listing-type alias map, so the type filter was silently dropped and the query lost its listingType clause entirely on every path (SSR, /api/products, and the listingProcessor Function); (2) the SSR page pushed a stockQuantity>0 inequality into Firestore against a createdAt sort, which Firestore rejects, and the failure was swallowed into a bare empty page. Load /art fresh, with no filters touched, and confirm both Art and Sticker items are visible.",
192
+ href: "/art",
193
+ },
194
+ {
195
+ key: "art-stickers-type-chips-narrow",
196
+ label: "On /art, the \"Art\" and \"Stickers\" type chips each narrow the grid to only that type — neither chip nor \"All\" ever returns zero rows",
197
+ href: "/art",
198
+ },
199
+ {
200
+ key: "art-stickers-show-sold-adds-not-reveals",
201
+ label: "On /art, turning \"Show sold\" ON adds sold-out items to the grid (it should not be required to see anything at all)",
202
+ href: "/art",
203
+ },
188
204
  {
189
205
  key: "auctions-show-ended-off-shows-live",
190
206
  label: "With \"Show ended\" off (the default), the Auctions listing shows LIVE auctions — not empty, and not requiring the toggle to see anything",
@@ -441,7 +457,22 @@ const rawTesterChecklistItems = [
441
457
  { key: "place-bid-live-self", label: "After placing a bid, the current bid amount and bid count update immediately on the auction page for the bidder — no manual page refresh needed" },
442
458
  { key: "place-bid-live-other-viewer", label: "Opening the same auction in two browser tabs (or two accounts) and placing a bid in one updates the current bid and bid count in the other within a few seconds, without a manual refresh (realtime SSE)" },
443
459
  { key: "outbid-notification", label: "Getting outbid triggers a notification" },
444
- { key: "win-auction", label: "Winning an auction creates a payable order correctly", href: "/auctions/auction-tester-sandbox-won" },
460
+ {
461
+ key: "win-auction",
462
+ label: "Winning an auction creates a payable locked cart line, not a stuck order",
463
+ description: "Fixed 2026-08-21 — settlement used to write a document that was not a real order (no items[], no buyerId, no payment method), so a winner had no way to pay anywhere in the product. Wait for `auction-tester-sandbox-won` to settle (or trigger the sweep manually), then confirm: a \"Won auction\" badge appears on the /user/bids row with a working \"Pay now\" link; the item shows up in the Cart's \"Won Auctions\" tab as a non-removable, non-editable line; and completing checkout produces a real order visible under the \"Auction wins\" tab on /user/orders.",
464
+ href: "/auctions/auction-tester-sandbox-won",
465
+ },
466
+ {
467
+ key: "auction-below-reserve-no-winner",
468
+ label: "An auction that ends below its reserve price declares no winner and archives the listing, instead of awarding it to the highest bidder anyway",
469
+ description: "Fixed 2026-08-21 — the reserve price was displayed and editable everywhere but never actually enforced at settlement.",
470
+ },
471
+ {
472
+ key: "auction-win-unpaid-forfeit",
473
+ label: "A won auction left unpaid past its 48-hour checkout window is removed from the cart and the bid is marked forfeited, with a notification to the buyer",
474
+ description: "Requires waiting out the window (or adjusting the fixture's checkoutDeadline) — a background/low-priority case.",
475
+ },
445
476
  { key: "bid-history", label: "My Bids page shows accurate bid history, paginated with the most recent bid first", href: "/user/bids" },
446
477
  { key: "bid-history-auction-detail-pagination", label: "An auction detail page's Bid History section shows the most recent bid first and paginates once there are more bids than fit on one page (try the L-Drago auction — 13 seeded bids)" },
447
478
  {
@@ -527,6 +558,191 @@ const rawTesterChecklistItems = [
527
558
  label: "On mobile, cart item cards stay fully within the screen width per seller group — no horizontal overflow/clipping — and a floating checkout bar (styled like the bulk-action bar, sitting above the bottom nav) shows the total and a Checkout button, with enough bottom margin that the last cart item is never hidden behind it",
528
559
  href: "/cart",
529
560
  },
561
+ {
562
+ key: "checkout-lanes-auction-blocks-others",
563
+ label: "With a won auction pending, the cart's Offers and Cart tabs render their checkout button DISABLED with a stated reason, and adding any new item to the cart is refused",
564
+ description: "Added 2026-08-21 (Checkout Lanes). Get a won auction into your cart (see win-auction), then try to: (a) check out from the Cart tab — button disabled, reason names the auction; (b) add a new standard product to cart — refused with the same reason, item does not appear.",
565
+ href: "/cart",
566
+ },
567
+ {
568
+ key: "checkout-lanes-offer-blocks-standard-only",
569
+ label: "With an accepted offer pending (and no won auction), only the Cart tab's checkout is disabled — the Offers tab itself is checkoutable",
570
+ href: "/cart",
571
+ },
572
+ {
573
+ key: "checkout-lanes-totals-scoped",
574
+ label: "Each cart tab's summary total reflects ONLY that tab's items — the Cart tab total never includes a pending auction or offer amount, and vice versa",
575
+ href: "/cart",
576
+ },
577
+ {
578
+ key: "checkout-lanes-checkout-page-matches-tab",
579
+ label: "Opening checkout from a specific cart tab lands on the matching lane, shows only that lane's items and total, and (for Auction/Offer lanes) shows an info banner and hides the coupon field",
580
+ href: "/checkout",
581
+ },
582
+ ],
583
+ },
584
+ {
585
+ pageKey: "buying-coupons",
586
+ pageLabel: "Coupons — Scoping & Stacking",
587
+ href: "/checkout",
588
+ cases: [
589
+ {
590
+ key: "coupon-help-visible-cart",
591
+ label: "The cart's order summary has an expandable \"How coupons work\" panel, collapsed by default",
592
+ href: "/cart",
593
+ description: "Open /cart with at least one item. Below the \"Apply coupons at checkout\" note there should be a collapsed \"How coupons work\" panel. Expand it and confirm it explains: one coupon per store, plus one platform-wide coupon, and why a coupon might not apply.",
594
+ },
595
+ {
596
+ key: "coupon-help-visible-checkout",
597
+ label: "The checkout coupon box has the same \"How coupons work\" panel, and it mentions re-checking at payment",
598
+ description: "At checkout, below the coupon input and any error text. The checkout version has one extra line the cart version does not: that coupons are re-checked when you place the order. Confirm the wording matches the cart panel otherwise.",
599
+ },
600
+ {
601
+ key: "coupon-help-visible-listing",
602
+ label: "The public coupons listing shows the same \"How coupons work\" panel above the coupon grid",
603
+ href: "/promotions",
604
+ description: "Also check a store-scoped listing at /stores/store-beyblade-arena/coupons. Wording must be identical on all three surfaces (cart, checkout, listing) — any difference is a bug.",
605
+ },
606
+ {
607
+ key: "coupon-stack-two-stores-plus-global",
608
+ label: "Two store coupons AND one platform-wide coupon all apply to the same cart at once",
609
+ description: "Build a cart with ≥₹1,000 from Beyblade Arena and ≥₹500 from LetItRip Official. At checkout apply ARENA25, then OFFICIAL10, then FREESHIP499. All three should stay applied and each should be listed in the applied-coupons list with its own discount amount. This is the headline behaviour — if any of the three is rejected, stop and report it.",
610
+ },
611
+ {
612
+ key: "coupon-stack-second-store-coupon-rejected",
613
+ label: "A second coupon for a store that already has one is declined with a clear message",
614
+ description: "With ARENA25 applied, try SEALED20 (also Beyblade Arena). Expect a message naming the already-applied code, along the lines of \"A coupon for this store is already applied (ARENA25). Remove it first.\" The first coupon must stay applied — it must not be silently swapped.",
615
+ },
616
+ {
617
+ key: "coupon-stack-second-global-rejected",
618
+ label: "A second platform-wide coupon is declined with a clear message",
619
+ description: "With FREESHIP499 applied, try BLADER50 (also platform-wide, needs ≥₹2,000). Expect \"Only one platform-wide coupon can be applied at a time (FREESHIP499). Remove it first.\" Then remove FREESHIP499 and confirm BLADER50 now applies — i.e. the slot frees up.",
620
+ },
621
+ {
622
+ key: "coupon-stack-duplicate-code-rejected",
623
+ label: "Applying the exact same code twice is declined",
624
+ description: "Apply ARENA25, then type ARENA25 again. Expect \"Coupon ARENA25 is already applied.\" and no duplicate row in the applied list.",
625
+ },
626
+ {
627
+ key: "coupon-store-scope-limited-to-its-own-items",
628
+ label: "A store coupon only discounts that store's items, not the whole cart",
629
+ description: "With items from both stores in the cart, apply only ARENA25 (25%, max ₹500). The discount must be computed from the Beyblade Arena items alone — it should NOT change if you add or remove LetItRip Official items (unless the Arena subtotal itself crosses the ₹1,000 minimum).",
630
+ },
631
+ {
632
+ key: "coupon-min-purchase-uses-eligible-subtotal",
633
+ label: "Minimum spend is measured against the coupon's eligible items, not the whole cart total",
634
+ description: "ARENA25 needs ₹1,000. Put ₹600 of Beyblade Arena items plus ₹900 of LetItRip Official items in the cart — cart total is ₹1,500 but the Arena-eligible subtotal is only ₹600, so ARENA25 must be REJECTED for not meeting the minimum. A cart-total-based check would wrongly accept it.",
635
+ },
636
+ {
637
+ key: "coupon-category-restriction-rejects-non-matching-cart",
638
+ label: "A category-restricted coupon is rejected when the cart has none of those categories",
639
+ description: "SEALED20 is restricted to the Burst and X categories. With a Beyblade Arena cart of ONLY Original/Metal-generation items, applying SEALED20 must be declined with a category-related message. Previously category restrictions were ignored entirely and it would have been accepted and discounted the whole store — that is the regression this case guards.",
640
+ },
641
+ {
642
+ key: "coupon-category-restriction-accepts-matching-cart",
643
+ label: "The same category-restricted coupon IS accepted once a matching item is in the cart",
644
+ description: "Continuing from the case above: add a Burst or X generation Beyblade to the cart, then apply SEALED20 again. It should now be accepted, and the discount should be based only on the Burst/X items — not on the Original/Metal items sharing the cart.",
645
+ },
646
+ {
647
+ key: "coupon-remove-one-of-many",
648
+ label: "Removing one coupon from a stack leaves the others applied and recalculates the total",
649
+ description: "With three coupons applied, remove the middle one. The other two must remain, the total must go up by exactly the removed coupon's amount, and the Order Summary discount line(s) must update to match.",
650
+ },
651
+ {
652
+ key: "coupon-summary-total-matches-sum",
653
+ label: "The Order Summary's discount equals the sum of the applied coupons",
654
+ description: "With a stack applied, add up the individual coupon amounts in the applied-coupons list and compare against the \"Coupon discount\" line in the Order Summary. They must agree. Also confirm the Total equals Subtotal − discount + shipping + fees + GST.",
655
+ },
656
+ {
657
+ key: "coupon-persists-across-reload",
658
+ label: "Applied coupons survive a page reload and are still there when you return to checkout",
659
+ description: "Apply a stack, reload the checkout page, and confirm all coupons are still listed with the same amounts. Then navigate to the cart and back to checkout — still there.",
660
+ },
661
+ {
662
+ key: "coupon-split-across-per-store-orders",
663
+ label: "After placing a multi-store order, each store's order shows its own share of the discount",
664
+ href: "/user/orders",
665
+ description: "Check out a multi-store cart carrying a platform-wide coupon plus at least one store coupon. A multi-store cart creates one order PER STORE. Open each resulting order: the store coupon must appear only on its own store's order, the platform-wide coupon must appear on every order as a proportional share, and the shares across all orders must add up to the discount shown at checkout.",
666
+ },
667
+ {
668
+ key: "coupon-all-codes-listed-on-order",
669
+ label: "An order placed with several coupons lists every one of them, not just the first",
670
+ href: "/user/orders",
671
+ description: "Open the order detail page and the invoice page for an order placed with 2+ coupons. Both must show a separate discount line per coupon, each naming its code. Previously only the first coupon's code was shown — a single \"Discount (CODE)\" line for a multi-coupon order is the bug this case guards.",
672
+ },
673
+ {
674
+ key: "coupon-expired-in-cart-dropped-at-placement",
675
+ label: "A coupon that expired while sitting in the cart is dropped at placement instead of being honoured",
676
+ description: "Needs an admin: apply a coupon at checkout, then have an admin deactivate or expire it in /admin/coupons, then place the order without touching the cart. The order must be priced WITHOUT that coupon, and the buyer must be told it was removed — the order must not silently charge the pre-discount amount without explanation, and must not honour a dead coupon.",
677
+ },
678
+ {
679
+ key: "coupon-usage-limit-increments-after-order",
680
+ label: "Placing an order increments the coupon's usage count and counts against your per-user limit",
681
+ description: "Note a coupon's current usage in /admin/coupons, place an order using it, then re-check. Usage must have gone up by one. Then, for a coupon with perUserLimit 1, try to apply it again on a new cart — it must be refused as limit-reached.",
682
+ },
683
+ {
684
+ key: "coupon-wallet-apply-lands-on-checkout",
685
+ label: "\"Use\" on a claimed coupon in My Coupons carries the code through to checkout",
686
+ href: "/user/coupons",
687
+ description: "From /user/coupons press Use on an active coupon. You should end up on checkout with that coupon already applied (it may route via /cart first). If the coupon can't apply to your current cart, the reason must be shown rather than failing silently.",
688
+ },
689
+ {
690
+ key: "coupon-auction-offer-lane-no-coupon-field",
691
+ label: "Auction and Offer checkout lanes hide the coupon field entirely",
692
+ description: "Open checkout from the Auction or Offer cart tab. The coupon input and the \"How coupons work\" panel should not be shown at all for those lanes, and an info banner should explain why.",
693
+ },
694
+ ],
695
+ },
696
+ {
697
+ pageKey: "offers",
698
+ pageLabel: "Offers",
699
+ href: "/products",
700
+ cases: [
701
+ {
702
+ key: "make-offer",
703
+ label: "Making an offer on a product's detail page works and the seller receives a notification",
704
+ href: "/products",
705
+ },
706
+ {
707
+ key: "seller-sees-and-can-act-on-offers",
708
+ label: "A seller's Offers page (/store/offers) actually lists incoming offers, and Accept / Decline / Counter all work from the row menu",
709
+ description: "Fixed 2026-08-21 — the page rendered the view with no handlers at all (so the row menu had nothing but a status badge), and separately the view read an API response key (`offers`) the route never sent (it sends `items`), so the list rendered empty even before the handler gap. Make an offer as a buyer, then confirm the seller can see it, open its details, and Accept/Decline/Counter each work.",
710
+ href: "/store/offers",
711
+ },
712
+ {
713
+ key: "buyer-sees-offer-status-changes",
714
+ label: "\"My Offers\" (/user/offers) actually lists the buyer's offers and reflects accept/decline/counter as the seller responds",
715
+ description: "Fixed 2026-08-21 — the panel read `json.items` off a response the API always wraps as `{success, data:{items}}`, so it showed \"No offers yet\" for every buyer regardless of how many offers existed.",
716
+ href: "/user/offers",
717
+ },
718
+ {
719
+ key: "offer-seller-can-read-before-acting",
720
+ label: "Before accepting/declining/countering, the seller can open a \"View details\" panel showing the buyer's note, listed vs. offered price, and expiry — not just a bare row with action buttons",
721
+ href: "/store/offers",
722
+ },
723
+ {
724
+ key: "counter-offer-has-a-form",
725
+ label: "Countering an offer opens a form asking for the counter amount (and an optional note) — it is not a bare confirm with no input",
726
+ description: "Fixed 2026-08-21 — the counter action previously took no input at all, so it had no real caller anywhere in the codebase.",
727
+ href: "/store/offers",
728
+ },
729
+ {
730
+ key: "offer-accept-checkout-charges-agreed-price",
731
+ label: "Accepting an offer, then completing checkout from the Offers cart tab, charges the NEGOTIATED price — not the listing's current price",
732
+ description: "Fixed 2026-08-21 — every copy of the checkout price calculation ignored the offer's locked price and billed the listing price instead, while the cart UI displayed the correct negotiated figure — so the buyer saw one number and was charged another. Accept an offer below the listing price, complete checkout, and confirm the order total matches the AGREED amount.",
733
+ href: "/user/offers",
734
+ },
735
+ {
736
+ key: "offer-flips-to-paid-after-order",
737
+ label: "After completing checkout on an accepted offer, the offer's status becomes \"paid\" and it can't be added to the cart or checked out again",
738
+ description: "Fixed 2026-08-21 — the \"paid\" status existed in the schema but had no server-side writer anywhere, so an accepted offer stayed re-orderable indefinitely.",
739
+ href: "/user/offers",
740
+ },
741
+ {
742
+ key: "offer-expired-accepted-cannot-checkout",
743
+ label: "An accepted offer past its 48-hour checkout window can no longer be checked out, and is cleared from the cart with a notification to the buyer",
744
+ description: "Background/low-priority — requires waiting out the window or adjusting the fixture's checkoutDeadline.",
745
+ },
530
746
  ],
531
747
  },
532
748
  {
@@ -713,6 +929,24 @@ const rawTesterChecklistItems = [
713
929
  href: "/store/products",
714
930
  cases: [
715
931
  { key: "seller-coupons-crud", label: "Seller can create, edit, and list their own coupons", href: "/store/coupons" },
932
+ {
933
+ key: "seller-coupon-auto-scoped-to-own-store",
934
+ label: "A seller-created coupon is automatically scoped to that seller's own store, with no store picker",
935
+ href: "/store/coupons/new",
936
+ description: "The coupon form must NOT offer a store selector — the store is taken from the logged-in seller. After creating one, confirm it only discounts that store's items when a buyer applies it, and that it can be stacked alongside a different store's coupon.",
937
+ },
938
+ {
939
+ key: "seller-coupon-no-stacking-toggle",
940
+ label: "The seller coupon form has no \"allow stacking\" option — stacking is always permitted",
941
+ href: "/store/coupons/new",
942
+ description: "Stacking is now governed by scope alone (one per store + one platform-wide), so any leftover stacking checkbox on this form is a bug.",
943
+ },
944
+ {
945
+ key: "seller-coupon-category-restriction-works",
946
+ label: "Restricting a seller coupon to specific categories actually limits which items it discounts",
947
+ href: "/store/coupons/new",
948
+ description: "Create a coupon restricted to one category, then as a buyer put a non-matching item from that store in the cart and try the code — it must be refused. Add a matching item and confirm the discount covers only the matching item's value.",
949
+ },
716
950
  { key: "seller-bundles-crud", label: "Seller can create, edit, and list bundles/grouped listings", href: "/store/bundles" },
717
951
  { key: "seller-classified-crud", label: "Seller can create, edit, and list classified listings", href: "/store/classified" },
718
952
  { key: "seller-digitalcodes-crud", label: "Seller can create, edit, and list digital-code listings", href: "/store/digital-codes" },
@@ -1077,6 +1311,80 @@ const rawTesterChecklistItems = [
1077
1311
  },
1078
1312
  ],
1079
1313
  },
1314
+ {
1315
+ pageKey: "carousel-arrow-bounds",
1316
+ pageLabel: "Carousel Arrows — Card Boundaries",
1317
+ href: "/",
1318
+ cases: [
1319
+ {
1320
+ key: "arrows-never-cover-cards",
1321
+ label: "On a laptop/desktop, the carousel's ← / → arrows sit in their own empty strip to the left and right of the cards — no part of any card (image, title, price, badge, button) is ever hidden behind an arrow",
1322
+ description: "Fixed 2026-08-21. Previously the arrows were drawn ON TOP of the cards: an opaque white circle sat over the card's left and right edges, cutting into the title and image. The arrows should now read as boundary walls with the cards living strictly between them.",
1323
+ href: "/",
1324
+ },
1325
+ {
1326
+ key: "arrows-no-overlap-mid-scroll",
1327
+ label: "Scroll a carousel to a HALFWAY position (drag it, or click → once and stop mid-way) and confirm the cards are still cleanly cut off at the inner edge of the arrow strip — a card must never slide underneath an arrow while scrolling",
1328
+ description: "This is the single most important case — the old bug was invisible at rest and only appeared once you scrolled. The arrows looked fine on first load, then cards slid under them the moment the row moved. Check several carousels, and stop scrolling at a few different in-between positions.",
1329
+ href: "/",
1330
+ },
1331
+ {
1332
+ key: "no-arrows-on-mobile",
1333
+ label: "On a phone, carousels show NO ← / → arrow buttons at all — you scroll them by swiping",
1334
+ description: "Intentional as of 2026-08-21: on a narrow screen the arrows would eat roughly 60px of card width, so they are hidden below tablet size and swiping is the only control. Seeing arrows on a phone is now a bug.",
1335
+ href: "/",
1336
+ },
1337
+ {
1338
+ key: "mobile-card-not-clipped",
1339
+ label: "On a phone, a carousel card fills the row edge-to-edge and is NOT cut off on either side — its title, price and button are fully visible without any part running off-screen",
1340
+ description: "Fixed 2026-08-21. Cards used to be rendered wider than the space available, so both the left and right edges were chopped off simultaneously. Compare against the section heading above it: the card should line up with the section's own left/right margins.",
1341
+ href: "/",
1342
+ },
1343
+ {
1344
+ key: "mobile-swipe-and-snap",
1345
+ label: "On a phone, swiping a carousel still scrolls it, and it settles neatly on one card at a time rather than stopping halfway between two cards",
1346
+ href: "/",
1347
+ },
1348
+ {
1349
+ key: "no-white-fade-smear",
1350
+ label: "There is no washed-out white gradient smear at the left/right ends of a carousel — especially on sections that have a coloured or photo background, where a white haze would look obviously wrong",
1351
+ description: "The old fade-edge effect was removed on 2026-08-21 because the arrow gutters now mark the boundary. Check a section with a background image (if one is configured) as well as a plain one, in both light and dark mode.",
1352
+ href: "/",
1353
+ },
1354
+ {
1355
+ key: "two-row-tall-arrows",
1356
+ label: "On a section that shows TWO rows of cards (e.g. Reserve Before It Ships / Live Auctions when configured for 2 rows), the taller full-height arrow bars also sit beside the cards, not over them",
1357
+ href: "/",
1358
+ },
1359
+ {
1360
+ key: "arrows-dark-mode",
1361
+ label: "In dark mode the carousel arrows are still clearly visible against the page and sit in the same position beside the cards as in light mode",
1362
+ href: "/",
1363
+ },
1364
+ {
1365
+ key: "arrow-end-state-no-jump",
1366
+ label: "Clicking → repeatedly to the end of a carousel that does not loop dims/greys the → arrow, and the cards do NOT shift sideways when that happens",
1367
+ href: "/",
1368
+ },
1369
+ {
1370
+ key: "resize-across-breakpoint",
1371
+ label: "On a desktop browser, slowly narrow the window until it is phone-width and back again — the arrows disappear/reappear at the size change and the cards re-fit cleanly each time, never ending up underneath an arrow",
1372
+ description: "Easiest with the browser's responsive/device-toolbar mode. Drag the width slowly rather than jumping between presets.",
1373
+ href: "/",
1374
+ },
1375
+ {
1376
+ key: "related-carousels-same-behaviour",
1377
+ label: "The \"You might also like\" / related-items carousels lower down a PRODUCT page behave exactly the same as the homepage ones — arrows beside the cards, nothing covered, no arrows on mobile",
1378
+ description: "These are a different surface using the same underlying scroller, so they are the best check that the fix applied everywhere rather than only on the homepage.",
1379
+ href: "/products/product-tester-standard-1",
1380
+ },
1381
+ {
1382
+ key: "category-page-carousels-same-behaviour",
1383
+ label: "Carousels on a CATEGORY page (e.g. related groups / bundles strips) behave the same — arrows beside the cards, nothing covered",
1384
+ href: "/categories/category-beyblade-burst",
1385
+ },
1386
+ ],
1387
+ },
1080
1388
  ]),
1081
1389
  ...group("public-pages", "Public & Marketing Pages", [
1082
1390
  {
@@ -1241,6 +1549,34 @@ const rawTesterChecklistItems = [
1241
1549
  { key: "coupon-min-max-discount", label: "Admin-set minPurchase and maxDiscount are correctly enforced at checkout" },
1242
1550
  { key: "coupon-scope-admin-vs-seller", label: "Admin-scope and seller-scope coupons both apply correctly, respecting scope rules" },
1243
1551
  { key: "coupon-expire-reject", label: "Expiring a coupon from the admin editor causes checkout to reject it immediately" },
1552
+ {
1553
+ key: "coupon-admin-is-platform-wide",
1554
+ label: "An admin-created coupon applies across every store in the cart, not just one",
1555
+ href: "/admin/coupons/new",
1556
+ description: "Admin coupons have no store field — they are platform-wide by design. Create one, then as a buyer apply it to a cart holding items from two different stores and confirm the discount is spread across both stores' items.",
1557
+ },
1558
+ {
1559
+ key: "coupon-admin-no-stacking-toggle",
1560
+ label: "The admin coupon form no longer has an \"Allow stacking with seller coupons\" toggle",
1561
+ href: "/admin/coupons/new",
1562
+ description: "Stacking is now unconditional (one per store + one platform-wide), so that toggle was removed. Its presence — or any saved coupon still blocking a seller coupon from stacking — is a bug.",
1563
+ },
1564
+ {
1565
+ key: "coupon-admin-category-restriction-enforced",
1566
+ label: "Restricting an admin coupon to categories actually limits which items it discounts",
1567
+ href: "/admin/coupons/new",
1568
+ description: "Create a platform-wide coupon restricted to one category. A cart with none of that category must have the code refused; a cart with some must discount only those items. Leaving the category list empty must keep the coupon applicable to everything.",
1569
+ },
1570
+ {
1571
+ key: "coupon-admin-edit-actually-saves",
1572
+ label: "Editing a coupon's name, discount, limits or dates actually persists after reload",
1573
+ description: "Change several fields at once, save, then hard-reload the edit page. Every change must still be there. A save that returns success but shows the old values after reload is a bug.",
1574
+ },
1575
+ {
1576
+ key: "coupon-admin-usage-visible",
1577
+ label: "The admin coupon list shows usage counts that go up after a buyer redeems the coupon",
1578
+ description: "Note the current usage, have a buyer complete an order with the coupon, then refresh the admin list. Check this for BOTH a cash/UPI order and (if Razorpay is enabled) an online-payment order — online payments previously did not record usage at all.",
1579
+ },
1244
1580
  ],
1245
1581
  },
1246
1582
  {
package/dist/index.d.ts CHANGED
@@ -2457,7 +2457,7 @@ export { getProductFilterKeys } from "./features/products/index";
2457
2457
  export { getProductSortOptions } from "./features/products/index";
2458
2458
  export { getProductTableColumns } from "./features/products/index";
2459
2459
  export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardListing, isPrizeDrawListing, isClassifiedListing, isDigitalCodeListing, isLiveListing, isArtListing, isStickersListing, } from "./features/products/index";
2460
- export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, } from "./_internal/shared/listing-types/feature-flags";
2460
+ export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, ALL_LISTING_TYPES, } from "./_internal/shared/listing-types/feature-flags";
2461
2461
  export { actionTracker, setActionTrackerSink, resetActionTrackerSink, type ActionEvent, type ActionTrackerSink, } from "./_internal/shared/listing-types/action-tracker";
2462
2462
  export { cartRequiresShipping, cartIsDigitalOnly, cartIsChatOnly, } from "./_internal/shared/listing-types/cart-shipping";
2463
2463
  export { ACTIONS, action, act, canPerformAction, actionsForListingType, actionLabel, type ActionDef, type ActionKind, type ActionResource, type ActionTree, type ActionConfirmation, } from "./_internal/shared/actions/action-registry";
@@ -2538,6 +2538,8 @@ export { COUPON_FIELDS } from "./features/promotions/index";
2538
2538
  export { COUPON_TYPE_LABELS } from "./features/promotions/index";
2539
2539
  export { COUPON_USAGE_SUBCOLLECTION } from "./features/promotions/index";
2540
2540
  export { CouponCard } from "./features/promotions/index";
2541
+ export { CouponHelpDetails } from "./features/promotions/index";
2542
+ export type { CouponHelpDetailsProps } from "./features/promotions/index";
2541
2543
  export { CouponsIndexListing } from "./features/promotions/index";
2542
2544
  export type { CouponsIndexListingProps } from "./features/promotions/index";
2543
2545
  export { DEFAULT_COUPON_DATA } from "./features/promotions/index";
@@ -2583,6 +2585,8 @@ export type { RestrictionsConfig } from "./features/promotions/index";
2583
2585
  export type { TieredDiscount } from "./features/promotions/index";
2584
2586
  export type { UsageConfig } from "./features/promotions/index";
2585
2587
  export type { ValidityConfig } from "./features/promotions/index";
2588
+ export { detectCouponConflict } from "./features/promotions/server";
2589
+ export type { IncomingCoupon } from "./features/promotions/server";
2586
2590
  export { getPromotions } from "./features/promotions/server";
2587
2591
  export { promotionsGET } from "./features/promotions/server";
2588
2592
  export { sellerCreateCoupon } from "./features/promotions/server";
@@ -3133,9 +3137,11 @@ export type { ProductFeatureAdminCreatePayload, ProductFeatureStoreCreatePayload
3133
3137
  export { LISTING_PARAM_NAMES, parseListingParams, parseListingSearchParams, serializeListingParams, } from "./utils/listing-params";
3134
3138
  export type { ListingParams } from "./utils/listing-params";
3135
3139
  export { PRODUCT_FEATURE_CATEGORY_OPTIONS, PRODUCT_FEATURE_PRODUCT_TYPE_OPTIONS, PRODUCT_FEATURE_SCOPE_OPTIONS, PRODUCT_FEATURE_ICON_COLOR_OPTIONS, PRODUCT_FEATURE_SCOPE_TABS, PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER, PRODUCT_FEATURE_CARD_MAX_VISIBLE, PRODUCT_FEATURE_QUERY_STALE_MS, } from "./features/products/constants/product-features.constants";
3136
- export { CATEGORY_PAGE_TABS, STORE_PAGE_TABS, SELLER_LISTING_TABS, SEARCH_RESULT_TABS, } from "./features/products/constants/listing-tabs";
3137
- export type { ListingTab, CategoryTabId, StoreTabId, SellerListingTabId, SearchTabId, } from "./features/products/constants/listing-tabs";
3138
- export { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, AUCTION_SORT_OPTIONS, AUCTION_PUBLIC_SORT_OPTIONS, PREORDER_SORT_OPTIONS, BUNDLE_SORT_OPTIONS, PRIZE_DRAW_SORT_OPTIONS, SORT_OPTIONS_BY_LISTING_TYPE, } from "./features/products/constants/sieve";
3140
+ export { CATEGORY_PAGE_TABS, STORE_PAGE_TABS, SELLER_LISTING_TABS, PRODUCT_TYPE_FILTER_TABS, ART_STICKERS_TYPE_FILTER_TABS, GENERIC_PRODUCT_LISTING_TYPES, ART_STICKERS_LISTING_TYPES, } from "./features/products/constants/listing-tabs";
3141
+ export type { ListingTab, CategoryTabId, StoreTabId, SellerListingTabId, } from "./features/products/constants/listing-tabs";
3142
+ export { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, AUCTION_SORT_OPTIONS, AUCTION_PUBLIC_SORT_OPTIONS, PREORDER_SORT_OPTIONS, PREORDER_PUBLIC_SORT_OPTIONS, BUNDLE_SORT_OPTIONS, PRIZE_DRAW_SORT_OPTIONS, PRIZE_DRAW_PUBLIC_SORT_OPTIONS, } from "./features/products/constants/sieve";
3143
+ export { sortOptionsFor, commonSortOptionsFor, hideDefaultsFor, } from "./_internal/shared/listing-types/_registry";
3144
+ export type { ListingHideDefault } from "./_internal/shared/listing-types/_registry";
3139
3145
  export type { SortOption } from "./features/products/constants/sieve";
3140
3146
  export { ALL_TAB, EMPTY_TAB, ADMIN_PRODUCT_STATUS_TABS, ADMIN_PRODUCT_LISTING_TYPE_TABS, ADMIN_BLOG_STATUS_TABS, ADMIN_USER_STATUS_TABS, ADMIN_USER_ROLE_TABS, ADMIN_STORE_STATUS_TABS, ADMIN_PAYOUT_STATUS_TABS, ADMIN_ORDER_STATUS_TABS, ADMIN_REVIEW_STATUS_TABS, ADMIN_REVIEW_RATING_TABS, ADMIN_BID_STATUS_TABS, ADMIN_CONTACT_STATUS_TABS, ADMIN_NEWSLETTER_STATUS_TABS, ADMIN_EVENT_ENTRY_STATUS_TABS, ADMIN_EVENT_STATUS_TABS, ADMIN_CART_OWNERSHIP_TABS, ADMIN_COUPON_TYPE_TABS, SELLER_PRODUCT_STATUS_TABS, SELLER_AUCTION_STATUS_TABS, SELLER_ORDER_STATUS_TABS, SELLER_OFFER_STATUS_TABS, SELLER_BID_STATUS_TABS, } from "./features/admin/constants/filter-tabs";
3141
3147
  export type { AdminFilterTab } from "./features/admin/constants/filter-tabs";
@@ -3264,3 +3270,4 @@ export { safeFireAndForget } from "./utils/safe-fire-forget";
3264
3270
  export { withRetry } from "./http/retry";
3265
3271
  export { normalizeError, getErrorMessage, isApiNormalized, isAppNormalized, isFirebaseAuthNormalized, isFirebaseFirestoreNormalized, isFirebaseStorageNormalized, isNativeNormalized, isNetworkNormalized, isUnknownNormalized, isZodNormalized, } from "./errors/normalize";
3266
3272
  export type { NormalizedError, NormalizedApiError, NormalizedAppError, NormalizedFirebaseAuthError, NormalizedFirebaseFirestoreError, NormalizedFirebaseStorageError, NormalizedNativeError, NormalizedNetworkError, NormalizedUnknownThrownValue, NormalizedZodError, } from "./errors/normalize";
3273
+ export { CART_LANE, CART_LANE_PRIORITY, CART_LANE_LABELS, laneOf, activeLane, laneItems, laneCounts, isLaneCheckoutable, laneBlockReason, isLockedLane, canAddNewItems, type CartLane, type LaneAssignable, } from "./_internal/shared/checkout/lanes";
package/dist/index.js CHANGED
@@ -4463,7 +4463,7 @@ export { getProductTableColumns } from "./features/products/index";
4463
4463
  // SB-UNI-F 2026-05-13 â€" Phase 2 predicates added (classified/digital-code/live).
4464
4464
  export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardListing, isPrizeDrawListing, isClassifiedListing, isDigitalCodeListing, isLiveListing, isArtListing, isStickersListing, } from "./features/products/index";
4465
4465
  // SB-UNI-X4 2026-05-13 â€" per-type feature-flag helpers.
4466
- export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, } from "./_internal/shared/listing-types/feature-flags";
4466
+ export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, ALL_LISTING_TYPES, } from "./_internal/shared/listing-types/feature-flags";
4467
4467
  // SB-UNI-X5 2026-05-13 â€" action telemetry sink.
4468
4468
  export { actionTracker, setActionTrackerSink, resetActionTrackerSink, } from "./_internal/shared/listing-types/action-tracker";
4469
4469
  // SB-UNI-S 2026-05-13 â€" cart-level shipping-requirement helpers.
@@ -4583,6 +4583,8 @@ export { COUPON_USAGE_SUBCOLLECTION } from "./features/promotions/index";
4583
4583
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
4584
4584
  // CouponCard - Component for coupon card.
4585
4585
  export { CouponCard } from "./features/promotions/index";
4586
+ // CouponHelpDetails - Expandable "How coupons work" explainer (scoping + stacking rules).
4587
+ export { CouponHelpDetails } from "./features/promotions/index";
4586
4588
  // CouponsIndexListing - Client component for browsing coupons with search/filter/sort.
4587
4589
  export { CouponsIndexListing } from "./features/promotions/index";
4588
4590
  // [SCHEMA]-Schema / data-shape constant â€" Zod validator, default-value object, or Firestore collection/field name constant.
@@ -4649,6 +4651,9 @@ export { useCouponValidate } from "./features/promotions/index";
4649
4651
  // usePromotions - React hook for use promotions.
4650
4652
  export { usePromotions } from "./features/promotions/index";
4651
4653
  // ./features/promotions/server
4654
+ // [SERVER-ONLY]-Server-only â€" uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
4655
+ // detectCouponConflict - Coupon stacking rule: one seller coupon per store + one admin coupon per cart.
4656
+ export { detectCouponConflict } from "./features/promotions/server";
4652
4657
  // [DB]-Database layer â€" uses firebase-admin or another server-side DB SDK; can only run in a trusted server environment.
4653
4658
  // getPromotions - Helper for get promotions.
4654
4659
  export { getPromotions } from "./features/promotions/server";
@@ -5534,8 +5539,12 @@ export { LISTING_PARAM_NAMES, parseListingParams, parseListingSearchParams, seri
5534
5539
  // Product features UI option lists (S8 refactor â€" shared by editor + selector)
5535
5540
  export { PRODUCT_FEATURE_CATEGORY_OPTIONS, PRODUCT_FEATURE_PRODUCT_TYPE_OPTIONS, PRODUCT_FEATURE_SCOPE_OPTIONS, PRODUCT_FEATURE_ICON_COLOR_OPTIONS, PRODUCT_FEATURE_SCOPE_TABS, PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER, PRODUCT_FEATURE_CARD_MAX_VISIBLE, PRODUCT_FEATURE_QUERY_STALE_MS, } from "./features/products/constants/product-features.constants";
5536
5541
  // SB10-A listing tab constants
5537
- export { CATEGORY_PAGE_TABS, STORE_PAGE_TABS, SELLER_LISTING_TABS, SEARCH_RESULT_TABS, } from "./features/products/constants/listing-tabs";
5538
- export { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, AUCTION_SORT_OPTIONS, AUCTION_PUBLIC_SORT_OPTIONS, PREORDER_SORT_OPTIONS, BUNDLE_SORT_OPTIONS, PRIZE_DRAW_SORT_OPTIONS, SORT_OPTIONS_BY_LISTING_TYPE, } from "./features/products/constants/sieve";
5542
+ export { CATEGORY_PAGE_TABS, STORE_PAGE_TABS, SELLER_LISTING_TABS, PRODUCT_TYPE_FILTER_TABS, ART_STICKERS_TYPE_FILTER_TABS, GENERIC_PRODUCT_LISTING_TYPES, ART_STICKERS_LISTING_TYPES, } from "./features/products/constants/listing-tabs";
5543
+ export { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, AUCTION_SORT_OPTIONS, AUCTION_PUBLIC_SORT_OPTIONS, PREORDER_SORT_OPTIONS, PREORDER_PUBLIC_SORT_OPTIONS, BUNDLE_SORT_OPTIONS, PRIZE_DRAW_SORT_OPTIONS, PRIZE_DRAW_PUBLIC_SORT_OPTIONS, } from "./features/products/constants/sieve";
5544
+ // The per-type sort lookup is DERIVED from the listing-type plugin registry
5545
+ // (it used to be a hand-written map here that was missing five of the nine
5546
+ // types and still listed the long-dead `bundle` listingType).
5547
+ export { sortOptionsFor, commonSortOptionsFor, hideDefaultsFor, } from "./_internal/shared/listing-types/_registry";
5539
5548
  // SB10-C admin + seller filter-chip tab sets (S8 2026-05-13)
5540
5549
  export { ALL_TAB, EMPTY_TAB, ADMIN_PRODUCT_STATUS_TABS, ADMIN_PRODUCT_LISTING_TYPE_TABS, ADMIN_BLOG_STATUS_TABS, ADMIN_USER_STATUS_TABS, ADMIN_USER_ROLE_TABS, ADMIN_STORE_STATUS_TABS, ADMIN_PAYOUT_STATUS_TABS, ADMIN_ORDER_STATUS_TABS, ADMIN_REVIEW_STATUS_TABS, ADMIN_REVIEW_RATING_TABS, ADMIN_BID_STATUS_TABS, ADMIN_CONTACT_STATUS_TABS, ADMIN_NEWSLETTER_STATUS_TABS, ADMIN_EVENT_ENTRY_STATUS_TABS, ADMIN_EVENT_STATUS_TABS, ADMIN_CART_OWNERSHIP_TABS, ADMIN_COUPON_TYPE_TABS, SELLER_PRODUCT_STATUS_TABS, SELLER_AUCTION_STATUS_TABS, SELLER_ORDER_STATUS_TABS, SELLER_OFFER_STATUS_TABS, SELLER_BID_STATUS_TABS, } from "./features/admin/constants/filter-tabs";
5541
5550
  // Grouped listings feature schemas
@@ -5666,3 +5675,6 @@ export { withRetry } from "./http/retry";
5666
5675
  // Every catch (e: unknown) funnels through normalizeError(e) which returns
5667
5676
  // a NormalizedError discriminated union. Audit-catch-normalize enforces this.
5668
5677
  export { normalizeError, getErrorMessage, isApiNormalized, isAppNormalized, isFirebaseAuthNormalized, isFirebaseFirestoreNormalized, isFirebaseStorageNormalized, isNativeNormalized, isNetworkNormalized, isUnknownNormalized, isZodNormalized, } from "./errors/normalize";
5678
+ // Checkout lanes — the derived auction > offer > standard partition of the
5679
+ // cart, and the priority rule that decides which one may be checked out.
5680
+ export { CART_LANE, CART_LANE_PRIORITY, CART_LANE_LABELS, laneOf, activeLane, laneItems, laneCounts, isLaneCheckoutable, laneBlockReason, isLockedLane, canAddNewItems, } from "./_internal/shared/checkout/lanes";
@@ -1,3 +1,4 @@
1
+ import type { ListingType } from "../../features/products/types/index";
1
2
  /**
2
3
  * W1-43 — useListingTypeFlags
3
4
  *
@@ -8,25 +9,17 @@
8
9
  * Defaults to all-enabled when settings haven't loaded yet — match the
9
10
  * permissive default used by `DEFAULT_SITE_SETTINGS_DATA` so first paint
10
11
  * doesn't briefly hide enabled features.
12
+ *
13
+ * KEYED ON THE UNION, NOT A HAND-WRITTEN LIST (2026-08-21). Both types below
14
+ * used to enumerate seven types by hand and had never been updated for `art`
15
+ * and `stickers` — so `isEnabled("art")` didn't even typecheck, and the seller
16
+ * TypeDropdown (its main consumer) could not offer either type. Deriving from
17
+ * `ListingType` makes a missing member a compile error, matching the same
18
+ * decision made for `ALL_LISTING_TYPES_MAP` in `feature-flags.ts`.
11
19
  */
12
- export type ListingTypeFlagsShape = Partial<{
13
- standard: boolean;
14
- auction: boolean;
15
- "pre-order": boolean;
16
- "prize-draw": boolean;
17
- classified: boolean;
18
- "digital-code": boolean;
19
- live: boolean;
20
- }>;
21
- export interface ListingTypeFlags {
22
- standard: boolean;
23
- auction: boolean;
24
- "pre-order": boolean;
25
- "prize-draw": boolean;
26
- classified: boolean;
27
- "digital-code": boolean;
28
- live: boolean;
20
+ export type ListingTypeFlagsShape = Partial<Record<ListingType, boolean>>;
21
+ export type ListingTypeFlags = Record<ListingType, boolean> & {
29
22
  /** True when the listing type is enabled (or the setting hasn't loaded yet). */
30
- isEnabled: (type: "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live") => boolean;
31
- }
23
+ isEnabled: (type: ListingType) => boolean;
24
+ };
32
25
  export declare function useListingTypeFlags(): ListingTypeFlags;
@@ -1,17 +1,12 @@
1
1
  "use client";
2
2
  import { useSiteSettings } from "../../core/hooks/useSiteSettings";
3
+ import { ALL_LISTING_TYPES } from "../../_internal/shared/listing-types/feature-flags";
3
4
  export function useListingTypeFlags() {
4
5
  const { data } = useSiteSettings();
5
6
  const lt = data?.featureFlags?.listingTypes;
6
- const flags = {
7
- standard: lt?.standard !== false,
8
- auction: lt?.auction !== false,
9
- "pre-order": lt?.["pre-order"] !== false,
10
- "prize-draw": lt?.["prize-draw"] !== false,
11
- classified: lt?.classified !== false,
12
- "digital-code": lt?.["digital-code"] !== false,
13
- live: lt?.live !== false,
14
- };
7
+ // A type is disabled only when EXPLICITLY set to false — a missing flag (or
8
+ // settings that haven't loaded) means enabled.
9
+ const flags = Object.fromEntries(ALL_LISTING_TYPES.map((type) => [type, lt?.[type] !== false]));
15
10
  return {
16
11
  ...flags,
17
12
  isEnabled: (type) => flags[type],