@mohasinac/appkit 4.47.0 → 4.48.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 +4 -4
- package/dist/_internal/server/features/checkout/expand-order-items.js +3 -0
- package/dist/_internal/server/features/messages/actions.d.ts +68 -0
- package/dist/_internal/server/features/messages/actions.js +249 -0
- package/dist/_internal/server/features/parts/buildable.d.ts +47 -0
- package/dist/_internal/server/features/parts/buildable.js +159 -0
- package/dist/_internal/server/features/products/list-public.d.ts +17 -0
- package/dist/_internal/server/features/products/list-public.js +92 -5
- package/dist/_internal/server/features/products/og.d.ts +7 -0
- package/dist/_internal/server/features/products/og.js +38 -0
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +6 -0
- package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +28 -0
- package/dist/_internal/server/jobs/core/notificationPrune.js +87 -3
- package/dist/_internal/server/jobs/core/priceIndexRollup.js +36 -1
- package/dist/_internal/shared/actions/action-registry.js +8 -0
- package/dist/_internal/shared/features/notifications/action-url.js +8 -0
- package/dist/_internal/shared/features/notifications/email-eligibility.js +3 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +4 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.js +8 -0
- package/dist/constants/api-endpoints.d.ts +48 -0
- package/dist/constants/api-endpoints.js +30 -0
- package/dist/constants/notification-types.d.ts +1 -1
- package/dist/features/about/components/GstExplainerView.d.ts +28 -0
- package/dist/features/about/components/GstExplainerView.js +59 -0
- package/dist/features/about/components/GuidesHubView.d.ts +28 -0
- package/dist/features/about/components/GuidesHubView.js +58 -0
- package/dist/features/about/components/PublicProfileView.js +5 -0
- package/dist/features/about/components/SpotAFakeView.d.ts +29 -0
- package/dist/features/about/components/SpotAFakeView.js +49 -0
- package/dist/features/admin/actions/notification-actions.js +4 -0
- package/dist/features/admin/schemas/firestore.d.ts +13 -3
- package/dist/features/admin/schemas/firestore.js +8 -0
- package/dist/features/blog/repository/blog.repository.js +21 -7
- package/dist/features/categories/api/route.js +41 -1
- package/dist/features/categories/components/BreadcrumbTrail.d.ts +39 -17
- package/dist/features/categories/components/BreadcrumbTrail.js +46 -50
- package/dist/features/categories/components/CategoryDetailPageView.js +92 -38
- package/dist/features/categories/components/ContextualTrail.d.ts +62 -0
- package/dist/features/categories/components/ContextualTrail.js +81 -0
- package/dist/features/categories/components/LastCategoryBeacon.d.ts +18 -0
- package/dist/features/categories/components/LastCategoryBeacon.js +33 -0
- package/dist/features/categories/repository/categories.repository.d.ts +21 -0
- package/dist/features/categories/repository/categories.repository.js +36 -3
- package/dist/features/categories/repository/price-index.repository.d.ts +25 -0
- package/dist/features/email/notification-templates.js +4 -0
- package/dist/features/events/repository/events.repository.js +10 -3
- package/dist/features/faq/constants/read-caps.d.ts +29 -0
- package/dist/features/faq/constants/read-caps.js +29 -0
- package/dist/features/faq/repository/faqs.repository.d.ts +0 -2
- package/dist/features/faq/repository/faqs.repository.js +19 -16
- package/dist/features/homepage/components/AdSlot.js +10 -4
- package/dist/features/homepage/hooks/useActiveAd.d.ts +18 -1
- package/dist/features/homepage/hooks/useActiveAd.js +42 -2
- package/dist/features/homepage/lib/section-renderer.js +12 -2
- package/dist/features/layout/AppLayoutShell.d.ts +4 -1
- package/dist/features/layout/AppLayoutShell.js +2 -2
- package/dist/features/layout/TitleBarLayout.d.ts +4 -1
- package/dist/features/layout/TitleBarLayout.js +8 -4
- package/dist/features/media/AvatarUpload.js +8 -1
- package/dist/features/media/MediaAvatar.js +1 -1
- package/dist/features/media/MediaImage.js +3 -1
- package/dist/features/media/MediaLightbox.js +4 -1
- package/dist/features/media/MediaPickerModal.js +6 -1
- package/dist/features/media/MediaVideo.js +3 -1
- package/dist/features/messages/components/ConversationThreadView.d.ts +3 -0
- package/dist/features/messages/components/ConversationThreadView.js +75 -0
- package/dist/features/messages/components/ConversationsInboxView.d.ts +1 -0
- package/dist/features/messages/components/ConversationsInboxView.js +39 -0
- package/dist/features/messages/components/MessageSellerButton.d.ts +17 -0
- package/dist/features/messages/components/MessageSellerButton.js +33 -0
- package/dist/features/messages/hooks/useConversationChannel.d.ts +24 -0
- package/dist/features/messages/hooks/useConversationChannel.js +89 -0
- package/dist/features/messages/hooks/useMessages.d.ts +347 -0
- package/dist/features/messages/hooks/useMessages.js +113 -0
- package/dist/features/messages/repository/conversations.repository.d.ts +44 -0
- package/dist/features/messages/repository/conversations.repository.js +173 -0
- package/dist/features/messages/repository/messages.repository.d.ts +42 -0
- package/dist/features/messages/repository/messages.repository.js +142 -0
- package/dist/features/messages/schemas/firestore.d.ts +99 -0
- package/dist/features/messages/schemas/firestore.js +46 -0
- package/dist/features/orders/repository/orders.repository.js +21 -7
- package/dist/features/orders/schemas/firestore.d.ts +11 -0
- package/dist/features/products/components/CityCollectiblesView.d.ts +31 -0
- package/dist/features/products/components/CityCollectiblesView.js +82 -0
- package/dist/features/products/components/MarketPriceAnchor.d.ts +45 -0
- package/dist/features/products/components/MarketPriceAnchor.js +62 -0
- package/dist/features/products/components/ProductDetailPageView.js +71 -5
- package/dist/features/products/constants/meetup-cities.d.ts +47 -0
- package/dist/features/products/constants/meetup-cities.js +49 -0
- package/dist/features/products/repository/category-assignment.d.ts +82 -11
- package/dist/features/products/repository/category-assignment.js +93 -12
- package/dist/features/products/repository/products.repository.d.ts +12 -1
- package/dist/features/products/repository/products.repository.js +66 -34
- package/dist/features/products/schemas/firestore.d.ts +24 -2
- package/dist/features/products/schemas/firestore.js +13 -1
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/promotions/repository/coupons.repository.js +11 -4
- package/dist/features/reviews/repository/reviews.repository.js +11 -4
- package/dist/features/scams/repository/scammer.repository.js +21 -7
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +2 -2
- package/dist/features/seller/repository/offer.repository.js +21 -6
- package/dist/features/seller/schemas/product-form.d.ts +3 -3
- package/dist/features/seller/schemas/product-form.js +1 -1
- package/dist/features/server-errors/repository/server-errors.repository.js +11 -0
- package/dist/features/server-errors/schemas/firestore.d.ts +17 -0
- package/dist/features/server-errors/schemas/firestore.js +2 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +17 -1
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +31 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +1 -1
- package/dist/features/store-extensions/schemas/firestore.js +4 -0
- package/dist/features/stores/repository/store.repository.js +11 -3
- package/dist/features/tester/seed-data/authored/community-support__messages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/community-support__messages.js +200 -0
- package/dist/features/tester/seed-data/authored/index.js +2 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +64 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +1 -1
- package/dist/next/routing/route-map.d.ts +14 -0
- package/dist/next/routing/route-map.js +26 -0
- package/dist/providers/db-firebase/rtdb-paths.d.ts +8 -0
- package/dist/providers/db-firebase/rtdb-paths.js +8 -0
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +4 -0
- package/dist/schemas/registry.d.ts +3 -3
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-schemas.d.ts +17 -0
- package/dist/security/pii-schemas.js +15 -7
- package/dist/seed/_helpers/category-content-generated.d.ts +3 -3
- package/dist/seed/_helpers/category-content-generated.js +23 -7
- package/dist/seed/_helpers/category-models.d.ts +1 -1
- package/dist/seed/_helpers/category-models.js +25 -1
- package/dist/seed/_helpers/category-names.d.ts +54 -0
- package/dist/seed/_helpers/category-names.js +118 -0
- package/dist/seed/_helpers/character-features.d.ts +1 -1
- package/dist/seed/_helpers/character-features.js +5 -4
- package/dist/seed/_helpers/final-sale-fixtures.js +1 -1
- package/dist/seed/categories-seed-data.js +55 -1
- package/dist/seed/grouped-listings-seed-data.js +2 -2
- package/dist/seed/products-art-seed-data.js +2 -1
- package/dist/seed/products-auctions-seed-data.js +42 -22
- package/dist/seed/products-classifieds-seed-data.js +21 -3
- package/dist/seed/products-digital-codes-seed-data.js +2 -1
- package/dist/seed/products-live-items-seed-data.js +2 -1
- package/dist/seed/products-preorders-seed-data.js +26 -11
- package/dist/seed/products-prize-draws-seed-data.js +8 -1
- package/dist/seed/products-standard-seed-data.js +26 -25
- package/dist/seed/products-stickers-seed-data.js +12 -11
- package/dist/server.d.ts +8 -0
- package/dist/server.js +15 -0
- package/dist/styles.css +113 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/sunset.js +17 -0
- package/dist/tokens/tokens.css +5 -0
- package/dist/ui/components/Breadcrumb.d.ts +8 -1
- package/dist/ui/components/Breadcrumb.js +2 -2
- package/dist/ui/components/Breadcrumb.style.css +37 -0
- package/dist/ui/components/Details.d.ts +11 -1
- package/dist/ui/components/Details.js +2 -2
- package/dist/ui/components/ImageMosaic.d.ts +43 -0
- package/dist/ui/components/ImageMosaic.js +46 -0
- package/dist/ui/components/ImageMosaic.style.css +69 -0
- package/dist/ui/components/SplitTile.d.ts +34 -0
- package/dist/ui/components/SplitTile.js +28 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +4 -0
- package/dist/utils/search-txt-query.d.ts +77 -0
- package/dist/utils/search-txt-query.js +50 -28
- package/package.json +1 -1
|
@@ -505,7 +505,7 @@ const rawTesterChecklistItems = [
|
|
|
505
505
|
{
|
|
506
506
|
key: "tester-fixtures-hidden-from-the-public",
|
|
507
507
|
label: "SIGNED OUT, no product on /products has an id containing \"tester\" — the sandbox is invisible to the public",
|
|
508
|
-
description: "Fixed 2026-09-01. BEFORE: the public products endpoint had no viewer concept, so an ANONYMOUS caller got 25 tester fixtures in its first 40 rows —
|
|
508
|
+
description: "Fixed 2026-09-01. BEFORE: the public products endpoint had no viewer concept, so an ANONYMOUS caller got 25 tester fixtures in its first 40 rows — sticker-beyblade-original-classic-sheet, prizedraw-beyblade-metal-closed-revealed, live-golden-retriever-puppy — and they filled the public marketplace. AFTER: open /products in a private window and scan the grid; nothing should be named \"Tester …\". Then sign in AS A TESTER and reload — the sandbox items must come back. Both halves matter: hiding them from everyone would break the tester programme, and a fixture that never reappears is the opposite bug.",
|
|
509
509
|
href: "/products",
|
|
510
510
|
},
|
|
511
511
|
{
|
|
@@ -3519,6 +3519,69 @@ const rawTesterChecklistItems = [
|
|
|
3519
3519
|
},
|
|
3520
3520
|
]),
|
|
3521
3521
|
...group("community-support", "Community & Support", [
|
|
3522
|
+
{
|
|
3523
|
+
/*
|
|
3524
|
+
* D9 chat. 🛑 Chat is OFF by default (`siteSettings.chat.enabled`, and
|
|
3525
|
+
* `undefined` reads as off), so case 1 asserts the off-state and case 2
|
|
3526
|
+
* turns it on — every case after depends on that order. Steps live in
|
|
3527
|
+
* ./authored/community-support__messages.ts.
|
|
3528
|
+
*/
|
|
3529
|
+
pageKey: "messages",
|
|
3530
|
+
pageLabel: "Messages (chat)",
|
|
3531
|
+
href: "/messages",
|
|
3532
|
+
cases: [
|
|
3533
|
+
{
|
|
3534
|
+
key: "chat-off-hides-everything",
|
|
3535
|
+
label: "With chat disabled, no Message Seller button, no header icon, and no unread-count request — absent, not disabled",
|
|
3536
|
+
description: "The baseline. `siteSettings.chat.enabled` is undefined on a fresh seed and must read as OFF. A feature that half-renders when disabled is the shape the original chat died of.",
|
|
3537
|
+
},
|
|
3538
|
+
{
|
|
3539
|
+
key: "enable-chat-reveals-entry-points",
|
|
3540
|
+
label: "Turning the Chat setting on makes both entry points appear with no code change or redeploy",
|
|
3541
|
+
},
|
|
3542
|
+
{
|
|
3543
|
+
key: "message-seller-creates-thread",
|
|
3544
|
+
label: "Message Seller creates ONE conversation, and clicking it again reuses that same thread rather than making a second",
|
|
3545
|
+
description: "A conversation is between two USERS, not per listing — a buyer with three questions about three items gets one thread. The storeId→ownerId resolution happens server-side because a product page never knows the owner's Auth UID.",
|
|
3546
|
+
},
|
|
3547
|
+
{
|
|
3548
|
+
key: "send-and-recipient-sees-it",
|
|
3549
|
+
label: "A sent message reaches the other participant with an unread badge, and opening the thread clears it",
|
|
3550
|
+
roles: ["buyer", "seller"],
|
|
3551
|
+
},
|
|
3552
|
+
{
|
|
3553
|
+
key: "pii-is-stripped-from-the-body",
|
|
3554
|
+
label: "A phone number, a dotted VPA and a bare name@bank VPA are ALL redacted before the message is stored",
|
|
3555
|
+
description: "The rule order is the fix: specific before general. A bare `name@bank` has no TLD dot so it matches neither the email nor the phone rule, and running phone first mangles `9876543210@upi` before the UPI rule sees it.",
|
|
3556
|
+
},
|
|
3557
|
+
{
|
|
3558
|
+
key: "retry-does-not-double-post",
|
|
3559
|
+
label: "Retrying a failed send posts the SAME message once, never a duplicate",
|
|
3560
|
+
description: "clientMessageId is generated once at compose time, not per attempt, and is the message's document id — so a replay resolves to the existing document.",
|
|
3561
|
+
},
|
|
3562
|
+
{
|
|
3563
|
+
key: "soft-ban-blocks-sending",
|
|
3564
|
+
label: "A user soft-banned from 'Send messages' is refused with the ban's own reason",
|
|
3565
|
+
description: "send_messages was a live BannedAction with NO reader anywhere — an admin could set it and nothing checked it. The check now lives in the domain function so every caller is covered.",
|
|
3566
|
+
roles: ["admin", "buyer"],
|
|
3567
|
+
},
|
|
3568
|
+
{
|
|
3569
|
+
key: "block-stops-the-other-side",
|
|
3570
|
+
label: "Blocking a participant refuses their next message with a clear reason, and leaves existing history readable",
|
|
3571
|
+
roles: ["buyer", "seller"],
|
|
3572
|
+
},
|
|
3573
|
+
{
|
|
3574
|
+
key: "live-ping-surfaces-failure",
|
|
3575
|
+
label: "When the realtime channel cannot connect, a visible warning says so — the failure is never swallowed",
|
|
3576
|
+
description: "THE control for the bug that killed the old feature: it swallowed permission-denied into setIsConnected(false) and a literal `// ignore`, so the channel looked implemented and was inert for its whole life. The message is still delivered; only the live notification is lost.",
|
|
3577
|
+
},
|
|
3578
|
+
{
|
|
3579
|
+
key: "inbox-read-is-bounded",
|
|
3580
|
+
label: "The inbox and thread reads are both cursor-paginated and capped — open 50 threads and the read count does not scale with them",
|
|
3581
|
+
description: "The negative control. The absence of a .limit() is literally what turned 20 messages into ~1,600 reads the first time this existed. Visible only in the network panel, never on the rendered page.",
|
|
3582
|
+
},
|
|
3583
|
+
],
|
|
3584
|
+
},
|
|
3522
3585
|
{
|
|
3523
3586
|
pageKey: "support-tickets",
|
|
3524
3587
|
pageLabel: "Support Tickets",
|
package/dist/index.d.ts
CHANGED
|
@@ -652,8 +652,8 @@ export { groupedListingsSeedData } from "./seed/index";
|
|
|
652
652
|
export type { CouponDiscountType } from "./seed/index";
|
|
653
653
|
export type { FirestoreIndexConfig } from "./seed/index";
|
|
654
654
|
export type { HomepageSectionType } from "./seed/index";
|
|
655
|
-
export type { NotificationType } from "./features/admin/schemas/firestore";
|
|
656
|
-
export { NOTIFICATION_TYPE_VALUES } from "./features/admin/schemas/firestore";
|
|
655
|
+
export type { NotificationType, NotificationRelatedType } from "./features/admin/schemas/firestore";
|
|
656
|
+
export { NOTIFICATION_TYPE_VALUES, NOTIFICATION_RELATED_TYPE_VALUES } from "./features/admin/schemas/firestore";
|
|
657
657
|
export type { PayoutStatus } from "./seed/index";
|
|
658
658
|
export type { SeedAddressDocument } from "./seed/index";
|
|
659
659
|
export type { SeedBaseOrderDocument } from "./seed/index";
|
|
@@ -3228,3 +3228,9 @@ export { resolveReturnAddress, RTO_ENTRY_CUSTODY, rtoCostBearer, RETURN_ABUSE_SC
|
|
|
3228
3228
|
export type { ReturnAddressResolution, ResolveReturnAddressInput, ReturnAbuseSignal, ReturnAbuseCounters, RtoCostBearer, } from "./_internal/shared/features/returns/destination";
|
|
3229
3229
|
export { COURIER_RATING_MIN_CONFIDENT_SAMPLES, courierRatingDisplay, isValidCourierVote, courierVoteId, rollUpCourierVotes, } from "./_internal/shared/features/shipping/courier-rating";
|
|
3230
3230
|
export type { CourierRatingPair, CourierRatingDisplay, CourierRatingVote, CourierStats, } from "./_internal/shared/features/shipping/courier-rating";
|
|
3231
|
+
/**
|
|
3232
|
+
* D9 — chat. Plain data types, safe for client components that render a
|
|
3233
|
+
* conversation/message prop — the write path (actions) stays server-only, see
|
|
3234
|
+
* server.ts.
|
|
3235
|
+
*/
|
|
3236
|
+
export type { ConversationDocument, ConversationStatus, ConversationLastMessage, ParticipantRole, MessageDocument, MessageProductRef, } from "./features/messages/schemas/firestore";
|
package/dist/index.js
CHANGED
|
@@ -1364,7 +1364,7 @@ export { historySeedData } from "./seed/index.js";
|
|
|
1364
1364
|
// SB-UNI-B â€" sublistingCategoriesSeedData merged into categoriesSeedData with categoryType:"sublisting".
|
|
1365
1365
|
// groupedListingsSeedData - Model for grouped listings seed data.
|
|
1366
1366
|
export { groupedListingsSeedData } from "./seed/index.js";
|
|
1367
|
-
export { NOTIFICATION_TYPE_VALUES } from "./features/admin/schemas/firestore.js";
|
|
1367
|
+
export { NOTIFICATION_TYPE_VALUES, NOTIFICATION_RELATED_TYPE_VALUES } from "./features/admin/schemas/firestore.js";
|
|
1368
1368
|
// [UTIL]-Pure utility â€" no framework or runtime dependency; safe to import from any environment.
|
|
1369
1369
|
// ErrorCategory - Shared export for error category.
|
|
1370
1370
|
export { ErrorCategory } from "./monitoring/index.js";
|
|
@@ -86,6 +86,10 @@ export declare const DEFAULT_ROUTE_MAP: {
|
|
|
86
86
|
readonly HOW_REVIEWS_WORK: "/how-reviews-work";
|
|
87
87
|
readonly HOW_PAYOUTS_WORK: "/how-payouts-work";
|
|
88
88
|
readonly FEES: "/fees";
|
|
89
|
+
readonly GST: "/gst";
|
|
90
|
+
readonly SPOT_A_FAKE: "/spot-a-fake";
|
|
91
|
+
readonly GUIDES: "/guides";
|
|
92
|
+
readonly CITY_COLLECTIBLES: (city: string) => string;
|
|
89
93
|
readonly EVENTS: "/events";
|
|
90
94
|
readonly EVENT_DETAIL: (id: string) => string;
|
|
91
95
|
readonly EVENT_PARTICIPATE: (id: string) => string;
|
|
@@ -175,6 +179,9 @@ export declare const DEFAULT_ROUTE_MAP: {
|
|
|
175
179
|
readonly CATALOGUE: "/user/catalogue";
|
|
176
180
|
readonly CATALOGUE_NEW: "/user/catalogue/new";
|
|
177
181
|
readonly CATALOGUE_EDIT: (id: string) => string;
|
|
182
|
+
/** D9 — one universal inbox, same real-top-level-path precedent as WISHLIST/CART. */
|
|
183
|
+
readonly MESSAGES: "/messages";
|
|
184
|
+
readonly MESSAGE_THREAD: (conversationId: string) => string;
|
|
178
185
|
};
|
|
179
186
|
readonly STORE: {
|
|
180
187
|
readonly DASHBOARD: "/store";
|
|
@@ -493,6 +500,10 @@ export declare const ROUTES: {
|
|
|
493
500
|
readonly HOW_REVIEWS_WORK: "/how-reviews-work";
|
|
494
501
|
readonly HOW_PAYOUTS_WORK: "/how-payouts-work";
|
|
495
502
|
readonly FEES: "/fees";
|
|
503
|
+
readonly GST: "/gst";
|
|
504
|
+
readonly SPOT_A_FAKE: "/spot-a-fake";
|
|
505
|
+
readonly GUIDES: "/guides";
|
|
506
|
+
readonly CITY_COLLECTIBLES: (city: string) => string;
|
|
496
507
|
readonly EVENTS: "/events";
|
|
497
508
|
readonly EVENT_DETAIL: (id: string) => string;
|
|
498
509
|
readonly EVENT_PARTICIPATE: (id: string) => string;
|
|
@@ -582,6 +593,9 @@ export declare const ROUTES: {
|
|
|
582
593
|
readonly CATALOGUE: "/user/catalogue";
|
|
583
594
|
readonly CATALOGUE_NEW: "/user/catalogue/new";
|
|
584
595
|
readonly CATALOGUE_EDIT: (id: string) => string;
|
|
596
|
+
/** D9 — one universal inbox, same real-top-level-path precedent as WISHLIST/CART. */
|
|
597
|
+
readonly MESSAGES: "/messages";
|
|
598
|
+
readonly MESSAGE_THREAD: (conversationId: string) => string;
|
|
585
599
|
};
|
|
586
600
|
readonly STORE: {
|
|
587
601
|
readonly DASHBOARD: "/store";
|
|
@@ -79,6 +79,29 @@ export const DEFAULT_ROUTE_MAP = {
|
|
|
79
79
|
HOW_REVIEWS_WORK: "/how-reviews-work",
|
|
80
80
|
HOW_PAYOUTS_WORK: "/how-payouts-work",
|
|
81
81
|
FEES: "/fees",
|
|
82
|
+
/*
|
|
83
|
+
* D6b — the GST explainer. A differentiator rather than table stakes:
|
|
84
|
+
* exactly ONE of the seventeen crawled Indian collectibles sites states
|
|
85
|
+
* tax-inclusive pricing to the buyer in words, and sixteen say nothing.
|
|
86
|
+
*/
|
|
87
|
+
GST: "/gst",
|
|
88
|
+
/*
|
|
89
|
+
* D6 — the anti-counterfeit guide. Framed on what the crawl actually
|
|
90
|
+
* measured: the biggest copy-seller in this market makes no authenticity
|
|
91
|
+
* claim at all, so the signals are silence + price, then code, then weight.
|
|
92
|
+
*/
|
|
93
|
+
SPOT_A_FAKE: "/spot-a-fake",
|
|
94
|
+
/* D6 — the content hub. Fourteen explainers whose only entry point was the
|
|
95
|
+
* footer; a footer link is reachable but it is not a destination, so it
|
|
96
|
+
* cannot rank and gives a crawler no page establishing the cluster. */
|
|
97
|
+
GUIDES: "/guides",
|
|
98
|
+
/*
|
|
99
|
+
* D5 — local SEO, one page per city a classified listing actually names.
|
|
100
|
+
* 🛑 STATIC (`generateStaticParams`): a city page that awaited
|
|
101
|
+
* `searchParams` would be dynamic, and this is the surface most likely to
|
|
102
|
+
* be crawled widely (Root Cause #94).
|
|
103
|
+
*/
|
|
104
|
+
CITY_COLLECTIBLES: (city) => `/collectibles/${city}`,
|
|
82
105
|
EVENTS: "/events",
|
|
83
106
|
EVENT_DETAIL: (id) => `/events/${id}`,
|
|
84
107
|
EVENT_PARTICIPATE: (id) => `/events/${id}/participate`,
|
|
@@ -197,6 +220,9 @@ export const DEFAULT_ROUTE_MAP = {
|
|
|
197
220
|
CATALOGUE: "/user/catalogue",
|
|
198
221
|
CATALOGUE_NEW: "/user/catalogue/new",
|
|
199
222
|
CATALOGUE_EDIT: (id) => `/user/catalogue/${id}/edit`,
|
|
223
|
+
/** D9 — one universal inbox, same real-top-level-path precedent as WISHLIST/CART. */
|
|
224
|
+
MESSAGES: "/messages",
|
|
225
|
+
MESSAGE_THREAD: (conversationId) => `/messages/${conversationId}`,
|
|
200
226
|
},
|
|
201
227
|
STORE: {
|
|
202
228
|
DASHBOARD: "/store",
|
|
@@ -26,4 +26,12 @@ export declare const RTDB_PATHS: {
|
|
|
26
26
|
* `cleanupRtdbEvents` alongside the other three.
|
|
27
27
|
*/
|
|
28
28
|
readonly EMAIL_EVENTS: "email_events";
|
|
29
|
+
/**
|
|
30
|
+
* D9 — new-message ping per conversation. RTDB-ping-plus-REST: the actual
|
|
31
|
+
* message content never crosses this channel, only "something changed,
|
|
32
|
+
* refetch" — `firestore.rules` forbids client Firestore access entirely, so
|
|
33
|
+
* this plus the bounded REST route is the only option, not a cost choice.
|
|
34
|
+
* Pruned by `cleanupRtdbEvents`'s sixth block.
|
|
35
|
+
*/
|
|
36
|
+
readonly CHAT_EVENTS: "chat_events";
|
|
29
37
|
};
|
|
@@ -26,4 +26,12 @@ export const RTDB_PATHS = {
|
|
|
26
26
|
* `cleanupRtdbEvents` alongside the other three.
|
|
27
27
|
*/
|
|
28
28
|
EMAIL_EVENTS: "email_events",
|
|
29
|
+
/**
|
|
30
|
+
* D9 — new-message ping per conversation. RTDB-ping-plus-REST: the actual
|
|
31
|
+
* message content never crosses this channel, only "something changed,
|
|
32
|
+
* refetch" — `firestore.rules` forbids client Firestore access entirely, so
|
|
33
|
+
* this plus the bounded REST route is the only option, not a cost choice.
|
|
34
|
+
* Pruned by `cleanupRtdbEvents`'s sixth block.
|
|
35
|
+
*/
|
|
36
|
+
CHAT_EVENTS: "chat_events",
|
|
29
37
|
};
|
|
@@ -64,3 +64,4 @@ export { MediaAssetsRepository, mediaAssetsRepository, } from "../features/media
|
|
|
64
64
|
export type { MediaAssetDocument, MediaAssetCreateInput, } from "../features/media/repository/media-assets.repository";
|
|
65
65
|
export { PageViewsRepository, pageViewsRepository, PAGE_VIEW_ENTITY_TYPES, } from "../features/analytics/repository/page-views.repository";
|
|
66
66
|
export type { PageViewDocument, PageViewEntityType, RecordPageViewInput, } from "../features/analytics/repository/page-views.repository";
|
|
67
|
+
export { ConversationsRepository, conversationsRepository, } from "../features/messages/repository/conversations.repository";
|
|
@@ -77,3 +77,7 @@ export { LotteryEntryRepository, lotteryEntryRepository, } from "../features/lot
|
|
|
77
77
|
// Media assets — short ID → storagePath mapping for SEO-friendly /media/{id} URLs
|
|
78
78
|
export { MediaAssetsRepository, mediaAssetsRepository, } from "../features/media/repository/media-assets.repository.js";
|
|
79
79
|
export { PageViewsRepository, pageViewsRepository, PAGE_VIEW_ENTITY_TYPES, } from "../features/analytics/repository/page-views.repository.js";
|
|
80
|
+
// D9 — chat. Messages themselves are a subcollection (functional module, see
|
|
81
|
+
// features/messages/repository/messages.repository.ts and server.ts), not a
|
|
82
|
+
// BaseRepository class — there is no subcollection form of BaseRepository.
|
|
83
|
+
export { ConversationsRepository, conversationsRepository, } from "../features/messages/repository/conversations.repository.js";
|
|
@@ -4660,7 +4660,7 @@ export declare const SCHEMAS: {
|
|
|
4660
4660
|
youtubeId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
4661
4661
|
externalVideoUrl: import("zod").ZodOptional<import("zod").ZodString>;
|
|
4662
4662
|
price: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4663
|
-
|
|
4663
|
+
originalPrice: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4664
4664
|
stockQuantity: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4665
4665
|
featured: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
4666
4666
|
isPromoted: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
@@ -4750,7 +4750,7 @@ export declare const SCHEMAS: {
|
|
|
4750
4750
|
youtubeId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
4751
4751
|
externalVideoUrl: import("zod").ZodOptional<import("zod").ZodString>;
|
|
4752
4752
|
price: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4753
|
-
|
|
4753
|
+
originalPrice: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4754
4754
|
stockQuantity: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4755
4755
|
featured: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
4756
4756
|
isPromoted: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
@@ -4840,7 +4840,7 @@ export declare const SCHEMAS: {
|
|
|
4840
4840
|
youtubeId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
4841
4841
|
externalVideoUrl: import("zod").ZodOptional<import("zod").ZodString>;
|
|
4842
4842
|
price: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4843
|
-
|
|
4843
|
+
originalPrice: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4844
4844
|
stockQuantity: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4845
4845
|
featured: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
4846
4846
|
isPromoted: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
package/dist/security/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export type { UserRole } from "./authorization";
|
|
|
6
6
|
export { redactPii, safeDisplayName, safeDisplayEmail, maskIp } from "./pii-redact";
|
|
7
7
|
export { encryptValue, decryptValue, hmacBlindIndex, encryptPiiFields, decryptPiiFields, encryptPii, decryptPii, piiBlindIndex, piiIndicesFor, getPiiConfigError, encryptPayoutDetails, decryptPayoutDetails, encryptShippingConfig, decryptShippingConfig, encryptPayoutBankAccount, decryptPayoutBankAccount, } from "./pii-encrypt";
|
|
8
8
|
export { isPiiEncrypted, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./pii-mask";
|
|
9
|
-
export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, } from "./pii-schemas";
|
|
9
|
+
export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, CONVERSATION_PII_FIELDS, MESSAGE_PII_FIELDS, } from "./pii-schemas";
|
|
10
10
|
export { encryptSecret, decryptSecret, isSecretEncrypted, maskSecret, } from "./settings-encryption";
|
package/dist/security/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { encryptValue, decryptValue, hmacBlindIndex, encryptPiiFields, decryptPi
|
|
|
7
7
|
// pii-encrypt.ts) so this barrel stays safe to re-export from the
|
|
8
8
|
// universal client+server index.ts. See pii-mask.ts's header comment.
|
|
9
9
|
export { isPiiEncrypted, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./pii-mask.js";
|
|
10
|
-
export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, } from "./pii-schemas.js";
|
|
10
|
+
export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, CONVERSATION_PII_FIELDS, MESSAGE_PII_FIELDS, } from "./pii-schemas.js";
|
|
11
11
|
export { encryptSecret, decryptSecret, isSecretEncrypted, maskSecret, } from "./settings-encryption.js";
|
|
12
12
|
// RBAC system — DELETED 2026-08-29.
|
|
13
13
|
//
|
|
@@ -56,6 +56,23 @@ export declare const BID_PII_FIELDS: readonly ["userEmail"];
|
|
|
56
56
|
export declare const TOKEN_PII_FIELDS: readonly ["email"];
|
|
57
57
|
/** Blind-index mapping for token email queries */
|
|
58
58
|
export declare const TOKEN_PII_INDEX_MAP: Record<string, string>;
|
|
59
|
+
/**
|
|
60
|
+
* D9 — chat. Re-created against the SUBCOLLECTION redesign, not the old
|
|
61
|
+
* embedded-array one the comment below used to describe.
|
|
62
|
+
*
|
|
63
|
+
* `messages/{id}` is a true Firestore subcollection now (one document per
|
|
64
|
+
* message, not `conversations.messages[]`), so there is no array to walk and
|
|
65
|
+
* the old `messages[].body` `[]`-syntax path does not apply here — `body` is a
|
|
66
|
+
* plain top-level field on each message document. Only `lastMessage.body`, a
|
|
67
|
+
* nested object on the PARENT `conversations` document, needs the dotted-path
|
|
68
|
+
* form `encryptPiiFields` already supports.
|
|
69
|
+
*
|
|
70
|
+
* No blind index on either: nothing queries chat by body content, and a
|
|
71
|
+
* `searchTxt` over an encrypted body would hand back only what the encryption
|
|
72
|
+
* withheld — chat search, if it exists, indexes participant names only.
|
|
73
|
+
*/
|
|
74
|
+
export declare const CONVERSATION_PII_FIELDS: readonly ["lastMessage.body"];
|
|
75
|
+
export declare const MESSAGE_PII_FIELDS: readonly ["body"];
|
|
59
76
|
/** PII fields in reviews */
|
|
60
77
|
export declare const REVIEW_PII_FIELDS: readonly ["userName"];
|
|
61
78
|
/** Blind-index mapping for queryable review PII */
|
|
@@ -85,15 +85,23 @@ export const TOKEN_PII_FIELDS = ["email"];
|
|
|
85
85
|
export const TOKEN_PII_INDEX_MAP = {
|
|
86
86
|
email: "emailIndex",
|
|
87
87
|
};
|
|
88
|
-
|
|
89
|
-
*
|
|
90
|
-
* the
|
|
88
|
+
/**
|
|
89
|
+
* D9 — chat. Re-created against the SUBCOLLECTION redesign, not the old
|
|
90
|
+
* embedded-array one the comment below used to describe.
|
|
91
|
+
*
|
|
92
|
+
* `messages/{id}` is a true Firestore subcollection now (one document per
|
|
93
|
+
* message, not `conversations.messages[]`), so there is no array to walk and
|
|
94
|
+
* the old `messages[].body` `[]`-syntax path does not apply here — `body` is a
|
|
95
|
+
* plain top-level field on each message document. Only `lastMessage.body`, a
|
|
96
|
+
* nested object on the PARENT `conversations` document, needs the dotted-path
|
|
97
|
+
* form `encryptPiiFields` already supports.
|
|
91
98
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* caller exercises is a path nothing proves.
|
|
99
|
+
* No blind index on either: nothing queries chat by body content, and a
|
|
100
|
+
* `searchTxt` over an encrypted body would hand back only what the encryption
|
|
101
|
+
* withheld — chat search, if it exists, indexes participant names only.
|
|
96
102
|
*/
|
|
103
|
+
export const CONVERSATION_PII_FIELDS = ["lastMessage.body"];
|
|
104
|
+
export const MESSAGE_PII_FIELDS = ["body"];
|
|
97
105
|
/** PII fields in reviews */
|
|
98
106
|
export const REVIEW_PII_FIELDS = ["userName"];
|
|
99
107
|
/** Blind-index mapping for queryable review PII */
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* move; a sentence saying "48 listings" is wrong the next day and nothing
|
|
18
18
|
* reports it.
|
|
19
19
|
*
|
|
20
|
-
* Model leaves with a body:
|
|
21
|
-
* Part leaves with a body:
|
|
20
|
+
* Model leaves with a body: 217 of 220
|
|
21
|
+
* Part leaves with a body: 534 of 752
|
|
22
22
|
*/
|
|
23
23
|
export declare const GENERATED_CATEGORY_CONTENT: Record<string, string>;
|
|
24
|
-
export declare const GENERATED_CATEGORY_CONTENT_COUNT =
|
|
24
|
+
export declare const GENERATED_CATEGORY_CONTENT_COUNT = 751;
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
* move; a sentence saying "48 listings" is wrong the next day and nothing
|
|
18
18
|
* reports it.
|
|
19
19
|
*
|
|
20
|
-
* Model leaves with a body:
|
|
21
|
-
* Part leaves with a body:
|
|
20
|
+
* Model leaves with a body: 217 of 220
|
|
21
|
+
* Part leaves with a body: 534 of 752
|
|
22
22
|
*/
|
|
23
23
|
export const GENERATED_CATEGORY_CONTENT = {
|
|
24
24
|
"category-0": `<p><strong>0</strong> is a Disc.</p>
|
|
25
|
-
<p>Shipped with Cho-Z Spriggan 0Wall Zeta, Spriggan Requiem 0 Zeta.</p>`,
|
|
25
|
+
<p>Shipped with Spryzen S2 0Q, Cho-Z Spriggan 0Wall Zeta, Spriggan Requiem 0 Zeta.</p>`,
|
|
26
26
|
"category-0-80": `<p><strong>0-80</strong> is a Ratchet.</p>
|
|
27
27
|
<p>Shipped with GhostCircle 0-80GB.</p>`,
|
|
28
28
|
"category-00": `<p><strong>00</strong> is a Disc.</p>
|
|
@@ -419,12 +419,17 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
419
419
|
<p>Shipped with Evil Gemios DF145FS, Gemios DF145FS, Libra DF145BS, Night Virgo DF145BS, Thunder Libra DF145BS.</p>`,
|
|
420
420
|
"category-df145-down-force-145": `<p><strong>DF145 Down Force 145</strong> is a Spin Track.</p>
|
|
421
421
|
<p>Shipped with Dark Wolf DF145FS.</p>`,
|
|
422
|
+
"category-diablo-nemesis-x-d": `<p>Diablo Nemesis X:D is a balance-type Beyblade, right-spin, built on the 4D System.</p>
|
|
423
|
+
<p>Product code <strong>BB-122</strong>.</p>
|
|
424
|
+
<p>Released as part of Beyblade: Metal Fury.</p>`,
|
|
422
425
|
"category-disk-ball": `<p><strong>Disk Ball</strong> is a Bit.</p>
|
|
423
426
|
<p>Shipped with WizardRod 5-70DB.</p>`,
|
|
424
427
|
"category-dj-spiker": `<p><strong>DJ Spiker</strong> is an Attack Ring.</p>
|
|
425
428
|
<p>Shipped with Wyvern DJ.</p>`,
|
|
426
429
|
"category-double-horn": `<p><strong>Double Horn</strong> is an Attack Ring.</p>
|
|
427
430
|
<p>Shipped with Rock Bison.</p>`,
|
|
431
|
+
"category-double-wing": `<p><strong>Double Wing</strong> is an Attack Ring.</p>
|
|
432
|
+
<p>Shipped with Dranzer S.</p>`,
|
|
428
433
|
"category-draciel": `<p><strong>Draciel</strong> is a Bit Chip.</p>
|
|
429
434
|
<p>Shipped with Draciel F, Draciel G, Draciel V, Draciel V2.</p>`,
|
|
430
435
|
"category-draciel-emblem": `<p><strong>Draciel Emblem</strong> is a Bit Chip.</p>
|
|
@@ -512,7 +517,7 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
512
517
|
<p>Sold outside Japan as <strong>Sword Dran 3-60F</strong> — the same mould under a different marque.</p>
|
|
513
518
|
<p>Succeeded by DranDagger 4-60R in the same lineage.</p>`,
|
|
514
519
|
"category-dranzer": `<p><strong>Dranzer</strong> is a Bit Chip.</p>
|
|
515
|
-
<p>Shipped with Dranzer F, Dranzer G, Dranzer GT, Dranzer V, Dranzer V2.</p>`,
|
|
520
|
+
<p>Shipped with Dranzer F, Dranzer G, Dranzer GT, Dranzer S, Dranzer V, Dranzer V2.</p>`,
|
|
516
521
|
"category-dranzer-emblem": `<p><strong>Dranzer Emblem</strong> is a Bit Chip.</p>
|
|
517
522
|
<p>Shipped with Dranzer MF, Dranzer MS.</p>`,
|
|
518
523
|
"category-dranzer-f": `<p>Dranzer F is a balance-type Beyblade, right-spin, built on the Spin Gear System.</p>
|
|
@@ -538,6 +543,9 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
538
543
|
"category-dranzer-ms": `<p>Dranzer MS is a balance-type Beyblade, dual-spin, built on the Hard Metal System.</p>
|
|
539
544
|
<p>Product code <strong>A-131</strong> by Takara.</p>
|
|
540
545
|
<p>Released as part of Beyblade: G-Revolution / manga Volume 13, Chapter 1.</p>`,
|
|
546
|
+
"category-dranzer-s": `<p>Dranzer S is a balance-type Beyblade, right-spin, built on the Spin Gear System.</p>
|
|
547
|
+
<p>Product code <strong>A-2</strong> by Takara.</p>
|
|
548
|
+
<p>Released as part of Bakuten Shoot Beyblade.</p>`,
|
|
541
549
|
"category-dranzer-v": `<p>Dranzer V is a balance-type Beyblade, right-spin, built on the Magnacore System.</p>
|
|
542
550
|
<p>Product code <strong>A-47</strong> by Takara.</p>
|
|
543
551
|
<p>Released as part of Beyblade: V-Force.</p>
|
|
@@ -613,7 +621,7 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
613
621
|
"category-eight-attacker": `<p><strong>Eight Attacker</strong> is an Attack Ring.</p>
|
|
614
622
|
<p>Shipped with Dragoon V.</p>`,
|
|
615
623
|
"category-eight-balance": `<p><strong>Eight Balance</strong> is a Weight Disk.</p>
|
|
616
|
-
<p>Shipped with Shadow Driger, Draciel F, Driger F, Griffolyon, Metal Draciel,
|
|
624
|
+
<p>Shipped with Shadow Driger, Draciel F, Dranzer S, Driger F, Griffolyon, Metal Draciel, and 3 more.</p>`,
|
|
617
625
|
"category-eight-heavy": `<p><strong>Eight Heavy</strong> is a Weight Disk.</p>
|
|
618
626
|
<p>Shipped with Dranzer F, Galeon, Galman.</p>`,
|
|
619
627
|
"category-eight-spike": `<p><strong>Eight Spike</strong> is an Attack Ring.</p>
|
|
@@ -1293,7 +1301,7 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
1293
1301
|
"category-right-mg-south-core": `<p><strong>Right MG South Core</strong> is a Spin Gear.</p>
|
|
1294
1302
|
<p>Shipped with Flash Leopard 2.</p>`,
|
|
1295
1303
|
"category-right-sg": `<p><strong>Right SG</strong> is a Spin Gear.</p>
|
|
1296
|
-
<p>Shipped with Shadow Driger, Draciel F, Dranzer F, Galeon, Galman,
|
|
1304
|
+
<p>Shipped with Shadow Driger, Draciel F, Dranzer F, Dranzer S, Galeon, Galman, and 7 more.</p>`,
|
|
1297
1305
|
"category-right-sg-bearing": `<p><strong>Right SG</strong> is a Spin Gear.</p>
|
|
1298
1306
|
<p>Shipped with Wolborg, Wolborg 2.</p>`,
|
|
1299
1307
|
"category-right-sg-full-auto": `<p><strong>Right SG</strong> is a Spin Gear.</p>
|
|
@@ -1469,6 +1477,8 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
1469
1477
|
<p>Shipped with Wyborg.</p>`,
|
|
1470
1478
|
"category-spike-dragon": `<p><strong>Spike Dragon</strong> is an Attack Ring.</p>
|
|
1471
1479
|
<p>Shipped with Dragoon V2.</p>`,
|
|
1480
|
+
"category-spiral-change-base": `<p><strong>Spiral Change Base</strong> is a Blade Base.</p>
|
|
1481
|
+
<p>Shipped with Dranzer S.</p>`,
|
|
1472
1482
|
"category-spiral-upper": `<p><strong>Spiral Upper</strong> is an Attack Ring.</p>
|
|
1473
1483
|
<p>Shipped with Dranzer MS.</p>`,
|
|
1474
1484
|
"category-spriggan-requiem-0-zeta": `<p>Spriggan Requiem 0 Zeta is a balance-type Beyblade, dual-spin, built on the God Layer System.</p>
|
|
@@ -1478,6 +1488,9 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
1478
1488
|
<p>Shipped with Einstein MS.</p>`,
|
|
1479
1489
|
"category-spryzen-requiem-s3": `<p><strong>Spryzen Requiem S3</strong> is a Layer.</p>
|
|
1480
1490
|
<p>Shipped with Spriggan Requiem 0 Zeta.</p>`,
|
|
1491
|
+
"category-spryzen-s2-0q": `<p>Spryzen S2 0Q is a balance-type Beyblade, dual-spin, built on the Burst System.</p>
|
|
1492
|
+
<p>Product code <strong>E1048</strong> by Hasbro.</p>
|
|
1493
|
+
<p>Released as part of Beyblade Burst.</p>`,
|
|
1481
1494
|
"category-sr200": `<p><strong>SR200</strong> is a Spin Track.</p>
|
|
1482
1495
|
<p>Shipped with Berserker Begirados SR200BWD.</p>`,
|
|
1483
1496
|
"category-sr200-slide-ride-200": `<p><strong>SR200 SLIDE-RIDE 200</strong> is a Spin Track.</p>
|
|
@@ -1748,6 +1761,9 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
1748
1761
|
<p>Shipped with Dranzer G.</p>`,
|
|
1749
1762
|
"category-wing-upper": `<p><strong>Wing Upper</strong> is an Attack Ring.</p>
|
|
1750
1763
|
<p>Shipped with Thunder Pegasus, Torch Pegasus.</p>`,
|
|
1764
|
+
"category-wizard-fafnir-ratchet-rise": `<p>Wizard Fafnir Ratchet Rise is a stamina-type Beyblade, left-spin, built on the Gatinko Layer System.</p>
|
|
1765
|
+
<p>Product code <strong>B-139</strong> by Takara Tomy.</p>
|
|
1766
|
+
<p>Released as part of Beyblade Burst Turbo / Cho-Z late / Burst Rise transition.</p>`,
|
|
1751
1767
|
"category-wizardarrow": `<p><strong>WizardArrow</strong> is a Blade.</p>
|
|
1752
1768
|
<p>Shipped with WizardArrow 4-80B.</p>`,
|
|
1753
1769
|
"category-wizardarrow-4-80b": `<p>WizardArrow 4-80B is a stamina-type Beyblade, right-spin.</p>
|
|
@@ -1834,4 +1850,4 @@ export const GENERATED_CATEGORY_CONTENT = {
|
|
|
1834
1850
|
<p>Released as part of Beyblade Burst Turbo.</p>
|
|
1835
1851
|
<p>Sold outside Japan as <strong>Turbo Luinor L4 Drake Spiral</strong> — the same mould under a different marque.</p>`,
|
|
1836
1852
|
};
|
|
1837
|
-
export const GENERATED_CATEGORY_CONTENT_COUNT =
|
|
1853
|
+
export const GENERATED_CATEGORY_CONTENT_COUNT = 751;
|
|
@@ -2,4 +2,4 @@ import type { CategoryTreeNode } from "./category-tree";
|
|
|
2
2
|
/** Named-model leaves, keyed by the tier-3 line they belong to. */
|
|
3
3
|
export declare const MODEL_LEAVES: Record<string, CategoryTreeNode[]>;
|
|
4
4
|
/** Total named-model leaves — asserted by the seed so a silent drop is loud. */
|
|
5
|
-
export declare const MODEL_LEAF_COUNT =
|
|
5
|
+
export declare const MODEL_LEAF_COUNT = 220;
|
|
@@ -142,6 +142,12 @@ export const MODEL_LEAVES = {
|
|
|
142
142
|
description: "Luinor L2 — an Attack-type, left-spin Burst Classic release, product code E1056. Release note: Lost Lúinor L2 Nine Spiral. Complete tops, loose parts, sticker sheets and pre-owned examples of this model are listed here.",
|
|
143
143
|
extra: { aliases: ["E1056", "Lost Lúinor L2", "Lost Longinus"], productDefaults: { defaultFeatures: ["feature-blader-lui-shirosagi", "feature-type-attack", "feature-left-spin"] } },
|
|
144
144
|
},
|
|
145
|
+
{
|
|
146
|
+
id: "category-spryzen-s2-0q",
|
|
147
|
+
name: "Spryzen S2 0Q",
|
|
148
|
+
description: "Spryzen S2 0Q — a Balance-type, dual-spin Burst Classic release, product code E1048, released by Hasbro. Complete tops, loose parts, sticker sheets and pre-owned examples of this model are listed here.",
|
|
149
|
+
extra: { aliases: ["E1048", "Spryzen S2 0Q Layer: Spryzen S2", "Disc: 0", "Driver: Q — later evolves to Spryzen Requiem S3 0Cross"], productDefaults: { defaultFeatures: ["feature-blader-shu-kurenai", "feature-type-balance", "feature-dual-spin"] } },
|
|
150
|
+
},
|
|
145
151
|
{
|
|
146
152
|
id: "category-storm-spriggan-7glaive",
|
|
147
153
|
name: "Storm Spriggan 7Glaive",
|
|
@@ -228,6 +234,12 @@ export const MODEL_LEAVES = {
|
|
|
228
234
|
description: "Vanish Fafnir Tapered Kick-3 — a Stamina-type, left-spin Dynamite Battle release, product code B-180-class, released by Takara Tomy, ~2021. Complete tops, loose parts, sticker sheets and pre-owned examples of this model are listed here.",
|
|
229
235
|
extra: { aliases: ["B-180-class"], productDefaults: { defaultFeatures: ["feature-blader-fumiya-kindo", "feature-type-stamina", "feature-left-spin"] } },
|
|
230
236
|
},
|
|
237
|
+
{
|
|
238
|
+
id: "category-wizard-fafnir-ratchet-rise",
|
|
239
|
+
name: "Wizard Fafnir Ratchet Rise",
|
|
240
|
+
description: "Wizard Fafnir Ratchet Rise — a Stamina-type, left-spin Dynamite Battle release, product code B-139, released by Takara Tomy, S3-S4 transitional. Complete tops, loose parts, sticker sheets and pre-owned examples of this model are listed here.",
|
|
241
|
+
extra: { aliases: ["B-139"], productDefaults: { defaultFeatures: ["feature-blader-fubuki-sumiye", "feature-type-stamina", "feature-left-spin"] } },
|
|
242
|
+
},
|
|
231
243
|
],
|
|
232
244
|
"category-burst-god": [
|
|
233
245
|
{
|
|
@@ -386,6 +398,12 @@ export const MODEL_LEAVES = {
|
|
|
386
398
|
description: "Cosmic Pegasis F:D — an Attack-type, right-spin Metal Fury (4D) release, product code BB-128, released by Takara Tomy. Complete tops, loose parts, sticker sheets and pre-owned examples of this model are listed here.",
|
|
387
399
|
extra: { aliases: ["BB-128"], productDefaults: { defaultFeatures: ["feature-blader-gingka-hagane", "feature-type-attack", "feature-right-spin"] } },
|
|
388
400
|
},
|
|
401
|
+
{
|
|
402
|
+
id: "category-diablo-nemesis-x-d",
|
|
403
|
+
name: "Diablo Nemesis X:D",
|
|
404
|
+
description: "Diablo Nemesis X:D — a Balance-type, right-spin Metal Fury (4D) release, product code BB-122. Complete tops, loose parts, sticker sheets and pre-owned examples of this model are listed here.",
|
|
405
|
+
extra: { aliases: ["BB-122"], productDefaults: { defaultFeatures: ["feature-blader-rago", "feature-type-balance", "feature-right-spin"] } },
|
|
406
|
+
},
|
|
389
407
|
{
|
|
390
408
|
id: "category-fang-leone-130w2d",
|
|
391
409
|
name: "Fang Leone 130W2D",
|
|
@@ -1146,6 +1164,12 @@ export const MODEL_LEAVES = {
|
|
|
1146
1164
|
description: "Dranzer F — a Balance-type, right-spin Spin Gear System release, product code A-27, released by Takara Tomy. Complete tops, loose parts, sticker sheets and pre-owned examples of this model are listed here.",
|
|
1147
1165
|
extra: { aliases: ["A-27", "Dranzer Flame"], productDefaults: { defaultFeatures: ["feature-blader-kai-hiwatari", "feature-type-balance", "feature-right-spin"] } },
|
|
1148
1166
|
},
|
|
1167
|
+
{
|
|
1168
|
+
id: "category-dranzer-s",
|
|
1169
|
+
name: "Dranzer S",
|
|
1170
|
+
description: "Dranzer S — a Balance-type, right-spin Spin Gear System release, product code A-2, released by Takara. Complete tops, loose parts, sticker sheets and pre-owned examples of this model are listed here.",
|
|
1171
|
+
extra: { aliases: ["A-2", "Dranzer Spiral"], productDefaults: { defaultFeatures: ["feature-blader-kai-hiwatari", "feature-type-balance", "feature-right-spin"] } },
|
|
1172
|
+
},
|
|
1149
1173
|
{
|
|
1150
1174
|
id: "category-driger-f",
|
|
1151
1175
|
name: "Driger F",
|
|
@@ -1361,4 +1385,4 @@ export const MODEL_LEAVES = {
|
|
|
1361
1385
|
],
|
|
1362
1386
|
};
|
|
1363
1387
|
/** Total named-model leaves — asserted by the seed so a silent drop is loud. */
|
|
1364
|
-
export const MODEL_LEAF_COUNT =
|
|
1388
|
+
export const MODEL_LEAF_COUNT = 220;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ProductDocument } from "../../features/products/schemas";
|
|
2
|
+
export declare function deriveCategoryNames(slugs: readonly string[] | undefined): string[] | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Derive the full ancestor closure from the head of an existing chain.
|
|
5
|
+
*
|
|
6
|
+
* ## 🛑 The drift this ends — measured, 6 of 1,039 live products
|
|
7
|
+
*
|
|
8
|
+
* `categorySlugs` is the ancestor closure and it was hand-written per record
|
|
9
|
+
* too, so a chain could SKIP A TIER and nothing noticed:
|
|
10
|
+
*
|
|
11
|
+
* product-beyblade-original-driger-v
|
|
12
|
+
* got driger-v > original-tops > beyblade-original > spinning-tops
|
|
13
|
+
* expected driger-v > original-magnacore > original-tops > …
|
|
14
|
+
*
|
|
15
|
+
* The category page matches `array-contains-any [its own id]` against this
|
|
16
|
+
* array (CLAUDE.md: "a category page matches on its own id alone"), so a
|
|
17
|
+
* missing intermediate means the product is **absent from that node's page**
|
|
18
|
+
* while appearing correctly on every other node in its chain. Six products were
|
|
19
|
+
* invisible on their own system sub-line.
|
|
20
|
+
*
|
|
21
|
+
* `countersReconcile`'s rollup invariant cannot see this: it computes the counts
|
|
22
|
+
* FROM these closures, so a skipped tier is simply a tier with a lower count,
|
|
23
|
+
* and `own + Σ(children.total) === total` still holds exactly. The only way to
|
|
24
|
+
* find it is to re-derive the chain and compare.
|
|
25
|
+
*
|
|
26
|
+
* ## 🛑 A SECOND ASSIGNMENT IS LEFT ALONE, NOT COLLAPSED
|
|
27
|
+
*
|
|
28
|
+
* This derives from `slugs[0]` — the head, which is what `readAssignments`
|
|
29
|
+
* treats as the assignment. A product filed under two roots would carry two
|
|
30
|
+
* concatenated closures, and deriving from the head alone would silently DELETE
|
|
31
|
+
* the second one, turning a drift-fix into data loss.
|
|
32
|
+
*
|
|
33
|
+
* So the rule is: overwrite only when the existing array is a SUBSET of the
|
|
34
|
+
* derived closure. That is precisely the drift case (ids missing from the
|
|
35
|
+
* middle). An id outside the derived closure means either a second assignment
|
|
36
|
+
* or something this helper cannot safely interpret, and both must survive to be
|
|
37
|
+
* reported rather than be quietly rewritten.
|
|
38
|
+
*
|
|
39
|
+
* Measured when this was written: 0 of 71 seeded products carry `categoryIds`
|
|
40
|
+
* and 0 carry a multi-root closure — so the subset rule fires on all six real
|
|
41
|
+
* cases and guards a case that does not exist yet.
|
|
42
|
+
*/
|
|
43
|
+
export declare function deriveCategoryClosure(slugs: readonly string[] | undefined): string[] | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Pipeline step: replace whatever `categorySlugs` and `categoryNames` a record
|
|
46
|
+
* carries with the derivation. Deliberately an OVERWRITE rather than a
|
|
47
|
+
* fill-if-absent — a hand-written value that disagrees IS the defect, so
|
|
48
|
+
* preferring it would preserve every one of them.
|
|
49
|
+
*
|
|
50
|
+
* Order matters: the closure is derived first, then the names from the derived
|
|
51
|
+
* closure. Deriving names from the stale chain and then fixing the chain would
|
|
52
|
+
* leave the two mirrors disagreeing again, one tier apart.
|
|
53
|
+
*/
|
|
54
|
+
export declare function withDerivedCategoryNames(p: Partial<ProductDocument>): Partial<ProductDocument>;
|