@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.
Files changed (160) hide show
  1. package/dist/.build-stamp.json +4 -4
  2. package/dist/_internal/server/features/categories/live-counts.d.ts +41 -0
  3. package/dist/_internal/server/features/categories/live-counts.js +72 -0
  4. package/dist/_internal/server/features/checkout/expand-order-items.js +17 -0
  5. package/dist/_internal/server/features/orders/adapters.js +29 -0
  6. package/dist/_internal/server/features/parts/assembly.d.ts +126 -0
  7. package/dist/_internal/server/features/parts/assembly.js +246 -0
  8. package/dist/_internal/server/features/parts/data.d.ts +65 -0
  9. package/dist/_internal/server/features/parts/data.js +170 -0
  10. package/dist/_internal/server/features/parts/og.d.ts +34 -0
  11. package/dist/_internal/server/features/parts/og.js +66 -0
  12. package/dist/_internal/server/features/search/search-everything.d.ts +75 -0
  13. package/dist/_internal/server/features/search/search-everything.js +120 -0
  14. package/dist/_internal/server/features/search/search-sources.d.ts +17 -0
  15. package/dist/_internal/server/features/search/search-sources.js +138 -0
  16. package/dist/_internal/server/jobs/core/categoryTaxonomyBackfill.d.ts +70 -0
  17. package/dist/_internal/server/jobs/core/categoryTaxonomyBackfill.js +302 -0
  18. package/dist/_internal/server/jobs/core/countersReconcile.js +128 -19
  19. package/dist/_internal/server/jobs/core/jobRunners.js +12 -0
  20. package/dist/_internal/server/jobs/core/onCategoryWrite.js +73 -3
  21. package/dist/_internal/server/jobs/core/onProductWrite.js +93 -43
  22. package/dist/_internal/server/jobs/core/productViewWindows.d.ts +2 -0
  23. package/dist/_internal/server/jobs/core/productViewWindows.js +173 -0
  24. package/dist/_internal/server/jobs/core/testDataCleanup.d.ts +1 -1
  25. package/dist/_internal/server/jobs/core/testDataCleanup.js +27 -1
  26. package/dist/_internal/server/jobs/handlers/pageViewRollup.d.ts +20 -0
  27. package/dist/_internal/server/jobs/handlers/pageViewRollup.js +25 -1
  28. package/dist/_internal/shared/actions/action-registry.js +36 -0
  29. package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
  30. package/dist/_internal/shared/features/shipping/protections.d.ts +93 -1
  31. package/dist/_internal/shared/features/shipping/protections.js +127 -0
  32. package/dist/_internal/shared/search/part-aliases.d.ts +44 -0
  33. package/dist/_internal/shared/search/part-aliases.js +104 -0
  34. package/dist/constants/api-endpoints.d.ts +18 -12
  35. package/dist/constants/api-endpoints.js +6 -8
  36. package/dist/constants/field-names.d.ts +0 -2
  37. package/dist/constants/field-names.js +0 -2
  38. package/dist/constants/index.d.ts +1 -1
  39. package/dist/constants/index.js +1 -1
  40. package/dist/core/index.d.ts +0 -3
  41. package/dist/core/index.js +0 -2
  42. package/dist/errors/messages.d.ts +0 -7
  43. package/dist/errors/messages.js +0 -8
  44. package/dist/features/admin/components/AdminSectionsView.js +83 -32
  45. package/dist/features/admin/components/AdminSiteSettingsView.js +6 -2
  46. package/dist/features/admin/components/sections/sectionTemplates.d.ts +117 -0
  47. package/dist/features/admin/components/sections/sectionTemplates.js +213 -0
  48. package/dist/features/admin/schemas/firestore.d.ts +15 -0
  49. package/dist/features/admin/types/index.d.ts +0 -1
  50. package/dist/features/auth/permissions/constants.d.ts +1 -1
  51. package/dist/features/auth/permissions/constants.js +0 -2
  52. package/dist/features/categories/api/[id]/description-template/route.js +40 -1
  53. package/dist/features/categories/components/BrandDetailPageView.js +14 -3
  54. package/dist/features/categories/components/BreadcrumbTrail.d.ts +24 -1
  55. package/dist/features/categories/components/BreadcrumbTrail.js +45 -4
  56. package/dist/features/categories/components/CategoryDetailPageView.js +89 -14
  57. package/dist/features/categories/components/CategoryTemplatePicker.js +9 -1
  58. package/dist/features/categories/repository/categories.repository.d.ts +65 -0
  59. package/dist/features/categories/repository/categories.repository.js +168 -22
  60. package/dist/features/categories/repository/category-counts.d.ts +143 -0
  61. package/dist/features/categories/repository/category-counts.js +224 -0
  62. package/dist/features/categories/schemas/category-counts.d.ts +155 -0
  63. package/dist/features/categories/schemas/category-counts.js +116 -0
  64. package/dist/features/categories/schemas/firestore.d.ts +0 -2
  65. package/dist/features/categories/schemas/firestore.js +0 -1
  66. package/dist/features/categories/utils/breadcrumb-chain.d.ts +194 -0
  67. package/dist/features/categories/utils/breadcrumb-chain.js +202 -0
  68. package/dist/features/corporate/schemas/index.d.ts +4 -4
  69. package/dist/features/orders/schemas/firestore.d.ts +13 -0
  70. package/dist/features/orders/types/index.d.ts +30 -0
  71. package/dist/features/parts/components/AssemblyDrawer.d.ts +107 -0
  72. package/dist/features/parts/components/AssemblyDrawer.js +250 -0
  73. package/dist/features/parts/components/ModelCompositionPanel.d.ts +37 -0
  74. package/dist/features/parts/components/ModelCompositionPanel.js +34 -0
  75. package/dist/features/parts/components/PartDetailPageView.d.ts +48 -0
  76. package/dist/features/parts/components/PartDetailPageView.js +20 -0
  77. package/dist/features/parts/components/PartsIndexPageView.d.ts +33 -0
  78. package/dist/features/parts/components/PartsIndexPageView.js +17 -0
  79. package/dist/features/parts/schemas/firestore.d.ts +257 -0
  80. package/dist/features/parts/schemas/firestore.js +181 -0
  81. package/dist/features/parts/utils/colourway-match.d.ts +62 -0
  82. package/dist/features/parts/utils/colourway-match.js +98 -0
  83. package/dist/features/parts/utils/part-breakdown.d.ts +53 -0
  84. package/dist/features/parts/utils/part-breakdown.js +77 -0
  85. package/dist/features/products/components/ProductDetailPageView.d.ts +14 -1
  86. package/dist/features/products/components/ProductDetailPageView.js +22 -3
  87. package/dist/features/products/components/ProductTabsShell.d.ts +3 -1
  88. package/dist/features/products/components/ProductTabsShell.js +4 -1
  89. package/dist/features/products/repository/category-assignment.d.ts +132 -0
  90. package/dist/features/products/repository/category-assignment.js +304 -0
  91. package/dist/features/products/repository/products.repository.d.ts +58 -1
  92. package/dist/features/products/repository/products.repository.js +255 -28
  93. package/dist/features/products/schemas/firestore.d.ts +147 -6
  94. package/dist/features/products/schemas/firestore.js +49 -0
  95. package/dist/features/search/components/SearchResultsPageView.d.ts +33 -0
  96. package/dist/features/search/components/SearchResultsPageView.js +21 -0
  97. package/dist/features/seller/components/CategoryInlineSelect.d.ts +17 -1
  98. package/dist/features/seller/components/CategoryInlineSelect.js +14 -3
  99. package/dist/features/seller/components/SellerProductShell.d.ts +26 -1
  100. package/dist/features/seller/components/SellerProductShell.js +30 -1
  101. package/dist/features/seller/components/SellerProductsView.js +129 -3
  102. package/dist/features/tester/seed-data/authored/admin__site-system.js +0 -15
  103. package/dist/features/tester/seed-data/authored/content-discovery__parts-database.d.ts +2 -0
  104. package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +166 -0
  105. package/dist/features/tester/seed-data/authored/index.js +2 -0
  106. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +28 -1
  107. package/dist/index.d.ts +11 -16
  108. package/dist/index.js +26 -18
  109. package/dist/next/routing/route-map.d.ts +6 -2
  110. package/dist/next/routing/route-map.js +14 -1
  111. package/dist/providers/db-firebase/base.repository.d.ts +31 -0
  112. package/dist/providers/db-firebase/base.repository.js +31 -0
  113. package/dist/repositories/index.d.ts +1 -2
  114. package/dist/repositories/index.js +0 -1
  115. package/dist/seed/_helpers/category-content-generated.d.ts +24 -0
  116. package/dist/seed/_helpers/category-content-generated.js +1837 -0
  117. package/dist/seed/_helpers/category-content.d.ts +29 -0
  118. package/dist/seed/_helpers/category-content.js +98 -1
  119. package/dist/seed/_helpers/category-forest.js +104 -24
  120. package/dist/seed/_helpers/category-parts.d.ts +9 -0
  121. package/dist/seed/_helpers/category-parts.js +4594 -0
  122. package/dist/seed/carousel-slides-seed-data.js +81 -8
  123. package/dist/seed/categories-seed-data.js +44 -6
  124. package/dist/seed/homepage-sections-seed-data.js +170 -9
  125. package/dist/seed/index.d.ts +2 -0
  126. package/dist/seed/index.js +13 -0
  127. package/dist/seed/model-composition-seed-data.d.ts +15 -0
  128. package/dist/seed/model-composition-seed-data.js +8918 -0
  129. package/dist/seed/part-glossary-seed-data.d.ts +15 -0
  130. package/dist/seed/part-glossary-seed-data.js +10729 -0
  131. package/dist/seed/products-standard-seed-data.js +52 -0
  132. package/dist/seed/seed-purpose.d.ts +103 -4
  133. package/dist/seed/seed-purpose.js +162 -11
  134. package/dist/seo/json-ld.d.ts +10 -1
  135. package/dist/seo/json-ld.js +9 -1
  136. package/dist/server.d.ts +7 -0
  137. package/dist/server.js +11 -0
  138. package/dist/styles.css +1 -1
  139. package/dist/tailwind-utilities.css +1 -1
  140. package/dist/utils/id-generators.d.ts +30 -0
  141. package/dist/utils/id-generators.js +31 -1
  142. package/dist/utils/search-txt-builders.js +22 -0
  143. package/dist/utils/search-txt.d.ts +22 -0
  144. package/dist/utils/search-txt.js +17 -0
  145. package/dist/values/success-messages.d.ts +0 -4
  146. package/dist/values/success-messages.js +0 -5
  147. package/package.json +1 -1
  148. package/scripts/seed-cli.mjs +292 -16
  149. package/dist/core/copilot-log.repository.d.ts +0 -44
  150. package/dist/core/copilot-log.repository.js +0 -61
  151. package/dist/features/copilot/components/AdminCopilotView.d.ts +0 -21
  152. package/dist/features/copilot/components/AdminCopilotView.js +0 -51
  153. package/dist/features/copilot/components/index.d.ts +0 -2
  154. package/dist/features/copilot/components/index.js +0 -1
  155. package/dist/features/copilot/hooks/useCopilotChat.d.ts +0 -25
  156. package/dist/features/copilot/hooks/useCopilotChat.js +0 -65
  157. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +0 -15
  158. package/dist/features/copilot/hooks/useCopilotFeedback.js +0 -15
  159. package/dist/features/copilot/index.d.ts +0 -3
  160. 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, PAYMENT_ENDPOINTS, COPILOT_ENDPOINTS, CORPORATE_ENDPOINTS, EVENT_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";
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
- // useCopilotFeedback - React hook for use copilot feedback.
3105
- export { useCopilotFeedback } from "./features/copilot/index.js";
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, PAYMENT_ENDPOINTS, COPILOT_ENDPOINTS, CORPORATE_ENDPOINTS, EVENT_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";
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 { CopilotLogRepository, copilotLogRepository } from "../core";
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;