@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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: Authored six-part procedures for content-discovery/parts-database.
|
|
3
|
+
* WHAT: 9 cases, keyed by full checklist id.
|
|
4
|
+
*
|
|
5
|
+
* 🛑 EVERY CASE HERE IS WRITTEN AS BEFORE → AFTER, because every defect this
|
|
6
|
+
* feature has already produced returned HTTP 200 with plausible content. A case
|
|
7
|
+
* reading "check the parts page works" passes against all four of them:
|
|
8
|
+
*
|
|
9
|
+
* 1. `getCompositionsForCategory` first queried a `searchTxt` field that
|
|
10
|
+
* `modelComposition` does not have. Zero rows, no error, an empty panel
|
|
11
|
+
* that reads as "no composition recorded".
|
|
12
|
+
* 2. The composition `name` was a FILE PATH for all 259 rows, so the
|
|
13
|
+
* category join matched 0 of 259 — and a model page with no panel is
|
|
14
|
+
* indistinguishable from a model we have no data for.
|
|
15
|
+
* 3. Without the paren-stripped fallback the join reached only 150 of 259,
|
|
16
|
+
* and HMS models in particular silently had no panel.
|
|
17
|
+
* 4. 🛑 The one live listing whose leaf has a composition is an ART POSTER,
|
|
18
|
+
* so before `LISTING_TYPE_HAS_COMPOSITION` the only Parts tab in the
|
|
19
|
+
* catalogue offered a poster an attack ring and a blade base — a correct
|
|
20
|
+
* part list for the wrong kind of object.
|
|
21
|
+
*
|
|
22
|
+
* So each case below names what the WRONG answer looked like, and several
|
|
23
|
+
* compare two surfaces that must agree rather than checking that something
|
|
24
|
+
* rendered.
|
|
25
|
+
*
|
|
26
|
+
* @tag domain:tester
|
|
27
|
+
* @tag layer:seed
|
|
28
|
+
* @tag pattern:none
|
|
29
|
+
* @tag access:server-only
|
|
30
|
+
* @tag consumers:seed-data/authored/index.ts
|
|
31
|
+
* @tag sideEffects:none
|
|
32
|
+
*/
|
|
33
|
+
export const authored = {
|
|
34
|
+
"checklist-content-discovery-parts-database-index-lists-every-classification": {
|
|
35
|
+
roles: ["guest"],
|
|
36
|
+
startPage: "/parts",
|
|
37
|
+
steps: [
|
|
38
|
+
"Open /parts.",
|
|
39
|
+
"Count the classification tiles.",
|
|
40
|
+
"Confirm every tile reads as a part NAME in sentence case — 'Performance Tip', 'Fusion Wheel', 'Bit Chip' — and not as a URL slug like 'performance-tip' or 'bit-chip'.",
|
|
41
|
+
"Open any one tile and confirm it loads a page rather than 404ing.",
|
|
42
|
+
],
|
|
43
|
+
expectedBehaviour: "BEFORE: there was no parts database at all, so a buyer reading '105RF' on a listing had nowhere to learn that 105 is a spin-track height and RF is a rubber flat tip. AFTER: 20 classifications, each a real page. The labels come from a compile-checked map rather than being title-cased from the slug, because 'Bit Chip' is not 'bit-chip' title-cased and 'Armour' is not 'armour'.",
|
|
44
|
+
expectedUiState: "Exactly 20 tiles, each labelled in sentence case, each a working link.",
|
|
45
|
+
expectedData: { classificationTiles: 20 },
|
|
46
|
+
endResult: "Nothing is changed; this case only reads.",
|
|
47
|
+
},
|
|
48
|
+
"checklist-content-discovery-parts-database-index-shows-no-fabricated-counts": {
|
|
49
|
+
roles: ["guest"],
|
|
50
|
+
startPage: "/parts",
|
|
51
|
+
steps: [
|
|
52
|
+
"Open /parts.",
|
|
53
|
+
"Look at each classification tile and confirm NO tile shows a number of parts.",
|
|
54
|
+
"Answer no if any tile shows a count, including a zero.",
|
|
55
|
+
],
|
|
56
|
+
expectedBehaviour: "A per-classification count on this page would be 20 queries or a 753-document scan on a page that exists only to route — against a ~3-round-trip budget per render. The honest version of 'we are not going to pay for that number' is to not show a number, rather than to show a stale or a zero one.",
|
|
57
|
+
expectedUiState: "Tiles carry a label and a link, and no count.",
|
|
58
|
+
expectedData: { tileShowsCount: false },
|
|
59
|
+
endResult: "Nothing is changed; this case only reads.",
|
|
60
|
+
},
|
|
61
|
+
"checklist-content-discovery-parts-database-classification-page-lists-its-parts": {
|
|
62
|
+
roles: ["guest"],
|
|
63
|
+
startPage: "/parts",
|
|
64
|
+
steps: [
|
|
65
|
+
"Open /parts, then open the Performance Tip tile.",
|
|
66
|
+
"Read the count in the line under the heading.",
|
|
67
|
+
"Count the rows listed.",
|
|
68
|
+
"Confirm the rows are in alphabetical order by part name.",
|
|
69
|
+
"Confirm a part that has a recorded weight shows it as '(N g)' and a part with no recorded weight shows nothing in its place — not '0 g' and not an em-dash.",
|
|
70
|
+
],
|
|
71
|
+
expectedBehaviour: "The count is a promise about the list directly beneath it, so the two must agree exactly. Only 98 of 753 parts carry a weight and only 23 carry a code; both are facts about our sources rather than missing data, so an absent one renders as nothing at all. A '0 g' would be a measurement we never took.",
|
|
72
|
+
expectedUiState: "The stated count equals the number of rows. Rows are alphabetical. Weights appear only where recorded.",
|
|
73
|
+
expectedData: { statedCountEqualsRowCount: true },
|
|
74
|
+
endResult: "Nothing is changed; this case only reads.",
|
|
75
|
+
},
|
|
76
|
+
"checklist-content-discovery-parts-database-unknown-classification-404s": {
|
|
77
|
+
roles: ["guest"],
|
|
78
|
+
startPage: "/parts",
|
|
79
|
+
steps: [
|
|
80
|
+
"Open /parts, then edit the address bar to /parts/zzzznope and load it.",
|
|
81
|
+
"Confirm you get a 404 page.",
|
|
82
|
+
"Then go back to /parts and open the Performance Tip tile as a positive control, and confirm it loads with rows.",
|
|
83
|
+
"Answer no if /parts/zzzznope renders an EMPTY classification page instead of 404ing.",
|
|
84
|
+
],
|
|
85
|
+
expectedBehaviour: "BEFORE the guard existed, casting a URL segment to a slot type would have made this a perfectly successful query for a classification no document has: zero rows, HTTP 200, and a page reading 'No parts recorded in this classification yet' — which is a lie about our data rather than a statement about the URL. The positive control is what distinguishes a working 404 from a page that 404s on everything.",
|
|
86
|
+
expectedUiState: "/parts/zzzznope is a 404. /parts/performance-tip loads and lists parts.",
|
|
87
|
+
expectedData: { unknownSegment404s: true, knownSegmentLoads: true },
|
|
88
|
+
endResult: "Nothing is changed; this case only reads.",
|
|
89
|
+
},
|
|
90
|
+
"checklist-content-discovery-parts-database-part-page-shows-which-models-use-it": {
|
|
91
|
+
roles: ["guest"],
|
|
92
|
+
startPage: "/parts",
|
|
93
|
+
steps: [
|
|
94
|
+
"Open /parts, then open Disc, then open the part named Sting.",
|
|
95
|
+
"Read the 'Used by N models' heading.",
|
|
96
|
+
"Count the model rows beneath it.",
|
|
97
|
+
"Click one model row and confirm it opens that model's category page.",
|
|
98
|
+
"Confirm any model row that is NOT a link is still readable as plain text rather than rendering as a broken link.",
|
|
99
|
+
],
|
|
100
|
+
expectedBehaviour: "'Which beys use this part' is the single most useful thing a part page says and the reason the glossary is a collection rather than a lookup table. 210 of 259 compositions joined to a category, so a model that did not join renders as plain text — a link to a page that does not exist is worse than no link.",
|
|
101
|
+
expectedUiState: "The heading count equals the number of rows. Linked models open their category page; unlinked models render as text.",
|
|
102
|
+
expectedData: { headingCountEqualsRowCount: true },
|
|
103
|
+
endResult: "Nothing is changed; this case only reads.",
|
|
104
|
+
},
|
|
105
|
+
"checklist-content-discovery-parts-database-part-page-shows-no-prices": {
|
|
106
|
+
roles: ["guest"],
|
|
107
|
+
startPage: "/parts",
|
|
108
|
+
steps: [
|
|
109
|
+
"Open /parts, then Disc, then Sting.",
|
|
110
|
+
"Read the Listings section.",
|
|
111
|
+
"Confirm NO row shows a rupee amount.",
|
|
112
|
+
"Confirm no row shows 'Sign in to see price' either.",
|
|
113
|
+
"Click 'Search the catalogue' or 'See all listings' and confirm it lands on /products with a search term.",
|
|
114
|
+
],
|
|
115
|
+
expectedBehaviour: "Prices are withheld from signed-out visitors, so an amount here would need the price gate and would make this a client-rendered page. A row carrying a title, a condition and a link has nothing to gate and therefore nothing to leak — which is why there is no 'Sign in to see price' placeholder either. The price lives one click away on the listing, where the gate already is.",
|
|
116
|
+
expectedUiState: "Listing rows show a title and optionally a condition. No rupee amounts, no sign-in-to-see-price text.",
|
|
117
|
+
expectedData: { pricesOnPartPage: false },
|
|
118
|
+
endResult: "Nothing is changed; this case only reads.",
|
|
119
|
+
},
|
|
120
|
+
"checklist-content-discovery-parts-database-model-page-shows-its-composition": {
|
|
121
|
+
roles: ["guest"],
|
|
122
|
+
startPage: "/categories/category-ace-dragon-sting-charge-zan",
|
|
123
|
+
steps: [
|
|
124
|
+
"Open /categories/category-ace-dragon-sting-charge-zan — a tier-4 named-model leaf. Every category id carries the `category-` prefix and a URL without it does not resolve.",
|
|
125
|
+
"Find the 'What it is made of' section.",
|
|
126
|
+
"Confirm it lists at least one slot row, each reading as a part classification on the left and a part name on the right.",
|
|
127
|
+
"Click a part name and confirm it opens that part's page under /parts/.",
|
|
128
|
+
"Then open /categories/category-burst-gt — the generation LINE one level up — and confirm it has NO 'What it is made of' section at all.",
|
|
129
|
+
],
|
|
130
|
+
expectedBehaviour: "BEFORE: the panel showed nothing on every model, twice over — the read queried a field the collection does not have, and separately every composition's name was a file path so none of them joined to a category. Both failures rendered as an absent section, which is indistinguishable from a model we have no data for. AFTER: the leaf shows its parts and each one links out. The LINE shows nothing deliberately — 'Burst GT' is not made of parts, the beys under it are.",
|
|
131
|
+
expectedUiState: "The model leaf shows a composition with linked part names. The line above it shows no composition section.",
|
|
132
|
+
expectedData: { leafHasPanel: true, lineHasPanel: false },
|
|
133
|
+
endResult: "Nothing is changed; this case only reads.",
|
|
134
|
+
},
|
|
135
|
+
"checklist-content-discovery-parts-database-art-listing-has-no-parts-tab": {
|
|
136
|
+
roles: ["guest"],
|
|
137
|
+
startPage: "/products/art-dranzer-phoenix-poster",
|
|
138
|
+
steps: [
|
|
139
|
+
"Open /products/art-dranzer-phoenix-poster — an art print filed under the Dranzer F model leaf.",
|
|
140
|
+
"Read the tab strip above the description.",
|
|
141
|
+
"Confirm there is NO 'Parts' tab.",
|
|
142
|
+
"Answer no if a Parts tab appears listing an attack ring, a weight disk or a blade base.",
|
|
143
|
+
],
|
|
144
|
+
expectedBehaviour: "BEFORE: this was the ONLY listing in the catalogue that satisfied every other condition, so the one Parts tab in the whole product catalogue offered a poster an attack ring and a blade base. It rendered a correct part list for the wrong kind of object, with no error anywhere. AFTER: a print, a sticker sheet and a redemption code are excluded by type. They file under the model's leaf on purpose — a sticker sheet belongs beside the bey it is for — and none of them is made of its parts.",
|
|
145
|
+
expectedUiState: "No Parts tab on an art listing.",
|
|
146
|
+
expectedData: { partsTabOnArtListing: false },
|
|
147
|
+
endResult: "Nothing is changed; this case only reads.",
|
|
148
|
+
},
|
|
149
|
+
"checklist-content-discovery-parts-database-seller-description-lists-parts": {
|
|
150
|
+
roles: ["seller"],
|
|
151
|
+
startPage: "/store/products/new",
|
|
152
|
+
steps: [
|
|
153
|
+
"Open /store/products/new and enter any title.",
|
|
154
|
+
"Pick the category category-ace-dragon-sting-charge-zan, a Burst GT named-model leaf.",
|
|
155
|
+
"Set the condition to Used.",
|
|
156
|
+
"Wait for the template picker to fill the Description field.",
|
|
157
|
+
"Read the filled description and confirm it contains a 'Parts:' line naming at least one real part.",
|
|
158
|
+
"Now change the category to category-pokemon-set-151 — a real trading-card expansion-set leaf, which has no parts — and confirm the refilled description contains NO 'Parts:' line at all.",
|
|
159
|
+
"Answer no if the second description shows an EMPTY 'Parts:' line.",
|
|
160
|
+
],
|
|
161
|
+
expectedBehaviour: "A listing filed under a named model can state what the bey is made of, from the same data the model page renders. The second half is the half that matters: an unfilled slot resolves to an empty string, so a shared template would render an empty 'Parts:' bullet on every listing in the catalogue that is not a Beyblade. The line is REMOVED rather than emptied — absent data must read as absent, not as blank.",
|
|
162
|
+
expectedUiState: "The Beyblade description carries a Parts line naming real parts. The trading-card description has no Parts line and no empty bullet.",
|
|
163
|
+
expectedData: { partsLineOnModelLeaf: true, emptyPartsLineElsewhere: false },
|
|
164
|
+
endResult: "Nothing is saved — the draft is abandoned without publishing, so no listing is created.",
|
|
165
|
+
},
|
|
166
|
+
};
|
|
@@ -76,6 +76,7 @@ import { authored as a_content_discovery__blog } from "./content-discovery__blog
|
|
|
76
76
|
import { authored as a_content_discovery__coupons } from "./content-discovery__coupons.js";
|
|
77
77
|
import { authored as a_content_discovery__events } from "./content-discovery__events.js";
|
|
78
78
|
import { authored as a_content_discovery__faq_help } from "./content-discovery__faq-help.js";
|
|
79
|
+
import { authored as a_content_discovery__parts_database } from "./content-discovery__parts-database.js";
|
|
79
80
|
import { authored as a_content_discovery__notifications } from "./content-discovery__notifications.js";
|
|
80
81
|
import { authored as a_content_discovery__search } from "./content-discovery__search.js";
|
|
81
82
|
import { authored as a_cta_layout__checkout_bottom_bar } from "./cta-layout__checkout-bottom-bar.js";
|
|
@@ -195,6 +196,7 @@ export const AUTHORED_CASES = {
|
|
|
195
196
|
...a_content_discovery__coupons,
|
|
196
197
|
...a_content_discovery__events,
|
|
197
198
|
...a_content_discovery__faq_help,
|
|
199
|
+
...a_content_discovery__parts_database,
|
|
198
200
|
...a_content_discovery__notifications,
|
|
199
201
|
...a_content_discovery__search,
|
|
200
202
|
...a_cta_layout__checkout_bottom_bar,
|
|
@@ -3091,6 +3091,34 @@ const rawTesterChecklistItems = [
|
|
|
3091
3091
|
{ key: "brand-count-matches-its-listing", label: "A brand page's count equals what that brand page lists" },
|
|
3092
3092
|
],
|
|
3093
3093
|
},
|
|
3094
|
+
{
|
|
3095
|
+
/*
|
|
3096
|
+
* D12 — the parts database, the composition panel, and the part line in a
|
|
3097
|
+
* seller's auto-filled description.
|
|
3098
|
+
*
|
|
3099
|
+
* 🛑 Four defects this feature has already produced, every one of them a
|
|
3100
|
+
* 200 with plausible content: a read against a field the collection does
|
|
3101
|
+
* not have; a composition `name` that was a FILE PATH for all 259 rows so
|
|
3102
|
+
* nothing joined; a join that reached 150 of 259 until a paren-stripped
|
|
3103
|
+
* fallback took it to 210; and a Parts tab on an ART POSTER offering it
|
|
3104
|
+
* an attack ring. None of them errored, and three of them rendered as an
|
|
3105
|
+
* ABSENT panel — indistinguishable from a model we have no data for.
|
|
3106
|
+
*/
|
|
3107
|
+
pageKey: "parts-database",
|
|
3108
|
+
pageLabel: "Parts database, composition panel and the part line in a description",
|
|
3109
|
+
href: "/parts",
|
|
3110
|
+
cases: [
|
|
3111
|
+
{ key: "index-lists-every-classification", label: "The parts index lists all 20 classifications, labelled as names and not as URL slugs" },
|
|
3112
|
+
{ key: "index-shows-no-fabricated-counts", label: "No classification tile shows a part count — not even a zero" },
|
|
3113
|
+
{ key: "classification-page-lists-its-parts", label: "A classification's stated count equals the rows it lists, and a missing weight renders as nothing rather than 0 g" },
|
|
3114
|
+
{ key: "unknown-classification-404s", label: "An unknown classification 404s instead of rendering an empty page — with a real one as the control" },
|
|
3115
|
+
{ key: "part-page-shows-which-models-use-it", label: "A part page names the models that use it, and an unjoined model reads as text rather than a broken link" },
|
|
3116
|
+
{ key: "part-page-shows-no-prices", label: "A part page's listing rows carry no price and no sign-in-to-see-price placeholder" },
|
|
3117
|
+
{ key: "model-page-shows-its-composition", label: "A named-model leaf shows what it is made of with linked parts; the generation line above it shows none" },
|
|
3118
|
+
{ key: "art-listing-has-no-parts-tab", label: "An art print filed under a model leaf has NO Parts tab" },
|
|
3119
|
+
{ key: "seller-description-lists-parts", label: "A listing under a model leaf gets a Parts line in its filled description, and a non-Beyblade category gets no empty one" },
|
|
3120
|
+
],
|
|
3121
|
+
},
|
|
3094
3122
|
{
|
|
3095
3123
|
/*
|
|
3096
3124
|
* Store reviews. The endpoint answered 200 with zero reviews for EVERY
|
|
@@ -5497,7 +5525,6 @@ const rawTesterChecklistItems = [
|
|
|
5497
5525
|
description: "Found 2026-09-02: this is the only page in the app calling a Google Cloud API rather than Firestore, and the call was unguarded — a permission or outage error took the whole page down (and failed the production build). It now degrades to an empty list and records a DEGRADED_READ. Confirm the page renders its heading and empty state rather than an error boundary.",
|
|
5498
5526
|
href: "/admin/maintenance/cloud-logs",
|
|
5499
5527
|
},
|
|
5500
|
-
{ key: "copilot-admin", label: "Admin copilot page works correctly", href: "/admin/copilot" },
|
|
5501
5528
|
{ key: "team-admin", label: "Admin team page works correctly", href: "/admin/team" },
|
|
5502
5529
|
{
|
|
5503
5530
|
key: "team-permission-group-filter",
|
package/dist/index.d.ts
CHANGED
|
@@ -365,10 +365,7 @@ export { FEATURE_SUBPATH_MAP } from "./cli/index";
|
|
|
365
365
|
export { mergeFeatureMessages } from "./cli/index";
|
|
366
366
|
export { withFeatures } from "./cli/index";
|
|
367
367
|
export type { NextConfig } from "./cli/index";
|
|
368
|
-
export { COPILOT_LOGS_COLLECTION } from "./core/index";
|
|
369
|
-
export { COPILOT_LOGS_INDEXED_FIELDS } from "./core/index";
|
|
370
368
|
export { CacheManager } from "./core/index";
|
|
371
|
-
export { CopilotLogRepository } from "./core/index";
|
|
372
369
|
export { EventBus } from "./core/index";
|
|
373
370
|
export { Logger } from "./core/index";
|
|
374
371
|
export { MUTATION_EVENTS } from "./core/index";
|
|
@@ -381,8 +378,6 @@ export { StorageManager } from "./core/index";
|
|
|
381
378
|
export { _resetActionMiddleware } from "./core/index";
|
|
382
379
|
export { cacheManager } from "./core/index";
|
|
383
380
|
export { configureMarketDefaults } from "./core/index";
|
|
384
|
-
export { copilotLogQueryHelpers } from "./core/index";
|
|
385
|
-
export { copilotLogRepository } from "./core/index";
|
|
386
381
|
export { createServerAction } from "./core/index";
|
|
387
382
|
export { createSiteConfig } from "./core/index";
|
|
388
383
|
export { emitMutation } from "./core/index";
|
|
@@ -414,9 +409,6 @@ export type { ActionResult } from "./_internal/shared/types/action-result";
|
|
|
414
409
|
export { handleActionError } from "./utils/action-response";
|
|
415
410
|
export type { CacheEntry } from "./core/index";
|
|
416
411
|
export type { CacheOptions } from "./core/index";
|
|
417
|
-
export type { CopilotFeedback } from "./core/index";
|
|
418
|
-
export type { CopilotLogCreateInput } from "./core/index";
|
|
419
|
-
export type { CopilotLogDocument } from "./core/index";
|
|
420
412
|
export type { EventSubscription } from "./core/index";
|
|
421
413
|
export type { LogEntry } from "./core/index";
|
|
422
414
|
export type { LogLevel } from "./core/index";
|
|
@@ -1612,13 +1604,6 @@ export type { ContactFormProps } from "./features/contact/index";
|
|
|
1612
1604
|
export type { ContactInfoItem } from "./features/contact/index";
|
|
1613
1605
|
export type { ContactInfoSidebarProps } from "./features/contact/index";
|
|
1614
1606
|
export type { UseContactSubmitOptions } from "./features/contact/index";
|
|
1615
|
-
export { AdminCopilotView } from "./features/copilot/index";
|
|
1616
|
-
export { useCopilotChat } from "./features/copilot/index";
|
|
1617
|
-
export { useCopilotFeedback } from "./features/copilot/index";
|
|
1618
|
-
export type { AdminCopilotViewProps } from "./features/copilot/index";
|
|
1619
|
-
export type { CopilotChatResponse } from "./features/copilot/index";
|
|
1620
|
-
export type { CopilotMessage } from "./features/copilot/index";
|
|
1621
|
-
export type { UseCopilotFeedbackOptions } from "./features/copilot/index";
|
|
1622
1607
|
export { CorporateInquiryForm } from "./features/corporate/index";
|
|
1623
1608
|
export { buildCorporateColumns } from "./features/corporate/index";
|
|
1624
1609
|
export { corporateAdminColumns } from "./features/corporate/index";
|
|
@@ -2999,7 +2984,7 @@ export { Ol } from "./ui/index";
|
|
|
2999
2984
|
export { LoginRequiredModal } from "./ui/components/LoginRequiredModal";
|
|
3000
2985
|
export type { LoginRequiredModalProps } from "./ui/components/LoginRequiredModal";
|
|
3001
2986
|
export { isAuthError } from "./utils/auth-error";
|
|
3002
|
-
export { API_ENDPOINTS, API_ROUTES, LOGS_ENDPOINTS, AUTH_ENDPOINTS, ACCOUNT_ENDPOINTS, NOTIFICATIONS_ENDPOINTS, ADMIN_ENDPOINTS, AUCTION_ENDPOINTS, BID_ENDPOINTS, CART_ENDPOINTS, CATEGORY_ENDPOINTS, CHECKOUT_ENDPOINTS,
|
|
2987
|
+
export { API_ENDPOINTS, API_ROUTES, LOGS_ENDPOINTS, AUTH_ENDPOINTS, ACCOUNT_ENDPOINTS, NOTIFICATIONS_ENDPOINTS, ADMIN_ENDPOINTS, AUCTION_ENDPOINTS, BID_ENDPOINTS, CART_ENDPOINTS, CATEGORY_ENDPOINTS, CHECKOUT_ENDPOINTS, FAQ_ENDPOINTS, HOMEPAGE_ENDPOINTS, LOYALTY_ENDPOINTS, ANALYTICS_ENDPOINTS, MEDIA_ENDPOINTS, ORDER_ENDPOINTS, PREORDER_ENDPOINTS, PRODUCT_ENDPOINTS, REVIEW_ENDPOINTS, SEARCH_ENDPOINTS, SELLER_ENDPOINTS, BLOG_ENDPOINTS, WISHLIST_ENDPOINTS, COLLECTION_CACHE_PATHS, resolveEndpoint, resolveEndpointFn, ROUTES, PUBLIC_ROUTES, PROTECTED_ROUTES, AUTH_ROUTES, WISHLIST_MAX, HISTORY_MAX, CART_MAX_ITEMS, WISHLIST_DOC_ID, HISTORY_DOC_ID, WISHLIST_COLLECTION, HISTORY_COLLECTION, } from "./constants/index";
|
|
3003
2988
|
export { WishlistFullError } from "./features/wishlist/server";
|
|
3004
2989
|
export { useHistory, useHistoryMergeOnLogin, getGuestHistory, trackGuestHistory, removeGuestHistoryItem, clearGuestHistory, getGuestHistoryCount, type GuestHistoryItem, type GuestHistoryType, type UserHistoryItem, type HistoryProductType, type HistoryItemSnapshot, type TrackArgs as TrackHistoryArgs, } from "./features/history";
|
|
3005
2990
|
export { historyRepository, UserHistoryRepository, trackHistoryView, getHistoryForUser, removeHistoryItem, clearHistory, mergeGuestHistory, } from "./features/history/server";
|
|
@@ -3072,6 +3057,16 @@ export { groupedListingFormSchema, groupedListingCreateSchema, groupedListingUpd
|
|
|
3072
3057
|
export { scammersSeedData } from "./seed/index";
|
|
3073
3058
|
export { productFeaturesSeedData } from "./seed/index";
|
|
3074
3059
|
export { taxCodesSeedData } from "./seed/index";
|
|
3060
|
+
export { PART_GLOSSARY_COLLECTION, MODEL_COMPOSITION_COLLECTION, PART_SLOT_VALUES, PART_SLOT_LABEL, ALL_PART_SLOTS, LISTING_TYPE_HAS_COMPOSITION, partSlotFromSegment, partGlossaryId, } from "./features/parts/schemas/firestore";
|
|
3061
|
+
export type { PartGlossaryDocument, ModelCompositionDocument, ModelSlot, ModelSlotMerge, PartSlot, } from "./features/parts/schemas/firestore";
|
|
3062
|
+
export { partGlossarySeedData } from "./seed/index";
|
|
3063
|
+
export { modelCompositionSeedData } from "./seed/index";
|
|
3064
|
+
export { ModelCompositionPanel } from "./features/parts/components/ModelCompositionPanel";
|
|
3065
|
+
export { PartsIndexPageView, PartClassificationPageView, } from "./features/parts/components/PartsIndexPageView";
|
|
3066
|
+
export { PartDetailPageView } from "./features/parts/components/PartDetailPageView";
|
|
3067
|
+
export { AssemblyDrawer } from "./features/parts/components/AssemblyDrawer";
|
|
3068
|
+
export type { AssemblyDrawerProps, AssemblyPartOption, AssemblyRow, AssemblyOutcome, } from "./features/parts/components/AssemblyDrawer";
|
|
3069
|
+
export type { PartListingRow } from "./features/parts/components/PartDetailPageView";
|
|
3075
3070
|
export { SCAMMER_COLLECTION, SCAMMER_ID_PREFIX, SCAMMER_MATCH_FIELDS, SCAMMER_INDEXED_FIELDS, SCAMMER_FIELDS, SCAMMER_INCIDENTS_SUBCOLLECTION, SCAMMER_COMMENTS_SUBCOLLECTION, SCAMMER_CONTESTS_SUBCOLLECTION, MAX_COMMENT_BODY_LENGTH, MAX_PENDING_SCAMMER_REPORTS_PER_USER, ScammerStatusValues, ScamPlatformValues, SocialPlatformValues, ContestTypeValues, ContestStatusValues, SCAM_PLATFORM_LABELS, SOCIAL_PLATFORM_LABELS, SCAMMER_STATUS_LABELS, CONTEST_TYPE_LABELS, CONTEST_STATUS_LABELS, DEFAULT_SCAMMER_DATA, DEFAULT_SCAMMER_INCIDENT_DATA, DEFAULT_SCAMMER_COMMENT_DATA, DEFAULT_SCAMMER_CONTEST_DATA, } from "./features/scams/schemas/firestore";
|
|
3076
3071
|
export type { ScammerStatus, ScamPlatform, SocialPlatform, ContestType, ContestStatus, ScammerMatchField, ScammerMatchStrength, ScammerSocialMedia, ScammerDocument, ScammerIncidentDocument, ScammerCommentDocument, ScammerContestDocument, ScammerSuggestion, ScammerCreateInput, ScammerAdminUpdateInput, ScammerIncidentCreateInput, ScammerCommentCreateInput, ScammerContestCreateInput, } from "./features/scams/schemas/firestore";
|
|
3077
3072
|
export { SCAM_TYPES, SCAM_CATEGORIES, SCAM_TYPE_LABELS, SCAM_CATEGORY_LABELS, getScamType, getScamTypesByCategory, getCategoryForScamType, } from "./features/scams/constants/scam-types";
|
package/dist/index.js
CHANGED
|
@@ -649,17 +649,11 @@ export { mergeFeatureMessages } from "./cli/index.js";
|
|
|
649
649
|
export { withFeatures } from "./cli/index.js";
|
|
650
650
|
// ./core/index
|
|
651
651
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
652
|
-
// COPILOT_LOGS_COLLECTION - Constant used across modules.
|
|
653
|
-
export { COPILOT_LOGS_COLLECTION } from "./core/index.js";
|
|
654
652
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
655
|
-
// COPILOT_LOGS_INDEXED_FIELDS - Constant used across modules.
|
|
656
|
-
export { COPILOT_LOGS_INDEXED_FIELDS } from "./core/index.js";
|
|
657
653
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
658
654
|
// CacheManager - Shared export for cache manager.
|
|
659
655
|
export { CacheManager } from "./core/index.js";
|
|
660
656
|
// [DB]-Database layer â€" uses firebase-admin or another server-side DB SDK; can only run in a trusted server environment.
|
|
661
|
-
// CopilotLogRepository - Shared export for copilot log repository.
|
|
662
|
-
export { CopilotLogRepository } from "./core/index.js";
|
|
663
657
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
664
658
|
// EventBus - Shared export for event bus.
|
|
665
659
|
export { EventBus } from "./core/index.js";
|
|
@@ -698,11 +692,7 @@ export { cacheManager } from "./core/index.js";
|
|
|
698
692
|
// configureMarketDefaults - Shared export for configure market defaults.
|
|
699
693
|
export { configureMarketDefaults } from "./core/index.js";
|
|
700
694
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
701
|
-
// copilotLogQueryHelpers - Shared export for copilot log query helpers.
|
|
702
|
-
export { copilotLogQueryHelpers } from "./core/index.js";
|
|
703
695
|
// [DB]-Database layer â€" uses firebase-admin or another server-side DB SDK; can only run in a trusted server environment.
|
|
704
|
-
// copilotLogRepository - Shared export for copilot log repository.
|
|
705
|
-
export { copilotLogRepository } from "./core/index.js";
|
|
706
696
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
707
697
|
// createServerAction - Helper for create server action.
|
|
708
698
|
export { createServerAction } from "./core/index.js";
|
|
@@ -1321,6 +1311,7 @@ export { catalogueSeedData } from "./seed/index.js";
|
|
|
1321
1311
|
// [SERVER-ONLY]-Server-only â€" uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
1322
1312
|
// productsStandardSeedData - Seed data for standard (buy-now) product listings.
|
|
1323
1313
|
export { productsStandardSeedData } from "./seed/index.js";
|
|
1314
|
+
// 1,173-node taxonomy has a catalogue in it (967 of 968 leaves were empty).
|
|
1324
1315
|
// [SERVER-ONLY]-Server-only â€" uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
1325
1316
|
// productsAuctionsSeedData - Seed data for auction product listings.
|
|
1326
1317
|
export { productsAuctionsSeedData } from "./seed/index.js";
|
|
@@ -3093,16 +3084,13 @@ export { ContactPageView } from "./features/contact/index.js";
|
|
|
3093
3084
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
3094
3085
|
// useContactSubmit - React hook for use contact submit.
|
|
3095
3086
|
export { useContactSubmit } from "./features/contact/index.js";
|
|
3096
|
-
// ./features/copilot/index
|
|
3097
3087
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
3098
|
-
// AdminCopilotView - Component for admin copilot view.
|
|
3099
|
-
export { AdminCopilotView } from "./features/copilot/index.js";
|
|
3100
3088
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
3101
|
-
// useCopilotChat - React hook for use copilot chat.
|
|
3102
|
-
export { useCopilotChat } from "./features/copilot/index.js";
|
|
3103
3089
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
3104
|
-
//
|
|
3105
|
-
export
|
|
3090
|
+
// [TYPE]-TypeScript type-only export â€" erased at compile time, zero runtime cost.
|
|
3091
|
+
// [TYPE]-TypeScript type-only export â€" erased at compile time, zero runtime cost.
|
|
3092
|
+
// [TYPE]-TypeScript type-only export â€" erased at compile time, zero runtime cost.
|
|
3093
|
+
// [TYPE]-TypeScript type-only export â€" erased at compile time, zero runtime cost.
|
|
3106
3094
|
// ./features/corporate/index
|
|
3107
3095
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
3108
3096
|
// CorporateInquiryForm - Component for corporate inquiry form.
|
|
@@ -5355,7 +5343,7 @@ export { Ol } from "./ui/index.js";
|
|
|
5355
5343
|
export { LoginRequiredModal } from "./ui/components/LoginRequiredModal.js";
|
|
5356
5344
|
export { isAuthError } from "./utils/auth-error.js";
|
|
5357
5345
|
// -- Missing constants ----------------------------------------------------------
|
|
5358
|
-
export { API_ENDPOINTS, API_ROUTES, LOGS_ENDPOINTS, AUTH_ENDPOINTS, ACCOUNT_ENDPOINTS, NOTIFICATIONS_ENDPOINTS, ADMIN_ENDPOINTS, AUCTION_ENDPOINTS, BID_ENDPOINTS, CART_ENDPOINTS, CATEGORY_ENDPOINTS, CHECKOUT_ENDPOINTS,
|
|
5346
|
+
export { API_ENDPOINTS, API_ROUTES, LOGS_ENDPOINTS, AUTH_ENDPOINTS, ACCOUNT_ENDPOINTS, NOTIFICATIONS_ENDPOINTS, ADMIN_ENDPOINTS, AUCTION_ENDPOINTS, BID_ENDPOINTS, CART_ENDPOINTS, CATEGORY_ENDPOINTS, CHECKOUT_ENDPOINTS, FAQ_ENDPOINTS, HOMEPAGE_ENDPOINTS, LOYALTY_ENDPOINTS, ANALYTICS_ENDPOINTS, MEDIA_ENDPOINTS, ORDER_ENDPOINTS, PREORDER_ENDPOINTS, PRODUCT_ENDPOINTS, REVIEW_ENDPOINTS, SEARCH_ENDPOINTS, SELLER_ENDPOINTS, BLOG_ENDPOINTS, WISHLIST_ENDPOINTS, COLLECTION_CACHE_PATHS, resolveEndpoint, resolveEndpointFn, ROUTES, PUBLIC_ROUTES, PROTECTED_ROUTES, AUTH_ROUTES, WISHLIST_MAX, HISTORY_MAX, CART_MAX_ITEMS, WISHLIST_DOC_ID, HISTORY_DOC_ID, WISHLIST_COLLECTION, HISTORY_COLLECTION, } from "./constants/index.js";
|
|
5359
5347
|
// Wishlist server errors (server-only via tree-shake â€" class only, no firebase-admin at module scope)
|
|
5360
5348
|
export { WishlistFullError } from "./features/wishlist/server.js";
|
|
5361
5349
|
// History â€" client hook + guest util are safe in main barrel; repository is server-only
|
|
@@ -5460,6 +5448,26 @@ export { productFeaturesSeedData } from "./seed/index.js";
|
|
|
5460
5448
|
* had no data, which is indistinguishable from an empty fixture set.
|
|
5461
5449
|
*/
|
|
5462
5450
|
export { taxCodesSeedData } from "./seed/index.js";
|
|
5451
|
+
/*
|
|
5452
|
+
* E4/D12 — generated from the extracted corpus research.
|
|
5453
|
+
*
|
|
5454
|
+
* 🛑 Exported from the MAIN barrel, because `seed-cli.mjs` destructures all of
|
|
5455
|
+
* its seed data from `appkit` (not from `/server`, which is what I assumed
|
|
5456
|
+
* first). Adding a collection to `seed/index.ts` and `COLLECTION_MAP` alone
|
|
5457
|
+
* leaves the loader reading `undefined` — and `appkit-seed status` then reports
|
|
5458
|
+
* "0 seed rows", i.e. a collection that looks registered and loads nothing.
|
|
5459
|
+
* That is Root Cause #90 exactly: the PII half was wired and the LOADING half
|
|
5460
|
+
* was not, so every grep found real hits while the collection stayed empty.
|
|
5461
|
+
*/
|
|
5462
|
+
// E4/D12 — the collection constants and types.
|
|
5463
|
+
export { PART_GLOSSARY_COLLECTION, MODEL_COMPOSITION_COLLECTION, PART_SLOT_VALUES, PART_SLOT_LABEL, ALL_PART_SLOTS, LISTING_TYPE_HAS_COMPOSITION, partSlotFromSegment, partGlossaryId, } from "./features/parts/schemas/firestore.js";
|
|
5464
|
+
export { partGlossarySeedData } from "./seed/index.js";
|
|
5465
|
+
export { modelCompositionSeedData } from "./seed/index.js";
|
|
5466
|
+
// D12 — the public part surfaces.
|
|
5467
|
+
export { ModelCompositionPanel } from "./features/parts/components/ModelCompositionPanel.js";
|
|
5468
|
+
export { PartsIndexPageView, PartClassificationPageView, } from "./features/parts/components/PartsIndexPageView.js";
|
|
5469
|
+
export { PartDetailPageView } from "./features/parts/components/PartDetailPageView.js";
|
|
5470
|
+
export { AssemblyDrawer } from "./features/parts/components/AssemblyDrawer.js";
|
|
5463
5471
|
// Scam registry â€" schema types and constants
|
|
5464
5472
|
export { SCAMMER_COLLECTION, SCAMMER_ID_PREFIX, SCAMMER_MATCH_FIELDS, SCAMMER_INDEXED_FIELDS, SCAMMER_FIELDS, SCAMMER_INCIDENTS_SUBCOLLECTION, SCAMMER_COMMENTS_SUBCOLLECTION, SCAMMER_CONTESTS_SUBCOLLECTION, MAX_COMMENT_BODY_LENGTH, MAX_PENDING_SCAMMER_REPORTS_PER_USER, ScammerStatusValues, ScamPlatformValues, SocialPlatformValues, ContestTypeValues, ContestStatusValues, SCAM_PLATFORM_LABELS, SOCIAL_PLATFORM_LABELS, SCAMMER_STATUS_LABELS, CONTEST_TYPE_LABELS, CONTEST_STATUS_LABELS, DEFAULT_SCAMMER_DATA, DEFAULT_SCAMMER_INCIDENT_DATA, DEFAULT_SCAMMER_COMMENT_DATA, DEFAULT_SCAMMER_CONTEST_DATA, } from "./features/scams/schemas/firestore.js";
|
|
5465
5473
|
// Scam registry â€" scam type definitions
|
|
@@ -106,6 +106,9 @@ export declare const DEFAULT_ROUTE_MAP: {
|
|
|
106
106
|
readonly ITEM_REQUESTS_NEW: "/item-requests/new";
|
|
107
107
|
readonly TOOLS: "/tools";
|
|
108
108
|
readonly TOOL_PRICE_INDEX: "/tools/price-index";
|
|
109
|
+
readonly PARTS: "/parts";
|
|
110
|
+
readonly PART_CLASSIFICATION: (classification: string) => string;
|
|
111
|
+
readonly PART_DETAIL: (classification: string, code: string) => string;
|
|
109
112
|
readonly SCAM_TYPES: "/scams/types";
|
|
110
113
|
readonly SCAM_FAQS: "/scams/faqs";
|
|
111
114
|
readonly CLASSIFIED: "/classified";
|
|
@@ -335,7 +338,6 @@ export declare const DEFAULT_ROUTE_MAP: {
|
|
|
335
338
|
readonly EVENT_ENTRIES: (id: string) => string;
|
|
336
339
|
readonly STORES: "/admin/stores";
|
|
337
340
|
readonly STORE_DETAIL: (id: string) => string;
|
|
338
|
-
readonly COPILOT: "/admin/copilot";
|
|
339
341
|
readonly ADS: "/admin/ads";
|
|
340
342
|
readonly ADS_NEW: "/admin/ads/new";
|
|
341
343
|
readonly ADS_EDIT: (id: string) => string;
|
|
@@ -511,6 +513,9 @@ export declare const ROUTES: {
|
|
|
511
513
|
readonly ITEM_REQUESTS_NEW: "/item-requests/new";
|
|
512
514
|
readonly TOOLS: "/tools";
|
|
513
515
|
readonly TOOL_PRICE_INDEX: "/tools/price-index";
|
|
516
|
+
readonly PARTS: "/parts";
|
|
517
|
+
readonly PART_CLASSIFICATION: (classification: string) => string;
|
|
518
|
+
readonly PART_DETAIL: (classification: string, code: string) => string;
|
|
514
519
|
readonly SCAM_TYPES: "/scams/types";
|
|
515
520
|
readonly SCAM_FAQS: "/scams/faqs";
|
|
516
521
|
readonly CLASSIFIED: "/classified";
|
|
@@ -740,7 +745,6 @@ export declare const ROUTES: {
|
|
|
740
745
|
readonly EVENT_ENTRIES: (id: string) => string;
|
|
741
746
|
readonly STORES: "/admin/stores";
|
|
742
747
|
readonly STORE_DETAIL: (id: string) => string;
|
|
743
|
-
readonly COPILOT: "/admin/copilot";
|
|
744
748
|
readonly ADS: "/admin/ads";
|
|
745
749
|
readonly ADS_NEW: "/admin/ads/new";
|
|
746
750
|
readonly ADS_EDIT: (id: string) => string;
|
|
@@ -106,6 +106,20 @@ export const DEFAULT_ROUTE_MAP = {
|
|
|
106
106
|
*/
|
|
107
107
|
TOOLS: "/tools",
|
|
108
108
|
TOOL_PRICE_INDEX: "/tools/price-index",
|
|
109
|
+
/*
|
|
110
|
+
* The PARTS database. A reference surface, so it lives beside the other
|
|
111
|
+
* two things that answer a question rather than sell you something.
|
|
112
|
+
*
|
|
113
|
+
* 🛑 `/parts/{classification}/{code}` — the classification is REQUIRED in
|
|
114
|
+
* the path, because a code alone is ambiguous: `105` is a Metal Fight
|
|
115
|
+
* spin-track height, a bare number in Burst is a disc, and `1-60` is a
|
|
116
|
+
* Beyblade X ratchet. The two segments together are the document id
|
|
117
|
+
* (`${slot}--${tail}`), so the page is a direct doc read with no query
|
|
118
|
+
* and no index.
|
|
119
|
+
*/
|
|
120
|
+
PARTS: "/parts",
|
|
121
|
+
PART_CLASSIFICATION: (classification) => `/parts/${classification}`,
|
|
122
|
+
PART_DETAIL: (classification, code) => `/parts/${classification}/${code}`,
|
|
109
123
|
SCAM_TYPES: "/scams/types",
|
|
110
124
|
SCAM_FAQS: "/scams/faqs",
|
|
111
125
|
CLASSIFIED: "/classified",
|
|
@@ -364,7 +378,6 @@ export const DEFAULT_ROUTE_MAP = {
|
|
|
364
378
|
EVENT_ENTRIES: (id) => `/admin/events/${id}/entries`,
|
|
365
379
|
STORES: "/admin/stores",
|
|
366
380
|
STORE_DETAIL: (id) => `/admin/stores/${id}/view`,
|
|
367
|
-
COPILOT: "/admin/copilot",
|
|
368
381
|
ADS: "/admin/ads",
|
|
369
382
|
ADS_NEW: "/admin/ads/new",
|
|
370
383
|
ADS_EDIT: (id) => `/admin/ads/${id}/edit`,
|
|
@@ -112,6 +112,37 @@ export declare abstract class BaseRepository<T extends DocumentData> {
|
|
|
112
112
|
patch: Partial<T>;
|
|
113
113
|
}>): Promise<number>;
|
|
114
114
|
create(data: Partial<T> | FirestoreDocument): Promise<T>;
|
|
115
|
+
/**
|
|
116
|
+
* Write a document at a CALLER-CHOSEN id.
|
|
117
|
+
*
|
|
118
|
+
* 🛑 **This OVERWRITES. It is an upsert, not a create**, despite the name —
|
|
119
|
+
* `.set()` with no merge option replaces the whole document, and there is no
|
|
120
|
+
* existence check. Writing an id that already exists silently discards
|
|
121
|
+
* everything that was there and returns a success.
|
|
122
|
+
*
|
|
123
|
+
* That is **deliberate and load-bearing for most of its 28 callers**, which
|
|
124
|
+
* are genuine upserts keyed on a deterministic id:
|
|
125
|
+
*
|
|
126
|
+
* - `testerChecklistResponses` — one doc per (tester, case), re-answered
|
|
127
|
+
* every time a verdict changes. CLAUDE.md records that this upsert is
|
|
128
|
+
* *"what makes Yes/No answers + comments + screenshots survive a page
|
|
129
|
+
* reload"*, so a create-only variant here would break verdict recording.
|
|
130
|
+
* - `sessions`, analytics rollups, `priceIndex` — all rewritten by design.
|
|
131
|
+
*
|
|
132
|
+
* 🛑 **So do NOT "fix" this to `.create()`.** I was about to: the plan carried
|
|
133
|
+
* it as a severity-1 silent-overwrite risk for products, on the reasoning that
|
|
134
|
+
* two physical units of one model would derive the same slug and the second
|
|
135
|
+
* would destroy the first. **Measured, that is false** —
|
|
136
|
+
* `ProductRepository.create` OVERRIDES this method and runs `generateUniqueId`
|
|
137
|
+
* with a real `findById` probe, so units get `-1`, `-2`, … and never collide.
|
|
138
|
+
* A blanket `.create()` would have broken the tester harness to fix a defect
|
|
139
|
+
* products do not have.
|
|
140
|
+
*
|
|
141
|
+
* **A caller that needs create-only semantics must probe first**, the way
|
|
142
|
+
* `ProductRepository.create` does. The hazard this docstring removes is the
|
|
143
|
+
* NAME: `createWithId` reads as "create" and behaves as "replace", and that
|
|
144
|
+
* gap is what made the wrong conclusion so easy to reach.
|
|
145
|
+
*/
|
|
115
146
|
createWithId(id: string, data: Partial<T>): Promise<T>;
|
|
116
147
|
update(id: string, data: Partial<T>): Promise<T>;
|
|
117
148
|
delete(id: string): Promise<void>;
|
|
@@ -284,6 +284,37 @@ export class BaseRepository {
|
|
|
284
284
|
throw new DatabaseError(`Failed to create document: ${detailOf(error)}`, error);
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Write a document at a CALLER-CHOSEN id.
|
|
289
|
+
*
|
|
290
|
+
* 🛑 **This OVERWRITES. It is an upsert, not a create**, despite the name —
|
|
291
|
+
* `.set()` with no merge option replaces the whole document, and there is no
|
|
292
|
+
* existence check. Writing an id that already exists silently discards
|
|
293
|
+
* everything that was there and returns a success.
|
|
294
|
+
*
|
|
295
|
+
* That is **deliberate and load-bearing for most of its 28 callers**, which
|
|
296
|
+
* are genuine upserts keyed on a deterministic id:
|
|
297
|
+
*
|
|
298
|
+
* - `testerChecklistResponses` — one doc per (tester, case), re-answered
|
|
299
|
+
* every time a verdict changes. CLAUDE.md records that this upsert is
|
|
300
|
+
* *"what makes Yes/No answers + comments + screenshots survive a page
|
|
301
|
+
* reload"*, so a create-only variant here would break verdict recording.
|
|
302
|
+
* - `sessions`, analytics rollups, `priceIndex` — all rewritten by design.
|
|
303
|
+
*
|
|
304
|
+
* 🛑 **So do NOT "fix" this to `.create()`.** I was about to: the plan carried
|
|
305
|
+
* it as a severity-1 silent-overwrite risk for products, on the reasoning that
|
|
306
|
+
* two physical units of one model would derive the same slug and the second
|
|
307
|
+
* would destroy the first. **Measured, that is false** —
|
|
308
|
+
* `ProductRepository.create` OVERRIDES this method and runs `generateUniqueId`
|
|
309
|
+
* with a real `findById` probe, so units get `-1`, `-2`, … and never collide.
|
|
310
|
+
* A blanket `.create()` would have broken the tester harness to fix a defect
|
|
311
|
+
* products do not have.
|
|
312
|
+
*
|
|
313
|
+
* **A caller that needs create-only semantics must probe first**, the way
|
|
314
|
+
* `ProductRepository.create` does. The hazard this docstring removes is the
|
|
315
|
+
* NAME: `createWithId` reads as "create" and behaves as "replace", and that
|
|
316
|
+
* gap is what made the wrong conclusion so easy to reach.
|
|
317
|
+
*/
|
|
287
318
|
async createWithId(id, data) {
|
|
288
319
|
try {
|
|
289
320
|
const now = new Date();
|
|
@@ -49,8 +49,7 @@ export { EventRepository, EventsRepository, eventRepository, } from "../features
|
|
|
49
49
|
export { EventEntryRepository, EventEntriesRepository, eventEntryRepository, } from "../features/events/repository/event-entry.repository";
|
|
50
50
|
export { NewsletterRepository, newsletterRepository, } from "../core/newsletter.repository";
|
|
51
51
|
export type { NewsletterSubscriberDocument, NewsletterSubscriberCreateInput, NewsletterSubscriberUpdateInput, NewsletterListModel, } from "../core/newsletter.repository";
|
|
52
|
-
export {
|
|
53
|
-
export type { CopilotFeedback, CopilotLogDocument, CopilotLogCreateInput, } from "../core";
|
|
52
|
+
export type {} from "../core";
|
|
54
53
|
export { ScammerRepository, scammerRepository, } from "../features/scams/repository/scammer.repository";
|
|
55
54
|
export { SupportRepository, supportRepository, } from "../features/support/repository/support.repository";
|
|
56
55
|
export type { SupportTicketDocument, SupportTicketCreateInput, SupportTicketUpdateInput, TicketMessage, TicketCategory, TicketStatus, TicketPriority, } from "../features/support/schemas/firestore";
|
|
@@ -61,7 +61,6 @@ export { historyRepository, } from "../features/history/repository/user-history.
|
|
|
61
61
|
export { EventRepository, EventsRepository, eventRepository, } from "../features/events/repository/events.repository.js";
|
|
62
62
|
export { EventEntryRepository, EventEntriesRepository, eventEntryRepository, } from "../features/events/repository/event-entry.repository.js";
|
|
63
63
|
export { NewsletterRepository, newsletterRepository, } from "../core/newsletter.repository.js";
|
|
64
|
-
export { CopilotLogRepository, copilotLogRepository } from "../core/index.js";
|
|
65
64
|
export { ScammerRepository, scammerRepository, } from "../features/scams/repository/scammer.repository.js";
|
|
66
65
|
export { SupportRepository, supportRepository, } from "../features/support/repository/support.repository.js";
|
|
67
66
|
// SB-UNI-B — SublistingCategoriesRepository deleted; sublistings now live on
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by scripts/gen-category-content.mjs — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* `contentBody` per category, composed from FACTS ONLY (I7). Every clause
|
|
5
|
+
* traces to a committed source: canonical-beys.json for a model leaf,
|
|
6
|
+
* part-glossary.json for a part leaf, the forest's own child lists above them.
|
|
7
|
+
*
|
|
8
|
+
* 🛑 A node with nothing factual to say is ABSENT from this map rather than
|
|
9
|
+
* carrying an empty body — `CategoryHighlightsAndFaqSection` renders nothing
|
|
10
|
+
* when its inputs are empty, and an empty paragraph reads as a defect.
|
|
11
|
+
*
|
|
12
|
+
* 🛑 No price band appears here. `MARKET_BANDS` omits a band rather than
|
|
13
|
+
* guessing one, and a body that stated an unmeasured "typical price" would be
|
|
14
|
+
* the same guess with better typography.
|
|
15
|
+
*
|
|
16
|
+
* 🛑 No listing COUNT appears here either. Counts live in `categoryCounts` and
|
|
17
|
+
* move; a sentence saying "48 listings" is wrong the next day and nothing
|
|
18
|
+
* reports it.
|
|
19
|
+
*
|
|
20
|
+
* Model leaves with a body: 213 of 216
|
|
21
|
+
* Part leaves with a body: 532 of 752
|
|
22
|
+
*/
|
|
23
|
+
export declare const GENERATED_CATEGORY_CONTENT: Record<string, string>;
|
|
24
|
+
export declare const GENERATED_CATEGORY_CONTENT_COUNT = 745;
|