@tokenoftrust/storefront-runner 1.3.2

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 (310) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +42 -0
  3. package/apps/storefront/astro.config.mjs +66 -0
  4. package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
  5. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
  6. package/apps/storefront/env.d.ts +162 -0
  7. package/apps/storefront/migrations/0000_baseline.sql +54 -0
  8. package/apps/storefront/migrations/0001_products_fts.sql +9 -0
  9. package/apps/storefront/migrations/README.md +54 -0
  10. package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
  11. package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
  12. package/apps/storefront/migrations/meta/_journal.json +20 -0
  13. package/apps/storefront/package.json +42 -0
  14. package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
  15. package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
  16. package/apps/storefront/public/brand/northwind-og.svg +13 -0
  17. package/apps/storefront/public/favicon.svg +5 -0
  18. package/apps/storefront/public/js/dashboard-team.js +147 -0
  19. package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
  20. package/apps/storefront/public/shared/commerce-chrome.css +369 -0
  21. package/apps/storefront/src/components/Badge.astro +34 -0
  22. package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
  23. package/apps/storefront/src/components/CollectionCard.astro +56 -0
  24. package/apps/storefront/src/components/EmptyState.astro +55 -0
  25. package/apps/storefront/src/components/Img.astro +68 -0
  26. package/apps/storefront/src/components/ProductCard.astro +228 -0
  27. package/apps/storefront/src/components/Section.astro +39 -0
  28. package/apps/storefront/src/components/Seo.astro +70 -0
  29. package/apps/storefront/src/components/WidgetFrame.astro +34 -0
  30. package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +531 -0
  31. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
  32. package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
  33. package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
  34. package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
  35. package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
  36. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
  37. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -0
  38. package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
  39. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
  40. package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
  41. package/apps/storefront/src/components/chrome/NavDropdown.astro +201 -0
  42. package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
  43. package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
  44. package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
  45. package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
  46. package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
  47. package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
  48. package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -0
  49. package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
  50. package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
  51. package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
  52. package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
  53. package/apps/storefront/src/components/commerce/ReviewList.astro +54 -0
  54. package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
  55. package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
  59. package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
  60. package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
  61. package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
  62. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
  63. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
  64. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
  65. package/apps/storefront/src/components/home/Hero.astro +164 -0
  66. package/apps/storefront/src/components/islands/AgeGate.tsx +109 -0
  67. package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
  68. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
  69. package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
  70. package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
  71. package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
  72. package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
  73. package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
  74. package/apps/storefront/src/components/islands/VariantSelector.tsx +375 -0
  75. package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
  76. package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
  77. package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
  78. package/apps/storefront/src/components/marketing/Faq.astro +37 -0
  79. package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
  80. package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
  81. package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
  82. package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
  83. package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
  84. package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
  85. package/apps/storefront/src/components/marketing/Steps.astro +36 -0
  86. package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
  87. package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
  88. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +56 -0
  89. package/apps/storefront/src/components/membership/TierCards.astro +164 -0
  90. package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
  91. package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
  92. package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -0
  93. package/apps/storefront/src/components/plp/Pagination.astro +99 -0
  94. package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
  95. package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +95 -0
  96. package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
  97. package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
  98. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
  99. package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
  100. package/apps/storefront/src/config/devTenantSeed.ts +78 -0
  101. package/apps/storefront/src/config/devTenants.ts +16 -0
  102. package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
  103. package/apps/storefront/src/config/resolver.ts +102 -0
  104. package/apps/storefront/src/config/tenants.ts +87 -0
  105. package/apps/storefront/src/layouts/Layout.astro +368 -0
  106. package/apps/storefront/src/lib/admin/README.md +122 -0
  107. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
  108. package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
  109. package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
  110. package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
  111. package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
  112. package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
  113. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
  114. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
  115. package/apps/storefront/src/lib/admin/envelope.ts +10 -0
  116. package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
  117. package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
  118. package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
  119. package/apps/storefront/src/lib/admin/seed.ts +54 -0
  120. package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
  121. package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
  122. package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
  123. package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
  124. package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
  125. package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
  126. package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
  127. package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
  128. package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
  129. package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
  130. package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
  131. package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
  132. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
  133. package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
  134. package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
  135. package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
  136. package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
  137. package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
  138. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
  139. package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -0
  140. package/apps/storefront/src/lib/analytics/index.ts +47 -0
  141. package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
  142. package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
  143. package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
  144. package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
  145. package/apps/storefront/src/lib/auth/loginGate.ts +119 -0
  146. package/apps/storefront/src/lib/auth/route.ts +146 -0
  147. package/apps/storefront/src/lib/auth/session.ts +203 -0
  148. package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
  149. package/apps/storefront/src/lib/basePath.ts +41 -0
  150. package/apps/storefront/src/lib/cfImage.ts +55 -0
  151. package/apps/storefront/src/lib/chrome/model.ts +244 -0
  152. package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
  153. package/apps/storefront/src/lib/colors.ts +97 -0
  154. package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
  155. package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -0
  156. package/apps/storefront/src/lib/content-edit/client.ts +230 -0
  157. package/apps/storefront/src/lib/content-edit/index.ts +14 -0
  158. package/apps/storefront/src/lib/content-edit/route.ts +25 -0
  159. package/apps/storefront/src/lib/content-edit/types.ts +19 -0
  160. package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
  161. package/apps/storefront/src/lib/d1/catalog.ts +366 -0
  162. package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
  163. package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
  164. package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
  165. package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
  166. package/apps/storefront/src/lib/demoGate.ts +113 -0
  167. package/apps/storefront/src/lib/dev/activityIngestToken.ts +78 -0
  168. package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
  169. package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
  170. package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
  171. package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
  172. package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
  173. package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
  174. package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
  175. package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
  176. package/apps/storefront/src/lib/dev/storeStatus.ts +142 -0
  177. package/apps/storefront/src/lib/diag/redact.ts +20 -0
  178. package/apps/storefront/src/lib/edgeCache.ts +128 -0
  179. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
  180. package/apps/storefront/src/lib/env.ts +60 -0
  181. package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
  182. package/apps/storefront/src/lib/format.ts +38 -0
  183. package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
  184. package/apps/storefront/src/lib/imageAttrs.ts +55 -0
  185. package/apps/storefront/src/lib/jsonld.ts +166 -0
  186. package/apps/storefront/src/lib/maintenance.ts +71 -0
  187. package/apps/storefront/src/lib/membership/model.ts +97 -0
  188. package/apps/storefront/src/lib/memoryKv.ts +32 -0
  189. package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
  190. package/apps/storefront/src/lib/placeholder.ts +99 -0
  191. package/apps/storefront/src/lib/previewReload.ts +125 -0
  192. package/apps/storefront/src/lib/priceBuckets.ts +33 -0
  193. package/apps/storefront/src/lib/rawChrome.ts +505 -0
  194. package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -0
  195. package/apps/storefront/src/lib/search/index.ts +74 -0
  196. package/apps/storefront/src/lib/sections.ts +129 -0
  197. package/apps/storefront/src/lib/securityHeaders.ts +31 -0
  198. package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
  199. package/apps/storefront/src/lib/storyblok/provider.ts +607 -0
  200. package/apps/storefront/src/lib/subscription/model.ts +68 -0
  201. package/apps/storefront/src/lib/tenantRouting.ts +91 -0
  202. package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
  203. package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
  204. package/apps/storefront/src/lib/tot/query.ts +249 -0
  205. package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
  206. package/apps/storefront/src/lib/url.ts +142 -0
  207. package/apps/storefront/src/lib/wishlist.ts +63 -0
  208. package/apps/storefront/src/middleware/index.ts +455 -0
  209. package/apps/storefront/src/pages/404.astro +61 -0
  210. package/apps/storefront/src/pages/[...slug].astro +90 -0
  211. package/apps/storefront/src/pages/admin.astro +1675 -0
  212. package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
  213. package/apps/storefront/src/pages/api/admin/orders.ts +71 -0
  214. package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
  215. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
  216. package/apps/storefront/src/pages/api/auth/send.ts +85 -0
  217. package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
  218. package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
  219. package/apps/storefront/src/pages/api/checkout-style.css.ts +43 -0
  220. package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
  221. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -0
  222. package/apps/storefront/src/pages/api/csp-report.ts +25 -0
  223. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
  224. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
  225. package/apps/storefront/src/pages/api/dev/activity.ts +124 -0
  226. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
  227. package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
  228. package/apps/storefront/src/pages/api/dev/file.ts +57 -0
  229. package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
  230. package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
  231. package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
  232. package/apps/storefront/src/pages/api/dev/write.ts +116 -0
  233. package/apps/storefront/src/pages/api/newsletter.ts +104 -0
  234. package/apps/storefront/src/pages/api/request-access.ts +172 -0
  235. package/apps/storefront/src/pages/auth/login.astro +149 -0
  236. package/apps/storefront/src/pages/auth/magic.astro +105 -0
  237. package/apps/storefront/src/pages/capabilities.astro +292 -0
  238. package/apps/storefront/src/pages/collections/[handle].astro +177 -0
  239. package/apps/storefront/src/pages/collections/index.astro +59 -0
  240. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
  241. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
  242. package/apps/storefront/src/pages/dashboard/index.astro +160 -0
  243. package/apps/storefront/src/pages/index.astro +290 -0
  244. package/apps/storefront/src/pages/llms.txt.ts +92 -0
  245. package/apps/storefront/src/pages/products/[handle].astro +402 -0
  246. package/apps/storefront/src/pages/robots.txt.ts +24 -0
  247. package/apps/storefront/src/pages/saved.astro +35 -0
  248. package/apps/storefront/src/pages/search.astro +150 -0
  249. package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
  250. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +1280 -0
  251. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
  252. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
  253. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
  254. package/apps/storefront/src/pages/style-guide/index.astro +98 -0
  255. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
  256. package/apps/storefront/src/styles/global.css +292 -0
  257. package/apps/storefront/src/themes/reference.ts +68 -0
  258. package/apps/storefront/src/themes/schema.ts +111 -0
  259. package/apps/storefront/src/themes/tenants/index.ts +51 -0
  260. package/apps/storefront/tsconfig.json +16 -0
  261. package/apps/storefront/vitest.config.ts +22 -0
  262. package/package.json +26 -0
  263. package/packages/public-runtime/LICENSE +58 -0
  264. package/packages/public-runtime/package.json +22 -0
  265. package/packages/public-runtime/src/binary-path.ts +68 -0
  266. package/packages/public-runtime/src/capabilities.ts +89 -0
  267. package/packages/public-runtime/src/catalog-api.ts +81 -0
  268. package/packages/public-runtime/src/catalog-d1.ts +192 -0
  269. package/packages/public-runtime/src/checkout-style-publish.ts +200 -0
  270. package/packages/public-runtime/src/checkout-style.ts +375 -0
  271. package/packages/public-runtime/src/csp.ts +109 -0
  272. package/packages/public-runtime/src/customization-preview.ts +197 -0
  273. package/packages/public-runtime/src/customization-reconcile.ts +63 -0
  274. package/packages/public-runtime/src/customization-runtime.ts +157 -0
  275. package/packages/public-runtime/src/customization-scripts.ts +64 -0
  276. package/packages/public-runtime/src/customization-versioning.ts +95 -0
  277. package/packages/public-runtime/src/foxy-orders.ts +136 -0
  278. package/packages/public-runtime/src/foxy.ts +196 -0
  279. package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
  280. package/packages/public-runtime/src/index.ts +34 -0
  281. package/packages/public-runtime/src/product.ts +159 -0
  282. package/packages/public-runtime/src/search.ts +45 -0
  283. package/packages/public-runtime/src/tenant-assets.ts +444 -0
  284. package/packages/public-runtime/src/tenant.ts +384 -0
  285. package/packages/public-runtime/tsconfig.json +8 -0
  286. package/packages/public-runtime/vitest.config.ts +8 -0
  287. package/pnpm-workspace.yaml +7 -0
  288. package/scripts/build/copy-tenant-assets.mjs +69 -0
  289. package/scripts/dev/ai-edit.mjs +183 -0
  290. package/scripts/dev/checkout-watch.mjs +116 -0
  291. package/scripts/dev/file-browser.mjs +159 -0
  292. package/scripts/dev/file-writer.mjs +141 -0
  293. package/scripts/dev/git-status.mjs +171 -0
  294. package/scripts/dev/locate-handler.mjs +163 -0
  295. package/scripts/dev/locate.mjs +112 -0
  296. package/scripts/dev/port-check.mjs +48 -0
  297. package/scripts/dev/safe-path.mjs +109 -0
  298. package/scripts/dev/shot.mjs +46 -0
  299. package/scripts/dev/tenant-source-root.mjs +76 -0
  300. package/scripts/dev/unified-diff.mjs +81 -0
  301. package/scripts/tot-dev.mjs +376 -0
  302. package/tenants/README.md +14 -0
  303. package/tenants/home/content/chrome.json +7 -0
  304. package/tenants/home/content/home.html +58 -0
  305. package/tenants/home/content/home.json +11 -0
  306. package/tenants/home/public/js/storefront.js +210 -0
  307. package/tenants/home/public/logo-wordmark.png +0 -0
  308. package/tenants/home/public/pages/storefront.css +212 -0
  309. package/tenants/home/theme.json +6 -0
  310. package/tsconfig.base.json +20 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Server-side client for tot-foxycart's order READ API (card 8585 / U2).
