@mohasinac/appkit 4.42.1 → 4.42.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/dist/_internal/server/features/offers/adapters.d.ts +29 -7
  2. package/dist/_internal/server/features/offers/adapters.js +14 -3
  3. package/dist/_internal/server/features/orders/adapters.js +54 -1
  4. package/dist/client.d.ts +1 -0
  5. package/dist/client.js +1 -0
  6. package/dist/features/account/components/UserOrdersView.js +30 -2
  7. package/dist/features/account/hooks/useAddresses.d.ts +21 -6
  8. package/dist/features/account/hooks/useAddresses.js +1 -1
  9. package/dist/features/addresses/hooks/usePostalLookup.js +84 -12
  10. package/dist/features/admin/components/AdminCategoriesView.js +37 -2
  11. package/dist/features/admin/components/AdminOffersView.js +2 -2
  12. package/dist/features/admin/constants/filter-tabs.d.ts +25 -0
  13. package/dist/features/admin/constants/filter-tabs.js +30 -0
  14. package/dist/features/admin/hooks/useAdminListingData.d.ts +35 -0
  15. package/dist/features/admin/hooks/useAdminListingData.js +45 -9
  16. package/dist/features/cart/schemas/index.d.ts +15 -0
  17. package/dist/features/cart/schemas/index.js +2 -0
  18. package/dist/features/cart/types/index.d.ts +22 -0
  19. package/dist/features/categories/components/CategoriesIndexListing.js +19 -1
  20. package/dist/features/categories/components/CategoriesIndexPageView.js +14 -1
  21. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +8 -0
  22. package/dist/features/classified/components/ClassifiedDetailPageView.js +1 -0
  23. package/dist/features/events/actions/event-actions.d.ts +13 -0
  24. package/dist/features/events/actions/event-actions.js +75 -1
  25. package/dist/features/events/repository/event-entry.repository.d.ts +17 -0
  26. package/dist/features/events/repository/event-entry.repository.js +36 -0
  27. package/dist/features/homepage/components/BrandsSection.js +1 -1
  28. package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
  29. package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
  30. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  31. package/dist/features/media/upload/MediaUploadField.js +21 -3
  32. package/dist/features/orders/components/OrdersList.js +2 -1
  33. package/dist/features/orders/index.d.ts +1 -0
  34. package/dist/features/orders/index.js +1 -0
  35. package/dist/features/orders/types/index.d.ts +2 -0
  36. package/dist/features/orders/utils/order-ref.d.ts +45 -0
  37. package/dist/features/orders/utils/order-ref.js +58 -0
  38. package/dist/features/products/components/MakeOfferButton.d.ts +18 -1
  39. package/dist/features/products/components/MakeOfferButton.js +23 -1
  40. package/dist/features/products/components/ProductDetailPageView.d.ts +5 -0
  41. package/dist/features/products/components/ProductDetailPageView.js +3 -2
  42. package/dist/features/products/repository/products.repository.js +29 -2
  43. package/dist/features/seller/actions/offer-actions.js +32 -0
  44. package/dist/features/seller/components/QuickProductForm.js +15 -1
  45. package/dist/features/seller/components/SellerBidsView.js +3 -1
  46. package/dist/features/seller/components/SellerOffersView.js +2 -2
  47. package/dist/features/seller/components/SellerOrdersView.js +49 -6
  48. package/dist/features/seller/components/SellerProductsCards.js +23 -1
  49. package/dist/features/seller/components/SellerProductsView.js +27 -2
  50. package/dist/features/seller/hooks/useSellerListingData.d.ts +0 -1
  51. package/dist/features/seller/hooks/useSellerListingData.js +17 -19
  52. package/dist/features/shell/SectionForm.js +33 -2
  53. package/dist/features/tester/seed-data/_happy-path.d.ts +23 -0
  54. package/dist/features/tester/seed-data/_happy-path.js +710 -0
  55. package/dist/features/tester/seed-data/_money-flows.js +34 -23
  56. package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +22 -3
  57. package/dist/features/tester/seed-data/authored/addresses__address-filters.js +17 -12
  58. package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +7 -6
  59. package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +10 -4
  60. package/dist/features/tester/seed-data/authored/addresses__state-picker.js +13 -8
  61. package/dist/features/tester/seed-data/authored/addresses__unban-request.js +49 -11
  62. package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +10 -6
  63. package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +71 -32
  64. package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +80 -31
  65. package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.js +62 -25
  66. package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +61 -31
  67. package/dist/features/tester/seed-data/authored/admin__bundles.js +84 -37
  68. package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +90 -57
  69. package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +38 -26
  70. package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +47 -28
  71. package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +118 -31
  72. package/dist/features/tester/seed-data/authored/admin__content-deletes.js +30 -24
  73. package/dist/features/tester/seed-data/authored/admin__content-marketing.js +83 -46
  74. package/dist/features/tester/seed-data/authored/admin__coupons.js +8 -8
  75. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +9 -9
  76. package/dist/features/tester/seed-data/authored/admin__firebase-function-effects.js +19 -4
  77. package/dist/features/tester/seed-data/authored/admin__media-watermark.js +77 -33
  78. package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +5 -5
  79. package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +29 -5
  80. package/dist/features/tester/seed-data/authored/admin__site-system.js +45 -23
  81. package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +53 -28
  82. package/dist/features/tester/seed-data/authored/admin__users-trust.js +20 -20
  83. package/dist/features/tester/seed-data/authored/buying__bidding.js +19 -2
  84. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +20 -5
  85. package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +28 -10
  86. package/dist/features/tester/seed-data/authored/buying__cart.js +10 -3
  87. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +0 -16
  88. package/dist/features/tester/seed-data/authored/buying__my-orders.js +27 -5
  89. package/dist/features/tester/seed-data/authored/buying__offers.js +22 -7
  90. package/dist/features/tester/seed-data/authored/buying__order-detail-actions.js +3 -3
  91. package/dist/features/tester/seed-data/authored/buying__product-detail.js +35 -4
  92. package/dist/features/tester/seed-data/authored/buying__return-request.js +19 -6
  93. package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +36 -18
  94. package/dist/features/tester/seed-data/authored/buying__reviews.js +16 -1
  95. package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +2 -1
  96. package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +16 -1
  97. package/dist/features/tester/seed-data/authored/community-support__public-profile.js +3 -2
  98. package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +4 -4
  99. package/dist/features/tester/seed-data/authored/content-discovery__blog.js +19 -2
  100. package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +4 -4
  101. package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +5 -5
  102. package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +41 -6
  103. package/dist/features/tester/seed-data/authored/content-discovery__event-detail-subroutes.js +63 -8
  104. package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +29 -29
  105. package/dist/features/tester/seed-data/authored/content-discovery__events.js +2 -2
  106. package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +23 -1
  107. package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +6 -6
  108. package/dist/features/tester/seed-data/authored/content-discovery__search.js +1 -1
  109. package/dist/features/tester/seed-data/authored/content-discovery__store-reviews-aggregate.js +1 -1
  110. package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +31 -6
  111. package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +34 -10
  112. package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -8
  113. package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +31 -9
  114. package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +29 -3
  115. package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +1 -1
  116. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +10 -10
  117. package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +3 -3
  118. package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +2 -2
  119. package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +2 -2
  120. package/dist/features/tester/seed-data/authored/design-ux__general-design.js +4 -4
  121. package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +1 -1
  122. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +8 -8
  123. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +2 -2
  124. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +2 -2
  125. package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +39 -16
  126. package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +43 -8
  127. package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +40 -14
  128. package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +31 -14
  129. package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +7 -6
  130. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +14 -5
  131. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +3 -3
  132. package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +1 -1
  133. package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +4 -2
  134. package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +3 -3
  135. package/dist/features/tester/seed-data/authored/public-pages__newly-wired-browse-indexes.js +5 -5
  136. package/dist/features/tester/seed-data/authored/public-pages__store-tabs.js +5 -5
  137. package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +3 -3
  138. package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +45 -11
  139. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -17
  140. package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +36 -5
  141. package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +29 -4
  142. package/dist/features/tester/seed-data/authored/selling__become-seller.js +23 -0
  143. package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +14 -13
  144. package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +14 -9
  145. package/dist/features/tester/seed-data/authored/selling__listing-edit-roundtrip.js +34 -7
  146. package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +17 -7
  147. package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +9 -9
  148. package/dist/features/tester/seed-data/authored/selling__media-limits.js +9 -4
  149. package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +3 -3
  150. package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +3 -3
  151. package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +25 -1
  152. package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +27 -6
  153. package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +19 -1
  154. package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +63 -22
  155. package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +9 -5
  156. package/dist/features/tester/seed-data/authored/selling__seller-guide.js +7 -6
  157. package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +3 -2
  158. package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +18 -18
  159. package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +13 -12
  160. package/dist/features/tester/seed-data/authored/selling__seller-orders-at-scale.js +19 -3
  161. package/dist/features/tester/seed-data/authored/selling__seller-orders.js +22 -4
  162. package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +20 -8
  163. package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +4 -4
  164. package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +2 -2
  165. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +5 -5
  166. package/dist/features/tester/seed-data/authored/seo__og-images.js +1 -1
  167. package/dist/features/tester/seed-data/authored/seo__page-metadata.js +7 -7
  168. package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +3 -3
  169. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +123 -21
  170. package/dist/features/tester/utils/phases.d.ts +17 -0
  171. package/dist/features/tester/utils/phases.js +29 -2
  172. package/dist/index.d.ts +1 -0
  173. package/dist/index.js +2 -0
  174. package/dist/seo/json-ld.d.ts +23 -0
  175. package/dist/seo/json-ld.js +3 -2
  176. package/dist/server.d.ts +1 -0
  177. package/dist/server.js +2 -0
  178. package/dist/styles.css +1 -1
  179. package/dist/tailwind-utilities.css +1 -1
  180. package/dist/ui/components/PaginatedSelect.js +0 -0
  181. package/package.json +1 -1
