@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
package/LICENSE ADDED
@@ -0,0 +1,58 @@
1
+ Token of Trust Source-Available License — Local Development Only
2
+ Version 1.0
3
+
4
+ Copyright (c) 2026 Token of Trust, Inc. All rights reserved.
5
+
6
+ 1. DEFINITIONS
7
+ "Software" means this source code and any accompanying files in this
8
+ repository or distribution, including the storefront renderer runtime, the
9
+ `@tot/public-runtime` package, the storefront-runner distributable, and any
10
+ derivative works.
11
+ "Local Development" means running, building, and modifying the Software on a
12
+ machine you control, for the sole purpose of evaluating it or developing and
13
+ previewing your own tenant content against it. Serving the Software to any
14
+ third party, or over any public or shared network, is NOT Local Development.
15
+ "Token of Trust" means Token of Trust, Inc.
16
+
17
+ 2. GRANT (LOCAL DEVELOPMENT ONLY)
18
+ Subject to the terms below, Token of Trust grants you a personal,
19
+ non-exclusive, non-transferable, revocable license to use, copy, build, and
20
+ modify the Software SOLELY for Local Development.
21
+
22
+ 3. PERMISSION REQUIRED FOR EVERYTHING ELSE
23
+ Any use of the Software beyond Local Development requires the PRIOR WRITTEN
24
+ PERMISSION of Token of Trust. Without such permission you may NOT, in whole or
25
+ in part:
26
+ (a) deploy, host, serve, or otherwise run the Software in production or for
27
+ any third party, or on any public, staging, or shared/network-accessible
28
+ environment;
29
+ (b) use the Software for any commercial purpose or to provide a service to
30
+ others;
31
+ (c) distribute, publish, sublicense, sell, rent, lease, or otherwise make the
32
+ Software (or a derivative of it) available to any third party; or
33
+ (d) remove or obscure this license or the copyright notices.
34
+ To request permission, contact Token of Trust at legal@tokenoftrust.com.
35
+
36
+ 4. NO TRADEMARK LICENSE
37
+ This license grants no rights to the Token of Trust name, logos, or
38
+ trademarks.
39
+
40
+ 5. RESERVATION OF RIGHTS
41
+ All rights not expressly granted are reserved by Token of Trust. This is a
42
+ source-available license, not an open-source license; the Software is not
43
+ licensed under any OSI-approved license.
44
+
45
+ 6. TERMINATION
46
+ This license terminates automatically if you breach it. On termination you
47
+ must stop using the Software and destroy all copies in your possession.
48
+
49
+ 7. DISCLAIMER OF WARRANTY
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
52
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
53
+
54
+ 8. LIMITATION OF LIABILITY
55
+ IN NO EVENT SHALL TOKEN OF TRUST BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
56
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
57
+ OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
58
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Storefront Runner
2
+
3
+ A world-shareable, multi-tenant **renderer runtime** for the Token of Trust
4
+ storefront. It serves N tenants (or differently-named variants of one tenant) at
5
+ path-prefix URLs — the same thing `astro dev` does for the full platform — with
6
+ the control-plane services (content authoring, compliance, and the publish/deploy
7
+ control plane) kept private and **physically carved out**.
8
+
9
+ ## Run
10
+
11
+ ```sh
12
+ pnpm install
13
+ pnpm dev # astro dev on http://localhost:4321
14
+ ```
15
+
16
+ Tenants resolve by `Host` header first, then by a `/<domain>/…` path prefix
17
+ (DNS-free, for localhost). Try:
18
+
19
+ - http://localhost:4321/ (the neutral "home" default)
20
+ - http://localhost:4321/sample-store.example/ (generic sample store — shows the 21+ compliance gate)
21
+
22
+ Your OWN tenant is served by the `tot dev` loop, which grafts your checkout into
23
+ `tenants/<id>/` at runtime — no bundled merchants ship with the runner.
24
+
25
+ ## Add a tenant
26
+
27
+ Adding a tenant is a one-object edit in `apps/storefront/src/config/tenants.ts`
28
+ (see `SAMPLE_TENANT`), plus a `tenants/<id>/` directory for its
29
+ content/theme/public assets.
30
+
31
+ ## IP boundary
32
+
33
+ This distributable depends only on `@tot/public-runtime` (the renderer-safe
34
+ model). The private control-plane package is absent, so a control-plane import
35
+ cannot resolve here.
36
+
37
+ ## License
38
+
39
+ Source-available, **local-development only** (see `LICENSE`). You may run, build,
40
+ and modify this for Local Development. Any other use — deploying, hosting, or
41
+ serving it to third parties, or any production/commercial use — requires the
42
+ prior written permission of Token of Trust, Inc.
@@ -0,0 +1,66 @@
1
+ // @ts-check
2
+ import { defineConfig } from "astro/config";
3
+ import cloudflare from "@astrojs/cloudflare";
4
+ import preact from "@astrojs/preact";
5
+ import tailwindcss from "@tailwindcss/vite";
6
+ import { tenantHotReload } from "./dev-plugins/tenant-hot-reload.mjs";
7
+
8
+ // SSR on Cloudflare Workers. `output: 'server'` because every response is
9
+ // tenant-resolved at the edge from the Host header (see src/middleware).
10
+ // Preact powers the handful of interactive islands (variant selector, gallery,
11
+ // facets, search typeahead, mobile drawer) — near-zero JS everywhere else.
12
+ export default defineConfig({
13
+ output: "server",
14
+ // Externalize all CSS (no auto-inlined <style>) so a strict CSP needs no
15
+ // inline-style allowance for elements — every stylesheet is a 'self' <link>.
16
+ // Small first-paint tradeoff, offset by cross-page caching (see LIMITATIONS).
17
+ build: { inlineStylesheets: "never" },
18
+ adapter: cloudflare({
19
+ // Local image/asset placeholders are served from /public; real product
20
+ // images get rehosted to R2 in Phase 1 ETL (see DECISIONS.md).
21
+ imageService: "compile",
22
+ }),
23
+ integrations: [preact({ compat: true })],
24
+ vite: {
25
+ plugins: [tailwindcss(), tenantHotReload()],
26
+ optimizeDeps: {
27
+ // Vite's SSR optimizer can try to prebundle the Cloudflare adapter's
28
+ // worker entrypoints, then the worker runner looks for a generated
29
+ // deps_ssr module that may not exist after a restart/HMR cache turn.
30
+ // Keep adapter entrypoints external so workerd loads the real package
31
+ // exports from @astrojs/cloudflare instead of an optimized cache file.
32
+ exclude: [
33
+ "@astrojs/cloudflare/entrypoints/server",
34
+ "@astrojs/cloudflare/entrypoints/server.js",
35
+ "@astrojs/cloudflare/entrypoints/preview",
36
+ "@astrojs/cloudflare/entrypoints/preview.js",
37
+ ],
38
+ },
39
+ // Dev-only: let the dev server accept tenant Host headers so host-based
40
+ // tenant routing can be tested locally (e.g. curl -H 'Host: sample-store.example').
41
+ // Prod runs on Cloudflare Workers, which has no such allowlist.
42
+ server: {
43
+ allowedHosts: [
44
+ "sample-store.example",
45
+ "www.sample-store.example",
46
+ "pilot.tokenoftrust.com",
47
+ ],
48
+ // Containerized `tot dev` (scripts/dev): a bind-mount often doesn't deliver
49
+ // inotify events, so Vite's file watcher must POLL for save→HMR to fire.
50
+ // Gated on CHOKIDAR_USEPOLLING (set by the dev image/compose) so native/local
51
+ // dev keeps instant, zero-overhead event-based watching. Explicit rather than
52
+ // relying on Vite auto-reading the env, so it's version-proof.
53
+ ...(process.env.CHOKIDAR_USEPOLLING
54
+ ? {
55
+ watch: {
56
+ usePolling: true,
57
+ interval: Number(process.env.CHOKIDAR_INTERVAL) || 300,
58
+ },
59
+ }
60
+ : {}),
61
+ },
62
+ },
63
+ // Canonical/sitemap host comes from the resolved tenant, not here.
64
+ // `site` is a fallback only used when no tenant context is available.
65
+ site: "https://pilot.tokenoftrust.com",
66
+ });
@@ -0,0 +1,147 @@
1
+ // @ts-check
2
+ //
3
+ // In-memory state for the /dev live-loop Monitor (C1). A module singleton — one
4
+ // per dev-server process, shared by the tenant-hot-reload plugin (which records
5
+ // saves) and the dev-loop-monitor plugin (which records reloads + serves the
6
+ // status endpoint). Never reaches production: every dev-plugin runs
7
+ // `apply: "serve"`, so this module is loaded only under `astro dev`.
8
+ //
9
+ // The Monitor reads this via GET /__tot/dev-loop-status (poll) and via the
10
+ // `tot:loop` custom HMR event; both carry the same snapshot(). The message
11
+ // envelope {type, at, seq, payload} is the extensible P0 seam — each later phase
12
+ // (tot:file-changed, tot:write-applied, …) adds a `type` string without
13
+ // changing this shape.
14
+ //
15
+ // NOTE — markReload()/snapshot()/loopEnvelope() are consumed ONLY by
16
+ // dev-loop-monitor.mjs, which is a cockpit-only plugin excluded from the
17
+ // shipped LOCAL RUNNER build (scripts/build/build-runner.mjs's DROP set) — this
18
+ // module ships there too (tenant-hot-reload.mjs's markSave/activity-bridge
19
+ // dependency), so within the pruned runner tree those three exports are
20
+ // unreachable. That's expected, not a bug: this file still serves BOTH the
21
+ // monorepo's own `astro dev` (where dev-loop-monitor.mjs IS wired, per
22
+ // apps/storefront/astro.config.mjs) and the runner (markSave/activity-bridge
23
+ // only). Don't delete them thinking they're dead — they're dead only from the
24
+ // runner's vantage point.
25
+
26
+ /** @typedef {"change"|"add"|"unlink"} LoopEvent */
27
+
28
+ const state = {
29
+ /** dev server is up (true for the life of the process). */
30
+ server: true,
31
+ /** resolved dev-server port, once known. */
32
+ port: /** @type {number|null} */ (null),
33
+ /** last saved file, repo-relative posix (e.g. "tokenoftrust/content/home.html"). */
34
+ watchedFile: /** @type {string|null} */ (null),
35
+ /** epoch ms of the last watched-file save seen. */
36
+ lastSaveAt: /** @type {number|null} */ (null),
37
+ /** epoch ms of the last full-reload broadcast to the browser. */
38
+ lastReloadAt: /** @type {number|null} */ (null),
39
+ /** kind of the last file event (C2 fills add/unlink; C1 only sees change). */
40
+ lastEvent: /** @type {LoopEvent|null} */ (null),
41
+ /** epoch ms the process/state came up. */
42
+ startedAt: Date.now(),
43
+ };
44
+
45
+ let seq = 0;
46
+
47
+ /** The live mutable state object (shared by the plugins). */
48
+ export function getLoopState() {
49
+ return state;
50
+ }
51
+
52
+ // --- local→hosted activity bridge ------------------------------------------
53
+ // When `tot login --code` cached an activity-bridge credential (piggybacked on
54
+ // the invite paste — see packages/cli/src/commands/login.mjs), `tot dev`
55
+ // threads it in as these two env vars (commands/dev.mjs runMonorepo). Report
56
+ // each save to the developer's own hosted /dev panel so its activity log +
57
+ // Step-2 gate can react — debounced (rapid saves collapse to the latest) and
58
+ // best-effort: a failed/offline hosted worker must NEVER break the local loop.
59
+ const ACTIVITY_REPORT_DEBOUNCE_MS = 800;
60
+ /** @type {ReturnType<typeof setTimeout> | null} */
61
+ let activityReportTimer = null;
62
+
63
+ /**
64
+ * @param {string} relFile tenant-relative posix path
65
+ * @param {LoopEvent} event
66
+ */
67
+ /** POST one activity body to the hosted panel, best-effort. No-op without the bridge env.
68
+ * @param {Record<string, unknown>} body */
69
+ function postActivity(body) {
70
+ const url = process.env.TOT_DEV_ACTIVITY_URL;
71
+ const token = process.env.TOT_DEV_ACTIVITY_TOKEN;
72
+ if (!url || !token) return; // no hosted bridge cached — plain local dev, unchanged
73
+ fetch(`${String(url).replace(/\/+$/, "")}/api/dev/activity`, {
74
+ method: "POST",
75
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
76
+ body: JSON.stringify(body),
77
+ }).catch(() => {
78
+ /* best-effort — hosted panel just won't show this event */
79
+ });
80
+ }
81
+
82
+ /** @param {string} relFile @param {LoopEvent} event */
83
+ function scheduleActivityReport(relFile, event) {
84
+ if (!process.env.TOT_DEV_ACTIVITY_URL || !process.env.TOT_DEV_ACTIVITY_TOKEN) return;
85
+ if (activityReportTimer) clearTimeout(activityReportTimer);
86
+ const at = Date.now();
87
+ activityReportTimer = setTimeout(() => postActivity({ file: relFile, event, at }), ACTIVITY_REPORT_DEBOUNCE_MS);
88
+ if (typeof activityReportTimer.unref === "function") activityReportTimer.unref();
89
+ }
90
+
91
+ /**
92
+ * Report that the developer's store is UP + serving — the hosted /dev panel uses this
93
+ * to complete Step 1 ("Get running") the moment the site is live, BEFORE any edit.
94
+ * One-shot, best-effort. Safe to call once the dev server answers.
95
+ */
96
+ export function reportServerUp() {
97
+ postActivity({ event: "up", at: Date.now() });
98
+ }
99
+
100
+ /**
101
+ * Record the resolved dev-server port (best-effort, idempotent).
102
+ * @param {unknown} p
103
+ */
104
+ export function setPort(p) {
105
+ if (typeof p === "number" && Number.isFinite(p)) state.port = p;
106
+ }
107
+
108
+ /**
109
+ * Record a watched-file save.
110
+ * @param {string} relFile repo-relative posix path
111
+ * @param {LoopEvent} [event]
112
+ */
113
+ export function markSave(relFile, event = "change") {
114
+ state.lastSaveAt = Date.now();
115
+ state.watchedFile = relFile;
116
+ state.lastEvent = event;
117
+ scheduleActivityReport(relFile, event);
118
+ }
119
+
120
+ /** Record a full-reload having been broadcast to the browser. */
121
+ export function markReload() {
122
+ state.lastReloadAt = Date.now();
123
+ }
124
+
125
+ /**
126
+ * Serializable snapshot for GET /__tot/dev-loop-status and the tot:loop payload.
127
+ * `tenant` is read live from the env (set by scripts/tot-dev.mjs on this Node
128
+ * process — the Vite layer sees it reliably, unlike SSR/workerd).
129
+ */
130
+ export function snapshot() {
131
+ return {
132
+ server: state.server,
133
+ port: state.port,
134
+ tenant: process.env.TOT_DEV_TENANT || null,
135
+ watchedFile: state.watchedFile,
136
+ lastSaveAt: state.lastSaveAt,
137
+ lastReloadAt: state.lastReloadAt,
138
+ lastEvent: state.lastEvent,
139
+ startedAt: state.startedAt,
140
+ };
141
+ }
142
+
143
+ /** The extensible message envelope (P0 seam): {type, at, seq, payload}. */
144
+ export function loopEnvelope() {
145
+ seq += 1;
146
+ return { type: "tot:loop", at: Date.now(), seq, payload: snapshot() };
147
+ }
@@ -0,0 +1,238 @@
1
+ // @ts-check
2
+ import { fileURLToPath } from "node:url";
3
+ import { readdirSync, existsSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { markSave, reportServerUp } from "./dev-loop-state.mjs";
6
+
7
+ // Repo-root tenants/ dir (colocated tenant layout) + the generated static dest
8
+ // the predev copy step mirrors public/ into. This module lives at
9
+ // apps/storefront/dev-plugins/, so tenants/ is three levels up. Forward-slash
10
+ // normalized to match Vite's ctx.file (Vite always reports posix-style paths).
11
+ const TENANTS_DIR = fileURLToPath(new URL("../../../tenants", import.meta.url)).replace(/\\/g, "/");
12
+ const PUBLIC_TENANTS_DEST = fileURLToPath(new URL("../public/tenants", import.meta.url));
13
+
14
+ /**
15
+ * Classify a watched absolute path as a tenant file: → { id, rest } where `rest`
16
+ * is tenant-relative posix ("content/home.html", "public/logo.png"), or null if
17
+ * the path isn't under a tenant. Shared by handleHotUpdate (change) and the
18
+ * add/unlink watcher handlers so both agree on the content/-vs-public/ split.
19
+ * @param {string} file
20
+ * @returns {{ id: string, rest: string } | null}
21
+ */
22
+ export function classifyTenantFile(file) {
23
+ const f = String(file).replace(/\\/g, "/");
24
+ if (!f.startsWith(TENANTS_DIR + "/")) return null;
25
+ const rel = f.slice(TENANTS_DIR.length + 1); // "<id>/<...>"
26
+ const slash = rel.indexOf("/");
27
+ if (slash < 0) return null;
28
+ return { id: rel.slice(0, slash), rest: rel.slice(slash + 1) };
29
+ }
30
+
31
+ /** True for the raw content HTML that's read fresh from disk per request. @param {string} rest */
32
+ export function isContentHtml(rest) {
33
+ return rest.startsWith("content/") && rest.endsWith(".html");
34
+ }
35
+
36
+ /**
37
+ * Is this tenant opted out of static-asset mirroring (R2-versioned)? Matches
38
+ * scripts/build/copy-tenant-assets.mjs's isVersioned EXACTLY (both the marker
39
+ * file AND the TENANT_ASSETS_VERSIONED env var count) — the predev build and
40
+ * this dev-loop mirror must agree, or a tenant opted out solely via the env
41
+ * var would still get its R2-versioned assets silently shadowed by a dev-loop
42
+ * save mirroring into public/tenants/<id>/.
43
+ * @param {string} id
44
+ */
45
+ export function isVersionedTenant(id) {
46
+ const envList = (process.env.TENANT_ASSETS_VERSIONED ?? "")
47
+ .split(",")
48
+ .map((s) => s.trim())
49
+ .filter(Boolean);
50
+ if (envList.includes(id)) return true;
51
+ return existsSync(join(TENANTS_DIR, id, ".tot", "assets-versioned"));
52
+ }
53
+
54
+ /**
55
+ * Re-mirror one changed/added public asset into public/tenants/<id>/ (skip R2-versioned).
56
+ * @param {string} id @param {string} rest @param {string} sourceFile
57
+ */
58
+ async function mirrorPublicAsset(id, rest, sourceFile) {
59
+ if (isVersionedTenant(id)) return;
60
+ const { cp, mkdir } = await import("node:fs/promises");
61
+ const target = join(PUBLIC_TENANTS_DEST, id, rest.slice("public/".length));
62
+ try {
63
+ await mkdir(dirname(target), { recursive: true });
64
+ await cp(sourceFile, target);
65
+ } catch {
66
+ /* best-effort; still reload */
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Remove the stale mirrored copy under public/tenants/<id>/ when a public asset is deleted.
72
+ * @param {string} id @param {string} rest
73
+ */
74
+ async function unmirrorPublicAsset(id, rest) {
75
+ if (isVersionedTenant(id)) return;
76
+ const { rm } = await import("node:fs/promises");
77
+ const target = join(PUBLIC_TENANTS_DEST, id, rest.slice("public/".length));
78
+ try {
79
+ await rm(target, { force: true });
80
+ } catch {
81
+ /* best-effort; still reload */
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Seed the set of tenant files present when watching starts. Chokidar re-emits
87
+ * an "add" for every pre-existing file under a path passed to `.add()` after the
88
+ * initial scan (TENANTS_DIR sits ABOVE the Astro root, so it IS a post-ready
89
+ * add) — without this snapshot that boot echo would flood the Monitor with
90
+ * bogus "add" events and re-mirror every asset. Real adds (files created after
91
+ * boot) aren't in the set. Dependency-free recursive walk; dev-only + small.
92
+ * @param {string} root
93
+ * @param {Set<string>} known
94
+ */
95
+ export function seedKnown(root, known) {
96
+ const walk = (/** @type {string} */ dir) => {
97
+ let entries;
98
+ try {
99
+ entries = readdirSync(dir, { withFileTypes: true });
100
+ } catch {
101
+ return;
102
+ }
103
+ for (const e of entries) {
104
+ const abs = `${dir}/${e.name}`;
105
+ if (e.isDirectory()) walk(abs);
106
+ else if (e.isFile()) known.add(abs);
107
+ }
108
+ };
109
+ walk(root);
110
+ }
111
+
112
+ // Dev-only default posture: save a colocated tenant file → the browser reloads.
113
+ // The `tenants/<id>/` tree sits ABOVE the Astro root and reaches the app only
114
+ // through a lazy `?raw` glob (HTML, bypassed by the dev disk-read) or the predev
115
+ // public/ copy — never Vite's own module graph — so Vite doesn't watch it and a
116
+ // save would otherwise do nothing. We watch it explicitly and reload on change:
117
+ // - content/**.html — read fresh from disk per request
118
+ // (LocalContentProvider.readRawDevFile), so just reload. (The Vite HMR client
119
+ // is injected into these otherwise-pipeline-bypassing pages in dev; see
120
+ // rawHtmlResponse in src/lib/rawMarketingHtml.ts.)
121
+ // - public/** — re-mirror the one changed asset into public/tenants/<id>/
122
+ // (skipping R2-versioned tenants, like copy-tenant-assets.mjs), then reload.
123
+ // content/*.json, themes/*.json and theme.json are ordinary glob imports, so Vite
124
+ // already invalidates + reloads them — left to default handling. Returning []
125
+ // suppresses the pointless glob HMR that would otherwise fire for the raw files.
126
+ //
127
+ // RENAME/DELETE (C2): handleHotUpdate fires only on "change". Empirically, an
128
+ // add/unlink of a content/*.html file already triggers a correct Vite full-reload
129
+ // (its `?raw` glob is invalidated) and never crashes — so for content HTML we ONLY
130
+ // record the event for the Monitor and let Vite reload (sending our own would race
131
+ // the worker teardown, same as the change path). Public assets are NOT in any
132
+ // graph, so Vite ignores their add/unlink entirely — we mirror (add) / un-mirror
133
+ // (unlink) and reload ourselves. loopState.lastEvent then reflects add/unlink.
134
+ export function tenantHotReload() {
135
+ // Typed `any`: `vite` isn't a direct dependency of this package (it's pulled in
136
+ // transitively via astro), so `import('vite').Plugin` can't be resolved here and
137
+ // importing from "vite" would even fail at runtime. `any` keeps the hook params
138
+ // untyped without that reference.
139
+ /** @type {any} */
140
+ const plugin = {
141
+ name: "tot:tenant-hot-reload",
142
+ apply: "serve",
143
+ /** @param {any} server */
144
+ configureServer(server) {
145
+ server.watcher.add(TENANTS_DIR);
146
+
147
+ // Tell the hosted /dev panel the store is UP the moment the dev server is
148
+ // listening — completes Step 1 ("Get running") before any edit. Best-effort,
149
+ // no-op without the activity-bridge env (see reportServerUp / dev-loop-state).
150
+ try {
151
+ server.httpServer?.once?.("listening", () => reportServerUp());
152
+ } catch { /* best-effort */ }
153
+
154
+ // Guard the initial-scan add echo (see seedKnown). Maintained as files are
155
+ // really added/removed so the guard stays accurate for the session.
156
+ /** @type {Set<string>} */
157
+ const known = new Set();
158
+ seedKnown(TENANTS_DIR, known);
159
+
160
+ /** @param {"add"|"unlink"} event @param {string} rawPath */
161
+ const onAddUnlink = async (event, rawPath) => {
162
+ const file = String(rawPath).replace(/\\/g, "/");
163
+ const c = classifyTenantFile(file);
164
+ if (!c) return;
165
+ const { id, rest } = c;
166
+
167
+ if (event === "add") {
168
+ if (known.has(file)) return; // initial-scan echo, not a real add
169
+ known.add(file);
170
+ } else {
171
+ known.delete(file);
172
+ }
173
+
174
+ // Record the event for the live-loop Monitor (C1) — lastEvent = add/unlink.
175
+ markSave(rest, event);
176
+
177
+ // Content HTML: Vite's `?raw` glob invalidation already emits a correctly
178
+ // timed full-reload; don't send our own (teardown race). Just recorded above.
179
+ if (isContentHtml(rest)) return;
180
+
181
+ // Public assets: Vite doesn't react (not in the module graph). Maintain the
182
+ // mirror ourselves, then reload (server.ws.send is timestamped by the Monitor).
183
+ if (rest.startsWith("public/")) {
184
+ if (event === "add") await mirrorPublicAsset(id, rest, file);
185
+ else await unmirrorPublicAsset(id, rest);
186
+ server.ws.send({ type: "full-reload" });
187
+ return;
188
+ }
189
+
190
+ // json/theme add/unlink: ordinary glob imports — Vite reloads them; the
191
+ // markSave above is enough for the Monitor.
192
+ };
193
+
194
+ server.watcher.on("add", (/** @type {string} */ p) => onAddUnlink("add", p));
195
+ server.watcher.on("unlink", (/** @type {string} */ p) => onAddUnlink("unlink", p));
196
+ },
197
+ /** @param {any} ctx */
198
+ async handleHotUpdate(ctx) {
199
+ const c = classifyTenantFile(ctx.file);
200
+ if (!c) return undefined;
201
+ const { id, rest } = c;
202
+
203
+ // Record the save for the live-loop Monitor (C1) the instant a watched
204
+ // tenant file changes — before any branch below decides how to reload.
205
+ // `rest` is tenant-relative (e.g. "content/home.html"), matching the
206
+ // Monitor's readout. Covers content HTML, public assets, and the
207
+ // json/theme files that fall through to Vite's default handling. The
208
+ // matching reload is timestamped by dev-loop-monitor's ws/hot wrap.
209
+ markSave(rest, "change");
210
+
211
+ // Content HTML: the change invalidates the SSR module graph, so the
212
+ // Cloudflare Vite plugin reloads the worker and — once it is ready — emits
213
+ // its own correctly-timed full-reload (which the injected Vite HMR client
214
+ // acts on). We must NOT send our own reload: firing on the raw file-change
215
+ // event races ahead of the worker reload, so the browser re-navigates while
216
+ // miniflare is mid-teardown → "page not loaded" + an error overlay. Just let
217
+ // default handling run.
218
+ if (isContentHtml(rest)) {
219
+ return undefined;
220
+ }
221
+
222
+ // Public assets (CSS, images) aren't in the module graph — they're mirrored
223
+ // into public/tenants/<id>/ at predev, and a programmatic write there does
224
+ // NOT trip the platform's own reload. So: re-mirror the one changed file
225
+ // (await it so the served copy is current), THEN send the reload ourselves.
226
+ // Unlike content HTML this reloads no worker, so there's no teardown race to
227
+ // fire ahead of. Skip R2-versioned tenants, like copy-tenant-assets.mjs.
228
+ if (rest.startsWith("public/")) {
229
+ await mirrorPublicAsset(id, rest, ctx.file.replace(/\\/g, "/"));
230
+ ctx.server.ws.send({ type: "full-reload" });
231
+ return [];
232
+ }
233
+
234
+ return undefined;
235
+ },
236
+ };
237
+ return plugin;
238
+ }