@tokenoftrust/storefront-runner 1.3.4-rc.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/apps/storefront/astro.config.mjs +15 -0
  2. package/apps/storefront/dev-plugins/dev-publish.mjs +55 -0
  3. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +86 -7
  4. package/apps/storefront/drizzle.config.apps.ts +13 -0
  5. package/apps/storefront/env.d.ts +10 -0
  6. package/apps/storefront/migrations/README.md +13 -7
  7. package/apps/storefront/migrations-apps/0000_fast_millenium_guard.sql +129 -0
  8. package/apps/storefront/migrations-apps/meta/0000_snapshot.json +843 -0
  9. package/apps/storefront/migrations-apps/meta/_journal.json +13 -0
  10. package/apps/storefront/package.json +13 -1
  11. package/apps/storefront/perf/README.md +64 -0
  12. package/apps/storefront/perf/assert-budgets.ts +159 -0
  13. package/apps/storefront/playwright.config.ts +23 -0
  14. package/apps/storefront/public/js/dashboard-apps.js +173 -0
  15. package/apps/storefront/public/shared/commerce-marketing.css +221 -0
  16. package/apps/storefront/src/components/CollectionCard.astro +1 -0
  17. package/apps/storefront/src/components/ProductCard.astro +1 -0
  18. package/apps/storefront/src/components/admin/AdminPublishTab.astro +1959 -0
  19. package/apps/storefront/src/components/apps/AppWidgetFrame.astro +30 -0
  20. package/apps/storefront/src/components/chrome/NavDropdown.astro +6 -3
  21. package/apps/storefront/src/components/chrome/SiteFooter.astro +10 -0
  22. package/apps/storefront/src/components/chrome/SiteHeader.astro +10 -0
  23. package/apps/storefront/src/components/commerce/RatingStars.astro +3 -2
  24. package/apps/storefront/src/components/content/Callout.astro +75 -0
  25. package/apps/storefront/src/components/content/NeedsReviewCallout.astro +66 -0
  26. package/apps/storefront/src/components/content/ProseSections.astro +121 -0
  27. package/apps/storefront/src/components/content/ProseToc.astro +34 -0
  28. package/apps/storefront/src/components/content/RichText.astro +44 -0
  29. package/apps/storefront/src/components/content/TrustStrip.astro +46 -0
  30. package/apps/storefront/src/components/home/Hero.astro +14 -0
  31. package/apps/storefront/src/components/islands/CheckoutComplianceGate.tsx +295 -0
  32. package/apps/storefront/src/components/islands/ImageGallery.tsx +42 -21
  33. package/apps/storefront/src/components/islands/VariantSelector.tsx +62 -12
  34. package/apps/storefront/src/components/plp/FacetSidebar.astro +2 -2
  35. package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +1 -0
  36. package/apps/storefront/src/config/compliance/rulesets.ts +79 -0
  37. package/apps/storefront/src/config/storeName.ts +34 -0
  38. package/apps/storefront/src/layouts/Layout.astro +97 -23
  39. package/apps/storefront/src/lib/analytics/budgets.json +69 -0
  40. package/apps/storefront/src/lib/analytics/lighthouseReport.ts +109 -0
  41. package/apps/storefront/src/lib/analytics/perfBudgets.ts +452 -0
  42. package/apps/storefront/src/lib/analytics/rumAlert.ts +179 -0
  43. package/apps/storefront/src/lib/analytics/webVitals.ts +269 -0
  44. package/apps/storefront/src/lib/apps/adminService.ts +106 -0
  45. package/apps/storefront/src/lib/apps/adminSession.ts +205 -0
  46. package/apps/storefront/src/lib/apps/apiAuth.ts +91 -0
  47. package/apps/storefront/src/lib/apps/apiRoute.ts +35 -0
  48. package/apps/storefront/src/lib/apps/catalogMapper.ts +39 -0
  49. package/apps/storefront/src/lib/apps/credentials.ts +153 -0
  50. package/apps/storefront/src/lib/apps/gatewayKeys.ts +156 -0
  51. package/apps/storefront/src/lib/apps/healthAggregate.ts +66 -0
  52. package/apps/storefront/src/lib/apps/orders/attributionService.ts +206 -0
  53. package/apps/storefront/src/lib/apps/orders/customerHash.ts +28 -0
  54. package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +203 -0
  55. package/apps/storefront/src/lib/apps/orders/idempotency.ts +100 -0
  56. package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +117 -0
  57. package/apps/storefront/src/lib/apps/orders/orderMapper.ts +91 -0
  58. package/apps/storefront/src/lib/apps/orders/ordersStore.ts +181 -0
  59. package/apps/storefront/src/lib/apps/registryService.ts +579 -0
  60. package/apps/storefront/src/lib/apps/scopes.ts +79 -0
  61. package/apps/storefront/src/lib/apps/tokenIssuer.ts +121 -0
  62. package/apps/storefront/src/lib/apps/tokenVerifier.ts +148 -0
  63. package/apps/storefront/src/lib/apps/widgets/eligibility.ts +18 -0
  64. package/apps/storefront/src/lib/apps/widgets/frameProps.ts +52 -0
  65. package/apps/storefront/src/lib/apps/widgets/launchToken.ts +84 -0
  66. package/apps/storefront/src/lib/apps/widgets/placements.ts +57 -0
  67. package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +111 -0
  68. package/apps/storefront/src/lib/auth/adminEntry.ts +119 -0
  69. package/apps/storefront/src/lib/auth/identityToken.ts +21 -2
  70. package/apps/storefront/src/lib/auth/loginGate.ts +102 -18
  71. package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +215 -0
  72. package/apps/storefront/src/lib/auth/route.ts +16 -1
  73. package/apps/storefront/src/lib/auth/session.ts +8 -0
  74. package/apps/storefront/src/lib/auth/stepUpChallenge.ts +107 -0
  75. package/apps/storefront/src/lib/auth/totAccessClient.ts +208 -0
  76. package/apps/storefront/src/lib/blog/provider.ts +39 -0
  77. package/apps/storefront/src/lib/blog/types.ts +26 -0
  78. package/apps/storefront/src/lib/checkoutCommerce.ts +39 -1
  79. package/apps/storefront/src/lib/chrome/model.ts +11 -0
  80. package/apps/storefront/src/lib/compliance/enforcement.ts +95 -0
  81. package/apps/storefront/src/lib/content/callout.ts +78 -0
  82. package/apps/storefront/src/lib/content/index.ts +27 -0
  83. package/apps/storefront/src/lib/content/needsReview.ts +58 -0
  84. package/apps/storefront/src/lib/content/prose.ts +186 -0
  85. package/apps/storefront/src/lib/content/richtext.ts +76 -0
  86. package/apps/storefront/src/lib/content/trustStrip.ts +74 -0
  87. package/apps/storefront/src/lib/content-edit/client.ts +70 -14
  88. package/apps/storefront/src/lib/d1/catalog.ts +12 -0
  89. package/apps/storefront/src/lib/d1/schema-apps.ts +213 -0
  90. package/apps/storefront/src/lib/dev/apiBase.ts +8 -2
  91. package/apps/storefront/src/lib/dev/cliSignInCode.ts +65 -115
  92. package/apps/storefront/src/lib/dev/cockpitStore.ts +65 -0
  93. package/apps/storefront/src/lib/dev/obstacleStore.ts +214 -0
  94. package/apps/storefront/src/lib/dev/previewStatus.ts +112 -0
  95. package/apps/storefront/src/lib/dev/rendezvousBroker.ts +241 -0
  96. package/apps/storefront/src/lib/dev/subjectReissue.ts +67 -0
  97. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +10 -10
  98. package/apps/storefront/src/lib/env.ts +12 -0
  99. package/apps/storefront/src/lib/jsonld.ts +12 -17
  100. package/apps/storefront/src/lib/membership/eligibility.ts +37 -0
  101. package/apps/storefront/src/lib/monitoring/manifest.ts +302 -0
  102. package/apps/storefront/src/lib/privacy/emailHint.ts +13 -5
  103. package/apps/storefront/src/lib/publish/apex-readiness.ts +337 -0
  104. package/apps/storefront/src/lib/publish/dispatchHealth.ts +269 -0
  105. package/apps/storefront/src/lib/publish/domainState.ts +351 -0
  106. package/apps/storefront/src/lib/publish/shipWorkspace.ts +362 -0
  107. package/apps/storefront/src/lib/rawChrome.ts +34 -3
  108. package/apps/storefront/src/lib/storyblok/content-model.ts +34 -2
  109. package/apps/storefront/src/lib/storyblok/provider.ts +11 -4
  110. package/apps/storefront/src/lib/subscription/model.ts +114 -0
  111. package/apps/storefront/src/lib/tot/ToTClient.ts +3 -3
  112. package/apps/storefront/src/lib/tot/query.ts +32 -0
  113. package/apps/storefront/src/lib/webhooks/cloudflareQueueDispatcher.ts +82 -0
  114. package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +245 -0
  115. package/apps/storefront/src/lib/webhooks/deliveryMapper.ts +34 -0
  116. package/apps/storefront/src/lib/webhooks/deliveryStore.ts +668 -0
  117. package/apps/storefront/src/lib/webhooks/dispatcher.ts +168 -0
  118. package/apps/storefront/src/lib/webhooks/emit.ts +167 -0
  119. package/apps/storefront/src/lib/webhooks/endpointGuard.ts +135 -0
  120. package/apps/storefront/src/lib/webhooks/events.ts +98 -0
  121. package/apps/storefront/src/lib/webhooks/getDispatcher.ts +49 -0
  122. package/apps/storefront/src/lib/webhooks/signing.ts +29 -0
  123. package/apps/storefront/src/lib/webhooks/webhookSigningKey.ts +146 -0
  124. package/apps/storefront/src/middleware/index.ts +28 -13
  125. package/apps/storefront/src/pages/404.astro +21 -9
  126. package/apps/storefront/src/pages/[...slug].astro +36 -2
  127. package/apps/storefront/src/pages/admin.astro +29 -3
  128. package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +60 -0
  129. package/apps/storefront/src/pages/api/apps/admin/health.ts +44 -0
  130. package/apps/storefront/src/pages/api/apps/admin/install.ts +100 -0
  131. package/apps/storefront/src/pages/api/apps/admin/list.ts +26 -0
  132. package/apps/storefront/src/pages/api/apps/admin/resume.ts +56 -0
  133. package/apps/storefront/src/pages/api/apps/admin/suspend.ts +57 -0
  134. package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +74 -0
  135. package/apps/storefront/src/pages/api/apps/admin/update.ts +103 -0
  136. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +59 -0
  137. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +51 -0
  138. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +172 -0
  139. package/apps/storefront/src/pages/api/apps/oauth/token.ts +87 -0
  140. package/apps/storefront/src/pages/api/apps/v1/attribution.ts +142 -0
  141. package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +39 -0
  142. package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +49 -0
  143. package/apps/storefront/src/pages/api/apps/v1/health.ts +32 -0
  144. package/apps/storefront/src/pages/api/apps/v1/inventory.ts +59 -0
  145. package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +48 -0
  146. package/apps/storefront/src/pages/api/apps/v1/orders.ts +73 -0
  147. package/apps/storefront/src/pages/api/apps/v1/reports.ts +21 -0
  148. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +62 -0
  149. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +47 -0
  150. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +48 -1
  151. package/apps/storefront/src/pages/api/auth/step-up-send.ts +57 -0
  152. package/apps/storefront/src/pages/api/auth/step-up-verify.ts +129 -0
  153. package/apps/storefront/src/pages/api/auth/verify.ts +23 -0
  154. package/apps/storefront/src/pages/api/compliance/preflight.ts +206 -0
  155. package/apps/storefront/src/pages/api/rum/vitals.ts +54 -0
  156. package/apps/storefront/src/pages/api/test/dev-session.ts +133 -0
  157. package/apps/storefront/src/pages/auth/login.astro +197 -41
  158. package/apps/storefront/src/pages/auth/magic.astro +75 -43
  159. package/apps/storefront/src/pages/blog/[slug].astro +107 -0
  160. package/apps/storefront/src/pages/blog/index.astro +98 -0
  161. package/apps/storefront/src/pages/capabilities.astro +8 -0
  162. package/apps/storefront/src/pages/cockpit.astro +496 -74
  163. package/apps/storefront/src/pages/collections/[handle].astro +8 -0
  164. package/apps/storefront/src/pages/collections/index.astro +10 -2
  165. package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +119 -0
  166. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +5 -0
  167. package/apps/storefront/src/pages/index.astro +57 -0
  168. package/apps/storefront/src/pages/llms.txt.ts +31 -10
  169. package/apps/storefront/src/pages/products/[handle].astro +100 -9
  170. package/apps/storefront/src/pages/sitemap.xml.ts +21 -4
  171. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +198 -0
  172. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +7 -0
  173. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +7 -0
  174. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -0
  175. package/apps/storefront/src/pages/style-guide/index.astro +10 -0
  176. package/apps/storefront/src/styles/fonts.css +54 -0
  177. package/apps/storefront/src/styles/global.css +22 -2
  178. package/apps/storefront/src/themes/schema.ts +3 -25
  179. package/apps/storefront/tsconfig.json +1 -1
  180. package/apps/storefront/vitest.config.ts +4 -1
  181. package/package.json +1 -1
  182. package/packages/public-runtime/src/candidate-index.ts +311 -0
  183. package/packages/public-runtime/src/checkout.ts +94 -2
  184. package/packages/public-runtime/src/compliance/evaluate.ts +265 -0
  185. package/packages/public-runtime/src/compliance/evidence-signals.ts +81 -0
  186. package/packages/public-runtime/src/compliance/index.ts +22 -0
  187. package/packages/public-runtime/src/compliance/pact-report.ts +94 -0
  188. package/packages/public-runtime/src/compliance/profile.ts +198 -0
  189. package/packages/public-runtime/src/compliance/ruleset.ts +117 -0
  190. package/packages/public-runtime/src/compliance/verification.ts +81 -0
  191. package/packages/public-runtime/src/csp.ts +27 -3
  192. package/packages/public-runtime/src/customization-reconcile.ts +35 -0
  193. package/packages/public-runtime/src/customization-runtime.ts +8 -0
  194. package/packages/public-runtime/src/customization-versioning.ts +17 -0
  195. package/packages/public-runtime/src/extension-contract.ts +2 -1
  196. package/packages/public-runtime/src/hash.ts +25 -0
  197. package/packages/public-runtime/src/index.ts +6 -0
  198. package/packages/public-runtime/src/membership.ts +353 -0
  199. package/packages/public-runtime/src/product.ts +24 -2
  200. package/packages/public-runtime/src/review-trust-proof.ts +194 -0
  201. package/packages/public-runtime/src/tenant-assets.ts +40 -5
  202. package/packages/public-runtime/src/tenant.ts +236 -0
  203. package/packages/public-runtime/src/widget-postmessage.ts +205 -0
  204. package/scripts/dev/publish.mjs +158 -0
  205. package/scripts/dev/transient-files.mjs +2 -1
  206. package/tenants/home/public/fonts/inter-latin-400-normal.woff2 +0 -0
  207. package/tenants/home/public/fonts/inter-latin-500-normal.woff2 +0 -0
  208. package/tenants/home/public/fonts/inter-latin-600-normal.woff2 +0 -0
  209. package/tenants/home/public/fonts/inter-latin-ext-400-normal.woff2 +0 -0
  210. package/tenants/home/public/fonts/inter-latin-ext-500-normal.woff2 +0 -0
  211. package/tenants/home/public/fonts/inter-latin-ext-600-normal.woff2 +0 -0
  212. package/tenants/home/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  213. package/tenants/home/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  214. package/tenants/home/public/fonts/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  215. package/tenants/home/public/fonts/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  216. package/tenants/home/public/fonts/plus-jakarta-sans-latin-500-normal.woff2 +0 -0
  217. package/tenants/home/public/fonts/plus-jakarta-sans-latin-600-normal.woff2 +0 -0
  218. package/tenants/home/public/fonts/plus-jakarta-sans-latin-700-normal.woff2 +0 -0
  219. package/tenants/home/public/fonts/plus-jakarta-sans-latin-800-normal.woff2 +0 -0
  220. package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-500-normal.woff2 +0 -0
  221. package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-600-normal.woff2 +0 -0
  222. package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-700-normal.woff2 +0 -0
  223. package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-800-normal.woff2 +0 -0
  224. package/tenants/home/public/pages/storefront.css +23 -0
  225. package/apps/storefront/src/lib/dev/hostedCockpit.ts +0 -169
