@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,64 @@
1
+ /**
2
+ * Settings view model (baseline §Settings) — admin policy, AI guardrails,
3
+ * regulated storefront policy, and audited changes. `ApprovalMode` and
4
+ * `SettingsAuditEvent` reuse the canonical substrate types from
5
+ * `lib/admin/ai` (ai-evidence-substrate) now that both domains live in the
6
+ * same lib/admin/** area.
7
+ */
8
+
9
+ import type { CheckoutStyle } from "@tot/public-runtime";
10
+ import type { ApprovalMode } from "../ai/approvalMode";
11
+ import type { AuditEvent } from "../ai/audit";
12
+
13
+ export type { ApprovalMode };
14
+ export type { CheckoutStyle };
15
+
16
+ export type SettingsSectionKey =
17
+ | "store"
18
+ | "checkout"
19
+ | "cart_appearance"
20
+ | "payments"
21
+ | "shipping"
22
+ | "taxes"
23
+ | "products"
24
+ | "customers"
25
+ | "notifications"
26
+ | "users_access"
27
+ | "integrations"
28
+ | "compliance"
29
+ | "ai_admin_policy";
30
+
31
+ export interface SettingsSection {
32
+ key: SettingsSectionKey;
33
+ label: string;
34
+ summary: string;
35
+ riskyChangeReview: boolean;
36
+ }
37
+
38
+ export interface ShippingRestriction {
39
+ region: string;
40
+ reason: string;
41
+ }
42
+
43
+ export interface SettingsAuditEvent extends AuditEvent {
44
+ /** Human-readable summary of what changed — `AuditEvent.action` is the machine-facing form. */
45
+ change: string;
46
+ }
47
+
48
+ export interface SettingsViewModel {
49
+ targetTenant: string;
50
+ sections: SettingsSection[];
51
+ shippingRestrictions: ShippingRestriction[];
52
+ aiPolicyMode: ApprovalMode;
53
+ internalTenantPickerEnabled: boolean;
54
+ auditTrail: SettingsAuditEvent[];
55
+ /**
56
+ * The target tenant's current checkout styling — the editable descriptor the
57
+ * "Checkout styling" section (U12) renders in its editor and saves back through
58
+ * the versioned publish spine (`POST /api/admin/checkout-style` →
59
+ * `publishToTest` on `tenants/<id>/checkout-style.json`). Always a complete
60
+ * `CheckoutStyle` (the resolved default merged with any published override), so
61
+ * the editor never has to reason about missing fields.
62
+ */
63
+ checkoutStyle: CheckoutStyle;
64
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The admin console's tenant envelope — { adminAppTenant, targetTenant } —
3
+ * per docs/handoffs/admin-app-implementation-baseline.md §Admin App As Tenant.
4
+ *
5
+ * PURE (no I/O, no Astro) so the staff-only override + its authorization are
6
+ * unit-testable in isolation. Reuses the EXISTING staff/membership machinery
7
+ * from `@/lib/dashboard/tenantSelection` and `@/lib/dashboard/staffAdmission`
8
+ * rather than inventing a parallel access model:
9
+ *
10
+ * - `adminAppTenant` is always the routed tenant (`Astro.locals.tenant`) —
11
+ * whichever domain actually served the request.
12
+ * - `targetTenant` defaults to `adminAppTenant`. Only a session carrying
13
+ * real ToT-staff scope (`isStaffSession`) may override it, and only to a
14
+ * tenant the viewer already has real access to — a `tenants[]` membership
15
+ * or a LIVE `staffSelection` (the same distinct staff-admission path the
16
+ * dashboard vendor picker uses). Deny-by-default: an unauthorized or
17
+ * unrecognized override is REFUSED (`denied: true`), never silently
18
+ * dropped-and-hidden — the caller renders that refusal, it doesn't just
19
+ * fall back quietly.
20
+ * - Non-staff viewers never see an override honored, regardless of what a
21
+ * request asks for: the staff check gates evaluation of the request
22
+ * entirely, not just the picker's visibility.
23
+ */
24
+ import type { SessionRecord } from "@/lib/auth/session";
25
+ import { isStaffSession } from "@/lib/dashboard/staffAdmission";
26
+ import { membershipFor, normalizeMemberships, type TenantMembership } from "@/lib/dashboard/tenantSelection";
27
+
28
+ export interface AdminTenantEnvelope {
29
+ /** The tenant that actually served this request. */
30
+ adminAppTenant: string;
31
+ /** The tenant this page's data/actions are scoped to. */
32
+ targetTenant: string;
33
+ /** The signed-in viewer's email, or null when anonymous. */
34
+ operator: string | null;
35
+ }
36
+
37
+ export interface AdminTenantModeResult {
38
+ envelope: AdminTenantEnvelope;
39
+ /** Whether the picker should render at all. */
40
+ isStaff: boolean;
41
+ /** Tenants the picker can offer — the viewer's real memberships, deduped. */
42
+ pickerTenants: TenantMembership[];
43
+ /** Set when a requested override was refused for lack of authorization. */
44
+ deniedTarget: string | null;
45
+ }
46
+
47
+ export function resolveAdminTenantMode(input: {
48
+ session: SessionRecord | null | undefined;
49
+ adminAppTenant: string;
50
+ requestedTarget: string | null | undefined;
51
+ nowSeconds: number;
52
+ }): AdminTenantModeResult {
53
+ const { session, adminAppTenant, nowSeconds } = input;
54
+ const isStaff = isStaffSession(session);
55
+ const operator = session?.email ?? null;
56
+ const requestedTarget = (input.requestedTarget ?? "").trim() || null;
57
+
58
+ const staffAdmission = session?.staffSelection
59
+ ? { selection: session.staffSelection, nowSeconds }
60
+ : undefined;
61
+
62
+ const pickerTenants = normalizeMemberships(session?.tenants);
63
+
64
+ // Non-staff: the override is never evaluated, not merely hidden from the UI.
65
+ if (!isStaff || !requestedTarget || requestedTarget === adminAppTenant) {
66
+ return {
67
+ envelope: { adminAppTenant, targetTenant: adminAppTenant, operator },
68
+ isStaff,
69
+ pickerTenants,
70
+ deniedTarget: null,
71
+ };
72
+ }
73
+
74
+ const membership = membershipFor(session?.tenants, requestedTarget, staffAdmission);
75
+ if (!membership) {
76
+ return {
77
+ envelope: { adminAppTenant, targetTenant: adminAppTenant, operator },
78
+ isStaff,
79
+ pickerTenants,
80
+ deniedTarget: requestedTarget,
81
+ };
82
+ }
83
+
84
+ return {
85
+ envelope: { adminAppTenant, targetTenant: requestedTarget, operator },
86
+ isStaff,
87
+ pickerTenants,
88
+ deniedTarget: null,
89
+ };
90
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Privacy-friendly analytics (brief §5 Analytics).
3
+ *
4
+ * Phase 1 ships TWO layers:
5
+ * 1. Pageviews — Cloudflare Web Analytics, a single beacon script injected in
6
+ * Layout.astro when PUBLIC_CF_ANALYTICS_TOKEN is set (no cookies, no PII).
7
+ * 2. Commerce events — product impression/click/PDP-view instrumented NOW to a
8
+ * NO-OP sink, so Phase 2 conversion work inherits the call sites for free.
9
+ *
10
+ * The event API is intentionally tiny and synchronous-looking; swap the sink
11
+ * (e.g. to a Workers Analytics Engine writer or a beacon) without touching call
12
+ * sites. See DECISIONS.md.
13
+ */
14
+
15
+ export type CommerceEvent =
16
+ | { type: "product_impression"; handle: string; list?: string; position?: number }
17
+ | { type: "product_click"; handle: string; list?: string }
18
+ | { type: "pdp_view"; handle: string; variant_id?: string }
19
+ | { type: "search"; query: string; results: number }
20
+ | { type: "collection_view"; handle: string }
21
+ | { type: "newsletter_signup"; source: string };
22
+
23
+ export interface AnalyticsSink {
24
+ track(event: CommerceEvent): void;
25
+ }
26
+
27
+ /** Default sink: discard. Visible in dev console when ?debugAnalytics is set. */
28
+ export class NoOpSink implements AnalyticsSink {
29
+ constructor(private readonly debug = false) {}
30
+ track(event: CommerceEvent): void {
31
+ if (this.debug && typeof console !== "undefined") {
32
+ // eslint-disable-next-line no-console
33
+ console.debug("[analytics]", event);
34
+ }
35
+ }
36
+ }
37
+
38
+ let sink: AnalyticsSink = new NoOpSink();
39
+
40
+ /** Phase 2: call setAnalyticsSink(new RealSink()) once at boot. */
41
+ export function setAnalyticsSink(next: AnalyticsSink): void {
42
+ sink = next;
43
+ }
44
+
45
+ export function track(event: CommerceEvent): void {
46
+ sink.track(event);
47
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Broker-assertion MINTER — the storefront half of the "staff proven by a signed
3
+ * assertion, not a plaintext field" contract (#8453, decision
4
+ * `staff-invite-proof-via-broker`).
5
+ *
6
+ * The earlier flow sent the signed-in staff email to tot20 as a PLAINTEXT
7
+ * `actorEmail`/`email` field and asked tot20 to trust it — a spoof: anything that
8
+ * can reach the operator endpoint could name any staff email. This module instead
9
+ * MINTS a short-lived, storefront-signed RS256 JWT (the "broker assertion") that
10
+ * PROVES the storefront asserts this staff actor. It is presented to tot20 in the
11
+ * `X-ToT-Broker-Assertion` header; tot20 verifies the signature (against the
12
+ * storefront's PUBLISHED public key) + replay (`jti`/`exp`) and derives the staff
13
+ * actor from the VERIFIED `sub` — never from an untrusted field.
14
+ *
15
+ * SERVER-SIDE ONLY. The RS256 PRIVATE key (`STOREFRONT_BROKER_PRIVATE_KEY_B64`)
16
+ * lives only in the Worker/server env and is used only here; it MUST NEVER reach
17
+ * the client bundle. Callers are the server-side API routes (they hold the
18
+ * verified `session.email`), which mint per call and hand the compact JWT to the
19
+ * tot20 client to attach as the header. The clients themselves never see the key.
20
+ *
21
+ * Mirrors the jose usage in `./identityToken.ts` (that module VERIFIES tot's
22
+ * ES256 assertion; this one SIGNS the storefront's RS256 assertion — the inverse
23
+ * direction of the same decoupled-authZ pattern).
24
+ */
25
+ import { SignJWT, importPKCS8, type CryptoKey } from "jose";
26
+ import { readEnv } from "@/lib/env";
27
+
28
+ /** The audience tot20's broker verifier pins. */
29
+ export const BROKER_AUDIENCE = "tot20-broker";
30
+
31
+ /** The only algorithm the storefront broker signs with (tot20 rejects anything else). */
32
+ export const BROKER_ALG = "RS256";
33
+
34
+ /** Default key id (overridable via `STOREFRONT_BROKER_KID`). Names the published key. */
35
+ export const DEFAULT_BROKER_KID = "storefront-qa-1";
36
+
37
+ /** Default issuer (overridable via `STOREFRONT_BROKER_ISSUER`). */
38
+ export const DEFAULT_BROKER_ISSUER = "storefront";
39
+
40
+ /** Assertion lifetime — deliberately short (replay window). */
41
+ export const BROKER_TTL_SECONDS = 300;
42
+
43
+ /** Env keys the minter reads (server-only). */
44
+ export const BROKER_PRIVATE_KEY_ENV = "STOREFRONT_BROKER_PRIVATE_KEY_B64";
45
+ export const BROKER_KID_ENV = "STOREFRONT_BROKER_KID";
46
+ export const BROKER_ISSUER_ENV = "STOREFRONT_BROKER_ISSUER";
47
+
48
+ /**
49
+ * Thrown when the minter can't sign — the private key env is absent/malformed, or
50
+ * no subject was supplied. Routes map this to a 503 ("integration not configured")
51
+ * and FAIL CLOSED: with no proof of the staff actor, the staff call must not go out.
52
+ */
53
+ export class BrokerAssertionError extends Error {}
54
+
55
+ /** Optional test/DI overrides (avoids touching process env in unit tests). */
56
+ export interface MintBrokerAssertionOverrides {
57
+ /** base64 of the PKCS8 PEM private key (defaults to the env value). */
58
+ privateKeyB64?: string;
59
+ kid?: string;
60
+ issuer?: string;
61
+ }
62
+
63
+ // Import the signing key once per distinct PEM (importPKCS8 is not free). Keyed on
64
+ // the base64 string so a key rotation (new env value) transparently re-imports.
65
+ const keyCache = new Map<string, CryptoKey>();
66
+
67
+ async function loadSigningKey(privateKeyB64: string): Promise<CryptoKey> {
68
+ const cached = keyCache.get(privateKeyB64);
69
+ if (cached) return cached;
70
+ let pem: string;
71
+ try {
72
+ pem = Buffer.from(privateKeyB64, "base64").toString("utf8");
73
+ } catch (cause) {
74
+ throw new BrokerAssertionError(
75
+ "broker assertion: STOREFRONT_BROKER_PRIVATE_KEY_B64 is not valid base64 " +
76
+ (cause instanceof Error ? cause.message : String(cause)),
77
+ );
78
+ }
79
+ if (!/-----BEGIN (RSA )?PRIVATE KEY-----/.test(pem)) {
80
+ throw new BrokerAssertionError(
81
+ "broker assertion: decoded STOREFRONT_BROKER_PRIVATE_KEY_B64 is not a PEM private key",
82
+ );
83
+ }
84
+ let key: CryptoKey;
85
+ try {
86
+ key = (await importPKCS8(pem, BROKER_ALG)) as CryptoKey;
87
+ } catch (cause) {
88
+ throw new BrokerAssertionError(
89
+ "broker assertion: failed to import the RS256 private key " +
90
+ (cause instanceof Error ? cause.message : String(cause)),
91
+ );
92
+ }
93
+ keyCache.set(privateKeyB64, key);
94
+ return key;
95
+ }
96
+
97
+ /**
98
+ * Mint a fresh, storefront-signed broker assertion PROVING `subjectEmail` is the
99
+ * staff actor. Returns the compact RS256 JWT to place in `X-ToT-Broker-Assertion`.
100
+ *
101
+ * Claims: `{ iss, sub:<subjectEmail>, aud:'tot20-broker', jti:<uuid>, iat, exp:iat+300 }`.
102
+ * Header: `{ alg:'RS256', kid, typ:'JWT' }`.
103
+ *
104
+ * FAILS CLOSED: throws {@link BrokerAssertionError} when the private key env is
105
+ * absent/malformed or no subject is supplied — the caller must NOT fall back to an
106
+ * unproven call.
107
+ */
108
+ export async function mintBrokerAssertion(
109
+ subjectEmail: string,
110
+ overrides: MintBrokerAssertionOverrides = {},
111
+ ): Promise<string> {
112
+ const sub = (subjectEmail ?? "").trim().toLowerCase();
113
+ if (!sub) {
114
+ throw new BrokerAssertionError("broker assertion: a subject email is required");
115
+ }
116
+ const privateKeyB64 = overrides.privateKeyB64 ?? (await readEnv(BROKER_PRIVATE_KEY_ENV));
117
+ if (!privateKeyB64) {
118
+ throw new BrokerAssertionError(
119
+ `broker assertion: ${BROKER_PRIVATE_KEY_ENV} is not configured (fail closed)`,
120
+ );
121
+ }
122
+ const kid = overrides.kid ?? (await readEnv(BROKER_KID_ENV)) ?? DEFAULT_BROKER_KID;
123
+ const issuer = overrides.issuer ?? (await readEnv(BROKER_ISSUER_ENV)) ?? DEFAULT_BROKER_ISSUER;
124
+
125
+ const key = await loadSigningKey(privateKeyB64);
126
+ const nowSeconds = Math.floor(Date.now() / 1000);
127
+ return new SignJWT({})
128
+ .setProtectedHeader({ alg: BROKER_ALG, kid, typ: "JWT" })
129
+ .setIssuer(issuer)
130
+ .setSubject(sub)
131
+ .setAudience(BROKER_AUDIENCE)
132
+ .setJti(crypto.randomUUID())
133
+ .setIssuedAt(nowSeconds)
134
+ .setExpirationTime(nowSeconds + BROKER_TTL_SECONDS)
135
+ .sign(key);
136
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Local dev auth — the SINGLE, auditable home for the localhost sign-in bypass.
3
+ *
4
+ * On localhost tot20 isn't reachable, so real sign-in/invite emails can't be sent
5
+ * or verified. This module lets a developer complete sign-in locally: `/api/auth/send`
6
+ * prints a one-time code to the server console + stores it here; `/api/auth/verify`
7
+ * consumes it and mints a session WITHOUT tot20/JWKS.
8
+ *
9
+ * SECURITY — this must never exist in a deployed build:
10
+ * 1. EVERY call site MUST wrap use in `if (import.meta.env.DEV)`. That is a STATIC
11
+ * boolean Vite/Astro evaluates at build time, so the whole branch (and this
12
+ * module's imports) are dead-code-eliminated from the production bundle — the
13
+ * same mechanism the middleware dev guards rely on.
14
+ * 2. `devAuthEnabled()` adds a runtime opt-in (`DEV_AUTH=1` in `.dev.vars`) so even
15
+ * a local dev build does nothing unless the developer explicitly turned it on.
16
+ * Keeping the bypass in one file means "does prod contain the bypass?" is a single
17
+ * grep (`devAuth`), and the production path in send/verify is never weakened.
18
+ */
19
+ import { readEnv } from "@/lib/env";
20
+ import { devTenantResources } from "@/config/devTenants";
21
+ import { createSession } from "./session.js";
22
+
23
+ /** SESSION-KV key prefix for the one-time dev code (distinct from `authsession:`). */
24
+ export const DEV_AUTH_CODE_PREFIX = "devauthcode:";
25
+ /** Dev code lifetime (KV clamps expirationTtl to >= 60). */
26
+ export const DEV_AUTH_CODE_TTL_SECONDS = 600;
27
+
28
+ /** Minimal KV seam (the `SESSION` binding). */
29
+ interface CodeKv {
30
+ get(key: string): Promise<string | null>;
31
+ put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>;
32
+ delete(key: string): Promise<void>;
33
+ }
34
+
35
+ /**
36
+ * Runtime opt-in. Call ONLY inside `if (import.meta.env.DEV)` so the whole thing is
37
+ * tree-shaken from prod regardless of any env var.
38
+ */
39
+ export async function devAuthEnabled(): Promise<boolean> {
40
+ return (await readEnv("DEV_AUTH")) === "1";
41
+ }
42
+
43
+ /** A 6-digit numeric one-time code (WebCrypto). */
44
+ export function devAuthCode(): string {
45
+ const n = new Uint32Array(1);
46
+ crypto.getRandomValues(n);
47
+ return String((n[0] ?? 0) % 1_000_000).padStart(6, "0");
48
+ }
49
+
50
+ /** Store a freshly-minted code for `email`. */
51
+ export async function storeDevCode(kv: CodeKv, email: string, code: string): Promise<void> {
52
+ await kv.put(`${DEV_AUTH_CODE_PREFIX}${email}`, code, {
53
+ expirationTtl: DEV_AUTH_CODE_TTL_SECONDS,
54
+ });
55
+ }
56
+
57
+ /**
58
+ * One-time consume: returns true iff `code` matches the stored code for `email`,
59
+ * deleting it on success so a code can't be replayed.
60
+ */
61
+ export async function consumeDevCode(kv: CodeKv, email: string, code: string): Promise<boolean> {
62
+ const stored = await kv.get(`${DEV_AUTH_CODE_PREFIX}${email}`);
63
+ if (!stored || stored !== code) return false;
64
+ await kv.delete(`${DEV_AUTH_CODE_PREFIX}${email}`);
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * Session fields for a local dev sign-in (option (c)): membership on EVERY registered
70
+ * tenant AND a staff scope, so the dashboard picker, every tenant, and the staff
71
+ * (any-tenant) paths are all exercisable locally. `resource` mirrors the real path's
72
+ * verified `aud` (the storefront app identity, `STOREFRONT_APP_DOMAIN`).
73
+ */
74
+ export async function devSessionFields(
75
+ email: string,
76
+ ): Promise<Parameters<typeof createSession>[1]> {
77
+ const resource = (await readEnv("STOREFRONT_APP_DOMAIN")) || "storefront";
78
+ const tenants = devTenantResources().map((resource) => ({
79
+ resource,
80
+ role: "developer",
81
+ }));
82
+ return {
83
+ email: email.toLowerCase(),
84
+ resource,
85
+ roles: [],
86
+ capability: "member",
87
+ tenants,
88
+ staff: ["staff"],
89
+ };
90
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * "Coming soon" holding page served to UNAUTHENTICATED visitors of a login-gated
3
+ * tenant (every non-self tenant on a gated host — see `loginGate.ts`).
4
+ *
5
+ * Instead of hard-redirecting to /auth/login, we serve a friendly holding page
6
+ * that withholds the tenant's content but offers a Sign-in button. The button
7
+ * carries the same `?next=` destination the gate would have redirected to, so a
8
+ * successful sign-in lands the visitor back where they started.
9
+ *
10
+ * The platform's own home ("self") tenant is exempt and never sees this — it
11
+ * renders publicly so people can browse + sign up.
12
+ */
13
+
14
+ /** Escape a string for safe interpolation into an HTML attribute value. */
15
+ function escapeAttr(s: string): string {
16
+ return s
17
+ .replace(/&/g, "&amp;")
18
+ .replace(/"/g, "&quot;")
19
+ .replace(/</g, "&lt;")
20
+ .replace(/>/g, "&gt;");
21
+ }
22
+
23
+ export function gatedHoldingHtml(loginHref: string): string {
24
+ const href = escapeAttr(loginHref);
25
+ return `<!doctype html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="utf-8" />
29
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
30
+ <meta name="robots" content="noindex, nofollow" />
31
+ <title>Coming soon</title>
32
+ <style>
33
+ :root { color-scheme: light dark; }
34
+ * { box-sizing: border-box; }
35
+ html, body { height: 100%; margin: 0; }
36
+ body {
37
+ display: flex; align-items: center; justify-content: center;
38
+ min-height: 100%; padding: 2rem;
39
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
40
+ background: radial-gradient(1200px 800px at 50% -10%, #1e293b, #0f172a 60%, #020617);
41
+ color: #e2e8f0;
42
+ }
43
+ main { max-width: 34rem; text-align: center; }
44
+ .badge {
45
+ display: inline-block; letter-spacing: .18em; text-transform: uppercase;
46
+ font-size: .72rem; font-weight: 600; color: #94a3b8;
47
+ border: 1px solid rgba(148,163,184,.35); border-radius: 999px;
48
+ padding: .35rem .8rem; margin-bottom: 1.75rem;
49
+ }
50
+ h1 { font-size: clamp(2rem, 6vw, 3rem); line-height: 1.1; margin: 0 0 1rem; color: #f8fafc; }
51
+ p { font-size: 1.05rem; line-height: 1.6; color: #cbd5e1; margin: 0 auto 2rem; max-width: 28rem; }
52
+ a.btn {
53
+ display: inline-block; text-decoration: none; font-weight: 600; font-size: 1rem;
54
+ color: #04121f; background: #38bdf8; border-radius: 10px; padding: .8rem 1.6rem;
55
+ transition: background .15s ease;
56
+ }
57
+ a.btn:hover { background: #7dd3fc; }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <main>
62
+ <span class="badge">Token of Trust</span>
63
+ <h1>Coming soon</h1>
64
+ <p>This storefront isn't open to the public yet. If you have access, sign in to preview it.</p>
65
+ <a class="btn" href="${href}">Sign in</a>
66
+ </main>
67
+ </body>
68
+ </html>`;
69
+ }
70
+
71
+ /** A `200 OK` holding page — never indexed, never cached. */
72
+ export function gatedHoldingResponse(loginHref: string): Response {
73
+ return new Response(gatedHoldingHtml(loginHref), {
74
+ status: 200,
75
+ headers: {
76
+ "content-type": "text/html; charset=utf-8",
77
+ "cache-control": "no-store",
78
+ "x-robots-tag": "noindex, nofollow",
79
+ },
80
+ });
81
+ }