@mohasinac/appkit 4.53.0 → 4.53.1
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/.build-stamp.json +3 -3
- package/dist/_internal/client/scaffolds/AppShell.js +1 -1
- package/dist/_internal/server/features/auctions/og.d.ts +3 -1
- package/dist/_internal/server/features/auctions/og.js +3 -1
- package/dist/_internal/server/features/bundles/actions.js +1 -1
- package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
- package/dist/_internal/server/features/business-documents/download.js +91 -0
- package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
- package/dist/_internal/server/features/categories/adapters.js +10 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
- package/dist/_internal/server/features/checkout/actions.js +106 -12
- package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
- package/dist/_internal/server/features/checkout/data.d.ts +22 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
- package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
- package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
- package/dist/_internal/server/features/classified/metadata.js +1 -1
- package/dist/_internal/server/features/classified/og.d.ts +3 -1
- package/dist/_internal/server/features/classified/og.js +3 -1
- package/dist/_internal/server/features/digital-code/metadata.js +1 -1
- package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
- package/dist/_internal/server/features/digital-code/og.js +3 -1
- package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
- package/dist/_internal/server/features/digital-code/pool.js +19 -4
- package/dist/_internal/server/features/live/metadata.js +1 -1
- package/dist/_internal/server/features/live/og.d.ts +3 -1
- package/dist/_internal/server/features/live/og.js +3 -1
- package/dist/_internal/server/features/messages/actions.js +1 -1
- package/dist/_internal/server/features/parts/og.js +1 -1
- package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
- package/dist/_internal/server/features/pre-orders/og.js +3 -1
- package/dist/_internal/server/features/price-index/search.d.ts +18 -0
- package/dist/_internal/server/features/price-index/search.js +47 -7
- package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
- package/dist/_internal/server/features/prize-draws/og.js +3 -1
- package/dist/_internal/server/features/products/group-members.d.ts +26 -0
- package/dist/_internal/server/features/products/group-members.js +7 -1
- package/dist/_internal/server/features/products/og.d.ts +3 -1
- package/dist/_internal/server/features/products/og.js +3 -1
- package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
- package/dist/_internal/server/features/search/scoped-sources.js +252 -0
- package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
- package/dist/_internal/server/features/search/search-everything.js +70 -20
- package/dist/_internal/server/features/seo/sitemap.js +6 -0
- package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
- package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
- package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
- package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
- package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
- package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +16 -0
- package/dist/_internal/server/features/stores/index.d.ts +1 -0
- package/dist/_internal/server/features/stores/index.js +1 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +25 -0
- package/dist/_internal/server/features/stores/tabs.js +122 -0
- package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
- package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
- package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
- package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
- package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
- package/dist/_internal/shared/checkout/rules/index.js +9 -0
- package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
- package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
- package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
- package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
- package/dist/_internal/shared/config/schema.d.ts +122 -0
- package/dist/_internal/shared/features/cart/schema.js +3 -2
- package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
- package/dist/_internal/shared/features/products/group-rules.js +142 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
- package/dist/_internal/shared/listing-types/_registry.js +39 -1
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +2 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +2 -0
- package/dist/_internal/shared/listing-types/availability.js +48 -0
- package/dist/_internal/shared/listing-types/capabilities.js +19 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +2 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +2 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
- package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
- package/dist/_internal/shared/listing-types/spares/config.js +60 -0
- package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
- package/dist/_internal/shared/listing-types/spares/og.js +1 -0
- package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
- package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +2 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
- package/dist/_internal/shared/search/match.d.ts +11 -0
- package/dist/_internal/shared/search/match.js +2 -1
- package/dist/_internal/shared/search/to-groups.d.ts +52 -0
- package/dist/_internal/shared/search/to-groups.js +141 -0
- package/dist/client.d.ts +8 -2
- package/dist/client.js +4 -1
- package/dist/constants/api-endpoints.d.ts +39 -15
- package/dist/constants/api-endpoints.js +28 -5
- package/dist/constants/cache-invalidation.js +3 -1
- package/dist/constants/field-names.d.ts +6 -0
- package/dist/constants/field-names.js +6 -0
- package/dist/features/about/components/PolicyPageView.d.ts +22 -2
- package/dist/features/about/components/PolicyPageView.js +14 -2
- package/dist/features/about/components/PublicProfileView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +42 -2
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/hooks/useProfileStats.js +5 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
- package/dist/features/addresses/repository/addresses.repository.js +21 -0
- package/dist/features/admin/actions/admin-actions.js +16 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
- package/dist/features/admin/components/AdminNavigationView.js +1 -1
- package/dist/features/admin/components/AdminPageHeader.js +1 -1
- package/dist/features/admin/components/AdminProductEditorView.js +17 -2
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
- package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
- package/dist/features/admin/components/AdminSparesView.js +30 -0
- package/dist/features/admin/components/AdminUsersView.js +9 -7
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
- package/dist/features/admin/constants/filter-tabs.js +1 -0
- package/dist/features/admin/schemas/firestore.d.ts +26 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +4 -11
- package/dist/features/auth/repository/user.repository.d.ts +13 -0
- package/dist/features/auth/repository/user.repository.js +28 -0
- package/dist/features/auth/schemas/firestore.d.ts +2 -0
- package/dist/features/auth/schemas/firestore.js +2 -0
- package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
- package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
- package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
- package/dist/features/business-documents/schemas/firestore.js +75 -0
- package/dist/features/cart/actions/cart-actions.js +83 -6
- package/dist/features/cart/repository/cart.repository.js +32 -4
- package/dist/features/cart/schemas/firestore.d.ts +20 -0
- package/dist/features/cart/schemas/index.d.ts +2 -2
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
- package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
- package/dist/features/categories/api/[id]/price-guidance/route.js +113 -0
- package/dist/features/categories/components/BrandDetailPageView.js +19 -3
- package/dist/features/categories/components/BrandDetailTabs.js +10 -0
- package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
- package/dist/features/categories/components/CategoryDetailPageView.js +141 -14
- package/dist/features/categories/components/CategoryDetailTabs.js +12 -0
- package/dist/features/categories/components/CategoryGrid.js +10 -1
- package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
- package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
- package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
- package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
- package/dist/features/categories/constants/category-tabs.d.ts +20 -25
- package/dist/features/categories/constants/category-tabs.js +22 -39
- package/dist/features/categories/repository/categories.repository.d.ts +12 -0
- package/dist/features/categories/repository/categories.repository.js +20 -0
- package/dist/features/categories/schemas/firestore.d.ts +22 -1
- package/dist/features/categories/schemas/firestore.js +10 -0
- package/dist/features/categories/schemas/index.d.ts +6 -0
- package/dist/features/categories/schemas/index.js +2 -0
- package/dist/features/categories/types/index.d.ts +8 -0
- package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
- package/dist/features/categories/utils/taxonomy-visual.js +26 -10
- package/dist/features/classified/components/ClassifiedDetailPageView.js +4 -9
- package/dist/features/contact/components/ContactPageView.d.ts +33 -1
- package/dist/features/contact/components/ContactPageView.js +58 -28
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +4 -9
- package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
- package/dist/features/grouped/schemas/firestore.d.ts +5 -0
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
- package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
- package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
- package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
- package/dist/features/homepage/components/HowItWorksSection.js +4 -4
- package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
- package/dist/features/homepage/components/PromoGrid.js +12 -3
- package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
- package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
- package/dist/features/homepage/components/index.d.ts +0 -20
- package/dist/features/homepage/components/index.js +0 -11
- package/dist/features/homepage/index.d.ts +0 -2
- package/dist/features/homepage/index.js +0 -2
- package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
- package/dist/features/homepage/lib/section-renderer.js +202 -58
- package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
- package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
- package/dist/features/homepage/schemas/firestore.d.ts +106 -3
- package/dist/features/homepage/schemas/firestore.js +32 -2
- package/dist/features/homepage/schemas/index.d.ts +36 -7
- package/dist/features/homepage/schemas/index.js +39 -27
- package/dist/features/homepage/server.d.ts +0 -3
- package/dist/features/homepage/server.js +0 -3
- package/dist/features/homepage/types/index.d.ts +0 -47
- package/dist/features/homepage/types/index.js +49 -0
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/BottomNavItem.js +1 -1
- package/dist/features/layout/BottomSheet.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +4 -9
- package/dist/features/media/hooks/useMedia.js +17 -0
- package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
- package/dist/features/media/repository/media-assets.repository.js +31 -0
- package/dist/features/media/trust-signals.d.ts +85 -0
- package/dist/features/media/trust-signals.js +126 -0
- package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
- package/dist/features/parts/components/PartDetailPageView.js +2 -2
- package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
- package/dist/features/parts/components/PartsIndexPageView.js +2 -2
- package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
- package/dist/features/parts/components/PartsSearchBox.js +57 -0
- package/dist/features/parts/schemas/firestore.d.ts +149 -3
- package/dist/features/parts/schemas/firestore.js +184 -0
- package/dist/features/payments/repository/payout.repository.d.ts +15 -0
- package/dist/features/payments/repository/payout.repository.js +21 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -11
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +3 -1
- package/dist/features/price-index/components/PriceIndexToolView.js +92 -13
- package/dist/features/products/components/CompareOverlay.d.ts +18 -1
- package/dist/features/products/components/CompareOverlay.js +21 -2
- package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
- package/dist/features/products/components/CustomSectionTabContent.js +29 -3
- package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
- package/dist/features/products/components/GroupMemberPicker.js +46 -8
- package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
- package/dist/features/products/components/GroupSettingsPanel.js +91 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
- package/dist/features/products/components/ProductDetailPageView.js +74 -15
- package/dist/features/products/components/ProductForm.d.ts +13 -1
- package/dist/features/products/components/ProductForm.js +16 -2
- package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
- package/dist/features/products/components/ProductsIndexListing.js +3 -3
- package/dist/features/products/components/ShowGroupSection.js +15 -2
- package/dist/features/products/components/SparesListView.d.ts +7 -0
- package/dist/features/products/components/SparesListView.js +39 -0
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/repository/products.repository.d.ts +98 -0
- package/dist/features/products/repository/products.repository.js +235 -17
- package/dist/features/products/schemas/firestore.d.ts +97 -4
- package/dist/features/products/schemas/firestore.js +104 -0
- package/dist/features/products/schemas/index.d.ts +10 -19
- package/dist/features/products/schemas/index.js +23 -6
- package/dist/features/products/types/index.d.ts +45 -1
- package/dist/features/products/utils/group-member-image.d.ts +39 -0
- package/dist/features/products/utils/group-member-image.js +3 -0
- package/dist/features/products/utils/listing-badge-variant.js +4 -0
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/scams/components/ScamProfileView.js +1 -1
- package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
- package/dist/features/search/components/EntityTypeahead.js +92 -0
- package/dist/features/search/components/Search.d.ts +12 -1
- package/dist/features/search/components/Search.js +47 -3
- package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
- package/dist/features/search/components/SearchFacetRail.js +92 -0
- package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
- package/dist/features/search/components/SearchResultsClient.js +103 -0
- package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/search/components/index.d.ts +0 -6
- package/dist/features/search/components/index.js +0 -3
- package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
- package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
- package/dist/features/search/hooks/useScopedSearch.js +114 -0
- package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
- package/dist/features/search/hooks/useSiteSearch.js +149 -0
- package/dist/features/search/index.d.ts +0 -4
- package/dist/features/search/index.js +0 -4
- package/dist/features/search/manifest.d.ts +12 -0
- package/dist/features/search/manifest.js +17 -2
- package/dist/features/search/server.d.ts +0 -3
- package/dist/features/search/server.js +0 -3
- package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
- package/dist/features/seller/components/SellerListingTypeView.js +20 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +2 -1
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
- package/dist/features/seller/components/SellerSparesView.js +6 -0
- package/dist/features/seller/components/index.d.ts +2 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/shell/CommandPalette.js +1 -1
- package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
- package/dist/features/stores/components/StoreHeader.js +1 -2
- package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
- package/dist/features/stores/components/StoreSparesPageView.js +48 -0
- package/dist/features/stores/schemas/firestore.d.ts +19 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +280 -45
- package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
- package/dist/features/tester/seed-data/authored/index.js +16 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +372 -18
- package/dist/index.d.ts +17 -57
- package/dist/index.js +35 -96
- package/dist/next/routing/route-map.d.ts +42 -1
- package/dist/next/routing/route-map.js +45 -0
- package/dist/providers/db-firebase/base.repository.d.ts +22 -0
- package/dist/providers/db-firebase/base.repository.js +36 -0
- package/dist/schemas/registry.d.ts +15 -6
- package/dist/security/blind-index-tokens.d.ts +54 -0
- package/dist/security/blind-index-tokens.js +152 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/settings-encryption.d.ts +35 -0
- package/dist/security/settings-encryption.js +91 -0
- package/dist/seed/_helpers/brand-rows.js +38 -14
- package/dist/seed/_helpers/category-forest.js +73 -7
- package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
- package/dist/seed/_helpers/category-images-generated.js +81 -6
- package/dist/seed/_helpers/category-models.d.ts +1 -1
- package/dist/seed/_helpers/category-models.js +7 -1
- package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +50 -2
- package/dist/seed/_helpers/character-features.d.ts +1 -1
- package/dist/seed/_helpers/character-features.js +2 -1
- package/dist/seed/_helpers/franchise-rows.js +10 -2
- package/dist/seed/_helpers/media.d.ts +47 -0
- package/dist/seed/_helpers/media.js +34 -0
- package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
- package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
- package/dist/seed/categories-seed-data.js +124 -20
- package/dist/seed/grouped-listings-seed-data.js +16 -0
- package/dist/seed/homepage-sections-seed-data.js +94 -5
- package/dist/seed/index.d.ts +2 -3
- package/dist/seed/index.js +2 -2
- package/dist/seed/manifest.js +3 -0
- package/dist/seed/media-assets-seed-data.d.ts +1 -1
- package/dist/seed/media-assets-seed-data.js +746 -196
- package/dist/seed/model-composition-seed-data.js +122 -6
- package/dist/seed/part-glossary-seed-data.d.ts +1 -1
- package/dist/seed/part-glossary-seed-data.js +37219 -463
- package/dist/seed/products-art-seed-data.js +7 -18
- package/dist/seed/products-auctions-seed-data.js +56 -47
- package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
- package/dist/seed/products-beyblade-x-seed-data.js +441 -0
- package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
- package/dist/seed/products-catalogue-seed-data.js +1500 -1240
- package/dist/seed/products-classifieds-seed-data.js +9 -27
- package/dist/seed/products-digital-codes-seed-data.js +9 -18
- package/dist/seed/products-live-items-seed-data.js +5 -13
- package/dist/seed/products-preorders-seed-data.js +67 -33
- package/dist/seed/products-prize-draws-seed-data.js +8 -17
- package/dist/seed/products-spares-seed-data.d.ts +2 -0
- package/dist/seed/products-spares-seed-data.js +368 -0
- package/dist/seed/products-standard-seed-data.js +39 -126
- package/dist/seed/products-stickers-seed-data.js +59 -15
- package/dist/seed/site-settings-seed-data.js +8 -0
- package/dist/seo/json-ld.d.ts +25 -0
- package/dist/seo/json-ld.js +28 -0
- package/dist/server-entry.d.ts +3 -1
- package/dist/server-entry.js +2 -2
- package/dist/server.d.ts +9 -9
- package/dist/server.js +27 -25
- package/dist/styles.css +202 -1
- package/dist/tailwind-input.css +31 -17
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Accordion.js +1 -1
- package/dist/ui/components/Breadcrumb.js +1 -1
- package/dist/ui/components/DetailPageGallery.js +0 -2
- package/dist/ui/components/DetailPageHero.js +0 -1
- package/dist/ui/components/DetailPageTabs.js +1 -1
- package/dist/ui/components/Divider.js +3 -3
- package/dist/ui/components/GatedPrice.js +5 -1
- package/dist/ui/components/HorizontalScroller.js +1 -1
- package/dist/ui/components/ListingLayout.js +3 -3
- package/dist/ui/components/MediaTabs.style.css +81 -0
- package/dist/ui/components/Pagination.js +1 -1
- package/dist/ui/components/PendingValue.d.ts +45 -0
- package/dist/ui/components/PendingValue.js +7 -0
- package/dist/ui/components/Radio.style.css +119 -0
- package/dist/ui/components/RatingDisplay.js +2 -2
- package/dist/ui/components/SettingsSection.js +0 -2
- package/dist/ui/components/SiteLogo.js +3 -0
- package/dist/ui/components/SiteMark.js +3 -0
- package/dist/ui/components/SortDropdown.js +1 -1
- package/dist/ui/components/SpecTable.d.ts +58 -0
- package/dist/ui/components/SpecTable.js +8 -0
- package/dist/ui/components/StarRating.js +1 -3
- package/dist/ui/components/SummaryCard.js +1 -1
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +9 -1
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +2 -0
- package/dist/utils/search-txt-query.d.ts +12 -0
- package/dist/utils/search-txt-query.js +12 -0
- package/package.json +1 -1
- package/scripts/dedupe-peer-deps.mjs +18 -0
- package/scripts/seed-cli.mjs +135 -26
- package/scripts/verify-css-build.mjs +118 -37
- package/dist/_internal/server/features/search/actions.d.ts +0 -7
- package/dist/_internal/server/features/search/actions.js +0 -21
- package/dist/_internal/server/features/search/data.d.ts +0 -15
- package/dist/_internal/server/features/search/data.js +0 -18
- package/dist/_internal/server/features/search/index.d.ts +0 -3
- package/dist/_internal/server/features/search/index.js +0 -2
- package/dist/features/homepage/api/[id]/route.d.ts +0 -23
- package/dist/features/homepage/api/[id]/route.js +0 -103
- package/dist/features/homepage/api/route.d.ts +0 -14
- package/dist/features/homepage/api/route.js +0 -140
- package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
- package/dist/features/homepage/api/sections/[id]/route.js +0 -95
- package/dist/features/homepage/api/sections/route.d.ts +0 -13
- package/dist/features/homepage/api/sections/route.js +0 -82
- package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
- package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
- package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
- package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
- package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
- package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
- package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
- package/dist/features/homepage/components/HeroBanner.js +0 -43
- package/dist/features/homepage/components/HeroSection.d.ts +0 -7
- package/dist/features/homepage/components/HeroSection.js +0 -6
- package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
- package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
- package/dist/features/homepage/components/HomepageView.d.ts +0 -25
- package/dist/features/homepage/components/HomepageView.js +0 -5
- package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
- package/dist/features/homepage/components/NewsletterBanner.js +0 -37
- package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
- package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
- package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
- package/dist/features/homepage/components/TrustBadges.js +0 -46
- package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
- package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
- package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
- package/dist/features/homepage/hooks/useHomepage.js +0 -17
- package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
- package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
- package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
- package/dist/features/homepage/repository/homepage.repository.js +0 -25
- package/dist/features/search/actions/index.d.ts +0 -1
- package/dist/features/search/actions/index.js +0 -1
- package/dist/features/search/actions/search-actions.d.ts +0 -18
- package/dist/features/search/actions/search-actions.js +0 -43
- package/dist/features/search/api/route.d.ts +0 -22
- package/dist/features/search/api/route.js +0 -128
- package/dist/features/search/columns/index.d.ts +0 -17
- package/dist/features/search/columns/index.js +0 -49
- package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
- package/dist/features/search/components/SearchFiltersRow.js +0 -21
- package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
- package/dist/features/search/components/SearchResultsSection.js +0 -35
- package/dist/features/search/components/SearchView.d.ts +0 -27
- package/dist/features/search/components/SearchView.js +0 -8
- package/dist/features/search/hooks/useSearch.d.ts +0 -21
- package/dist/features/search/hooks/useSearch.js +0 -33
- package/dist/features/search/repository/search.repository.d.ts +0 -14
- package/dist/features/search/repository/search.repository.js +0 -59
- package/dist/features/search/schemas/index.d.ts +0 -70
- package/dist/features/search/schemas/index.js +0 -36
- package/dist/features/search/types/index.d.ts +0 -48
- package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
- package/dist/features/stores/components/StoreScopedSearch.js +0 -27
- package/dist/seed/defaults/homepage-sections.d.ts +0 -2
- package/dist/seed/defaults/homepage-sections.js +0 -60
- package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
- package/dist/seed/factories/homepage-section.factory.js +0 -13
- /package/dist/{features/search/types/index.js → _internal/shared/listing-types/spares/og.d.ts} +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { PricesOnly, Stack, Text } from "../../../ui/index.js";
|
|
5
|
+
import { apiClient } from "../../../http/index.js";
|
|
6
|
+
import { CATEGORY_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
7
|
+
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
8
|
+
function unwrap(res) {
|
|
9
|
+
const body = res?.data ?? res;
|
|
10
|
+
return body && typeof body === "object" && "guidance" in body ? body : null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The seller-facing price hint `description-template/route.ts`'s own
|
|
14
|
+
* docstring names as "C1's job, from a surface that knows who is asking" —
|
|
15
|
+
* renders the inherited competitor-derived market band beside the price
|
|
16
|
+
* field, on the SELLER'S own tax basis (the `/price-guidance` endpoint does
|
|
17
|
+
* that conversion server-side; this component only formats what it returns).
|
|
18
|
+
*
|
|
19
|
+
* Mounted via `ProductForm`'s `renderPriceGuidance` slot rather than fetched
|
|
20
|
+
* unconditionally inside the form — `ProductForm` has no data-fetching of its
|
|
21
|
+
* own (every app-specific concern is a render-prop), and this component is
|
|
22
|
+
* seller/admin-only, not something the form itself should assume a caller
|
|
23
|
+
* wants.
|
|
24
|
+
*
|
|
25
|
+
* 🛑 NEVER mount this on a public page (category/brand detail). A
|
|
26
|
+
* competitor-derived market band is exactly the "a neighbouring surface
|
|
27
|
+
* publishes the range" leak the guest price gate exists to close (CLAUDE.md
|
|
28
|
+
* § "Guest price gating"), even though the figure is advisory rather than a
|
|
29
|
+
* real listing price — which is also why `/price-guidance` itself is
|
|
30
|
+
* seller/admin/moderator-gated, not a public, edge-cached endpoint like its
|
|
31
|
+
* `description-template` sibling.
|
|
32
|
+
*/
|
|
33
|
+
export function PriceGuidanceHint({ categoryId, currency = "INR", className = "", }) {
|
|
34
|
+
const query = useQuery({
|
|
35
|
+
queryKey: ["category-price-guidance", categoryId],
|
|
36
|
+
enabled: categoryId.length > 0,
|
|
37
|
+
staleTime: 60 * 60 * 1000,
|
|
38
|
+
queryFn: async () => unwrap(await apiClient.get(CATEGORY_ENDPOINTS.PRICE_GUIDANCE(categoryId))),
|
|
39
|
+
});
|
|
40
|
+
const guidance = query.data?.guidance;
|
|
41
|
+
const basisNote = query.data?.basisNote;
|
|
42
|
+
if (!guidance?.market)
|
|
43
|
+
return null;
|
|
44
|
+
const { median, p25, p75 } = guidance.market;
|
|
45
|
+
const fmt = (n) => formatCurrency(n, currency);
|
|
46
|
+
// p25/p75 are independently optional (see CategoryPriceGuidance's own
|
|
47
|
+
// docstring) — a range renders only when BOTH are present, never a
|
|
48
|
+
// half-range that would misreport the band's shape.
|
|
49
|
+
const range = p25 !== undefined && p75 !== undefined ? ` (${fmt(p25)}–${fmt(p75)})` : "";
|
|
50
|
+
/*
|
|
51
|
+
* `<PricesOnly>` rather than `<GatedPrice>`: this is advisory guidance
|
|
52
|
+
* BESIDE the seller's own price input, not a listing's primary price, so a
|
|
53
|
+
* viewer who may not see prices should get nothing here — a "Sign in to see
|
|
54
|
+
* price" prompt inside a seller form would be nonsense. In practice it
|
|
55
|
+
* never renders for a guest anyway, because the data source
|
|
56
|
+
* (`/api/categories/[id]/price-guidance`) is seller/admin/moderator-gated
|
|
57
|
+
* and 403s before there is a number to format; the wrapper makes that
|
|
58
|
+
* guarantee visible at the render site instead of implied by the route.
|
|
59
|
+
*/
|
|
60
|
+
return (_jsx(PricesOnly, { children: _jsxs(Stack, { gap: "xs", className: className, children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Market median:", " ", _jsx(Text, { as: "span", weight: "medium", color: "primary", children: fmt(median) }), range, basisNote?.suffix ? ` ${basisNote.suffix}` : ""] }), basisNote?.note && _jsx(Text, { size: "xs", color: "faint", children: basisNote.note })] }) }));
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface SubcategoryCardItem {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
href: string;
|
|
6
|
+
/** Rollup listing count. Omitted when zero — a card reading "0" invites a dead click. */
|
|
7
|
+
count?: number;
|
|
8
|
+
/** A real picture, when the row resolved one. */
|
|
9
|
+
imageUrl?: string;
|
|
10
|
+
/** An emoji, when that is all the row has. Never passed to an `<img>`. */
|
|
11
|
+
emoji?: string;
|
|
12
|
+
/** Direct children's names — searched, never rendered. */
|
|
13
|
+
childNames?: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Depth in the forest: 0 is a root, 1 its child, and so on.
|
|
16
|
+
*
|
|
17
|
+
* Rendered as a small "L0 / L1 / L2" marker so a row of ancestors reads as a
|
|
18
|
+
* LADDER rather than as five unrelated cards. Omitted on the subcategory row
|
|
19
|
+
* itself, where every card is the same depth by construction and the marker
|
|
20
|
+
* would be noise repeated N times.
|
|
21
|
+
*/
|
|
22
|
+
level?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function SubcategoryCardRow({ items, label, }: {
|
|
25
|
+
items: readonly SubcategoryCardItem[];
|
|
26
|
+
label?: string;
|
|
27
|
+
}): React.JSX.Element | null;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/*
|
|
4
|
+
* WHY: the Subcategories row was a pill per child in an `<OverflowRow>`, which
|
|
5
|
+
* collapses to a searchable dropdown when it does not fit. A pill carries
|
|
6
|
+
* only a name and a count, so on a tree this wide every child looked the
|
|
7
|
+
* same and the row read as a wall of words.
|
|
8
|
+
* WHAT: small cards in the shared `<HorizontalScroller>`, with a filter box
|
|
9
|
+
* that matches ONE LEVEL DEEPER than the cards themselves.
|
|
10
|
+
*
|
|
11
|
+
* EXPORTS: SubcategoryCardRow, SubcategoryCardItem
|
|
12
|
+
*
|
|
13
|
+
* ## 🛑 The filter matches grandchildren, and that is the point
|
|
14
|
+
*
|
|
15
|
+
* Searching "Dranzer" on Spinning Tops should surface *Beyblade Original* —
|
|
16
|
+
* the card you must click to get there — not nothing. So each card carries its
|
|
17
|
+
* own direct children's names (`childNames`) and the match runs over the card
|
|
18
|
+
* name PLUS that list. One level only: the full subtree of a root is ~1,200
|
|
19
|
+
* nodes, and matching all of it would surface a card because of a name six
|
|
20
|
+
* levels down that the visitor will never see on the next page either.
|
|
21
|
+
*
|
|
22
|
+
* `childNames` is built from data the page has ALREADY fetched
|
|
23
|
+
* (`getDescendants` is one array-contains query over `parentIds`), so the
|
|
24
|
+
* filter costs no request — it is pure in-browser narrowing of a list that is
|
|
25
|
+
* already in memory.
|
|
26
|
+
*
|
|
27
|
+
* ## 🛑 Why this does not reintroduce the bug `OverflowRow` was built for
|
|
28
|
+
*
|
|
29
|
+
* CLAUDE.md records a bare `overflow-x-auto` row as an anti-pattern, measured:
|
|
30
|
+
* "4 of 12 subcategories past the right edge with no scrollbar and no arrow".
|
|
31
|
+
* The discoverability problem is real and this row answers it twice over —
|
|
32
|
+
* `<HorizontalScroller showArrows>` renders real scroll affordances, and the
|
|
33
|
+
* filter box makes an off-screen card reachable by typing rather than by
|
|
34
|
+
* scrolling to it. What is given up is the collapse-to-dropdown behaviour,
|
|
35
|
+
* which was the fallback for exactly that problem.
|
|
36
|
+
*
|
|
37
|
+
* @tag domain:categories
|
|
38
|
+
* @tag layer:feature
|
|
39
|
+
* @tag pattern:none
|
|
40
|
+
* @tag access:client
|
|
41
|
+
* @tag sideEffects:none
|
|
42
|
+
*/
|
|
43
|
+
import { useMemo, useState } from "react";
|
|
44
|
+
import { Div, HorizontalScroller, Row, Span, Stack, Text } from "../../../ui/index.js";
|
|
45
|
+
import { FieldInput } from "../../../ui/forms/index.js";
|
|
46
|
+
import { MediaImage } from "../../media/MediaImage.js";
|
|
47
|
+
import { TextLink } from "../../../ui/components/TextLink.js";
|
|
48
|
+
/**
|
|
49
|
+
* The filter appears once this many NAMES are searchable — cards plus the
|
|
50
|
+
* grandchildren they match on.
|
|
51
|
+
*
|
|
52
|
+
* 🛑 Counting cards alone was wrong, and wrong in the case that prompted this
|
|
53
|
+
* row. Spinning Tops shows FIVE cards, so a `cards >= 6` rule hid the filter
|
|
54
|
+
* on exactly the page the request came from — while that page actually has 26
|
|
55
|
+
* searchable names (5 cards + 21 grandchildren), which is precisely where
|
|
56
|
+
* typing beats scanning.
|
|
57
|
+
*
|
|
58
|
+
* The number of cards is not what the filter searches, so it is not what
|
|
59
|
+
* should decide whether the filter exists.
|
|
60
|
+
*/
|
|
61
|
+
const FILTER_MIN_NAMES = 12;
|
|
62
|
+
/** Case- and whitespace-insensitive contains. */
|
|
63
|
+
function matches(item, needle) {
|
|
64
|
+
if (item.name.toLowerCase().includes(needle))
|
|
65
|
+
return true;
|
|
66
|
+
/*
|
|
67
|
+
* 🛑 `?? []`, not a non-null assertion. A card whose children were never
|
|
68
|
+
* resolved must still match on its OWN name — otherwise a transient fetch
|
|
69
|
+
* failure upstream silently removes it from every search, which looks like
|
|
70
|
+
* the category does not exist rather than like a degraded read.
|
|
71
|
+
*/
|
|
72
|
+
return (item.childNames ?? []).some((n) => n.toLowerCase().includes(needle));
|
|
73
|
+
}
|
|
74
|
+
export function SubcategoryCardRow({ items, label = "Subcategories", }) {
|
|
75
|
+
const [query, setQuery] = useState("");
|
|
76
|
+
const needle = query.trim().toLowerCase();
|
|
77
|
+
const filtered = useMemo(() => (needle ? items.filter((i) => matches(i, needle)) : items), [items, needle]);
|
|
78
|
+
const searchableNames = useMemo(() => items.reduce((n, i) => n + 1 + (i.childNames?.length ?? 0), 0), [items]);
|
|
79
|
+
if (items.length === 0)
|
|
80
|
+
return null;
|
|
81
|
+
const showFilter = searchableNames >= FILTER_MIN_NAMES;
|
|
82
|
+
return (_jsxs(Stack, { gap: "xs", children: [_jsxs(Row, { justify: "between", align: "center", gap: "md", wrap: true, children: [_jsx(Text, { size: "xs", color: "muted", weight: "medium", children: label }), showFilter && (_jsx(Div, { className: "w-full sm:w-64", children: _jsx(FieldInput, { name: "subcategory-filter", type: "search", value: query, onChange: setQuery, placeholder: "Filter subcategories\u2026", "aria-label": "Filter subcategories" }) }))] }), filtered.length === 0 ? (
|
|
83
|
+
/*
|
|
84
|
+
* A real empty state rather than a blank strip. The row is otherwise
|
|
85
|
+
* indistinguishable from "this category has no subcategories", which
|
|
86
|
+
* is a different and much more alarming thing to tell someone.
|
|
87
|
+
*/
|
|
88
|
+
_jsxs(Text, { size: "sm", color: "muted", children: ["No subcategory matches \u201C", query.trim(), "\u201D."] })) : (_jsx(HorizontalScroller, { showArrows: true, showScrollbar: false, gap: 12, minItemWidth: 150, items: filtered, keyExtractor: (item) => item.id, renderItem: (item) => (_jsx(TextLink, { href: item.href, className: "group block h-full no-underline", "aria-label": item.count !== undefined
|
|
89
|
+
? `${item.name}, ${item.count} listings`
|
|
90
|
+
: item.name, children: _jsxs(Stack, { gap: "xs", surface: "card", rounded: "lg", border: "subtle", padding: "sm", className: "h-full transition-colors hover:bg-surface-hover", children: [_jsx(Div, { surface: "muted", rounded: "md", className: `relative aspect-square w-full overflow-hidden flex-shrink-0`, children: item.imageUrl ? (_jsx(MediaImage, { src: item.imageUrl, alt: "", size: "thumbnail", className: "transition-transform duration-300 group-hover:scale-105" })) : (
|
|
91
|
+
/*
|
|
92
|
+
* The emoji is the FALLBACK, never an overlay — same rule
|
|
93
|
+
* as `CategoryCard` and `resolveTaxonomyVisual`: an emoji
|
|
94
|
+
* is what you show when there is no picture, not something
|
|
95
|
+
* you paint on top of one.
|
|
96
|
+
*/
|
|
97
|
+
_jsx(Row, { align: "center", justify: "center", textSize: "2xl", className: "absolute inset-0", children: item.emoji ?? "" })) }), item.level !== undefined && (_jsx(Span, { size: "xs", color: "muted", weight: "medium", children: item.level === 0 ? "L0 · root" : `L${item.level}` })), _jsx(Text, { size: "xs", weight: "medium", className: "line-clamp-2", children: item.name }), item.count !== undefined && (_jsx(Span, { size: "xs", color: "muted", children: item.count.toLocaleString() }))] }) })) }))] }));
|
|
98
|
+
}
|
|
@@ -10,29 +10,36 @@
|
|
|
10
10
|
* this shape is browsed, and what the category detail page already supports
|
|
11
11
|
* (it fetches every descendant and renders them tier-grouped).
|
|
12
12
|
*
|
|
13
|
-
* ## 🛑 Parts
|
|
13
|
+
* ## 🛑 There is no Parts tab, and that is deliberate
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* It was removed 2026-10-20. Parts have a better home than a flat tab over
|
|
16
|
+
* 752 leaves: `/parts/[classification]/[code]` is a purpose-built part
|
|
17
|
+
* catalogue with composition data, and the `spares` listing type sells loose
|
|
18
|
+
* parts. A fifth tab listing every part leaf duplicated the worse half of
|
|
19
|
+
* both.
|
|
20
|
+
*
|
|
21
|
+
* The part leaves themselves are untouched and still reachable by drilling
|
|
22
|
+
* down from their `*-parts` node, which is also why no `categoryType` was
|
|
23
|
+
* involved: a part is identified by ANCESTRY, never by a discriminator
|
|
24
|
+
* (`gen-category-parts.mjs` omits `categoryType` because 92 files read it and
|
|
25
|
+
* a new value opts a row out of every path that works).
|
|
26
|
+
*
|
|
27
|
+
* 🛑 Do not confuse this with the product-detail **Parts composition tab**
|
|
28
|
+
* ("What it is made of"), gated by `LISTING_TYPE_HAS_COMPOSITION`. That is a
|
|
29
|
+
* different feature on a different surface and is unaffected.
|
|
21
30
|
*
|
|
22
31
|
* ## Why the tabs partition ONE payload
|
|
23
32
|
*
|
|
24
|
-
* The listing already fetches `?flat=true` once. Deriving all
|
|
33
|
+
* The listing already fetches `?flat=true` once. Deriving all four tabs from
|
|
25
34
|
* that single cached response costs no extra request — a per-tab query would
|
|
26
|
-
* turn one fetch into
|
|
35
|
+
* turn one fetch into four for the same data.
|
|
27
36
|
*
|
|
28
37
|
* @tag domain:categories
|
|
29
38
|
* @tag layer:constants
|
|
30
39
|
* @tag pattern:shared-literal
|
|
31
40
|
* @tag sideEffects:none
|
|
32
41
|
*/
|
|
33
|
-
|
|
34
|
-
export declare const PART_PARENT_IDS: readonly string[];
|
|
35
|
-
export type CategoryTabKey = "categories" | "brands" | "franchises" | "parts" | "bundles";
|
|
42
|
+
export type CategoryTabKey = "categories" | "brands" | "franchises" | "bundles";
|
|
36
43
|
export interface CategoryTabDef {
|
|
37
44
|
key: CategoryTabKey;
|
|
38
45
|
label: string;
|
|
@@ -40,7 +47,7 @@ export interface CategoryTabDef {
|
|
|
40
47
|
/**
|
|
41
48
|
* 🛑 No "All" tab. It was the default and it was the problem — it is what
|
|
42
49
|
* rendered 1,185 undifferentiated rows. Every tab here answers a question
|
|
43
|
-
* ("which
|
|
50
|
+
* ("which brands do you carry?"); "all" answers none of them.
|
|
44
51
|
*/
|
|
45
52
|
export declare const CATEGORY_TABS: readonly CategoryTabDef[];
|
|
46
53
|
export declare const DEFAULT_CATEGORY_TAB: CategoryTabKey;
|
|
@@ -48,19 +55,7 @@ export declare const DEFAULT_CATEGORY_TAB: CategoryTabKey;
|
|
|
48
55
|
interface TabbableCategory {
|
|
49
56
|
categoryType?: string | null;
|
|
50
57
|
tier?: number | null;
|
|
51
|
-
parentIds?: readonly string[] | null;
|
|
52
|
-
isLeaf?: boolean | null;
|
|
53
58
|
}
|
|
54
|
-
/**
|
|
55
|
-
* A LEAF whose ancestry passes through a `*-parts` node.
|
|
56
|
-
*
|
|
57
|
-
* 🛑 `isLeaf` is required, not incidental. Without it the tab also matches the
|
|
58
|
-
* 20 classification NODES — "Attack Rings", "Weight Disks" — because their own
|
|
59
|
-
* `parentIds` include a `*-parts` node too, giving 772 entries that mix groups
|
|
60
|
-
* with the parts inside them. A part is a leaf by itself; the classification is
|
|
61
|
-
* a branch you reach by drilling down.
|
|
62
|
-
*/
|
|
63
|
-
export declare function isPartCategory(c: TabbableCategory): boolean;
|
|
64
59
|
/**
|
|
65
60
|
* Does this row belong on the given tab?
|
|
66
61
|
*
|
|
@@ -10,63 +10,48 @@
|
|
|
10
10
|
* this shape is browsed, and what the category detail page already supports
|
|
11
11
|
* (it fetches every descendant and renders them tier-grouped).
|
|
12
12
|
*
|
|
13
|
-
* ## 🛑 Parts
|
|
13
|
+
* ## 🛑 There is no Parts tab, and that is deliberate
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* It was removed 2026-10-20. Parts have a better home than a flat tab over
|
|
16
|
+
* 752 leaves: `/parts/[classification]/[code]` is a purpose-built part
|
|
17
|
+
* catalogue with composition data, and the `spares` listing type sells loose
|
|
18
|
+
* parts. A fifth tab listing every part leaf duplicated the worse half of
|
|
19
|
+
* both.
|
|
20
|
+
*
|
|
21
|
+
* The part leaves themselves are untouched and still reachable by drilling
|
|
22
|
+
* down from their `*-parts` node, which is also why no `categoryType` was
|
|
23
|
+
* involved: a part is identified by ANCESTRY, never by a discriminator
|
|
24
|
+
* (`gen-category-parts.mjs` omits `categoryType` because 92 files read it and
|
|
25
|
+
* a new value opts a row out of every path that works).
|
|
26
|
+
*
|
|
27
|
+
* 🛑 Do not confuse this with the product-detail **Parts composition tab**
|
|
28
|
+
* ("What it is made of"), gated by `LISTING_TYPE_HAS_COMPOSITION`. That is a
|
|
29
|
+
* different feature on a different surface and is unaffected.
|
|
21
30
|
*
|
|
22
31
|
* ## Why the tabs partition ONE payload
|
|
23
32
|
*
|
|
24
|
-
* The listing already fetches `?flat=true` once. Deriving all
|
|
33
|
+
* The listing already fetches `?flat=true` once. Deriving all four tabs from
|
|
25
34
|
* that single cached response costs no extra request — a per-tab query would
|
|
26
|
-
* turn one fetch into
|
|
35
|
+
* turn one fetch into four for the same data.
|
|
27
36
|
*
|
|
28
37
|
* @tag domain:categories
|
|
29
38
|
* @tag layer:constants
|
|
30
39
|
* @tag pattern:shared-literal
|
|
31
40
|
* @tag sideEffects:none
|
|
32
41
|
*/
|
|
33
|
-
/** The tier-2 nodes every part leaf hangs beneath. */
|
|
34
|
-
export const PART_PARENT_IDS = [
|
|
35
|
-
"category-original-parts",
|
|
36
|
-
"category-metal-parts",
|
|
37
|
-
"category-burst-parts",
|
|
38
|
-
"category-x-parts",
|
|
39
|
-
];
|
|
40
42
|
/**
|
|
41
43
|
* 🛑 No "All" tab. It was the default and it was the problem — it is what
|
|
42
44
|
* rendered 1,185 undifferentiated rows. Every tab here answers a question
|
|
43
|
-
* ("which
|
|
45
|
+
* ("which brands do you carry?"); "all" answers none of them.
|
|
44
46
|
*/
|
|
45
47
|
export const CATEGORY_TABS = [
|
|
46
48
|
{ key: "categories", label: "Categories" },
|
|
47
49
|
{ key: "brands", label: "Brands" },
|
|
48
50
|
{ key: "franchises", label: "Franchises" },
|
|
49
|
-
{ key: "parts", label: "Parts" },
|
|
50
51
|
{ key: "bundles", label: "Bundles" },
|
|
51
52
|
];
|
|
52
53
|
export const DEFAULT_CATEGORY_TAB = "categories";
|
|
53
54
|
const typeOf = (c) => String(c.categoryType ?? "");
|
|
54
|
-
/**
|
|
55
|
-
* A LEAF whose ancestry passes through a `*-parts` node.
|
|
56
|
-
*
|
|
57
|
-
* 🛑 `isLeaf` is required, not incidental. Without it the tab also matches the
|
|
58
|
-
* 20 classification NODES — "Attack Rings", "Weight Disks" — because their own
|
|
59
|
-
* `parentIds` include a `*-parts` node too, giving 772 entries that mix groups
|
|
60
|
-
* with the parts inside them. A part is a leaf by itself; the classification is
|
|
61
|
-
* a branch you reach by drilling down.
|
|
62
|
-
*/
|
|
63
|
-
export function isPartCategory(c) {
|
|
64
|
-
if (typeOf(c))
|
|
65
|
-
return false; // brand / bundle / franchise / sublisting
|
|
66
|
-
if (c.isLeaf !== true)
|
|
67
|
-
return false;
|
|
68
|
-
return (c.parentIds ?? []).some((p) => PART_PARENT_IDS.includes(String(p)));
|
|
69
|
-
}
|
|
70
55
|
/**
|
|
71
56
|
* Does this row belong on the given tab?
|
|
72
57
|
*
|
|
@@ -83,13 +68,11 @@ export function matchesCategoryTab(c, tab) {
|
|
|
83
68
|
return type === "franchise";
|
|
84
69
|
case "bundles":
|
|
85
70
|
return type === "bundle";
|
|
86
|
-
case "parts":
|
|
87
|
-
return isPartCategory(c);
|
|
88
71
|
case "categories":
|
|
89
72
|
/*
|
|
90
|
-
* A plain, top-level category.
|
|
91
|
-
*
|
|
92
|
-
*
|
|
73
|
+
* A plain, top-level category. Part and model leaves are excluded by the
|
|
74
|
+
* tier test — no leaf is tier 0 — but a reader should not have to derive
|
|
75
|
+
* that, so the root test carries the meaning.
|
|
93
76
|
*/
|
|
94
77
|
return type === "" && Number(c.tier ?? -1) === 0;
|
|
95
78
|
default:
|
|
@@ -32,6 +32,18 @@ export declare class CategoriesRepository extends BaseRepository<CategoryDocumen
|
|
|
32
32
|
* indistinguishable from a count of nothing (Root Cause #72).
|
|
33
33
|
*/
|
|
34
34
|
liveTotalsFor(ids: readonly string[]): Promise<Map<string, number>>;
|
|
35
|
+
/**
|
|
36
|
+
* `db.getAll`, not N `findById` calls — same reasoning as `liveTotalsFor`
|
|
37
|
+
* just above. Built for the site-wide search index's local-resolution
|
|
38
|
+
* path: the client resolves candidate category ids from the public
|
|
39
|
+
* artifact at zero reads, and this is the one query the server still
|
|
40
|
+
* needs to turn those ids into real documents.
|
|
41
|
+
*
|
|
42
|
+
* A missing id is simply ABSENT from the returned array, never null —
|
|
43
|
+
* the caller filters, so there is nothing to distinguish a deleted
|
|
44
|
+
* category from one that never existed, and nothing needs to.
|
|
45
|
+
*/
|
|
46
|
+
getManyByIds(ids: readonly string[]): Promise<CategoryDocument[]>;
|
|
35
47
|
getCategoryBySlug(slug: string): Promise<CategoryDocument | null>;
|
|
36
48
|
getRootCategories(): Promise<CategoryDocument[]>;
|
|
37
49
|
getLeafCategories(): Promise<CategoryDocument[]>;
|
|
@@ -128,6 +128,26 @@ export class CategoriesRepository extends BaseRepository {
|
|
|
128
128
|
const { total } = await getLiveCategoryCounts(this.db, ids);
|
|
129
129
|
return total;
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* `db.getAll`, not N `findById` calls — same reasoning as `liveTotalsFor`
|
|
133
|
+
* just above. Built for the site-wide search index's local-resolution
|
|
134
|
+
* path: the client resolves candidate category ids from the public
|
|
135
|
+
* artifact at zero reads, and this is the one query the server still
|
|
136
|
+
* needs to turn those ids into real documents.
|
|
137
|
+
*
|
|
138
|
+
* A missing id is simply ABSENT from the returned array, never null —
|
|
139
|
+
* the caller filters, so there is nothing to distinguish a deleted
|
|
140
|
+
* category from one that never existed, and nothing needs to.
|
|
141
|
+
*/
|
|
142
|
+
async getManyByIds(ids) {
|
|
143
|
+
if (ids.length === 0)
|
|
144
|
+
return [];
|
|
145
|
+
const refs = ids.map((id) => this.getCollection().doc(id));
|
|
146
|
+
const snaps = await this.db.getAll(...refs);
|
|
147
|
+
return snaps
|
|
148
|
+
.filter((s) => s.exists)
|
|
149
|
+
.map((s) => this.mapDoc(s));
|
|
150
|
+
}
|
|
131
151
|
async getCategoryBySlug(slug) {
|
|
132
152
|
try {
|
|
133
153
|
const snapshot = await this.db
|
|
@@ -126,6 +126,27 @@ export interface CategoryDocument extends BaseDocument {
|
|
|
126
126
|
brandCountry?: string;
|
|
127
127
|
/** Brand founding year — categoryType==="brand". */
|
|
128
128
|
brandFounded?: number;
|
|
129
|
+
/**
|
|
130
|
+
* The Hasbro / international-market name for this generation or wave, where
|
|
131
|
+
* it genuinely differs from the Takara-Tomy one. Two real pairs exist and
|
|
132
|
+
* they are separate waves, not one renaming: TT **God** ships as Hasbro
|
|
133
|
+
* **Evolution**, and TT **Cho-Z** ships as Hasbro **Turbo**.
|
|
134
|
+
*
|
|
135
|
+
* 🛑 RENDERED — "Also known as" on the category detail page. That is the
|
|
136
|
+
* whole difference from `aliases` below, which feeds `searchTxt` and is
|
|
137
|
+
* rendered nowhere. A buyer who only knows the Hasbro season name needs to
|
|
138
|
+
* SEE the correspondence, not merely be found by it.
|
|
139
|
+
*
|
|
140
|
+
* Left unset wherever a divergence is not confirmed. An honest gap beats a
|
|
141
|
+
* guessed pairing — same precedent as the 25 of 220 models with no cover and
|
|
142
|
+
* the deliberately partial `FRANCHISE_BY_CATEGORY_ROOT`.
|
|
143
|
+
*/
|
|
144
|
+
hasbroName?: string;
|
|
145
|
+
/**
|
|
146
|
+
* Cover for the Hasbro-named variant, shown beside the primary cover when
|
|
147
|
+
* both exist. Same `/media/<slug>` form as `display.coverImage`.
|
|
148
|
+
*/
|
|
149
|
+
hasbroCoverImage?: string;
|
|
129
150
|
/**
|
|
130
151
|
* Short bullet points — "why shop this category/brand." Category and
|
|
131
152
|
* brand detail pages only; renders nothing when absent/empty.
|
|
@@ -255,7 +276,7 @@ export declare const MIN_ITEMS_FOR_FEATURED: 8;
|
|
|
255
276
|
export declare const MAX_FEATURED_CATEGORIES: 4;
|
|
256
277
|
export declare const DEFAULT_CATEGORY_DATA: Partial<CategoryDocument>;
|
|
257
278
|
export declare const CATEGORIES_PUBLIC_FIELDS: readonly ["id", "name", "slug", "description", "rootId", "parentIds", "childrenIds", "tier", "path", "order", "position", "subtreeSize", "isLeaf", "metrics.totalProductCount", "metrics.totalAuctionCount", "metrics.totalItemCount", "isFeatured", "featuredPriority", "isBrand", "seo", "display", "isActive", "ancestors", "highlights", "faqs", "contentBody", "descriptionTemplates", "productDefaults", "aliases"];
|
|
258
|
-
export declare const CATEGORIES_UPDATABLE_FIELDS: readonly ["name", "slug", "description", "order", "isFeatured", "featuredPriority", "isBrand", "seo", "display", "isActive", "isSearchable", "highlights", "faqs", "contentBody", "descriptionTemplates", "productDefaults", "aliases"];
|
|
279
|
+
export declare const CATEGORIES_UPDATABLE_FIELDS: readonly ["name", "slug", "description", "order", "isFeatured", "featuredPriority", "isBrand", "seo", "display", "isActive", "isSearchable", "highlights", "faqs", "contentBody", "descriptionTemplates", "productDefaults", "aliases", "hasbroName", "hasbroCoverImage"];
|
|
259
280
|
export type CategoryCreateInput = Omit<CategoryDocument, "id" | "createdAt" | "updatedAt" | "metrics" | "isLeaf" | "ancestors"> & {
|
|
260
281
|
parentIds?: string[];
|
|
261
282
|
parentId?: string | null;
|
|
@@ -123,6 +123,16 @@ export const CATEGORIES_UPDATABLE_FIELDS = [
|
|
|
123
123
|
"descriptionTemplates",
|
|
124
124
|
"productDefaults",
|
|
125
125
|
"aliases",
|
|
126
|
+
/*
|
|
127
|
+
* The regional-naming pair. Updatable for the same reason the five above
|
|
128
|
+
* are: without it `CategoryUpdateInput` cannot express the field and the
|
|
129
|
+
* admin editor would save a 200 and write nothing. Wave names are editorial
|
|
130
|
+
* and get corrected — the one case this plan would not set (Burst Ultimate ↔
|
|
131
|
+
* QuadStrike) is exactly the kind a human may later confirm and want to fill
|
|
132
|
+
* in without a reseed.
|
|
133
|
+
*/
|
|
134
|
+
"hasbroName",
|
|
135
|
+
"hasbroCoverImage",
|
|
126
136
|
];
|
|
127
137
|
export const categoryQueryHelpers = {
|
|
128
138
|
bySlug: (slug) => ["slug", "==", slug],
|
|
@@ -769,6 +769,8 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
|
|
|
769
769
|
brandWebsite: z.ZodOptional<z.ZodString>;
|
|
770
770
|
brandCountry: z.ZodOptional<z.ZodString>;
|
|
771
771
|
brandFounded: z.ZodOptional<z.ZodNumber>;
|
|
772
|
+
hasbroName: z.ZodOptional<z.ZodString>;
|
|
773
|
+
hasbroCoverImage: z.ZodOptional<z.ZodString>;
|
|
772
774
|
highlights: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
773
775
|
faqs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
774
776
|
question: z.ZodString;
|
|
@@ -1363,6 +1365,8 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
|
|
|
1363
1365
|
brandWebsite?: string | undefined;
|
|
1364
1366
|
brandCountry?: string | undefined;
|
|
1365
1367
|
brandFounded?: number | undefined;
|
|
1368
|
+
hasbroName?: string | undefined;
|
|
1369
|
+
hasbroCoverImage?: string | undefined;
|
|
1366
1370
|
highlights?: string[] | undefined;
|
|
1367
1371
|
contentBody?: string | undefined;
|
|
1368
1372
|
descriptionTemplates?: {
|
|
@@ -1534,6 +1538,8 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
|
|
|
1534
1538
|
brandWebsite?: string | undefined;
|
|
1535
1539
|
brandCountry?: string | undefined;
|
|
1536
1540
|
brandFounded?: number | undefined;
|
|
1541
|
+
hasbroName?: string | undefined;
|
|
1542
|
+
hasbroCoverImage?: string | undefined;
|
|
1537
1543
|
highlights?: string[] | undefined;
|
|
1538
1544
|
contentBody?: string | undefined;
|
|
1539
1545
|
descriptionTemplates?: {
|
|
@@ -167,6 +167,8 @@ export const categoryFirestoreSchema = z.object({
|
|
|
167
167
|
brandWebsite: z.string().optional(),
|
|
168
168
|
brandCountry: z.string().optional(),
|
|
169
169
|
brandFounded: z.number().int().optional(),
|
|
170
|
+
hasbroName: z.string().optional(),
|
|
171
|
+
hasbroCoverImage: z.string().optional(),
|
|
170
172
|
highlights: z.array(z.string()).optional(),
|
|
171
173
|
faqs: z.array(z.object({ question: z.string(), answer: z.string() })).optional(),
|
|
172
174
|
aliases: z.array(z.string()).optional(),
|
|
@@ -49,6 +49,14 @@ export interface CategoryItem {
|
|
|
49
49
|
brandCountry?: string;
|
|
50
50
|
/** Brand founded year — only set when categoryType==="brand". */
|
|
51
51
|
brandFounded?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Hasbro / international-market name for this generation or wave (TT "God" →
|
|
54
|
+
* Hasbro "Evolution"; TT "Cho-Z" → Hasbro "Turbo"). Rendered as "Also known
|
|
55
|
+
* as" — unlike `aliases`, which only feeds search.
|
|
56
|
+
*/
|
|
57
|
+
hasbroName?: string;
|
|
58
|
+
/** Cover for the Hasbro-named variant, shown beside the primary cover. */
|
|
59
|
+
hasbroCoverImage?: string;
|
|
52
60
|
/** Short bullet points — "why shop this category/brand." */
|
|
53
61
|
highlights?: string[];
|
|
54
62
|
/** A handful of Q&A pairs shown on the category/brand detail page. */
|
|
@@ -14,8 +14,21 @@ export interface TaxonomyVisual {
|
|
|
14
14
|
/**
|
|
15
15
|
* Resolve the leading visual for a taxonomy row.
|
|
16
16
|
*
|
|
17
|
-
* Precedence: `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Precedence: an `icon` that is a URL → `coverImage` → an `icon` that is an
|
|
18
|
+
* EMOJI. Returns an empty object when the row has none, so the caller can fall
|
|
19
|
+
* back to an initial rather than reserving a blank box.
|
|
20
|
+
*
|
|
21
|
+
* 🛑 The emoji ranks LAST, and that ordering changed 2026-10-20. It used to be
|
|
22
|
+
* "icon, whatever it holds, then cover", which meant a row carrying both a
|
|
23
|
+
* real photograph and a decorative emoji rendered the emoji — the twelve
|
|
24
|
+
* forest roots are exactly that shape. `brand-rows.ts` trap 2 already names
|
|
25
|
+
* this as the defect rather than the intent: the brand seed leaves `icon`
|
|
26
|
+
* unset *precisely because* an emoji there would beat a real logo. Honouring
|
|
27
|
+
* that reading everywhere is what lets a root keep its emoji for menus while
|
|
28
|
+
* its page shows the photograph.
|
|
29
|
+
*
|
|
30
|
+
* An icon that is a URL still wins — that is `BrandsSection`'s long-standing
|
|
31
|
+
* convention, where `icon` holds the brand logo and is the more specific
|
|
32
|
+
* choice than a generic cover.
|
|
20
33
|
*/
|
|
21
34
|
export declare function resolveTaxonomyVisual(row: TaxonomyVisualSource | null | undefined): TaxonomyVisual;
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* either prints a raw URL as visible text, or hands an emoji to an
|
|
8
8
|
* `<img src>` that then 404s into a broken-image glyph.
|
|
9
9
|
*
|
|
10
|
-
* On top of that
|
|
11
|
-
*
|
|
12
|
-
* because an emoji
|
|
13
|
-
*
|
|
10
|
+
* On top of that there is an ORDERING question between the two, and
|
|
11
|
+
* `brand-rows.ts` trap 2 settles it: the brand seed leaves `icon` unset
|
|
12
|
+
* *precisely because* an emoji there would win over a real logo. So an
|
|
13
|
+
* emoji must rank BELOW a real cover (it is decoration), while an icon
|
|
14
|
+
* that is a URL ranks above one (it is the more specific asset). That is
|
|
15
|
+
* the precedence implemented below, corrected 2026-10-20.
|
|
14
16
|
*
|
|
15
17
|
* Two decisions, both easy to get wrong, previously made at each call
|
|
16
18
|
* site. This is the one place that makes them.
|
|
@@ -42,17 +44,31 @@ function looksLikeUrl(value) {
|
|
|
42
44
|
/**
|
|
43
45
|
* Resolve the leading visual for a taxonomy row.
|
|
44
46
|
*
|
|
45
|
-
* Precedence: `
|
|
46
|
-
*
|
|
47
|
-
*
|
|
47
|
+
* Precedence: an `icon` that is a URL → `coverImage` → an `icon` that is an
|
|
48
|
+
* EMOJI. Returns an empty object when the row has none, so the caller can fall
|
|
49
|
+
* back to an initial rather than reserving a blank box.
|
|
50
|
+
*
|
|
51
|
+
* 🛑 The emoji ranks LAST, and that ordering changed 2026-10-20. It used to be
|
|
52
|
+
* "icon, whatever it holds, then cover", which meant a row carrying both a
|
|
53
|
+
* real photograph and a decorative emoji rendered the emoji — the twelve
|
|
54
|
+
* forest roots are exactly that shape. `brand-rows.ts` trap 2 already names
|
|
55
|
+
* this as the defect rather than the intent: the brand seed leaves `icon`
|
|
56
|
+
* unset *precisely because* an emoji there would beat a real logo. Honouring
|
|
57
|
+
* that reading everywhere is what lets a root keep its emoji for menus while
|
|
58
|
+
* its page shows the photograph.
|
|
59
|
+
*
|
|
60
|
+
* An icon that is a URL still wins — that is `BrandsSection`'s long-standing
|
|
61
|
+
* convention, where `icon` holds the brand logo and is the more specific
|
|
62
|
+
* choice than a generic cover.
|
|
48
63
|
*/
|
|
49
64
|
export function resolveTaxonomyVisual(row) {
|
|
50
65
|
const icon = row?.display?.icon?.trim();
|
|
51
|
-
if (icon)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
66
|
+
if (icon && looksLikeUrl(icon))
|
|
67
|
+
return { imageUrl: icon };
|
|
54
68
|
const cover = row?.display?.coverImage?.trim();
|
|
55
69
|
if (cover && looksLikeUrl(cover))
|
|
56
70
|
return { imageUrl: cover };
|
|
71
|
+
if (icon)
|
|
72
|
+
return { emoji: icon };
|
|
57
73
|
return {};
|
|
58
74
|
}
|