@@ -64,6 +64,21 @@ import { tenantHotReload } from "./dev-plugins/tenant-hot-reload.mjs";
64
64
  // facets, search typeahead, mobile drawer) — near-zero JS everywhere else.
65
65
  export default defineConfig({
66
66
  output: "server",
67
+ // Prefetch (finding 5): every nav today is a cold MPA reload. Enable Astro's
68
+ // prefetch so key navigation + product-card links warm the next SSR response
69
+ // on intent. OPT-IN (prefetchAll:false) — only links carrying
70
+ // `data-astro-prefetch` prefetch (nav items, product/collection cards), so we
71
+ // don't blanket-fetch cart/auth/account/wishlist or over-fetch on load. The
72
+ // 'hover' default strategy fires on mouseenter/focus (intent-driven, cheap on
73
+ // bandwidth), degrading to tap on touch. Works on output:'server' with NO
74
+ // prerendered pages — prefetch just issues the same GET the click would.
75
+ // NOTE: this is prefetch WITHOUT View Transitions; no <ClientRouter> is added
76
+ // (see DECISIONS — it would need every once-run island/inline script rewired
77
+ // to astro:page-load and risks the CSP-nonce/age-gate paths).
78
+ prefetch: {
79
+ prefetchAll: false,
80
+ defaultStrategy: "hover",
81
+ },
67
82
  // Externalize all CSS (no auto-inlined <style>) so a strict CSP needs no
68
83
  // inline-style allowance for elements — every stylesheet is a 'self' <link>.
69
84
  // Small first-paint tradeoff, offset by cross-page caching (see LIMITATIONS).
@@ -0,0 +1,55 @@
1
+ // @ts-check
2
+ import { handlePublish } from "../../../scripts/dev/publish.mjs";
3
+
4
+ // C5 · Publish in browser — server side. Dev-only (`apply: "serve"`), never
5
+ // reaches the production Worker. Maps POST /__tot/publish {path} onto the
6
+ // candidate_open flow in scripts/dev/publish.mjs — the local-loop counterpart
7
+ // to the hosted cockpit's /api/dev/publish.ts (unit s6), reusing the SAME
8
+ // per-actor deriveChangeId idempotency `tot submit` relies on so repeat
9
+ // publishes update ONE PR instead of piling up duplicates.
10
+
11
+ /** @param {any} req @returns {Promise<string>} */
12
+ function readBody(req) {
13
+ return new Promise((resolve) => {
14
+ let data = "";
15
+ req.on("data", (/** @type {any} */ c) => {
16
+ data += c;
17
+ if (data.length > 4 * 1024 * 1024) req.destroy();
18
+ });
19
+ req.on("end", () => resolve(data));
20
+ req.on("error", () => resolve(data));
21
+ });
22
+ }
23
+
24
+ export function devPublish() {
25
+ /** @type {any} */
26
+ const plugin = {
27
+ name: "tot:dev-publish",
28
+ apply: "serve",
29
+ /** @param {any} server */
30
+ configureServer(server) {
31
+ server.middlewares.use(
32
+ async (/** @type {any} */ req, /** @type {any} */ res, /** @type {any} */ next) => {
33
+ if (req.method !== "POST" || !req.url || req.url.split("?")[0] !== "/__tot/publish") {
34
+ return next();
35
+ }
36
+ let body;
37
+ try {
38
+ body = JSON.parse((await readBody(req)) || "{}");
39
+ } catch {
40
+ res.statusCode = 400;
41
+ res.setHeader("content-type", "application/json");
42
+ res.end(JSON.stringify({ ok: false, status: "bad_json", message: "invalid JSON body" }));
43
+ return;
44
+ }
45
+ const out = await handlePublish(process.env, body);
46
+ res.statusCode = out.status;
47
+ res.setHeader("content-type", "application/json");
48
+ res.setHeader("cache-control", "no-store");
49
+ res.end(JSON.stringify(out.json));
50
+ },
51
+ );
52
+ },
53
+ };
54
+ return plugin;
55
+ }
@@ -1,8 +1,10 @@
1
1
  // @ts-check
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { readdirSync, existsSync } from "node:fs";
4
- import { dirname, join } from "node:path";
4
+ import { basename, dirname, join } from "node:path";
5
5
  import { markSave, reportServerUp } from "./dev-loop-state.mjs";
6
+ import { isTransientWatchFile } from "../../../scripts/dev/transient-files.mjs";
7
+ import { readFile } from "node:fs/promises";
6
8
 
7
9
  // Repo-root tenants/ dir (colocated tenant layout) + the generated static dest
8
10
  // the predev copy step mirrors public/ into. This module lives at
@@ -11,11 +13,40 @@ import { markSave, reportServerUp } from "./dev-loop-state.mjs";
11
13
  const TENANTS_DIR = fileURLToPath(new URL("../../../tenants", import.meta.url)).replace(/\\/g, "/");
12
14
  const PUBLIC_TENANTS_DEST = fileURLToPath(new URL("../public/tenants", import.meta.url));
13
15
 
16
+ /**
17
+ * Load a TS module's exports for Node-side (plugin) use, reusing Vite's own
18
+ * transform pipeline instead of a plain `import` (which can't parse `.ts`) or
19
+ * `ssrLoadModule` (which requires the 'ssr' environment to be Node-runnable —
20
+ * it isn't here, since the Cloudflare adapter's ssr environment runs in
21
+ * workerd). `src/themes/schema.ts` and `reference.ts` only ever import types
22
+ * (fully erased by the transform), so the transformed output has zero runtime
23
+ * imports left to resolve — safe to eval directly via a data: URL import.
24
+ * @param {any} server @param {string} url
25
+ */
26
+ async function loadTsModule(server, url) {
27
+ const result = await server.transformRequest(url);
28
+ if (!result) throw new Error(`transformRequest returned nothing for ${url}`);
29
+ // Vite's dev transform prepends module-graph bookkeeping imports (e.g. a
30
+ // preact-debug preamble, HMR deps) that only resolve inside Vite's own
31
+ // module graph, not a bare data: URL import. schema.ts/reference.ts only
32
+ // ever import types (fully erased already), so stripping every top-level
33
+ // `import ...;` line leaves the real, self-contained function/const
34
+ // exports intact.
35
+ const code = result.code.replace(/^import\s.*;\s*$/gm, "");
36
+ const dataUrl = `data:text/javascript;base64,${Buffer.from(code, "utf8").toString("base64")}`;
37
+ return import(dataUrl);
38
+ }
39
+
14
40
  /**
15
41
  * Classify a watched absolute path as a tenant file: → { id, rest } where `rest`
16
42
  * 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.
43
+ * the path isn't under a tenant OR is an editor/OS transient file (vim swap,
44
+ * emacs autosave, `.DS_Store`, a dotfile, …) those are watcher noise, not real
45
+ * content, so neither reload logic nor the activity Monitor should ever see them
46
+ * (a vim `.home.html.swp` add/change/unlink was leaking into the developer's
47
+ * "Your activity" ledger before this check). Shared by handleHotUpdate (change)
48
+ * and the add/unlink watcher handlers so both agree on the content/-vs-public/
49
+ * split — and on what counts as noise.
19
50
  * @param {string} file
20
51
  * @returns {{ id: string, rest: string } | null}
21
52
  */
@@ -25,7 +56,9 @@ export function classifyTenantFile(file) {
25
56
  const rel = f.slice(TENANTS_DIR.length + 1); // "<id>/<...>"
26
57
  const slash = rel.indexOf("/");
27
58
  if (slash < 0) return null;
28
- return { id: rel.slice(0, slash), rest: rel.slice(slash + 1) };
59
+ const rest = rel.slice(slash + 1);
60
+ if (isTransientWatchFile(basename(rest))) return null;
61
+ return { id: rel.slice(0, slash), rest };
29
62
  }
30
63
 
31
64
  /** True for the raw content HTML that's read fresh from disk per request. @param {string} rest */
@@ -33,6 +66,22 @@ export function isContentHtml(rest) {
33
66
  return rest.startsWith("content/") && rest.endsWith(".html");
34
67
  }
35
68
 
69
+ /**
70
+ * Strip the documentation keys (`_vendor`, `_note`, `$schema`, …) a tenant's
71
+ * theme.json may carry, mirroring src/themes/tenants/index.ts's stripMeta
72
+ * exactly — both must agree on what counts as a real token vs. a comment key.
73
+ * @param {Record<string, unknown>} parsed
74
+ */
75
+ export function stripThemeMeta(parsed) {
76
+ /** @type {Record<string, unknown>} */
77
+ const out = {};
78
+ for (const [k, v] of Object.entries(parsed)) {
79
+ if (k.startsWith("_") || k.startsWith("$")) continue;
80
+ out[k] = v;
81
+ }
82
+ return out;
83
+ }
84
+
36
85
  /**
37
86
  * Is this tenant opted out of static-asset mirroring (R2-versioned)? Matches
38
87
  * scripts/build/copy-tenant-assets.mjs's isVersioned EXACTLY (both the marker
@@ -120,9 +169,12 @@ export function seedKnown(root, known) {
120
169
  // rawHtmlResponse in src/lib/rawMarketingHtml.ts.)
121
170
  // - public/** — re-mirror the one changed asset into public/tenants/<id>/
122
171
  // (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.
172
+ // content/*.json and other themes/*.json globs are ordinary glob imports, so
173
+ // Vite already invalidates + reloads them — left to default handling. A
174
+ // tenant's own theme.json (D1) is the one exception: instead of the default
175
+ // reload, handleHotUpdate below recomputes its CSS vars and patches them into
176
+ // the open page in place. Returning [] suppresses the pointless glob HMR that
177
+ // would otherwise fire for the raw files.
126
178
  //
127
179
  // RENAME/DELETE (C2): handleHotUpdate fires only on "change". Empirically, an
128
180
  // add/unlink of a content/*.html file already triggers a correct Vite full-reload
@@ -231,6 +283,33 @@ export function tenantHotReload() {
231
283
  return [];
232
284
  }
233
285
 
286
+ // theme.json (D1): the only thing a save actually changes is the CSS
287
+ // custom-property values themeToCssVars() already serializes per request
288
+ // — recompute them the SAME way the SSR path does (middleware/index.ts)
289
+ // and patch them into the open page in place instead of reloading it.
290
+ // mergeTheme/themeToCssVars live in src/themes/schema.ts, a TS module
291
+ // only reachable through Vite's own transform pipeline — a plain Node
292
+ // `import` from this .mjs file can't load it, and `ssrLoadModule`
293
+ // requires the 'ssr' environment to be Node-runnable, which it isn't
294
+ // under the Cloudflare adapter (that environment runs in workerd). So
295
+ // loadTsModule() reuses the transform pipeline directly instead.
296
+ if (rest === "theme.json") {
297
+ try {
298
+ const raw = await readFile(ctx.file, "utf8");
299
+ const override = stripThemeMeta(JSON.parse(raw));
300
+ const { mergeTheme, themeToCssVars } = await loadTsModule(ctx.server, "/src/themes/schema.ts");
301
+ const { referenceTheme } = await loadTsModule(ctx.server, "/src/themes/reference.ts");
302
+ const css = themeToCssVars(mergeTheme(referenceTheme, override));
303
+ ctx.server.ws.send({ type: "custom", event: "tot:theme-vars", data: { css } });
304
+ return [];
305
+ } catch {
306
+ // Malformed theme.json mid-edit, or the module graph failed to
307
+ // load — degrade to the default full-reload rather than leaving
308
+ // the page on stale CSS vars or hanging silently.
309
+ return undefined;
310
+ }
311
+ }
312
+
234
313
  return undefined;
235
314
  },
236
315
  };
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+
3
+ // Generate-only config for the STOREFRONT_APPS_DB lineage — sibling to
4
+ // drizzle.config.ts (catalog). Same dialect/driver; diffs schema-apps.ts into
5
+ // numbered SQL under migrations-apps/, which `wrangler d1 migrations apply
6
+ // STOREFRONT_APPS_DB` runs. Kept as a separate config (not a second `schema`
7
+ // entry on the catalog config) so each D1 gets its own independent migration
8
+ // lineage/journal — the whole point of splitting the database.
9
+ export default defineConfig({
10
+ dialect: "sqlite",
11
+ schema: "./src/lib/d1/schema-apps.ts",
12
+ out: "./migrations-apps",
13
+ });
@@ -159,6 +159,16 @@ declare global {
159
159
  };
160
160
  /** Cloudflare runtime (env bindings) — provided by the adapter on Workers. */
161
161
  runtime?: { env: CloudflareEnv };
162
+ /**
163
+ * Every private-app widget placement actually rendered for this request
164
+ * (PrivateApps epic, D5). The middleware seeds this to `[]` before
165
+ * `next()`; each page/layout that renders a widget slot (`renderSlot.ts`)
166
+ * pushes its resolved placements onto it as the page renders, so the
167
+ * middleware can read the full per-request set back out AFTER `next()`
168
+ * to extend the CSP's `frame-src` (`appWidgetCspAdditions`) with exactly
169
+ * the origins that ended up on the page — never more.
170
+ */
171
+ activeWidgetPlacements?: { endpoint: string }[];
162
172
  }
163
173
  }