@@ -15,6 +15,29 @@
15
15
  * within the platform's compute budget, so the mark is a client overlay and its
16
16
  * settings have to reach a second consumer.
17
17
  *
18
+ * 🛑 …AND THAT SECOND CONSUMER IS NOT ON THE PRODUCT PAGE (found 2026-09-29).
19
+ * `<MediaVideo>`, which owns the overlay, is mounted in exactly three places:
20
+ * `HeroCarousel` and the two admin upload previews. A product video plays in
21
+ * `ImageLightbox`, which renders a bare `<video controls>` — no overlay layer at
22
+ * all. So the poster frame is watermarked (it is a still, proxied like any
23
+ * image) and the playing video is not. The parity case now expects that and
24
+ * names the homepage hero as the surface where the overlay does apply.
25
+ *
26
+ * The watermark tab is on /admin/site → 'Watermark'. Its real controls:
27
+ * a 'Watermark type' select (Text | Image), 'Watermark text' or a
28
+ * 'Watermark image' upload, a `Size — N% of image width` slider (5-100 step 5),
29
+ * an `Opacity — N%` slider (5-100 step 5), a 'Position' select, and a
30
+ * 'Use a custom X/Y offset instead' toggle revealing two labelled offset
31
+ * inputs measured in percent FROM CENTER (+right/+down).
32
+ *
33
+ * 🛑 THEME RECOLOUR READS `defaultLightThemeId` AND NOTHING ELSE.
34
+ * `resolveThemeGradientStops` looks up the theme record named by
35
+ * `theme.defaultLightThemeId` (falling back to "default-light") and takes its
36
+ * `--appkit-gradient-logo` stops. Changing the DARK default, or a viewer's own
37
+ * mode, does not move the composited mark's colour — so the case must change
38
+ * the default LIGHT theme or it will report a failure that is really a
39
+ * mis-aimed step.
40
+ *
18
41
  * @tag domain:tester
19
42
  * @tag layer:seed
20
43
  * @tag pattern:none
@@ -28,14 +51,20 @@ export const authored = {
28
51
  startPage: "/admin/site",
29
52
  steps: [
30
53
  "Sign in as admin@letitrip.in / TempPass123!.",
31
- "Open /admin/site and go to the watermark settings, noting the current size and opacity.",
32
- "Open a product image in a private window and note how the mark looks.",
33
- "Change the size to its largest offered value and the opacity to its most opaque, then save.",
34
- "Reload the product image in the private window and compare.",
35
- "Restore both settings to their original values and reload the image again.",
54
+ "Open /admin/site, go to its 'Watermark' tab, and write down the current values of the `Size — N% of image width` and `Opacity — N%` sliders.",
55
+ "Sign out and open /products/product-beyblade-burst-valkyrie, then open its main image and note how the mark looks.",
56
+ "Sign back in and set Size to 100 and Opacity to 100, then save.",
57
+ "Sign out, reload that product image with a HARD reload, and compare.",
58
+ "Sign back in, restore both sliders to the values written down in step 2, save, and reload the image again.",
36
59
  ],
60
+ inputs: {
61
+ product: "product-beyblade-burst-valkyrie",
62
+ sizeAfter: 100,
63
+ opacityAfter: 100,
64
+ sliderStep: 5,
65
+ },
37
66
  expectedBehaviour: "Size and opacity reach the served image. The mark is composited server-side as the image is proxied, so a setting that saves without changing the output means the render path is not reading it — and because images are cached aggressively, a hard reload may be needed before concluding either way.",
38
- expectedUiState: "The mark is visibly larger and more opaque after the change, and back to its original appearance after the restore. A settings page that saves with no visible effect on the served image is the failure.",
67
+ expectedUiState: "The mark is visibly larger and more opaque after the change, and back to its original appearance after the restore. Both sliders move in steps of 5 between 5 and 100, so a value typed outside that range is not reachable and is not a finding. A settings page that saves with no visible effect on the served image IS the failure.",
39
68
  endResult: "Both settings are restored. Note whether a hard reload was required — that is cache behaviour rather than a defect, but it changes how the next tester reads this page.",
40
69
  },
