@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
@@ -10,6 +10,17 @@
10
10
  * A page with no nav entry and a nav entry with no page are the two halves of one
11
11
  * defect, and both ship as "half done" without anything failing.
12
12
  *
13
+ * 🛑 Two fixtures pinned on 2026-09-29. Exactly ONE named carousel is seeded
14
+ * — carousel-hero-default, 'Homepage Hero', status active — so 'the first
15
+ * carousel' is that one, and its status toggles between exactly two values,
16
+ * active and draft. The 'Edit carousel' control lives on the DETAIL page
17
+ * /admin/carousels/{id}, not on the list, which only links to each row.
18
+ *
19
+ * Verified before asserting: /admin/events/new really does derive its type list
20
+ * from ALL_EVENT_TYPES now, so Lottery is in the picker and the first case
21
+ * should pass. Its own file still carries the note about the local copy that
22
+ * omitted it, which is why lottery events could once only come from the seeder.
23
+ *
13
24
  * @tag domain:tester
14
25
  * @tag layer:seed
15
26
  * @tag pattern:none
@@ -29,7 +40,7 @@ export const authored = {
29
40
  "Open /admin/lotteries and open the new event's slot editor.",
30
41
  "Add three slots numbered 1, 2 and 3, give each a name, and save.",
31
42
  "Sign out and sign in as rehan.sheikh@gmail.com / TempPass123!.",
32
- "Open the new lottery's public page and pull slot 1.",
43
+ "Open /lottery/{the new event id} and pull slot 1.",
33
44
  ],
34
45
  inputs: { eventTitle: "QA Event lottery-created-without-seeding", slotCount: 3, pulledSlot: 1 },
35
46
  expectedBehaviour: "A lottery can be built entirely through the UI. The event and its slots are edited in different places on purpose — the slot editor has no fields for title, dates, status or media, so a lottery created there alone would have no dates, and the draw window is measured from them.",
@@ -42,18 +53,24 @@ export const authored = {
42
53
  startPage: "/admin/carousels",
43
54
  steps: [
44
55
  "Sign in as admin@letitrip.in / TempPass123!.",
45
- "Open /admin/carousels and read the list.",
46
- "Open the first carousel and look for an 'Edit carousel' control.",
47
- "Change its name to 'QA Carousel can-be-renamed' and change its status.",
56
+ "Open /admin/carousels and read the list — one carousel is seeded, 'Homepage Hero', with status active.",
57
+ "Open /admin/carousels/carousel-hero-default and find its 'Edit carousel' control. It is on this detail page, not on the list.",
58
+ "Change the name to 'QA Carousel can-be-renamed' and the status from active to draft.",
48
59
  "Save.",
49
- "RELOAD /admin/carousels and read the name and status.",
50
- "Set both back to their original values.",
60
+ "RELOAD /admin/carousels and read the name and status in the row.",
61
+ "Set the name back to 'Homepage Hero' and the status back to active, then reload once more to confirm.",
51
62
  ],
52
- inputs: { newName: "QA Carousel can-be-renamed" },
63
+ inputs: {
64
+ carousel: "carousel-hero-default",
65
+ originalName: "Homepage Hero",
66
+ newName: "QA Carousel can-be-renamed",
67
+ statusBefore: "active",
68
+ statusAfter: "draft",
69
+ },
53
70
  expectedBehaviour: "A named carousel can be edited after creation. The group editor was create-only, so a carousel's name was fixed for its entire life with no path to change it — and /admin/carousels rendered the flat slide editor rather than a list of named carousels at all.",
54
- expectedUiState: "The list page shows named carousels, not a bare slide editor. 'Edit carousel' exists, opens with the current values, and after saving and reloading the new name and status are shown.",
71
+ expectedUiState: "The list page shows named carousels, not a bare slide editor. 'Edit carousel' exists on the detail page, opens with the current values, and after saving and reloading the list row reads 'QA Carousel can-be-renamed' with status draft. Status has exactly two values here — active and draft — so a picker offering more is itself worth noting.",
55
72
  expectedData: { editControlPresent: true },
56
- endResult: "The name and status are restored by the final step. Other cases read this carousel by its seeded name.",
73
+ endResult: "The name reads 'Homepage Hero' and the status is active again. Other cases read this carousel by that seeded name, and it is the only one there is.",
57
74
  },
