@tokenoftrust/storefront-runner 1.3.2 → 1.3.4-rc.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 (79) hide show
  1. package/apps/storefront/astro.config.mjs +59 -0
  2. package/apps/storefront/env.d.ts +24 -8
  3. package/apps/storefront/package.json +1 -1
  4. package/apps/storefront/src/components/admin/{AdminFoxyCartSettingsPanel.astro → AdminCheckoutSettingsPanel.astro} +50 -51
  5. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  6. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +613 -0
  7. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +3 -3
  8. package/apps/storefront/src/components/chrome/SiteHeader.astro +3 -4
  9. package/apps/storefront/src/components/commerce/{FoxySidecartStyles.astro → CartDrawerStyles.astro} +13 -14
  10. package/apps/storefront/src/components/commerce/CheckoutLoader.astro +21 -0
  11. package/apps/storefront/src/components/islands/VariantSelector.tsx +101 -20
  12. package/apps/storefront/src/config/adminTargetTenant.ts +2 -2
  13. package/apps/storefront/src/config/devTenantSeed.ts +65 -16
  14. package/apps/storefront/src/config/resolver.ts +48 -1
  15. package/apps/storefront/src/layouts/Layout.astro +14 -12
  16. package/apps/storefront/src/lib/admin/README.md +2 -2
  17. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +5 -5
  18. package/apps/storefront/src/lib/admin/orderClient.ts +20 -20
  19. package/apps/storefront/src/lib/admin/services/mock/{foxyCartSettings.ts → checkoutSettings.ts} +21 -22
  20. package/apps/storefront/src/lib/admin/services/mock/index.ts +3 -2
  21. package/apps/storefront/src/lib/admin/services/mock/settings.ts +1 -1
  22. package/apps/storefront/src/lib/admin/services/mock/subscriptions.ts +398 -0
  23. package/apps/storefront/src/lib/admin/services/orders.ts +21 -21
  24. package/apps/storefront/src/lib/admin/viewmodels/subscriptions.ts +106 -0
  25. package/apps/storefront/src/lib/auth/loginGate.ts +14 -3
  26. package/apps/storefront/src/lib/{foxyCommerce.ts → checkoutCommerce.ts} +20 -18
  27. package/apps/storefront/src/lib/commerce/{foxyCartSettings.ts → checkoutSettings.ts} +65 -66
  28. package/apps/storefront/src/lib/dev/activityIngestToken.ts +9 -0
  29. package/apps/storefront/src/lib/dev/activityStore.ts +12 -0
  30. package/apps/storefront/src/lib/dev/cliSignInCode.ts +23 -4
  31. package/apps/storefront/src/lib/dev/runtimeStore.ts +193 -22
  32. package/apps/storefront/src/lib/edgeCache.ts +7 -6
  33. package/apps/storefront/src/lib/health.ts +53 -0
  34. package/apps/storefront/src/lib/privacy/emailHint.ts +33 -0
  35. package/apps/storefront/src/lib/rawMarketingHtml.ts +1 -0
  36. package/apps/storefront/src/lib/sections.ts +19 -23
  37. package/apps/storefront/src/lib/securityHeaders.ts +1 -1
  38. package/apps/storefront/src/lib/storyblok/content-model.ts +3 -2
  39. package/apps/storefront/src/middleware/index.ts +29 -12
  40. package/apps/storefront/src/pages/[...slug].astro +5 -6
  41. package/apps/storefront/src/pages/admin.astro +10 -3
  42. package/apps/storefront/src/pages/api/admin/orders/[id].ts +4 -4
  43. package/apps/storefront/src/pages/api/admin/orders.ts +5 -5
  44. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +14 -7
  45. package/apps/storefront/src/pages/api/checkout-style.css.ts +5 -5
  46. package/apps/storefront/src/pages/auth/login.astro +4 -1
  47. package/apps/storefront/src/pages/auth/magic.astro +263 -43
  48. package/apps/storefront/src/pages/capabilities.astro +1 -1
  49. package/apps/storefront/src/pages/cockpit.astro +2365 -0
  50. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +19 -0
  51. package/apps/storefront/src/pages/index.astro +23 -29
  52. package/apps/storefront/src/pages/products/[handle].astro +50 -20
  53. package/package.json +6 -2
  54. package/packages/public-runtime/src/capabilities.ts +97 -9
  55. package/packages/public-runtime/src/{foxy-orders.ts → checkout-orders.ts} +28 -27
  56. package/packages/public-runtime/src/checkout-style-publish.ts +25 -25
  57. package/packages/public-runtime/src/checkout-style.ts +13 -13
  58. package/packages/public-runtime/src/{foxy.ts → checkout.ts} +111 -34
  59. package/packages/public-runtime/src/csp.ts +11 -11
  60. package/packages/public-runtime/src/customization-scripts.ts +28 -0
  61. package/packages/public-runtime/src/extension-contract.ts +73 -0
  62. package/packages/public-runtime/src/index.ts +4 -2
  63. package/packages/public-runtime/src/product.ts +57 -0
  64. package/packages/public-runtime/src/raw-html-policy.ts +35 -0
  65. package/packages/public-runtime/src/tenant.ts +14 -11
  66. package/pnpm-workspace.yaml +1 -0
  67. package/scripts/dev/checkout-watch.mjs +9 -0
  68. package/scripts/dev/file-browser.mjs +5 -1
  69. package/scripts/dev/transient-files.mjs +34 -0
  70. package/scripts/tot-dev.mjs +16 -3
  71. package/apps/storefront/src/components/commerce/FoxyLoader.astro +0 -23
  72. package/apps/storefront/src/pages/api/dev/activity.ts +0 -124
  73. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +0 -73
  74. package/apps/storefront/src/pages/api/dev/diff.ts +0 -68
  75. package/apps/storefront/src/pages/api/dev/file.ts +0 -57
  76. package/apps/storefront/src/pages/api/dev/locate.ts +0 -125
  77. package/apps/storefront/src/pages/api/dev/publish.ts +0 -128
  78. package/apps/storefront/src/pages/api/dev/tree.ts +0 -70
  79. package/apps/storefront/src/pages/api/dev/write.ts +0 -116
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * REAL orders view model (card 8585 / U3) — replaces the deterministic mock as
3
- * the Orders tab's data source. Fetches the tenant's orders from tot-foxycart via
4
- * U2's order client and maps the frozen DTO (`@tot/public-runtime` foxy-orders)
3
+ * the Orders tab's data source. Fetches the tenant's checkout orders via U2's
4
+ * order client and maps the frozen DTO (`@tot/public-runtime` checkout-orders)
5
5
  * into the EXISTING `OrdersViewModel` shape, so `OrdersWorkspace.astro` renders
