@mohasinac/appkit 4.27.0 → 4.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +16 -16
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +29 -6
  3. package/dist/_internal/client/features/lottery/LotteryListView.js +13 -1
  4. package/dist/_internal/server/features/checkout/actions.js +6 -4
  5. package/dist/_internal/server/features/classified/metadata.js +3 -1
  6. package/dist/_internal/server/features/digital-code/metadata.js +3 -1
  7. package/dist/_internal/server/features/live/metadata.js +3 -1
  8. package/dist/_internal/server/features/refunds/actions.js +3 -2
  9. package/dist/_internal/server/features/reviews/actions.js +3 -2
  10. package/dist/_internal/server/features/seo/sitemap.js +189 -40
  11. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  12. package/dist/_internal/server/features/stores/adapters.js +5 -0
  13. package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
  14. package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
  15. package/dist/_internal/shared/actions/action-registry.js +14 -0
  16. package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
  17. package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
  18. package/dist/_internal/shared/features/checkout/config.js +6 -3
  19. package/dist/client.d.ts +32 -2
  20. package/dist/client.js +37 -2
  21. package/dist/constants/api-endpoints.d.ts +21 -3
  22. package/dist/constants/api-endpoints.js +14 -1
  23. package/dist/constants/field-names.d.ts +2 -2
  24. package/dist/constants/field-names.js +2 -2
  25. package/dist/core/newsletter.repository.js +14 -1
  26. package/dist/features/about/components/FAQPageView.js +1 -1
  27. package/dist/features/about/components/FeesView.js +1 -1
  28. package/dist/features/account/components/AddressForm.d.ts +2 -0
  29. package/dist/features/account/components/AddressForm.js +5 -1
  30. package/dist/features/account/components/UserBidsView.js +1 -1
  31. package/dist/features/account/components/UserSidebar.js +1 -1
  32. package/dist/features/account/components/UserSupportView.js +24 -2
  33. package/dist/features/account/hooks/useAddresses.d.ts +29 -1
  34. package/dist/features/account/hooks/useAddresses.js +4 -1
  35. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  36. package/dist/features/account/hooks/useProfile.js +1 -1
  37. package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
  38. package/dist/features/addresses/repository/addresses.repository.js +19 -0
  39. package/dist/features/admin/actions/chat-actions.js +5 -1
  40. package/dist/features/admin/components/AdminAdsView.js +1 -1
  41. package/dist/features/admin/components/AdminBidsView.js +6 -1
  42. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
  43. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
  44. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  45. package/dist/features/admin/components/AdminContentGuideView.js +1 -1
  46. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
  47. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
  48. package/dist/features/admin/components/AdminSidebar.js +1 -1
  49. package/dist/features/admin/components/AdminViewCards.js +2 -2
  50. package/dist/features/admin/components/DataTable.js +2 -2
  51. package/dist/features/admin/schemas/firestore.js +9 -3
  52. package/dist/features/auctions/actions/bid-actions.js +45 -4
  53. package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
  54. package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
  55. package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
  56. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  57. package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
  58. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
  59. package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
  60. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  61. package/dist/features/auth/hooks/useAuth.d.ts +23 -1
  62. package/dist/features/auth/hooks/useAuth.js +27 -1
  63. package/dist/features/auth/permissions/constants.d.ts +1 -1
  64. package/dist/features/auth/permissions/constants.js +3 -0
  65. package/dist/features/auth/repository/session.repository.d.ts +16 -1
  66. package/dist/features/auth/repository/session.repository.js +23 -0
  67. package/dist/features/auth/repository/user.repository.js +13 -2
  68. package/dist/features/blog/components/BlogListView.js +2 -2
  69. package/dist/features/blog/repository/blog.repository.d.ts +4 -0
  70. package/dist/features/blog/repository/blog.repository.js +1 -0
  71. package/dist/features/blog/schemas/firestore.d.ts +3 -1
  72. package/dist/features/blog/schemas/firestore.js +1 -0
  73. package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
  74. package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
  75. package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
  76. package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
  77. package/dist/features/cart/components/index.d.ts +1 -1
  78. package/dist/features/cart/components/index.js +1 -1
  79. package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
  80. package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
  81. package/dist/features/cart/utils/line-total.d.ts +25 -0
  82. package/dist/features/cart/utils/line-total.js +26 -0
  83. package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
  84. package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
  85. package/dist/features/categories/components/CategoryTree.js +2 -2
  86. package/dist/features/contact/components/ContactForm.js +1 -1
  87. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
  88. package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
  89. package/dist/features/events/components/AdminEventEditorView.js +16 -9
  90. package/dist/features/events/components/AdminEventEntriesView.js +7 -13
  91. package/dist/features/events/components/EventRafflesSection.js +14 -2
  92. package/dist/features/events/components/EventStatusBadge.js +1 -1
  93. package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
  94. package/dist/features/events/components/event-entry-detail-fields.js +13 -0
  95. package/dist/features/events/repository/events.repository.js +1 -0
  96. package/dist/features/events/schemas/firestore.d.ts +3 -1
  97. package/dist/features/events/schemas/firestore.js +1 -0
  98. package/dist/features/faq/components/ContactCTA.js +1 -1
  99. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  100. package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
  101. package/dist/features/faq/repository/faqs.repository.js +64 -54
  102. package/dist/features/faq/schemas/firestore.d.ts +3 -2
  103. package/dist/features/faq/schemas/firestore.js +2 -2
  104. package/dist/features/faq/schemas/index.d.ts +3 -3
  105. package/dist/features/faq/schemas/index.js +1 -1
  106. package/dist/features/filters/FilterFacetSection.js +2 -2
  107. package/dist/features/forms/Toggle.js +1 -1
  108. package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
  109. package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
  110. package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
  111. package/dist/features/homepage/components/BrandsSection.js +1 -1
  112. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  113. package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
  114. package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
  115. package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
  116. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  117. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  118. package/dist/features/homepage/lib/section-renderer.js +2 -1
  119. package/dist/features/layout/AppLayoutShell.js +5 -5
  120. package/dist/features/layout/BottomActions.js +5 -5
  121. package/dist/features/layout/BottomSheet.js +1 -1
  122. package/dist/features/layout/MainNavbar.js +1 -1
  123. package/dist/features/layout/NavbarLayout.js +1 -1
  124. package/dist/features/layout/TitleBarLayout.js +3 -3
  125. package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
  126. package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
  127. package/dist/features/lottery/schemas/config-write.d.ts +141 -0
  128. package/dist/features/lottery/schemas/config-write.js +139 -0
  129. package/dist/features/media/MediaPickerModal.js +3 -1
  130. package/dist/features/media/MediaVideo.js +4 -2
  131. package/dist/features/media/components/MediaSlider.js +1 -1
  132. package/dist/features/messages/repository/conversations.repository.js +15 -5
  133. package/dist/features/orders/components/OrdersList.js +2 -2
  134. package/dist/features/payments/repository/payout.repository.d.ts +11 -1
  135. package/dist/features/payments/repository/payout.repository.js +14 -6
  136. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
  137. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  138. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  139. package/dist/features/products/columns/productTableColumns.js +3 -3
  140. package/dist/features/products/components/CompareOverlay.js +1 -1
  141. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  142. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  143. package/dist/features/products/components/PrizeDrawsSection.js +5 -1
  144. package/dist/features/products/components/ProductDetailActions.js +11 -7
  145. package/dist/features/products/components/ProductDetailPageView.js +3 -3
  146. package/dist/features/products/components/ProductFeatureBadges.js +2 -2
  147. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  148. package/dist/features/products/components/ProductFilters.js +15 -8
  149. package/dist/features/products/components/ProductGrid.js +8 -17
  150. package/dist/features/products/components/ProductTabs.js +7 -4
  151. package/dist/features/products/components/ProductTabsShell.js +2 -7
  152. package/dist/features/products/constants/action-defs.d.ts +21 -4
  153. package/dist/features/products/constants/action-defs.js +7 -3
  154. package/dist/features/products/hooks/useRelatedProducts.js +2 -1
  155. package/dist/features/products/repository/products.repository.d.ts +1 -1
  156. package/dist/features/products/repository/products.repository.js +38 -16
  157. package/dist/features/products/schemas/firestore.d.ts +3 -2
  158. package/dist/features/products/schemas/firestore.js +1 -1
  159. package/dist/features/reviews/actions/review-actions.d.ts +8 -1
  160. package/dist/features/reviews/actions/review-actions.js +14 -3
  161. package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
  162. package/dist/features/reviews/components/ReviewModal.js +1 -1
  163. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  164. package/dist/features/reviews/repository/reviews.repository.js +54 -13
  165. package/dist/features/reviews/schemas/firestore.d.ts +9 -1
  166. package/dist/features/reviews/schemas/firestore.js +1 -0
  167. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  168. package/dist/features/search/components/Search.js +3 -3
  169. package/dist/features/search/components/SearchResultsSection.js +1 -1
  170. package/dist/features/seller/components/SellerAddressesView.js +3 -1
  171. package/dist/features/seller/components/SellerBidsView.js +6 -1
  172. package/dist/features/seller/components/SellerSidebar.js +1 -1
  173. package/dist/features/shell/SectionForm.d.ts +21 -1
  174. package/dist/features/shell/SectionForm.js +15 -3
  175. package/dist/features/shell/build-sections.js +10 -1
  176. package/dist/features/shell/field-ui-meta.d.ts +21 -0
  177. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
  178. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  179. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
  180. package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
  181. package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
  182. package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
  183. package/dist/features/stores/actions/store-query-actions.js +25 -1
  184. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  185. package/dist/features/stores/components/StoreHeader.js +1 -1
  186. package/dist/features/stores/components/StoreNavTabs.js +20 -8
  187. package/dist/features/stores/repository/store.repository.js +1 -0
  188. package/dist/features/stores/schemas/firestore.d.ts +2 -0
  189. package/dist/features/support/repository/support.repository.d.ts +12 -0
  190. package/dist/features/support/repository/support.repository.js +15 -0
  191. package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
  192. package/dist/features/support/schemas/ticket-create-form.js +82 -0
  193. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +0 -1
  194. package/dist/features/tester/repository/tester-checklist-item.repository.js +6 -15
  195. package/dist/features/tester/schemas/firestore.d.ts +5 -4
  196. package/dist/features/tester/schemas/firestore.js +3 -3
  197. package/dist/features/tester/seed-data/products-tester-seed-data.js +2 -2
  198. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +138 -1
  199. package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
  200. package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
  201. package/dist/features/wishlist/components/index.d.ts +2 -1
  202. package/dist/features/wishlist/components/index.js +1 -1
  203. package/dist/index.d.ts +10 -3
  204. package/dist/index.js +19 -4
  205. package/dist/monitoring/error-tracking.js +39 -5
  206. package/dist/monitoring/server-logger.js +70 -32
  207. package/dist/next/components/ErrorView.js +1 -1
  208. package/dist/next/routing/route-map.d.ts +39 -7
  209. package/dist/next/routing/route-map.js +34 -4
  210. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  211. package/dist/providers/db-firebase/base.repository.js +56 -9
  212. package/dist/react/ErrorBoundary.js +9 -1
  213. package/dist/react/contexts/SessionContext.js +6 -2
  214. package/dist/react/hooks/useAuthGate.d.ts +22 -0
  215. package/dist/react/hooks/useAuthGate.js +86 -7
  216. package/dist/schemas/registry.d.ts +3 -3
  217. package/dist/security/index.d.ts +1 -1
  218. package/dist/security/index.js +1 -1
  219. package/dist/security/pii-encrypt.d.ts +13 -5
  220. package/dist/security/pii-encrypt.js +132 -6
  221. package/dist/security/pii-schemas.d.ts +32 -4
  222. package/dist/security/pii-schemas.js +38 -5
  223. package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
  224. package/dist/seed/_helpers/product-search-txt.js +33 -0
  225. package/dist/seed/_helpers/search-txt-wrappers.d.ts +20 -0
  226. package/dist/seed/_helpers/search-txt-wrappers.js +73 -0
  227. package/dist/seed/blog-posts-seed-data.d.ts +4 -0
  228. package/dist/seed/blog-posts-seed-data.js +7 -1
  229. package/dist/seed/events-seed-data.d.ts +5 -1
  230. package/dist/seed/events-seed-data.js +7 -1
  231. package/dist/seed/faq-seed-data.d.ts +16 -0
  232. package/dist/seed/faq-seed-data.js +22 -1
  233. package/dist/seed/products-art-seed-data.js +49 -7
  234. package/dist/seed/products-auctions-seed-data.js +12 -4
  235. package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
  236. package/dist/seed/products-classifieds-seed-data.js +65 -9
  237. package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
  238. package/dist/seed/products-digital-codes-seed-data.js +65 -9
  239. package/dist/seed/products-live-items-seed-data.d.ts +5 -0
  240. package/dist/seed/products-live-items-seed-data.js +33 -5
  241. package/dist/seed/products-preorders-seed-data.d.ts +1 -1
  242. package/dist/seed/products-preorders-seed-data.js +6 -2
  243. package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
  244. package/dist/seed/products-prize-draws-seed-data.js +57 -8
  245. package/dist/seed/products-standard-seed-data.js +6 -2
  246. package/dist/seed/products-stickers-seed-data.js +49 -7
  247. package/dist/seed/reviews-seed-data.js +2 -1
  248. package/dist/seed/site-settings-seed-data.js +5 -3
  249. package/dist/seed/stores-seed-data.d.ts +4 -0
  250. package/dist/seed/stores-seed-data.js +7 -1
  251. package/dist/seo/json-ld.d.ts +44 -2
  252. package/dist/seo/json-ld.js +57 -11
  253. package/dist/styles.css +368 -90
  254. package/dist/tailwind-utilities.css +1 -1
  255. package/dist/tokens/index.d.ts +1 -1
  256. package/dist/tokens/index.js +1 -1
  257. package/dist/tokens/themes/default-dark.js +6 -0
  258. package/dist/tokens/themes/default-light.js +5 -0
  259. package/dist/tokens/tokens.css +84 -0
  260. package/dist/ui/DataTable.js +1 -1
  261. package/dist/ui/DataTable.style.css +3 -3
  262. package/dist/ui/components/BulkActionBar.style.css +1 -3
  263. package/dist/ui/components/Button.js +15 -6
  264. package/dist/ui/components/Dropdown.style.css +1 -4
  265. package/dist/ui/components/FilterChipGroup.js +1 -1
  266. package/dist/ui/components/FilterDrawer.style.css +1 -1
  267. package/dist/ui/components/Icon.d.ts +53 -0
  268. package/dist/ui/components/Icon.js +41 -0
  269. package/dist/ui/components/IconButton.d.ts +13 -0
  270. package/dist/ui/components/IconButton.js +13 -0
  271. package/dist/ui/components/IconButton.style.css +18 -10
  272. package/dist/ui/components/ListingLayout.style.css +2 -10
  273. package/dist/ui/components/ListingToolbar.js +6 -6
  274. package/dist/ui/components/Modal.js +1 -1
  275. package/dist/ui/components/PaginatedSelect.style.css +3 -3
  276. package/dist/ui/components/Pagination.style.css +1 -6
  277. package/dist/ui/components/RichTextEditor.js +1 -1
  278. package/dist/ui/components/RowActionMenu.style.css +1 -1
  279. package/dist/ui/components/SideDrawer.style.css +1 -1
  280. package/dist/ui/components/Slider.js +1 -1
  281. package/dist/ui/components/TabBarShell.d.ts +78 -0
  282. package/dist/ui/components/TabBarShell.js +95 -0
  283. package/dist/ui/components/Tabs.js +12 -8
  284. package/dist/ui/components/Tabs.style.css +249 -41
  285. package/dist/ui/components/ViewToggle.style.css +1 -5
  286. package/dist/ui/components/tabs-overflow.d.ts +17 -0
  287. package/dist/ui/components/tabs-overflow.js +142 -0
  288. package/dist/ui/icons/icon-registry.d.ts +38 -0
  289. package/dist/ui/icons/icon-registry.js +37 -0
  290. package/dist/ui/index.d.ts +6 -0
  291. package/dist/ui/index.js +3 -0
  292. package/dist/utils/index.d.ts +1 -1
  293. package/dist/utils/index.js +1 -1
  294. package/dist/utils/media-url.d.ts +27 -4
  295. package/dist/utils/media-url.js +37 -6
  296. package/dist/utils/search-txt.d.ts +71 -0
  297. package/dist/utils/search-txt.js +118 -0
  298. package/package.json +1 -1