164
174
  }
@@ -1,14 +1,19 @@
1
1
  # D1 provision migrations (Drizzle Kit)
2
2
 
3
- The catalog + customization D1 schema as numbered migrations, applied by `provision.sh`
4
- (the **provision** lifecycle phase — never `deploy`, never runtime). Generated by
5
- **Drizzle Kit** (ADR-0002), applied by `wrangler d1 migrations apply`.
3
+ The catalog + customization D1 (`CATALOG_DB`) schema as numbered migrations, applied by
4
+ `provision.sh` (the **provision** lifecycle phase — never `deploy`, never runtime). Generated
5
+ by **Drizzle Kit** (ADR-0002), applied by `wrangler d1 migrations apply`.
6
+
7
+ > The storefront-apps registry/webhooks/orders tables have their OWN D1
8
+ > (`STOREFRONT_APPS_DB`) + config (`drizzle.config.apps.ts`) + lineage
9
+ > (`../migrations-apps/`) — this directory holds ONLY catalog + customization.
6
10
 
7
11
  ## Source of truth
8
12
 
9
- - **`src/lib/d1/schema.ts`** (Drizzle schema) is the source of truth for migration
10
- *generation*. `drizzle-kit generate` diffs it into numbered `migrations/*.sql`
11
- (+ `meta/` journal) and owns the SQLite table-rebuild logic for `ALTER`s.
13
+ - **`src/lib/d1/schema.ts`** (Drizzle schema, catalog + customization ONLY) is the
14
+ source of truth for migration *generation*. `drizzle-kit generate` diffs it into
15
+ numbered `migrations/*.sql` (+ `meta/` journal) and owns the SQLite table-rebuild
16
+ logic for `ALTER`s.
12
17
  - **`products_fts`** (FTS5 virtual table) can't be modelled by Drizzle — it's a custom