58
75
  "checklist-page-wiring-reachability-grouped-listing-members-editable-from-its-own-page": {
59
76
  roles: ["seller"],
@@ -73,7 +90,7 @@ export const authored = {
73
90
  member1: "product-beyblade-burst-valkyrie",
74
91
  member2: "product-beyblade-x-wizard-arrow",
75
92
  minActiveMembers: 2,
76
- coverImage: "public/test-media/sample-image.png",
93
+ coverImage: "/test-media/sample-image.png",
77
94
  },
78
95
  expectedBehaviour: "Members are chosen while the group is being created, in the same form. productIds was hardcoded to an empty array, so a group could only ever be created empty and populated from somewhere else — and neither the minimum-active-members nor the cover-image field had an input anywhere in the app.",
79
96
  expectedUiState: "All three controls are present on the create form. After saving and reloading, the group holds both products, a minimum of 2, and the uploaded cover image. A group that saves with zero members is the failure.",
@@ -84,15 +101,15 @@ export const authored = {
84
101
  roles: ["guest"],
85
102
  startPage: "/",
86
103
  steps: [
87
- "Open / in a private window with no session.",
104
+ "Signed out, open /.",
88
105
  "Click every link in the public header navigation in turn, reading each destination and going back.",
89
106
  "Open the sidebar support links and click each one, reading each destination.",
90
107
  "Scroll to the footer and click every link in every column, reading each destination.",
91
108
  "Write down any link that lands on a not-found page, an empty shell, or back on the homepage.",
92
109
  ],
93
- expectedBehaviour: "Every public-facing link resolves to a real page with content. The nav audit only ever checked the three portal sidebars, so not one of these 55 hrefs was covered by anything — a rename anywhere in the public surface rotted silently.",
94
- expectedUiState: "All 55 destinations render real content. None shows a 404, none renders chrome with an empty body, and none silently redirects back to / — a redirect to the homepage looks like a working link and is not one.",
95
- expectedData: { brokenLinkCount: 0 },
110
+ expectedBehaviour: "Every public-facing link resolves to a real page with content. The nav audit only ever checked the three portal sidebars, so not one of these 62 hrefs was covered by anything — a rename anywhere in the public surface rotted silently.",
111
+ expectedUiState: "All 62 destinations render real content. None shows a 404, none renders chrome with an empty body, and none silently redirects back to / — a redirect to the homepage looks like a working link and is not one.",
112
+ expectedData: { brokenLinkCount: 0, linksChecked: 62 },
96
113
  endResult: "Read-only; nothing persists. Record every broken href in the comment, not just the first — one report naming all of them is worth more than a verdict.",
97
114
  },
98
115
  "checklist-page-wiring-reachability-user-tester-hub-reachable-from-user-sidebar": {
@@ -42,11 +42,12 @@ export const authored = {
42
42
  "Open /auth/forgot-password in a private window with no session.",
43
43
  "Click 'Send reset link' with the field empty and read where the error appears.",
44
44
  "Type not-an-email and click 'Send reset link'.",
45
- "Type divya.funko@gmail.com and click 'Send reset link'.",
46
- "Open that inbox, open the reset link, and read the page it lands on.",
45
+ "Type divya.funko@gmail.com and click 'Send reset link'. Read only the CONFIRMATION here — the generic 'if an account exists' message is the whole point of this step and needs no inbox.",
46
+ "🛑 DO NOT COMPLETE A RESET FOR A SEEDED PERSONA. The harness cannot open divya's inbox — both check-inbox.mjs and the emailEvents recorder key on TESTER_EMAIL_ID — and a reset that half-completes leaves a PRESERVE-tier login changed with nothing to restore it from, breaking every other case that signs in as her. For the link half, register a throwaway account on the harness mailbox using a plus-address and request the reset for THAT.",
47
+ "Capture a timestamp, request the link for the throwaway address, then run: node tester/scripts/check-inbox.mjs --since <that number> --subject \"reset\". Open the link it reports and read the page it lands on.",
47
48
  "Submit the reset form with two different passwords typed into its two fields.",
48
49
  "Set both fields to QaAuthPages654! and submit.",
49
- "Request another link for the same address and use it to set the password back to TempPass123!.",
50
+ "No restore is needed: the password changed belongs to a throwaway account, not to a seeded persona. Note in your comment that the account remains — the purge script matches only qa-signup+…@letitrip-qa.test, so a Gmail plus-address is not swept automatically.",
50
51
  ],
51
52
  inputs: {
52
53
  email: "divya.funko@gmail.com",
@@ -61,8 +62,8 @@ export const authored = {
61
62
  roles: ["guest"],
62
63
  startPage: "/auth/register",
63
64
  steps: [
64
- "Open /auth/register in a private window and create an account with an inbox you can open, using TestPass123! as the password.",
65
- "Open the inbox and find the verification email.",
65
+ "Open /auth/register in a private window and create an account on the HARNESS mailbox using a plus-address — no seeded persona can receive testable email, since both instruments key on TESTER_EMAIL_ID — with TestPass123! as the password.",
66
+ "Capture a timestamp before registering, then find the verification email: node tester/scripts/check-inbox.mjs --since <that number> --subject \"verify\". Read the exit code — 0 arrived, 1 did not (a defect), 2 could not open the mailbox (a harness problem, answer null).",
66
67
  "Open the verification link and read the page it lands on.",
67
68
  "Open /user and look for an unverified-email warning.",
68
69
  "Open the same verification link a second time and read the page.",
@@ -100,7 +101,7 @@ export const authored = {
100
101
  ],
101
102
  inputs: { productId: "product-beyblade-burst-valkyrie", price: 199, paymentMethod: "Cash on Delivery" },
102
103
  expectedBehaviour: "The success page reads the real order rather than whatever the checkout had in memory, so it survives a reload. It is also order-scoped data, so a signed-out visitor with the URL must not be shown someone's delivery address.",
103
- expectedUiState: "The page names an order number, 'Test Gadget — Standard Listing #1' at ₹199.00, the delivery address and a total. After the reload it shows the same. In the private window it does not render the order details — it asks for sign-in or reports not-found.",
104
+ expectedUiState: "The page names an order number, 'Beyblade Burst Valkyrie' at ₹1,899.00 — the product the steps actually add, the delivery address and a total. After the reload it shows the same. In the private window it does not render the order details — it asks for sign-in or reports not-found.",
104
105
  expectedData: { orderTotal: 199 },
105
106
  endResult: "The order exists in /user/orders. A success page that goes blank on reload was rendering from checkout state and not from the order.",
106
107
  },
@@ -25,12 +25,21 @@ export const authored = {
25
25
  "Open /bug-hunters in a private window with no session.",
26
26
  "Read the rows from the top and note each name and confirmed-bug count.",
27
27
  "Check that the counts descend from the first row to the last.",
28
- "Look for 'Mock User 18' and read the count beside it.",
28
+ /*
29
+ * 'Mock User 3' — the demo fixture's bugHunterName, matching
30
+ * bugHunterId "user-yugi-muto". This said 'Mock User 18', and there is no
31
+ * such persona: the seed runs Mock User 1–17 plus two Mock Employees.
32
+ *
33
+ * So the tester looked for a name that cannot be on the board, found
34
+ * nothing, and the case failed for a reason that is not a bug — while
35
+ * the leaderboard's real behaviour went unchecked.
36
+ */
37
+ "Look for 'Mock User 3' and read the count beside it.",
29
38
  "Look for the Claude tester account by name anywhere in the list.",
30
39
  ],
31
- inputs: { seededHunter: "Mock User 18", seededBugCount: 1 },
40
+ inputs: { seededHunter: "Mock User 3", seededBugCount: 1 },
32
41
  expectedBehaviour: "The board ranks testers by confirmed-bug count, highest first, and is fed by the demo fixture under Admin (Testing) → Bug Hunter Rewards. The bot account is excluded because it carries isBot — credit belongs to people, and a runner working the whole checklist would otherwise sit at the top forever.",
33
- expectedUiState: "'Mock User 18' appears with 1 confirmed bug. Counts never increase as you read down the list. The Claude tester account does not appear at all.",
42
+ expectedUiState: "'Mock User 3' appears with 1 confirmed bug. Counts never increase as you read down the list. The Claude tester account does not appear at all.",
34
43
  expectedData: { botAccountOnLeaderboard: 0 },
35
44
  endResult: "Read-only; nothing persists. A bot row on the board is a fail even if the ordering is otherwise correct.",
36
45
  },
@@ -39,7 +48,7 @@ export const authored = {
39
48
  startPage: "/bug-hunters",
40
49
  steps: [
41
50
  "Sign in as admin@letitrip.in / TempPass123!.",
42
- "Open /admin/tester-feedback and find the confirmed bug credited to 'Mock User 18'.",
51
+ "Open /admin/tester-feedback and find the confirmed bug credited to 'Mock User 3'.",
43
52
  "Un-confirm it, so no confirmed bugs remain.",
44
53
  "Open /bug-hunters in a private window with no session.",
45
54
  "Read the page.",
@@ -47,7 +56,7 @@ export const authored = {
47
56
  "Reload /bug-hunters in the private window.",
48
57
  ],
49
58
  expectedBehaviour: "With nothing to rank the page shows a named empty state rather than a blank container or an error. Zero rows is a normal condition for this page on a new install, not a failure.",
50
- expectedUiState: "With no confirmed bugs the page reads 'No confirmed bugs yet' or equivalent, inside the normal site chrome. It is not a blank white page, not an error boundary, and not a heading with nothing under it. After re-confirming, 'Mock User 18' is back.",
59
+ expectedUiState: "With no confirmed bugs the page reads 'No confirmed bugs yet' or equivalent, inside the normal site chrome. It is not a blank white page, not an error boundary, and not a heading with nothing under it. After re-confirming, 'Mock User 3' is back.",
51
60
  expectedData: { emptyStateShown: true },
52
61
  endResult: "The confirmation is restored by the final step, so the leaderboard-loads case above still has its fixture. Leaving it un-confirmed silently breaks that case.",
53
62
  },
@@ -40,7 +40,7 @@ export const authored = {
40
40
  startPage: "/",
41
41
  steps: [
42
42
  "Open / on a device or emulator with WhatsApp installed.",
43
- "Find the 'Join the Community' button and read the URL it points at.",
43
+ "Find the WhatsApp community button — its default label is 'Join our WhatsApp community', not 'Join the Community' — and read the URL it points at.",
44
44
  "Tap it and read what WhatsApp shows.",
45
45
  "Open /contact and find its WhatsApp link.",
46
46
  "Read that URL and compare it with the first.",
@@ -83,14 +83,14 @@ export const authored = {
83
83
  steps: [
84
84
  "Open /about in a private window with no session.",
85
85
  "Scroll to the 'Our Values' section.",
86
- "Count the values listed.",
86
+ "Count the values listed, and read whether each has a second smaller paragraph under its heading.",
87
87
  "Read each one for a second, smaller paragraph beneath its heading.",
88
88
  "Find the 'How we hold ourselves to this →' link and click it.",
89
89
  "Read the address bar.",
90
90
  ],
91
91
  inputs: { expectedValueCount: 6 },
92
92
  expectedBehaviour: "The section carries six values, each with a subtitle paragraph, and links onward to the Ethics page — where the commitments are actually detailed. Values with no onward link are a statement with nothing behind it.",
93
- expectedUiState: "Six values are shown, not three. Each has a second smaller paragraph under its heading rather than a heading alone. The link is present and lands on /ethics.",
93
+ expectedUiState: "Six values are shown, not three, each with a second smaller paragraph under its heading, and the link lands on /ethics. 🛑 EXPECT THREE WITHOUT SUBTITLES ON A FRESHLY-SEEDED ENVIRONMENT, and record that as a CONTENT gap rather than a UI failure: appkit/src/seed/site-settings-seed-data.ts ships three valueItems and none of them carries a `detail` field, so it predates this expansion. aboutContent lives in siteSettings, which is PRESERVE-tier and never reseeded, so production holds whatever an admin last saved and may well show six. Report the count you actually see and which environment you saw it in — the link and the subtitle rendering are separately testable and the link should work either way.",
94
94
  expectedData: { valueCount: 6, ethicsHref: "/ethics" },
95
95
  endResult: "Read-only; nothing persists.",
96
96
  },
@@ -100,7 +100,7 @@ export const authored = {
100
100
  steps: [
101
101
  "Open /how-emi-works signed out and read the whole page.",
102
102
  "Write down the eligibility threshold it states and the worked example it gives.",
103
- "Sign in as rehan.sheikh@gmail.com / TempPass123! and put live-golden-retriever-puppy in the cart.",
103
+ "Switch identity before continuing: this case is roles [guest] and the harness gives a guest batch NO session at all, so copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER first — the MCP reads that file only at context creation. Then sign in as rehan.sheikh@gmail.com / TempPass123! and put live-golden-retriever-puppy in the cart. That fixture is priced at 25,000, comfortably over the seeded EMI minOrderValue of 10,000, so EMI should be offered; a cheaper item would show no EMI for the right reason and read as a failure.",
104
104
  "Reach the payment step and read the instalment figures offered.",
105
105
  "Compare them against the page's worked example.",
106
106
  "Empty the cart, add product-beyblade-burst-valkyrie instead, and check EMI is NOT offered below the threshold.",
@@ -129,9 +129,11 @@ export const authored = {
129
129
  roles: ["buyer", "admin"],
130
130
  startPage: "/scams/report",
131
131
  steps: [
132
+ "START AS THE BUYER. This case lists roles [buyer, admin] and the harness resolves that to the ADMIN session — admin wins whenever it is present — so the batch opens as the wrong person for this first half. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER before continuing; the MCP reads that file only when the context is created.",
132
133
  "Sign in as rehan.sheikh@gmail.com / TempPass123! and submit a report with the description 'QA scam report routing probe — checklist case.'",
134
+ "Switch identity before continuing: copy tester/.tester-runs/session-admin.json over tester/.tester-runs/session.json and CLOSE THE BROWSER first — the MCP server reads that file when the context is created, so copying it over an open browser changes nothing and the next steps run as the previous person.",
133
135
  "Sign in as admin@letitrip.in / TempPass123!.",
134
- "Find that report in an admin surface.",
136
+ "Find that report at /admin/scammers — the scam-report queue — and if it is not there, check /admin/reports before concluding it did not route.",
135
137
  "Open it and read the description and the reporter.",
136
138
  "Check it can be actioned — accepted, rejected or turned into a registry entry.",
137
139
  "Remove the QA report if the interface allows.",
@@ -162,7 +164,7 @@ export const authored = {
162
164
  steps: [
163
165
  "Open /seller-guide signed out and read its content.",
164
166
  "Note whether it reads as marketing for prospective sellers or as a dashboard manual.",
165
- "Sign in as tyson@beybladearena.in / TempPass123! and open /store/guide.",
167
+ "Do NOT sign in — this case is signed out by design and the harness gives a guest batch no session at all. The comparison against the seller-dashboard hub at /store/guide belongs to selling/seller-guide, which runs as the seller; note here only that /seller-guide is a DIFFERENT, public page and not that hub.",
166
168
  "Read that content.",
167
169
  "Compare the two.",
168
170
  "Check neither is a copy of the other.",
@@ -108,13 +108,13 @@ export const authored = {
108
108
  startPage: "/admin/site",
109
109
  steps: [
110
110
  "Open /ethics in a private window and read the first paragraph.",
111
- "Sign in as admin@letitrip.in / TempPass123! in a normal window.",
112
- "Open /admin/site?tab=legal and find the 'Our Ethics' textarea.",
111
+ "Sign in as admin@letitrip.in / TempPass123!. This case lists roles [admin, guest] and resolves to ADMIN, so the batch already opens signed in; the guest reads below can be done in a private window, or by swapping tester/.tester-runs/session-guest.json over session.json and closing the browser between each switch.",
112
+ "Open /admin/site?tab=legal and find the 'Our Ethics' textarea. WRITE DOWN exactly what it contains before typing — the seed ships legalPages.ethics as an empty string, but siteSettings is PRESERVE-tier and never reseeded, so on a long-lived environment an admin may have authored real policy copy there.",
113
113
  "Type <p>QA Ethics policy-admin-html-override</p> into it.",
114
114
  "Save.",
115
115
  "Return to the private window and reload /ethics immediately.",
116
116
  "Read the first paragraph.",
117
- "Go back to the admin textarea, clear it completely, and save again.",
117
+ "Go back to the admin textarea and RESTORE exactly what step 3 recorded — which on a fresh seed means clearing it, since the field ships empty, but on any other environment means putting the original copy back. Clearing a field that held real policy text is not a restore. Save again.",
118
118
  "Reload /ethics in the private window once more.",
119
119
  ],
120
120
  inputs: { override: "<p>QA Ethics policy-admin-html-override</p>" },
@@ -24,7 +24,7 @@ export const authored = {
24
24
  roles: ["guest"],
25
25
  startPage: "/",
26
26
  steps: [
27
- "Open the homepage and scroll to the footer.",
27
+ "Open the homepage and scroll to the footer. Of the six indexes, THREE are redirect shims into the catalogue (/classified, /digital-codes, /live -> /products?listingType=…) and three are real pages (/lottery, /sellers, /brands). A footer link that lands on /products carrying a query has worked; judge each by where it ends up, not by whether the URL changed.",
28
28
  "Read the Shop column and list every link in it.",
29
29
  "Click Classifieds and confirm where it lands.",
30
30
  "Go back and repeat for Digital Codes, Live Items, Lotteries, Brands and Verified Sellers.",
@@ -38,7 +38,7 @@ export const authored = {
38
38
  roles: ["guest"],
39
39
  startPage: "/classified",
40
40
  steps: [
41
- "Open /classified.",
41
+ "Open /classified. 🛑 IT REDIRECTS to /products?listingType=classified, and that IS the wiring this page exists to confirm — the per-type browse indexes were folded into the one catalogue with a type filter, so landing on /products with a query is the design, not a broken link. Everything below is read on that filtered catalogue.",
42
42
  "Read the badge on each of the first six cards.",
43
43
  "Open the first card and read the badge on its detail page.",
44
44
  "Note the URL the card led to.",
@@ -52,7 +52,7 @@ export const authored = {
52
52
  roles: ["guest"],
53
53
  startPage: "/digital-codes",
54
54
  steps: [
55
- "Open /digital-codes.",
55
+ "Open /digital-codes. It redirects to /products?listingType=digital-code — same folding as /classified.",
56
56
  "Read the badge on each of the first six cards.",
57
57
  "Open the first card and confirm the URL it leads to.",
58
58
  "Read whether the detail page states how many codes remain.",
@@ -66,7 +66,7 @@ export const authored = {
66
66
  roles: ["guest"],
67
67
  startPage: "/live",
68
68
  steps: [
69
- "Open /live.",
69
+ "Open /live. It redirects to /products?listingType=live — same folding as /classified.",
70
70
  "Read the badge on every card shown.",
71
71
  "Open the first card and confirm the URL.",
72
72
  "Read whether the detail page shows the species and jurisdiction fields a live item carries.",
@@ -128,7 +128,7 @@ export const authored = {
128
128
  startPage: "/classified",
129
129
  steps: [
130
130
  "Open a private window with no session.",
131
- "Open each of /classified, /digital-codes, /live, /lottery, /brands and /sellers in turn.",
131
+ "Open each of /classified, /digital-codes, /live, /lottery, /brands and /sellers in turn. The first three redirect into /products?listingType=… — expected, and not a sign the gate broke navigation — while the last three are real pages.",
132
132
  "On each, confirm cards render and note what appears where a price would be.",
133
133
  "Open one listing detail page from a price-bearing index.",
134
134
  "Read the price area on that detail page.",
@@ -23,7 +23,7 @@ export const authored = {
23
23
  roles: ["guest"],
24
24
  startPage: "/stores",
25
25
  steps: [
26
- "Open /stores and click through to Beyblade Arena.",
26
+ "Open /stores and click through to Beyblade Arena. The store URL /stores/store-beyblade-arena redirects to /stores/store-beyblade-arena/products — that redirect IS the default-tab behaviour this case is checking, so landing on a /products URL is the pass condition rather than a surprise.",
27
27
  "Note which tab is selected on arrival.",
28
28
  "Count the listings shown and read the total the pager reports.",
29
29
  "Open two of the cards and read which store each says it belongs to.",
@@ -52,9 +52,9 @@ export const authored = {
52
52
  },
53
53
  "checklist-public-pages-store-tabs-empty-tab-says-empty": {
54
54
  roles: ["guest"],
55
- startPage: "/stores/store-beyblade-arena",
55
+ startPage: "/stores/store-letitrip-official",
56
56
  steps: [
57
- "Open Beyblade Arena and work through every tab in turn.",
57
+ "Open /stores/store-letitrip-official and work through all twelve tabs in turn: Products, Auctions, Pre-orders, Prize Draws, Classified, Digital Codes, Live, Art, Bundles, Coupons, Reviews, About. 🛑 USE THIS STORE, NOT Beyblade Arena: the official store carries SIX listings and every one is a prize draw, so eleven of its twelve tabs are genuinely empty. Beyblade Arena has stock in every single type, so it has no empty tab to find and the case cannot be performed there at all.",
58
58
  "Find one that lists nothing.",
59
59
  "Wait for it to finish loading before judging it.",
60
60
  "Read what it shows.",
@@ -85,8 +85,8 @@ export const authored = {
85
85
  roles: ["guest"],
86
86
  startPage: "/stores/store-letitrip-official",
87
87
  steps: [
88
- "Open /stores/store-letitrip-official — a store with a small catalogue, so a leak is obvious.",
89
- "Work through every tab and read the store name on each card.",
88
+ "Open /stores/store-letitrip-official — SIX listings, all of them prize draws, which is what makes a leak obvious: any card on any other tab belongs to someone else, and the Prize Draws tab should hold exactly six.",
89
+ "Work through all twelve tabs and read the store name on each card: Products, Auctions, Pre-orders, Prize Draws, Classified, Digital Codes, Live, Art, Bundles, Coupons, Reviews, About.",
90
90
  "Note any card naming a different store.",
91
91
  "Open one card and confirm on its detail page which store it belongs to.",
92
92
  ],
@@ -180,10 +180,10 @@ export const authored = {
180
180
  },
181
181
  "checklist-public-pages-stores-sellers-directories-store-tab-empty-state-not-error": {
182
182
  roles: ["guest"],
183
- startPage: "/stores/store-vintage-vault-co",
183
+ startPage: "/stores/store-letitrip-official",
184
184
  steps: [
185
- "Open /stores/store-vintage-vault-co in a private window with no session.",
186
- "Open every tab offered and find one that is genuinely empty.",
185
+ "Open /stores/store-letitrip-official in a private window with no session. 🛑 NOT store-vintage-vault-co, which this case used to name: that store is SUSPENDED with isPublic false, and getStoreBySlug returns null unless status is ACTIVE and isPublic is true — so its public page calls notFound(). A tester would meet a 404 and have to judge an empty state on a page that does not exist.",
186
+ "Open every tab offered and find one that is genuinely empty. This store carries SIX listings, all prize draws, so eleven of its twelve tabs are empty and one is not — which gives both halves of the comparison.",
187
187
  "Read exactly what is shown in that empty tab.",
188
188
  "Wait 10 seconds and read it again.",
189
189
  "Check for any error toast anywhere on the page.",
@@ -19,6 +19,38 @@
19
19
  * @tag consumers:seed-data/authored/index.ts
20
20
  * @tag sideEffects:none
21
21
  */
22
+ /*
23
+ * Measured on 2026-09-29 with `node scripts/diff-employee-sidebar.mjs`, which
24
+ * exists for exactly this and computes what every preset sees:
25
+ *
26
+ * total 90 items across 12 groups (the case said 91)
27
+ * blog_poster sees 5
28
+ * trust_and_safety sees 24
29
+ * maintenance_employee sees 77 (the most permissive preset)
30
+ * 20 presets in all, and the SMALLEST is 5
31
+ *
32
+ * The nine groups the first case names as vanishing for the blog employee are
33
+ * exactly the nine the script reports: Finance, Procurement, Catalog, Testing,
34
+ * Site, Events, Trust & Safety, System, Maintenance. That is a strong
35
+ * confirmation, so the case is kept as written apart from the count.
36
+ *
37
+ * 🛑 THE SECOND CASE ASKED FOR 20 SIGN-IN CYCLES to prove something the repo
38
+ * already computes. Setting each preset as admin, signing in as the employee
39
+ * and counting, twenty times over, is most of a session; and the answer is in
40
+ * the script above, where the smallest preset is 5 and no preset reaches zero.
41
+ * It now checks two presets by hand and defers the sweep to the script, which
42
+ * is the difference between a case a tester will run and one they will skip.
43
+ *
44
+ * Two things the script itself reports and neither case mentioned:
45
+ *
46
+ * — 45 distinct preset permissions gate NO sidebar entry at all; they gate
47
+ * routes and actions instead. So a preset can differ from another in
48
+ * permissions and not differ in sidebar count, and identical counts
49
+ * between two presets are not automatically the bug.
50
+ * — An employee with hand-picked `permissions[]` rather than a preset is NOT
51
+ * covered by any of this. That is a real coverage gap, recorded here
52
+ * rather than silently left out.
53
+ */
22
54
  export const authored = {
23
55
  "checklist-search-and-nav-employee-permissions-employee-sees-only-their-permissions": {
24
56
  roles: ["employee", "admin"],
@@ -33,9 +65,9 @@ export const authored = {
33
65
  "Sign out and sign in as admin@letitrip.in / TempPass123!, then open /admin and count the entries.",
34
66
  ],
35
67
  inputs: { blogEmployee: "employee-blog@letitrip.in", trustEmployee: "employee-trust@letitrip.in" },
36
- expectedBehaviour: "Nav filtering keys on each item's own id to look up its required permission. The filter short-circuits when an item has no id, and for a long time no item had one — so all 91 entries passed for everybody and every requiredPermission in the config was decorative. A group left with no visible children is removed entirely rather than rendering an empty heading.",
37
- expectedUiState: "The blog employee sees roughly five entries, not 91. Finance, Procurement, Catalog, Testing, Site, Events, Trust & Safety, System and Maintenance are absent as headings, not present-and-empty. The trust employee sees a different set, roughly 24. The admin sees everything. Three genuinely different counts is the pass; three identical counts is the exact bug.",
38
- expectedData: { blogEntriesApprox: 5, trustEntriesApprox: 24 },
68
+ expectedBehaviour: "Nav filtering keys on each item's own id to look up its required permission. `filterNavItems` opens with `if (!item.id) return true;` and no item in the config had one — so all 90 entries passed for everybody and every requiredPermission was decorative. A group left with no visible children is removed entirely rather than rendering an empty heading.",
69
+ expectedUiState: "The blog employee sees FIVE entries, not 90. Finance, Procurement, Catalog, Testing, Site, Events, Trust & Safety and System and Maintenance are absent as HEADINGS, not present-and-empty. The trust employee sees 24. The admin sees all 90 across 12 groups. Three genuinely different counts is the pass; three identical counts is the exact bug — and 90 for all three is the specific bug this replaced.",
70
+ expectedData: { blogEntries: 5, trustEntries: 24, adminEntries: 90, adminGroups: 12 },
39
71
  endResult: "Read-only; nothing persists. Comparing two presets against each other AND against admin is what distinguishes real filtering from a filter that happens to hide the same fixed set from everyone.",
40
72
  },
41
73
  "checklist-search-and-nav-employee-permissions-employee-sidebar-never-empty": {
@@ -44,16 +76,18 @@ export const authored = {
44
76
  steps: [
45
77
  "Sign in as admin@letitrip.in / TempPass123!.",
46
78
  "Open /admin/team and open the editor for employee-blog@letitrip.in.",
47
- "Read every permission preset the editor offers and write the list down.",
48
- "Set the account to the first preset and save.",
79
+ "Read every permission preset the editor offers and count them — there are 20.",
80
+ "Set the account to blog_poster, the smallest preset, and save.",
49
81
  "Sign out, sign in as employee-blog@letitrip.in / TempPass123!, open /admin and count the sidebar entries.",
50
- "Repeat for every remaining preset: set it as admin, then sign in as the employee and count.",
51
- "Set the account back to the blog poster preset as the last step.",
82
+ "Sign back in as admin, set the account to maintenance_employee, the most permissive preset, and save.",
83
+ "Sign in as the employee again and count the entries.",
84
+ "Sign back in as admin and set the account back to blog_poster as the last step.",
85
+ "For the remaining 18 presets, run `node scripts/diff-employee-sidebar.mjs` rather than signing in 18 more times — it computes every preset's count from the same config the sidebar reads.",
52
86
  ],
53
87
  inputs: { employee: "employee-blog@letitrip.in" },
54
- expectedBehaviour: "No preset produces a completely blank sidebar. At minimum every employee reaches the dashboard itself, so the portal is navigable from the moment they sign in.",
55
- expectedUiState: "For every preset, at least one entry is visible in the sidebar. A blank sidebar is the failure — an employee cannot tell it from a broken account and will report it as one, which is a support ticket the permission system caused rather than prevented.",
56
- expectedData: { minimumVisibleEntries: 1 },
57
- endResult: "The account is back on the blog poster preset, which the case above depends on. Leaving it on another preset silently breaks that case's expected count of roughly five.",
88
+ expectedBehaviour: "No preset produces a completely blank sidebar. At minimum every employee reaches the dashboard itself, so the portal is navigable from the moment they sign in. Measured: the smallest of the 20 presets is blog_poster at 5 entries and the largest is maintenance_employee at 77, so the floor is well clear of zero.",
89
+ expectedUiState: "Both presets checked by hand show entries — 5 for blog_poster and 77 for maintenance_employee — and the script reports no preset below 5. A blank sidebar is the failure: an employee cannot tell it from a broken account and will report it as one, which is a support ticket the permission system caused rather than prevented.",
90
+ expectedData: { blogPosterEntries: 5, maintenanceEmployeeEntries: 77, presetCount: 20 },
91
+ endResult: "The account is back on blog_poster, which the case above depends on. Leaving it on another preset silently breaks that case's expected count of exactly 5.",
58
92
  },
59
93
  };
@@ -18,30 +18,70 @@
18
18
  * @tag consumers:seed-data/authored/index.ts
19
19
  * @tag sideEffects:none
20
20
  */
21
+ /*
22
+ * 🛑 THIS IS THE SIDEBAR SEARCH, NOT THE HEADER SEARCH (corrected 2026-09-29).
23
+ *
24
+ * All four cases said to open / and click into the header search. Two things
25
+ * are wrong with that. The public header's Search is a CATALOGUE search: it
26
+ * carries a resource-type dropdown and navigates to a listing URL with ?q=,
27
+ * so it never returns a settings toggle or an admin screen. And on / there is
28
+ * no admin sidebar at all.
29
+ *
30
+ * The search these cases describe is `useSidebarSearch`, mounted in each
31
+ * portal's sidebar, and its own header states exactly what they assert:
32
+ *
33
+ * "refund found nothing, because the screen is called Payouts. postcode
34
+ * found nothing, because the field is called PIN code. maintenance found
35
+ * nothing, because that toggle lives inside Site Settings."
36
+ *
37
+ * and the ranking rule they turn on:
38
+ *
39
+ * "A label hit beats a keyword hit beats a description hit, so typing
40
+ * orders puts Orders above the guide that merely mentions orders."
41
+ *
42
+ * Groups keep their declared order deliberately, so ranking is WITHIN a group.
43
+ * A case expecting one flat list ranked end to end would misread that.
44
+ *
45
+ * 🛑 AND THERE IS A NAV GROUP TITLED 'Maintenance', with Overview, Server
46
+ * Errors, Client Errors and Function Errors under it. The first case claimed
47
+ * no nav item is called Maintenance and that a label-matching search returns
48
+ * nothing for the word. Both are false, and the corrected case uses that: the
49
+ * query must return BOTH the Maintenance group and the Site Settings entry
50
+ * whose keywords carry 'maintenance mode', which is the harder half.
51
+ *
52
+ * The Maintenance mode toggle really is on the Branding tab, verified at
53
+ * AdminSiteSettingsView's `setting-maintenance-mode` anchor. But that is tab
54
+ * TWO of TWENTY, not one of nineteen.
55
+ *
56
+ * Case 4's filter is by CONSTRUCTION rather than by a permission check — each
57
+ * portal's sidebar is built from its own nav groups, so a buyer's sidebar has
58
+ * no admin items to surface. That is a stronger guarantee than filtering, and
59
+ * the case now says so instead of implying a runtime check it does not make.
60
+ */
21
61
  export const authored = {
22
62
  "checklist-search-and-nav-header-search-search-finds-maintenance-toggle": {
23
63
  roles: ["admin"],
24
- startPage: "/",
64
+ startPage: "/admin",
25
65
  steps: [
26
66
  "Sign in as admin@letitrip.in / TempPass123!.",
27
- "Open / and click into the header search.",
67
+ "Open /admin and click into the sidebar search box.",
28
68
  "Type maintenance.",
29
69
  "Read the results.",
30
- "Click the maintenance quick link.",
31
- "Read which tab of Site Settings is open and what is on screen.",
70
+ "Read whether BOTH the Maintenance nav group and the Site Settings entry are returned, then click the Site Settings entry.",
71
+ "Read which tab of Site Settings opens and whether the Maintenance mode toggle is on screen.",
32
72
  ],
33
73
  inputs: { query: "maintenance" },
34
- expectedBehaviour: "Search reaches individual SETTINGS, not just screens. No nav item is called Maintenance, so a label-matching search returns nothing for this word — and the toggle lives on tab one of nineteen with no way to find it but hunting.",
35
- expectedUiState: "A quick link for maintenance appears in the results. Clicking it opens Site Settings on the Branding tab with the Maintenance mode toggle scrolled into view. Landing on Site Settings' first tab with the toggle somewhere off screen is a FAIL, not a partial pass.",
36
- expectedData: { resultCount: 1 },
74
+ expectedBehaviour: "Search reaches individual SETTINGS, not just screens. There IS a nav group called Maintenance, so the word finds those pages on its label alone — the harder half is the Site Settings entry, which matches only through its keywords, and whose Maintenance mode toggle sits on tab two of twenty with no way to find it but hunting.",
75
+ expectedUiState: "The results include the Maintenance nav group AND the Site Settings entry. Opening Site Settings lands on the Branding tab with the Maintenance mode toggle on screen. Landing on the About tab, which is the first of twenty, with the toggle somewhere below is a FAIL rather than a partial pass. Returning only the Maintenance pages and not Site Settings means keyword matching is not running — that is the finding this case exists for.",
76
+ expectedData: { siteSettingsReturned: true, maintenanceGroupReturned: true },
37
77
  endResult: "Read-only; do not flip the toggle. Turning on maintenance mode would take the site down for every other case in the run.",
38
78
  },
39
79
  "checklist-search-and-nav-header-search-search-finds-by-what-it-does": {
40
80
  roles: ["admin"],
41
- startPage: "/",
81
+ startPage: "/admin",
42
82
  steps: [
43
83
  "Sign in as admin@letitrip.in / TempPass123!.",
44
- "Open / and click into the header search.",
84
+ "Open /admin and click into the sidebar search box.",
45
85
  "Type refund and read the results.",
46
86
  "Clear the box, type postcode and read the results.",
47
87
  "Clear the box, type cod and read the results.",
@@ -55,33 +95,33 @@ export const authored = {
55
95
  },
56
96
  "checklist-search-and-nav-header-search-search-ranks-exact-first": {
57
97
  roles: ["admin"],
58
- startPage: "/",
98
+ startPage: "/admin",
59
99
  steps: [
60
100
  "Sign in as admin@letitrip.in / TempPass123!.",
61
- "Open / and click into the header search.",
101
+ "Open /admin and click into the sidebar search box.",
62
102
  "Type orders.",
63
103
  "Read the results in order from the top.",
64
104
  ],
65
105
  inputs: { query: "orders" },
66
- expectedBehaviour: "An exact label match outranks an entry that merely mentions the word in its description. With no ranking at all the results fall into alphabetical order and the thing actually called Orders can land ninth.",
67
- expectedUiState: "The entry named Orders is first. Guides and settings whose descriptions mention orders appear below it. Orders appearing anywhere but first is the failure, even though it is present.",
106
+ expectedBehaviour: "An exact label match outranks a keyword hit, which outranks a description hit. With no ranking the results fall into declaration order and the thing actually called Orders can land well down the list. Ranking is WITHIN each group — groups keep their declared order on purpose, so that the sidebar does not rearrange itself under the reader while they type.",
107
+ expectedUiState: "Within its own group, the entry named Orders is first. Guides and settings whose descriptions merely mention orders rank below it, inside their own groups. Orders appearing below a description-only match IN THE SAME GROUP is the failure, even though it is present. Do not expect one flat list — groups are not reordered by score.",
68
108
  expectedData: { exactMatchPosition: 1 },
69
109
  endResult: "Read-only; nothing persists.",
70
110
  },
71
111
  "checklist-search-and-nav-header-search-search-never-shows-what-you-cannot-reach": {
72
112
  roles: ["buyer"],
73
- startPage: "/",
113
+ startPage: "/user",
74
114
  steps: [
75
115
  "Sign in as rehan.sheikh@gmail.com / TempPass123!, an ordinary buyer.",
76
- "Open / and click into the header search.",
116
+ "Open /user and click into the sidebar search box — a buyer has a user sidebar, not an admin one.",
77
117
  "Type users and read every result.",
78
118
  "Clear the box, type payouts and read every result.",
79
119
  "Clear the box, type maintenance and read every result.",
80
120
  "Clear the box, type orders and read every result.",
81
121
  ],
82
122
  inputs: { query1: "users", query2: "payouts", query3: "maintenance", query4: "orders" },
83
- expectedBehaviour: "Results are filtered by what this viewer may actually reach. The labels alone are a site map of the admin panel, so an entry a buyer cannot open is one they must not be shown — hiding the destination behind a 403 is not enough.",
84
- expectedUiState: "'users', 'payouts' and 'maintenance' return no admin entries at all — not greyed out, not present-but-unclickable, absent. 'orders' still returns the buyer's OWN orders page, which proves the filter is scoping rather than simply returning nothing.",
123
+ expectedBehaviour: "A buyer can never surface an admin entry, and the reason is stronger than filtering: each portal builds its sidebar from its OWN nav groups, so there are no admin items in a buyer's list to match against. The labels alone would be a site map of the admin panel, so absence by construction is the right answer — hiding the destination behind a 403 would not be.",
124
+ expectedUiState: "'users', 'payouts' and 'maintenance' return no admin entries at all — not greyed out, not present-but-unclickable, absent. 'orders' still returns the buyer's OWN orders page, which is the control: a search returning nothing for everything would pass the first three for the wrong reason.",
85
125
  expectedData: { adminResultsForBuyer: 0 },
86
126
  endResult: "Read-only; nothing persists. The fourth query is the control — a search that returned nothing for everything would pass the first three for the wrong reason.",
87
127
  },