@@ -174,6 +174,29 @@ const rawTesterChecklistItems = [
174
174
  { key: "browse-categories", label: "Browsing categories shows relevant products", href: "/categories" },
175
175
  { key: "search", label: "Search returns relevant results", href: "/search" },
176
176
  { key: "filters", label: "Search/listing filters (price, brand, condition) work correctly" },
177
+ {
178
+ key: "homepage-prize-draws-section",
179
+ label: "The homepage shows a \"Prize Draws\" strip with real cards — NOT an empty gap and NOT a \"Something went wrong\" block",
180
+ description: "This section crashed the entire homepage in production (React error 441) until 2026-08-26. Scroll the whole homepage: every strip should render or be cleanly absent. If any single strip is replaced by an error message while the rest of the page is fine, that is the section boundary doing its job — report which strip.",
181
+ href: "/",
182
+ },
183
+ {
184
+ key: "homepage-event-raffles-section",
185
+ label: "If there are active raffle events, the homepage shows a \"Live Raffles & Spin Wheels\" strip",
186
+ description: "This section silently rendered nothing in production — it looked exactly like \"no active raffles\". Cross-check against /events: if an event with a raffle is active there but no strip appears on the homepage, that is a bug.",
187
+ href: "/",
188
+ },
189
+ {
190
+ key: "listing-no-missing-message",
191
+ label: "Open the browser console on /products and confirm there are NO \"MISSING_MESSAGE\" errors",
192
+ description: "Press F12 → Console tab, then load the page. A missing translation key logs 'MISSING_MESSAGE: <key>' and renders the raw key text instead of a readable label. Report the exact key name if you see one.",
193
+ href: "/products",
194
+ },
195
+ {
196
+ key: "product-filter-status-labels",
197
+ label: "In an admin/seller listing's filter drawer, every Status option reads as words (Published / Draft / In Review / Archived) — never a raw key like \"filters.statusInReview\"",
198
+ href: "/products",
199
+ },
177
200
  { key: "pagination", label: "Pagination / infinite scroll works on listing pages" },
178
201
  {
179
202
  key: "compare-custom-fields",
@@ -1486,7 +1509,7 @@ const rawTesterChecklistItems = [
1486
1509
  {
1487
1510
  key: "store-feature-edit-page-exists",
1488
1511
  label: "A store feature badge can be edited on its own page, not only in a drawer",
1489
- description: "ROUTES.STORE.FEATURES_EDIT pointed at /store/features/[id]/edit with no page behind it and no caller anywhere — a dead route key. Open a badge's edit page directly by URL and confirm it loads with the badge's current values.",
1512
+ description: "ROUTES.STORE.FEATURES_EDIT used to point at /store/features/[id]/edit with no page behind it. The page exists now, but nothing links to it yet — so open a badge's edit page directly by URL and confirm it loads with that badge's current values, not an empty form.",
1490
1513
  href: "/store/features",
1491
1514
  },
1492
1515
  {
@@ -1790,6 +1813,12 @@ const rawTesterChecklistItems = [
1790
1813
  pageLabel: "Become a Seller & Store Setup",
1791
1814
  href: "/user/become-seller",
1792
1815
  cases: [
1816
+ {
1817
+ key: "sell-redirect",
1818
+ label: "Visiting /sell redirects to the Become a Seller page — it must NOT show \"Something went wrong\"",
1819
+ description: "In production this returned a 200 carrying an error instead of redirecting, so the whole seller-onboarding entry point was dead while looking healthy to monitoring. Type /sell in the address bar directly; the URL should end up on /user/become-seller.",
1820
+ href: "/sell",
1821
+ },
1793
1822
  { key: "apply-seller", label: "Applying to become a seller works", href: "/user/become-seller" },
1794
1823
  { key: "store-setup", label: "Setting up store name/description/logo works" },
1795
1824
  { key: "store-address", label: "Adding a pickup address for the store works" },
@@ -2688,6 +2717,12 @@ const rawTesterChecklistItems = [
2688
2717
  cases: [
2689
2718
  { key: "carousel-loops", label: "Homepage carousels (Shop by Category, Top Brands, Featured Products, Live Auctions, Reserve Before It Ships, Verified Stores, Tournaments & Events, Collector Reviews) loop back to the first item after reaching the last instead of getting stuck at the end", href: "/" },
2690
2719
  { key: "carousel-no-flicker", label: "Homepage carousel auto-scroll does not flash/flicker when looping back to the first item" },
2720
+ {
2721
+ key: "hero-carousel-video-plays",
2722
+ label: "The hero carousel slide with a video background actually PLAYS the video — not a blank/black panel",
2723
+ description: "Open the browser console (F12 → Console) while the homepage loads. There must be no 'Load of media resource … failed' or 404 for a .mp4. If the slide is blank, note the failing URL from the console's Network tab.",
2724
+ href: "/",
2725
+ },
2691
2726
  { key: "carousel-pause-on-interaction", label: "Homepage carousel auto-scroll pauses while hovering, touching, keyboard-focusing (Tab + arrow keys), or scrolling it, and resumes afterward" },
2692
2727
  { key: "carousel-arrows-work", label: "Homepage carousel prev/next arrow buttons work and wrap around at both ends" },
2693
2728
  { key: "hero-banner-loops", label: "Hero banner at the top of the homepage rotates through all slides and loops back to the first without getting stuck" },
@@ -3690,6 +3725,108 @@ const rawTesterChecklistItems = [
3690
3725
  version: 2,
3691
3726
  previousVersionId: "checklist-admin-bug-hunter-rewards-demo-fixture",
3692
3727
  },
3728
+ ...group("page-wiring", "Page Wiring & Reachability", [
3729
+ {
3730
+ pageKey: "drawer-pages",
3731
+ pageLabel: "Editors that are also pages",
3732
+ href: "/admin/team",
3733
+ cases: [
3734
+ {
3735
+ key: "page-drawer-opens-already-open",
3736
+ label: "Opening an editor page directly shows the panel ALREADY OPEN",
3737
+ description: "Go to /admin/team/new by URL. The invite panel must already be open — not a blank page you have to click into. Close it and you should land back on /admin/team, not on a dead route. Same for /admin/navigation/new and /admin/tester-checklist/new.",
3738
+ },
3739
+ {
3740
+ key: "page-drawer-deep-link-survives-reload",
3741
+ label: "A deep link to an editor survives a reload",
3742
+ description: "Open a team member's edit page, then reload. The panel is still open, on the same record. This is the whole reason these pages exist — a drawer alone cannot be bookmarked, shared with a colleague, or reopened after a crash.",
3743
+ },
3744
+ ],
3745
+ },
3746
+ {
3747
+ pageKey: "detail-pages",
3748
+ pageLabel: "Record detail pages",
3749
+ href: "/admin/bids",
3750
+ cases: [
3751
+ {
3752
+ key: "detail-page-matches-list-modal",
3753
+ label: "A bid's detail PAGE shows the same fields as the list's modal",
3754
+ description: "Open a bid from /admin/bids (a modal opens) and note the fields. Now open /admin/bids/{id}/view directly. The fields must be identical — both are built by one buildBidDetailFields, so any difference means a second copy has appeared and the two will drift.",
3755
+ },
3756
+ {
3757
+ key: "detail-page-hides-identity-per-portal",
3758
+ label: "The buyer's own bid page does NOT reveal other bidders",
3759
+ description: "Open the same bid at /admin/bids/{id}/view and at /user/bids/{id}/view. The buyer view must not show the other bidder's identity. One `viewer` argument decides this, in one place — if the buyer view leaks a name, that argument is being ignored somewhere.",
3760
+ },
3761
+ ],
3762
+ },
3763
+ {
3764
+ pageKey: "data-loss",
3765
+ pageLabel: "Saves that used to destroy data",
3766
+ href: "/admin/lotteries",
3767
+ cases: [
3768
+ {
3769
+ key: "lottery-edit-preserves-bookings",
3770
+ label: "🛑 Editing a lottery does NOT wipe slots people already pulled",
3771
+ description: "On a seeded lottery, pull/book a slot first. Then open the admin lottery editor, change something unrelated (rename a different slot), and save. Reopen: the booked slot must STILL be booked, with the same buyer name and lottery number. Before this wave the editor sent isBooked:false for every slot and the event PATCH let it through, so the first save erased every purchased slot — with a success message and no error anywhere.",
3772
+ },
3773
+ {
3774
+ key: "lottery-booked-slot-cannot-be-deleted",
3775
+ label: "Removing a slot somebody already pulled is refused, by number",
3776
+ description: "In the same editor, try to delete the slot that has been booked. The save must fail and name that slot number. It is a conflict, not a form error — the correct next action is to reopen the pull, not to fix a field.",
3777
+ },
3778
+ {
3779
+ key: "admin-grouped-listing-title-actually-saves",
3780
+ label: "🛑 An admin renaming a grouped listing actually persists it",
3781
+ description: "Edit a grouped listing's title at /admin/grouped-listings/{id}/edit, save, then RELOAD. The new title must still be there. Reloading is the only way to tell: the old admin PATCH accepted productIds only and silently dropped everything else, so it returned a perfectly normal 200 and wrote nothing.",
3782
+ href: "/admin/grouped-listings",
3783
+ },
3784
+ {
3785
+ key: "store-address-landmark-survives-edit",
3786
+ label: "🛑 A store address keeps its landmark through an edit",
3787
+ description: "Create a store pickup address from the /store/addresses drawer WITH a landmark filled in. Now edit that address at /store/addresses/{id}/edit, change anything else, and save. The landmark must still be set. The shared address form had no landmark field, so it sent undefined and dropped it on every edit.",
3788
+ href: "/store/addresses",
3789
+ },
3790
+ ],
3791
+ },
3792
+ {
3793
+ pageKey: "reachability",
3794
+ pageLabel: "Everything built is reachable",
3795
+ href: "/admin",
3796
+ cases: [
3797
+ {
3798
+ key: "lottery-can-be-created-without-seeding",
3799
+ label: "An admin can create a lottery end to end, with no seed script",
3800
+ description: "Create an event of type Lottery from /admin/events/new (the type must be offered — it was missing from the picker entirely), then configure its slots from /admin/lotteries/{id}/edit, then pull a slot as a buyer. Until this wave lottery events could ONLY come from `npm run seed`.",
3801
+ href: "/admin/events/new",
3802
+ },
3803
+ {
3804
+ key: "carousel-can-be-renamed",
3805
+ label: "A named carousel can be renamed after it is created",
3806
+ description: "Open /admin/carousels, pick a carousel, use Edit carousel, change its name and status, save. There was no edit path at all: the group editor was create-only, so a carousel's name was fixed for its whole life.",
3807
+ href: "/admin/carousels",
3808
+ },
3809
+ {
3810
+ key: "grouped-listing-members-editable-from-its-own-page",
3811
+ label: "A grouped listing's MEMBERS can be picked while creating it",
3812
+ description: "Create a group at /store/grouped-listings/new and add products in the same form. Previously productIds was hardcoded to an empty array, so a group could only ever be created empty and filled in from somewhere else. Check the minimum-active-members and cover-image fields are present too — neither had an input anywhere.",
3813
+ href: "/store/grouped-listings/new",
3814
+ },
3815
+ {
3816
+ key: "public-nav-and-footer-resolve",
3817
+ label: "Every header, sidebar-support and footer link opens a real page",
3818
+ description: "Walk the public header nav, the sidebar support links and every footer column. All 55 hrefs must land on a real page. No audit checked ANY of them until this wave — the nav audit only ever looked at the three portal sidebars.",
3819
+ href: "/",
3820
+ },
3821
+ {
3822
+ key: "user-tester-hub-reachable-from-user-sidebar",
3823
+ label: "A tester reaches the Tester Hub from their OWN sidebar",
3824
+ description: "As a user with the tester flag, open /user and find Tester Hub in the sidebar under Testing. The group is injected at runtime and is empty for non-testers — confirm a NON-tester account does not see it.",
3825
+ href: "/user",
3826
+ },
3827
+ ],
3828
+ },
3829
+ ]),
3693
3830
  ];
3694
3831
  const defaultPhases = assignDefaultPhases(rawTesterChecklistItems);
3695
3832
  export const testerChecklistSeedData = rawTesterChecklistItems.map((item, index) => ({
@@ -0,0 +1,55 @@
1
+ import type { MouseEvent } from "react";
2
+ /** The heart renders a <button>, so handlers get the real element type. */
3
+ type HeartMouseEvent = MouseEvent<HTMLButtonElement>;
4
+ /**
5
+ * Where the heart sits, which decides its box — never passed as a raw size.
6
+ *
7
+ * - `card` — the only affordance in its corner of a product card. 44px, the
8
+ * minimum comfortable tap target.
9
+ * - `inline` — sits in a row beside a title or price. 40px.
10
+ * - `detail` — a product detail page's standalone control. 48px.
11
+ */
12
+ declare const PLACEMENT_SIZE: {
13
+ readonly inline: "md";
14
+ readonly card: "touch";
15
+ readonly detail: "lg";
16
+ };
17
+ export type WishlistHeartPlacement = keyof typeof PLACEMENT_SIZE;
18
+ export interface WishlistHeartButtonProps {
19
+ inWishlist: boolean;
20
+ isLoading?: boolean;
21
+ onToggle: (e: HeartMouseEvent) => void | Promise<void>;
22
+ addLabel: string;
23
+ removeLabel: string;
24
+ /** See PLACEMENT_SIZE. */
25
+ size?: WishlistHeartPlacement;
26
+ /**
27
+ * `overlay` floats above product imagery and needs its own backdrop;
28
+ * `plain` sits on an ordinary surface.
29
+ */
30
+ variant?: "overlay" | "plain";
31
+ className?: string;
32
+ }
33
+ /**
34
+ * The ONE wishlist heart.
35
+ *
36
+ * WHY: there were seven render sites using THREE different glyph technologies —
37
+ * an inline `<svg>` (product grid card), lucide `<Heart>` at two different sizes
38
+ * (auction card list vs grid), and the text characters `♥`/`♡` (product list
39
+ * row, both pre-order card layouts, and baked into the detail page's label
40
+ * strings). Text glyphs cannot be sized by `w-*`/`h-*` at all and fall back to a
41
+ * platform font, which is literally the "the wishlist icon is very small"
42
+ * report. The auction card's lucide heart was 14px inside a `md` Button whose
43
+ * min-height is ~44px, so the largest tap target carried the smallest glyph.
44
+ *
45
+ * Built on `IconButton`, not `Button`: `.appkit-button--sm` sets
46
+ * `min-height: 36px` and, under `important: true`, beats a caller's `h-8`, so
47
+ * the product card's heart rendered as a 32x36 ellipse under `rounded-full`.
48
+ * `IconButton` boxes are true squares.
49
+ *
50
+ * 🛑 It owns NO auth modal. `useAuthGate` is the single gate (Rule #7) and the
51
+ * caller wraps `onToggle` in `requireAuth`. The predecessor this replaces kept
52
+ * its own `LoginRequiredModal` and would have raced the gate's.
53
+ */
54
+ export declare function WishlistHeartButton({ inWishlist, isLoading, onToggle, addLabel, removeLabel, size, variant, className, }: WishlistHeartButtonProps): import("react").JSX.Element;
55
+ export {};
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { IconButton, Icon } from "../../../ui";
4
+ import { ICON_BUTTON_ICON_SIZE } from "../../../ui/components/IconButton";
5
+ /**
6
+ * Where the heart sits, which decides its box — never passed as a raw size.
7
+ *
8
+ * - `card` — the only affordance in its corner of a product card. 44px, the
9
+ * minimum comfortable tap target.
10
+ * - `inline` — sits in a row beside a title or price. 40px.
11
+ * - `detail` — a product detail page's standalone control. 48px.
12
+ */
13
+ const PLACEMENT_SIZE = {
14
+ inline: "md",
15
+ card: "touch",
16
+ detail: "lg",
17
+ };
18
+ /**
19
+ * The ONE wishlist heart.
20
+ *
21
+ * WHY: there were seven render sites using THREE different glyph technologies —
22
+ * an inline `<svg>` (product grid card), lucide `<Heart>` at two different sizes
23
+ * (auction card list vs grid), and the text characters `♥`/`♡` (product list
24
+ * row, both pre-order card layouts, and baked into the detail page's label
25
+ * strings). Text glyphs cannot be sized by `w-*`/`h-*` at all and fall back to a
26
+ * platform font, which is literally the "the wishlist icon is very small"
27
+ * report. The auction card's lucide heart was 14px inside a `md` Button whose
28
+ * min-height is ~44px, so the largest tap target carried the smallest glyph.
29
+ *
30
+ * Built on `IconButton`, not `Button`: `.appkit-button--sm` sets
31
+ * `min-height: 36px` and, under `important: true`, beats a caller's `h-8`, so
32
+ * the product card's heart rendered as a 32x36 ellipse under `rounded-full`.
33
+ * `IconButton` boxes are true squares.
34
+ *
35
+ * 🛑 It owns NO auth modal. `useAuthGate` is the single gate (Rule #7) and the
36
+ * caller wraps `onToggle` in `requireAuth`. The predecessor this replaces kept
37
+ * its own `LoginRequiredModal` and would have raced the gate's.
38
+ */
39
+ export function WishlistHeartButton({ inWishlist, isLoading = false, onToggle, addLabel, removeLabel, size = "inline", variant = "plain", className = "", }) {
40
+ const label = inWishlist ? removeLabel : addLabel;
41
+ const box = PLACEMENT_SIZE[size];
42
+ return (_jsx(IconButton, { type: "button", "aria-label": label, title: label, size: box, variant: "ghost", rounded: "full", disabled: isLoading, "aria-pressed": inWishlist, onClick: (e) => void onToggle(e), className: [
43
+ "transition-colors duration-150",
44
+ variant === "overlay"
45
+ ? "bg-[var(--appkit-color-surface-elevated)]/90 shadow-sm backdrop-blur-sm"
46
+ : "",
47
+ isLoading ? "opacity-50" : "",
48
+ className,
49
+ ]
50
+ .filter(Boolean)
51
+ .join(" "), icon: _jsx(Icon, { name: "wishlist", size: ICON_BUTTON_ICON_SIZE[box], filled: inWishlist, tone: inWishlist ? "error" : "muted" }) }));
52
+ }
@@ -1,5 +1,6 @@
1
1
  export { WishlistCard, WishlistPage } from "./WishlistPage";
2
- export { WishlistToggleButton } from "./WishlistToggleButton";
2
+ export { WishlistHeartButton } from "./WishlistHeartButton";
3
+ export type { WishlistHeartButtonProps, WishlistHeartPlacement } from "./WishlistHeartButton";
3
4
  export { WishlistView } from "./WishlistView";
4
5
  export type { WishlistViewProps, WishlistTab } from "./WishlistView";
5
6
  export { WishlistCapWatcher } from "./WishlistCapWatcher";
@@ -1,4 +1,4 @@
1
1
  export { WishlistCard, WishlistPage } from "./WishlistPage";
2
- export { WishlistToggleButton } from "./WishlistToggleButton";
2
+ export { WishlistHeartButton } from "./WishlistHeartButton";
3
3
  export { WishlistView } from "./WishlistView";
4
4
  export { WishlistCapWatcher } from "./WishlistCapWatcher";
package/dist/index.d.ts CHANGED
@@ -1755,6 +1755,10 @@ export { EVENTS_COLLECTION } from "./features/events/index";
1755
1755
  export { EVENT_ADMIN_SORT_OPTIONS } from "./features/events/index";
1756
1756
  export { EVENT_ENTRIES_COLLECTION } from "./features/events/index";
1757
1757
  export { LOTTERY_ENTRIES_COLLECTION } from "./features/lottery/schemas/firestore";
1758
+ export { supportTicketCreateSchema } from "./features/support/schemas/ticket-create-form";
1759
+ export type { SupportTicketCreateValues } from "./features/support/schemas/ticket-create-form";
1760
+ export { lotteryConfigWriteSchema, lotterySlotWriteSchema, mergeLotteryConfig } from "./features/lottery/schemas/config-write";
1761
+ export type { LotteryConfigWriteInput, LotteryMergeResult } from "./features/lottery/schemas/config-write";
1758
1762
  export { lotteryEntriesSeedData } from "./seed/index";
1759
1763
  export { LOTTERY_ENTRY_FIELDS } from "./features/lottery/schemas/firestore";
1760
1764
  export { EVENT_ENTRY_FIELDS } from "./features/events/index";
@@ -1839,6 +1843,7 @@ export type { EventListResponse } from "./features/events/index";
1839
1843
  export type { EventParticipateViewProps } from "./features/events/index";
1840
1844
  export type { EventStatus } from "./features/events/index";
1841
1845
  export type { EventType } from "./features/events/index";
1846
+ export { ALL_EVENT_TYPES, ALL_EVENT_STATUSES } from "./features/events/index";
1842
1847
  export type { EventUpdateInput } from "./features/events/index";
1843
1848
  export type { FeedbackConfig } from "./features/events/index";
1844
1849
  export type { FormFieldType } from "./features/events/index";
@@ -3018,7 +3023,7 @@ export { sendWhatsAppBusinessMessage, syncProductsToCatalog, buildPurchaseAnnoun
3018
3023
  export type { WaBusinessSendInput, CatalogSyncProduct, CatalogSyncInput, CatalogSyncResult, PurchaseAnnouncementInput } from "./features/whatsapp-bot/index";
3019
3024
  export { WishlistCard } from "./features/wishlist/index";
3020
3025
  export { WishlistPage } from "./features/wishlist/index";
3021
- export { WishlistToggleButton } from "./features/wishlist/index";
3026
+ export { WishlistHeartButton } from "./features/wishlist/index";
3022
3027
  export { WishlistView } from "./features/wishlist/index";
3023
3028
  export { buildWishlistColumns } from "./features/wishlist/index";
3024
3029
  export { useUserWishlist } from "./features/wishlist/index";
@@ -3091,6 +3096,8 @@ export { ToastProvider, StarRating, StepperNav, ViewToggle, PriceDisplay, Summar
3091
3096
  export { CountdownDisplay, ItemRow, HorizontalScroller } from "./ui/index";
3092
3097
  export { CardBody, MenuTrigger, MenuContent, MenuSeparator, RoleBadge, SkipToMain } from "./ui/index";
3093
3098
  export { Tabs, TabsList, TabsTrigger, TabsContent, PasswordStrengthIndicator, TablePagination } from "./ui/index";
3099
+ export { TabBarShell, TabBarButton, TabsNavSelect } from "./ui/index";
3100
+ export type { TabBarShellProps, TabBarButtonProps, TabsNavSelectProps } from "./ui/index";
3094
3101
  export { Stack, classNames, mergeTailwindClasses, SideModal, SideDrawer, RichText, RichTextRenderer } from "./ui/index";
3095
3102
  export type { RichTextRendererProps } from "./ui/index";
3096
3103
  export { SlottedListingView, DetailViewShell, StackedViewShell } from "./ui/index";
@@ -3132,7 +3139,7 @@ export { formatMonthYear } from "./utils/date.formatter";
3132
3139
  export { generateMediaFilename } from "./utils/id-generators";
3133
3140
  export { deriveContextTypeFromFilename } from "./utils/id-generators";
3134
3141
  export type { MediaFilenameContext } from "./utils/id-generators";
3135
- export { resolveMediaUrl, FIREBASE_STORAGE_HOST, GCS_HOST } from "./utils/media-url";
3142
+ export { resolveMediaUrl, resolveVideoUrl, FIREBASE_STORAGE_HOST, GCS_HOST } from "./utils/media-url";
3136
3143
  export { generateFAQId } from "./utils/id-generators";
3137
3144
  export type { GenerateFAQIdInput } from "./utils/id-generators";
3138
3145
  export { generateCroppedImageFilename } from "./utils/id-generators";
@@ -3245,7 +3252,7 @@ export { TABLE_KEYS, VIEW_MODE } from "./constants/table-keys";
3245
3252
  export type { TableKey, ViewMode } from "./constants/table-keys";
3246
3253
  export { SIEVE_OP, SIEVE_PIPE_OPS, sieveFilter, sieveMultiEq, expandSieveParam, sieveAnd, } from "./utils/sieve-builder";
3247
3254
  export type { SieveOp } from "./utils/sieve-builder";
3248
- export { buildSearchTokens, tokenizeQuery } from "./utils/search-tokens";
3255
+ export { buildSearchTxt, parseSearchTxtQuery, matchesAllSearchTerms, normalizeSearchText, type SearchTxtOptions, } from "./utils/search-txt";
3249
3256
  export { ActionPermissionsManager } from "./features/site-settings/components/ActionPermissionsManager";
3250
3257
  export type { ActionPermissionsManagerProps } from "./features/site-settings/components/ActionPermissionsManager";
3251
3258
  export { NavPermissionsManager } from "./features/site-settings/components/NavPermissionsManager";
package/dist/index.js CHANGED
@@ -3313,6 +3313,10 @@ export { EVENT_ADMIN_SORT_OPTIONS } from "./features/events/index";
3313
3313
  export { EVENT_ENTRIES_COLLECTION } from "./features/events/index";
3314
3314
  // LOTTERY_ENTRIES_COLLECTION - New lotteryEntries collection constant.
3315
3315
  export { LOTTERY_ENTRIES_COLLECTION } from "./features/lottery/schemas/firestore";
3316
+ export { supportTicketCreateSchema } from "./features/support/schemas/ticket-create-form";
3317
+ // The lotteryConfig WRITE contract + the booking-preserving merge. The only
3318
+ // sanctioned way to write lotteryConfig — see the schema module's header.
3319
+ export { lotteryConfigWriteSchema, lotterySlotWriteSchema, mergeLotteryConfig } from "./features/lottery/schemas/config-write";
3316
3320
  // lotteryEntriesSeedData - Seed data for the lotteryEntries collection.
3317
3321
  export { lotteryEntriesSeedData } from "./seed/index";
3318
3322
  // [SCHEMA]-Schema / data-shape constant â€" Zod validator, default-value object, or Firestore collection/field name constant.
@@ -3445,6 +3449,11 @@ export { useEventLeaderboard } from "./features/events/index";
3445
3449
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
3446
3450
  // useEvents - React hook for use events.
3447
3451
  export { useEvents } from "./features/events/index";
3452
+ // ALL_EVENT_TYPES / ALL_EVENT_STATUSES - derived from a `Record<Union, true>`,
3453
+ // so a union member missing from either is a COMPILE error rather than a
3454
+ // silently short list. Exported because consumers were hand-writing their own
3455
+ // copies and drifting (Root Cause #61).
3456
+ export { ALL_EVENT_TYPES, ALL_EVENT_STATUSES } from "./features/events/index";
3448
3457
  // ./features/events/server
3449
3458
  // [SERVER-ONLY]-Server-only â€" uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
3450
3459
  // adminGetEventById - Shared export for admin get event by id.
@@ -5387,8 +5396,10 @@ export { WishlistCard } from "./features/wishlist/index";
5387
5396
  // WishlistPage - Component for wishlist page.
5388
5397
  export { WishlistPage } from "./features/wishlist/index";
5389
5398
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
5390
- // WishlistToggleButton - Component for wishlist toggle button.
5391
- export { WishlistToggleButton } from "./features/wishlist/index";
5399
+ // WishlistHeartButton - the single wishlist heart. Replaced
5400
+ // WishlistToggleButton, which was dead code with its own competing
5401
+ // LoginRequiredModal; see the component for the full rationale.
5402
+ export { WishlistHeartButton } from "./features/wishlist/index";
5392
5403
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
5393
5404
  // WishlistView - Component for wishlist view.
5394
5405
  export { WishlistView } from "./features/wishlist/index";
@@ -5487,6 +5498,8 @@ export { ToastProvider, StarRating, StepperNav, ViewToggle, PriceDisplay, Summar
5487
5498
  export { CountdownDisplay, ItemRow, HorizontalScroller } from "./ui/index";
5488
5499
  export { CardBody, MenuTrigger, MenuContent, MenuSeparator, RoleBadge, SkipToMain } from "./ui/index";
5489
5500
  export { Tabs, TabsList, TabsTrigger, TabsContent, PasswordStrengthIndicator, TablePagination } from "./ui/index";
5501
+ // TabBarShell - rail + overflow scroll arrows around a role="tablist"; TabBarButton / TabsNavSelect - its two trigger flavours
5502
+ export { TabBarShell, TabBarButton, TabsNavSelect } from "./ui/index";
5490
5503
  export { Stack, classNames, mergeTailwindClasses, SideModal, SideDrawer, RichText, RichTextRenderer } from "./ui/index";
5491
5504
  export { SlottedListingView, DetailViewShell, StackedViewShell } from "./ui/index";
5492
5505
  export { buildColumns, createColumnBuilder } from "./ui/index";
@@ -5537,7 +5550,7 @@ export { formatCurrency, formatFileSize } from "./utils/number.formatter";
5537
5550
  export { formatMonthYear } from "./utils/date.formatter";
5538
5551
  export { generateMediaFilename } from "./utils/id-generators"; // generateProductImageFilename already exported from "./utils/id-generators";
5539
5552
  export { deriveContextTypeFromFilename } from "./utils/id-generators";
5540
- export { resolveMediaUrl, FIREBASE_STORAGE_HOST, GCS_HOST } from "./utils/media-url";
5553
+ export { resolveMediaUrl, resolveVideoUrl, FIREBASE_STORAGE_HOST, GCS_HOST } from "./utils/media-url";
5541
5554
  export { generateFAQId } from "./utils/id-generators";
5542
5555
  export { generateCroppedImageFilename } from "./utils/id-generators";
5543
5556
  // Messages / Conversations feature schemas
@@ -5655,7 +5668,9 @@ export { SORT_DIR, sortBy } from "./constants/sort";
5655
5668
  export { TABLE_KEYS, VIEW_MODE } from "./constants/table-keys";
5656
5669
  // ── Sieve query builder ───────────────────────────────────────────────────────
5657
5670
  export { SIEVE_OP, SIEVE_PIPE_OPS, sieveFilter, sieveMultiEq, expandSieveParam, sieveAnd, } from "./utils/sieve-builder";
5658
- export { buildSearchTokens, tokenizeQuery } from "./utils/search-tokens";
5671
+ // searchTxt — normalized edge-n-gram search field. Supersedes search-tokens;
5672
+ // see appkit/src/utils/search-txt.ts for why it is an array and not a string.
5673
+ export { buildSearchTxt, parseSearchTxtQuery, matchesAllSearchTerms, normalizeSearchText, } from "./utils/search-txt";
5659
5674
  // [CLIENT-ONLY] — Admin panel components for action/nav permission management.
5660
5675
  export { ActionPermissionsManager } from "./features/site-settings/components/ActionPermissionsManager";
5661
5676
  export { NavPermissionsManager } from "./features/site-settings/components/NavPermissionsManager";
@@ -23,12 +23,46 @@ export var ErrorCategory;
23
23
  ErrorCategory["UNKNOWN"] = "unknown";
24
24
  })(ErrorCategory || (ErrorCategory = {}));
25
25
  const TRACKER_KEY = "__mohasinac_error_tracker__";
26
+ /**
27
+ * Default sink — structured, severity-tagged JSON on the server.
28
+ *
29
+ * `setErrorTracker()` is exported so an app can install Sentry or similar, and
30
+ * its only reference anywhere in this repo was its own doc comment: nothing ever
31
+ * called it. So every tracked error went out as a plain `console.error` string,
32
+ * which Cloud Logging files as unparsed DEFAULT severity — invisible to any
33
+ * severity filter or alert.
34
+ *
35
+ * Emitting `{severity, message, stack}` fixes that with no dependency and no
36
+ * DSN: GCP Error Reporting automatically ingests ERROR-severity log entries
37
+ * that carry a stack trace.
38
+ *
39
+ * The formatting is inlined rather than delegated to `serverLogger` on purpose.
40
+ * This module is re-exported from `client.ts`, and `server-logger` imports
41
+ * `next/server` — importing it here would pull server-only code along every
42
+ * client import chain, which is how Root Cause #24 gets reproduced. Eight lines
43
+ * of duplicated shape is the cheaper trade.
44
+ */
45
+ function defaultTracker(error, category, severity, context) {
46
+ const isError = error instanceof Error;
47
+ const message = isError ? error.message : error;
48
+ // In a browser there is no log collector to parse JSON, and a readable console
49
+ // entry is worth more to whoever is looking at it.
50
+ if (typeof window !== "undefined") {
51
+ console.error(`[${severity.toUpperCase()}][${category}] ${message}`, context);
52
+ return;
53
+ }
54
+ console.error(JSON.stringify({
55
+ severity: "ERROR",
56
+ message,
57
+ category,
58
+ errorSeverity: severity,
59
+ timestamp: new Date().toISOString(),
60
+ ...(isError && error.stack ? { stack: error.stack } : {}),
61
+ ...(context ? { context } : {}),
62
+ }));
63
+ }
26
64
  function getTracker() {
27
- return (globalThis[TRACKER_KEY] ??
28
- ((error, category, severity, context) => {
29
- const msg = error instanceof Error ? error.message : error;
30
- console.error(`[${severity.toUpperCase()}][${category}] ${msg}`, context);
31
- }));
65
+ return globalThis[TRACKER_KEY] ?? defaultTracker;
32
66
  }
33
67
  /** Override the default console-based tracker with a custom implementation. */
34
68
  export function setErrorTracker(fn) {
@@ -18,7 +18,26 @@ function getLogsDir() {
18
18
  }
19
19
  const MAX_LOG_FILE_SIZE = 10 * 1024 * 1024;
20
20
  const MAX_LOG_FILES = 10;
21
- const isFileLoggingEnabled = !process.env.VERCEL;
21
+ /**
22
+ * File logging is OPT-IN, not "anywhere that isn't Vercel".
23
+ *
24
+ * This was `!process.env.VERCEL`, which is `true` inside Cloud Functions — where
25
+ * `VERCEL` is simply unset and the filesystem is read-only. Every log line in
26
+ * every Function therefore ran mkdir/stat/appendFile, failed, and emitted a
27
+ * SECOND line carrying the EROFS stack: every Function log doubled, plus four
28
+ * wasted syscalls per line, against a log buffer that drops at ~4 KB/s
29
+ * (CLAUDE.md Rule #6).
30
+ *
31
+ * Serverless runtimes have no durable disk and their stdout is already
32
+ * collected, so the only place local files help is a developer machine.
33
+ * `LOG_TO_FILE=1` turns it on there; nothing else does.
34
+ */
35
+ const isServerlessRuntime = Boolean(process.env.VERCEL ||
36
+ process.env.FUNCTION_TARGET || // Cloud Functions (2nd gen)
37
+ process.env.K_SERVICE || // Cloud Run / Functions
38
+ process.env.FUNCTIONS_EMULATOR ||
39
+ process.env.AWS_LAMBDA_FUNCTION_NAME);
40
+ const isFileLoggingEnabled = process.env.LOG_TO_FILE === "1" && !isServerlessRuntime;
22
41
  function serializeError(error) {
23
42
  const serialized = {
24
43
  name: error.name,
@@ -136,46 +155,65 @@ async function writeLog(entry) {
136
155
  console.error("Failed to write log:", error);
137
156
  }
138
157
  }
139
- export const serverLogger = {
140
- debug(message, data) {
141
- const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
142
- console.debug(`[DEBUG] ${message}`, sanitized);
143
- },
144
- info(message, data) {
145
- const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
146
- const entry = {
147
- level: "info",
158
+ /**
159
+ * Cloud Logging severity names. GCP parses a JSON stdout line and promotes
160
+ * `severity` into the real log level; a plain `[INFO] msg` string arrives as
161
+ * unparsed `DEFAULT`-severity text, which is what every appkit server log has
162
+ * been doing — invisible to any severity filter or alert.
163
+ */
164
+ const GCP_SEVERITY = {
165
+ debug: "DEBUG",
166
+ info: "INFO",
167
+ warn: "WARNING",
168
+ error: "ERROR",
169
+ };
170
+ /**
171
+ * Emit one line. Structured JSON where a collector is reading stdout
172
+ * (Vercel/Cloud Functions/Cloud Run), human-readable locally.
173
+ */
174
+ function emit(level, message, sanitized) {
175
+ const consoleFn = level === "error" ? console.error : level === "warn" ? console.warn : level === "debug" ? console.debug : console.info;
176
+ if (isServerlessRuntime) {
177
+ consoleFn(JSON.stringify({
178
+ severity: GCP_SEVERITY[level],
148
179
  message,
149
180
  timestamp: new Date().toISOString(),
150
- data: sanitized,
151
- };
152
- console.info(`[INFO] ${message}`, sanitized);
153
- if (isFileLoggingEnabled)
154
- writeLog(entry).catch((err) => { process.stderr.write(String(err) + "\n"); });
155
- },
156
- warn(message, data) {
157
- const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
181
+ ...(sanitized !== undefined ? { data: sanitized } : {}),
182
+ }));
183
+ return;
184
+ }
185
+ consoleFn(`[${level.toUpperCase()}] ${message}`, sanitized);
186
+ }
187
+ function record(level, message, data) {
188
+ const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
189
+ emit(level, message, sanitized);
190
+ if (isFileLoggingEnabled) {
158
191
  const entry = {
159
- level: "warn",
192
+ level,
160
193
  message,
161
194
  timestamp: new Date().toISOString(),
162
195
  data: sanitized,
163
196
  };
164
- console.warn(`[WARN] ${message}`, sanitized);
165
- if (isFileLoggingEnabled)
166
- writeLog(entry).catch((err) => { process.stderr.write(String(err) + "\n"); });
197
+ writeLog(entry).catch((err) => {
198
+ // stderr, not the logger — a logger that logs its own failure through
199
+ // itself recurses. normalizeError is pure classification, so it is safe
200
+ // to call here.
201
+ process.stderr.write(`log write failed: ${normalizeError(err).message}\n`);
202
+ });
203
+ }
204
+ }
205
+ export const serverLogger = {
206
+ debug(message, data) {
207
+ record("debug", message, data);
208
+ },
209
+ info(message, data) {
210
+ record("info", message, data);
211
+ },
212
+ warn(message, data) {
213
+ record("warn", message, data);
167
214
  },
168
215
  error(message, data) {
169
- const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
170
- const entry = {
171
- level: "error",
172
- message,
173
- timestamp: new Date().toISOString(),
174
- data: sanitized,
175
- };
176
- console.error(`[ERROR] ${message}`, sanitized);
177
- if (isFileLoggingEnabled)
178
- writeLog(entry).catch((err) => { process.stderr.write(String(err) + "\n"); });
216
+ record("error", message, data);
179
217
  },
180
218
  };
181
219
  /** Pull the commonly needed headers from a NextRequest in one call. */
@@ -28,5 +28,5 @@ export function ErrorView({ error, reset, heading = "Something went wrong", desc
28
28
  metadata: { digest: error.digest ?? null },
29
29
  });
30
30
  }, [error]);
31
- return (_jsxs(Stack, { justify: "center", role: "alert", className: "min-h-[50vh] text-center", padding: "x-xl", align: "center", children: [_jsx(Heading, { level: 2, className: "mb-2", children: heading }), _jsx(Text, { variant: "secondary", className: "mb-6", children: description }), process.env.NODE_ENV === "development" && error.digest && (_jsxs(Text, { variant: "muted", size: "xs", className: "mb-4", children: ["digest: ", error.digest] })), _jsx(Button, { type: "button", variant: "primary", onClick: reset, children: retryLabel })] }));
31
+ return (_jsxs(Stack, { justify: "center", role: "alert", className: "min-h-[50vh] text-center", padding: "x-xl", align: "center", children: [_jsx(Heading, { level: 2, className: "mb-2", children: heading }), _jsx(Text, { variant: "secondary", className: "mb-6", children: description }), error.digest && (_jsxs(Text, { variant: "muted", size: "xs", className: "mb-4", children: ["Reference: ", error.digest] })), _jsx(Button, { type: "button", variant: "primary", onClick: reset, children: retryLabel })] }));
32
32
  }