13
18
  migration (`0001_products_fts.sql`), hand-maintained.
14
19
  - The raw-SQL read model + ETL still consume `CATALOG_D1_SCHEMA`/`CUSTOMIZATION_D1_SCHEMA`
@@ -51,4 +56,5 @@ wrangler d1 migrations apply CATALOG_DB --env <env> [--remote|--local]
51
56
  ```
52
57
 
53
58
  > Catalog + customization tables share the **one** `CATALOG_DB` binding (one edge DB),
54
- > per `src/lib/customization/d1Store.ts`.
59
+ > per `src/lib/customization/d1Store.ts`. The storefront-apps tables are a SEPARATE D1
60
+ > (`STOREFRONT_APPS_DB`) applied the same way, from `../migrations-apps/`.
@@ -0,0 +1,129 @@
1
+ CREATE TABLE `app_attributions` (
2
+ `install_id` text NOT NULL,
3
+ `attribution_id` text NOT NULL,
4
+ `tenant_id` text NOT NULL,
5
+ `order_id` text NOT NULL,
6
+ `campaign` text,
7
+ `referral_code` text,
8
+ `commission_amount` real,
9
+ `commission_currency` text,
10
+ `customer_hash` text,
11
+ `recorded_at` text NOT NULL,
12
+ PRIMARY KEY(`install_id`, `attribution_id`)
13
+ );
14
+ --> statement-breakpoint
15
+ CREATE INDEX `idx_app_attributions_tenant_order` ON `app_attributions` (`tenant_id`,`order_id`);--> statement-breakpoint
16
+ CREATE TABLE `app_audit_log` (
17
+ `id` text PRIMARY KEY NOT NULL,
18
+ `install_id` text NOT NULL,
19
+ `tenant_id` text NOT NULL,
20
+ `event` text NOT NULL,
21
+ `actor` text NOT NULL,
22
+ `trace_id` text,
23
+ `detail` text DEFAULT '{}' NOT NULL,
24
+ `at` text NOT NULL
25
+ );
26
+ --> statement-breakpoint
27
+ CREATE INDEX `idx_app_audit_install` ON `app_audit_log` (`install_id`);--> statement-breakpoint
28
+ CREATE TABLE `app_credentials` (
29
+ `install_id` text PRIMARY KEY NOT NULL,
30
+ `client_id` text NOT NULL,
31
+ `secret_hash` text NOT NULL,
32
+ `secret_created_at` text NOT NULL,
33
+ `rotated_at` text,
34
+ `jwks_kid` text NOT NULL
35
+ );
36
+ --> statement-breakpoint
37
+ CREATE UNIQUE INDEX `app_credentials_client_id_unique` ON `app_credentials` (`client_id`);--> statement-breakpoint
38
+ CREATE TABLE `app_installs` (
39
+ `install_id` text PRIMARY KEY NOT NULL,
40
+ `app_id` text NOT NULL,
41
+ `app_version` text NOT NULL,
42
+ `tenant_id` text NOT NULL,
43
+ `env` text NOT NULL,
44
+ `status` text DEFAULT 'active' NOT NULL,
45
+ `scopes` text DEFAULT '[]' NOT NULL,
46
+ `manifest` text NOT NULL,
47
+ `webhook_endpoint` text,
48
+ `widget_placements` text DEFAULT '[]' NOT NULL,
49
+ `install_actor` text NOT NULL,
50
+ `created_at` text NOT NULL,
51
+ `updated_at` text NOT NULL
52
+ );
53
+ --> statement-breakpoint
54
+ CREATE UNIQUE INDEX `uq_app_installs_app_tenant_env` ON `app_installs` (`app_id`,`tenant_id`,`env`);--> statement-breakpoint
55
+ CREATE INDEX `idx_app_installs_tenant` ON `app_installs` (`tenant_id`);--> statement-breakpoint
56
+ CREATE TABLE `idempotency_keys` (
57
+ `install_id` text NOT NULL,
58
+ `idempotency_key` text NOT NULL,
59
+ `body_hash` text NOT NULL,
60
+ `response_body` text NOT NULL,
61
+ `created_at` text NOT NULL,
62
+ PRIMARY KEY(`install_id`, `idempotency_key`)
63
+ );
64
+ --> statement-breakpoint
65
+ CREATE TABLE `orders` (
66
+ `tenant_id` text NOT NULL,
67
+ `external_order_id` text NOT NULL,
68
+ `env` text NOT NULL,
69
+ `status` text NOT NULL,
70
+ `currency` text NOT NULL,
71
+ `total` real DEFAULT 0 NOT NULL,
72
+ `subtotal` real,
73
+ `customer_hash` text,
74
+ `line_items` text DEFAULT '[]' NOT NULL,
75
+ `created_at` text NOT NULL,
76
+ `updated_at` text NOT NULL,
77
+ PRIMARY KEY(`tenant_id`, `external_order_id`)
78
+ );
79
+ --> statement-breakpoint
80
+ CREATE INDEX `idx_orders_tenant` ON `orders` (`tenant_id`);--> statement-breakpoint
81
+ CREATE TABLE `webhook_deliveries` (
82
+ `delivery_id` text PRIMARY KEY NOT NULL,
83
+ `event_id` text NOT NULL,
84
+ `install_id` text NOT NULL,
85
+ `tenant_id` text NOT NULL,
86
+ `topic` text NOT NULL,
87
+ `state` text DEFAULT 'pending' NOT NULL,
88
+ `attempts` integer DEFAULT 0 NOT NULL,
89
+ `last_status` integer,
90
+ `last_error_class` text,
91
+ `last_trace_id` text,
92
+ `next_attempt_at` text,
93
+ `created_at` text NOT NULL,
94
+ `updated_at` text NOT NULL
95
+ );
96
+ --> statement-breakpoint
97
+ CREATE INDEX `idx_webhook_deliveries_install` ON `webhook_deliveries` (`install_id`);--> statement-breakpoint
98
+ CREATE INDEX `idx_webhook_deliveries_due` ON `webhook_deliveries` (`state`,`next_attempt_at`);--> statement-breakpoint
99
+ CREATE TABLE `webhook_delivery_attempts` (
100
+ `id` text PRIMARY KEY NOT NULL,
101
+ `delivery_id` text NOT NULL,
102
+ `attempt_no` integer NOT NULL,
103
+ `started_at` text NOT NULL,
104
+ `duration_ms` integer,
105
+ `http_status` integer,
106
+ `error_class` text,
107
+ `trace_id` text
108
+ );
109
+ --> statement-breakpoint
110
+ CREATE INDEX `idx_webhook_delivery_attempts_delivery` ON `webhook_delivery_attempts` (`delivery_id`);--> statement-breakpoint
111
+ CREATE TABLE `webhook_events` (
112
+ `id` text PRIMARY KEY NOT NULL,
113
+ `tenant_id` text NOT NULL,
114
+ `topic` text NOT NULL,
115
+ `dataschema_version` text NOT NULL,
116
+ `payload` text NOT NULL,
117
+ `created_at` text NOT NULL
118
+ );
119
+ --> statement-breakpoint
120
+ CREATE TABLE `webhook_install_health` (
121
+ `install_id` text PRIMARY KEY NOT NULL,
122
+ `last_success_at` text,
123
+ `failure_streak` integer DEFAULT 0 NOT NULL,
124
+ `dlq_count` integer DEFAULT 0 NOT NULL,
125
+ `last_status` integer,
126
+ `last_error_class` text,
127
+ `recent_trace_ids` text DEFAULT '[]' NOT NULL,
128
+ `updated_at` text NOT NULL
129
+ );