41
70
  "checklist-admin-media-watermark-watermark-position-presets": {
@@ -43,12 +72,13 @@ export const authored = {
43
72
  startPage: "/admin/site",
44
73
  steps: [
45
74
  "Sign in as admin@letitrip.in / TempPass123!.",
46
- "Open the watermark settings and read every position preset offered.",
47
- "Select each preset in turn, saving after each.",
48
- "After each save, open a product image in a private window and note where the mark sits.",
49
- "Check the mark stays fully inside the image at every preset.",
50
- "Restore the original preset.",
75
+ "Open /admin/site → 'Watermark' and read every option in the 'Position' select, writing down which is currently set.",
76
+ "Select each preset in turn — the four corners and center — saving after each. Leave 'custom' to the offset case.",
77
+ "After each save, sign out and open /products/product-beyblade-burst-valkyrie's main image, noting where the mark sits.",
78
+ "Repeat one corner preset against a portrait image and a landscape one.",
79
+ "Restore the preset written down in step 2.",
51
80
  ],
81
+ inputs: { product: "product-beyblade-burst-valkyrie" },
52
82
  expectedBehaviour: "Every preset places the mark somewhere different and every one keeps it inside the frame. A corner preset that pushes part of the mark off the edge is the likely failure, since the offset is computed from the image's dimensions and a portrait image behaves differently from a landscape one.",
53
83
  expectedUiState: "Each preset moves the mark to its named position and the whole mark remains visible within the image at all of them. Test at least one portrait and one landscape image — a preset correct on one aspect and clipped on the other is the finding.",
54
84
  endResult: "The original preset is restored.",
@@ -58,14 +88,20 @@ export const authored = {
58
88
  startPage: "/admin/site",
59
89
  steps: [
60
90
  "Sign in as admin@letitrip.in / TempPass123!.",
61
- "Open the watermark settings and find the custom offset controls.",
62
- "Set a modest offset, save, and read a product image in a private window.",
63
- "Set an extreme offset — larger than the image itself would allow — save, and read the image again.",
64
- "Read whether the mark is clipped, hidden entirely, or clamped to stay visible.",
65
- "Restore the original offset.",
91
+ "Open /admin/site → 'Watermark' and write down the current Position and any offsets.",
92
+ "Turn on 'Use a custom X/Y offset instead' and read the two inputs it reveals — 'X offset (%) — + right / − left of center' and 'Y offset (%) — + down / − up from center'.",
93
+ "Set X to 10 and Y to 10, save, sign out, and read /products/product-beyblade-burst-valkyrie's main image.",
94
+ "Sign back in, set X to 100 and Y to 100 — far enough to push the mark past the edge — save, sign out, and read the image again.",
95
+ "Read whether the mark is clipped, gone entirely, or clamped so it stays fully inside the frame.",
96
+ "Sign back in and restore the Position and offsets written down in step 2.",
66
97
  ],
98
+ inputs: {
99
+ product: "product-beyblade-burst-valkyrie",
100
+ modestOffset: 10,
101
+ extremeOffset: 100,
102
+ },
67
103
  expectedBehaviour: "A custom offset moves the mark, and an offset that would push it out of frame is clamped rather than obeyed. Obeying it produces images with no visible watermark at all — which is indistinguishable from the proxy being bypassed and would mask a much larger problem.",
68
- expectedUiState: "The modest offset moves the mark by the amount set. The extreme offset leaves the mark still visible, clamped to the frame, rather than pushing it off the image. A silently unwatermarked image is the failure.",
104
+ expectedUiState: "The modest offset moves the mark by the amount set. The extreme offset leaves the mark still visible, clamped flush to the frame, rather than pushing it off the image. A silently unwatermarked image is the failure. This is expected to PASS — the offset is clamped twice, once when the setting is read and again when the overlay is placed — so a clipped mark here would mean one of those clamps stopped running.",
69
105
  endResult: "The original offset is restored.",
70
106
  },
71
107
  "checklist-admin-media-watermark-watermark-theme-recolor": {
@@ -73,15 +109,17 @@ export const authored = {
73
109
  startPage: "/admin/site",
74
110
  steps: [
75
111
  "Sign in as admin@letitrip.in / TempPass123!.",
76
- "Open the watermark settings and confirm no custom image override is set.",
77
- "Open a product image in a private window and note the mark's colour.",
78
- "Open the Themes tab, switch the active theme to one with different brand colours, and save.",
79
- "Reload the product image and compare the mark's colour.",
80
- "Read a dark product photo and a light one and check the mark is visible on both.",
81
- "Restore the original theme.",
112
+ "Open /admin/site → 'Watermark' and confirm 'Watermark type' has no uploaded 'Watermark image' override — with none set the mark falls through to the bundled brand glyph, which is the only tier the theme can recolour.",
113
+ "Sign out, open /products/product-beyblade-burst-valkyrie's main image, and note the mark's colour.",
114
+ "Sign back in, open the 'Themes' tab, and write down which theme is set as the DEFAULT LIGHT theme.",
115
+ "Change the default LIGHT theme to one with different brand colours and save. Changing the dark default will not move the mark — the resolver reads the light one only.",
116
+ "Sign out, hard-reload the product image, and compare the mark's colour.",
117
+ "Compare a dark product photo and a light one and check the mark is legible on both.",
118
+ "Sign back in and restore the default light theme written down in step 4.",
82
119
  ],
120
+ inputs: { product: "product-beyblade-burst-valkyrie", themeSetting: "defaultLightThemeId" },
83
121
  expectedBehaviour: "With no image override the mark falls through to the bundled brand glyph, whose gradient stops come from theme colour tokens — so changing the theme recolours it without a second asset. It must stay legible over both dark and light photographs, which is the part a colour change can quietly break.",
84
- expectedUiState: "The mark's colour follows the theme. It remains visible on both a dark and a light product photo after the change. A mark that vanishes into one of the two is the finding.",
122
+ expectedUiState: "The mark's colour follows the DEFAULT LIGHT theme's logo gradient. It remains legible on both a dark and a light product photo after the change. A mark that vanishes into one of the two is the finding. A mark that does not change at all is only a finding if the default LIGHT theme was the one changed — say which you changed.",
85
123
  endResult: "The original theme is restored.",
86
124
  },
87
125
  "checklist-admin-media-watermark-watermark-video-overlay-parity": {
@@ -89,16 +127,22 @@ export const authored = {
89
127
  startPage: "/admin/site",
90
128
  steps: [
91
129
  "Sign in as admin@letitrip.in / TempPass123!.",
92
- "Open the watermark settings and note the size, opacity and position.",
93
- "Open /products/product-beyblade-burst-spryzen-video-demo in a private window.",
94
- "Look at the video's POSTER frame in the gallery strip and note the mark.",
95
- "Play the video and look for the mark while it is playing.",
96
- "Compare the mark on the video against the mark on a still image on the same page.",
97
- "Change the watermark position, save, and check both the still and the video follow.",
130
+ "Open /admin/site → 'Watermark' and write down the size, opacity and position.",
131
+ "Sign out and open /products/product-beyblade-burst-spryzen-video-demo.",
132
+ "Look at the video's POSTER frame in the gallery strip and note whether it carries the mark.",
133
+ "Click it to open the lightbox, play the video, and look for the mark while it is playing.",
134
+ "Compare that against a still image on the same page.",
135
+ "Now open / and find the 'Live auctions' hero slide, whose background is a video, and look for the mark on it while it plays.",
136
+ "Sign back in, change the watermark position, save, sign out, and check which of the three — the poster, the product video, the hero video — followed.",
98
137
  "Restore the position.",
99
138
  ],
100
- expectedBehaviour: "The poster frame is a still image and goes through the proxy, so it is composited like any other. The video itself cannot be composited server-side within the platform's compute budget, so its mark is a client overlay reading the same settings from the public settings response — two consumers, one configuration, and they must agree.",
101
- expectedUiState: "The poster frame carries the mark. The playing video carries a matching overlay in the same position at a comparable size. After the position change both the still and the video move. A video with no mark, or one whose mark ignores the setting, is the failure.",
102
- endResult: "The position is restored. Parity between the two paths is the case — either alone proves nothing.",
139
+ inputs: {
140
+ product: "product-beyblade-burst-spryzen-video-demo",
141
+ heroSlide: "Live auctions",
142
+ },
143
+ expectedBehaviour: "A still goes through the proxy and is composited server-side. A video cannot be, within the platform's compute budget, so its mark has to be a client overlay reading the same settings — two consumers, one configuration. The case is whether every surface that plays a video has that second consumer.",
144
+ expectedUiState: "The poster frame carries the mark, and follows a position change.\n\n🛑 EXPECT THE PRODUCT VIDEO TO CARRY NO MARK, AND RECORD IT AS A FAILURE. The overlay lives in `<MediaVideo>`, which is mounted only by the homepage hero carousel and the two admin upload previews; a product video plays in the lightbox, which renders a bare `<video controls>` with no overlay layer. So expect: poster marked, product video unmarked, hero video marked. Report which of the three followed the position change — that comparison is the finding, and a bare 'the video has no watermark' does not distinguish a missing overlay from a broken one.",
145
+ expectedData: { posterMarked: true, productVideoMarked: false, heroVideoMarked: true },
146
+ endResult: "The position is restored. Parity between the paths is the case — a mark on the poster alone proves nothing about the frames a buyer actually watches.",
103
147
  },
104
148
  };
@@ -77,11 +77,11 @@ export const authored = {
77
77
  steps: [
78
78
  "Sign in as admin@letitrip.in / TempPass123!.",
79
79
  "Open /admin/orders and find the manual-payment filter chips.",
80
- "Read every chip offered and write the list down.",
80
+ "Read every chip offered and write the list down — there are three: All, 'Awaiting payment' and 'Awaiting verification'.",
81
81
  "Select each in turn and read the rows returned.",
82
82
  "Check the awaiting-payment chip returns orders with NO proof uploaded.",
83
83
  "Check the awaiting-verification chip returns orders WITH a proof and no decision.",
84
- "Read the URL and confirm the selection is encoded there.",
84
+ "Read the URL and confirm the selection is encoded there as ?paymentReview=awaiting_proof or ?paymentReview=awaiting_verification — a query param of its own, NOT a Sieve f= filter, which is the whole point of the case.",
85
85
  ],
86
86
  expectedBehaviour: "The queue is not an ordinary Sieve filter. 'Has a proof' cannot be expressed as a query predicate — an inequality on the proof field excludes every document where it was never written, which IS the awaiting-payment set, so the query would return only the opposite of what was asked. The queue runs one bounded query and refines in memory instead, reached through its own query parameter.",
87
87
  expectedUiState: "The awaiting-payment chip returns orders with no proof, and the awaiting-verification chip returns orders with a proof and no decision — two visibly different sets. A chip returning the inverse of its name is the predicate failure.",
@@ -206,7 +206,7 @@ export const authored = {
206
206
  startPage: "/admin/orders",
207
207
  steps: [
208
208
  "Sign in as admin@letitrip.in / TempPass123!.",
209
- "Open the admin returns surface and read the rows.",
209
+ "Open /admin/return-requests and read the rows.",
210
210
  "Check each row shows the item, the buyer's stated reason and the order it belongs to.",
211
211
  "Read the scope tabs and check which scope a return-requested order sits under.",
212
212
  "Open one and read the full reason before acting.",
@@ -222,7 +222,7 @@ export const authored = {
222
222
  startPage: "/admin/orders",
223
223
  steps: [
224
224
  "Sign in as admin@letitrip.in / TempPass123!.",
225
- "Open the admin fulfillment queue and read every row's primary label.",
225
+ "Open /admin/fulfillment and read every row's primary label.",
226
226
  "Check each names the ITEM and shows its thumbnail rather than only an order id.",
227
227
  "Read a multi-item order's row and check it indicates there is more than one item.",
228
228
  "Read the queue's ordering and check the oldest unfulfilled order is first.",
@@ -237,7 +237,7 @@ export const authored = {
237
237
  startPage: "/admin/shipments",
238
238
  steps: [
239
239
  "Sign in as admin@letitrip.in / TempPass123!.",
240
- "Open the admin shipments surface and read the existing shipments.",
240
+ "Open /admin/shipments and read the existing shipments.",
241
241
  "Create one named 'QA Shipment admin-crud' with a supplier and a date, and save.",
242
242
  "RELOAD and read every field.",
243
243
  "Add a lot and an item to it, save, RELOAD, and read them back.",
@@ -29,7 +29,7 @@ export const authored = {
29
29
  "Create a prize draw titled 'QA Prize Draw admin-create' at 80 per entry, 25 entries, instant-reveal mode.",
30
30
  "Define at least two prizes with names and save.",
31
31
  "RELOAD the editor and read the mode, the per-entry price, the entry count and the prizes.",
32
- "Open the public page in a private window and read the purchase panel's wording.",
32
+ "Open the prize draw's public page at /prize-draws/{its id} in a private window and read the purchase panel's wording.",
33
33
  "Read whether the panel describes buying an ENTRY rather than buying the prize.",
34
34
  ],
35
35
  inputs: { title: "QA Prize Draw admin-create", pricePerEntry: 80, entryCount: 25 },
@@ -44,7 +44,7 @@ export const authored = {
44
44
  "Sign in as rehan.sheikh@gmail.com / TempPass123! and buy one entry in 'QA Prize Draw admin-create' with Cash on Delivery.",
45
45
  "Read what the order page shows immediately after payment.",
46
46
  "Sign out and sign in as admin@letitrip.in / TempPass123!.",
47
- "Open the draw's admin view and read the entries and any reveal control.",
47
+ "Open /admin/prize-draws/{its id}/edit and read the entries and any reveal control.",
48
48
  "Trigger the reveal if the mode requires it, and read the assigned prize.",
49
49
  "Sign back in as the buyer and read the prize on their order.",
50
50
  "Compare the prize the admin sees against the one the buyer sees.",
@@ -74,11 +74,11 @@ export const authored = {
74
74
  startPage: "/admin/products",
75
75
  steps: [
76
76
  "Sign in as admin@letitrip.in / TempPass123!.",
77
- "Open a prize draw's admin view and read what the reveal mechanism is described as.",
77
+ "Open /admin/prize-draws/prizedraw-beyblade-burst-collectors-draw/edit — a seeded draw that is still open — and read what the reveal mechanism is described as.",
78
78
  "Read whether the admin can choose WHO wins a specific prize.",
79
79
  "Read whether the admin can see, before revealing, which prize an entry will draw.",
80
80
  "Trigger a reveal and read whether the outcome was predictable from anything on screen beforehand.",
81
- "Open the public page and read whether the odds or prize pool are disclosed to buyers.",
81
+ "Open the prize draw's public page at /prize-draws/{its id} and read whether the odds or prize pool are disclosed to buyers.",
82
82
  ],
83
83
  expectedBehaviour: "The draw is not steerable. Prizes are assigned by cryptographic randomness at reveal, so an admin cannot pick a winner and cannot see the outcome in advance — a prize draw an operator can steer is a rigged one, whatever else the page does correctly.",
84
84
  expectedUiState: "No control assigns a specific prize to a specific entry or buyer. Nothing on the admin screen discloses an entry's outcome before it is revealed. The public page states the prize pool so buyers know what they are entering.",
@@ -89,7 +89,7 @@ export const authored = {
89
89
  startPage: "/admin/products",
90
90
  steps: [
91
91
  "Sign in as admin@letitrip.in / TempPass123!.",
92
- "Open a prize draw's entries view and read every column.",
92
+ "Open /admin/prize-draws/prizedraw-beyblade-metal-closed-revealed/entries — a CLOSED and revealed draw, so it has entries and a recorded winner to read rather than an empty table.",
93
93
  "Check each row shows enough to identify the entry without opening it.",
94
94
  "Read whether buyer identities are shown in full or masked.",
95
95
  "Read the row actions offered and check at least one lets you VIEW the entry.",
@@ -102,4 +102,28 @@ export const authored = {
102
102
  expectedData: { nonsenseResultCount: 0 },
103
103
  endResult: "Read-only. Delete 'QA Prize Draw admin-create' as the last action of this page.",
104
104
  },
105
+ "checklist-admin-prize-draws-lotteries-lottery-edit-preserves-bookings": {
106
+ roles: ["admin"],
107
+ startPage: "/admin/lotteries",
108
+ steps: [
109
+ "Sign in as admin@letitrip.in / TempPass123!.",
110
+ "Open /admin/lotteries and find event-pokemon-number-draw-july-2026 — 25 total slots, of which FIVE are already booked in the seed.",
111
+ "Open its slot editor at /admin/lotteries/event-pokemon-number-draw-july-2026/edit.",
112
+ "WRITE DOWN which slot numbers show as booked and the buyer named against each.",
113
+ "Change something harmless and unrelated — rename ONE unbooked slot's prize — and save.",
114
+ "RELOAD the editor and read the booked slots again.",
115
+ "Compare every booked slot number and buyer against what you wrote down.",
116
+ "Read the prices shown on the slots too: they must be the real stored amounts, not zeros.",
117
+ "Restore the renamed slot and save again.",
118
+ ],
119
+ inputs: {
120
+ eventId: "event-pokemon-number-draw-july-2026",
121
+ totalSlots: 25,
122
+ bookedSlots: 5,
123
+ },
124
+ expectedBehaviour: "Editing a lottery's prizes leaves its bookings alone. This is the one save in the application that has destroyed buyer data: the editor once sent isBooked:false and weight:0 for every slot and dropped the buyer fields entirely, and because the event PATCH was passthrough, the first save of a live lottery marked every purchased slot available again and erased who had bought them — with a 200 and no error anywhere. The write shape now cannot even express booking state, and the merge reattaches bookings by slotNumber rather than by array position, so deleting a slot cannot hand slot 7's buyer slot 8's prize.",
125
+ expectedUiState: "All five booked slots are still booked after the reload, against the same buyers, and only the renamed prize differs. Prices read as their stored amounts. 🛑 EVERY SLOT SHOWING AS AVAILABLE is the catastrophic outcome and it looks like a successful save — the page re-renders, the toast is green, and only the buyers know. Every price reading zero is the same defect's twin, from seeding the form off the public projection, which strips price and weight.",
126
+ expectedData: { bookedSlotsAfterSave: 5, slotsPricedZero: 0 },
127
+ endResult: "The renamed slot is restored and all five bookings survive. If any booking is gone, STOP and report it immediately rather than continuing the batch — there is no undo, and the remaining cases on this page would be run against corrupted data.",
128
+ },
105
129
  };
@@ -1,6 +1,27 @@
1
1
  /*
2
2
  * WHY: Authored six-part procedures for the admin/site-system checklist page.
3
- * WHAT: 35 case(s), keyed by full checklist id.
3
+ * WHAT: 37 case(s), keyed by full checklist id.
4
+ *
5
+ * 🛑 SITE SETTINGS IS PRESERVE-TIER, AND THE TESTER RULES SAY NEVER MODIFY IT.
6
+ * That rule and this page are not in conflict, but the reconciliation has to be
7
+ * stated or a tester following the rules literally answers `null` to all of them.
8
+ *
9
+ * The rule exists because `siteSettings` is NEVER wiped and NEVER reseeded — the
10
+ * lifecycle preserves it by design, so unlike the catalogue there is nothing to
11
+ * put it back. What it forbids is leaving it CHANGED, not touching it.
12
+ *
13
+ * So every mutating case here does three things, and a case that skips any of
14
+ * them is the defect rather than the coverage:
15
+ *
16
+ * 1. read and write down the existing value BEFORE editing,
17
+ * 2. make the smallest change that proves the point,
18
+ * 3. RESTORE the recorded value — not "clear the field", which is a different
19
+ * destructive edit wearing the same clothes.
20
+ *
21
+ * Three cases had no restore at all until 2026-09-29, and one of them overwrote
22
+ * the PhonePe Client ID, Client Secret and both webhook credentials with QA
23
+ * values and walked away. Running it would have disabled online payment for real
24
+ * buyers, silently, until somebody with the real keys noticed.
4
25
  *
5
26
  * Written by hand, case by case, against the label each one states. There is no
6
27
  * generator: the authoring scripts were deleted once it was clear they were
@@ -51,18 +72,18 @@ export const authored = {
51
72
  steps: [
52
73
  "Sign in as admin@letitrip.in / TempPass123!.",
53
74
  "Open /admin/site and go to the credentials tab.",
54
- "Read how existing credentials are displayed.",
55
- "Type QA-CREDENTIAL-118427 into one credential field and save.",
75
+ "Read how existing credentials are displayed, and WRITE DOWN the masked value of every field on the tab before touching anything.",
76
+ "Pick a field whose masked value shows a *_PLACEHOLDER or is empty — never one holding a real key. Type QA-CREDENTIAL-118427 into that field and save.",
56
77
  "Read the confirmation and RELOAD the tab.",
57
78
  "Read how that credential now displays.",
58
79
  "Open /api/site-settings in a private window and search the response for QA-CREDENTIAL-118427.",
59
- "Clear the field, save, and reload to confirm.",
80
+ "RESTORE the field to exactly what step 3 recorded — clearing it is NOT restoring it, and siteSettings survives every run, so a cleared real key is gone for good. Save and reload to confirm the original masked value is back.",
60
81
  ],
61
82
  inputs: { credential: "QA-CREDENTIAL-118427" },
62
83
  expectedBehaviour: "A credential is encrypted before storage and returned masked. The encryption helper THROWS when its key is unset — so a save that fails outright is a missing-key problem rather than a form bug, and a save that succeeds while storing plaintext is worse than either.",
63
84
  expectedUiState: "After the reload the credential shows masked rather than as the typed value or as raw ciphertext. The public settings response contains no trace of it. A save that errors should name the missing configuration rather than showing a stack trace.",
64
85
  expectedData: { credentialInPublicResponse: 0 },
65
- endResult: "The field is cleared. A hit in the public response is a credential leak and fails the case outright.",
86
+ endResult: "The field holds exactly what it held before the case ran — restored, not cleared. A hit in the public response is a credential leak and fails the case outright.",
66
87
  },
67
88
  "checklist-admin-site-system-site-settings-credentials-partial-save-keeps-others": {
68
89
  roles: ["admin"],
@@ -120,11 +141,12 @@ export const authored = {
120
141
  steps: [
121
142
  "Sign in as admin@letitrip.in / TempPass123!.",
122
143
  "Open /admin/site and switch to the Integrations tab.",
123
- "Read the current PhonePe Client ID, Client Secret, Client Version, Environment and both webhook credential fields.",
144
+ "WRITE DOWN the current PhonePe Client ID, Client Secret, Client Version, Environment and both webhook credential fields — the masked form of each. You must be able to put them back; nothing else will.",
124
145
  "Type QA-PHONEPE-CLIENT-991 into the Client ID field, QA-PHONEPE-SECRET-991 into the Client Secret field, and QA-WEBHOOK-USER-991 / QA-WEBHOOK-PASS-991 into the two webhook fields.",
125
146
  "Save, then hard-reload the page.",
126
147
  "Read how the Client Secret and both webhook fields display.",
127
148
  "Change only the PhonePe Environment dropdown, save again, and reload.",
149
+ "RESTORE every one of the six fields to exactly what step 3 recorded, save, and reload to confirm. This is not optional housekeeping: siteSettings is never reseeded, so QA values left in the PhonePe Client ID and Secret disable online payment for real buyers until someone notices and has the real keys to hand.",
128
150
  ],
129
151
  inputs: {
130
152
  clientId: "QA-PHONEPE-CLIENT-991",
@@ -142,11 +164,11 @@ export const authored = {
142
164
  steps: [
143
165
  "Sign in as admin@letitrip.in / TempPass123!.",
144
166
  "Open /admin/site and switch to the Shipping tab.",
145
- "Read the current state of the 'PhonePe (online card/UPI) enabled' toggle.",
146
- "Turn it OFF, save, and open /checkout in another tab (with an item in the cart) to confirm 'Pay Online (PhonePe)' is absent from the payment step.",
147
- "Return to Site Settings, turn the toggle back ON, and save.",
148
- "Reload the settings page to confirm it reads ON.",
149
- "Reopen /checkout and confirm 'Pay Online (PhonePe)' is now offered.",
167
+ "Read the current state of the 'PhonePe (online card/UPI) enabled' toggle. It is seeded OFF (phonepeEnabled: false), so ON is the direction that proves anything here — turning OFF something already off would leave PhonePe absent from checkout for the reason it was always absent, and pass trivially.",
168
+ "Turn it ON, save, and open /checkout in another tab (with an item in the cart) to confirm 'Pay Online (PhonePe)' is now offered in the payment step.",
169
+ "Return to Site Settings, RELOAD, and confirm the toggle still reads ON — the save is not proven until the reload.",
170
+ "Turn it back OFF and save, restoring the seeded state.",
171
+ "Reopen /checkout and confirm 'Pay Online (PhonePe)' is absent again.",
150
172
  ],
151
173
  expectedBehaviour: "The toggle lives under the Shipping tab's 'Payment methods' section, not a dedicated Payments tab — the same admin field checkout's payment-method list reads from. It gates whether the PhonePe option is offered at all, independent of the manual-UPI and COD toggles beside it.",
152
174
  expectedUiState: "With the toggle OFF, /checkout offers only whichever of manual UPI/cash and COD are separately enabled — no 'Pay Online (PhonePe)' button. With it back ON, the button reappears.",
@@ -157,12 +179,12 @@ export const authored = {
157
179
  startPage: "/admin/site",
158
180
  steps: [
159
181
  "Sign in as admin@letitrip.in / TempPass123!.",
160
- "Open the WhatsApp settings and read every field and its current state.",
182
+ "Open /admin/site, switch to the Integrations tab, and WRITE DOWN every WhatsApp field and its current masked state before touching anything.",
161
183
  "Type QA-WA-TOKEN-552310 into the access-token field and fill any account identifier fields.",
162
184
  "Save and RELOAD, then read how each field displays.",
163
185
  "Open /api/site-settings in a private window and search for QA-WA-TOKEN-552310.",
164
186
  "Open a public store page's source and search for it too.",
165
- "Clear the fields, save, and reload.",
187
+ "RESTORE every field to exactly what step 3 recorded — clearing is not restoring, and a real Meta token cleared here cannot be recovered from the seed. Save and reload to confirm.",
166
188
  ],
167
189
  inputs: { token: "QA-WA-TOKEN-552310" },
168
190
  expectedBehaviour: "The token is encrypted at rest and masked on read. It is also decrypted on every read of a store document, so any page handing a raw store document to a client component serialises it into public HTML — which is why both the settings response and a public store page are checked rather than the admin form alone.",
@@ -175,8 +197,8 @@ export const authored = {
175
197
  startPage: "/admin/site",
176
198
  steps: [
177
199
  "Sign in as admin@letitrip.in / TempPass123!.",
178
- "Open the WhatsApp settings and read every channel toggle and its state.",
179
- "Turn one OFF and save.",
200
+ "Open /admin/site, switch to the Notifications tab, and read every channel toggle and its state. Four are listed: in-app, email, WhatsApp and SMS.",
201
+ "Turn the WHATSAPP channel off and save. Pick that one specifically — SMS is seeded OFF (and has no sender at all), so toggling it off would be a no-op that proves nothing.",
180
202
  "RELOAD and read every toggle.",
181
203
  "Check only the one changed.",
182
204
  "Turn it back on, save, and reload to confirm.",
@@ -285,7 +307,7 @@ export const authored = {
285
307
  startPage: "/admin/analytics",
286
308
  steps: [
287
309
  "Sign in as admin@letitrip.in / TempPass123!.",
288
- "Open the admin analytics page and read every card and chart.",
310
+ "Open /admin/analytics and read every card and chart.",
289
311
  "Write down any figure reading zero or a dash.",
290
312
  "Change the date range if one is offered and read the figures again.",
291
313
  "Reload and confirm the figures are stable.",
@@ -316,7 +338,7 @@ export const authored = {
316
338
  steps: [
317
339
  "Sign in as admin@letitrip.in / TempPass123!.",
318
340
  "Open the browser console and clear it.",
319
- "Open the admin analytics page and wait for every card to settle.",
341
+ "Open /admin/analytics and wait for every card to settle.",
320
342
  "Read the console for any permission-denied message.",
321
343
  "Open the admin dashboard and read the console again.",
322
344
  "Note which card or widget was loading when any such message appeared.",
@@ -361,7 +383,7 @@ export const authored = {
361
383
  startPage: "/admin/maintenance",
362
384
  steps: [
363
385
  "Sign in as admin@letitrip.in / TempPass123!.",
364
- "Open the maintenance section and read every page it links to.",
386
+ "Open /admin/maintenance and read every page it links to.",
365
387
  "Open each in turn and confirm it renders content rather than an error.",
366
388
  "Note any that shows a failed-precondition or missing-index error.",
367
389
  "Note any that shows a permission or credentials error.",
@@ -376,10 +398,10 @@ export const authored = {
376
398
  startPage: "/admin/maintenance",
377
399
  steps: [
378
400
  "Sign in as admin@letitrip.in / TempPass123!.",
379
- "Open the server errors list and read the rows.",
401
+ "Open /admin/maintenance/server-errors and read the rows.",
380
402
  "Read whether rows carry a source, a code, a message and a time.",
381
- "Open the client errors list and read its rows.",
382
- "Open the function errors list and read its rows.",
403
+ "Open /admin/maintenance/client-errors and read its rows.",
404
+ "Open /admin/maintenance/function-errors and read its rows.",
383
405
  "Note which of the three lists is empty.",
384
406
  "Check the sort order puts the newest first.",
385
407
  ],
@@ -392,7 +414,7 @@ export const authored = {
392
414
  startPage: "/admin/maintenance",
393
415
  steps: [
394
416
  "Sign in as admin@letitrip.in / TempPass123!.",
395
- "Open the cloud logs page and read what renders.",
417
+ "Open /admin/maintenance/cloud-logs and read what renders.",
396
418
  "Read whether it shows log entries, an empty state, or an error.",
397
419
  "If it errors, read whether the message names the missing configuration.",
398
420
  "Reload and confirm the behaviour is consistent.",
@@ -407,7 +429,7 @@ export const authored = {
407
429
  startPage: "/admin",
408
430
  steps: [
409
431
  "Sign in as admin@letitrip.in / TempPass123!.",
410
- "Open the copilot or assistant surface and read what it offers.",
432
+ "Open /admin/copilot and read what it offers.",
411
433
  "Read whether it states which model or provider it uses.",
412
434
  "Submit a simple request and read the response.",
413
435
  "Read whether any error surfaces a provider key or a raw response body.",
@@ -12,6 +12,22 @@
12
12
  * speculative cases against a screen nobody has opened is how a checklist grows
13
13
  * cases that measure nothing.
14
14
  *
15
+ * Checked 2026-09-29, and three descriptions were wrong about the affordance:
16
+ *
17
+ * · /admin/featured is "Featured Products" — `buildFilters: () => "featured==true"`
18
+ * — so the row action REMOVES a product from the list rather than toggling a
19
+ * two-state control. Re-featuring is done from /admin/products' Quick edit,
20
+ * which is the only surface with a Featured toggle.
21
+ * · /admin/deals is "Deals (Promoted Products)" and its rows DO open — via
22
+ * `rowHrefTemplate` to /admin/products/{id}/edit, not to a detail of its own.
23
+ * · /admin/stickers has exactly ONE facet, `filterKeys: ["status"]`, so "apply a
24
+ * different filter" means a different status VALUE. Only `published` stickers
25
+ * are seeded, so the other three statuses returning nothing is correct.
26
+ *
27
+ * All twelve guide routes named in the guides case exist, and all twelve are in
28
+ * ADMIN_NAV_GROUPS — verified rather than assumed, because the hrefs audit
29
+ * checks `href`/`startPage` only and cannot see a route named in step prose.
30
+ *
15
31
  * @tag domain:tester
16
32
  * @tag layer:seed
17
33
  * @tag pattern:none
@@ -130,13 +146,14 @@ export const authored = {
130
146
  startPage: "/admin/settings/actions",
131
147
  steps: [
132
148
  SIGN_IN_ADMIN,
133
- "Open /admin/settings/actions.",
134
- "Read what the page lets an admin change.",
135
- "Change one setting and save.",
149
+ "Open /admin/settings/actions and read its two panels — the action-permissions manager and the nav-permissions panel.",
150
+ "Write down the current value of the one control you are about to change.",
151
+ "Change ONE action's permission — not a nav permission — and save.",
136
152
  "Reload and read whether the change held.",
137
- "Restore the original value.",
153
+ "Restore the value written down in step 3 and reload once more to confirm it went back.",
138
154
  ],
139
- expectedBehaviour: "A change made here persists. A settings save that returns success without writing is the most dangerous failure a settings screen has — there is no stale display to notice and no error to read, and it has happened on this codebase before.",
155
+ inputs: { panel: "action permissions" },
156
+ expectedBehaviour: "A change made here persists. A settings save that returns success without writing is the most dangerous failure a settings screen has — there is no stale display to notice and no error to read, and it has happened on this codebase before.\n\n🛑 Change an ACTION permission, not a nav permission. The nav panel drives the disabled-routes gate that runs in the proxy, so disabling the wrong entry 404s a real route for every later case in the run.",
140
157
  expectedUiState: "The changed value is still changed after the reload. A value that reverts is the finding, even though the save reported success.",
141
158
  endResult: "The original value is restored.",
142
159
  },
@@ -160,14 +177,16 @@ export const authored = {
160
177
  startPage: "/admin/stickers",
161
178
  steps: [
162
179
  SIGN_IN_ADMIN,
163
- "Open /admin/stickers.",
164
- "Read the rows listed and count them.",
165
- "Apply a status filter and read the rows again.",
166
- "Clear it and apply a different filter.",
167
- "Check each filter returns a plausible, non-empty result where such rows exist.",
180
+ "Open /admin/stickers and read the rows listed, counting them — five sticker listings are seeded.",
181
+ "Open the filter drawer: this page has ONE facet, Status.",
182
+ "Select Published and read the rows.",
183
+ "Select Draft, then In Review, then Archived, reading the rows each time.",
184
+ "Type zzzznope into the search box and read the count.",
168
185
  ],
186
+ inputs: { seededStickerCount: 5, nonsenseQuery: "zzzznope" },
169
187
  expectedBehaviour: "The list and its filters return real sticker listings. A filter whose value does not exactly match a stored one returns zero rows forever with no error — the single most common silent defect in this product's listing surfaces, and it looks identical to an empty catalogue.",
170
- expectedUiState: "Rows on the default view, and each filter returning a subset rather than nothing. An always-empty filter is the finding, named.",
188
+ expectedUiState: "Rows on the default view, and Published returning them. Draft, In Review and Archived are EXPECTED to return nothing — only published stickers are seeded, so an empty result there is correct and is not a finding. What would be a finding is Published returning nothing while the unfiltered list holds rows, which is the always-empty-filter shape. 'zzzznope' returns none.",
189
+ expectedData: { nonsenseResultCount: 0 },
171
190
  endResult: "Read-only.",
172
191
  },
173
192
  "checklist-admin-uncovered-admin-pages-admin-deals-renders": {
@@ -175,14 +194,16 @@ export const authored = {
175
194
  startPage: "/admin/deals",
176
195
  steps: [
177
196
  SIGN_IN_ADMIN,
178
- "Open /admin/deals.",
179
- "Read what the page lists.",
180
- "Open one row.",
181
- "Read what the detail shows.",
197
+ "Open /admin/deals and read its title — it lists promoted products.",
198
+ "Read the rows listed.",
199
+ "Click one row and read the URL it lands on.",
200
+ "Read whether the page it opened shows that product, and whether it is a product editor rather than a deal detail.",
182
201
  "Read the console for errors.",
183
202
  ],
203
+ inputs: { expectedRowDestination: "/admin/products/{id}/edit" },
184
204
  expectedBehaviour: "Rows can be opened. A row offering only mutations, or no way in at all, is a dead end — an admin can act on a record they were never able to read.",
185
- expectedUiState: "Rows render and one opens to a readable detail. A list with no detail affordance is the finding.",
205
+ expectedUiState: "Rows render and one opens — to the product editor at /admin/products/{id}/edit, which is this page's detail affordance rather than a deal-specific view. That is a pass: the record being listed IS a product. A row that does nothing on click is the finding.",
206
+ expectedData: { consoleErrors: 0 },
186
207
  endResult: "Read-only.",
187
208
  },
188
209
  "checklist-admin-uncovered-admin-pages-admin-featured-renders": {
@@ -190,15 +211,19 @@ export const authored = {
190
211
  startPage: "/admin/featured",
191
212
  steps: [
192
213
  SIGN_IN_ADMIN,
193
- "Open /admin/featured and read what it lists.",
194
- "Toggle one record's featured state.",
195
- "Reload the page and read the same record's state.",
196
- "Open the public surface that shows featured content and check it followed.",
197
- "Restore the original state.",
214
+ "Open /admin/featured, read its title and note which products it lists — every row is a product whose featured flag is set.",
215
+ "Write down the name of one of them.",
216
+ "Sign out and open /, find the 'Featured Products' section, and confirm that product is in it.",
217
+ "Sign back in, use the row action that removes that product from featured, and reload /admin/featured to confirm it is gone from the list.",
218
+ "Sign out and reload /, checking the 'Featured Products' section no longer carries it.",
219
+ "Sign back in, open /admin/products, find that product, and use the row menu's 'Quick edit' to turn its Featured toggle back on — this page can only remove, so restoring is done there.",
220
+ "Reload /admin/featured and confirm the product is listed again.",
198
221
  ],
199
- expectedBehaviour: "A featured toggle persists and the public surface follows it. A list endpoint that omits a field the edit form sends back is how a toggle silently resets — saving any other field on the record re-sends a wrong default for the one the list never returned.",
200
- expectedUiState: "The toggled state survives the reload and appears publicly. A state that reverts is the finding.",
201
- endResult: "The original featured state is restored.",
222
+ inputs: { publicSection: "Featured Products", restoreVia: "/admin/products Quick edit" },
223
+ expectedBehaviour: "The featured flag persists and the homepage follows it, in both directions. This page is a FILTERED VIEW of products where `featured == true`, not a toggle board, so removal happens here and restoring happens on /admin/products — a case that expects one control to do both would report a missing affordance that was never there. A list endpoint that omits a field the edit form sends back is how such a flag silently resets: saving any other field on the record re-sends a wrong default for the one the list never returned.",
224
+ expectedUiState: "The removal survives a reload and the homepage's 'Featured Products' section loses the product; re-featuring from /admin/products brings it back to both. A state that reverts on its own is the finding. Note the section shows at most twelve products, so a product that does not appear may simply be beyond the cap — pick one that was visible in step 3 before concluding anything.",
225
+ expectedData: { removedFromPublicSection: true, restoredAfterQuickEdit: true },
226
+ endResult: "The product is featured again and back in the homepage section.",
202
227
  },
203
228
  "checklist-admin-uncovered-admin-pages-admin-features-renders": {
204
229
  roles: ["admin"],
@@ -222,13 +247,13 @@ export const authored = {
222
247
  steps: [
223
248
  SIGN_IN_ADMIN,
224
249
  "Open each of these by URL in turn: /admin/guide, /admin/guide/analytics, /admin/guide/catalog, /admin/guide/content, /admin/guide/orders, /admin/guide/payments, /admin/guide/site, /admin/guide/stores, /admin/guide/team, /admin/guide/trust, /admin/guide/users and /admin/guide/whatsapp.",
225
- "Check every one of them is also linked from the Guide area rather than only reachable by URL.",
250
+ "Check every one of them is also reachable from the admin sidebar's Guides group rather than only by URL — all twelve have a nav entry, so a missing one is a finding.",
226
251
  "Record any that 404 or render empty.",
227
- "On two of them, check the screen they describe still exists and is named the same way.",
228
- "Record any guide describing a screen or a control that is no longer there.",
252
+ "Pick the Site Configuration and Content & Marketing guides and, for each screen or control they name, open it and check it still exists under that name.",
253
+ "Record any guide describing a screen or a control that is no longer there, naming both the guide and the screen.",
229
254
  ],
230
255
  expectedBehaviour: "Every guide page renders and describes something that still exists. A guide is documentation with no compiler behind it, so it rots silently — and a guide naming a deleted screen is worse than none, because it sends an employee looking for something that is gone.",
231
- expectedUiState: "Every guide loads with real content. A 404 is a finding; so is a guide describing a screen that no longer exists, recorded with which guide and which screen.",
256
+ expectedUiState: "Every guide loads with real content and every one is in the sidebar's Guides group. A 404 is a finding; so is a guide describing a screen that no longer exists, recorded with which guide and which screen. The second is the likelier of the two and the reason this case exists — the routes are checked by an audit on every build, the PROSE inside them is checked by nobody.",
232
257
  expectedData: { guidePagesNotFound: 0 },
233
258
  endResult: "Read-only.",
234
259
  },