3
+ *
4
+ * Calls `GET /commerce/orders[/:orderId]` with the tenant's per-tenant bearer key
5
+ * (the ToT `partnerApi.apiKey`, held as a Worker secret — NEVER shipped to the
6
+ * browser). Returns the frozen DTO shapes from `@tot/public-runtime`. Read-only
7
+ * (scope-locks); no write paths.
8
+ *
9
+ * The fetch runs on the Cloudflare Worker (server), so the call is same-origin →
10
+ * cross-service server-to-server; it is NOT browser-originated and therefore not
11
+ * CSP-governed — the U2 CSP connect-src entry is intentionally skipped (board
12
+ * decision `u2-csp-server-side-fetch`).
13
+ *
14
+ * `fetchImpl` is injectable so the client is unit-testable offline.
15
+ */
16
+ import type {
17
+ FoxyOrderDetail,
18
+ FoxyOrderDetailResponse,
19
+ FoxyOrderListFilters,
20
+ FoxyOrderListResponse,
21
+ } from "@tot/public-runtime";
22
+ import type { CustomizationEnv } from "@tot/public-runtime";
23
+
24
+ /** Fetch shape we depend on — the platform `fetch`, injectable for tests. */
25
+ export type FetchLike = (
26
+ input: string,
27
+ init?: { method?: string; headers?: Record<string, string> },
28
+ ) => Promise<{ ok: boolean; status: number; text: () => Promise<string> }>;
29
+
30
+ /** Stable error codes surfaced by the order API (see contract §9). */
31
+ export type OrderApiErrorCode =
32
+ | "missing_app_domain"
33
+ | "missing_bearer"
34
+ | "unknown_tenant"
35
+ | "not_provisioned"
36
+ | "invalid_partner_key"
37
+ | "order_not_found"
38
+ | "bad_gateway"
39
+ | "unexpected";
40
+
41
+ /** A typed failure from the order API — carries an HTTP status + a stable code. */
42
+ export class OrderApiError extends Error {
43
+ constructor(
44
+ readonly code: OrderApiErrorCode,
45
+ readonly status: number,
46
+ message?: string,
47
+ ) {
48
+ super(message ?? code);
49
+ this.name = "OrderApiError";
50
+ }
51
+ }
52
+
53
+ export interface FoxyOrderClient {
54
+ listOrders(filters?: FoxyOrderListFilters): Promise<FoxyOrderListResponse>;
55
+ getOrder(orderId: string): Promise<FoxyOrderDetail>;
56
+ }
57
+
58
+ export interface FoxyOrderClientConfig {
59
+ /** tot-foxycart proxy base, e.g. `https://foxycart-test.tokenoftrust.com`. */
60
+ baseUrl: string;
61
+ /** The tenant, sent as `?appDomain=` (e.g. `sample-store.example`). */
62
+ appDomain: string;
63
+ /** The tenant's `partnerApi.apiKey` — the `Authorization: Bearer` token. */
64
+ bearer: string;
65
+ fetchImpl?: FetchLike;
66
+ }
67
+
68
+ /**
69
+ * Resolve the tot-foxycart proxy base URL for an environment (contract §2).
70
+ * qa runs `NODE_ENV=test` → the `foxycart-test` proxy; `live` → prod. Kept
71
+ * private so it does not collide with U11b's exported `foxyProxyBaseUrl` before
72
+ * the units fold; the integrator can dedup into one shared export.
73
+ */
74
+ export function orderApiBaseUrl(env: CustomizationEnv): string {
75
+ return env === "live"
76
+ ? "https://foxycart.tokenoftrust.com"
77
+ : "https://foxycart-test.tokenoftrust.com";
78
+ }
79
+
80
+ /** Map an HTTP status + endpoint body to a stable OrderApiError code. */
81
+ function toError(status: number, body: string): OrderApiError {
82
+ const detail = body.slice(0, 300);
83
+ if (status === 400) return new OrderApiError("missing_app_domain", 400, detail);
84
+ if (status === 401) {
85
+ // The endpoint distinguishes these in the body; pass it through for logging.
86
+ const code: OrderApiErrorCode = /not_provisioned/.test(body)
87
+ ? "not_provisioned"
88
+ : /unknown_tenant/.test(body)
89
+ ? "unknown_tenant"
90
+ : /missing_bearer/.test(body)
91
+ ? "missing_bearer"
92
+ : "invalid_partner_key";
93
+ return new OrderApiError(code, 401, detail);
94
+ }
95
+ if (status === 404) return new OrderApiError("order_not_found", 404, detail);
96
+ if (status >= 500) return new OrderApiError("bad_gateway", 502, detail);
97
+ return new OrderApiError("unexpected", status, detail);
98
+ }
99
+
100
+ /** Build the list querystring: appDomain (required) + any provided filters. */
101
+ function listQuery(appDomain: string, filters?: FoxyOrderListFilters): string {
102
+ const p = new URLSearchParams({ appDomain });
103
+ if (filters) {
104
+ if (filters.queue) p.set("queue", filters.queue);
105
+ if (filters.status) p.set("status", filters.status);
106
+ if (filters.from) p.set("from", filters.from);
107
+ if (filters.to) p.set("to", filters.to);
108
+ if (filters.limit != null) p.set("limit", String(filters.limit));
109
+ if (filters.cursor) p.set("cursor", filters.cursor);
110
+ }
111
+ return p.toString();
112
+ }
113
+
114
+ /** Create a per-request order client bound to one tenant + env. */
115
+ export function createFoxyOrderClient(
116
+ config: FoxyOrderClientConfig,
117
+ ): FoxyOrderClient {
118
+ const doFetch = config.fetchImpl ?? (globalThis.fetch as unknown as FetchLike);
119
+ const base = config.baseUrl.replace(/\/+$/, "");
120
+ const headers = { authorization: `Bearer ${config.bearer}` };
121
+
122
+ async function getJson<T>(url: string): Promise<T> {
123
+ const res = await doFetch(url, { method: "GET", headers });
124
+ const body = await res.text();
125
+ if (!res.ok) throw toError(res.status, body);
126
+ try {
127
+ return JSON.parse(body) as T;
128
+ } catch {
129
+ throw new OrderApiError("unexpected", res.status, "malformed JSON");
130
+ }
131
+ }
132
+
133
+ return {
134
+ async listOrders(filters) {
135
+ const qs = listQuery(config.appDomain, filters);
136
+ return getJson<FoxyOrderListResponse>(`${base}/commerce/orders?${qs}`);
137
+ },
138
+ async getOrder(orderId) {
139
+ const qs = new URLSearchParams({ appDomain: config.appDomain }).toString();
140
+ const { order } = await getJson<FoxyOrderDetailResponse>(
141
+ `${base}/commerce/orders/${encodeURIComponent(orderId)}?${qs}`,
142
+ );
143
+ return order;
144
+ },
145
+ };
146
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Deterministic seeding for mock admin data. All mock services derive their
3
+ * randomness from `seededRandom(key)`, where `key` includes `targetTenant` — so
4
+ * the same tenant always produces the same rows/values (data-view-contracts
5
+ * acceptance: "same tenant ⇒ same data"), with no reliance on wall-clock time.
6
+ */
7
+
8
+ function hashString(input: string): number {
9
+ let h = 2166136261;
10
+ for (let i = 0; i < input.length; i += 1) {
11
+ h ^= input.charCodeAt(i);
12
+ h = Math.imul(h, 16777619);
13
+ }
14
+ return h >>> 0;
15
+ }
16
+
17
+ /** mulberry32 PRNG — small, fast, deterministic for a given integer seed. */
18
+ function mulberry32(seed: number): () => number {
19
+ let state = seed | 0;
20
+ return () => {
21
+ state = (state + 0x6d2b79f5) | 0;
22
+ let t = Math.imul(state ^ (state >>> 15), 1 | state);
23
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
24
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
25
+ };
26
+ }
27
+
28
+ /** A `() => number` in `[0, 1)` that is stable for a given string key. */
29
+ export function seededRandom(key: string): () => number {
30
+ return mulberry32(hashString(key));
31
+ }
32
+
33
+ export function pick<T>(rng: () => number, items: readonly T[]): T {
34
+ const value = items[Math.floor(rng() * items.length) % items.length];
35
+ if (value === undefined) throw new Error("pick() called with an empty list");
36
+ return value;
37
+ }
38
+
39
+ export function randomInt(rng: () => number, min: number, max: number): number {
40
+ return min + Math.floor(rng() * (max - min + 1));
41
+ }
42
+
43
+ export function randomBool(rng: () => number, probabilityTrue = 0.5): boolean {
44
+ return rng() < probabilityTrue;
45
+ }
46
+
47
+ /** Deterministic ISO timestamp: a fixed anchor plus a seed-derived offset. */
48
+ export function offsetIso(anchorIso: string, offsetMinutes: number): string {
49
+ const anchor = new Date(anchorIso).getTime();
50
+ return new Date(anchor + offsetMinutes * 60_000).toISOString();
51
+ }
52
+
53
+ /** Anchor instant every mock service builds relative timestamps from — never `Date.now()`. */
54
+ export const MOCK_ANCHOR_ISO = "2026-07-14T16:00:00.000Z";
@@ -0,0 +1,59 @@
1
+ import type { AdminTenantEnvelope } from "../../envelope";
2
+ import { memoizeByEnvelope } from "../../memoizeByEnvelope";
3
+ import { MOCK_ANCHOR_ISO, offsetIso, pick, randomInt, seededRandom } from "../../seed";
4
+ import { AI_WORKFLOW_NAMES, OPERATIONAL_WORKFLOW_KEYS, type AiWorkflowsViewModel, type ApprovalMode, type WorkflowRecord, type WorkflowStatus } from "../../viewmodels/aiWorkflows";
5
+
6
+ const OWNERS = ["Ops", "Trust & Safety", "Growth", "Fulfillment"];
7
+ const APPROVAL_MODES: ApprovalMode[] = ["draft_only", "approval_required", "guarded_automation"];
8
+ // Operational workflows are human-acted recommendation flows, never auto-actions,
9
+ // so they can never be assigned guarded_automation (invariant in ai/approvalMode.ts,
10
+ // ai/operationalRuns.ts, and OrdersWorkspace.astro).
11
+ const OPERATIONAL_APPROVAL_MODES: ApprovalMode[] = ["draft_only", "approval_required"];
12
+ const STATUSES: WorkflowStatus[] = ["scheduled", "needs_review", "planned"];
13
+ const SYSTEM_OPERATOR = "system:ai-workflows";
14
+
15
+ function slugify(name: string): string {
16
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
17
+ }
18
+
19
+ function computeAiWorkflowsViewModel(envelope: AdminTenantEnvelope): AiWorkflowsViewModel {
20
+ const { targetTenant } = envelope;
21
+ const rng = seededRandom(`ai-workflows:${targetTenant}`);
22
+
23
+ const workflows: WorkflowRecord[] = AI_WORKFLOW_NAMES.map((name) => {
24
+ const key = slugify(name);
25
+ const lastRunAt = offsetIso(MOCK_ANCHOR_ISO, -randomInt(rng, 60, 4000));
26
+ const owner = pick(rng, OWNERS);
27
+ const isOperational = (OPERATIONAL_WORKFLOW_KEYS as readonly string[]).includes(key);
28
+ const approvalMode = pick(rng, isOperational ? OPERATIONAL_APPROVAL_MODES : APPROVAL_MODES);
29
+ const evidenceLinks = [`evidence:${targetTenant}:workflow:${key}-latest`];
30
+ const scaffoldedEvent = { operator: SYSTEM_OPERATOR, targetTenant, action: "workflow.scaffolded", at: lastRunAt };
31
+
32
+ return {
33
+ key,
34
+ name,
35
+ cadence: pick(rng, ["Hourly", "Daily", "Weekly"]),
36
+ triggerType: pick(rng, ["Scheduled", "Event: new order", "Event: catalog change"]),
37
+ owner,
38
+ status: pick(rng, STATUSES),
39
+ approvalMode,
40
+ lastRunAt,
41
+ nextRunAt: offsetIso(lastRunAt, randomInt(rng, 60, 1440)),
42
+ sourceData: ["orders", "products"].slice(0, randomInt(rng, 1, 2)),
43
+ evidenceLinks,
44
+ suggestedAction: {
45
+ summary: `Review ${name.toLowerCase()} findings for ${targetTenant}.`,
46
+ confidence: Math.round((0.55 + rng() * 0.4) * 100) / 100,
47
+ evidenceRefs: evidenceLinks,
48
+ owner,
49
+ approvalMode,
50
+ auditTrail: [scaffoldedEvent],
51
+ },
52
+ auditTrail: [scaffoldedEvent],
53
+ };
54
+ });
55
+
56
+ return { targetTenant, workflows };
57
+ }
58
+
59
+ export const getAiWorkflowsViewModel = memoizeByEnvelope(computeAiWorkflowsViewModel);
@@ -0,0 +1,86 @@
1
+ import type { AdminTenantEnvelope } from "../../envelope";
2
+ import { memoizeByEnvelope } from "../../memoizeByEnvelope";
3
+ import { MOCK_ANCHOR_ISO, offsetIso, pick, randomBool, randomInt, seededRandom } from "../../seed";
4
+ import type {
5
+ BlockedReason,
6
+ CustomerBlockedQueueRow,
7
+ CustomerDetail,
8
+ CustomerRow,
9
+ CustomersViewModel,
10
+ VerificationState,
11
+ } from "../../viewmodels/customers";
12
+
13
+ // Must stay index-aligned with orders.ts's CUSTOMER_NAMES: a customer's id is
14
+ // `cust-<tenant>-<position here>`, and orders.ts derives each order's customerId
15
+ // from the same position, so the evidence graph can join order→customer by id.
16
+ const NAMES = [
17
+ "Riley Harper",
18
+ "Jordan Lee",
19
+ "Casey Nguyen",
20
+ "Morgan Blake",
21
+ "Sam Ortiz",
22
+ "Avery Chen",
23
+ "Quinn Sullivan",
24
+ "Reese Patel",
25
+ ];
26
+
27
+ const BLOCKED_REASONS: { blocker: BlockedReason; detail: string; action: string }[] = [
28
+ { blocker: "verification", detail: "Age/identity verification is pending re-check.", action: "Resend verification" },
29
+ { blocker: "destination", detail: "Destination state restricts this product category.", action: "Review destination policy" },
30
+ { blocker: "address", detail: "Shipping address failed carrier validation.", action: "Confirm address" },
31
+ { blocker: "support", detail: "Open support ticket is blocking fulfillment.", action: "Resolve ticket" },
32
+ ];
33
+
34
+ function computeCustomersViewModel(envelope: AdminTenantEnvelope): CustomersViewModel {
35
+ const { targetTenant } = envelope;
36
+ const rng = seededRandom(`customers:${targetTenant}`);
37
+
38
+ const directory: CustomerRow[] = NAMES.map((name, index) => {
39
+ const orderCount = randomInt(rng, 1, 12);
40
+ const verificationState: VerificationState = index === 0 ? "pending" : randomBool(rng, 0.85) ? "verified" : "blocked";
41
+ return {
42
+ id: `cust-${targetTenant}-${index}`,
43
+ name,
44
+ email: `${name.toLowerCase().replace(/\s+/g, ".")}@example.com`,
45
+ orderCount,
46
+ lifetimeValue: orderCount * randomInt(rng, 45, 220),
47
+ lastOrderAt: offsetIso(MOCK_ANCHOR_ISO, -randomInt(rng, 60, 90_000)),
48
+ segment: orderCount > 6 ? "vip" : orderCount > 1 ? "returning" : "new",
49
+ supportFlags: randomBool(rng, 0.2) ? ["Open ticket"] : [],
50
+ verificationState,
51
+ };
52
+ });
53
+
54
+ const detailsById: Record<string, CustomerDetail> = {};
55
+ for (const row of directory) {
56
+ detailsById[row.id] = {
57
+ ...row,
58
+ orders: Array.from({ length: Math.min(row.orderCount, 4) }, (_, i) => ({
59
+ orderId: `#${2_200_000_000 + Math.floor(rng() * 90_000_000)}`,
60
+ placedAt: offsetIso(MOCK_ANCHOR_ISO, -randomInt(rng, 60, 90_000) - i * 1440),
61
+ total: randomInt(rng, 45, 380),
62
+ })),
63
+ activity: [{ title: "Account created", detail: "Customer record created from first checkout.", at: row.lastOrderAt ?? MOCK_ANCHOR_ISO }],
64
+ notes: [],
65
+ };
66
+ }
67
+
68
+ const valuableButBlocked: CustomerBlockedQueueRow[] = directory
69
+ .filter((row) => row.verificationState === "blocked" || row.verificationState === "pending")
70
+ .map((row) => {
71
+ const reason = pick(rng, BLOCKED_REASONS);
72
+ return {
73
+ customerId: row.id,
74
+ name: row.name,
75
+ recoverableValue: row.lifetimeValue,
76
+ blocker: reason.blocker,
77
+ blockerDetail: reason.detail,
78
+ action: reason.action,
79
+ };
80
+ })
81
+ .sort((a, b) => b.recoverableValue - a.recoverableValue);
82
+
83
+ return { targetTenant, directory, detailsById, valuableButBlocked };
84
+ }
85
+
86
+ export const getCustomersViewModel = memoizeByEnvelope(computeCustomersViewModel);
@@ -0,0 +1,99 @@
1
+ import type { AdminTenantEnvelope } from "../../envelope";
2
+ import { memoizeByEnvelope } from "../../memoizeByEnvelope";
3
+ import type { EvidenceReference } from "../../evidence/evidenceReference";
4
+ import { getCustomersViewModel } from "./customers";
5
+ import { getOrdersViewModel } from "./orders";
6
+ import { getProductsViewModel } from "./products";
7
+
8
+ /**
9
+ * Deterministic evidence graph, built by connecting the domains already
10
+ * shipped in data-view-contracts: each order's evidence ref links to its
11
+ * customer and to the product(s) mentioned in its line items — the "graph"
12
+ * edges the baseline's Evidence Graph section asks for, without inventing a
13
+ * fifth mock dataset to disagree with the other four.
14
+ *
15
+ * Order→customer and order→product edges join on the shared ids
16
+ * (`OrderRow.customerId` / `OrderRow.productId`), which orders.ts derives from
17
+ * the same name-pool positions customers.ts/products.ts use for their ids — a
18
+ * stable id join, not a fragile display-name string match.
19
+ */
20
+ function computeEvidenceViewModel(envelope: AdminTenantEnvelope): EvidenceReference[] {
21
+ const { targetTenant } = envelope;
22
+ const orders = getOrdersViewModel(envelope);
23
+ const customers = getCustomersViewModel(envelope);
24
+ const products = getProductsViewModel(envelope);
25
+
26
+ const customerIds = new Set(customers.directory.map((c) => c.id));
27
+ const productIds = new Set(products.rows.map((p) => p.id));
28
+
29
+ const orderRef = (orderId: string) => `evidence:${targetTenant}:order:${orderId}`;
30
+ const customerRef = (customerId: string) => `evidence:${targetTenant}:customer:${customerId}`;
31
+ const productRef = (productId: string) => `evidence:${targetTenant}:product:${productId}`;
32
+
33
+ const refs: EvidenceReference[] = [];
34
+
35
+ for (const row of orders.rows) {
36
+ const detail = orders.detailsById[row.id];
37
+ const related: string[] = [];
38
+ if (customerIds.has(row.customerId)) related.push(customerRef(row.customerId));
39
+ if (productIds.has(row.productId)) related.push(productRef(row.productId));
40
+
41
+ refs.push({
42
+ id: orderRef(row.id),
43
+ kind: "order",
44
+ recordId: row.id,
45
+ label: `Order ${row.id}`,
46
+ at: row.placedAt,
47
+ relatedRefs: related,
48
+ });
49
+
50
+ if (detail?.evidenceTimeline.length) {
51
+ refs.push({
52
+ id: `evidence:${targetTenant}:verification:${row.id}`,
53
+ kind: row.blocker === "Age verification hold" ? "verification_event" : "policy_acceptance",
54
+ recordId: row.id,
55
+ label: `Checkout assurance for ${row.id}`,
56
+ at: row.placedAt,
57
+ relatedRefs: [orderRef(row.id)],
58
+ });
59
+ }
60
+ }
61
+
62
+ for (const customer of customers.directory) {
63
+ refs.push({
64
+ id: customerRef(customer.id),
65
+ kind: "customer",
66
+ recordId: customer.id,
67
+ label: customer.name,
68
+ at: customer.lastOrderAt ?? new Date(0).toISOString(),
69
+ relatedRefs: [],
70
+ });
71
+ }
72
+
73
+ for (const product of products.rows) {
74
+ const detail = products.detailsById[product.id];
75
+ const at = detail?.activity[0]?.at ?? new Date(0).toISOString();
76
+ refs.push({
77
+ id: productRef(product.id),
78
+ kind: "product",
79
+ recordId: product.id,
80
+ label: product.name,
81
+ at,
82
+ relatedRefs: [],
83
+ });
84
+ if (product.missingExciseCount > 0) {
85
+ refs.push({
86
+ id: `evidence:${targetTenant}:tax_excise:${product.id}`,
87
+ kind: "tax_excise",
88
+ recordId: product.id,
89
+ label: `Excise metadata gaps — ${product.name}`,
90
+ at,
91
+ relatedRefs: [productRef(product.id)],
92
+ });
93
+ }
94
+ }
95
+
96
+ return refs;
97
+ }
98
+
99
+ export const getEvidenceViewModel = memoizeByEnvelope(computeEvidenceViewModel);
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Mock service for the admin "Cart & Checkout appearance" section. Resolves the
3
+ * TARGET tenant's theme (the style-guide) and any saved overrides into the flat
4
+ * field list the panel renders — so each row shows its theme default, current
5
+ * value, provenance (theme vs override), and a reset affordance.
6
+ *
7
+ * The target tenant's config is INJECTED by the panel boundary (resolved through
8
+ * the `TenantResolver` seam via `config/adminTargetTenant`), never looked up from
9
+ * the static tenant map here: `lib/**` stays behind the `tenancy-resolver-fence`
10
+ * so a single-tenant extraction stays a drop-in. A null tenant (or one with no
11
+ * FoxyCart store) renders the panel's inert "no store" state.
12
+ *
13
+ * Overrides are read from a mock store here (no live persistence yet — the save
14
+ * hop is the tot-foxycart `/theme/foxy` PUT/refresh + Foxy color PATCH seam). One
15
+ * example override is seeded so the panel demonstrates the theme/override/reset
16
+ * states without a backend.
17
+ */
18
+ import { resolveFoxyCommerce } from "@tot/public-runtime";
19
+ import type { TenantConfig } from "@tot/public-runtime";
20
+ import { referenceTheme } from "@/themes/reference";
21
+ import { mergeTheme } from "@/themes/schema";
22
+ import type { AdminTenantEnvelope } from "../../envelope";
23
+ import {
24
+ buildFoxySettingsFields,
25
+ deriveFoxyCartSettings,
26
+ type FoxyCartSettingsOverrides,
27
+ type FoxySettingsField,
28
+ } from "@/lib/commerce/foxyCartSettings";
29
+
30
+ export interface FoxyCartSettingsViewModel {
31
+ targetTenant: string;
32
+ /** True when the tenant has a FoxyCart store (else the section is inert). */
33
+ hasStore: boolean;
34
+ fields: FoxySettingsField[];
35
+ overrideCount: number;
36
+ }
37
+
38
+ /**
39
+ * Mock override store. Keyed by tenant id. Seeded with one override for the
40
+ * generic sample tenant so the panel shows a mixed theme/override state out of
41
+ * the box. Real overrides would come from the tenant's saved cart settings.
42
+ */
43
+ const MOCK_OVERRIDES: Record<string, FoxyCartSettingsOverrides> = {
44
+ "sample-store.example": {
45
+ // Example: the merchant pinned a custom tertiary accent, overriding the
46
+ // theme's --color-sale default — the panel renders this row as "Overridden"
47
+ // with a Reset control.
48
+ colors: { tertiary: "#e11d48" },
49
+ },
50
+ };
51
+
52
+ /**
53
+ * Build the FoxyCart cart-appearance view model from the resolved TARGET tenant
54
+ * config. `tenant` is the target's resolved `TenantConfig` (or null when no
55
+ * tenant claims the id) — resolved by the caller through the resolver seam.
56
+ */
57
+ export function getFoxyCartSettingsViewModel(
58
+ envelope: AdminTenantEnvelope,
59
+ tenant: TenantConfig | null,
60
+ ): FoxyCartSettingsViewModel {
61
+ const { targetTenant } = envelope;
62
+ const foxy = tenant?.commerce?.foxy ?? null;
63
+
64
+ if (!tenant || !foxy) {
65
+ return { targetTenant, hasStore: false, fields: [], overrideCount: 0 };
66
+ }
67
+
68
+ const theme = mergeTheme(referenceTheme, tenant.theme_tokens);
69
+ const commerce = resolveFoxyCommerce(foxy);
70
+ const defaults = deriveFoxyCartSettings(theme, commerce, {
71
+ originBase: `https://${targetTenant}`,
72
+ });
73
+ const overrides = MOCK_OVERRIDES[targetTenant] ?? {};
74
+ const fields = buildFoxySettingsFields(defaults, overrides);
75
+ const overrideCount = fields.filter((f) => f.source === "override").length;
76
+
77
+ return { targetTenant, hasStore: true, fields, overrideCount };
78
+ }
@@ -0,0 +1,62 @@
1
+ import type { AdminTenantEnvelope } from "../../envelope";
2
+ import { memoizeByEnvelope } from "../../memoizeByEnvelope";
3
+ import { pick, randomBool, randomInt, seededRandom } from "../../seed";
4
+ import type {
5
+ FulfillmentAssistDraft,
6
+ FulfillmentQueueKey,
7
+ FulfillmentQueueSummary,
8
+ FulfillmentViewModel,
9
+ ShipmentRow,
10
+ } from "../../viewmodels/fulfillment";
11
+
12
+ const QUEUE_DEFS: { key: FulfillmentQueueKey; label: string }[] = [
13
+ { key: "ready_to_ship", label: "Ready to Ship" },
14
+ { key: "holds", label: "Holds" },
15
+ { key: "picked", label: "Picked" },
16
+ { key: "packed", label: "Packed" },
17
+ { key: "label_needed", label: "Label needed" },
18
+ { key: "shipped", label: "Shipped" },
19
+ { key: "exceptions", label: "Exceptions" },
20
+ ];
21
+
22
+ const CARRIERS = ["USPS", "UPS", "FedEx"];
23
+ const DESTINATIONS = ["Minneapolis, MN", "Portland, OR", "Austin, TX", "Columbus, OH", "Denver, CO"];
24
+
25
+ function computeFulfillmentViewModel(envelope: AdminTenantEnvelope): FulfillmentViewModel {
26
+ const { targetTenant } = envelope;
27
+ const rng = seededRandom(`fulfillment:${targetTenant}`);
28
+
29
+ const shipments: ShipmentRow[] = QUEUE_DEFS.flatMap((def) =>
30
+ Array.from({ length: randomInt(rng, 1, 3) }, () => {
31
+ const labelLocked = def.key === "label_needed" || (def.key === "holds" && randomBool(rng, 0.6));
32
+ return {
33
+ orderId: `#${2_200_000_000 + Math.floor(rng() * 90_000_000)}`,
34
+ queue: def.key,
35
+ carrier: pick(rng, CARRIERS),
36
+ destination: pick(rng, DESTINATIONS),
37
+ slaAgeHours: randomInt(rng, 1, 48),
38
+ labelLocked,
39
+ labelLockReason: labelLocked ? pick(rng, ["Shipping restriction on destination", "Payment not yet captured", "Age verification pending"]) : null,
40
+ carrierReady: !labelLocked && randomBool(rng, 0.9),
41
+ destinationReady: randomBool(rng, 0.85),
42
+ };
43
+ }),
44
+ );
45
+
46
+ const queues: FulfillmentQueueSummary[] = QUEUE_DEFS.map((def) => ({
47
+ key: def.key,
48
+ label: def.label,
49
+ count: shipments.filter((s) => s.queue === def.key).length,
50
+ }));
51
+
52
+ const exceptionShipments = shipments.filter((s) => s.queue === "exceptions" || s.labelLocked);
53
+ const assistDrafts: FulfillmentAssistDraft[] = exceptionShipments.map((s) => ({
54
+ orderId: s.orderId,
55
+ kind: "blocker_explanation",
56
+ summary: s.labelLockReason ? `${s.labelLockReason} — resolve to unlock the label for ${s.orderId}.` : `Exception queue: ${s.orderId} needs manual review.`,
57
+ }));
58
+
59
+ return { targetTenant, queues, shipments, assistDrafts };
60
+ }
61
+
62
+ export const getFulfillmentViewModel = memoizeByEnvelope(computeFulfillmentViewModel);
@@ -0,0 +1,12 @@
1
+ export { getOrdersViewModel } from "./orders";
2
+ export { getProductsViewModel } from "./products";
3
+ export { getCustomersViewModel } from "./customers";
4
+ export { getFulfillmentViewModel } from "./fulfillment";
5
+ export { getReportingViewModel } from "./reporting";
6
+ export { getSettingsViewModel } from "./settings";
7
+ export { getFoxyCartSettingsViewModel } from "./foxyCartSettings";
8
+ export type { FoxyCartSettingsViewModel } from "./foxyCartSettings";
9
+ export { getAiWorkflowsViewModel } from "./aiWorkflows";
10
+ export { getEvidenceViewModel } from "./evidence";
11
+ export { SEEDED_TENANTS, tenantProfile } from "./tenants";
12
+ export type { SeededTenantProfile, MockVertical } from "./tenants";