@mohasinac/appkit 4.45.0 → 4.47.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/categories/live-counts.d.ts +41 -0
- package/dist/_internal/server/features/categories/live-counts.js +72 -0
- package/dist/_internal/server/features/checkout/expand-order-items.js +17 -0
- package/dist/_internal/server/features/orders/adapters.js +29 -0
- package/dist/_internal/server/features/parts/assembly.d.ts +126 -0
- package/dist/_internal/server/features/parts/assembly.js +246 -0
- package/dist/_internal/server/features/parts/data.d.ts +65 -0
- package/dist/_internal/server/features/parts/data.js +170 -0
- package/dist/_internal/server/features/parts/og.d.ts +34 -0
- package/dist/_internal/server/features/parts/og.js +66 -0
- package/dist/_internal/server/features/search/search-everything.d.ts +75 -0
- package/dist/_internal/server/features/search/search-everything.js +120 -0
- package/dist/_internal/server/features/search/search-sources.d.ts +17 -0
- package/dist/_internal/server/features/search/search-sources.js +138 -0
- package/dist/_internal/server/jobs/core/categoryTaxonomyBackfill.d.ts +70 -0
- package/dist/_internal/server/jobs/core/categoryTaxonomyBackfill.js +302 -0
- package/dist/_internal/server/jobs/core/countersReconcile.js +128 -19
- package/dist/_internal/server/jobs/core/jobRunners.js +12 -0
- package/dist/_internal/server/jobs/core/onCategoryWrite.js +73 -3
- package/dist/_internal/server/jobs/core/onProductWrite.js +93 -43
- package/dist/_internal/server/jobs/core/productViewWindows.d.ts +2 -0
- package/dist/_internal/server/jobs/core/productViewWindows.js +173 -0
- package/dist/_internal/server/jobs/core/testDataCleanup.d.ts +1 -1
- package/dist/_internal/server/jobs/core/testDataCleanup.js +27 -1
- package/dist/_internal/server/jobs/handlers/pageViewRollup.d.ts +20 -0
- package/dist/_internal/server/jobs/handlers/pageViewRollup.js +25 -1
- package/dist/_internal/shared/actions/action-registry.js +36 -0
- package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
- package/dist/_internal/shared/features/shipping/protections.d.ts +93 -1
- package/dist/_internal/shared/features/shipping/protections.js +127 -0
- package/dist/_internal/shared/search/part-aliases.d.ts +44 -0
- package/dist/_internal/shared/search/part-aliases.js +104 -0
- package/dist/constants/api-endpoints.d.ts +18 -12
- package/dist/constants/api-endpoints.js +6 -8
- package/dist/constants/field-names.d.ts +0 -2
- package/dist/constants/field-names.js +0 -2
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/core/index.d.ts +0 -3
- package/dist/core/index.js +0 -2
- package/dist/errors/messages.d.ts +0 -7
- package/dist/errors/messages.js +0 -8
- package/dist/features/admin/components/AdminSectionsView.js +83 -32
- package/dist/features/admin/components/AdminSiteSettingsView.js +6 -2
- package/dist/features/admin/components/sections/sectionTemplates.d.ts +117 -0
- package/dist/features/admin/components/sections/sectionTemplates.js +213 -0
- package/dist/features/admin/schemas/firestore.d.ts +15 -0
- package/dist/features/admin/types/index.d.ts +0 -1
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +0 -2
- package/dist/features/categories/api/[id]/description-template/route.js +40 -1
- package/dist/features/categories/components/BrandDetailPageView.js +14 -3
- package/dist/features/categories/components/BreadcrumbTrail.d.ts +24 -1
- package/dist/features/categories/components/BreadcrumbTrail.js +45 -4
- package/dist/features/categories/components/CategoryDetailPageView.js +89 -14
- package/dist/features/categories/components/CategoryTemplatePicker.js +9 -1
- package/dist/features/categories/repository/categories.repository.d.ts +65 -0
- package/dist/features/categories/repository/categories.repository.js +168 -22
- package/dist/features/categories/repository/category-counts.d.ts +143 -0
- package/dist/features/categories/repository/category-counts.js +224 -0
- package/dist/features/categories/schemas/category-counts.d.ts +155 -0
- package/dist/features/categories/schemas/category-counts.js +116 -0
- package/dist/features/categories/schemas/firestore.d.ts +0 -2
- package/dist/features/categories/schemas/firestore.js +0 -1
- package/dist/features/categories/utils/breadcrumb-chain.d.ts +194 -0
- package/dist/features/categories/utils/breadcrumb-chain.js +202 -0
- package/dist/features/corporate/schemas/index.d.ts +4 -4
- package/dist/features/orders/schemas/firestore.d.ts +13 -0
- package/dist/features/orders/types/index.d.ts +30 -0
- package/dist/features/parts/components/AssemblyDrawer.d.ts +107 -0
- package/dist/features/parts/components/AssemblyDrawer.js +250 -0
- package/dist/features/parts/components/ModelCompositionPanel.d.ts +37 -0
- package/dist/features/parts/components/ModelCompositionPanel.js +34 -0
- package/dist/features/parts/components/PartDetailPageView.d.ts +48 -0
- package/dist/features/parts/components/PartDetailPageView.js +20 -0
- package/dist/features/parts/components/PartsIndexPageView.d.ts +33 -0
- package/dist/features/parts/components/PartsIndexPageView.js +17 -0
- package/dist/features/parts/schemas/firestore.d.ts +257 -0
- package/dist/features/parts/schemas/firestore.js +181 -0
- package/dist/features/parts/utils/colourway-match.d.ts +62 -0
- package/dist/features/parts/utils/colourway-match.js +98 -0
- package/dist/features/parts/utils/part-breakdown.d.ts +53 -0
- package/dist/features/parts/utils/part-breakdown.js +77 -0
- package/dist/features/products/components/ProductDetailPageView.d.ts +14 -1
- package/dist/features/products/components/ProductDetailPageView.js +22 -3
- package/dist/features/products/components/ProductTabsShell.d.ts +3 -1
- package/dist/features/products/components/ProductTabsShell.js +4 -1
- package/dist/features/products/repository/category-assignment.d.ts +132 -0
- package/dist/features/products/repository/category-assignment.js +304 -0
- package/dist/features/products/repository/products.repository.d.ts +58 -1
- package/dist/features/products/repository/products.repository.js +255 -28
- package/dist/features/products/schemas/firestore.d.ts +147 -6
- package/dist/features/products/schemas/firestore.js +49 -0
- package/dist/features/search/components/SearchResultsPageView.d.ts +33 -0
- package/dist/features/search/components/SearchResultsPageView.js +21 -0
- 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 +26 -1
- package/dist/features/seller/components/SellerProductShell.js +30 -1
- package/dist/features/seller/components/SellerProductsView.js +129 -3
- package/dist/features/tester/seed-data/authored/admin__site-system.js +0 -15
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +166 -0
- package/dist/features/tester/seed-data/authored/index.js +2 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +28 -1
- package/dist/index.d.ts +11 -16
- package/dist/index.js +26 -18
- package/dist/next/routing/route-map.d.ts +6 -2
- package/dist/next/routing/route-map.js +14 -1
- package/dist/providers/db-firebase/base.repository.d.ts +31 -0
- package/dist/providers/db-firebase/base.repository.js +31 -0
- package/dist/repositories/index.d.ts +1 -2
- package/dist/repositories/index.js +0 -1
- package/dist/seed/_helpers/category-content-generated.d.ts +24 -0
- package/dist/seed/_helpers/category-content-generated.js +1837 -0
- package/dist/seed/_helpers/category-content.d.ts +29 -0
- package/dist/seed/_helpers/category-content.js +98 -1
- package/dist/seed/_helpers/category-forest.js +104 -24
- package/dist/seed/_helpers/category-parts.d.ts +9 -0
- package/dist/seed/_helpers/category-parts.js +4594 -0
- package/dist/seed/carousel-slides-seed-data.js +81 -8
- package/dist/seed/categories-seed-data.js +44 -6
- package/dist/seed/homepage-sections-seed-data.js +170 -9
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.js +13 -0
- package/dist/seed/model-composition-seed-data.d.ts +15 -0
- package/dist/seed/model-composition-seed-data.js +8918 -0
- package/dist/seed/part-glossary-seed-data.d.ts +15 -0
- package/dist/seed/part-glossary-seed-data.js +10729 -0
- package/dist/seed/products-standard-seed-data.js +52 -0
- package/dist/seed/seed-purpose.d.ts +103 -4
- package/dist/seed/seed-purpose.js +162 -11
- package/dist/seo/json-ld.d.ts +10 -1
- package/dist/seo/json-ld.js +9 -1
- package/dist/server.d.ts +7 -0
- package/dist/server.js +11 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/utils/id-generators.d.ts +30 -0
- package/dist/utils/id-generators.js +31 -1
- package/dist/utils/search-txt-builders.js +22 -0
- package/dist/utils/search-txt.d.ts +22 -0
- package/dist/utils/search-txt.js +17 -0
- package/dist/values/success-messages.d.ts +0 -4
- package/dist/values/success-messages.js +0 -5
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +292 -16
- package/dist/core/copilot-log.repository.d.ts +0 -44
- package/dist/core/copilot-log.repository.js +0 -61
- package/dist/features/copilot/components/AdminCopilotView.d.ts +0 -21
- package/dist/features/copilot/components/AdminCopilotView.js +0 -51
- package/dist/features/copilot/components/index.d.ts +0 -2
- package/dist/features/copilot/components/index.js +0 -1
- package/dist/features/copilot/hooks/useCopilotChat.d.ts +0 -25
- package/dist/features/copilot/hooks/useCopilotChat.js +0 -65
- package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +0 -15
- package/dist/features/copilot/hooks/useCopilotFeedback.js +0 -15
- package/dist/features/copilot/index.d.ts +0 -3
- package/dist/features/copilot/index.js +0 -3
|
@@ -194,8 +194,46 @@ const _rawSlides = [
|
|
|
194
194
|
updatedAt: daysAgo(3),
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
|
+
/*
|
|
198
|
+
* ── G1c — THE AUTHENTICITY SLIDE ────────────────────────────────────────
|
|
199
|
+
*
|
|
200
|
+
* 🛑 This slide's id has said `authenticated-originals` since it was
|
|
201
|
+
* written and its copy said "Turn your collection into income" — a
|
|
202
|
+
* seller-acquisition message under an authenticity name. So authenticity,
|
|
203
|
+
* which is this marketplace's actual differentiator, had NO hero surface
|
|
204
|
+
* at all while the id implied it did.
|
|
205
|
+
*
|
|
206
|
+
* It is the one hero message the crawl independently validates three ways:
|
|
207
|
+
* beybladeartshop runs it as their slide 5 ("100% original Takara Tomy
|
|
208
|
+
* Beyblades. Don't fall for cheap imitations!"); worldhobbyshop — the SEO
|
|
209
|
+
* leader of the fifteen — ships a top-level "MidFake Beyblades" category
|
|
210
|
+
* and an FAQ promising that midfake and reproduction stock is kept
|
|
211
|
+
* separate with authenticity disclosed; and we already have the feature
|
|
212
|
+
* (`ProductAuthenticity` is a REQUIRED field with a badge and four FAQ
|
|
213
|
+
* rows) with nowhere on the homepage that says so.
|
|
214
|
+
*
|
|
215
|
+
* 🛑 THE COPY ANSWERS "AFTERMARKET", NOT "FAKE", and that is the finding
|
|
216
|
+
* that matters. The counterfeit seller measured in section 3.7 makes **zero**
|
|
217
|
+
* authenticity claims across 1,277 listings — no "original", no "replica",
|
|
218
|
+
* no "fake" — and sells itself as "a popular brand" offering "custom parts
|
|
219
|
+
* or upgraded components", "like many other aftermarket brands". So
|
|
220
|
+
* "aftermarket" and "custom" are the words our buyers will actually be
|
|
221
|
+
* sold, and a slide that only rebuts the word "fake" rebuts nothing they
|
|
222
|
+
* will hear.
|
|
223
|
+
*
|
|
224
|
+
* It also states the three-tier vocabulary rather than a binary, because
|
|
225
|
+
* the tiers are the honest part: a reproduction IS sellable when labelled,
|
|
226
|
+
* and claiming everything is original would be the same unfalsifiable
|
|
227
|
+
* promise the counterfeiter makes in reverse.
|
|
228
|
+
*
|
|
229
|
+
* 🛑 Seller acquisition is NOT lost — it keeps its own `banner` section
|
|
230
|
+
* ("Have collectibles to sell?" → "Start selling →"), the `features` and
|
|
231
|
+
* `stats` sections, and two nav entries. Verified before this was changed.
|
|
232
|
+
* `MAX_ACTIVE_SLIDES` is 5 and all five were taken, so this is a swap
|
|
233
|
+
* rather than an addition.
|
|
234
|
+
*/
|
|
197
235
|
id: "slide-authenticated-originals",
|
|
198
|
-
title: "
|
|
236
|
+
title: "Original, reproduction, or unverified — every listing says which",
|
|
199
237
|
order: 5,
|
|
200
238
|
active: true,
|
|
201
239
|
background: {
|
|
@@ -205,18 +243,53 @@ const _rawSlides = [
|
|
|
205
243
|
},
|
|
206
244
|
overlay: {
|
|
207
245
|
enabled: true,
|
|
208
|
-
subtitle: "
|
|
209
|
-
|
|
210
|
-
|
|
246
|
+
subtitle: "Authenticity, stated",
|
|
247
|
+
// 🛑 "We make sellers say which" was the same overclaim as the body —
|
|
248
|
+
// the field is optional, so nobody is MADE to. What is true is that
|
|
249
|
+
// every listing carries a label, which is the verifiable version.
|
|
250
|
+
title: "“Aftermarket” is not a brand. Every listing says which.",
|
|
251
|
+
/*
|
|
252
|
+
* 🛑 THE CLAIM WAS FALSE WHEN FIRST WRITTEN, and the check is the point.
|
|
253
|
+
*
|
|
254
|
+
* It read "the seller has to pick one before it can go live" — and
|
|
255
|
+
* `authenticity` is `.optional()` in the write schema while being ABSENT
|
|
256
|
+
* on all 71 live products. A hero promise nobody could keep, on the one
|
|
257
|
+
* subject where this marketplace's credibility rests.
|
|
258
|
+
*
|
|
259
|
+
* Both halves were fixed before the copy was: the root default now makes
|
|
260
|
+
* 381/381 listing nodes resolve a value, and `withDerivedCategoryFields`
|
|
261
|
+
* stamps it on every seeded product (measured after loading:
|
|
262
|
+
* `original=4`, `unverified=67`, zero absent).
|
|
263
|
+
*
|
|
264
|
+
* The copy still says what is TRUE rather than what would be strongest.
|
|
265
|
+
* A seller may still omit the field — the category then decides, and it
|
|
266
|
+
* decides `unverified`, never `original`. So the promise is "every
|
|
267
|
+
* listing carries a label and we never silently claim original", which
|
|
268
|
+
* is both accurate and the more useful thing to say.
|
|
269
|
+
*/
|
|
270
|
+
description: "Every listing carries one of three labels — original, reproduction or unverified. A seller can say which; if they do not, it reads “unverified”, because we will not claim “original” on your behalf. Reproductions are welcome here when they are labelled; a listing that leaves you guessing is not.",
|
|
211
271
|
button: {
|
|
212
|
-
id: "slide-
|
|
213
|
-
text: "
|
|
214
|
-
|
|
272
|
+
id: "slide-authenticity-cta",
|
|
273
|
+
text: "How to spot a fake",
|
|
274
|
+
/*
|
|
275
|
+
* 🛑 `/faqs/product_information`, the CATEGORY — not a question slug.
|
|
276
|
+
* The route is `/faqs/[category]` and it matches on
|
|
277
|
+
* `CATEGORY_KEYS.includes(category)`, so `/faqs/faq-how-to-spot-a-fake`
|
|
278
|
+
* would silently fall back to "all" and land the reader on the full
|
|
279
|
+
* index instead of the answer. Verified against the route before
|
|
280
|
+
* writing it, because the FAQ documents DO carry a `seo.slug` of
|
|
281
|
+
* exactly that shape, which makes the wrong link the obvious one.
|
|
282
|
+
*
|
|
283
|
+
* This category holds both authenticity answers — "How do I spot a fake
|
|
284
|
+
* Beyblade?" and "What is a reproduction or midfake? Is it a fake?" —
|
|
285
|
+
* which is the pair the slide's claim rests on.
|
|
286
|
+
*/
|
|
287
|
+
link: "/faqs/product_information",
|
|
215
288
|
variant: "primary",
|
|
216
289
|
openInNewTab: false,
|
|
217
290
|
},
|
|
218
291
|
},
|
|
219
|
-
link: { url:
|
|
292
|
+
link: { url: "/faqs/product_information", openInNewTab: false },
|
|
220
293
|
settings: SLIDE_SETTINGS,
|
|
221
294
|
createdBy: "user-admin-letitrip",
|
|
222
295
|
createdAt: daysAgo(40),
|
|
@@ -57,6 +57,7 @@ import { withCategorySearchTxt } from "./_helpers/search-txt-wrappers.js";
|
|
|
57
57
|
import { CATEGORY_FOREST } from "./_helpers/category-forest.js";
|
|
58
58
|
import { BRAND_ROW_COUNT, buildBrandRows } from "./_helpers/brand-rows.js";
|
|
59
59
|
import { MODEL_LEAF_COUNT } from "./_helpers/category-models.js";
|
|
60
|
+
import { PART_LEAF_COUNT } from "./_helpers/category-parts.js";
|
|
60
61
|
import { MARKET_BAND_LEAF_IDS } from "./_helpers/category-content.js";
|
|
61
62
|
import { productsStandardSeedData } from "./products-standard-seed-data.js";
|
|
62
63
|
const NOW = new Date();
|
|
@@ -461,12 +462,49 @@ const allCategoryRows = [
|
|
|
461
462
|
* (the seed CLI, the tester fixtures, the audits) wants to fail before writing
|
|
462
463
|
* rather than after.
|
|
463
464
|
*/
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
465
|
+
/*
|
|
466
|
+
* 🛑 TIER 4 IS NOW TWO GENERATED FAMILIES, not one.
|
|
467
|
+
*
|
|
468
|
+
* This assertion compared the tier-4 count against `MODEL_LEAF_COUNT` alone,
|
|
469
|
+
* and it CAUGHT the part leaves the moment they landed — 968 against an
|
|
470
|
+
* expected 216 — which is precisely the job it was written for. Widening it to
|
|
471
|
+
* the sum is the fix; deleting it or loosening it to `>=` would retire the one
|
|
472
|
+
* check that can see a generated map silently empty.
|
|
473
|
+
*
|
|
474
|
+
* Kept as an exact equality for the same reason: a `>=` would pass with
|
|
475
|
+
* `PART_LEAVES` entirely gone.
|
|
476
|
+
*/
|
|
477
|
+
/*
|
|
478
|
+
* 🛑 DISTINCT IDS. This assertion did not exist, and its absence is what let 20
|
|
479
|
+
* part leaves overwrite 20 model leaves in production.
|
|
480
|
+
*
|
|
481
|
+
* The whole failure was silent at every layer: the forest walked 1,138 nodes
|
|
482
|
+
* with only 1,118 distinct ids, `appkit-seed load` is a `set(…, {merge:true})`
|
|
483
|
+
* so the second row with an id quietly replaced the first, and the CLI reported
|
|
484
|
+
* `created 1173, errors 0` while Firestore held 1,153 documents. A buyer opening
|
|
485
|
+
* `/categories/category-galeon` got the attack ring instead of the bey.
|
|
486
|
+
*
|
|
487
|
+
* Counting rows is not counting documents, and this is the one check that can
|
|
488
|
+
* tell the difference BEFORE anything is written.
|
|
489
|
+
*/
|
|
490
|
+
const allIds = allCategoryRows.map((c) => c.id).filter((x) => typeof x === "string");
|
|
491
|
+
const duplicateIds = [...new Set(allIds.filter((id, i) => allIds.indexOf(id) !== i))];
|
|
492
|
+
if (duplicateIds.length > 0) {
|
|
493
|
+
throw new Error(`category seed: ${duplicateIds.length} duplicate id(s) — a merge write would ` +
|
|
494
|
+
`silently overwrite, so the load would report success and lose rows. ` +
|
|
495
|
+
`First few: ${duplicateIds.slice(0, 8).join(", ")}`);
|
|
496
|
+
}
|
|
497
|
+
if (allIds.length !== allCategoryRows.length) {
|
|
498
|
+
throw new Error(`category seed: ${allCategoryRows.length - allIds.length} row(s) have no string id`);
|
|
499
|
+
}
|
|
500
|
+
const tier4Rows = rawCategories.filter((c) => c.tier === 4).length;
|
|
501
|
+
const expectedTier4 = MODEL_LEAF_COUNT + PART_LEAF_COUNT;
|
|
502
|
+
if (tier4Rows !== expectedTier4) {
|
|
503
|
+
throw new Error(`category seed: ${tier4Rows} tier-4 leaves reached the tree but the generated ` +
|
|
504
|
+
`maps declare ${expectedTier4} (${MODEL_LEAF_COUNT} models + ${PART_LEAF_COUNT} parts). ` +
|
|
505
|
+
`A parent id in the forest no longer matches a key in MODEL_LEAVES or PART_LEAVES — ` +
|
|
506
|
+
`re-run node scripts/gen-category-models.mjs and node scripts/gen-category-parts.mjs ` +
|
|
507
|
+
`and check the parent ids.`);
|
|
470
508
|
}
|
|
471
509
|
if (brandRows.length !== BRAND_ROW_COUNT) {
|
|
472
510
|
throw new Error(`category seed: ${brandRows.length} brand rows built, expected ${BRAND_ROW_COUNT}`);
|
|
@@ -446,20 +446,62 @@ export const homepageSectionsSeedData = [
|
|
|
446
446
|
title: "Frequently Asked Questions",
|
|
447
447
|
subtitle: "The questions new buyers ask most.",
|
|
448
448
|
showOnHomepage: true,
|
|
449
|
-
|
|
449
|
+
/*
|
|
450
|
+
* B6b — 6 → 10. The FAQ block is the densest single SEO win on the page
|
|
451
|
+
* (worldhobbyshop's 10-question block is a measured part of why it leads
|
|
452
|
+
* the fifteen on SEO) and it is an answer-engine surface, so it feeds
|
|
453
|
+
* `faqJsonLd`. 20 rows are flagged `showOnHomepage`; showing 10 of them
|
|
454
|
+
* is the intended shape.
|
|
455
|
+
*/
|
|
456
|
+
displayCount: 10,
|
|
450
457
|
linkToFullPage: true,
|
|
451
458
|
viewMoreLabel: "View all FAQs →",
|
|
452
459
|
showCategoryTabs: true,
|
|
453
|
-
|
|
460
|
+
/*
|
|
461
|
+
* 🛑 EMPTY ON PURPOSE — the tabs DERIVE from the items actually shown.
|
|
462
|
+
*
|
|
463
|
+
* This listed `["shipping_delivery", "returns_refunds",
|
|
464
|
+
* "orders_payment"]` and measured live on 2026-10-12 **all three
|
|
465
|
+
* rendered EMPTY PANELS**, while the three categories whose rows were
|
|
466
|
+
* actually displayed (`general`, `product_information`,
|
|
467
|
+
* `fees_and_taxes`) had no tab at all.
|
|
468
|
+
*
|
|
469
|
+
* The cause is that the two halves answer different questions and
|
|
470
|
+
* neither knows about the other: the ITEMS come from
|
|
471
|
+
* `getHomepageFAQs()` — `showOnHomepage` ordered by `priority` DESC,
|
|
472
|
+
* then sliced to `displayCount` — while the TABS were a hand-written
|
|
473
|
+
* list. Raise a row's priority, or add a higher-priority row in another
|
|
474
|
+
* category (B6b's FAQ half added 22, including a whole new
|
|
475
|
+
* `fees_and_taxes`), and the slice moves out from under the tabs
|
|
476
|
+
* silently. That is Root Cause #72's shape: a tab you can click with
|
|
477
|
+
* nothing behind it.
|
|
478
|
+
*
|
|
479
|
+
* `FAQSection` already derives tabs from its items when `tabs` is empty
|
|
480
|
+
* (`if (tabsProp && tabsProp.length > 0) return tabsProp;`), so an empty
|
|
481
|
+
* array makes an empty tab STRUCTURALLY IMPOSSIBLE rather than something
|
|
482
|
+
* to re-sync by hand. A hand-maintained enumeration beside a query is
|
|
483
|
+
* the defect, not the list's contents.
|
|
484
|
+
*
|
|
485
|
+
* 🛑 `[]` rather than deleting the key, because `appkit-seed load` is a
|
|
486
|
+
* MERGE write and cannot remove a field — dropping it from the seed
|
|
487
|
+
* would leave the three stale values in Firestore forever and the fix
|
|
488
|
+
* would read as applied while changing nothing.
|
|
489
|
+
*/
|
|
490
|
+
visibleTabs: [],
|
|
454
491
|
allowMultipleOpen: false,
|
|
455
492
|
defaultOpenCount: 1,
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
493
|
+
/*
|
|
494
|
+
* 🛑 NOT a filter, despite the name. The renderer uses this ONLY as a
|
|
495
|
+
* fallback tab list when `visibleTabs` is empty
|
|
496
|
+
* (`cfg?.visibleTabs?.length ? cfg.visibleTabs : cfg?.categories`), and
|
|
497
|
+
* the items are never filtered by it. So a category absent from here is
|
|
498
|
+
* still displayed — which is why `fees_and_taxes` rows appear on the
|
|
499
|
+
* homepage today while being absent from this list.
|
|
500
|
+
*
|
|
501
|
+
* Emptied too, or it would simply become the hand-written tab list this
|
|
502
|
+
* change exists to delete.
|
|
503
|
+
*/
|
|
504
|
+
categories: [],
|
|
463
505
|
},
|
|
464
506
|
createdAt: daysAgo(90),
|
|
465
507
|
updatedAt: daysAgo(16),
|
|
@@ -517,4 +559,123 @@ export const homepageSectionsSeedData = [
|
|
|
517
559
|
* array does not remove them from Firestore — delete both documents at
|
|
518
560
|
* /admin/sections after seeding, or they keep rendering.
|
|
519
561
|
*/
|
|
562
|
+
/*
|
|
563
|
+
* ── B6b — THE SYSTEMS MATRIX ─────────────────────────────────────────────
|
|
564
|
+
*
|
|
565
|
+
* worldhobbyshop's single biggest SEO asset, and it is TAXONOMY ROUTING
|
|
566
|
+
* rather than merchandising: 25 internal links telling a crawler which
|
|
567
|
+
* sub-collections exist. It is most of why they have 79 unique internal
|
|
568
|
+
* links (BAS has 57) while running the FEWEST products and words on their
|
|
569
|
+
* homepage of the fifteen crawled — 8 product cards and 1,816 words.
|
|
570
|
+
*
|
|
571
|
+
* 🛑 `custom-cards`, NOT `collection-cards`, and the plan had this wrong.
|
|
572
|
+
* `collection-cards` is capped at `COLLECTION_CARDS_MAX_ENTRIES = 3` and its
|
|
573
|
+
* entries are LIVE-CONTENT queries (`{type, limit, categorySlug}`), so it
|
|
574
|
+
* cannot express a link matrix at all. `custom-cards` carries
|
|
575
|
+
* `cards[].buttons[]` of `{label, href}`, which is exactly a matrix.
|
|
576
|
+
*
|
|
577
|
+
* 🛑 Every href is a REAL post-B5 category id, read off `categoriesSeedData`
|
|
578
|
+
* rather than typed from memory. A dead link here is the Root Cause #33
|
|
579
|
+
* shape on the routing axis — it 404s, and `audit-nav-page-wiring` does not
|
|
580
|
+
* reach section configs.
|
|
581
|
+
*
|
|
582
|
+
* No prices anywhere, which is what makes this safe where a price-band tile
|
|
583
|
+
* is not — see the note below the array.
|
|
584
|
+
*/
|
|
585
|
+
{
|
|
586
|
+
id: "section-systems-matrix",
|
|
587
|
+
type: "custom-cards",
|
|
588
|
+
order: 5,
|
|
589
|
+
enabled: true,
|
|
590
|
+
config: {
|
|
591
|
+
title: "Every system, every part family",
|
|
592
|
+
layout: "grid",
|
|
593
|
+
columns: 4,
|
|
594
|
+
autoScroll: false,
|
|
595
|
+
loop: false,
|
|
596
|
+
cards: [
|
|
597
|
+
{
|
|
598
|
+
id: "matrix-original",
|
|
599
|
+
eyebrow: "1999–2003",
|
|
600
|
+
title: "Beyblade Original",
|
|
601
|
+
body: "Four part systems in four years, and they do not interchange. Plastic Generation through Heavy Metal System.",
|
|
602
|
+
buttons: [
|
|
603
|
+
{ label: "Plastic Generation", href: "/categories/category-original-plastic-gen", variant: "ghost" },
|
|
604
|
+
{ label: "Spin Gear", href: "/categories/category-original-spin-gear", variant: "ghost" },
|
|
605
|
+
{ label: "Magnacore", href: "/categories/category-original-magnacore", variant: "ghost" },
|
|
606
|
+
{ label: "Engine Gear", href: "/categories/category-original-engine-gear", variant: "ghost" },
|
|
607
|
+
{ label: "Heavy Metal System", href: "/categories/category-original-hms", variant: "ghost" },
|
|
608
|
+
{ label: "Parts", href: "/categories/category-original-parts", variant: "outline" },
|
|
609
|
+
{ label: "Launchers", href: "/categories/category-original-launchers", variant: "outline" },
|
|
610
|
+
],
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
id: "matrix-metal",
|
|
614
|
+
eyebrow: "2008–2013",
|
|
615
|
+
title: "Metal Fight",
|
|
616
|
+
body: "The tuning era, and the only generation where loose parts outnumber complete tops.",
|
|
617
|
+
buttons: [
|
|
618
|
+
{ label: "Metal System", href: "/categories/category-metal-phws", variant: "ghost" },
|
|
619
|
+
{ label: "Metal Fusion", href: "/categories/category-metal-fusion", variant: "ghost" },
|
|
620
|
+
{ label: "Metal Masters", href: "/categories/category-metal-masters", variant: "ghost" },
|
|
621
|
+
{ label: "Metal Fury (4D)", href: "/categories/category-metal-fury-4d", variant: "ghost" },
|
|
622
|
+
{ label: "Shogun Steel", href: "/categories/category-metal-shogun-steel", variant: "ghost" },
|
|
623
|
+
{ label: "Parts", href: "/categories/category-metal-parts", variant: "outline" },
|
|
624
|
+
{ label: "Launchers", href: "/categories/category-metal-launchers", variant: "outline" },
|
|
625
|
+
],
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
id: "matrix-burst",
|
|
629
|
+
eyebrow: "2015–2023",
|
|
630
|
+
title: "Beyblade Burst",
|
|
631
|
+
body: "Seven waves of the bursting mechanic, from single-layer Classic to Burst Ultimate.",
|
|
632
|
+
buttons: [
|
|
633
|
+
{ label: "Burst Classic", href: "/categories/category-burst-classic", variant: "ghost" },
|
|
634
|
+
{ label: "Burst God", href: "/categories/category-burst-god", variant: "ghost" },
|
|
635
|
+
{ label: "Cho-Z", href: "/categories/category-burst-cho-z", variant: "ghost" },
|
|
636
|
+
{ label: "GT", href: "/categories/category-burst-gt", variant: "ghost" },
|
|
637
|
+
{ label: "Superking", href: "/categories/category-burst-superking", variant: "ghost" },
|
|
638
|
+
{ label: "Dynamite Battle", href: "/categories/category-burst-db", variant: "ghost" },
|
|
639
|
+
{ label: "Burst Ultimate", href: "/categories/category-burst-bu", variant: "ghost" },
|
|
640
|
+
{ label: "Parts", href: "/categories/category-burst-parts", variant: "outline" },
|
|
641
|
+
],
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
id: "matrix-x",
|
|
645
|
+
eyebrow: "2023–",
|
|
646
|
+
title: "Beyblade X",
|
|
647
|
+
body: "The Xtreme Line rail is the centre of the design — blades, ratchets and bits, and a stadium that is part of the game.",
|
|
648
|
+
buttons: [
|
|
649
|
+
{ label: "Basic Line", href: "/categories/category-x-basic", variant: "ghost" },
|
|
650
|
+
{ label: "Unique Line (UX)", href: "/categories/category-x-unique-ux", variant: "ghost" },
|
|
651
|
+
{ label: "Custom Line (CX)", href: "/categories/category-x-custom-cx", variant: "ghost" },
|
|
652
|
+
{ label: "X-Over Project", href: "/categories/category-x-over", variant: "ghost" },
|
|
653
|
+
{ label: "Parts", href: "/categories/category-x-parts", variant: "outline" },
|
|
654
|
+
{ label: "Launchers", href: "/categories/category-x-launchers", variant: "outline" },
|
|
655
|
+
{ label: "Stadiums", href: "/categories/category-x-stadiums", variant: "outline" },
|
|
656
|
+
],
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
},
|
|
660
|
+
createdAt: daysAgo(1),
|
|
661
|
+
updatedAt: daysAgo(1),
|
|
662
|
+
},
|
|
663
|
+
/*
|
|
664
|
+
* 🛑 TCI's five BUDGET TILES ("Under ₹499 / ₹799 / ₹999 / ₹1499") are
|
|
665
|
+
* DELIBERATELY NOT SEEDED, and this is a decision rather than an omission.
|
|
666
|
+
*
|
|
667
|
+
* The plan argued they are safe because "the tiles are LINKS, not prices".
|
|
668
|
+
* That is true of the tile and false of the mechanism. Prices are hidden from
|
|
669
|
+
* signed-out visitors, and the price-range facet and every money sort are
|
|
670
|
+
* withheld specifically because "hiding the number is not hiding the value" —
|
|
671
|
+
* bracketing a hidden price with a range filter is the one casually reachable
|
|
672
|
+
* way around the gate. The documented residue is that a HAND-WRITTEN
|
|
673
|
+
* `?f=price<499` still works, and five tiles on the homepage promote that
|
|
674
|
+
* residue to the most prominent path on the site: a guest could bucket the
|
|
675
|
+
* entire catalogue into five price bands in five clicks, without an account.
|
|
676
|
+
*
|
|
677
|
+
* The matrix above has no such property — a category link reveals no amount.
|
|
678
|
+
* Revisit if guest price-gating is ever relaxed; until then this is a tile
|
|
679
|
+
* that would quietly undo a gate another step built.
|
|
680
|
+
*/
|
|
520
681
|
];
|
package/dist/seed/index.d.ts
CHANGED
|
@@ -90,3 +90,5 @@ export { mergeFirestoreIndices, generateMergedFirestoreIndexFile, } from "./fire
|
|
|
90
90
|
export { assertPiiRoundTrip, seedForTest } from "./test-utils";
|
|
91
91
|
export type { TestSeedHandles } from "./test-utils";
|
|
92
92
|
export { type SeedPurpose, type SeedPurposeFields, type WithSeedPurpose, SEED_PURPOSE_VALUES, ALL_SEED_PURPOSES, SEED_COLLECTION_PURPOSE, SEED_TEST_TTL_DAYS, isSeedPurpose, resolveSeedPurpose, seedPurposeFields, stampSeedPurpose, asSeedPurpose, } from "./seed-purpose";
|
|
93
|
+
export { partGlossarySeedData } from "./part-glossary-seed-data";
|
|
94
|
+
export { modelCompositionSeedData } from "./model-composition-seed-data";
|
package/dist/seed/index.js
CHANGED
|
@@ -35,6 +35,16 @@ export { storesSeedData } from "./stores-seed-data.js";
|
|
|
35
35
|
export { storeAddressesSeedData } from "./store-addresses-seed-data.js";
|
|
36
36
|
export { categoriesSeedData, categoriesP1SeedData } from "./categories-seed-data.js";
|
|
37
37
|
export { productsStandardSeedData } from "./products-standard-seed-data.js";
|
|
38
|
+
/*
|
|
39
|
+
* 🛑 `productsCatalogueSeedData` DELETED (I6, 2026-10-13) — 968 generated
|
|
40
|
+
* listings, one per tier-4 leaf. "I do not need products ... rest is either
|
|
41
|
+
* standard or test data."
|
|
42
|
+
*
|
|
43
|
+
* A merge-write loader CANNOT remove a document, so deleting the file alone
|
|
44
|
+
* leaves all 968 rows live and `appkit-seed status` reports the collection in
|
|
45
|
+
* sync either way (it counts ids). The removal is
|
|
46
|
+
* npx appkit-seed delete --yes --collections products && npx appkit-seed load
|
|
47
|
+
*/
|
|
38
48
|
export { productsAuctionsSeedData } from "./products-auctions-seed-data.js";
|
|
39
49
|
export { productsPreordersSeedData } from "./products-preorders-seed-data.js";
|
|
40
50
|
export { productsPrizeDrawsSeedData } from "./products-prize-draws-seed-data.js";
|
|
@@ -92,3 +102,6 @@ export { assertPiiRoundTrip, seedForTest } from "./test-utils.js";
|
|
|
92
102
|
* must never be hand-written beside it (`audit-seed-purpose` blocks it).
|
|
93
103
|
*/
|
|
94
104
|
export { SEED_PURPOSE_VALUES, ALL_SEED_PURPOSES, SEED_COLLECTION_PURPOSE, SEED_TEST_TTL_DAYS, isSeedPurpose, resolveSeedPurpose, seedPurposeFields, stampSeedPurpose, asSeedPurpose, } from "./seed-purpose.js";
|
|
105
|
+
// E4/D12 — generated from `docs/research/part-composition-2026-10/`.
|
|
106
|
+
export { partGlossarySeedData } from "./part-glossary-seed-data.js";
|
|
107
|
+
export { modelCompositionSeedData } from "./model-composition-seed-data.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `scripts/generate-parts-seed.mjs` — do not hand-edit.
|
|
3
|
+
* Source: `docs/research/part-composition-2026-10/` (E4a/E4b), itself
|
|
4
|
+
* extracted from the LOCAL corpus with no network crawl.
|
|
5
|
+
*
|
|
6
|
+
* Model composition — which parts each bey is made of — 259 rows.
|
|
7
|
+
*
|
|
8
|
+
* 🛑 Facts only. Fandom is CC-BY-SA, so names, codes, weights and slot
|
|
9
|
+
* structure are taken and the PROSE is not — our own descriptions come from
|
|
10
|
+
* the category templates. Regenerate rather than edit: a hand-fix here is lost
|
|
11
|
+
* on the next run and, worse, makes the committed output disagree with the
|
|
12
|
+
* script that is supposed to produce it.
|
|
13
|
+
*/
|
|
14
|
+
import type { ModelCompositionDocument } from "../features/parts/schemas/firestore";
|
|
15
|
+
export declare const modelCompositionSeedData: Partial<ModelCompositionDocument>[];
|