6
6
  * unchanged. Read-only (scope-locks) — no write paths.
7
7
  *
@@ -19,11 +19,11 @@
19
19
  * `verificationDetail` slots — no shape change needed.
20
20
  */
21
21
  import type {
22
- FoxyOrderDetail,
23
- FoxyOrderListFilters,
24
- FoxyOrderPage,
25
- FoxyOrderSummary,
26
- FoxyOrderVerification,
22
+ CheckoutOrderDetail,
23
+ CheckoutOrderListFilters,
24
+ CheckoutOrderPage,
25
+ CheckoutOrderSummary,
26
+ CheckoutOrderVerification,
27
27
  } from "@tot/public-runtime";
28
28
  import type {
29
29
  OrderDetail,
@@ -34,7 +34,7 @@ import type {
34
34
  OrderShipState,
35
35
  OrdersViewModel,
36
36
  } from "../viewmodels/orders";
37
- import type { FoxyOrderClient } from "../orderClient";
37
+ import type { CheckoutOrderClient } from "../orderClient";
38
38
 
39
39
  /** Next-step label, mirroring the mock's queue semantics. */
40
40
  function deriveNextStep(fulfillment: OrderShipState, blocker: string | null): string {
@@ -47,7 +47,7 @@ function deriveNextStep(fulfillment: OrderShipState, blocker: string | null): st
47
47
 
48
48
  /** Provisional risk until U4 fills `verification`. Prefer a real riskLevel when present. */
49
49
  function deriveRisk(
50
- verification: FoxyOrderVerification | null,
50
+ verification: CheckoutOrderVerification | null,
51
51
  blocker: string | null,
52
52
  ): OrderRow["riskLevel"] {
53
53
  const r = verification?.riskLevel;
@@ -57,7 +57,7 @@ function deriveRisk(
57
57
  }
58
58
 
59
59
  /** Map a DTO summary to the view model's OrderRow. */
60
- export function mapSummaryToRow(s: FoxyOrderSummary): OrderRow {
60
+ export function mapSummaryToRow(s: CheckoutOrderSummary): OrderRow {
61
61
  return {
62
62
  id: s.id,
63
63
  placedAt: s.placedAt,
@@ -76,7 +76,7 @@ export function mapSummaryToRow(s: FoxyOrderSummary): OrderRow {
76
76
  }
77
77
 
78
78
  /** Commerce-derived readiness checks (U4 enriches the identity dimension). */
79
- function deriveReadinessChecks(d: FoxyOrderDetail): OrderReadinessCheck[] {
79
+ function deriveReadinessChecks(d: CheckoutOrderDetail): OrderReadinessCheck[] {
80
80
  const ageHold = d.blocker === "Age verification hold";
81
81
  return [
82
82
  {
@@ -119,7 +119,7 @@ function deriveReadinessChecks(d: FoxyOrderDetail): OrderReadinessCheck[] {
119
119
  }
120
120
 
121
121
  /** Base commerce evidence events (U4 replaces with the real verification timeline). */
122
- function deriveEvidenceTimeline(d: FoxyOrderDetail): OrderEvidenceEvent[] {
122
+ function deriveEvidenceTimeline(d: CheckoutOrderDetail): OrderEvidenceEvent[] {
123
123
  const events: OrderEvidenceEvent[] = [
124
124
  { title: "Order imported", detail: "Storefront order became available in the admin queue.", at: d.placedAt },
125
125
  { title: "Payment authorized", detail: "The customer completed checkout and payment was authorized.", at: d.placedAt },
@@ -131,7 +131,7 @@ function deriveEvidenceTimeline(d: FoxyOrderDetail): OrderEvidenceEvent[] {
131
131
  }
132
132
 
133
133
  /** Map a DTO detail (its summary already mapped to `row`) to the view model's OrderDetail. */
134
- export function mapDetailToDetail(d: FoxyOrderDetail, row: OrderRow): OrderDetail {
134
+ export function mapDetailToDetail(d: CheckoutOrderDetail, row: OrderRow): OrderDetail {
135
135
  return {
136
136
  ...row,
137
137
  lineItems: d.lineItems.map((li) => ({
@@ -168,8 +168,8 @@ function buildQueues(rows: OrderRow[]): OrderQueueSummary[] {
168
168
  */
169
169
  export function mapOrdersToViewModel(
170
170
  targetTenant: string,
171
- summaries: FoxyOrderSummary[],
172
- details: Record<string, FoxyOrderDetail>,
171
+ summaries: CheckoutOrderSummary[],
172
+ details: Record<string, CheckoutOrderDetail>,
173
173
  ): OrdersViewModel {
174
174
  const rows = summaries.map(mapSummaryToRow);
175
175
 
@@ -214,9 +214,9 @@ export function emptyOrdersViewModel(targetTenant: string): OrdersViewModel {
214
214
 
215
215
  export interface GetOrdersViewModelOptions {
216
216
  targetTenant: string;
217
- client: FoxyOrderClient;
217
+ client: CheckoutOrderClient;
218
218
  /** List filters (queue/status/pagination); passed through to the order client. */
219
- filters?: Parameters<FoxyOrderClient["listOrders"]>[0];
219
+ filters?: Parameters<CheckoutOrderClient["listOrders"]>[0];
220
220
  }
221
221
 
222
222
  /**
@@ -236,7 +236,7 @@ export type OrdersLoadStatus = "loaded" | "empty" | "error" | "not_configured";
236
236
  export interface OrdersLoadResult {
237
237
  status: OrdersLoadStatus;
238
238
  vm: OrdersViewModel;
239
- page: FoxyOrderPage | null;
239
+ page: CheckoutOrderPage | null;
240
240
  }
241
241
 
242
242
  /**
@@ -245,8 +245,8 @@ export interface OrdersLoadResult {
245
245
  * `payment`/search are NOT here — they are client-side refinements (the DTO list
246
246
  * API has no payment filter, per contract §9).
247
247
  */
248
- export function parseOrderFilters(params: URLSearchParams): FoxyOrderListFilters {
249
- const f: FoxyOrderListFilters = {};
248
+ export function parseOrderFilters(params: URLSearchParams): CheckoutOrderListFilters {
249
+ const f: CheckoutOrderListFilters = {};
250
250
  const queue = params.get("queue");
251
251
  if (queue && queue !== "all") f.queue = queue;
252
252
  const status = params.get("status");
@@ -272,7 +272,7 @@ export async function getOrdersViewModelResult(
272
272
  ): Promise<OrdersLoadResult> {
273
273
  try {
274
274
  const { orders, page } = await opts.client.listOrders(opts.filters);
275
- const details: Record<string, FoxyOrderDetail> = {};
275
+ const details: Record<string, CheckoutOrderDetail> = {};
276
276
  await Promise.all(
277
277
  orders.map(async (o) => {
278
278
  try {
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Subscriptions view model (read-only v1). The source of truth is catalog
3
+ * purchase options; active lifecycle actions require a later subscription API
4
+ * and audited mutation gates.
5
+ */
6
+
7
+ export type SubscriptionProgramReadiness = "ready" | "needs_setup" | "blocked";
8
+
9
+ export type SubscriptionAttentionKind =
10
+ | "setup_gap"
11
+ | "verification_hold"
12
+ | "payment_attention"
13
+ | "product_blocker";
14
+
15
+ export type SubscriptionSubscriberStatus =
16
+ | "active"
17
+ | "paused"
18
+ | "payment_attention"
19
+ | "verification_hold";
20
+
21
+ export type SubscriptionRenewalState = "scheduled" | "attention" | "held";
22
+
23
+ export interface SubscriptionSummary {
24
+ subscriptionProducts: number;
25
+ activeSubscribers: number;
26
+ upcomingRenewalValue: number;
27
+ needsAttention: number;
28
+ }
29
+
30
+ export interface SubscriptionCheckoutField {
31
+ name: string;
32
+ value: string;
33
+ }
34
+
35
+ export interface SubscriptionSubscriberRow {
36
+ id: string;
37
+ programId: string;
38
+ customerId: string;
39
+ customerName: string;
40
+ email: string;
41
+ status: SubscriptionSubscriberStatus;
42
+ verificationState: "verified" | "pending" | "blocked" | "not_required";
43
+ paymentState: "current" | "attention";
44
+ nextRenewalAt: string;
45
+ renewalValue: number;
46
+ }
47
+
48
+ export interface SubscriptionAttentionRow {
49
+ id: string;
50
+ kind: SubscriptionAttentionKind;
51
+ programId: string;
52
+ productId: string;
53
+ customerId: string | null;
54
+ label: string;
55
+ detail: string;
56
+ action: string;
57
+ tone: "amber" | "red" | "blue";
58
+ blocking: boolean;
59
+ }
60
+
61
+ export interface SubscriptionRenewalEvent {
62
+ id: string;
63
+ customerId: string;
64
+ customerName: string;
65
+ at: string;
66
+ amount: number;
67
+ state: SubscriptionRenewalState;
68
+ }
69
+
70
+ export interface SubscriptionEvidenceEvent {
71
+ title: string;
72
+ detail: string;
73
+ at: string;
74
+ }
75
+
76
+ export interface SubscriptionProgramRow {
77
+ id: string;
78
+ productId: string;
79
+ productName: string;
80
+ optionId: string;
81
+ optionLabel: string;
82
+ frequency: string;
83
+ savingsLabel: string | null;
84
+ priceAdjustmentLabel: string | null;
85
+ readiness: SubscriptionProgramReadiness;
86
+ readinessDetail: string;
87
+ subscriberCount: number;
88
+ upcomingRenewalValue: number;
89
+ needsAttentionCount: number;
90
+ }
91
+
92
+ export interface SubscriptionProgramDetail extends SubscriptionProgramRow {
93
+ checkoutFields: SubscriptionCheckoutField[];
94
+ subscribers: SubscriptionSubscriberRow[];
95
+ upcomingRenewals: SubscriptionRenewalEvent[];
96
+ evidenceTimeline: SubscriptionEvidenceEvent[];
97
+ attention: SubscriptionAttentionRow[];
98
+ }
99
+
100
+ export interface SubscriptionsViewModel {
101
+ targetTenant: string;
102
+ summary: SubscriptionSummary;
103
+ programs: SubscriptionProgramRow[];
104
+ detailsById: Record<string, SubscriptionProgramDetail>;
105
+ attention: SubscriptionAttentionRow[];
106
+ }
@@ -64,11 +64,22 @@ export function parseGateMode(raw: string | null | undefined): GateMode {
64
64
 
65
65
  /**
66
66
  * Paths that MUST stay reachable without a session, or the visitor could never
67
- * authenticate: the login page and the send/verify endpoints. Everything else on
68
- * a login-gated host requires a session.
67
+ * authenticate: the login page, the GET-safe magic-link confirm landing, the
68
+ * send/verify endpoints, and the exact unauthenticated magic-link resend endpoint.
69
+ * Everything else on a login-gated host requires a session.
70
+ *
71
+ * `/auth/magic` is a PRE-auth entry point — an invitee arrives here with no session
72
+ * to confirm sign-in. Gating it hands the invitee a "Coming soon" holding page and
73
+ * dead-ends the onboarding flow (the exact bug when a magic link lands on a gated
74
+ * host). `/api/auth/magic-exchange` is already covered by the `/api/auth/` prefix.
69
75
  */
70
76
  export function isAuthPath(pathname: string): boolean {
71
- return pathname === "/auth/login" || pathname.startsWith("/api/auth/");
77
+ return (
78
+ pathname === "/auth/login" ||
79
+ pathname === "/auth/magic" ||
80
+ pathname.startsWith("/api/auth/") ||
81
+ pathname === "/api/grants/magic-resend"
82
+ );
72
83
  }
73
84
 
74
85
  /**
@@ -1,39 +1,41 @@
1
1
  /**
2
- * Request-scoped FoxyCart commerce resolution (Track A).
2
+ * Request-scoped checkout commerce resolution (Track A).
3
3
  *
4
- * Bridges the tenant's static `commerce.foxy` config with runtime env so the
4
+ * Bridges the tenant's static `commerce.checkout` config with runtime env so the
5
5
  * demo store can be swapped without a code change and the signing secret stays
6
- * out of source:
7
- * - `FOXY_STORE_SUBDOMAIN` overrides the tenant's default subdomain (one env
8
- * var points every commerce tenant at our shared demo store).
6
+ * out of source. The env binding names intentionally remain `FOXY_*` because
7
+ * existing internal developer secrets and provider setup use those operational
8
+ * names:
9
+ * - `FOXY_STORE_SUBDOMAIN` overrides the tenant's default subdomain (one
10
+ * env var points every commerce tenant at our shared demo store).
9
11
  * - `FOXY_CART_SECRET` is the cart-validation secret used only in `signed`
10
12
  * mode; the `unsigned-demo` mode never reads it (validation is OFF), which
11
13
  * is what lets a developer run the whole cart locally with zero secrets.
12
14
  */
13
- import type { TenantConfig, ResolvedFoxyCommerce } from "@tot/public-runtime";
14
- import { resolveFoxyCommerce } from "@tot/public-runtime";
15
+ import type { TenantConfig, ResolvedManagedCheckout } from "@tot/public-runtime";
16
+ import { resolveManagedCheckout } from "@tot/public-runtime";
15
17
  import { readEnv } from "@/lib/env";
16
18
 
17
19
  /**
18
- * Resolve the effective FoxyCart facts for a tenant, or null when the tenant has
19
- * no store configured. Applies the `FOXY_STORE_SUBDOMAIN` override.
20
+ * Resolve the effective checkout facts for a tenant, or null when the tenant has
21
+ * no store configured. Applies the checkout env overrides.
20
22
  */
21
23
  export async function resolveTenantCommerce(
22
24
  tenant: TenantConfig,
23
- ): Promise<ResolvedFoxyCommerce | null> {
24
- const foxy = tenant.commerce?.foxy;
25
- if (!foxy) return null;
25
+ ): Promise<ResolvedManagedCheckout | null> {
26
+ const checkout = tenant.commerce?.checkout;
27
+ if (!checkout) return null;
26
28
  // Per-environment overrides so the SAME tenant can point at different stores /
27
- // checkout hosts by env: FOXY_STORE_SUBDOMAIN picks the Foxy store; FOXY_STORE_
28
- // DOMAIN sets a branded custom checkout domain (e.g. checkout.preview.
29
+ // checkout hosts by env: FOXY_STORE_SUBDOMAIN picks the checkout store;
30
+ // FOXY_STORE_DOMAIN sets a branded custom checkout domain (e.g. checkout.preview.
29
31
  // tokenoftrust.com on the shared test env, secure.<merchant>.com on live).
30
32
  const subOverride = await readEnv("FOXY_STORE_SUBDOMAIN");
31
33
  const domainOverride = await readEnv("FOXY_STORE_DOMAIN");
32
34
  const storeSubDomain =
33
- subOverride && subOverride.length ? subOverride : foxy.storeSubDomain;
35
+ subOverride && subOverride.length ? subOverride : checkout.storeSubDomain;
34
36
  const storeDomain =
35
- domainOverride && domainOverride.length ? domainOverride : foxy.storeDomain;
36
- return resolveFoxyCommerce({ ...foxy, storeSubDomain, storeDomain });
37
+ domainOverride && domainOverride.length ? domainOverride : checkout.storeDomain;
38
+ return resolveManagedCheckout({ ...checkout, storeSubDomain, storeDomain });
37
39
  }
38
40
 
39
41
  /**
@@ -42,7 +44,7 @@ export async function resolveTenantCommerce(
42
44
  * no secret is a misconfiguration the caller must handle (fall back to no cart).
43
45
  */
44
46
  export async function readCartSecret(
45
- commerce: ResolvedFoxyCommerce,
47
+ commerce: ResolvedManagedCheckout,
46
48
  ): Promise<string | undefined> {
47
49
  if (commerce.mode !== "signed") return undefined;
48
50
  return readEnv("FOXY_CART_SECRET");
@@ -1,17 +1,16 @@
1
1
  /**
2
- * FoxyCart settings model — the admin "Cart & Checkout appearance" section.
2
+ * Managed checkout settings model — the admin "Cart & Checkout appearance" section.
3
3
  *
4
- * Mirrors the settings surface that the **tot-foxycart** service actually manages
5
- * (verified against that repo), and defines how each field is PREPOPULATED from
6
- * the tenant's theme (the style-guide), OVERRIDDEN, and RESET.
4
+ * Mirrors the settings surface that the commerce service actually manages, and
5
+ * defines how each field is PREPOPULATED from the tenant's theme (the
6
+ * style-guide), OVERRIDDEN, and RESET.
7
7
  *
8
- * tot-foxycart's real settings are three groups:
9
- * 1. `colors` — Foxy's native `template_config.colors` (primary/secondary/
10
- * tertiary + usage). The only named brand-color fields in the
11
- * Foxy model; documented in tot-foxycart, applied via the theme.
8
+ * The remote settings are three groups:
9
+ * 1. `colors` — provider-native brand colors (primary/secondary/tertiary +
10
+ * usage), applied via the hosted checkout theme.
12
11
  * 2. `templates`— per-template-type `contentUrl` pointers (checkout / cart /
13
- * cart_include[sidecart] / receipt / email), set via
14
- * `PUT /theme/foxy` then published via `POST /theme/foxy/refresh`.
12
+ * cart_include[drawer] / receipt / email), set and refreshed
13
+ * through the commerce service.
15
14
  * The colors/fonts/logo live INSIDE the hosted template file.
16
15
  * 3. `store` — storeSubDomain + loader feature toggles (excise tax, age/ID
17
16
  * verification). Non-visual; no theme default.
@@ -20,21 +19,21 @@
20
19
  * the template pointers (defaulted to a ToT-hosted, theme-derived template URL).
21
20
  * The feature toggles are mirrored as plain config (no default → not resettable).
22
21
  *
23
- * The ACTUAL persistence — PATCHing Foxy's colors + PUT/refresh of the template
24
- * URLs — is the tot-foxycart HTTP seam (`/theme/foxy`), intentionally NOT wired
25
- * here; this module is the pure model the admin renders and edits.
22
+ * The ACTUAL persistence — PATCHing provider colors + PUT/refresh of template
23
+ * URLs — is the commerce-service seam, intentionally NOT wired here; this module
24
+ * is the pure model the admin renders and edits.
26
25
  */
27
- import type { ResolvedFoxyCommerce, ThemeTokens } from "@tot/public-runtime";
26
+ import type { ResolvedManagedCheckout, ThemeTokens } from "@tot/public-runtime";
28
27
 
29
- /** Foxy template types (tot-foxycart `TEMPLATE_TYPES`). */
30
- export type FoxyTemplateType =
28
+ /** Checkout template types exposed by the commerce service. */
29
+ export type CheckoutTemplateType =
31
30
  | "checkout"
32
31
  | "cart"
33
32
  | "cart_include"
34
33
  | "receipt"
35
34
  | "email";
36
35
 
37
- export const FOXY_TEMPLATE_TYPES: readonly FoxyTemplateType[] = [
36
+ export const CHECKOUT_TEMPLATE_TYPES: readonly CheckoutTemplateType[] = [
38
37
  "checkout",
39
38
  "cart",
40
39
  "cart_include",
@@ -42,60 +41,60 @@ export const FOXY_TEMPLATE_TYPES: readonly FoxyTemplateType[] = [
42
41
  "email",
43
42
  ] as const;
44
43
 
45
- export const FOXY_TEMPLATE_LABELS: Record<FoxyTemplateType, string> = {
44
+ export const CHECKOUT_TEMPLATE_LABELS: Record<CheckoutTemplateType, string> = {
46
45
  checkout: "Checkout page",
47
46
  cart: "Full cart page",
48
- cart_include: "Sidecart (slide-out)",
47
+ cart_include: "Cart drawer (slide-out)",
49
48
  receipt: "Receipt page",
50
49
  email: "Order email",
51
50
  };
52
51
 
53
- /** Foxy native `template_config.colors` — the only named brand-color fields. */
54
- export interface FoxyBrandColors {
52
+ /** Hosted checkout brand-color fields. */
53
+ export interface CheckoutBrandColors {
55
54
  primary: string;
56
55
  secondary: string;
57
56
  tertiary: string;
58
- /** Foxy's `usage`: how broadly the colors apply. "none" = Foxy default look. */
57
+ /** How broadly the colors apply. "none" keeps the provider default look. */
59
58
  usage: "none" | "all";
60
59
  }
61
60
 
62
- /** A per-template-type pointer (mirrors `PUT /theme/foxy`). */
63
- export interface FoxyTemplatePointer {
61
+ /** A per-template-type pointer managed by the commerce service. */
62
+ export interface CheckoutTemplatePointer {
64
63
  contentUrl: string;
65
64
  templateSet: string;
66
65
  }
67
66
 
68
- /** Store + loader feature config (foxyCartOptions + totOpts). Non-visual. */
69
- export interface FoxyStoreConfig {
67
+ /** Store + loader feature config. Non-visual. */
68
+ export interface CheckoutStoreConfig {
70
69
  storeSubDomain: string;
71
70
  exciseTaxCollectionEnabled: boolean;
72
71
  verificationEnabled: boolean;
73
72
  }
74
73
 
75
- /** The full tot-foxycart settings mirror. */
76
- export interface FoxyCartSettings {
77
- colors: FoxyBrandColors;
78
- templates: Record<FoxyTemplateType, FoxyTemplatePointer>;
79
- store: FoxyStoreConfig;
74
+ /** The full checkout settings mirror. */
75
+ export interface CheckoutSettings {
76
+ colors: CheckoutBrandColors;
77
+ templates: Record<CheckoutTemplateType, CheckoutTemplatePointer>;
78
+ store: CheckoutStoreConfig;
80
79
  }
81
80
 
82
81
  /** Overrides are a deep-partial of the mirror; anything unset falls back to the
83
82
  * theme-derived default. Reset === delete the key here. */
84
- export type FoxyCartSettingsOverrides = {
85
- colors?: Partial<FoxyBrandColors>;
86
- templates?: Partial<Record<FoxyTemplateType, Partial<FoxyTemplatePointer>>>;
87
- store?: Partial<FoxyStoreConfig>;
83
+ export type CheckoutSettingsOverrides = {
84
+ colors?: Partial<CheckoutBrandColors>;
85
+ templates?: Partial<Record<CheckoutTemplateType, Partial<CheckoutTemplatePointer>>>;
86
+ store?: Partial<CheckoutStoreConfig>;
88
87
  };
89
88
 
90
89
  // ---------------------------------------------------------------------------
91
- // Prepopulation: theme (style-guide) → FoxyCart settings defaults.
90
+ // Prepopulation: theme (style-guide) → checkout settings defaults.
92
91
  // ---------------------------------------------------------------------------
93
92
 
94
93
  export interface DeriveOptions {
95
94
  /**
96
95
  * Absolute origin the ToT-hosted, theme-derived templates are served from —
97
96
  * the tenant's own storefront origin (e.g. `https://sample-store.example`). Templates
98
- * live at `${originBase}/foxy-templates/<type>.html`.
97
+ * live at `${originBase}/checkout-templates/<type>.html`.
99
98
  */
100
99
  originBase: string;
101
100
  }
@@ -105,25 +104,25 @@ export interface DeriveOptions {
105
104
  * step: brand colors come straight from the theme tokens; each template pointer
106
105
  * defaults to a stable ToT-hosted URL for that tenant's theme-derived template.
107
106
  */
108
- export function deriveFoxyCartSettings(
107
+ export function deriveCheckoutSettings(
109
108
  theme: ThemeTokens,
110
- commerce: ResolvedFoxyCommerce | null,
109
+ commerce: ResolvedManagedCheckout | null,
111
110
  opts: DeriveOptions,
112
- ): FoxyCartSettings {
111
+ ): CheckoutSettings {
113
112
  const base = opts.originBase.replace(/\/$/, "");
114
- const templateUrl = (type: FoxyTemplateType) =>
115
- `${base}/foxy-templates/${type}.html`;
113
+ const templateUrl = (type: CheckoutTemplateType) =>
114
+ `${base}/checkout-templates/${type}.html`;
116
115
 
117
116
  const templates = Object.fromEntries(
118
- FOXY_TEMPLATE_TYPES.map((type) => [
117
+ CHECKOUT_TEMPLATE_TYPES.map((type) => [
119
118
  type,
120
119
  { contentUrl: templateUrl(type), templateSet: "DEFAULT" },
121
120
  ]),
122
- ) as Record<FoxyTemplateType, FoxyTemplatePointer>;
121
+ ) as Record<CheckoutTemplateType, CheckoutTemplatePointer>;
123
122
 
124
123
  return {
125
124
  colors: {
126
- // Foxy primary/secondary/tertiary ← the tenant's brand tokens.
125
+ // Checkout primary/secondary/tertiary ← the tenant's brand tokens.
127
126
  primary: theme.color.primary,
128
127
  secondary: theme.color.accent,
129
128
  tertiary: theme.color.sale,
@@ -143,16 +142,16 @@ export function deriveFoxyCartSettings(
143
142
  // Resolve with provenance: defaults + overrides → renderable field descriptors.
144
143
  // ---------------------------------------------------------------------------
145
144
 
146
- export type FoxyFieldKind = "color" | "url" | "text" | "boolean" | "select";
147
- export type FoxyFieldGroup = "colors" | "templates" | "store";
145
+ export type CheckoutFieldKind = "color" | "url" | "text" | "boolean" | "select";
146
+ export type CheckoutFieldGroup = "colors" | "templates" | "store";
148
147
 
149
148
  /** One renderable settings row: resolved value + provenance + reset affordance. */
150
- export interface FoxySettingsField {
151
- group: FoxyFieldGroup;
152
- /** Dot path into FoxyCartSettings, e.g. "colors.primary" / "templates.checkout.contentUrl". */
149
+ export interface CheckoutSettingsField {
150
+ group: CheckoutFieldGroup;
151
+ /** Dot path into CheckoutSettings, e.g. "colors.primary" / "templates.checkout.contentUrl". */
153
152
  key: string;
154
153
  label: string;
155
- kind: FoxyFieldKind;
154
+ kind: CheckoutFieldKind;
156
155
  value: string | boolean;
157
156
  /** The theme-derived default; undefined when the field has no theme default. */
158
157
  default?: string | boolean;
@@ -178,17 +177,17 @@ function readPath(obj: unknown, path: string): unknown {
178
177
  * path, else "theme". `resettable` marks fields with a real theme default (the
179
178
  * visual ones) so the UI can show/hide the "Reset to theme" control.
180
179
  */
181
- export function buildFoxySettingsFields(
182
- defaults: FoxyCartSettings,
183
- overrides: FoxyCartSettingsOverrides,
184
- ): FoxySettingsField[] {
180
+ export function buildCheckoutSettingsFields(
181
+ defaults: CheckoutSettings,
182
+ overrides: CheckoutSettingsOverrides,
183
+ ): CheckoutSettingsField[] {
185
184
  const field = (
186
- group: FoxyFieldGroup,
185
+ group: CheckoutFieldGroup,
187
186
  key: string,
188
187
  label: string,
189
- kind: FoxyFieldKind,
188
+ kind: CheckoutFieldKind,
190
189
  opts: { resettable?: boolean; options?: string[]; help?: string } = {},
191
- ): FoxySettingsField => {
190
+ ): CheckoutSettingsField => {
192
191
  const def = readPath(defaults, key);
193
192
  const ov = readPath(overrides, key);
194
193
  const overridden = ov !== undefined;
@@ -207,7 +206,7 @@ export function buildFoxySettingsFields(
207
206
  };
208
207
  };
209
208
 
210
- const fields: FoxySettingsField[] = [
209
+ const fields: CheckoutSettingsField[] = [
211
210
  // Group 1 — brand colors (theme-derived; the "make it look bigd" surface).
212
211
  field("colors", "colors.primary", "Primary color", "color", {
213
212
  help: "Checkout/cart primary buttons and accents. From --color-primary.",
@@ -220,28 +219,28 @@ export function buildFoxySettingsFields(
220
219
  }),
221
220
  field("colors", "colors.usage", "Apply brand colors", "select", {
222
221
  options: ["all", "none"],
223
- help: '"all" applies the brand palette to Foxy; "none" keeps Foxy defaults.',
222
+ help: '"all" applies the brand palette to hosted checkout; "none" keeps checkout defaults.',
224
223
  }),
225
224
  ];
226
225
 
227
226
  // Group 2 — template pointers (default = ToT-hosted theme-derived template).
228
- for (const type of FOXY_TEMPLATE_TYPES) {
227
+ for (const type of CHECKOUT_TEMPLATE_TYPES) {
229
228
  fields.push(
230
- field("templates", `templates.${type}.contentUrl`, FOXY_TEMPLATE_LABELS[type], "url", {
231
- help: `Hosted template URL for the ${FOXY_TEMPLATE_LABELS[type].toLowerCase()}. Defaults to the ToT theme-derived template; override with a custom URL.`,
229
+ field("templates", `templates.${type}.contentUrl`, CHECKOUT_TEMPLATE_LABELS[type], "url", {
230
+ help: `Hosted template URL for the ${CHECKOUT_TEMPLATE_LABELS[type].toLowerCase()}. Defaults to the ToT theme-derived template; override with a custom URL.`,
232
231
  }),
233
232
  );
234
233
  }
235
234
 
236
235
  // Group 3 — store/feature config (no theme default → not resettable).
237
236
  fields.push(
238
- field("store", "store.storeSubDomain", "Foxy store subdomain", "text", {
237
+ field("store", "store.storeSubDomain", "Checkout store subdomain", "text", {
239
238
  resettable: false,
240
- help: "The <sub>.foxycart.com store this tenant checks out through.",
239
+ help: "Internal checkout store identifier for this tenant.",
241
240
  }),
242
241
  field("store", "store.exciseTaxCollectionEnabled", "Collect excise tax", "boolean", {
243
242
  resettable: false,
244
- help: "tot-foxycart pre-cart excise tax collection.",
243
+ help: "Managed pre-cart excise tax collection.",
245
244
  }),
246
245
  field("store", "store.verificationEnabled", "Age / identity verification", "boolean", {
247
246
  resettable: false,
@@ -25,6 +25,15 @@ export interface ActivityIngestClaims {
25
25
  email: string;
26
26
  tenant: string;
27
27
  capability: string;
28
+ /**
29
+ * The invite→terminal→problem TRACE id, minted once when this developer's
30
+ * sign-in code was issued (see api/dev/cli-signin-code.ts). It rides in the
31
+ * claims so every runtime/activity write the local loop makes with this token
32
+ * is stampable with the same id — support looks a live session up by it.
33
+ * Optional so tokens minted before this field existed still verify (they just
34
+ * carry no trace); every freshly-minted token sets it.
35
+ */
36
+ traceId?: string;
28
37
  }
29
38
 
30
39
  function newToken(): string {
@@ -14,6 +14,12 @@ export interface ActivityEvent {
14
14
  event: ActivityEventKind;
15
15
  /** Epoch ms, as reported by the local loop. */
16
16
  at: number;
17
+ /**
18
+ * The invite→terminal→problem TRACE id, stamped from the ingest-token claims
19
+ * (never the body) so a file save is attributable to the same span as the
20
+ * runtime record. Absent on events written by tokens minted before tracing.
21
+ */
22
+ traceId?: string;
17
23
  }
18
24
 
19
25
  const KEY_PREFIX = "dev-activity:";
@@ -48,6 +54,12 @@ export async function appendActivity(
48
54
  await kv.put(key(appDomain), JSON.stringify(next), { expirationTtl: TTL_SECONDS });
49
55
  }
50
56
 
57
+ /** Delete the vendor's activity ring buffer — part of a "true reset" so stale saves
58
+ * don't re-complete Step 1 after the developer stops their local loop. */
59
+ export async function clearActivity(kv: KVNamespace, appDomain: string): Promise<void> {
60
+ await kv.delete(key(appDomain));
61
+ }
62
+
51
63
  /**
52
64
  * Business-readable translation of a reported file path + event, for the
53
65
  * activity log. File-level only — no per-line diff enrichment (Darrin,