@mohasinac/appkit 4.57.0 → 4.58.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/beys/data.d.ts +44 -0
- package/dist/_internal/server/features/beys/data.js +107 -0
- package/dist/_internal/server/features/beys/og.d.ts +42 -0
- package/dist/_internal/server/features/beys/og.js +89 -0
- package/dist/_internal/server/features/parts/data.js +3 -2
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +86 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +12 -0
- package/dist/constants/api-endpoints.js +10 -0
- package/dist/constants/field-names.d.ts +46 -0
- package/dist/constants/field-names.js +46 -0
- package/dist/features/admin/components/AdminBeysView.d.ts +27 -0
- package/dist/features/admin/components/AdminBeysView.js +178 -0
- package/dist/features/admin/components/AdminPartsView.d.ts +27 -0
- package/dist/features/admin/components/AdminPartsView.js +160 -0
- package/dist/features/admin/components/OwnerQuickActions.d.ts +19 -2
- package/dist/features/admin/components/OwnerQuickActions.js +31 -3
- package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +31 -0
- package/dist/features/admin/components/RecordOwnerQuickActions.js +128 -0
- package/dist/features/admin/components/ReferenceRecordEditor.d.ts +45 -0
- package/dist/features/admin/components/ReferenceRecordEditor.js +153 -0
- package/dist/features/admin/schemas/firestore.d.ts +1 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +15 -2
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +12 -1
- package/dist/features/beys/components/BeyDetailPageView.d.ts +50 -0
- package/dist/features/beys/components/BeyDetailPageView.js +98 -0
- package/dist/features/beys/schemas/firestore.d.ts +417 -0
- package/dist/features/beys/schemas/firestore.js +65 -0
- package/dist/features/blog/components/BlogFeaturedCard.js +15 -2
- package/dist/features/blog/components/BlogPostView.js +2 -2
- package/dist/features/categories/components/BrandDetailPageView.js +4 -2
- package/dist/features/categories/components/BundleDetailView.js +2 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +36 -3
- package/dist/features/categories/components/CategoryGrid.js +24 -2
- package/dist/features/categories/components/FranchiseDetailPageView.js +2 -3
- package/dist/features/events/components/EventCard.js +15 -2
- package/dist/features/grouped/components/GroupedListingDetailView.js +2 -3
- package/dist/features/homepage/components/CustomCardsSection.js +67 -50
- package/dist/features/homepage/components/ShopByCategorySection.js +23 -10
- package/dist/features/homepage/schemas/index.d.ts +10 -10
- package/dist/features/parts/components/PartDetailPageView.js +5 -3
- package/dist/features/parts/components/PartsSearchBox.js +79 -4
- package/dist/features/parts/schemas/admin-edit.d.ts +81 -0
- package/dist/features/parts/schemas/admin-edit.js +89 -0
- package/dist/features/parts/schemas/firestore.d.ts +2 -1
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +15 -2
- package/dist/features/products/components/MarketplaceBundleCard.js +15 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +15 -2
- package/dist/features/products/components/ProductGrid.js +18 -2
- package/dist/features/products/schemas/firestore.d.ts +18 -0
- package/dist/features/seller/components/CategoryInlineSelect.d.ts +10 -1
- package/dist/features/seller/components/CategoryInlineSelect.js +53 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +39 -0
- package/dist/features/seller/components/SellerProductShell.js +31 -3
- package/dist/index.d.ts +8 -0
- package/dist/index.js +13 -0
- package/dist/next/routing/route-map.d.ts +13 -1
- package/dist/next/routing/route-map.js +37 -0
- package/dist/schemas/registry.d.ts +6 -6
- package/dist/seed/_helpers/category-parts-glossary-map.js +38 -38
- package/dist/seed/_helpers/category-parts.js +39 -39
- package/dist/seed/bey-products-seed-data.d.ts +18 -0
- package/dist/seed/bey-products-seed-data.js +18011 -0
- package/dist/seed/beys/chunk0.d.ts +17 -0
- package/dist/seed/beys/chunk0.js +36733 -0
- package/dist/seed/beys/chunk1.d.ts +17 -0
- package/dist/seed/beys/chunk1.js +35728 -0
- package/dist/seed/beys/chunk2.d.ts +17 -0
- package/dist/seed/beys/chunk2.js +35857 -0
- package/dist/seed/beys/chunk3.d.ts +17 -0
- package/dist/seed/beys/chunk3.js +34476 -0
- package/dist/seed/beys-seed-data.d.ts +18 -0
- package/dist/seed/beys-seed-data.js +11 -0
- package/dist/seed/homepage-sections-seed-data.js +16 -0
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.js +3 -0
- package/dist/seed/media-assets-seed-data.js +4 -4
- package/dist/seed/products-catalogue/chunk0.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk0.js +7262 -0
- package/dist/seed/products-catalogue/chunk1.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk1.js +7290 -0
- package/dist/seed/products-catalogue/chunk10.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk10.js +7290 -0
- package/dist/seed/products-catalogue/chunk11.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk11.js +7005 -0
- package/dist/seed/products-catalogue/chunk12.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk12.js +7002 -0
- package/dist/seed/products-catalogue/chunk13.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk13.js +7002 -0
- package/dist/seed/products-catalogue/chunk14.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk14.js +7002 -0
- package/dist/seed/products-catalogue/chunk15.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk15.js +7002 -0
- package/dist/seed/products-catalogue/chunk16.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk16.js +7002 -0
- package/dist/seed/products-catalogue/chunk17.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk17.js +7002 -0
- package/dist/seed/products-catalogue/chunk18.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk18.js +4398 -0
- package/dist/seed/products-catalogue/chunk2.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk2.js +7268 -0
- package/dist/seed/products-catalogue/chunk3.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk3.js +7264 -0
- package/dist/seed/products-catalogue/chunk4.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk4.js +7268 -0
- package/dist/seed/products-catalogue/chunk5.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk5.js +7258 -0
- package/dist/seed/products-catalogue/chunk6.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk6.js +7268 -0
- package/dist/seed/products-catalogue/chunk7.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk7.js +7311 -0
- package/dist/seed/products-catalogue/chunk8.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk8.js +7304 -0
- package/dist/seed/products-catalogue/chunk9.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk9.js +7309 -0
- package/dist/seed/products-catalogue-seed-data.d.ts +2 -22
- package/dist/seed/products-catalogue-seed-data.js +58 -86382
- package/dist/seed/seed-purpose.js +9 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +8 -0
- package/dist/styles.css +468 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PosterCard.d.ts +99 -0
- package/dist/ui/components/PosterCard.js +109 -0
- package/dist/ui/components/PosterCard.style.css +466 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +2 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +108 -1
package/dist/.build-stamp.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"stampVersion": 1,
|
|
3
|
-
"version": "4.
|
|
4
|
-
"head": "
|
|
3
|
+
"version": "4.58.0",
|
|
4
|
+
"head": "978d9ef892ec5d3ef08a87c7f040877faf3c5daa",
|
|
5
5
|
"branch": "main",
|
|
6
|
-
"dirty":
|
|
7
|
-
"builtAt": "2026-10-
|
|
6
|
+
"dirty": false,
|
|
7
|
+
"builtAt": "2026-10-20T20:29:00.050Z"
|
|
8
8
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type BeyDocument, type BeyProductDocument } from "../../../../features/beys/schemas/firestore";
|
|
2
|
+
/**
|
|
3
|
+
* One bey, by slug.
|
|
4
|
+
*
|
|
5
|
+
* 🛑 A DIRECT DOC READ — `id === slug`, so this needs no query, no index and
|
|
6
|
+
* no scan. That is the whole reason the id is the slug.
|
|
7
|
+
*/
|
|
8
|
+
export declare const getBeyBySlug: (slug: string) => Promise<BeyDocument | null>;
|
|
9
|
+
/**
|
|
10
|
+
* The bey(s) paired with a category leaf — the inverse of `BeyDocument.categoryId`.
|
|
11
|
+
*
|
|
12
|
+
* 🛑 ONE INDEXED EQUALITY, no composite index, which is the same shape
|
|
13
|
+
* `getCompositionsForCategory` uses for the parts side of this exact job. A
|
|
14
|
+
* single-field `==` is served by Firestore's automatic index, so this adds no
|
|
15
|
+
* index to declare and no deploy to wait on.
|
|
16
|
+
*
|
|
17
|
+
* 🛑 NOT `.limit(1)`, and that is measured rather than cautious. 1,880 beys
|
|
18
|
+
* carry a `categoryId` across 1,858 distinct leaves — so **22 leaves pair with
|
|
19
|
+
* TWO beys**, which are the vendor pairs (a Takara-Tomy release and its Hasbro
|
|
20
|
+
* equivalent merged onto one taxonomy leaf). `limit(1)` would render one of
|
|
21
|
+
* them and silently drop the other on 22 pages, with nothing to indicate a
|
|
22
|
+
* second existed. The cap is small and the extra rows are the point.
|
|
23
|
+
*
|
|
24
|
+
* Fails OPEN to an empty list for the same reason as below: a category page
|
|
25
|
+
* missing its reference link is a worse page, a category page that throws is no
|
|
26
|
+
* page at all.
|
|
27
|
+
*/
|
|
28
|
+
export declare const getBeysForCategory: (categoryId: string) => Promise<BeyDocument[]>;
|
|
29
|
+
/**
|
|
30
|
+
* The products that ship a bey.
|
|
31
|
+
*
|
|
32
|
+
* 🛑 A QUERY, unlike `getModelsUsingPart`'s `getAll`, and the difference is a
|
|
33
|
+
* real asymmetry in the data rather than an inconsistency: a part row carries
|
|
34
|
+
* `usedBy` so its models are ids already in hand, whereas the product→bey edge
|
|
35
|
+
* lives on the PRODUCT (`assortment[].beyId`). A bey has no stored list of the
|
|
36
|
+
* products containing it.
|
|
37
|
+
*
|
|
38
|
+
* So this is one `array-contains` over a denormalised id array, bounded at 12.
|
|
39
|
+
* 🛑 It needs `assortmentBeyIds` on the product — a flat array of the ids
|
|
40
|
+
* inside `assortment[]`, because Firestore cannot `array-contains` on a field
|
|
41
|
+
* nested inside objects in an array. Until the seed carries that field this
|
|
42
|
+
* returns [] and says so, rather than running a scan.
|
|
43
|
+
*/
|
|
44
|
+
export declare const getProductsContainingBey: (beyId: string) => Promise<BeyProductDocument[]>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads for the public bey surfaces.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 Wrapped in `React.cache` because a page and its `generateMetadata` share
|
|
5
|
+
* the call — and the NARROWEST shared call is what gets cached, not whatever
|
|
6
|
+
* is convenient to export. `React.cache` memoises per argument list, so two
|
|
7
|
+
* call sites passing different arguments get two entries and dedupe nothing
|
|
8
|
+
* (the `getStoreBySlug(slug)` vs `getStoreBySlug(slug, viewer)` trap).
|
|
9
|
+
*/
|
|
10
|
+
import { cache } from "react";
|
|
11
|
+
import { BEYS_COLLECTION, BEY_PRODUCTS_COLLECTION, } from "../../../../features/beys/schemas/firestore.js";
|
|
12
|
+
import { getAdminDb } from "../../../../providers/db-firebase/index.js";
|
|
13
|
+
import { BEY_FIELDS } from "../../../../constants/field-names.js";
|
|
14
|
+
import { serverLogger } from "../../../../monitoring/server-logger.js";
|
|
15
|
+
import { normalizeError } from "../../../../errors/normalize.js";
|
|
16
|
+
/**
|
|
17
|
+
* One bey, by slug.
|
|
18
|
+
*
|
|
19
|
+
* 🛑 A DIRECT DOC READ — `id === slug`, so this needs no query, no index and
|
|
20
|
+
* no scan. That is the whole reason the id is the slug.
|
|
21
|
+
*/
|
|
22
|
+
export const getBeyBySlug = cache(async (slug) => {
|
|
23
|
+
try {
|
|
24
|
+
const snap = await getAdminDb().collection(BEYS_COLLECTION).doc(slug).get();
|
|
25
|
+
return snap.exists ? { id: snap.id, ...snap.data() } : null;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
const e = normalizeError(err);
|
|
29
|
+
serverLogger.warn("getBeyBySlug failed", { slug, error: e.message });
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* The bey(s) paired with a category leaf — the inverse of `BeyDocument.categoryId`.
|
|
35
|
+
*
|
|
36
|
+
* 🛑 ONE INDEXED EQUALITY, no composite index, which is the same shape
|
|
37
|
+
* `getCompositionsForCategory` uses for the parts side of this exact job. A
|
|
38
|
+
* single-field `==` is served by Firestore's automatic index, so this adds no
|
|
39
|
+
* index to declare and no deploy to wait on.
|
|
40
|
+
*
|
|
41
|
+
* 🛑 NOT `.limit(1)`, and that is measured rather than cautious. 1,880 beys
|
|
42
|
+
* carry a `categoryId` across 1,858 distinct leaves — so **22 leaves pair with
|
|
43
|
+
* TWO beys**, which are the vendor pairs (a Takara-Tomy release and its Hasbro
|
|
44
|
+
* equivalent merged onto one taxonomy leaf). `limit(1)` would render one of
|
|
45
|
+
* them and silently drop the other on 22 pages, with nothing to indicate a
|
|
46
|
+
* second existed. The cap is small and the extra rows are the point.
|
|
47
|
+
*
|
|
48
|
+
* Fails OPEN to an empty list for the same reason as below: a category page
|
|
49
|
+
* missing its reference link is a worse page, a category page that throws is no
|
|
50
|
+
* page at all.
|
|
51
|
+
*/
|
|
52
|
+
export const getBeysForCategory = cache(async (categoryId) => {
|
|
53
|
+
try {
|
|
54
|
+
const snap = await getAdminDb()
|
|
55
|
+
.collection(BEYS_COLLECTION)
|
|
56
|
+
.where(BEY_FIELDS.CATEGORY_ID, "==", categoryId)
|
|
57
|
+
.limit(4)
|
|
58
|
+
.get();
|
|
59
|
+
return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const e = normalizeError(err);
|
|
63
|
+
serverLogger.warn("getBeysForCategory failed", { categoryId, error: e.message });
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* The products that ship a bey.
|
|
69
|
+
*
|
|
70
|
+
* 🛑 A QUERY, unlike `getModelsUsingPart`'s `getAll`, and the difference is a
|
|
71
|
+
* real asymmetry in the data rather than an inconsistency: a part row carries
|
|
72
|
+
* `usedBy` so its models are ids already in hand, whereas the product→bey edge
|
|
73
|
+
* lives on the PRODUCT (`assortment[].beyId`). A bey has no stored list of the
|
|
74
|
+
* products containing it.
|
|
75
|
+
*
|
|
76
|
+
* So this is one `array-contains` over a denormalised id array, bounded at 12.
|
|
77
|
+
* 🛑 It needs `assortmentBeyIds` on the product — a flat array of the ids
|
|
78
|
+
* inside `assortment[]`, because Firestore cannot `array-contains` on a field
|
|
79
|
+
* nested inside objects in an array. Until the seed carries that field this
|
|
80
|
+
* returns [] and says so, rather than running a scan.
|
|
81
|
+
*/
|
|
82
|
+
export const getProductsContainingBey = cache(async (beyId) => {
|
|
83
|
+
try {
|
|
84
|
+
const snap = await getAdminDb()
|
|
85
|
+
.collection(BEY_PRODUCTS_COLLECTION)
|
|
86
|
+
.where("assortmentBeyIds", "array-contains", beyId)
|
|
87
|
+
.limit(12)
|
|
88
|
+
.get();
|
|
89
|
+
return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const e = normalizeError(err);
|
|
93
|
+
/*
|
|
94
|
+
* Fails OPEN to an empty list. A missing composite index throws
|
|
95
|
+
* `FAILED_PRECONDITION`, and the right outcome is a bey page without its
|
|
96
|
+
* "found in" section rather than a broken page — the caller wraps this in
|
|
97
|
+
* `safeRead` as well, so the degradation is recorded rather than silent.
|
|
98
|
+
*
|
|
99
|
+
* 🛑 `normalizeError` is the FIRST statement on purpose. It was below
|
|
100
|
+
* this comment, which put it outside audit-catch-normalize's proximity
|
|
101
|
+
* window — so the audit correctly reported it as missing even though the
|
|
102
|
+
* call was right there. Comment after the call, never before it.
|
|
103
|
+
*/
|
|
104
|
+
serverLogger.warn("getProductsContainingBey failed", { beyId, error: e.message });
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OG card for `/beys/{slug}`.
|
|
3
|
+
*
|
|
4
|
+
* ── 🛑 NO IMAGE FETCHES, FOR THE REASON THE PARTS CARDS DO NOT EITHER ─────
|
|
5
|
+
*
|
|
6
|
+
* Beys DO have photographs — 1,903 of 1,988 — so this is a deliberate refusal
|
|
7
|
+
* rather than a gap. The project was suspended for **Fast Origin Transfer at
|
|
8
|
+
* 5.25× its allowance** (52.52 GB against 10 GB), traced to render-time image
|
|
9
|
+
* proxying (Root Cause #104). 1,988 bey routes with one remote fetch each, on
|
|
10
|
+
* a crawler sweep, is precisely the amplification that was just removed. Type
|
|
11
|
+
* on a gradient costs one lambda and nothing else.
|
|
12
|
+
*
|
|
13
|
+
* ── 🛑 NO MONEY ON THE CANVAS ─────────────────────────────────────────────
|
|
14
|
+
*
|
|
15
|
+
* All eight money-bearing OG renderers were stripped, and the rule is stated
|
|
16
|
+
* in CLAUDE.md: an OG image is a public image URL with the amount burned into
|
|
17
|
+
* the pixels, so no sign-in gate reaches it and no paywall markup describes
|
|
18
|
+
* it. 1,309 beys carry a price in the research data and none of it appears
|
|
19
|
+
* here.
|
|
20
|
+
*
|
|
21
|
+
* `audit-guest-price-leak`'s R1 blocks a `₹` in any `og.tsx`, so this is
|
|
22
|
+
* mechanically enforced rather than left to discipline.
|
|
23
|
+
*/
|
|
24
|
+
import type { ReactElement } from "react";
|
|
25
|
+
import type { BeyDocument } from "../../../../features/beys/schemas/firestore";
|
|
26
|
+
/**
|
|
27
|
+
* One bey's card.
|
|
28
|
+
*
|
|
29
|
+
* 🛑 EVERY CLAUSE IS CONDITIONAL, because the coverage genuinely varies: 93%
|
|
30
|
+
* of beys have a mass but only 248 state it directly, and `system` is on 94%
|
|
31
|
+
* while `type` and `spinDirection` are lower. A card reading "0 g" or an empty
|
|
32
|
+
* separator would be a claim the data does not support — the same reason the
|
|
33
|
+
* parts card builds its detail line by filtering.
|
|
34
|
+
*
|
|
35
|
+
* 🛑 The mass is marked `≈` when it was SUMMED from the parts rather than
|
|
36
|
+
* stated, which is the same honesty the detail page applies. A derived figure
|
|
37
|
+
* printed as an exact one is the single easiest way for this dataset to start
|
|
38
|
+
* lying.
|
|
39
|
+
*/
|
|
40
|
+
export declare function renderBeyOg(bey: BeyDocument | null | undefined, opts: {
|
|
41
|
+
siteName: string;
|
|
42
|
+
}): ReactElement;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const BG = "#0f172a";
|
|
3
|
+
const GRADIENT = "linear-gradient(135deg, #0f172a 0%, #1e293b 100%)";
|
|
4
|
+
const MUTED = "#94a3b8";
|
|
5
|
+
const ACCENT = "#5eead4";
|
|
6
|
+
/*
|
|
7
|
+
* 🛑 A COPY of the parts shell, not an import, and that is a considered
|
|
8
|
+
* choice rather than laziness.
|
|
9
|
+
*
|
|
10
|
+
* The Duplication Decision Framework's Rule of Three says extract on the
|
|
11
|
+
* THIRD copy, not the second — "two copies is observation; three is a
|
|
12
|
+
* pattern". There are now exactly two OG shells of this shape. Lifting it to
|
|
13
|
+
* a shared module today would also put a third consumer between every OG
|
|
14
|
+
* renderer and its layout for no behavioural gain.
|
|
15
|
+
*
|
|
16
|
+
* When a third appears, extract then. The constants above are duplicated for
|
|
17
|
+
* the same reason and in the same breath.
|
|
18
|
+
*/
|
|
19
|
+
function shell(eyebrow, title, detail, siteName) {
|
|
20
|
+
return (_jsxs("div", { style: {
|
|
21
|
+
display: "flex",
|
|
22
|
+
width: "100%",
|
|
23
|
+
height: "100%",
|
|
24
|
+
background: BG,
|
|
25
|
+
fontFamily: "sans-serif",
|
|
26
|
+
position: "relative",
|
|
27
|
+
overflow: "hidden",
|
|
28
|
+
alignItems: "center",
|
|
29
|
+
justifyContent: "center",
|
|
30
|
+
}, children: [_jsx("div", { style: { position: "absolute", inset: 0, background: GRADIENT } }), _jsxs("div", { style: {
|
|
31
|
+
position: "relative",
|
|
32
|
+
display: "flex",
|
|
33
|
+
flexDirection: "column",
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
gap: 24,
|
|
36
|
+
padding: "60px",
|
|
37
|
+
textAlign: "center",
|
|
38
|
+
}, children: [_jsx("div", { style: {
|
|
39
|
+
fontSize: 20,
|
|
40
|
+
color: ACCENT,
|
|
41
|
+
fontWeight: 600,
|
|
42
|
+
letterSpacing: 3,
|
|
43
|
+
textTransform: "uppercase",
|
|
44
|
+
}, children: eyebrow }), _jsx("div", { style: {
|
|
45
|
+
fontSize: title.length > 34 ? 56 : 76,
|
|
46
|
+
color: "#ffffff",
|
|
47
|
+
fontWeight: 700,
|
|
48
|
+
lineHeight: 1.1,
|
|
49
|
+
}, children: title }), detail && _jsx("div", { style: { fontSize: 28, color: MUTED }, children: detail }), _jsx("div", { style: { fontSize: 20, color: MUTED, marginTop: 8 }, children: siteName })] })] }));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One bey's card.
|
|
53
|
+
*
|
|
54
|
+
* 🛑 EVERY CLAUSE IS CONDITIONAL, because the coverage genuinely varies: 93%
|
|
55
|
+
* of beys have a mass but only 248 state it directly, and `system` is on 94%
|
|
56
|
+
* while `type` and `spinDirection` are lower. A card reading "0 g" or an empty
|
|
57
|
+
* separator would be a claim the data does not support — the same reason the
|
|
58
|
+
* parts card builds its detail line by filtering.
|
|
59
|
+
*
|
|
60
|
+
* 🛑 The mass is marked `≈` when it was SUMMED from the parts rather than
|
|
61
|
+
* stated, which is the same honesty the detail page applies. A derived figure
|
|
62
|
+
* printed as an exact one is the single easiest way for this dataset to start
|
|
63
|
+
* lying.
|
|
64
|
+
*/
|
|
65
|
+
export function renderBeyOg(bey, opts) {
|
|
66
|
+
if (!bey)
|
|
67
|
+
return shell("Beyblades", "Beyblade database", "", opts.siteName);
|
|
68
|
+
const statedMass = typeof bey.weightG === "number" ? bey.weightG : null;
|
|
69
|
+
const summedMass = typeof bey.weightGFromParts === "number" ? bey.weightGFromParts : null;
|
|
70
|
+
const mass = statedMass !== null
|
|
71
|
+
? `${statedMass} g`
|
|
72
|
+
: summedMass !== null
|
|
73
|
+
? `≈ ${Math.round(summedMass * 10) / 10} g`
|
|
74
|
+
: null;
|
|
75
|
+
const bits = [
|
|
76
|
+
bey.type ? `${bey.type} type` : null,
|
|
77
|
+
bey.spinDirection ?? null,
|
|
78
|
+
mass,
|
|
79
|
+
bey.composition?.length ? `${bey.composition.length} parts` : null,
|
|
80
|
+
].filter((b) => b !== null);
|
|
81
|
+
/*
|
|
82
|
+
* The eyebrow is the SYSTEM (Hybrid Wheel System, Burst Ultimate…) because
|
|
83
|
+
* that is the one line a collector uses to place a bey in time, and it is
|
|
84
|
+
* present on 94% of them. `series` is the fallback and "Beyblade" the floor
|
|
85
|
+
* — never an empty eyebrow, which reads as a broken template.
|
|
86
|
+
*/
|
|
87
|
+
const eyebrow = bey.system ?? bey.series ?? "Beyblade";
|
|
88
|
+
return shell(eyebrow, bey.name, bits.join(" · "), opts.siteName);
|
|
89
|
+
}
|
|
@@ -11,6 +11,7 @@ import { PART_GLOSSARY_COLLECTION, MODEL_COMPOSITION_COLLECTION, partGlossaryId,
|
|
|
11
11
|
import { getAdminDb } from "../../../../providers/db-firebase/index.js";
|
|
12
12
|
import { serverLogger } from "../../../../monitoring/server-logger.js";
|
|
13
13
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
14
|
+
import { MODEL_COMPOSITION_FIELDS, PART_GLOSSARY_FIELDS, } from "../../../../constants/field-names.js";
|
|
14
15
|
/** The bucket cap. 87 is the largest real classification, so this clears it. */
|
|
15
16
|
export const PART_SLOT_PAGE_SIZE = 120;
|
|
16
17
|
/** One part, by `(slot, code|name)` — the only key this collection has. */
|
|
@@ -125,7 +126,7 @@ export const getCompositionsForCategory = cache(async (categoryId) => {
|
|
|
125
126
|
try {
|
|
126
127
|
const snap = await getAdminDb()
|
|
127
128
|
.collection(MODEL_COMPOSITION_COLLECTION)
|
|
128
|
-
.where(
|
|
129
|
+
.where(MODEL_COMPOSITION_FIELDS.CATEGORY_ID, "==", categoryId)
|
|
129
130
|
.limit(8)
|
|
130
131
|
.get();
|
|
131
132
|
return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
@@ -154,7 +155,7 @@ export const listPartsBySlot = cache(async (slot) => {
|
|
|
154
155
|
try {
|
|
155
156
|
const snap = await getAdminDb()
|
|
156
157
|
.collection(PART_GLOSSARY_COLLECTION)
|
|
157
|
-
.where(
|
|
158
|
+
.where(PART_GLOSSARY_FIELDS.SLOT, "==", slot)
|
|
158
159
|
.limit(PART_SLOT_PAGE_SIZE)
|
|
159
160
|
.get();
|
|
160
161
|
const parts = snap.docs
|
|
@@ -29,7 +29,7 @@ import type { IconKey } from "../../../ui/icons/icon-registry";
|
|
|
29
29
|
/** What KIND of action — drives default UI affordance (link vs button vs danger). */
|
|
30
30
|
export type ActionKind = "primary" | "secondary" | "danger" | "ghost" | "link" | "chip";
|
|
31
31
|
/** Resource bucket — keyed top-level in the ACTIONS tree. */
|
|
32
|
-
export type ActionResource = "PRODUCT" | "AUCTION" | "PRE_ORDER" | "PRIZE_DRAW" | "DIGITAL_CODE" | "BUNDLE" | "GROUP" | "CATEGORY" | "BRAND" | "SUBLISTING" | "STORE" | "BLOG" | "EVENT" | "REVIEW" | "SHIPMENT" | "CATALOGUE" | "USER" | "SELLER" | "ADMIN" | "CART" | "CHECKOUT" | "NAV" | "MEDIA" | "SUPPORT" | "LOTTERY" | "TESTER";
|
|
32
|
+
export type ActionResource = "PRODUCT" | "AUCTION" | "PRE_ORDER" | "PRIZE_DRAW" | "DIGITAL_CODE" | "BUNDLE" | "GROUP" | "CATEGORY" | "BRAND" | "PART" | "BEY" | "SUBLISTING" | "STORE" | "BLOG" | "EVENT" | "REVIEW" | "SHIPMENT" | "CATALOGUE" | "USER" | "SELLER" | "ADMIN" | "CART" | "CHECKOUT" | "NAV" | "MEDIA" | "SUPPORT" | "LOTTERY" | "TESTER";
|
|
33
33
|
/** Confirmation modal config — when set, <Button action={...}> opens
|
|
34
34
|
* a confirm dialog before firing the actual handler. */
|
|
35
35
|
export interface ActionConfirmation {
|
|
@@ -432,6 +432,92 @@ export const ACTIONS = {
|
|
|
432
432
|
},
|
|
433
433
|
},
|
|
434
434
|
},
|
|
435
|
+
/*
|
|
436
|
+
* ── 🛑 THE TWO GENERATED REFERENCE DATABASES ─────────────────────────────
|
|
437
|
+
*
|
|
438
|
+
* `partGlossary` (2,488) and `beys` (1,988) are built by committed
|
|
439
|
+
* generators from a crawled corpus. That shapes every leaf below:
|
|
440
|
+
*
|
|
441
|
+
* · NO `create`. A row authored by hand has no source, vanishes on the
|
|
442
|
+
* next regeneration, and makes "this dataset rebuilds from the repo"
|
|
443
|
+
* false. Adding a part is a change to the generator, not a button.
|
|
444
|
+
* · `edit` is CORRECT-A-WRONG-EXTRACTION, and says so, because that is
|
|
445
|
+
* what it is for — the corpus was measurably wrong five times in one
|
|
446
|
+
* session on mode data alone.
|
|
447
|
+
* · `delete`'s confirmation does NOT say "cannot be undone", because that
|
|
448
|
+
* would be a lie: the row is in a committed seed file and the next
|
|
449
|
+
* reseed restores it. It says what actually happens.
|
|
450
|
+
*/
|
|
451
|
+
PART: {
|
|
452
|
+
view: {
|
|
453
|
+
iconKey: "view",
|
|
454
|
+
id: "part.view",
|
|
455
|
+
label: "View",
|
|
456
|
+
ariaLabel: "View part",
|
|
457
|
+
description: "Open the part reference page.",
|
|
458
|
+
kind: "secondary",
|
|
459
|
+
},
|
|
460
|
+
edit: {
|
|
461
|
+
iconKey: "edit",
|
|
462
|
+
id: "part.edit",
|
|
463
|
+
label: "Edit",
|
|
464
|
+
ariaLabel: "Edit part",
|
|
465
|
+
description: "Correct this part's recorded details. The correction is kept when the database is regenerated.",
|
|
466
|
+
kind: "secondary",
|
|
467
|
+
permissions: ["admin"],
|
|
468
|
+
},
|
|
469
|
+
delete: {
|
|
470
|
+
iconKey: "delete",
|
|
471
|
+
id: "part.delete",
|
|
472
|
+
label: "Delete",
|
|
473
|
+
ariaLabel: "Delete part",
|
|
474
|
+
description: "Remove this part from the live database.",
|
|
475
|
+
kind: "danger",
|
|
476
|
+
permissions: ["admin"],
|
|
477
|
+
confirmation: {
|
|
478
|
+
title: "Remove this part from the live database?",
|
|
479
|
+
body: "Beyblades built from it keep their parts list and will show its name without a link. " +
|
|
480
|
+
"This part is also in the committed seed data, so regenerating the database will bring it " +
|
|
481
|
+
"back — to remove it for good, take it out of the generator.",
|
|
482
|
+
confirmLabel: "Remove",
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
BEY: {
|
|
487
|
+
view: {
|
|
488
|
+
iconKey: "view",
|
|
489
|
+
id: "bey.view",
|
|
490
|
+
label: "View",
|
|
491
|
+
ariaLabel: "View Beyblade",
|
|
492
|
+
description: "Open the Beyblade reference page.",
|
|
493
|
+
kind: "secondary",
|
|
494
|
+
},
|
|
495
|
+
edit: {
|
|
496
|
+
iconKey: "edit",
|
|
497
|
+
id: "bey.edit",
|
|
498
|
+
label: "Edit",
|
|
499
|
+
ariaLabel: "Edit Beyblade",
|
|
500
|
+
description: "Correct this Beyblade's recorded details. The correction is kept when the database is regenerated.",
|
|
501
|
+
kind: "secondary",
|
|
502
|
+
permissions: ["admin"],
|
|
503
|
+
},
|
|
504
|
+
delete: {
|
|
505
|
+
iconKey: "delete",
|
|
506
|
+
id: "bey.delete",
|
|
507
|
+
label: "Delete",
|
|
508
|
+
ariaLabel: "Delete Beyblade",
|
|
509
|
+
description: "Remove this Beyblade from the live database.",
|
|
510
|
+
kind: "danger",
|
|
511
|
+
permissions: ["admin"],
|
|
512
|
+
confirmation: {
|
|
513
|
+
title: "Remove this Beyblade from the live database?",
|
|
514
|
+
body: "Its category page and any listings under it are not touched, and boxed sets that include " +
|
|
515
|
+
"it keep their contents list. It is also in the committed seed data, so regenerating the " +
|
|
516
|
+
"database will bring it back — to remove it for good, take it out of the generator.",
|
|
517
|
+
confirmLabel: "Remove",
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
},
|
|
435
521
|
CATEGORY: {
|
|
436
522
|
view: {
|
|
437
523
|
iconKey: "view",
|
package/dist/client.d.ts
CHANGED
|
@@ -437,6 +437,8 @@ export type { ListingTypeCapability } from "./_internal/shared/listing-types/cap
|
|
|
437
437
|
export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_internal/shared/listing-types/_registry";
|
|
438
438
|
export { useStoreListingEntitlements } from "./features/seller/hooks/useStoreListingEntitlements";
|
|
439
439
|
export { OwnerQuickActions } from "./features/admin/components/OwnerQuickActions";
|
|
440
|
+
export { RecordOwnerQuickActions } from "./features/admin/components/RecordOwnerQuickActions";
|
|
441
|
+
export type { RecordOwnerQuickActionsProps, RecordQuickActionResource } from "./features/admin/components/RecordOwnerQuickActions";
|
|
440
442
|
export { YourPurchaseStrip } from "./features/orders/components/YourPurchaseStrip";
|
|
441
443
|
export { ReviewFormDrawer, reviewFormSchema } from "./features/reviews/components/ReviewFormDrawer";
|
|
442
444
|
export type { ReviewFormDrawerProps, ReviewFormValues } from "./features/reviews/components/ReviewFormDrawer";
|
package/dist/client.js
CHANGED
|
@@ -460,6 +460,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
|
|
|
460
460
|
export { useStoreListingEntitlements } from "./features/seller/hooks/useStoreListingEntitlements.js";
|
|
461
461
|
// Pencil / clone / bin for whoever owns the record, on a PUBLIC surface.
|
|
462
462
|
export { OwnerQuickActions } from "./features/admin/components/OwnerQuickActions.js";
|
|
463
|
+
export { RecordOwnerQuickActions } from "./features/admin/components/RecordOwnerQuickActions.js";
|
|
463
464
|
// "You ordered this - track it" / "your review - edit it", on a product page.
|
|
464
465
|
export { YourPurchaseStrip } from "./features/orders/components/YourPurchaseStrip.js";
|
|
465
466
|
// The first review form this app has had - see the component's header.
|
|
@@ -126,6 +126,10 @@ export declare const ADMIN_ENDPOINTS: {
|
|
|
126
126
|
readonly BUNDLE_REBUILD: (id: string) => string;
|
|
127
127
|
readonly CATEGORIES: "/api/admin/categories";
|
|
128
128
|
readonly CATEGORY_BY_ID: (id: string) => string;
|
|
129
|
+
readonly PARTS: "/api/admin/parts";
|
|
130
|
+
readonly PART_BY_ID: (id: string) => string;
|
|
131
|
+
readonly BEYS: "/api/admin/beys";
|
|
132
|
+
readonly BEY_BY_ID: (id: string) => string;
|
|
129
133
|
readonly BRANDS: "/api/admin/brands";
|
|
130
134
|
readonly BRAND_BY_ID: (id: string) => string;
|
|
131
135
|
readonly FAQS: "/api/admin/faqs";
|
|
@@ -690,6 +694,10 @@ export declare const API_ENDPOINTS: {
|
|
|
690
694
|
readonly BUNDLE_REBUILD: (id: string) => string;
|
|
691
695
|
readonly CATEGORIES: "/api/admin/categories";
|
|
692
696
|
readonly CATEGORY_BY_ID: (id: string) => string;
|
|
697
|
+
readonly PARTS: "/api/admin/parts";
|
|
698
|
+
readonly PART_BY_ID: (id: string) => string;
|
|
699
|
+
readonly BEYS: "/api/admin/beys";
|
|
700
|
+
readonly BEY_BY_ID: (id: string) => string;
|
|
693
701
|
readonly BRANDS: "/api/admin/brands";
|
|
694
702
|
readonly BRAND_BY_ID: (id: string) => string;
|
|
695
703
|
readonly FAQS: "/api/admin/faqs";
|
|
@@ -1219,6 +1227,10 @@ export declare const API_ROUTES: {
|
|
|
1219
1227
|
readonly BUNDLE_REBUILD: (id: string) => string;
|
|
1220
1228
|
readonly CATEGORIES: "/api/admin/categories";
|
|
1221
1229
|
readonly CATEGORY_BY_ID: (id: string) => string;
|
|
1230
|
+
readonly PARTS: "/api/admin/parts";
|
|
1231
|
+
readonly PART_BY_ID: (id: string) => string;
|
|
1232
|
+
readonly BEYS: "/api/admin/beys";
|
|
1233
|
+
readonly BEY_BY_ID: (id: string) => string;
|
|
1222
1234
|
readonly BRANDS: "/api/admin/brands";
|
|
1223
1235
|
readonly BRAND_BY_ID: (id: string) => string;
|
|
1224
1236
|
readonly FAQS: "/api/admin/faqs";
|
|
@@ -157,6 +157,16 @@ export const ADMIN_ENDPOINTS = {
|
|
|
157
157
|
BUNDLE_REBUILD: (id) => `/api/admin/bundles/${id}/rebuild`,
|
|
158
158
|
CATEGORIES: "/api/admin/categories",
|
|
159
159
|
CATEGORY_BY_ID: (id) => `/api/admin/categories/${id}`,
|
|
160
|
+
/*
|
|
161
|
+
* The generated reference databases. PATCH corrects a wrong extraction;
|
|
162
|
+
* DELETE removes a row that should not exist. No POST — see the note on
|
|
163
|
+
* `ROUTES.ADMIN.PARTS` for why creating one by hand is not a supported
|
|
164
|
+
* operation on generated data.
|
|
165
|
+
*/
|
|
166
|
+
PARTS: "/api/admin/parts",
|
|
167
|
+
PART_BY_ID: (id) => `/api/admin/parts/${id}`,
|
|
168
|
+
BEYS: "/api/admin/beys",
|
|
169
|
+
BEY_BY_ID: (id) => `/api/admin/beys/${id}`,
|
|
160
170
|
BRANDS: "/api/admin/brands",
|
|
161
171
|
BRAND_BY_ID: (id) => `/api/admin/brands/${id}`,
|
|
162
172
|
FAQS: "/api/admin/faqs",
|
|
@@ -509,6 +509,52 @@ export declare const STORE_FIELDS: {
|
|
|
509
509
|
readonly TOTAL_PRODUCTS: "stats.totalProducts";
|
|
510
510
|
};
|
|
511
511
|
};
|
|
512
|
+
/**
|
|
513
|
+
* The two GENERATED reference databases.
|
|
514
|
+
*
|
|
515
|
+
* 🛑 NOT reused from `CATEGORY_FIELDS`. `audit-route-nav-field-constants`
|
|
516
|
+
* suggested `CATEGORY_FIELDS.NAME` for a query against `partGlossary`, and the
|
|
517
|
+
* string would have been identical — which is exactly why that would be the
|
|
518
|
+
* wrong fix. A constant's job is to say WHICH collection's field is meant, so
|
|
519
|
+
* that renaming one collection's field cannot silently retarget another's
|
|
520
|
+
* query. Same bytes, different claim.
|
|
521
|
+
*/
|
|
522
|
+
/**
|
|
523
|
+
* `modelComposition` — which parts a model is built from.
|
|
524
|
+
*
|
|
525
|
+
* 🛑 ITS OWN `CATEGORY_ID`, not `BEY_FIELDS.CATEGORY_ID`. The audit suggested
|
|
526
|
+
* the bey constant for a query against THIS collection, and the string is
|
|
527
|
+
* identical — which is precisely the substitution the comment on
|
|
528
|
+
* `PART_GLOSSARY_FIELDS` warns about. A composition row's `categoryId` points
|
|
529
|
+
* at the model leaf it describes; a bey's points at its own paired leaf. Same
|
|
530
|
+
* bytes, two different facts, and only one of them moves if either is renamed.
|
|
531
|
+
*/
|
|
532
|
+
export declare const MODEL_COMPOSITION_FIELDS: {
|
|
533
|
+
readonly ID: "id";
|
|
534
|
+
readonly CATEGORY_ID: "categoryId";
|
|
535
|
+
readonly SLOT: "slot";
|
|
536
|
+
};
|
|
537
|
+
export declare const PART_GLOSSARY_FIELDS: {
|
|
538
|
+
readonly ID: "id";
|
|
539
|
+
readonly NAME: "name";
|
|
540
|
+
readonly SLOT: "slot";
|
|
541
|
+
readonly CLASSIFICATION: "classification";
|
|
542
|
+
readonly CODE: "code";
|
|
543
|
+
readonly WEIGHT_G: "weightG";
|
|
544
|
+
/** Set by `applyAdminEdit`; the seed loader queries on it. */
|
|
545
|
+
readonly ADMIN_EDITED: "adminEdited";
|
|
546
|
+
readonly ADMIN_EDITED_FIELDS: "adminEditedFields";
|
|
547
|
+
};
|
|
548
|
+
export declare const BEY_FIELDS: {
|
|
549
|
+
readonly ID: "id";
|
|
550
|
+
readonly SLUG: "slug";
|
|
551
|
+
readonly NAME: "name";
|
|
552
|
+
readonly TYPE: "type";
|
|
553
|
+
readonly SYSTEM: "system";
|
|
554
|
+
readonly CATEGORY_ID: "categoryId";
|
|
555
|
+
readonly ADMIN_EDITED: "adminEdited";
|
|
556
|
+
readonly ADMIN_EDITED_FIELDS: "adminEditedFields";
|
|
557
|
+
};
|
|
512
558
|
export declare const CATEGORY_FIELDS: {
|
|
513
559
|
readonly ID: "id";
|
|
514
560
|
readonly NAME: "name";
|
|
@@ -613,6 +613,52 @@ export const STORE_FIELDS = {
|
|
|
613
613
|
// ============================================================================
|
|
614
614
|
// CATEGORY FIELDS
|
|
615
615
|
// ============================================================================
|
|
616
|
+
/**
|
|
617
|
+
* The two GENERATED reference databases.
|
|
618
|
+
*
|
|
619
|
+
* 🛑 NOT reused from `CATEGORY_FIELDS`. `audit-route-nav-field-constants`
|
|
620
|
+
* suggested `CATEGORY_FIELDS.NAME` for a query against `partGlossary`, and the
|
|
621
|
+
* string would have been identical — which is exactly why that would be the
|
|
622
|
+
* wrong fix. A constant's job is to say WHICH collection's field is meant, so
|
|
623
|
+
* that renaming one collection's field cannot silently retarget another's
|
|
624
|
+
* query. Same bytes, different claim.
|
|
625
|
+
*/
|
|
626
|
+
/**
|
|
627
|
+
* `modelComposition` — which parts a model is built from.
|
|
628
|
+
*
|
|
629
|
+
* 🛑 ITS OWN `CATEGORY_ID`, not `BEY_FIELDS.CATEGORY_ID`. The audit suggested
|
|
630
|
+
* the bey constant for a query against THIS collection, and the string is
|
|
631
|
+
* identical — which is precisely the substitution the comment on
|
|
632
|
+
* `PART_GLOSSARY_FIELDS` warns about. A composition row's `categoryId` points
|
|
633
|
+
* at the model leaf it describes; a bey's points at its own paired leaf. Same
|
|
634
|
+
* bytes, two different facts, and only one of them moves if either is renamed.
|
|
635
|
+
*/
|
|
636
|
+
export const MODEL_COMPOSITION_FIELDS = {
|
|
637
|
+
ID: "id",
|
|
638
|
+
CATEGORY_ID: "categoryId",
|
|
639
|
+
SLOT: "slot",
|
|
640
|
+
};
|
|
641
|
+
export const PART_GLOSSARY_FIELDS = {
|
|
642
|
+
ID: "id",
|
|
643
|
+
NAME: "name",
|
|
644
|
+
SLOT: "slot",
|
|
645
|
+
CLASSIFICATION: "classification",
|
|
646
|
+
CODE: "code",
|
|
647
|
+
WEIGHT_G: "weightG",
|
|
648
|
+
/** Set by `applyAdminEdit`; the seed loader queries on it. */
|
|
649
|
+
ADMIN_EDITED: "adminEdited",
|
|
650
|
+
ADMIN_EDITED_FIELDS: "adminEditedFields",
|
|
651
|
+
};
|
|
652
|
+
export const BEY_FIELDS = {
|
|
653
|
+
ID: "id",
|
|
654
|
+
SLUG: "slug",
|
|
655
|
+
NAME: "name",
|
|
656
|
+
TYPE: "type",
|
|
657
|
+
SYSTEM: "system",
|
|
658
|
+
CATEGORY_ID: "categoryId",
|
|
659
|
+
ADMIN_EDITED: "adminEdited",
|
|
660
|
+
ADMIN_EDITED_FIELDS: "adminEditedFields",
|
|
661
|
+
};
|
|
616
662
|
export const CATEGORY_FIELDS = {
|
|
617
663
|
ID: "id",
|
|
618
664
|
NAME: "name",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdminBeysView — browse and correct the generated Beyblade database.
|
|
3
|
+
*
|
|
4
|
+
* ── 🛑 WHY THIS IS A CORRECTION SURFACE, NOT A CRUD SCREEN ────────────────
|
|
5
|
+
*
|
|
6
|
+
* `beys` is 1,988 rows built by committed generators from a crawled corpus. So
|
|
7
|
+
* there is no "Add Beyblade" button: a row authored here would have no source,
|
|
8
|
+
* would vanish on the next regeneration, and would make the claim "this dataset
|
|
9
|
+
* rebuilds from `appkit/src/seed/` alone" false. Adding one is a change to the
|
|
10
|
+
* generator.
|
|
11
|
+
*
|
|
12
|
+
* What this surface IS for is the thing the corpus demonstrably needs — fixing
|
|
13
|
+
* a wrong extraction. On mode data alone the source was wrong five times in one
|
|
14
|
+
* session: an inverted tip transition, a mechanism attributed to the wrong
|
|
15
|
+
* part, a part with no modes at all, and two misexpanded abbreviations. A
|
|
16
|
+
* correction made here survives the next reseed, field by field
|
|
17
|
+
* (`adminEditedFields`).
|
|
18
|
+
*
|
|
19
|
+
* @tag domain:beys
|
|
20
|
+
* @tag layer:feature
|
|
21
|
+
* @tag access:client
|
|
22
|
+
* @tag consumers:app/[locale]/admin/beys/page.tsx
|
|
23
|
+
*/
|
|
24
|
+
import React from "react";
|
|
25
|
+
import type { ListingLayoutProps } from "../../../ui";
|
|
26
|
+
export type AdminBeysViewProps = ListingLayoutProps;
|
|
27
|
+
export declare function AdminBeysView({ children, ...props }: AdminBeysViewProps): React.JSX.Element;
|