@mohasinac/appkit 4.46.0 → 4.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +58 -13
- package/dist/features/categories/components/BreadcrumbTrail.js +63 -26
- 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/CategoryInlineSelect.d.ts +17 -1
- package/dist/features/seller/components/CategoryInlineSelect.js +14 -3
- package/dist/features/seller/components/SellerProductShell.d.ts +27 -2
- package/dist/features/seller/components/SellerProductShell.js +32 -3
- 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 +24 -0
- package/dist/seed/_helpers/category-content-generated.js +1853 -0
- 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
|
@@ -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>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { CATEGORY_FOREST } from "./category-forest.js";
|
|
2
|
+
let nameById = null;
|
|
3
|
+
/** id -> its ancestors, NEAREST FIRST (parent, grandparent, …, root). */
|
|
4
|
+
let ancestorsById = null;
|
|
5
|
+
function build() {
|
|
6
|
+
const n = new Map();
|
|
7
|
+
const a = new Map();
|
|
8
|
+
const walk = (nodes, trail) => {
|
|
9
|
+
for (const node of nodes ?? []) {
|
|
10
|
+
if (typeof node.id === "string") {
|
|
11
|
+
n.set(node.id, node.name);
|
|
12
|
+
a.set(node.id, [...trail]);
|
|
13
|
+
walk(node.children, [node.id, ...trail]);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// A node with no id contributes no ancestry, but its children still do.
|
|
17
|
+
walk(node.children, trail);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
walk(CATEGORY_FOREST, []);
|
|
22
|
+
nameById = n;
|
|
23
|
+
ancestorsById = a;
|
|
24
|
+
}
|
|
25
|
+
function names() {
|
|
26
|
+
if (!nameById)
|
|
27
|
+
build();
|
|
28
|
+
return nameById;
|
|
29
|
+
}
|
|
30
|
+
function ancestors() {
|
|
31
|
+
if (!ancestorsById)
|
|
32
|
+
build();
|
|
33
|
+
return ancestorsById;
|
|
34
|
+
}
|
|
35
|
+
export function deriveCategoryNames(slugs) {
|
|
36
|
+
if (!Array.isArray(slugs) || slugs.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
const byId = names();
|
|
39
|
+
return slugs.map((id) => byId.get(id) ?? id);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Derive the full ancestor closure from the head of an existing chain.
|
|
43
|
+
*
|
|
44
|
+
* ## 🛑 The drift this ends — measured, 6 of 1,039 live products
|
|
45
|
+
*
|
|
46
|
+
* `categorySlugs` is the ancestor closure and it was hand-written per record
|
|
47
|
+
* too, so a chain could SKIP A TIER and nothing noticed:
|
|
48
|
+
*
|
|
49
|
+
* product-beyblade-original-driger-v
|
|
50
|
+
* got driger-v > original-tops > beyblade-original > spinning-tops
|
|
51
|
+
* expected driger-v > original-magnacore > original-tops > …
|
|
52
|
+
*
|
|
53
|
+
* The category page matches `array-contains-any [its own id]` against this
|
|
54
|
+
* array (CLAUDE.md: "a category page matches on its own id alone"), so a
|
|
55
|
+
* missing intermediate means the product is **absent from that node's page**
|
|
56
|
+
* while appearing correctly on every other node in its chain. Six products were
|
|
57
|
+
* invisible on their own system sub-line.
|
|
58
|
+
*
|
|
59
|
+
* `countersReconcile`'s rollup invariant cannot see this: it computes the counts
|
|
60
|
+
* FROM these closures, so a skipped tier is simply a tier with a lower count,
|
|
61
|
+
* and `own + Σ(children.total) === total` still holds exactly. The only way to
|
|
62
|
+
* find it is to re-derive the chain and compare.
|
|
63
|
+
*
|
|
64
|
+
* ## 🛑 A SECOND ASSIGNMENT IS LEFT ALONE, NOT COLLAPSED
|
|
65
|
+
*
|
|
66
|
+
* This derives from `slugs[0]` — the head, which is what `readAssignments`
|
|
67
|
+
* treats as the assignment. A product filed under two roots would carry two
|
|
68
|
+
* concatenated closures, and deriving from the head alone would silently DELETE
|
|
69
|
+
* the second one, turning a drift-fix into data loss.
|
|
70
|
+
*
|
|
71
|
+
* So the rule is: overwrite only when the existing array is a SUBSET of the
|
|
72
|
+
* derived closure. That is precisely the drift case (ids missing from the
|
|
73
|
+
* middle). An id outside the derived closure means either a second assignment
|
|
74
|
+
* or something this helper cannot safely interpret, and both must survive to be
|
|
75
|
+
* reported rather than be quietly rewritten.
|
|
76
|
+
*
|
|
77
|
+
* Measured when this was written: 0 of 71 seeded products carry `categoryIds`
|
|
78
|
+
* and 0 carry a multi-root closure — so the subset rule fires on all six real
|
|
79
|
+
* cases and guards a case that does not exist yet.
|
|
80
|
+
*/
|
|
81
|
+
export function deriveCategoryClosure(slugs) {
|
|
82
|
+
if (!Array.isArray(slugs) || slugs.length === 0)
|
|
83
|
+
return undefined;
|
|
84
|
+
const head = slugs[0];
|
|
85
|
+
const chain = ancestors().get(head);
|
|
86
|
+
// An unknown head keeps its hand-written chain: inventing one from a node the
|
|
87
|
+
// forest does not have would be worse than leaving a visibly wrong value for
|
|
88
|
+
// the dangling-id sweep to find.
|
|
89
|
+
if (!chain)
|
|
90
|
+
return undefined;
|
|
91
|
+
const derived = [head, ...chain];
|
|
92
|
+
const outside = slugs.filter((s) => !derived.includes(s));
|
|
93
|
+
if (outside.length > 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
return derived;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Pipeline step: replace whatever `categorySlugs` and `categoryNames` a record
|
|
99
|
+
* carries with the derivation. Deliberately an OVERWRITE rather than a
|
|
100
|
+
* fill-if-absent — a hand-written value that disagrees IS the defect, so
|
|
101
|
+
* preferring it would preserve every one of them.
|
|
102
|
+
*
|
|
103
|
+
* Order matters: the closure is derived first, then the names from the derived
|
|
104
|
+
* closure. Deriving names from the stale chain and then fixing the chain would
|
|
105
|
+
* leave the two mirrors disagreeing again, one tier apart.
|
|
106
|
+
*/
|
|
107
|
+
export function withDerivedCategoryNames(p) {
|
|
108
|
+
const closure = deriveCategoryClosure(p.categorySlugs);
|
|
109
|
+
const slugs = closure ?? p.categorySlugs;
|
|
110
|
+
const names = deriveCategoryNames(slugs);
|
|
111
|
+
if (!closure && !names)
|
|
112
|
+
return p;
|
|
113
|
+
return {
|
|
114
|
+
...p,
|
|
115
|
+
...(closure ? { categorySlugs: closure } : {}),
|
|
116
|
+
...(names ? { categoryNames: names } : {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
*/
|
|
23
23
|
export const CHARACTER_SEEDS = [
|
|
24
24
|
{ slug: "blader-tyson-granger", name: "Tyson Granger", beyCount: 9 },
|
|
25
|
-
{ slug: "blader-kai-hiwatari", name: "Kai Hiwatari", beyCount:
|
|
25
|
+
{ slug: "blader-kai-hiwatari", name: "Kai Hiwatari", beyCount: 8 },
|
|
26
|
+
{ slug: "blader-shu-kurenai", name: "Shu Kurenai", beyCount: 7 },
|
|
26
27
|
{ slug: "blader-daichi-sumeragi", name: "Daichi Sumeragi", beyCount: 6 },
|
|
27
28
|
{ slug: "blader-max-tate", name: "Max Tate", beyCount: 6 },
|
|
28
29
|
{ slug: "blader-ray-kon", name: "Ray Kon", beyCount: 6 },
|
|
29
|
-
{ slug: "blader-shu-kurenai", name: "Shu Kurenai", beyCount: 6 },
|
|
30
30
|
{ slug: "blader-gingka-hagane", name: "Gingka Hagane", beyCount: 5 },
|
|
31
31
|
{ slug: "blader-lui-shirosagi", name: "Lui Shirosagi", beyCount: 5 },
|
|
32
32
|
{ slug: "blader-tala", name: "Tala", beyCount: 5 },
|
|
@@ -56,6 +56,7 @@ export const CHARACTER_SEEDS = [
|
|
|
56
56
|
{ slug: "blader-michael-summers", name: "Michael Summers", beyCount: 2 },
|
|
57
57
|
{ slug: "blader-miguel", name: "Miguel", beyCount: 2 },
|
|
58
58
|
{ slug: "blader-ozuma", name: "Ozuma", beyCount: 2 },
|
|
59
|
+
{ slug: "blader-rago", name: "Rago", beyCount: 2 },
|
|
59
60
|
{ slug: "blader-robin-kazami", name: "Robin Kazami", beyCount: 2 },
|
|
60
61
|
{ slug: "blader-tsubasa-otori", name: "Tsubasa Otori", beyCount: 2 },
|
|
61
62
|
{ slug: "blader-wakiya-murasaki", name: "Wakiya Murasaki", beyCount: 2 },
|
|
@@ -75,6 +76,7 @@ export const CHARACTER_SEEDS = [
|
|
|
75
76
|
{ slug: "blader-dan-sodo-and-reiki-sodo", name: "Dan Sodo and Reiki Sodo", beyCount: 1 },
|
|
76
77
|
{ slug: "blader-doji", name: "Doji", beyCount: 1 },
|
|
77
78
|
{ slug: "blader-faust", name: "Faust", beyCount: 1 },
|
|
79
|
+
{ slug: "blader-fubuki-sumiye", name: "Fubuki Sumiye", beyCount: 1 },
|
|
78
80
|
{ slug: "blader-garland-siebald", name: "Garland Siebald", beyCount: 1 },
|
|
79
81
|
{ slug: "blader-gary", name: "Gary", beyCount: 1 },
|
|
80
82
|
{ slug: "blader-ginro", name: "Ginro", beyCount: 1 },
|
|
@@ -98,7 +100,6 @@ export const CHARACTER_SEEDS = [
|
|
|
98
100
|
{ slug: "blader-nine-kurosu", name: "Nine Kurosu", beyCount: 1 },
|
|
99
101
|
{ slug: "blader-omega-shiroboshi", name: "Omega Shiroboshi", beyCount: 1 },
|
|
100
102
|
{ slug: "blader-phi", name: "Phi", beyCount: 1 },
|
|
101
|
-
{ slug: "blader-rago", name: "Rago", beyCount: 1 },
|
|
102
103
|
{ slug: "blader-reiji-mizuchi", name: "Reiji Mizuchi", beyCount: 1 },
|
|
103
104
|
{ slug: "blader-reiyu-kuwabara", name: "Reiyu Kuwabara", beyCount: 1 },
|
|
104
105
|
{ slug: "blader-rex-jura", name: "Rex Jura", beyCount: 1 },
|
|
@@ -115,4 +116,4 @@ export const CHARACTER_SEEDS = [
|
|
|
115
116
|
{ slug: "blader-yuni-naniwa", name: "Yuni Naniwa", beyCount: 1 },
|
|
116
117
|
{ slug: "blader-zyro-kurogane", name: "Zyro Kurogane", beyCount: 1 },
|
|
117
118
|
];
|
|
118
|
-
export const CHARACTER_SEED_COUNT =
|
|
119
|
+
export const CHARACTER_SEED_COUNT = 94;
|
|
@@ -36,7 +36,7 @@ export const RETURNABLE_FIXTURE_IDS = new Set([
|
|
|
36
36
|
// art
|
|
37
37
|
"art-valkyrie-holographic-print",
|
|
38
38
|
// stickers
|
|
39
|
-
"
|
|
39
|
+
"sticker-beyblade-x-holographic-pack",
|
|
40
40
|
]);
|
|
41
41
|
/**
|
|
42
42
|
* Set `finalSale: false` on the opted-out fixtures and leave it ABSENT on
|
|
@@ -59,6 +59,7 @@ import { BRAND_ROW_COUNT, buildBrandRows } from "./_helpers/brand-rows.js";
|
|
|
59
59
|
import { MODEL_LEAF_COUNT } from "./_helpers/category-models.js";
|
|
60
60
|
import { PART_LEAF_COUNT } from "./_helpers/category-parts.js";
|
|
61
61
|
import { MARKET_BAND_LEAF_IDS } from "./_helpers/category-content.js";
|
|
62
|
+
import { GENERATED_CATEGORY_CONTENT, GENERATED_CATEGORY_CONTENT_COUNT, } from "./_helpers/category-content-generated.js";
|
|
62
63
|
import { productsStandardSeedData } from "./products-standard-seed-data.js";
|
|
63
64
|
const NOW = new Date();
|
|
64
65
|
const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
|
|
@@ -442,12 +443,42 @@ const bundleRows = [
|
|
|
442
443
|
* name/slug/description only and silently drop the lineage search — typing
|
|
443
444
|
* "burst" would stop reaching a tier-4 model under Beyblade Burst.
|
|
444
445
|
*/
|
|
446
|
+
/**
|
|
447
|
+
* I7 — attach the GENERATED body to any row that has one and does not already
|
|
448
|
+
* carry a hand-authored one.
|
|
449
|
+
*
|
|
450
|
+
* 🛑 The generator ran and its 745 bodies were imported by NOBODY. The corpus
|
|
451
|
+
* sat in the repo as 128 KB of dead weight: not seeded, so not in Firestore,
|
|
452
|
+
* so unreadable by the page that renders it. The generator being written is
|
|
453
|
+
* not the same as its output being wired, and the second half is invisible —
|
|
454
|
+
* a category page with no body renders perfectly well, it just ranks on
|
|
455
|
+
* nothing.
|
|
456
|
+
*
|
|
457
|
+
* 🛑 A HAND-AUTHORED body always wins. The four generation roots and several
|
|
458
|
+
* tier-1 nodes carry prose written against the crawl research; the generator
|
|
459
|
+
* composes from facts and is deliberately the weaker writer at those tiers
|
|
460
|
+
* (its own header says the part leaves are its strongest tier). Overwriting
|
|
461
|
+
* authored copy with composed copy would be a silent downgrade of the exact
|
|
462
|
+
* pages this is for.
|
|
463
|
+
*
|
|
464
|
+
* 🛑 And nothing is INVENTED here: a row absent from the map keeps no body at
|
|
465
|
+
* all, because `GENERATED_CATEGORY_CONTENT` omits a node it has nothing
|
|
466
|
+
* factual to say about rather than emitting an empty one.
|
|
467
|
+
*/
|
|
468
|
+
function withGeneratedContent(row) {
|
|
469
|
+
if (typeof row.contentBody === "string" && row.contentBody.trim().length > 0)
|
|
470
|
+
return row;
|
|
471
|
+
const generated = row.id ? GENERATED_CATEGORY_CONTENT[row.id] : undefined;
|
|
472
|
+
return generated ? { ...row, contentBody: generated } : row;
|
|
473
|
+
}
|
|
445
474
|
const allCategoryRows = [
|
|
446
475
|
...rawCategories.map((c) => ({ ancestors: [], ...c, createdByType: "admin" })),
|
|
447
476
|
...sublistingRows.map((s) => ({ ancestors: [], ...s, createdByType: "admin" })),
|
|
448
477
|
...brandRows.map((b) => ({ ancestors: [], ...b, createdByType: "admin" })),
|
|
449
478
|
...bundleRows.map((b) => ({ ancestors: [], ...b })),
|
|
450
|
-
]
|
|
479
|
+
]
|
|
480
|
+
.map(withGeneratedContent)
|
|
481
|
+
.map(withCategorySearchTxt);
|
|
451
482
|
/*
|
|
452
483
|
* ── Assertions, at MODULE LOAD, so a silent drop is loud ─────────────────────
|
|
453
484
|
*
|
|
@@ -487,6 +518,29 @@ const allCategoryRows = [
|
|
|
487
518
|
* Counting rows is not counting documents, and this is the one check that can
|
|
488
519
|
* tell the difference BEFORE anything is written.
|
|
489
520
|
*/
|
|
521
|
+
/*
|
|
522
|
+
* 🛑 THE GENERATED BODIES ACTUALLY LANDED ON ROWS.
|
|
523
|
+
*
|
|
524
|
+
* I7's corpus is a `Record<id, html>` keyed on category ids, and a key that no
|
|
525
|
+
* longer matches a row is invisible everywhere: the map still exports 745
|
|
526
|
+
* entries, the seed still loads 1,173 rows, `appkit-seed status` still reports
|
|
527
|
+
* in sync, and every one of those category pages renders without a body. That
|
|
528
|
+
* is the same failure shape as the 20 ids that overwrote each other below —
|
|
529
|
+
* counting the SOURCE is not counting what reached a row.
|
|
530
|
+
*
|
|
531
|
+
* Tolerance rather than exact equality, for one real reason: a hand-authored
|
|
532
|
+
* body legitimately wins over a generated one, so the number of rows carrying
|
|
533
|
+
* a GENERATED body is `COUNT` minus however many nodes have been written by
|
|
534
|
+
* hand since. A floor of 90% catches a key-space break (which drops it to ~0)
|
|
535
|
+
* without failing on authored prose being added.
|
|
536
|
+
*/
|
|
537
|
+
const generatedApplied = allCategoryRows.filter((c) => typeof c.id === "string" && GENERATED_CATEGORY_CONTENT[c.id] === c.contentBody).length;
|
|
538
|
+
if (generatedApplied < Math.floor(GENERATED_CATEGORY_CONTENT_COUNT * 0.9)) {
|
|
539
|
+
throw new Error(`category seed: only ${generatedApplied} of ${GENERATED_CATEGORY_CONTENT_COUNT} generated ` +
|
|
540
|
+
`content bodies matched a row id. The corpus is keyed on category ids, so this means ` +
|
|
541
|
+
`the key space has drifted — re-run scripts/gen-category-content.mjs against the ` +
|
|
542
|
+
`current forest. Nothing would error at load time: the pages would simply have no body.`);
|
|
543
|
+
}
|
|
490
544
|
const allIds = allCategoryRows.map((c) => c.id).filter((x) => typeof x === "string");
|
|
491
545
|
const duplicateIds = [...new Set(allIds.filter((id, i) => allIds.indexOf(id) !== i))];
|
|
492
546
|
if (duplicateIds.length > 0) {
|
|
@@ -226,8 +226,8 @@ export const groupedListingsSeedData = [
|
|
|
226
226
|
productIds: [
|
|
227
227
|
"art-dranzer-phoenix-poster",
|
|
228
228
|
"art-valkyrie-holographic-print",
|
|
229
|
-
"
|
|
230
|
-
"
|
|
229
|
+
"sticker-beyblade-original-classic-sheet",
|
|
230
|
+
"sticker-beyblade-x-holographic-pack",
|
|
231
231
|
],
|
|
232
232
|
coverImage: seedPhoto("group-beyblade-fan-art-and-stickers-20260819", 1200, 900),
|
|
233
233
|
groupTheme: "related",
|
|
@@ -21,6 +21,7 @@ import { PRODUCT_FIELDS } from "../constants/field-names.js";
|
|
|
21
21
|
import { buildSearchTxt } from "../utils/search-txt.js";
|
|
22
22
|
import { seedPhoto } from "./_helpers/media.js";
|
|
23
23
|
import { withFinalSale } from "./_helpers/final-sale-fixtures.js";
|
|
24
|
+
import { withDerivedCategoryNames } from "./_helpers/category-names.js";
|
|
24
25
|
const _rawProductsArtSeedData = [
|
|
25
26
|
{
|
|
26
27
|
id: "art-dranzer-phoenix-poster",
|
|
@@ -296,4 +297,4 @@ const _rawProductsArtSeedData = [
|
|
|
296
297
|
* so the final-sale opt-outs are applied the same way everywhere rather than
|
|
297
298
|
* being hand-written into two records that reviewers would have to find.
|
|
298
299
|
*/
|
|
299
|
-
export const productsArtSeedData = _rawProductsArtSeedData.map(withFinalSale);
|
|
300
|
+
export const productsArtSeedData = _rawProductsArtSeedData.map(withFinalSale).map(withDerivedCategoryNames);
|
|
@@ -26,6 +26,7 @@ import { PRODUCT_FIELDS } from "../constants/field-names.js";
|
|
|
26
26
|
import { buildSearchTxt } from "../utils/search-txt.js";
|
|
27
27
|
import { seedPhoto } from "./_helpers/media.js";
|
|
28
28
|
import { withFinalSale } from "./_helpers/final-sale-fixtures.js";
|
|
29
|
+
import { withDerivedCategoryNames } from "./_helpers/category-names.js";
|
|
29
30
|
function withTokens(p) {
|
|
30
31
|
return {
|
|
31
32
|
tags: [],
|
|
@@ -53,7 +54,7 @@ const _rawProductsAuctionsSeedData = [
|
|
|
53
54
|
slug: "auction-beyblade-original-dragoon-storm",
|
|
54
55
|
title: "Beyblade Original — Dragoon Storm (Rare Sealed)",
|
|
55
56
|
description: "Sealed, unopened Dragoon Storm from the original 1999-2003 series. A grail piece for original-generation collectors.",
|
|
56
|
-
categorySlugs: ["category-
|
|
57
|
+
categorySlugs: ["category-dragoon-storm", "category-original-tops", "category-beyblade-original", "category-spinning-tops"],
|
|
57
58
|
categoryNames: ["Plastic Generation", "Original Tops", "Beyblade Original", "Spinning Tops"],
|
|
58
59
|
brandSlug: "brand-beyblade",
|
|
59
60
|
brand: "Beyblade",
|
|
@@ -97,7 +98,7 @@ const _rawProductsAuctionsSeedData = [
|
|
|
97
98
|
slug: "auction-beyblade-metal-lightning-l-drago",
|
|
98
99
|
title: "Metal Fight Beyblade BB-99 Lightning L-Drago",
|
|
99
100
|
description: "Lightning L-Drago 100HF/S, a highly sought-after Metal Fury era top in excellent condition.",
|
|
100
|
-
categorySlugs: ["category-metal-
|
|
101
|
+
categorySlugs: ["category-lightning-l-drago-100hf", "category-metal-fusion", "category-metal-tops", "category-beyblade-metal", "category-spinning-tops"],
|
|
101
102
|
categoryNames: ["Metal Masters", "Metal Fight Tops", "Beyblade Metal Fight", "Spinning Tops"],
|
|
102
103
|
brandSlug: "brand-takara-tomy",
|
|
103
104
|
brand: "Takara-Tomy",
|
|
@@ -138,7 +139,7 @@ const _rawProductsAuctionsSeedData = [
|
|
|
138
139
|
slug: "auction-beyblade-x-shark-edge",
|
|
139
140
|
title: "Beyblade X BX-04 Shark Edge (Buy It Now Available)",
|
|
140
141
|
description: "Shark Edge 3-60GF straight from a fresh case pull. Grab it at the Buy It Now price before the clock runs, or start the bidding.",
|
|
141
|
-
categorySlugs: ["category-
|
|
142
|
+
categorySlugs: ["category-sharkedge-3-60lf", "category-x-tops", "category-beyblade-x", "category-spinning-tops"],
|
|
142
143
|
categoryNames: ["Basic Line", "Beyblade X Tops", "Beyblade X", "Spinning Tops"],
|
|
143
144
|
brandSlug: "brand-takara-tomy",
|
|
144
145
|
brand: "Takara-Tomy",
|
|
@@ -179,7 +180,7 @@ const _rawProductsAuctionsSeedData = [
|
|
|
179
180
|
slug: "auction-beyblade-burst-xcalius-passed-bin",
|
|
180
181
|
title: "Beyblade Burst Xcalius X2 (bidding passed Buy It Now)",
|
|
181
182
|
description: "Xcalius X2 attack type. The Buy It Now price has already been overtaken by live bidding, so only bidding remains.",
|
|
182
|
-
categorySlugs: ["category-
|
|
183
|
+
categorySlugs: ["category-xcalius-x2-1iron", "category-burst-classic", "category-burst-tops", "category-beyblade-burst", "category-spinning-tops"],
|
|
183
184
|
categoryNames: ["Cho-Z", "Burst Tops", "Beyblade Burst", "Spinning Tops"],
|
|
184
185
|
brandSlug: "brand-takara-tomy",
|
|
185
186
|
brand: "Takara-Tomy",
|
|
@@ -215,7 +216,13 @@ const _rawProductsAuctionsSeedData = [
|
|
|
215
216
|
slug: "auction-beyblade-original-seaborg",
|
|
216
217
|
title: "Beyblade Original — Seaborg 2000 (Reserve Auction)",
|
|
217
218
|
description: "Rare Seaborg 2000 defense-type top from the original series. Reserve price set by the seller — current bids haven't met it yet.",
|
|
218
|
-
|
|
219
|
+
/*
|
|
220
|
+
* Was filed at `category-original-hms` — the Heavy Metal System line, a
|
|
221
|
+
* DIFFERENT generation from the one its own copy names ("from the original
|
|
222
|
+
* series"). Seaborg 2000 is the Spin Gear System release the canonical
|
|
223
|
+
* corpus carries as "Seaborg 2", which is its leaf.
|
|
224
|
+
*/
|
|
225
|
+
categorySlugs: ["category-seaborg-2", "category-original-spin-gear", "category-original-tops", "category-beyblade-original", "category-spinning-tops"],
|
|
219
226
|
categoryNames: ["Heavy Metal System", "Original Tops", "Beyblade Original", "Spinning Tops"],
|
|
220
227
|
brandSlug: "brand-beyblade",
|
|
221
228
|
brand: "Beyblade",
|
|
@@ -252,7 +259,7 @@ const _rawProductsAuctionsSeedData = [
|
|
|
252
259
|
slug: "auction-beyblade-metal-diablo-nemesis",
|
|
253
260
|
title: "Metal Fight Beyblade BB-122 Diablo Nemesis (Ended — Sold)",
|
|
254
261
|
description: "Diablo Nemesis XF, the final boss top of Metal Fight Beyblade Zero-G. Auction closed with a winning bidder.",
|
|
255
|
-
categorySlugs: ["category-metal-fury-4d", "category-metal-tops", "category-beyblade-metal", "category-spinning-tops"],
|
|
262
|
+
categorySlugs: ["category-diablo-nemesis-x-d", "category-metal-fury-4d", "category-metal-tops", "category-beyblade-metal", "category-spinning-tops"],
|
|
256
263
|
categoryNames: ["Metal Fury (4D)", "Metal Fight Tops", "Beyblade Metal Fight", "Spinning Tops"],
|
|
257
264
|
brandSlug: "brand-takara-tomy",
|
|
258
265
|
brand: "Takara-Tomy",
|
|
@@ -294,7 +301,7 @@ const _rawProductsAuctionsSeedData = [
|
|
|
294
301
|
slug: "auction-beyblade-burst-spriggan-requiem-bought-out",
|
|
295
302
|
title: "Beyblade Burst B-128 Spriggan Requiem (Ended — Bought Out)",
|
|
296
303
|
description: "Spriggan Requiem, the dual-spin legend of Burst Super Z. Bidding had reached ₹3,100 when a buyer took the ₹4,999 Buy Now price and ended the auction early.",
|
|
297
|
-
categorySlugs: ["category-burst-tops", "category-beyblade-burst", "category-spinning-tops"],
|
|
304
|
+
categorySlugs: ["category-spriggan-requiem-0-zeta", "category-burst-cho-z", "category-burst-tops", "category-beyblade-burst", "category-spinning-tops"],
|
|
298
305
|
categoryNames: ["Burst Tops", "Beyblade Burst", "Spinning Tops"],
|
|
299
306
|
brandSlug: "brand-takara-tomy",
|
|
300
307
|
brand: "Takara-Tomy",
|
|
@@ -331,7 +338,7 @@ const _rawProductsAuctionsSeedData = [
|
|
|
331
338
|
slug: "auction-beyblade-burst-cho-z-achilles",
|
|
332
339
|
title: "Beyblade Burst B-100 Cho-Z Achilles",
|
|
333
340
|
description: "Cho-Z Achilles.Ow.Zn', a Super Zeta stamina-type top from the Cho-Z generation, in excellent spinning condition.",
|
|
334
|
-
categorySlugs: ["category-burst-
|
|
341
|
+
categorySlugs: ["category-cho-z-achilles-00-dimension", "category-burst-cho-z", "category-burst-tops", "category-beyblade-burst", "category-spinning-tops"],
|
|
335
342
|
categoryNames: ["Superking", "Burst Tops", "Beyblade Burst", "Spinning Tops"],
|
|
336
343
|
brandSlug: "brand-beyblade",
|
|
337
344
|
brand: "Beyblade",
|
|
@@ -365,9 +372,9 @@ const _rawProductsAuctionsSeedData = [
|
|
|
365
372
|
{
|
|
366
373
|
id: "auction-beyblade-x-wizard-fafnir",
|
|
367
374
|
slug: "auction-beyblade-x-wizard-fafnir",
|
|
368
|
-
title: "Beyblade X
|
|
369
|
-
description: "
|
|
370
|
-
categorySlugs: ["category-x-
|
|
375
|
+
title: "Beyblade X UX-03 WizardRod 5-70DB (Long-Running Auction)",
|
|
376
|
+
description: "WizardRod 5-70DB from the Unique Line, one week left on the clock — a rare Beyblade X chase top with active bidding.",
|
|
377
|
+
categorySlugs: ["category-wizardrod-5-70db", "category-x-unique-ux", "category-x-tops", "category-beyblade-x", "category-spinning-tops"],
|
|
371
378
|
categoryNames: ["Basic Line", "Beyblade X Tops", "Beyblade X", "Spinning Tops"],
|
|
372
379
|
brandSlug: "brand-takara-tomy",
|
|
373
380
|
brand: "Takara-Tomy",
|
|
@@ -398,15 +405,24 @@ const _rawProductsAuctionsSeedData = [
|
|
|
398
405
|
updatedAt: new Date("2026-08-19"),
|
|
399
406
|
},
|
|
400
407
|
];
|
|
401
|
-
//
|
|
402
|
-
// spread
|
|
403
|
-
//
|
|
404
|
-
//
|
|
408
|
+
// 🛑 EVERY wrapper is applied to the WHOLE array at the end, never to the
|
|
409
|
+
// spread. It used to wrap only the spread, so the ended-unsold fixture appended
|
|
410
|
+
// after it shipped with no searchTxt — the one row that proves the auction
|
|
411
|
+
// availability branch works, invisible to search.
|
|
412
|
+
//
|
|
413
|
+
// `listingType` was the SAME BUG, one field over, and survived that fix: it
|
|
414
|
+
// stayed inside the spread while `withTokens` was lifted out, so the appended
|
|
415
|
+
// fixture reached Firestore with no `listingType` at all. Measured 2026-10-14 —
|
|
416
|
+
// 9 of the 10 seeded auctions carried it and this one did not. A missing
|
|
417
|
+
// `listingType` is invisible to inspection (`normalizeListingType` defaults it
|
|
418
|
+
// to "standard" in memory) and fatal to every query, because a Firestore
|
|
419
|
+
// equality cannot match an absent field: `where("listingType","==","auction")`
|
|
420
|
+
// skipped it, so the fixture never appeared on /auctions and the "Ended" tab it
|
|
421
|
+
// exists to prove had nothing to show. Root Cause #101.
|
|
422
|
+
//
|
|
423
|
+
// Anything added to this pipeline goes AFTER the `]`, applied to all rows.
|
|
405
424
|
export const productsAuctionsSeedData = [
|
|
406
|
-
..._rawProductsAuctionsSeedData
|
|
407
|
-
...a,
|
|
408
|
-
listingType: PRODUCT_FIELDS.LISTING_TYPE_VALUES.AUCTION,
|
|
409
|
-
})),
|
|
425
|
+
..._rawProductsAuctionsSeedData,
|
|
410
426
|
{
|
|
411
427
|
// Ended with NO winner — reserve never met, zero bids.
|
|
412
428
|
//
|
|
@@ -417,9 +433,9 @@ export const productsAuctionsSeedData = [
|
|
|
417
433
|
// the base check and exercises nothing type-specific.
|
|
418
434
|
id: "auction-beyblade-burst-lord-spryzen-ended-unsold",
|
|
419
435
|
slug: "auction-beyblade-burst-lord-spryzen-ended-unsold",
|
|
420
|
-
title: "Beyblade Burst
|
|
436
|
+
title: "Beyblade Burst Storm Spriggan Knuckle Unite (Ended — Reserve Not Met)",
|
|
421
437
|
description: "Auction closed without meeting its reserve, so no winner was declared. Relisting soon.",
|
|
422
|
-
categorySlugs: ["category-burst-classic", "category-burst-tops", "category-beyblade-burst", "category-spinning-tops"],
|
|
438
|
+
categorySlugs: ["category-storm-spriggan-knuckle-unite", "category-burst-classic", "category-burst-tops", "category-beyblade-burst", "category-spinning-tops"],
|
|
423
439
|
categoryNames: ["Burst Classic", "Burst Tops", "Beyblade Burst", "Spinning Tops"],
|
|
424
440
|
brandSlug: "brand-takara-tomy",
|
|
425
441
|
brand: "Takara-Tomy",
|
|
@@ -443,4 +459,8 @@ export const productsAuctionsSeedData = [
|
|
|
443
459
|
createdAt: new Date("2026-08-10"),
|
|
444
460
|
updatedAt: new Date("2026-08-24"),
|
|
445
461
|
},
|
|
446
|
-
]
|
|
462
|
+
]
|
|
463
|
+
.map((a) => ({ ...a, listingType: PRODUCT_FIELDS.LISTING_TYPE_VALUES.AUCTION }))
|
|
464
|
+
.map(withTokens)
|
|
465
|
+
.map(withFinalSale)
|
|
466
|
+
.map(withDerivedCategoryNames);
|