@tokenoftrust/storefront-runner 1.3.4-rc.4 → 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.
- package/apps/storefront/astro.config.mjs +15 -0
- package/apps/storefront/dev-plugins/dev-publish.mjs +55 -0
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +86 -7
- package/apps/storefront/drizzle.config.apps.ts +13 -0
- package/apps/storefront/env.d.ts +10 -0
- package/apps/storefront/migrations/README.md +13 -7
- package/apps/storefront/migrations-apps/0000_fast_millenium_guard.sql +129 -0
- package/apps/storefront/migrations-apps/meta/0000_snapshot.json +843 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +13 -0
- package/apps/storefront/package.json +13 -1
- package/apps/storefront/perf/README.md +64 -0
- package/apps/storefront/perf/assert-budgets.ts +159 -0
- package/apps/storefront/playwright.config.ts +23 -0
- package/apps/storefront/public/js/dashboard-apps.js +173 -0
- package/apps/storefront/public/shared/commerce-marketing.css +221 -0
- package/apps/storefront/src/components/CollectionCard.astro +1 -0
- package/apps/storefront/src/components/ProductCard.astro +1 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1959 -0
- package/apps/storefront/src/components/apps/AppWidgetFrame.astro +30 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +6 -3
- package/apps/storefront/src/components/chrome/SiteFooter.astro +10 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +10 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +3 -2
- package/apps/storefront/src/components/content/Callout.astro +75 -0
- package/apps/storefront/src/components/content/NeedsReviewCallout.astro +66 -0
- package/apps/storefront/src/components/content/ProseSections.astro +121 -0
- package/apps/storefront/src/components/content/ProseToc.astro +34 -0
- package/apps/storefront/src/components/content/RichText.astro +44 -0
- package/apps/storefront/src/components/content/TrustStrip.astro +46 -0
- package/apps/storefront/src/components/home/Hero.astro +14 -0
- package/apps/storefront/src/components/islands/CheckoutComplianceGate.tsx +295 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +42 -21
- package/apps/storefront/src/components/islands/VariantSelector.tsx +62 -12
- package/apps/storefront/src/components/plp/FacetSidebar.astro +2 -2
- package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +1 -0
- package/apps/storefront/src/config/compliance/rulesets.ts +79 -0
- package/apps/storefront/src/config/storeName.ts +34 -0
- package/apps/storefront/src/layouts/Layout.astro +97 -23
- package/apps/storefront/src/lib/analytics/budgets.json +69 -0
- package/apps/storefront/src/lib/analytics/lighthouseReport.ts +109 -0
- package/apps/storefront/src/lib/analytics/perfBudgets.ts +452 -0
- package/apps/storefront/src/lib/analytics/rumAlert.ts +179 -0
- package/apps/storefront/src/lib/analytics/webVitals.ts +269 -0
- package/apps/storefront/src/lib/apps/adminService.ts +106 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +205 -0
- package/apps/storefront/src/lib/apps/apiAuth.ts +91 -0
- package/apps/storefront/src/lib/apps/apiRoute.ts +35 -0
- package/apps/storefront/src/lib/apps/catalogMapper.ts +39 -0
- package/apps/storefront/src/lib/apps/credentials.ts +153 -0
- package/apps/storefront/src/lib/apps/gatewayKeys.ts +156 -0
- package/apps/storefront/src/lib/apps/healthAggregate.ts +66 -0
- package/apps/storefront/src/lib/apps/orders/attributionService.ts +206 -0
- package/apps/storefront/src/lib/apps/orders/customerHash.ts +28 -0
- package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +203 -0
- package/apps/storefront/src/lib/apps/orders/idempotency.ts +100 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +117 -0
- package/apps/storefront/src/lib/apps/orders/orderMapper.ts +91 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +181 -0
- package/apps/storefront/src/lib/apps/registryService.ts +579 -0
- package/apps/storefront/src/lib/apps/scopes.ts +79 -0
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +121 -0
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +148 -0
- package/apps/storefront/src/lib/apps/widgets/eligibility.ts +18 -0
- package/apps/storefront/src/lib/apps/widgets/frameProps.ts +52 -0
- package/apps/storefront/src/lib/apps/widgets/launchToken.ts +84 -0
- package/apps/storefront/src/lib/apps/widgets/placements.ts +57 -0
- package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +111 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +119 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +21 -2
- package/apps/storefront/src/lib/auth/loginGate.ts +102 -18
- package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +215 -0
- package/apps/storefront/src/lib/auth/route.ts +16 -1
- package/apps/storefront/src/lib/auth/session.ts +8 -0
- package/apps/storefront/src/lib/auth/stepUpChallenge.ts +107 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +208 -0
- package/apps/storefront/src/lib/blog/provider.ts +39 -0
- package/apps/storefront/src/lib/blog/types.ts +26 -0
- package/apps/storefront/src/lib/checkoutCommerce.ts +39 -1
- package/apps/storefront/src/lib/chrome/model.ts +11 -0
- package/apps/storefront/src/lib/compliance/enforcement.ts +95 -0
- package/apps/storefront/src/lib/content/callout.ts +78 -0
- package/apps/storefront/src/lib/content/index.ts +27 -0
- package/apps/storefront/src/lib/content/needsReview.ts +58 -0
- package/apps/storefront/src/lib/content/prose.ts +186 -0
- package/apps/storefront/src/lib/content/richtext.ts +76 -0
- package/apps/storefront/src/lib/content/trustStrip.ts +74 -0
- package/apps/storefront/src/lib/content-edit/client.ts +70 -14
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +213 -0
- package/apps/storefront/src/lib/dev/apiBase.ts +8 -2
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +65 -115
- package/apps/storefront/src/lib/dev/cockpitStore.ts +65 -0
- package/apps/storefront/src/lib/dev/previewStatus.ts +112 -0
- package/apps/storefront/src/lib/dev/rendezvousBroker.ts +241 -0
- package/apps/storefront/src/lib/dev/subjectReissue.ts +67 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +10 -10
- package/apps/storefront/src/lib/env.ts +12 -0
- package/apps/storefront/src/lib/jsonld.ts +12 -17
- package/apps/storefront/src/lib/membership/eligibility.ts +37 -0
- package/apps/storefront/src/lib/monitoring/manifest.ts +302 -0
- package/apps/storefront/src/lib/privacy/emailHint.ts +13 -5
- package/apps/storefront/src/lib/publish/apex-readiness.ts +337 -0
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +269 -0
- package/apps/storefront/src/lib/publish/domainState.ts +351 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +362 -0
- package/apps/storefront/src/lib/rawChrome.ts +34 -3
- package/apps/storefront/src/lib/storyblok/content-model.ts +34 -2
- package/apps/storefront/src/lib/storyblok/provider.ts +11 -4
- package/apps/storefront/src/lib/subscription/model.ts +114 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +3 -3
- package/apps/storefront/src/lib/tot/query.ts +32 -0
- package/apps/storefront/src/lib/webhooks/cloudflareQueueDispatcher.ts +82 -0
- package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +245 -0
- package/apps/storefront/src/lib/webhooks/deliveryMapper.ts +34 -0
- package/apps/storefront/src/lib/webhooks/deliveryStore.ts +668 -0
- package/apps/storefront/src/lib/webhooks/dispatcher.ts +168 -0
- package/apps/storefront/src/lib/webhooks/emit.ts +167 -0
- package/apps/storefront/src/lib/webhooks/endpointGuard.ts +135 -0
- package/apps/storefront/src/lib/webhooks/events.ts +98 -0
- package/apps/storefront/src/lib/webhooks/getDispatcher.ts +49 -0
- package/apps/storefront/src/lib/webhooks/signing.ts +29 -0
- package/apps/storefront/src/lib/webhooks/webhookSigningKey.ts +146 -0
- package/apps/storefront/src/middleware/index.ts +28 -13
- package/apps/storefront/src/pages/404.astro +21 -9
- package/apps/storefront/src/pages/[...slug].astro +36 -2
- package/apps/storefront/src/pages/admin.astro +29 -3
- package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +60 -0
- package/apps/storefront/src/pages/api/apps/admin/health.ts +44 -0
- package/apps/storefront/src/pages/api/apps/admin/install.ts +100 -0
- package/apps/storefront/src/pages/api/apps/admin/list.ts +26 -0
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +56 -0
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +57 -0
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +74 -0
- package/apps/storefront/src/pages/api/apps/admin/update.ts +103 -0
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +59 -0
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +51 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +172 -0
- package/apps/storefront/src/pages/api/apps/oauth/token.ts +87 -0
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +142 -0
- package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +39 -0
- package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +49 -0
- package/apps/storefront/src/pages/api/apps/v1/health.ts +32 -0
- package/apps/storefront/src/pages/api/apps/v1/inventory.ts +59 -0
- package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +48 -0
- package/apps/storefront/src/pages/api/apps/v1/orders.ts +73 -0
- package/apps/storefront/src/pages/api/apps/v1/reports.ts +21 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +62 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +47 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +48 -1
- package/apps/storefront/src/pages/api/auth/step-up-send.ts +57 -0
- package/apps/storefront/src/pages/api/auth/step-up-verify.ts +129 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +23 -0
- package/apps/storefront/src/pages/api/compliance/preflight.ts +206 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +54 -0
- package/apps/storefront/src/pages/api/test/dev-session.ts +133 -0
- package/apps/storefront/src/pages/auth/login.astro +197 -41
- package/apps/storefront/src/pages/auth/magic.astro +75 -43
- package/apps/storefront/src/pages/blog/[slug].astro +107 -0
- package/apps/storefront/src/pages/blog/index.astro +98 -0
- package/apps/storefront/src/pages/capabilities.astro +8 -0
- package/apps/storefront/src/pages/cockpit.astro +433 -69
- package/apps/storefront/src/pages/collections/[handle].astro +8 -0
- package/apps/storefront/src/pages/collections/index.astro +10 -2
- package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +119 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +5 -0
- package/apps/storefront/src/pages/index.astro +57 -0
- package/apps/storefront/src/pages/llms.txt.ts +31 -10
- package/apps/storefront/src/pages/products/[handle].astro +100 -9
- package/apps/storefront/src/pages/sitemap.xml.ts +21 -4
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +198 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +7 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +7 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -0
- package/apps/storefront/src/pages/style-guide/index.astro +10 -0
- package/apps/storefront/src/styles/fonts.css +54 -0
- package/apps/storefront/src/styles/global.css +22 -2
- package/apps/storefront/src/themes/schema.ts +3 -25
- package/apps/storefront/tsconfig.json +1 -1
- package/apps/storefront/vitest.config.ts +4 -1
- package/package.json +1 -1
- package/packages/public-runtime/src/candidate-index.ts +311 -0
- package/packages/public-runtime/src/checkout.ts +94 -2
- package/packages/public-runtime/src/compliance/evaluate.ts +265 -0
- package/packages/public-runtime/src/compliance/evidence-signals.ts +81 -0
- package/packages/public-runtime/src/compliance/index.ts +22 -0
- package/packages/public-runtime/src/compliance/pact-report.ts +94 -0
- package/packages/public-runtime/src/compliance/profile.ts +198 -0
- package/packages/public-runtime/src/compliance/ruleset.ts +117 -0
- package/packages/public-runtime/src/compliance/verification.ts +81 -0
- package/packages/public-runtime/src/csp.ts +27 -3
- package/packages/public-runtime/src/customization-reconcile.ts +35 -0
- package/packages/public-runtime/src/customization-runtime.ts +8 -0
- package/packages/public-runtime/src/customization-versioning.ts +17 -0
- package/packages/public-runtime/src/extension-contract.ts +2 -1
- package/packages/public-runtime/src/hash.ts +25 -0
- package/packages/public-runtime/src/index.ts +6 -0
- package/packages/public-runtime/src/membership.ts +353 -0
- package/packages/public-runtime/src/product.ts +24 -2
- package/packages/public-runtime/src/review-trust-proof.ts +194 -0
- package/packages/public-runtime/src/tenant-assets.ts +40 -5
- package/packages/public-runtime/src/tenant.ts +236 -0
- package/packages/public-runtime/src/widget-postmessage.ts +205 -0
- package/scripts/dev/publish.mjs +158 -0
- package/scripts/dev/transient-files.mjs +2 -1
- package/tenants/home/public/fonts/inter-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-800-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-800-normal.woff2 +0 -0
- package/tenants/home/public/pages/storefront.css +23 -0
- package/apps/storefront/src/lib/dev/hostedCockpit.ts +0 -169
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* Every page renders inside this.
|
|
6
6
|
*/
|
|
7
7
|
import "@/styles/global.css";
|
|
8
|
+
// Self-hosted web fonts (audit finding 6) — replaces the render-blocking
|
|
9
|
+
// fonts.googleapis.com stylesheet. Bundled by Vite, served same-origin.
|
|
10
|
+
import "@/styles/fonts.css";
|
|
8
11
|
import Seo from "@/components/Seo.astro";
|
|
9
12
|
import AnnouncementBar from "@/components/chrome/AnnouncementBar.astro";
|
|
10
13
|
import SiteHeader from "@/components/chrome/SiteHeader.astro";
|
|
@@ -17,10 +20,13 @@ import ShippingRestrictionNotice from "@/components/compliance/ShippingRestricti
|
|
|
17
20
|
import IsolatedAgeGate from "@/components/islands/IsolatedAgeGate.tsx";
|
|
18
21
|
import AuthBadge from "@/components/auth/AuthBadge.astro";
|
|
19
22
|
import { referenceTheme } from "@/themes/reference";
|
|
20
|
-
import { mergeTheme
|
|
23
|
+
import { mergeTheme } from "@/themes/schema";
|
|
21
24
|
import { organizationLd, websiteLd } from "@/lib/jsonld";
|
|
22
25
|
import { nonceInlineTags, CHROME_BODY_MARKER } from "@/lib/rawMarketingHtml";
|
|
23
26
|
import { readViewerSession } from "@/lib/auth/route";
|
|
27
|
+
import AppWidgetFrame from "@/components/apps/AppWidgetFrame.astro";
|
|
28
|
+
import { getRegistryService } from "@/lib/apps/adminService.js";
|
|
29
|
+
import { renderWidgetSlot } from "@/lib/apps/widgets/renderSlot.js";
|
|
24
30
|
|
|
25
31
|
interface Props {
|
|
26
32
|
title: string;
|
|
@@ -49,7 +55,6 @@ const allJsonLd = [
|
|
|
49
55
|
];
|
|
50
56
|
|
|
51
57
|
const theme = mergeTheme(referenceTheme, tenant.theme_tokens);
|
|
52
|
-
const fontHref = themeFontHref(theme);
|
|
53
58
|
|
|
54
59
|
const chrome = await Astro.locals.content.getChrome();
|
|
55
60
|
|
|
@@ -92,12 +97,22 @@ const compliance = tenant.compliance;
|
|
|
92
97
|
// open hosts where the gate never ran. Signed-out "Sign in" carries the current
|
|
93
98
|
// path as `next`, so after the email-code round-trip the visitor lands back
|
|
94
99
|
// where they were instead of the site root.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
//
|
|
101
|
+
// Marketing tenants (siteType:"marketing" → features.catalog === false) have no
|
|
102
|
+
// shopper/vendor account surface to link to: /auth/login + /dashboard are also
|
|
103
|
+
// excluded outright from the static-publish route set (routeClassifier's
|
|
104
|
+
// auth/+dashboard/ prefixes), so a marketing-only site can never resolve either
|
|
105
|
+
// URL. Skip the affordance rather than render a dangling link — mirrors the
|
|
106
|
+
// same commerce/marketing split index.astro already keys off `!features.catalog`.
|
|
107
|
+
let authLink: { label: string; href: string } | undefined;
|
|
108
|
+
if (features.catalog) {
|
|
109
|
+
const viewer = Astro.locals.viewer ?? (await readViewerSession(Astro));
|
|
110
|
+
const nextParam = encodeURIComponent(Astro.url.pathname + Astro.url.search);
|
|
111
|
+
authLink = {
|
|
112
|
+
label: viewer ? "Dashboard" : "Sign in",
|
|
113
|
+
href: viewer ? "/dashboard" : `/auth/login?next=${nextParam}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
101
116
|
|
|
102
117
|
// Per-vendor header treatment from the theme file: a "dark" header renders the
|
|
103
118
|
// bar in the primary color carrying the light logo (e.g. Giant Vapes' navy +
|
|
@@ -121,6 +136,23 @@ const legalLine = `© ${new Date().getFullYear()} ${tenant.displayName}${
|
|
|
121
136
|
const analyticsToken = import.meta.env.PUBLIC_CF_ANALYTICS_TOKEN;
|
|
122
137
|
const analyticsEnabled =
|
|
123
138
|
analyticsToken && !/REPLACE|PLACEHOLDER/i.test(analyticsToken);
|
|
139
|
+
|
|
140
|
+
// Private-app widgets (D5) — global.footer slot. Resolved once here so it
|
|
141
|
+
// renders for BOTH footer paths below (the tenant-owned chrome.html splice
|
|
142
|
+
// and the generic <Footer>) instead of being owned by either. No dark-mode
|
|
143
|
+
// preference infra exists yet — "light" is the only theme hint until it does.
|
|
144
|
+
const footerWidgets = await renderWidgetSlot(
|
|
145
|
+
{
|
|
146
|
+
registry: await getRegistryService(),
|
|
147
|
+
tenantId: tenant.tenant_id,
|
|
148
|
+
customizationEnv: Astro.locals.customizationEnv,
|
|
149
|
+
slot: "global.footer",
|
|
150
|
+
route: path,
|
|
151
|
+
locale: tenant.locale,
|
|
152
|
+
theme: "light",
|
|
153
|
+
},
|
|
154
|
+
Astro.locals.activeWidgetPlacements,
|
|
155
|
+
);
|
|
124
156
|
---
|
|
125
157
|
|
|
126
158
|
<!doctype html>
|
|
@@ -130,27 +162,20 @@ const analyticsEnabled =
|
|
|
130
162
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
131
163
|
<link rel="icon" type="image/svg+xml" href={theme.brand.favicon} />
|
|
132
164
|
|
|
133
|
-
{/* Per-tenant theme tokens injected at the edge (brief §7).
|
|
134
|
-
|
|
165
|
+
{/* Per-tenant theme tokens injected at the edge (brief §7). The id is the
|
|
166
|
+
dev-loop hot-patch target: tenant-hot-reload.mjs recomputes this same
|
|
167
|
+
`:root{...}` block on a theme.json save and the script below swaps its
|
|
168
|
+
textContent in place — no full reload. Inert in production (no listener
|
|
169
|
+
attaches there). */}
|
|
170
|
+
<style id="tot-theme-vars" set:html={themeStyle} nonce={cspNonce}></style>
|
|
135
171
|
|
|
136
172
|
{/* Shared chrome's own stylesheet(s) — the tenant theme (/tenants/...) plus
|
|
137
173
|
either the tenant's own mkt.css or the platform /shared/commerce-chrome.css
|
|
138
174
|
— so the ported chrome renders in-brand on Layout (commerce) pages too. */}
|
|
139
175
|
{sharedChrome?.css.map((href) => <link rel="stylesheet" href={href} />)}
|
|
140
176
|
|
|
141
|
-
{
|
|
142
|
-
|
|
143
|
-
<>
|
|
144
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
145
|
-
<link
|
|
146
|
-
rel="preconnect"
|
|
147
|
-
href="https://fonts.gstatic.com"
|
|
148
|
-
crossorigin
|
|
149
|
-
/>
|
|
150
|
-
<link rel="stylesheet" href={fontHref} />
|
|
151
|
-
</>
|
|
152
|
-
)
|
|
153
|
-
}
|
|
177
|
+
{/* Web fonts are self-hosted (audit finding 6) via `@/styles/fonts.css`,
|
|
178
|
+
imported above — no third-party font stylesheet / preconnects. */}
|
|
154
179
|
|
|
155
180
|
<Seo
|
|
156
181
|
title={title}
|
|
@@ -230,6 +255,20 @@ const analyticsEnabled =
|
|
|
230
255
|
</>
|
|
231
256
|
)}
|
|
232
257
|
|
|
258
|
+
{/* Private-app widgets (D5, global.footer) — sandboxed per install; one
|
|
259
|
+
resolution above covers both footer paths. */}
|
|
260
|
+
{footerWidgets.map((w) => (
|
|
261
|
+
<div class="container-prose py-6">
|
|
262
|
+
<AppWidgetFrame
|
|
263
|
+
endpoint={w.endpoint}
|
|
264
|
+
launchToken={w.launchToken}
|
|
265
|
+
appOrigin={w.appOrigin}
|
|
266
|
+
installId={w.installId}
|
|
267
|
+
title={w.title ?? "App widget"}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
))}
|
|
271
|
+
|
|
233
272
|
{capabilities.ageVerification.enabled && compliance?.minAge && (
|
|
234
273
|
<IsolatedAgeGate client:load minAge={compliance.minAge} brand={tenant.displayName} />
|
|
235
274
|
)}
|
|
@@ -342,6 +381,31 @@ const analyticsEnabled =
|
|
|
342
381
|
</script>
|
|
343
382
|
)}
|
|
344
383
|
|
|
384
|
+
{/* Dev-loop theme hot-patch (D1): tenant-hot-reload.mjs broadcasts a
|
|
385
|
+
`tot:theme-vars` HMR custom event on every theme.json save instead of
|
|
386
|
+
forcing a full reload; swap the CSS var block in place so edits repaint
|
|
387
|
+
instantly with no lost scroll position. import.meta.hot is undefined in
|
|
388
|
+
production builds, so this is a no-op there. */}
|
|
389
|
+
{import.meta.env.DEV && (
|
|
390
|
+
<script>
|
|
391
|
+
if (import.meta.hot) {
|
|
392
|
+
import.meta.hot.on("tot:theme-vars", (data: { css?: string }) => {
|
|
393
|
+
const el = document.getElementById("tot-theme-vars");
|
|
394
|
+
if (el && typeof data?.css === "string") el.textContent = data.css;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
</script>
|
|
398
|
+
)}
|
|
399
|
+
|
|
400
|
+
{/* Private-app widget postMessage bridge (D5) — mounted unconditionally
|
|
401
|
+
(cheap no-op with zero tracked iframes); scans for
|
|
402
|
+
`[data-tot-widget-origin]` frames itself, so it doesn't need to know
|
|
403
|
+
which slots rendered above. */}
|
|
404
|
+
<script>
|
|
405
|
+
import { mountWidgetPostMessageBridge } from "@tot/public-runtime";
|
|
406
|
+
mountWidgetPostMessageBridge();
|
|
407
|
+
</script>
|
|
408
|
+
|
|
345
409
|
{/* Scroll-reveal + analytics pageview hook. ~0.5KB, no framework. */}
|
|
346
410
|
<script>
|
|
347
411
|
// Reveal-on-scroll (respects reduced motion via CSS).
|
|
@@ -366,5 +430,15 @@ const analyticsEnabled =
|
|
|
366
430
|
els.forEach((el) => el.classList.add("is-in"));
|
|
367
431
|
}
|
|
368
432
|
</script>
|
|
433
|
+
|
|
434
|
+
{/* Field Web Vitals (F14 RUM) — measures LCP/INP/CLS for real visitors and
|
|
435
|
+
beacons per-route samples to the RUM sink. ~1KB, no framework; no-ops
|
|
436
|
+
where PerformanceObserver is absent. This is the per-route-template
|
|
437
|
+
signal F16's `core-web-vitals` monitor consumes (the CF Web Analytics
|
|
438
|
+
beacon above only gives aggregate field CWV). */}
|
|
439
|
+
<script>
|
|
440
|
+
import { startWebVitals } from "@/lib/analytics/webVitals";
|
|
441
|
+
startWebVitals();
|
|
442
|
+
</script>
|
|
369
443
|
</body>
|
|
370
444
|
</html>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "F14 performance budgets manifest — the checked-in, tool-neutral source of truth for the storefront go-live framework's perf gate. Consumed by (1) the lab budget-assertion harness (perf/assert-budgets.ts), (2) the Lighthouse budgets exporter (toLighthouseBudgets), and (3) the field RUM regression-alert contract (rumAlert.ts). Edit HERE — everything else derives from it. Shape + validation: src/lib/analytics/perfBudgets.ts (parsePerfBudgets).",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"webVitals": {
|
|
5
|
+
"$comment": "Core Web Vitals thresholds, evaluated at the 75th percentile (p75) of the field distribution — the same percentile Google's CrUX / the RUM alert contract use.",
|
|
6
|
+
"lcpMs": 2500,
|
|
7
|
+
"inpMs": 200,
|
|
8
|
+
"cls": 0.1
|
|
9
|
+
},
|
|
10
|
+
"resourceBudgets": {
|
|
11
|
+
"$comment": "Per-page transfer budgets in kibibytes (KiB), compressed-over-the-wire. Defaults; a route may tighten or (rarely) loosen them via its own resourceBudgets.",
|
|
12
|
+
"jsKib": 180,
|
|
13
|
+
"cssKib": 90,
|
|
14
|
+
"imageKib": 900,
|
|
15
|
+
"totalKib": 1300
|
|
16
|
+
},
|
|
17
|
+
"thirdPartyScriptAllowlist": [
|
|
18
|
+
"static.cloudflareinsights.com",
|
|
19
|
+
"cdn.foxycart.com"
|
|
20
|
+
],
|
|
21
|
+
"routes": [
|
|
22
|
+
{
|
|
23
|
+
"id": "home",
|
|
24
|
+
"label": "Home",
|
|
25
|
+
"samplePath": "/",
|
|
26
|
+
"match": ["="]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "plp",
|
|
30
|
+
"label": "Product listing",
|
|
31
|
+
"samplePath": "/collections/all",
|
|
32
|
+
"match": ["/collections"]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "pdp",
|
|
36
|
+
"label": "Product detail",
|
|
37
|
+
"samplePath": "/products/",
|
|
38
|
+
"match": ["/products/"],
|
|
39
|
+
"resourceBudgets": {
|
|
40
|
+
"$comment": "PDP carries the largest hero + gallery imagery.",
|
|
41
|
+
"imageKib": 1100,
|
|
42
|
+
"totalKib": 1500
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "search",
|
|
47
|
+
"label": "Search results",
|
|
48
|
+
"samplePath": "/search?q=cap",
|
|
49
|
+
"match": ["/search"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "account",
|
|
53
|
+
"label": "Account / dashboard",
|
|
54
|
+
"samplePath": "/dashboard",
|
|
55
|
+
"match": ["/dashboard", "/account", "/saved"]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "checkout",
|
|
59
|
+
"label": "Checkout entry",
|
|
60
|
+
"samplePath": "/cart",
|
|
61
|
+
"match": ["/cart", "/checkout"],
|
|
62
|
+
"resourceBudgets": {
|
|
63
|
+
"$comment": "The managed-checkout loader (cdn.foxycart.com) ships its own JS bundle; give this route headroom the on-site pages do not need.",
|
|
64
|
+
"jsKib": 320,
|
|
65
|
+
"totalKib": 1600
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F14 — Lighthouse result → budget measurement (the lab hook, pure half).
|
|
3
|
+
*
|
|
4
|
+
* The budget gate (`perfBudgets.ts`) evaluates a tool-neutral `RouteMeasurement`.
|
|
5
|
+
* This module is the one adapter that turns a Lighthouse result object (LHR JSON,
|
|
6
|
+
* as `lighthouse --output=json` / LHCI emit) into that shape, so a live
|
|
7
|
+
* Lighthouse run in preview validation feeds the SAME gate the unit tests do.
|
|
8
|
+
*
|
|
9
|
+
* Kept pure (LHR in → measurement out, no fs/network) so `lighthouseReport.test.ts`
|
|
10
|
+
* can prove the extraction against a fixture LHR without running Lighthouse; the
|
|
11
|
+
* thin CLI (`perf/assert-budgets.ts`) does the fs + Lighthouse invocation.
|
|
12
|
+
*
|
|
13
|
+
* We read only the stable, documented LHR fields:
|
|
14
|
+
* - `audits['largest-contentful-paint'].numericValue` (ms) → webVitals.lcpMs
|
|
15
|
+
* - `audits['cumulative-layout-shift'].numericValue` (unitless)→ webVitals.cls
|
|
16
|
+
* - `audits['resource-summary'].details.items[]` (bytes) → resource KiB
|
|
17
|
+
* - `audits['network-requests'].details.items[]` (url) → third-party origins
|
|
18
|
+
* INP is intentionally absent — it has no lab value; it is enforced from field
|
|
19
|
+
* RUM (`rumAlert.ts`), matching `toLighthouseBudgets`' note.
|
|
20
|
+
*
|
|
21
|
+
* Third-party origins are taken from `network-requests` (real request URLs →
|
|
22
|
+
* hostnames), NOT `third-party-summary` — the latter reports entity DISPLAY
|
|
23
|
+
* names ("Google Fonts"), which would never match the manifest's hostname
|
|
24
|
+
* allowlist. We keep hostnames that loaded a Script and are cross-origin to the
|
|
25
|
+
* page, so the gate's allowlist check compares like-for-like.
|
|
26
|
+
*/
|
|
27
|
+
import type { RouteId, RouteMeasurement } from "./perfBudgets.js";
|
|
28
|
+
|
|
29
|
+
/** The (loose) slice of a Lighthouse result we read. */
|
|
30
|
+
export interface LighthouseResult {
|
|
31
|
+
audits?: Record<
|
|
32
|
+
string,
|
|
33
|
+
{
|
|
34
|
+
numericValue?: number;
|
|
35
|
+
details?: {
|
|
36
|
+
items?: Array<Record<string, unknown>>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
>;
|
|
40
|
+
finalUrl?: string;
|
|
41
|
+
requestedUrl?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hostnameOf = (url: string | undefined): string | undefined => {
|
|
45
|
+
if (!url) return undefined;
|
|
46
|
+
try {
|
|
47
|
+
return new URL(url).hostname.toLowerCase();
|
|
48
|
+
} catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const BYTES_PER_KIB = 1024;
|
|
54
|
+
const toKib = (bytes: unknown): number | undefined =>
|
|
55
|
+
typeof bytes === "number" && Number.isFinite(bytes)
|
|
56
|
+
? Math.round((bytes / BYTES_PER_KIB) * 10) / 10
|
|
57
|
+
: undefined;
|
|
58
|
+
|
|
59
|
+
/** Map a Lighthouse `resource-summary` resourceType onto our budget keys. */
|
|
60
|
+
const RESOURCE_TYPE_KEY: Record<string, "jsKib" | "cssKib" | "imageKib" | "totalKib"> = {
|
|
61
|
+
script: "jsKib",
|
|
62
|
+
stylesheet: "cssKib",
|
|
63
|
+
image: "imageKib",
|
|
64
|
+
total: "totalKib",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extract a `RouteMeasurement` for `routeId` from a Lighthouse result. Missing
|
|
69
|
+
* audits simply yield missing dimensions — the gate only checks what was
|
|
70
|
+
* measured — so a partial/failed audit under-reports rather than crashing.
|
|
71
|
+
*/
|
|
72
|
+
export function lhrToMeasurement(lhr: LighthouseResult, routeId: RouteId): RouteMeasurement {
|
|
73
|
+
const audits = lhr.audits ?? {};
|
|
74
|
+
|
|
75
|
+
const resources: NonNullable<RouteMeasurement["resources"]> = {};
|
|
76
|
+
const summaryItems = audits["resource-summary"]?.details?.items ?? [];
|
|
77
|
+
for (const item of summaryItems) {
|
|
78
|
+
const type = typeof item["resourceType"] === "string" ? (item["resourceType"] as string) : "";
|
|
79
|
+
const key = RESOURCE_TYPE_KEY[type];
|
|
80
|
+
const kib = toKib(item["transferSize"]);
|
|
81
|
+
if (key && kib !== undefined) resources[key] = kib;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const webVitals: NonNullable<RouteMeasurement["webVitals"]> = {};
|
|
85
|
+
const lcp = audits["largest-contentful-paint"]?.numericValue;
|
|
86
|
+
if (typeof lcp === "number") webVitals.lcpMs = Math.round(lcp);
|
|
87
|
+
const cls = audits["cumulative-layout-shift"]?.numericValue;
|
|
88
|
+
if (typeof cls === "number") webVitals.cls = Math.round(cls * 1e4) / 1e4;
|
|
89
|
+
|
|
90
|
+
// Third-party SCRIPT hostnames, cross-origin to the page, from real requests.
|
|
91
|
+
const pageHost = hostnameOf(lhr.finalUrl ?? lhr.requestedUrl);
|
|
92
|
+
const netItems = audits["network-requests"]?.details?.items ?? [];
|
|
93
|
+
const thirdPartyOrigins: string[] = [];
|
|
94
|
+
for (const item of netItems) {
|
|
95
|
+
const type = typeof item["resourceType"] === "string" ? (item["resourceType"] as string) : "";
|
|
96
|
+
if (type.toLowerCase() !== "script") continue;
|
|
97
|
+
const host = hostnameOf(typeof item["url"] === "string" ? (item["url"] as string) : undefined);
|
|
98
|
+
if (!host || host === pageHost) continue;
|
|
99
|
+
if (!thirdPartyOrigins.includes(host)) thirdPartyOrigins.push(host);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
routeId,
|
|
104
|
+
...(lhr.finalUrl || lhr.requestedUrl ? { url: lhr.finalUrl ?? lhr.requestedUrl } : {}),
|
|
105
|
+
...(Object.keys(resources).length ? { resources } : {}),
|
|
106
|
+
...(Object.keys(webVitals).length ? { webVitals } : {}),
|
|
107
|
+
...(thirdPartyOrigins.length ? { thirdPartyOrigins } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|