@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.
- package/dist/_internal/server/features/offers/adapters.d.ts +29 -7
- package/dist/_internal/server/features/offers/adapters.js +14 -3
- package/dist/_internal/server/features/orders/adapters.js +54 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.js +1 -0
- package/dist/features/account/components/UserOrdersView.js +30 -2
- package/dist/features/account/hooks/useAddresses.d.ts +21 -6
- package/dist/features/account/hooks/useAddresses.js +1 -1
- package/dist/features/addresses/hooks/usePostalLookup.js +84 -12
- package/dist/features/admin/components/AdminCategoriesView.js +37 -2
- package/dist/features/admin/components/AdminOffersView.js +2 -2
- package/dist/features/admin/constants/filter-tabs.d.ts +25 -0
- package/dist/features/admin/constants/filter-tabs.js +30 -0
- package/dist/features/admin/hooks/useAdminListingData.d.ts +35 -0
- package/dist/features/admin/hooks/useAdminListingData.js +45 -9
- package/dist/features/cart/schemas/index.d.ts +15 -0
- package/dist/features/cart/schemas/index.js +2 -0
- package/dist/features/cart/types/index.d.ts +22 -0
- package/dist/features/categories/components/CategoriesIndexListing.js +19 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +14 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +8 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.js +1 -0
- package/dist/features/events/actions/event-actions.d.ts +13 -0
- package/dist/features/events/actions/event-actions.js +75 -1
- package/dist/features/events/repository/event-entry.repository.d.ts +17 -0
- package/dist/features/events/repository/event-entry.repository.js +36 -0
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +21 -3
- package/dist/features/orders/components/OrdersList.js +2 -1
- package/dist/features/orders/index.d.ts +1 -0
- package/dist/features/orders/index.js +1 -0
- package/dist/features/orders/types/index.d.ts +2 -0
- package/dist/features/orders/utils/order-ref.d.ts +45 -0
- package/dist/features/orders/utils/order-ref.js +58 -0
- package/dist/features/products/components/MakeOfferButton.d.ts +18 -1
- package/dist/features/products/components/MakeOfferButton.js +23 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +5 -0
- package/dist/features/products/components/ProductDetailPageView.js +3 -2
- package/dist/features/products/repository/products.repository.js +29 -2
- package/dist/features/seller/actions/offer-actions.js +32 -0
- package/dist/features/seller/components/QuickProductForm.js +15 -1
- package/dist/features/seller/components/SellerBidsView.js +3 -1
- package/dist/features/seller/components/SellerOffersView.js +2 -2
- package/dist/features/seller/components/SellerOrdersView.js +49 -6
- package/dist/features/seller/components/SellerProductsCards.js +23 -1
- package/dist/features/seller/components/SellerProductsView.js +27 -2
- package/dist/features/seller/hooks/useSellerListingData.d.ts +0 -1
- package/dist/features/seller/hooks/useSellerListingData.js +17 -19
- package/dist/features/shell/SectionForm.js +33 -2
- package/dist/features/tester/seed-data/_happy-path.d.ts +23 -0
- package/dist/features/tester/seed-data/_happy-path.js +710 -0
- package/dist/features/tester/seed-data/_money-flows.js +34 -23
- package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +22 -3
- package/dist/features/tester/seed-data/authored/addresses__address-filters.js +17 -12
- package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +7 -6
- package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +10 -4
- package/dist/features/tester/seed-data/authored/addresses__state-picker.js +13 -8
- package/dist/features/tester/seed-data/authored/addresses__unban-request.js +49 -11
- package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +10 -6
- package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +71 -32
- package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +80 -31
- package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.js +62 -25
- package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +61 -31
- package/dist/features/tester/seed-data/authored/admin__bundles.js +84 -37
- package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +90 -57
- package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +38 -26
- package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +47 -28
- package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +118 -31
- package/dist/features/tester/seed-data/authored/admin__content-deletes.js +30 -24
- package/dist/features/tester/seed-data/authored/admin__content-marketing.js +83 -46
- package/dist/features/tester/seed-data/authored/admin__coupons.js +8 -8
- package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +9 -9
- package/dist/features/tester/seed-data/authored/admin__firebase-function-effects.js +19 -4
- package/dist/features/tester/seed-data/authored/admin__media-watermark.js +77 -33
- package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +5 -5
- package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +29 -5
- package/dist/features/tester/seed-data/authored/admin__site-system.js +45 -23
- package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +53 -28
- package/dist/features/tester/seed-data/authored/admin__users-trust.js +20 -20
- package/dist/features/tester/seed-data/authored/buying__bidding.js +19 -2
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +20 -5
- package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +28 -10
- package/dist/features/tester/seed-data/authored/buying__cart.js +10 -3
- package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +0 -16
- package/dist/features/tester/seed-data/authored/buying__my-orders.js +27 -5
- package/dist/features/tester/seed-data/authored/buying__offers.js +22 -7
- package/dist/features/tester/seed-data/authored/buying__order-detail-actions.js +3 -3
- package/dist/features/tester/seed-data/authored/buying__product-detail.js +35 -4
- package/dist/features/tester/seed-data/authored/buying__return-request.js +19 -6
- package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +36 -18
- package/dist/features/tester/seed-data/authored/buying__reviews.js +16 -1
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +2 -1
- package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +16 -1
- package/dist/features/tester/seed-data/authored/community-support__public-profile.js +3 -2
- package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +4 -4
- package/dist/features/tester/seed-data/authored/content-discovery__blog.js +19 -2
- package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +4 -4
- package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +5 -5
- package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +41 -6
- package/dist/features/tester/seed-data/authored/content-discovery__event-detail-subroutes.js +63 -8
- package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +29 -29
- package/dist/features/tester/seed-data/authored/content-discovery__events.js +2 -2
- package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +23 -1
- package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +6 -6
- package/dist/features/tester/seed-data/authored/content-discovery__search.js +1 -1
- package/dist/features/tester/seed-data/authored/content-discovery__store-reviews-aggregate.js +1 -1
- package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +31 -6
- package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +34 -10
- package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -8
- package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +31 -9
- package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +29 -3
- package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +1 -1
- package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +10 -10
- package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +3 -3
- package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__general-design.js +4 -4
- package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +1 -1
- package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +8 -8
- package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +2 -2
- package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +39 -16
- package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +43 -8
- package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +40 -14
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +31 -14
- package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +7 -6
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +14 -5
- package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +3 -3
- package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +1 -1
- package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +4 -2
- package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +3 -3
- package/dist/features/tester/seed-data/authored/public-pages__newly-wired-browse-indexes.js +5 -5
- package/dist/features/tester/seed-data/authored/public-pages__store-tabs.js +5 -5
- package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +3 -3
- package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +45 -11
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -17
- package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +36 -5
- package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +29 -4
- package/dist/features/tester/seed-data/authored/selling__become-seller.js +23 -0
- package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +14 -13
- package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +14 -9
- package/dist/features/tester/seed-data/authored/selling__listing-edit-roundtrip.js +34 -7
- package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +17 -7
- package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +9 -9
- package/dist/features/tester/seed-data/authored/selling__media-limits.js +9 -4
- package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +3 -3
- package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +3 -3
- package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +25 -1
- package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +27 -6
- package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +19 -1
- package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +63 -22
- package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +9 -5
- package/dist/features/tester/seed-data/authored/selling__seller-guide.js +7 -6
- package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +3 -2
- package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +18 -18
- package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +13 -12
- package/dist/features/tester/seed-data/authored/selling__seller-orders-at-scale.js +19 -3
- package/dist/features/tester/seed-data/authored/selling__seller-orders.js +22 -4
- package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +20 -8
- package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +4 -4
- package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +2 -2
- package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +5 -5
- package/dist/features/tester/seed-data/authored/seo__og-images.js +1 -1
- package/dist/features/tester/seed-data/authored/seo__page-metadata.js +7 -7
- package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +3 -3
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +123 -21
- package/dist/features/tester/utils/phases.d.ts +17 -0
- package/dist/features/tester/utils/phases.js +29 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/seo/json-ld.d.ts +23 -0
- package/dist/seo/json-ld.js +3 -2
- package/dist/server.d.ts +1 -0
- package/dist/server.js +2 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* @tag sideEffects:none
|
|
19
19
|
*/
|
|
20
20
|
import { assignDefaultPhases } from "../utils/phases";
|
|
21
|
+
import { happyPathPages } from "./_happy-path";
|
|
21
22
|
import { moneyFlowsPages } from "./_money-flows";
|
|
22
23
|
import { AUTHORED_CASES } from "./authored";
|
|
23
24
|
const ADMIN_TESTING_GROUP_LABEL = "Admin (Testing)";
|
|
@@ -100,6 +101,18 @@ function group(groupKey, groupLabel, pages, opts) {
|
|
|
100
101
|
* group and is not globally unique across groups.
|
|
101
102
|
*/
|
|
102
103
|
const rawTesterChecklistItems = [
|
|
104
|
+
/*
|
|
105
|
+
* ๐ FIRST ON PURPOSE. The happy path is the core commerce flow โ browse a
|
|
106
|
+
* category, reach a brand and a store, open a standard product, buy it, pay
|
|
107
|
+
* for it, watch the seller fulfil and the admin verify.
|
|
108
|
+
*
|
|
109
|
+
* It leads the catalogue because a run that is interrupted, rate-limited or
|
|
110
|
+
* abandoned half way should have covered "can anyone buy anything" before it
|
|
111
|
+
* covered SEO metadata. Ordering is enforced by HAPPY_PATH_FIRST in
|
|
112
|
+
* ../utils/phases, not by this position โ array position alone would be
|
|
113
|
+
* silently undone by the round-robin phase assignment below.
|
|
114
|
+
*/
|
|
115
|
+
...group("happy-path", "Happy Path (core flows)", happyPathPages),
|
|
103
116
|
...group("account-auth", "Account & Auth", [
|
|
104
117
|
{
|
|
105
118
|
pageKey: "signup-login",
|
|
@@ -313,9 +326,21 @@ const rawTesterChecklistItems = [
|
|
|
313
326
|
href: "/products",
|
|
314
327
|
},
|
|
315
328
|
{
|
|
329
|
+
/*
|
|
330
|
+
* ๐ The KEY still says "toggle" and must stay that way: it is the
|
|
331
|
+
* document id (`${testerId}__${checklistItemId}`), so renaming it
|
|
332
|
+
* orphans every recorded response. The LABEL and DESCRIPTION are not
|
|
333
|
+
* part of the id and were still describing the removed control.
|
|
334
|
+
*
|
|
335
|
+
* The showSold/showEnded/showClosed pills were replaced by the
|
|
336
|
+
* three-tab availability bar, and the three fixtures named here
|
|
337
|
+
* ("Test Collectible โ Sold Out" et al) went with the tester-sandbox
|
|
338
|
+
* catalogue. A tester reads the label FIRST, so a stale one sends them
|
|
339
|
+
* hunting before the steps ever get a chance to correct them.
|
|
340
|
+
*/
|
|
316
341
|
key: "show-sold-toggle-reveals-items",
|
|
317
|
-
label: "The
|
|
318
|
-
description: "Verify against the seeded fixtures: \"
|
|
342
|
+
label: "The availability tab bar hides sold-out, ended and closed items by default, and the middle tab reveals them",
|
|
343
|
+
description: "Verify against the seeded fixtures: \"Beyblade X BX-34 Dran Buster (Sold Out)\" on /products, \"Beyblade Burst B-128 Spriggan Requiem (Ended โ Bought Out)\" on /auctions, and \"Metal Fight Mystery Draw โ Ended (Revealed)\" on /prize-draws. Each must be genuinely absent on 'Available' and present on the middle tab โ not merely missing because an unrelated query broke. Only the middle label varies by type: 'Sold & Ended' on /products, 'Ended' on both /auctions and /prize-draws.",
|
|
319
344
|
href: "/products",
|
|
320
345
|
},
|
|
321
346
|
{
|
|
@@ -621,12 +646,23 @@ const rawTesterChecklistItems = [
|
|
|
621
646
|
label: "Prize-draw collage tiles stack the image ABOVE the title/value caption at full card width โ the image is never squashed beside the text at half width",
|
|
622
647
|
href: "/prize-draws/prizedraw-beyblade-mystery-box",
|
|
623
648
|
},
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
649
|
+
/*
|
|
650
|
+
* REMOVED 2026-09-28 โ concern-card-icon-above-label.
|
|
651
|
+
*
|
|
652
|
+
* `ConcernCard` is rendered only by `ConcernGrid`, and `ConcernGrid` has
|
|
653
|
+
* NO consumer anywhere in src/ or appkit/src/. Both are exported from
|
|
654
|
+
* appkit's public index, which is why they look alive from a grep of the
|
|
655
|
+
* barrel. Nothing puts them on a page.
|
|
656
|
+
*
|
|
657
|
+
* The case's href said /categories and its overlay startPage said /, and
|
|
658
|
+
* the component renders on neither โ so it could only ever be answered
|
|
659
|
+
* "could not test", forever, on every run. A permanently un-passable case
|
|
660
|
+
* is worse than no case: it occupies a batch slot, and a `null` verdict
|
|
661
|
+
* reads as a coverage gap rather than as a catalogue defect.
|
|
662
|
+
*
|
|
663
|
+
* The dead component itself is recorded in docs/TEST-RUN-3-OUTOFSCOPE.md.
|
|
664
|
+
* Restore this case if ConcernGrid is ever mounted.
|
|
665
|
+
*/
|
|
630
666
|
{
|
|
631
667
|
key: "media-picker-existing-grid",
|
|
632
668
|
label: "In any media field, \"Choose existing\" shows each file as a square thumbnail with the filename underneath โ not thumbnail-beside-filename with a shrunken image",
|
|
@@ -2625,6 +2661,12 @@ const rawTesterChecklistItems = [
|
|
|
2625
2661
|
description: "Proves the category actually persisted to Firestore rather than only existing in the form's local state.",
|
|
2626
2662
|
href: "/store/products/new",
|
|
2627
2663
|
},
|
|
2664
|
+
{
|
|
2665
|
+
key: "deep-category-chain-derived",
|
|
2666
|
+
label: "A product filed against a DEEP category is written with its full ancestor chain, so it appears on every ancestor's page and not just the leaf's",
|
|
2667
|
+
description: "BEFORE: the chain was maintained by hand in the seed, so a form-created listing got a one-element array and was invisible on every ancestor page. AFTER (2026-09-14): ProductRepository.deriveTaxonomy() derives it on create and update. Latent by construction โ every seeded product already has the chain, so only a listing created through the UI can catch a regression.",
|
|
2668
|
+
href: "/store/products/new",
|
|
2669
|
+
},
|
|
2628
2670
|
{
|
|
2629
2671
|
key: "seller-category-inline-create-duplicate-rejected",
|
|
2630
2672
|
label: "Creating a category with a name that already exists shows a readable \"already exists\" error, not a generic failure or a silent no-op",
|
|
@@ -3004,6 +3046,18 @@ const rawTesterChecklistItems = [
|
|
|
3004
3046
|
pageLabel: "Event detail โ participate, leaderboard, winner, spin results",
|
|
3005
3047
|
href: "/events",
|
|
3006
3048
|
cases: [
|
|
3049
|
+
{
|
|
3050
|
+
key: "spin-results-subroute",
|
|
3051
|
+
label: "The public 'Last 10 Spin Results' feed masks every participant's display name",
|
|
3052
|
+
description: "Re-scoped 2026-09-29. This case used to assert the route listed 'this account's own spins'; it does not and never did โ getSpinResultsCached takes only the event id and returns the ten most recent spins across ALL users, so a buyer who had never spun was shown three strangers' prizes with no empty state. The page now has two sections and this case covers the PUBLIC one. What it checks is the masking: a public event page was publishing real winners' full display names beside the prize each won, which is the same shape as the bidder-name leak on public bid history (Root Cause #50). The account-scoped claim moved to my-spins-scoped-to-viewer.",
|
|
3053
|
+
href: "/events/event-daily-beyblade-pull-wheel/spin-results",
|
|
3054
|
+
},
|
|
3055
|
+
{
|
|
3056
|
+
key: "my-spins-scoped-to-viewer",
|
|
3057
|
+
label: "'Your Spins' shows only the signed-in caller's own spins, with an empty state about YOU",
|
|
3058
|
+
description: "The account-scoped half of the old spin-results case, now a section of its own above the public feed. The bug it guards against is specific: when the viewer-scoped block did not exist, someone who had never spun saw the public feed's rows and had no way to tell that none of them were theirs. So the empty state must name the viewer ('You have not spun on this event yet.') rather than the event ('No spins yet'), which would be false while the feed below is full. Signed out it must prompt to sign in, never show anyone else's results.",
|
|
3059
|
+
href: "/events/event-daily-beyblade-pull-wheel/spin-results",
|
|
3060
|
+
},
|
|
3007
3061
|
{ key: "detail-tabs-match-event-type", label: "An event's tabs match its TYPE โ a poll shows no spin results, a sale shows no leaderboard" },
|
|
3008
3062
|
{ key: "participate-records-an-entry", label: "Participating records an entry that is still there after a reload" },
|
|
3009
3063
|
{ key: "participate-twice-is-refused", label: "Participating a second time is refused with a reason, not silently duplicated" },
|
|
@@ -3058,6 +3112,12 @@ const rawTesterChecklistItems = [
|
|
|
3058
3112
|
pageLabel: "Blog",
|
|
3059
3113
|
href: "/blog",
|
|
3060
3114
|
cases: [
|
|
3115
|
+
{
|
|
3116
|
+
key: "blog-search-filters-the-list",
|
|
3117
|
+
label: "The blog listing's search box actually filters, and the filtered result survives a reload",
|
|
3118
|
+
description: "BEFORE: BlogIndexPageView never read q while /api/blog did, and listPublished had no search parameter at all -- so the SSR first paint was the unfiltered list, and a public listing hook sets staleTime Infinity once given SSR data, freezing it. AFTER: listPublished takes the same push-down. Needs a nonsense control: a real term returns plausible cards either way.",
|
|
3119
|
+
href: "/blog",
|
|
3120
|
+
},
|
|
3061
3121
|
{ key: "read-post", label: "Reading a blog post renders correctly (images, formatting)" },
|
|
3062
3122
|
{ key: "blog-listing", label: "Blog listing page shows all published posts" },
|
|
3063
3123
|
{ key: "blog-cover-image-display", label: "A blog post's cover image displays on both its listing card and its detail page" },
|
|
@@ -3218,6 +3278,12 @@ const rawTesterChecklistItems = [
|
|
|
3218
3278
|
pageLabel: "FAQ & Help",
|
|
3219
3279
|
href: "/faqs",
|
|
3220
3280
|
cases: [
|
|
3281
|
+
{
|
|
3282
|
+
key: "faq-search-filters-and-survives-reload",
|
|
3283
|
+
label: "The FAQ search box filters the questions, the term round-trips through ?q=, and a nonsense term shows an empty state rather than the full list",
|
|
3284
|
+
description: "No case covered /faqs search, on the one page whose documented near-miss WAS search appearing empty -- it renders zero questions at 2.5s and 62 at 6s, so a count taken early is confidently wrong. Needs both a nonsense control and an explicit wait.",
|
|
3285
|
+
href: "/faqs",
|
|
3286
|
+
},
|
|
3221
3287
|
{ key: "faq-bottom-borders", label: "FAQ question rows show a clear bottom-border divider on the homepage and the FAQs page", href: "/faqs" },
|
|
3222
3288
|
{ key: "faq-mobile-count", label: "Homepage FAQ section shows a good number of questions on mobile, not just 1-2", href: "/" },
|
|
3223
3289
|
{ key: "tabs-mobile-dropdown", label: "Tabs on category/brand/product/event detail pages collapse into a colored dropdown on mobile once there are more than 5 tabs" },
|
|
@@ -3507,11 +3573,11 @@ const rawTesterChecklistItems = [
|
|
|
3507
3573
|
roles: ["guest", "admin"],
|
|
3508
3574
|
startPage: "/contact",
|
|
3509
3575
|
steps: [
|
|
3510
|
-
"Open the public contact page as a signed-out visitor.",
|
|
3576
|
+
"Open the public contact page as a signed-out visitor. ๐ THIS CASE LISTS roles [guest, admin] AND THE HARNESS RESOLVES THAT TO ADMIN โ admin wins whenever present โ so the batch opens SIGNED IN. Copy tester/.tester-runs/session-guest.json (an empty storage state, by design) over session.json and CLOSE THE BROWSER first; the MCP reads it only at context creation.",
|
|
3511
3577
|
"Fill in name, email, subject and a message of at least 10 characters.",
|
|
3512
3578
|
"Submit the form.",
|
|
3513
|
-
"
|
|
3514
|
-
"
|
|
3579
|
+
"Swap to tester/.tester-runs/session-admin.json, closing the browser again, then open /admin/contact.",
|
|
3580
|
+
"Prove NO email was sent: node tester/scripts/check-inbox.mjs --since <a timestamp captured before the submission> --subject \"contact\" --expect-none. There is no support address to open โ EMAIL_SUPPORT was never configured in any runtime, which is half of why this path was removed โ so 'check the inbox' had nowhere to go. --expect-none waits the full window and succeeds only on silence; without it, nothing-arrived is unprovable.",
|
|
3515
3581
|
],
|
|
3516
3582
|
inputs: {
|
|
3517
3583
|
name: "Tester Probe",
|
|
@@ -3539,6 +3605,8 @@ const rawTesterChecklistItems = [
|
|
|
3539
3605
|
expectedUiState: "The form shows a failure message, not the success confirmation.",
|
|
3540
3606
|
endResult: "Nothing appears in /admin/contact for that attempt, and the visitor knows to try again.",
|
|
3541
3607
|
href: "/admin/contact",
|
|
3608
|
+
requiresHumanChannel: true,
|
|
3609
|
+
humanChannelReason: "Needs the contactSubmissions write forced to fail โ a Firestore rules change or a deploy-time fault injection. A browser cannot cause it, so a tester answers null and says so rather than spending turns trying. Kept in the catalogue because it guards the sharpest edge in the change: with the email gone, that write is the ONLY record a customer wrote in, and a save that fails while the form still says thank you loses the message silently.",
|
|
3542
3610
|
},
|
|
3543
3611
|
{
|
|
3544
3612
|
key: "contact-digest-mailto-reply",
|
|
@@ -3548,7 +3616,8 @@ const rawTesterChecklistItems = [
|
|
|
3548
3616
|
startPage: "/admin/contact",
|
|
3549
3617
|
steps: [
|
|
3550
3618
|
"Submit a contact message from the public form.",
|
|
3551
|
-
|
|
3619
|
+
// audit-hardcoded-api-routes-ok: no button exists; the tester pastes this URL into a console by hand.
|
|
3620
|
+
"As admin, trigger the daily digest: run fetch('/api/admin/daily-digest/trigger',{method:'POST'}) from the browser console. There is no button for it โ Site Settings only carries a toggle to enable the schedule โ and this is the same instruction the other digest case uses.",
|
|
3552
3621
|
"Open the digest and find the message under Contact messages.",
|
|
3553
3622
|
"Click its Reply link.",
|
|
3554
3623
|
],
|
|
@@ -4902,6 +4971,12 @@ const rawTesterChecklistItems = [
|
|
|
4902
4971
|
pageLabel: "Prize Draws / Lotteries",
|
|
4903
4972
|
href: "/admin/prize-draws",
|
|
4904
4973
|
cases: [
|
|
4974
|
+
{
|
|
4975
|
+
key: "lottery-edit-preserves-bookings",
|
|
4976
|
+
label: "Editing a lottery's prizes leaves its bookings intact -- booked slots stay booked, against the same buyers",
|
|
4977
|
+
description: "BEFORE: the editor sent isBooked:false and weight:0 for every slot and dropped the buyer fields, and the event PATCH was passthrough -- so the first save of a live lottery freed every purchased slot and erased who bought them, with a 200 and no error. AFTER: the write shape cannot express booking state at all, and the merge reattaches by slotNumber rather than array index. Uses the seeded 25-slot lottery, five of them booked.",
|
|
4978
|
+
href: "/admin/lotteries",
|
|
4979
|
+
},
|
|
4905
4980
|
{ key: "prizedraw-create", label: "Admin can create a prize-draw listing, choosing instant or scheduled reveal mode and a 1โ15 day duration", href: "/admin/prize-draws" },
|
|
4906
4981
|
{ key: "prizedraw-reveal-winner", label: "Winners are assigned automatically via crypto.randomInt (on payment confirmation for instant mode, or at expiry/sellout for scheduled mode) โ never by a manual admin click" },
|
|
4907
4982
|
{ key: "prizedraw-scam-guard", label: "A prize draw with active entries cannot be unpublished, archived, or deleted, and an already-won item's details cannot be edited" },
|
|
@@ -4928,11 +5003,25 @@ const rawTesterChecklistItems = [
|
|
|
4928
5003
|
{
|
|
4929
5004
|
pageKey: "classifieds-digitalcodes-live",
|
|
4930
5005
|
pageLabel: "Classifieds, Digital Codes & Live Listings",
|
|
4931
|
-
|
|
5006
|
+
/*
|
|
5007
|
+
* ๐ /admin/products, not /admin/classified. The three per-type admin
|
|
5008
|
+
* pages exist and are real, but they are browse-only wrappers over the
|
|
5009
|
+
* shared listing-type config โ no row actions, no Approve/Reject, no
|
|
5010
|
+
* Quick edit. Moderation lives on /admin/products alone, so an href
|
|
5011
|
+
* pointing at the per-type page sends the tester to a surface where
|
|
5012
|
+
* the thing being tested cannot be done.
|
|
5013
|
+
*/
|
|
5014
|
+
href: "/admin/products",
|
|
4932
5015
|
cases: [
|
|
4933
|
-
{ key: "classified-create-moderate", label: "Admin can create and moderate classified listings", href: "/admin/
|
|
4934
|
-
{ key: "digitalcode-create-moderate", label: "Admin can create and moderate digital-code listings", href: "/admin/
|
|
4935
|
-
{ key: "live-create-moderate", label: "Admin can create and moderate live-item listings", href: "/admin/
|
|
5016
|
+
{ key: "classified-create-moderate", label: "Admin can create and moderate classified listings", href: "/admin/products" },
|
|
5017
|
+
{ key: "digitalcode-create-moderate", label: "Admin can create and moderate digital-code listings", href: "/admin/products" },
|
|
5018
|
+
{ key: "live-create-moderate", label: "Admin can create and moderate live-item listings", href: "/admin/products" },
|
|
5019
|
+
{
|
|
5020
|
+
key: "reject-status-has-no-chip",
|
|
5021
|
+
label: "After the Reject row action, the listing is still findable by an admin โ some status chip returns it",
|
|
5022
|
+
description: "BEFORE: Reject writes { status: \"rejected\" }, and ProductStatus is draft|published|in_review|archived โ no such value. The PATCH schema types status as a bare z.string(), so it persists unvalidated, and the row then matches none of the four status chips: an admin can reach it only under All. AFTER: a rejected listing is isolatable by a status filter. The public half already works โ every public query filters on published, so the listing does disappear, which is why this went unnoticed.",
|
|
5023
|
+
href: "/admin/products",
|
|
5024
|
+
},
|
|
4936
5025
|
],
|
|
4937
5026
|
},
|
|
4938
5027
|
{
|
|
@@ -5585,7 +5674,7 @@ const rawTesterChecklistItems = [
|
|
|
5585
5674
|
{
|
|
5586
5675
|
key: "product-created-with-deep-category-visible-at-root",
|
|
5587
5676
|
label: "A product created through the form against a deep category appears on that category's ROOT page too",
|
|
5588
|
-
description: "
|
|
5677
|
+
description: "BEFORE: nothing on the write side appended ancestors, so a listing filed deep got a one-element chain and was invisible on every ancestor page. AFTER (2026-09-14): ProductRepository.deriveTaxonomy() derives the chain on create and update, so all three pages list it. Latent by construction โ every seeded product already carries a correct chain, so only a product created through a form can catch a regression.",
|
|
5589
5678
|
href: "/admin/products",
|
|
5590
5679
|
},
|
|
5591
5680
|
{
|
|
@@ -5602,13 +5691,14 @@ const rawTesterChecklistItems = [
|
|
|
5602
5691
|
},
|
|
5603
5692
|
{
|
|
5604
5693
|
key: "category-type-not-guessable",
|
|
5605
|
-
label: "
|
|
5694
|
+
label: "A row's kind is set by WHICH admin surface authored it, and the surfaces stay disjoint โ a brand never appears in the category tree, and vice versa",
|
|
5695
|
+
description: "Brands, bundles, sub-listing groups and ordinary categories share one collection, discriminated by categoryType. The admin category form has no kind dropdown and correctly should not: /admin/brands filters and writes categoryType==brand while /admin/categories writes an ordinary category. This tests that separation rather than the absent control. (Key kept โ renaming orphans responses.)",
|
|
5606
5696
|
href: "/admin/categories",
|
|
5607
5697
|
},
|
|
5608
5698
|
{
|
|
5609
5699
|
key: "delete-category-with-children-refused",
|
|
5610
|
-
label: "Deleting a category
|
|
5611
|
-
description: "
|
|
5700
|
+
label: "Deleting a category CASCADES to its parent โ its children re-parent to the grandparent and its products are re-filed there with rebuilt ancestor chains",
|
|
5701
|
+
description: "BEFORE: a flat 409 refusal whenever the category had subcategories. AFTER: the delete cascades. Products are re-filed through the repository, so each one's chain is rebuilt from its new leaf; the order is load-bearing because a product must move while the document still exists. The failure to watch for is a product left pointing at the deleted row. (Key kept despite the stale 'refused' wording โ renaming it would orphan every response recorded against it.)",
|
|
5612
5702
|
href: "/admin/categories",
|
|
5613
5703
|
},
|
|
5614
5704
|
],
|
|
@@ -5654,7 +5744,13 @@ const rawTesterChecklistItems = [
|
|
|
5654
5744
|
pageLabel: "Bans, Unbans & Trust",
|
|
5655
5745
|
href: "/admin/users",
|
|
5656
5746
|
cases: [
|
|
5657
|
-
{
|
|
5747
|
+
{
|
|
5748
|
+
key: "soft-ban-blocks-signin",
|
|
5749
|
+
/* Key kept โ renaming it orphans every verdict recorded against it. */
|
|
5750
|
+
label: "A soft ban blocks the ONE action it names and leaves sign-in working; a hard ban refuses sign-in. Both tell the person why",
|
|
5751
|
+
description: "The label used to read \"Soft-banning a user prevents them signing in\", which is false: a soft ban appends to user.softBans[] as a per-action restriction (place_bids, write_reviews, โฆ) and /api/auth/login refuses only on the Auth record being disabled, which is what a HARD ban sets. A tester following the old label would have reported working behaviour as a defect.",
|
|
5752
|
+
href: "/admin/users",
|
|
5753
|
+
},
|
|
5658
5754
|
{ key: "soft-ban-requires-reason", label: "A ban cannot be applied without a reason, and that reason reaches the banned user", href: "/admin/users" },
|
|
5659
5755
|
{
|
|
5660
5756
|
key: "unban-restores-access",
|
|
@@ -5971,6 +6067,12 @@ const rawTesterChecklistItems = [
|
|
|
5971
6067
|
label: "The buyer's own bid page does NOT reveal other bidders",
|
|
5972
6068
|
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.",
|
|
5973
6069
|
},
|
|
6070
|
+
{
|
|
6071
|
+
key: "detail-page-seller-sees-bidder",
|
|
6072
|
+
label: "A seller opening a bid on their own auction sees who placed it, and the modal and the full page agree",
|
|
6073
|
+
description: "buildBidDetailFields(bid, viewer) has four callers and the seller leg had no case. Its only viewer-dependent row is Bidder, added when the viewer is not the buyer, so this is the other side of the identity case: the buyer must NOT see a competitor, and the seller MUST see who is bidding on their own auction.",
|
|
6074
|
+
href: "/store/bids",
|
|
6075
|
+
},
|
|
5974
6076
|
],
|
|
5975
6077
|
},
|
|
5976
6078
|
{
|
|
@@ -22,6 +22,23 @@ export interface TesterPhaseAssignable {
|
|
|
22
22
|
/** Target item count per phase โ real phase sizes vary a little around this
|
|
23
23
|
* since a page's items are never split across two phases. */
|
|
24
24
|
export declare const DEFAULT_TESTER_PHASE_SIZE = 25;
|
|
25
|
+
/**
|
|
26
|
+
* Groups that take the earliest phases, ahead of the round-robin.
|
|
27
|
+
*
|
|
28
|
+
* ๐ THE ROUND-ROBIN BELOW IS RIGHT FOR EVERYTHING ELSE AND WRONG FOR THIS.
|
|
29
|
+
* Interleaving exists so an early stop costs a proportional slice of every
|
|
30
|
+
* group rather than the whole tail โ a fair outcome when no group matters more
|
|
31
|
+
* than another. But `happy-path` is not one group among fourteen: it is the
|
|
32
|
+
* question "can anyone buy anything on this site", and a run that covered 7% of
|
|
33
|
+
* it alongside 7% of SEO metadata has answered nothing.
|
|
34
|
+
*
|
|
35
|
+
* So these groups are packed FIRST, in their own catalogue order, and the
|
|
36
|
+
* round-robin then runs over the remainder exactly as before.
|
|
37
|
+
*
|
|
38
|
+
* Adding a group here is a real cost: every group after it loses its early
|
|
39
|
+
* proportional slice. Two entries is the intended size of this list.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PRIORITY_GROUPS: readonly string[];
|
|
25
42
|
/**
|
|
26
43
|
* Returns a same-length array of 1-based phase numbers, one per input item,
|
|
27
44
|
* computed by greedily packing whole pages (consecutive runs of the same
|
|
@@ -18,6 +18,23 @@
|
|
|
18
18
|
/** Target item count per phase โ real phase sizes vary a little around this
|
|
19
19
|
* since a page's items are never split across two phases. */
|
|
20
20
|
export const DEFAULT_TESTER_PHASE_SIZE = 25;
|
|
21
|
+
/**
|
|
22
|
+
* Groups that take the earliest phases, ahead of the round-robin.
|
|
23
|
+
*
|
|
24
|
+
* ๐ THE ROUND-ROBIN BELOW IS RIGHT FOR EVERYTHING ELSE AND WRONG FOR THIS.
|
|
25
|
+
* Interleaving exists so an early stop costs a proportional slice of every
|
|
26
|
+
* group rather than the whole tail โ a fair outcome when no group matters more
|
|
27
|
+
* than another. But `happy-path` is not one group among fourteen: it is the
|
|
28
|
+
* question "can anyone buy anything on this site", and a run that covered 7% of
|
|
29
|
+
* it alongside 7% of SEO metadata has answered nothing.
|
|
30
|
+
*
|
|
31
|
+
* So these groups are packed FIRST, in their own catalogue order, and the
|
|
32
|
+
* round-robin then runs over the remainder exactly as before.
|
|
33
|
+
*
|
|
34
|
+
* Adding a group here is a real cost: every group after it loses its early
|
|
35
|
+
* proportional slice. Two entries is the intended size of this list.
|
|
36
|
+
*/
|
|
37
|
+
export const PRIORITY_GROUPS = ["happy-path"];
|
|
21
38
|
/**
|
|
22
39
|
* Returns a same-length array of 1-based phase numbers, one per input item,
|
|
23
40
|
* computed by greedily packing whole pages (consecutive runs of the same
|
|
@@ -73,7 +90,18 @@ export function assignDefaultPhases(itemsInCatalogOrder, targetPhaseSize = DEFAU
|
|
|
73
90
|
byGroup.set(g, []);
|
|
74
91
|
byGroup.get(g).push(pageId);
|
|
75
92
|
}
|
|
93
|
+
/*
|
|
94
|
+
* Priority groups first, whole, in catalogue order โ then the round-robin over
|
|
95
|
+
* everything else. See PRIORITY_GROUPS for why this one group is exempt from
|
|
96
|
+
* the interleaving the rest depends on.
|
|
97
|
+
*/
|
|
98
|
+
const interleaved = [];
|
|
99
|
+
for (const g of PRIORITY_GROUPS) {
|
|
100
|
+
for (const pageId of byGroup.get(g) ?? [])
|
|
101
|
+
interleaved.push(pageId);
|
|
102
|
+
}
|
|
76
103
|
const groupsLargestFirst = [...byGroup.entries()]
|
|
104
|
+
.filter(([g]) => !PRIORITY_GROUPS.includes(g))
|
|
77
105
|
.map(([g, pages]) => ({
|
|
78
106
|
g,
|
|
79
107
|
pages,
|
|
@@ -82,8 +110,7 @@ export function assignDefaultPhases(itemsInCatalogOrder, targetPhaseSize = DEFAU
|
|
|
82
110
|
.sort((a, b) => b.cases - a.cases || a.g.localeCompare(b.g))
|
|
83
111
|
.map((e) => e.g);
|
|
84
112
|
const cursor = new Map(groupsLargestFirst.map((g) => [g, 0]));
|
|
85
|
-
|
|
86
|
-
let remaining = pageOrder.length;
|
|
113
|
+
let remaining = pageOrder.length - interleaved.length;
|
|
87
114
|
while (remaining > 0) {
|
|
88
115
|
for (const g of groupsLargestFirst) {
|
|
89
116
|
const i = cursor.get(g);
|
package/dist/index.d.ts
CHANGED
|
@@ -1766,6 +1766,7 @@ export { eventIdGET } from "./features/events/server";
|
|
|
1766
1766
|
export { getEventLeaderboard } from "./features/events/server";
|
|
1767
1767
|
export { getEventPollResults } from "./features/events/server";
|
|
1768
1768
|
export { getEventSpinResults } from "./features/events/server";
|
|
1769
|
+
export { getUserSpinResultsForEvent } from "./features/events/server";
|
|
1769
1770
|
export { getBugHunterLeaderboard } from "./features/tester/server";
|
|
1770
1771
|
export { getPublicEventById } from "./features/events/server";
|
|
1771
1772
|
export { listPublicEvents } from "./features/events/server";
|
package/dist/index.js
CHANGED
|
@@ -3339,6 +3339,8 @@ export { getEventPollResults } from "./features/events/server";
|
|
|
3339
3339
|
// [SERVER-ONLY]-Server-only รขโฌ" uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
3340
3340
|
// getEventSpinResults - Last N winning spins for a spin_wheel event, most recent first.
|
|
3341
3341
|
export { getEventSpinResults } from "./features/events/server";
|
|
3342
|
+
// getUserSpinResultsForEvent - The CALLER'S OWN spins for an event, unmasked. Companion to the masked public feed above.
|
|
3343
|
+
export { getUserSpinResultsForEvent } from "./features/events/server";
|
|
3342
3344
|
// [SERVER-ONLY]-Server-only รขโฌ" uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
3343
3345
|
// getBugHunterLeaderboard - Ranks testers by confirmed-bug count.
|
|
3344
3346
|
export { getBugHunterLeaderboard } from "./features/tester/server";
|
package/dist/seo/json-ld.d.ts
CHANGED
|
@@ -36,6 +36,29 @@ export interface ProductJsonLdInput {
|
|
|
36
36
|
auctionEndDate?: Date;
|
|
37
37
|
/** Canonical discriminator (SB1-G Phase 4). */
|
|
38
38
|
listingType?: ListingType;
|
|
39
|
+
/**
|
|
40
|
+
* The path this listing actually lives at, e.g. `/auctions/{slug}`.
|
|
41
|
+
*
|
|
42
|
+
* ๐ Both builders hardcoded `/products/${slug}`, so an auction's own
|
|
43
|
+
* structured data advertised its offer โ and its `url` โ at a path that
|
|
44
|
+
* belongs to a different listing type. Measured live: the block on
|
|
45
|
+
* `/auctions/auction-beyblade-metal-lightning-l-drago` carried
|
|
46
|
+
* `offers.url: ".../products/auction-beyblade-metal-lightning-l-drago"`,
|
|
47
|
+
* and that URL served a full standard product page with Buy Now and Add to
|
|
48
|
+
* Cart and no bid controls at all. So the one consumer that reads this field
|
|
49
|
+
* โ a crawler โ was sent to the wrong page, and told it was canonical there.
|
|
50
|
+
*
|
|
51
|
+
* Six of the nine listing types have their own detail route (Root Cause
|
|
52
|
+
* #48), so `/products/{slug}` is a wrong default for two thirds of them.
|
|
53
|
+
*
|
|
54
|
+
* Defaults to `/products/${slug}` when omitted, which is correct for
|
|
55
|
+
* `standard`, `art` and `stickers` โ the three that genuinely live there โ
|
|
56
|
+
* and keeps every existing caller behaving exactly as before. The caller
|
|
57
|
+
* knows its own route; resolving it here would mean importing the listing-type
|
|
58
|
+
* registry into `seo/`, which must stay reachable from server-only metadata
|
|
59
|
+
* paths without widening its import graph (Root Cause #76).
|
|
60
|
+
*/
|
|
61
|
+
detailPath?: string;
|
|
39
62
|
/**
|
|
40
63
|
* Denormalised rating, straight off `ProductDocument.avgRating` /
|
|
41
64
|
* `.reviewCount` โ so emitting it costs **zero** extra Firestore reads.
|
package/dist/seo/json-ld.js
CHANGED
|
@@ -85,7 +85,7 @@ export function gatedPriceWebPageJsonLd(path) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
export function productJsonLd(product) {
|
|
88
|
-
const url = `${SITE_URL}
|
|
88
|
+
const url = `${SITE_URL}${product.detailPath ?? `/products/${product.slug}`}`;
|
|
89
89
|
// Absolutise, then de-duplicate: `mainImage` is very often also `images[0]`,
|
|
90
90
|
// which produced the same URL twice in the emitted array.
|
|
91
91
|
const images = [
|
|
@@ -246,7 +246,8 @@ export function searchBoxJsonLd(opts) {
|
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
248
|
export function auctionJsonLd(auction) {
|
|
249
|
-
|
|
249
|
+
// An auction lives at /auctions/{slug} โ see `detailPath`'s note.
|
|
250
|
+
const url = `${SITE_URL}${auction.detailPath ?? `/auctions/${auction.slug}`}`;
|
|
250
251
|
const base = productJsonLd(auction);
|
|
251
252
|
return {
|
|
252
253
|
...base,
|
package/dist/server.d.ts
CHANGED
|
@@ -266,6 +266,7 @@ export { getEventLeaderboard } from "./features/events/server";
|
|
|
266
266
|
export { getEventPollResults } from "./features/events/server";
|
|
267
267
|
export type { PollResultEntry, SpinResultEntry } from "./features/events/index";
|
|
268
268
|
export { getEventSpinResults } from "./features/events/server";
|
|
269
|
+
export { getUserSpinResultsForEvent } from "./features/events/server";
|
|
269
270
|
export { getBugHunterLeaderboard } from "./features/tester/server";
|
|
270
271
|
export { getPublicEventById } from "./features/events/server";
|
|
271
272
|
export { listPublicEvents } from "./features/events/server";
|
package/dist/server.js
CHANGED
|
@@ -777,6 +777,8 @@ export { getEventPollResults } from "./features/events/server";
|
|
|
777
777
|
// [SERVER-ONLY]-Server-only โ uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
778
778
|
// getEventSpinResults - Last N winning spins for a spin_wheel event, most recent first.
|
|
779
779
|
export { getEventSpinResults } from "./features/events/server";
|
|
780
|
+
// getUserSpinResultsForEvent - The CALLER'S OWN spins for an event, unmasked. Companion to the masked public feed above.
|
|
781
|
+
export { getUserSpinResultsForEvent } from "./features/events/server";
|
|
780
782
|
// [SERVER-ONLY]-Server-only โ uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
781
783
|
// getBugHunterLeaderboard - Ranks testers by confirmed-bug count.
|
|
782
784
|
export { getBugHunterLeaderboard } from "./features/tester/server";
|