@tokenoftrust/storefront-runner 2.2.126 → 2.3.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 (107) hide show
  1. package/apps/storefront/package.json +3 -1
  2. package/apps/storefront/public/shared/commerce-chrome.css +57 -15
  3. package/apps/storefront/public/shared/commerce-marketing.css +7 -7
  4. package/apps/storefront/src/components/Badge.astro +1 -1
  5. package/apps/storefront/src/components/Breadcrumbs.astro +8 -3
  6. package/apps/storefront/src/components/Section.astro +4 -1
  7. package/apps/storefront/src/components/Seo.astro +7 -5
  8. package/apps/storefront/src/components/blog/BlogIndexView.astro +22 -13
  9. package/apps/storefront/src/components/blog/ShareRow.astro +43 -0
  10. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +1 -1
  11. package/apps/storefront/src/components/chrome/NavDropdown.astro +7 -7
  12. package/apps/storefront/src/components/chrome/SiteFooter.astro +17 -0
  13. package/apps/storefront/src/components/chrome/SiteHeader.astro +5 -5
  14. package/apps/storefront/src/components/commerce/TagFilterCloud.astro +1 -1
  15. package/apps/storefront/src/components/compliance/NicotineWarning.astro +12 -22
  16. package/apps/storefront/src/components/compliance/PactActNotice.astro +9 -10
  17. package/apps/storefront/src/components/content/ProseSections.astro +2 -2
  18. package/apps/storefront/src/components/home/Hero.astro +34 -12
  19. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +39 -5
  20. package/apps/storefront/src/components/islands/VariantSelector.tsx +3 -3
  21. package/apps/storefront/src/components/marketing/CardGrid.astro +1 -1
  22. package/apps/storefront/src/components/marketing/CodeSample.astro +1 -1
  23. package/apps/storefront/src/components/marketing/CtaBand.astro +1 -1
  24. package/apps/storefront/src/components/marketing/Faq.astro +1 -1
  25. package/apps/storefront/src/components/marketing/Integrations.astro +1 -1
  26. package/apps/storefront/src/components/marketing/MarketingCtas.astro +1 -1
  27. package/apps/storefront/src/components/marketing/MarketingHero.astro +1 -1
  28. package/apps/storefront/src/components/marketing/ProofStrip.astro +1 -1
  29. package/apps/storefront/src/components/marketing/SplitCompare.astro +1 -1
  30. package/apps/storefront/src/components/marketing/Steps.astro +1 -1
  31. package/apps/storefront/src/components/marketing/Testimonials.astro +2 -2
  32. package/apps/storefront/src/components/marketing/TrustBar.astro +1 -1
  33. package/apps/storefront/src/components/membership/TierComparisonTable.astro +1 -1
  34. package/apps/storefront/src/components/plp/ActiveFilters.astro +1 -1
  35. package/apps/storefront/src/components/plp/FacetSidebar.astro +4 -4
  36. package/apps/storefront/src/components/plp/Pagination.astro +1 -1
  37. package/apps/storefront/src/components/plp/SortSelect.astro +1 -1
  38. package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +18 -3
  39. package/apps/storefront/src/components/the-build/TheBuildIndexView.astro +9 -5
  40. package/apps/storefront/src/config/devTenantSeed.ts +20 -34
  41. package/apps/storefront/src/config/resolver.ts +0 -38
  42. package/apps/storefront/src/layouts/Layout.astro +8 -1
  43. package/apps/storefront/src/lib/assets/preview-version-cookie.ts +52 -5
  44. package/apps/storefront/src/lib/blog/pagination.ts +20 -10
  45. package/apps/storefront/src/lib/blog/presentation.ts +94 -0
  46. package/apps/storefront/src/lib/blog/rss.ts +1 -1
  47. package/apps/storefront/src/lib/blog/types.ts +24 -7
  48. package/apps/storefront/src/lib/breadcrumbs.ts +25 -0
  49. package/apps/storefront/src/lib/chrome/model.ts +4 -0
  50. package/apps/storefront/src/lib/cloudflare-workers.d.ts +53 -0
  51. package/apps/storefront/src/lib/compliance/rawComplianceNotices.ts +38 -18
  52. package/apps/storefront/src/lib/dashboard/delegateGate.ts +28 -0
  53. package/apps/storefront/src/lib/homeVisualParity.ts +8 -5
  54. package/apps/storefront/src/lib/jsonld.ts +5 -5
  55. package/apps/storefront/src/lib/pinnedRouteRewriteBoundary.ts +20 -2
  56. package/apps/storefront/src/lib/rawChrome.ts +74 -12
  57. package/apps/storefront/src/lib/runtimeEnv.ts +1 -1
  58. package/apps/storefront/src/lib/seo/documentTitle.ts +19 -0
  59. package/apps/storefront/src/lib/shared/addToRelease.ts +32 -0
  60. package/apps/storefront/src/lib/storyblok/content-model.ts +3 -0
  61. package/apps/storefront/src/lib/styleGuideThemes.ts +20 -22
  62. package/apps/storefront/src/pages/[...slug].astro +13 -8
  63. package/apps/storefront/src/pages/blog/[slug].astro +56 -24
  64. package/apps/storefront/src/pages/blog/author/[author].astro +17 -9
  65. package/apps/storefront/src/pages/blog/category/[category].astro +18 -10
  66. package/apps/storefront/src/pages/blog/index.astro +2 -0
  67. package/apps/storefront/src/pages/blog/page/[n].astro +2 -0
  68. package/apps/storefront/src/pages/blog/tag/[tag].astro +18 -10
  69. package/apps/storefront/src/pages/collections/[handle].astro +5 -4
  70. package/apps/storefront/src/pages/collections/index.astro +4 -3
  71. package/apps/storefront/src/pages/index.astro +34 -27
  72. package/apps/storefront/src/pages/products/[handle].astro +5 -4
  73. package/apps/storefront/src/pages/saved.astro +4 -3
  74. package/apps/storefront/src/pages/search.astro +4 -3
  75. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +129 -118
  76. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +19 -15
  77. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +23 -27
  78. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -14
  79. package/apps/storefront/src/pages/style-guide/index.astro +6 -5
  80. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +32 -7
  81. package/apps/storefront/src/pages/the-build/[slug].astro +44 -20
  82. package/apps/storefront/src/pages/the-build/index.astro +2 -0
  83. package/apps/storefront/src/pages/the-build/page/[n].astro +2 -0
  84. package/apps/storefront/src/styles/admin.css +47 -0
  85. package/apps/storefront/src/styles/global.css +104 -56
  86. package/apps/storefront/src/themes/derivedTokens.ts +34 -0
  87. package/apps/storefront/src/themes/schema.ts +6 -0
  88. package/apps/storefront/test/pipeline/harness.ts +164 -0
  89. package/apps/storefront/test/pipeline/intake-worker.mjs +14 -0
  90. package/apps/storefront/test/pipeline/pipeline-host.ts +77 -0
  91. package/apps/storefront/test/pipeline/synthetic-job.ts +156 -0
  92. package/apps/storefront/vitest.workers.config.cts +67 -0
  93. package/docs/widget-library-guide.md +1496 -0
  94. package/package.json +1 -1
  95. package/packages/cli/src/declared-config.mjs +400 -5
  96. package/packages/public-runtime/package.json +1 -0
  97. package/packages/public-runtime/src/chrome.ts +93 -2
  98. package/packages/public-runtime/src/compliance-coherence.d.mts +17 -0
  99. package/packages/public-runtime/src/compliance-coherence.mjs +84 -0
  100. package/packages/public-runtime/src/declared-tenant-config.d.mts +1 -1
  101. package/packages/public-runtime/src/declared-tenant-config.mjs +6 -2
  102. package/packages/public-runtime/src/extension-contract-values.d.mts +4 -1
  103. package/packages/public-runtime/src/extension-contract-values.mjs +14 -1
  104. package/packages/public-runtime/src/extension-contract.ts +2 -0
  105. package/packages/public-runtime/src/tenant.ts +48 -4
  106. package/pnpm-lock.runner.yaml +469 -0
  107. package/scripts/tenant/validate.mjs +27 -1
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Compliance COHERENCE: what a regulated tenant's declared `compliance` block must hold
3
+ * together before its preview can pass the `age_gate`, `excise` and `jurisdiction`
4
+ * evidence checks. Every rule here reads declared config only, so it is checkable from
5
+ * a checkout with no platform state:
6
+ *
7
+ * - the hosted reconcile grades these checks with it
8
+ * (packages/private-controlplane/src/evidence-producer.ts);
9
+ * - `tot validate` reports the same failures locally, from the CLI's own copy
10
+ * (packages/cli/src/declared-config.mjs), pinned to this one by
11
+ * packages/cli/test/declared-config-parity.test.mjs.
12
+ *
13
+ * Each failure names the exact `compliance.*` field that resolves it, in both the
14
+ * detail and the hint, so the first failure a developer sees is actionable.
15
+ *
16
+ * Plain JS on purpose (same reason as declared-tenant-config.mjs): plain-`node`
17
+ * tooling and the CLI's parity test read it alongside the TypeScript runtime.
18
+ */
19
+
20
+ /** The evidence checks this module grades, in report order. */
21
+ export const COHERENCE_CHECK_IDS = Object.freeze(["age_gate", "excise", "jurisdiction"]);
22
+
23
+ /**
24
+ * True when the declared compliance block marks the tenant regulated (an age minimum
25
+ * or the FDA nicotine warning).
26
+ *
27
+ * @param {Record<string, unknown> | null | undefined} compliance
28
+ */
29
+ export function isRegulatedCompliance(compliance) {
30
+ const minAge = compliance?.minAge;
31
+ return (typeof minAge === "number" && minAge > 0) || !!compliance?.nicotineWarning;
32
+ }
33
+
34
+ /**
35
+ * True when the declared block renders a shipping/jurisdiction notice: the platform's
36
+ * fixed-wording PACT Act notice (`pactAct: true`) or the tenant's own restriction
37
+ * text (`shippingRestriction`). Both render in the site footer.
38
+ *
39
+ * @param {Record<string, unknown> | null | undefined} compliance
40
+ */
41
+ export function declaresJurisdictionNotice(compliance) {
42
+ const text = compliance?.shippingRestriction;
43
+ return compliance?.pactAct === true || (typeof text === "string" && text.trim().length > 0);
44
+ }
45
+
46
+ /**
47
+ * Every coherence failure in a declared compliance block, keyed by evidence check id.
48
+ * An unregulated block has none. An absent key means that check is coherent.
49
+ *
50
+ * @param {Record<string, unknown> | null | undefined} compliance
51
+ * @returns {Partial<Record<"age_gate" | "excise" | "jurisdiction", { field: string, detail: string, hint: string }>>}
52
+ */
53
+ export function complianceCoherenceFailures(compliance) {
54
+ /** @type {Partial<Record<"age_gate" | "excise" | "jurisdiction", { field: string, detail: string, hint: string }>>} */
55
+ const out = {};
56
+ if (!isRegulatedCompliance(compliance)) return out;
57
+ const minAge = typeof compliance?.minAge === "number" ? compliance.minAge : null;
58
+ if (minAge == null || minAge < 18) {
59
+ out.age_gate = {
60
+ field: "compliance.minAge",
61
+ detail: "Age-restricted/nicotine products require `compliance.minAge` (18 or higher) — none is set.",
62
+ hint: "Set `compliance.minAge` (18+) in .tot/config.json.",
63
+ };
64
+ }
65
+ if (compliance?.nicotineWarning && (minAge == null || minAge < 21)) {
66
+ out.excise = {
67
+ field: "compliance.minAge",
68
+ detail: "Nicotine products (`compliance.nicotineWarning`) require `compliance.minAge` of 21 or higher.",
69
+ hint: "Set `compliance.minAge` to 21 in .tot/config.json (nicotine requires 21+, not just 18+).",
70
+ };
71
+ }
72
+ if (!declaresJurisdictionNotice(compliance)) {
73
+ out.jurisdiction = {
74
+ field: "compliance.pactAct",
75
+ detail:
76
+ "Regulated products require a shipping/jurisdiction notice: set `compliance.pactAct: true` " +
77
+ "or `compliance.shippingRestriction` — neither is set.",
78
+ hint:
79
+ "Set `compliance.pactAct: true` for the platform's PACT Act notice, or " +
80
+ "`compliance.shippingRestriction` to your own restriction text, in .tot/config.json.",
81
+ };
82
+ }
83
+ return out;
84
+ }
@@ -1,7 +1,7 @@
1
1
  /** Types for the dependency-free declared-tenant-config module (declared-tenant-config.mjs). */
2
2
  import type { TenantConfig } from "./tenant.js";
3
3
 
4
- export type DeclaredTenantConfigField = "siteType" | "compliance" | "capabilities" | "features" | "embeds";
4
+ export type DeclaredTenantConfigField = "displayName" | "siteType" | "compliance" | "capabilities" | "features" | "embeds";
5
5
  export type DeclaredTenantConfig = Partial<Pick<TenantConfig, DeclaredTenantConfigField>>;
6
6
 
7
7
  export const DECLARED_TENANT_CONFIG_FIELDS: readonly DeclaredTenantConfigField[];
@@ -19,6 +19,7 @@
19
19
 
20
20
  /** `.tot/config.json` keys that become TenantConfig fields. */
21
21
  export const DECLARED_TENANT_CONFIG_FIELDS = Object.freeze([
22
+ "displayName",
22
23
  "siteType",
23
24
  "compliance",
24
25
  "capabilities",
@@ -87,7 +88,10 @@ export function declaredTenantConfig(config) {
87
88
  /** @type {Record<string, unknown>} */
88
89
  const out = {};
89
90
  if (!isPlainObject(config)) return out;
90
- const { siteType, compliance, capabilities, features, embeds } = /** @type {Record<string, unknown>} */ (config);
91
+ const { displayName, siteType, compliance, capabilities, features, embeds } = /** @type {Record<string, unknown>} */ (config);
92
+ // The store's name as visitors read it: the `<title>` suffix, og:site_name,
93
+ // Organization JSON-LD, breadcrumbs, the default blog heading, the copyright line.
94
+ if (typeof displayName === "string" && displayName.trim()) out.displayName = displayName.trim();
91
95
  if (typeof siteType === "string" && SITE_TYPES.has(siteType)) out.siteType = siteType;
92
96
  if (isPlainObject(compliance)) out.compliance = compliance;
93
97
  if (isPlainObject(capabilities)) out.capabilities = capabilities;
@@ -102,7 +106,7 @@ export function isEmptyDeclaredTenantConfig(declared) {
102
106
  }
103
107
 
104
108
  /**
105
- * Overlay a declaration onto a resolved tenant. `siteType` is replaced; the object
109
+ * Overlay a declaration onto a resolved tenant. `displayName` and `siteType` are replaced; the object
106
110
  * fields merge key by key, and `embeds` (catalog slugs) is unioned, so a declaration
107
111
  * adds to — and can set individual keys of — what the platform record already
108
112
  * carries, but never erases what it omits.
@@ -1,4 +1,4 @@
1
- export declare const BLOCK_PALETTE_CURRENT_CONTRACT: "block-palette@6";
1
+ export declare const BLOCK_PALETTE_CURRENT_CONTRACT: "block-palette@7";
2
2
  export declare const BLOCK_PALETTE_SUPPORTED_CONTRACTS: readonly [
3
3
  "block-palette@1",
4
4
  "block-palette@2",
@@ -6,6 +6,7 @@ export declare const BLOCK_PALETTE_SUPPORTED_CONTRACTS: readonly [
6
6
  "block-palette@4",
7
7
  "block-palette@5",
8
8
  "block-palette@6",
9
+ "block-palette@7",
9
10
  ];
10
11
  export declare const FEATURED_PRODUCT_PRESENTATIONS: readonly ["grid", "carousel"];
11
12
  export declare const PROMO_TILE_PRESENTATIONS: readonly [
@@ -14,6 +15,8 @@ export declare const PROMO_TILE_PRESENTATIONS: readonly [
14
15
  "editorial-cards",
15
16
  "logo-strip",
16
17
  ];
18
+ export declare const THEME_HEADING_LEVELS: readonly ["hero", "h1", "h2", "h3"];
19
+ export declare function isThemeCssSize(value: unknown): value is string;
17
20
  export declare function isSafeRehostedAssetPath(value: unknown): value is string;
18
21
  export declare function isSafeRehostedAssetPathForTenant(value: unknown, tenantId: unknown): value is string;
19
22
  export declare function isSafeStorefrontHref(value: unknown): value is string;
@@ -5,7 +5,7 @@
5
5
  * runtime and repository Node tooling. The separately published CLI mirrors
6
6
  * these values and pins that mirror with a parity test.
7
7
  */
8
- export const BLOCK_PALETTE_CURRENT_CONTRACT = "block-palette@6";
8
+ export const BLOCK_PALETTE_CURRENT_CONTRACT = "block-palette@7";
9
9
 
10
10
  export const BLOCK_PALETTE_SUPPORTED_CONTRACTS = Object.freeze([
11
11
  "block-palette@1",
@@ -13,6 +13,7 @@ export const BLOCK_PALETTE_SUPPORTED_CONTRACTS = Object.freeze([
13
13
  "block-palette@3",
14
14
  "block-palette@4",
15
15
  "block-palette@5",
16
+ "block-palette@6",
16
17
  BLOCK_PALETTE_CURRENT_CONTRACT,
17
18
  ]);
18
19
 
@@ -25,6 +26,18 @@ export const PROMO_TILE_PRESENTATIONS = Object.freeze([
25
26
  "logo-strip",
26
27
  ]);
27
28
 
29
+ /** Heading levels a theme may size (theme.json `type.headings`). */
30
+ export const THEME_HEADING_LEVELS = Object.freeze(["hero", "h1", "h2", "h3"]);
31
+
32
+ /**
33
+ * A heading size a theme may emit: a CSS length or a math function over lengths
34
+ * ("28px", "1.75rem", "clamp(1.5rem, 4vw, 2rem)"). The character set cannot close
35
+ * the declaration or the injected `<style>` block.
36
+ */
37
+ export function isThemeCssSize(value) {
38
+ return typeof value === "string" && value.length <= 120 && /^[0-9a-z.%(),+\-*/ ]*[0-9a-z%)]$/i.test(value.trim());
39
+ }
40
+
28
41
  /** True only for static assets materialized into a governed serving namespace. */
29
42
  export function isSafeRehostedAssetPath(value) {
30
43
  if (typeof value !== "string" || !value.startsWith("/")) return false;
@@ -16,9 +16,11 @@ export {
16
16
  BLOCK_PALETTE_SUPPORTED_CONTRACTS,
17
17
  FEATURED_PRODUCT_PRESENTATIONS,
18
18
  PROMO_TILE_PRESENTATIONS,
19
+ THEME_HEADING_LEVELS,
19
20
  isSafeRehostedAssetPath,
20
21
  isSafeRehostedAssetPathForTenant,
21
22
  isSafeStorefrontHref,
23
+ isThemeCssSize,
22
24
  } from "./extension-contract-values.mjs";
23
25
 
24
26
  export type PromoTilePresentation =
@@ -61,9 +61,12 @@ export interface ThemeColorTokens {
61
61
  error?: string;
62
62
  /** Informational state (notices, help). */
63
63
  info?: string;
64
- /** Accent tuned for SMALL text/links on light surfaces (AA). Default: a
65
- * derived darken of `accent`; set to hand-tune contrast. */
64
+ /** Accent tuned for SMALL text/links on light surfaces (AA). Default:
65
+ * `accent` with its lightness capped; set to hand-tune contrast. */
66
66
  "accent-ink"?: string;
67
+ /** Readable text/icon color ON an `accent` fill. Default: black or white,
68
+ * whichever the accent's lightness favours; set to hand-tune contrast. */
69
+ "accent-contrast"?: string;
67
70
  /** Hover/active shade of `primary`. Default: a derived darken of `primary`. */
68
71
  "primary-hover"?: string;
69
72
  /** Hover/active shade of `accent`. Default: a derived darken of `accent`. */
@@ -98,6 +101,9 @@ export interface ThemeColorTokens {
98
101
  /** Trust / reassurance accent — guarantee callouts, security and compliance
99
102
  * badges, proof strips. Default: `success`. */
100
103
  trust?: string;
104
+ /** Home hero eyebrow text. Default: `muted` on a light hero, a translucent
105
+ * `primary-contrast` on a bold hero. */
106
+ "hero-eyebrow"?: string;
101
107
  }
102
108
 
103
109
  export interface ThemeFontFamily {
@@ -112,8 +118,24 @@ export interface ThemeTypeTokens {
112
118
  /** Type scale ratio, e.g. 1.25 (major third). */
113
119
  scale: number;
114
120
  tracking?: string;
121
+ /**
122
+ * Fixed heading sizes (any CSS length: "28px", "1.75rem", "clamp(…)"). Each
123
+ * replaces the fluid display-scale size for its level: `hero` sizes the home
124
+ * hero headline; `h1`–`h3` size every other heading of that level (page and
125
+ * listing titles, block titles, rich-text headings). Unset levels keep the
126
+ * fluid scale derived from `scale`.
127
+ */
128
+ headings?: ThemeHeadingSizes;
129
+ }
130
+
131
+ export interface ThemeHeadingSizes {
132
+ hero?: string;
133
+ h1?: string;
134
+ h2?: string;
135
+ h3?: string;
115
136
  }
116
137
 
138
+
117
139
  export interface ThemeSpaceTokens {
118
140
  /** Base spacing unit in px. */
119
141
  base: number;
@@ -259,6 +281,24 @@ export interface ExciseTaxConfig {
259
281
  */
260
282
  export type ComplianceRuleProfile = "vape-nicotine-us";
261
283
 
284
+ /**
285
+ * The FDA nicotine warning's presentation: a validated `preset` ("dark" by default,
286
+ * "light"), optionally adjusted by `style`. Any `style` that leaves the preset is a
287
+ * custom presentation the owner approves before it goes live. Validated and resolved
288
+ * by packages/cli/src/declared-config.mjs; the wording is never configurable.
289
+ */
290
+ export interface NicotineWarningDeclaration {
291
+ preset?: string;
292
+ style?: {
293
+ background?: string;
294
+ text?: string;
295
+ border?: string;
296
+ textTransform?: "uppercase" | "none";
297
+ fontWeight?: number;
298
+ fontSizePx?: number;
299
+ };
300
+ }
301
+
262
302
  export interface ComplianceConfig {
263
303
  /**
264
304
  * The reviewed rule PROFILE whose ruleset the enforcement gate evaluates for
@@ -272,8 +312,12 @@ export interface ComplianceConfig {
272
312
  ruleProfile?: ComplianceRuleProfile;
273
313
  /** Minimum age to enter. Drives the soft affirmation gate (e.g. 21). */
274
314
  minAge?: number;
275
- /** Render the FDA nicotine warning sitewide + reinforced on PDP. */
276
- nicotineWarning?: boolean;
315
+ /**
316
+ * Render the FDA nicotine warning sitewide + reinforced on PDP. `true` renders the
317
+ * default preset; an object picks a validated preset and, optionally, a custom style
318
+ * the owner reviews at go-live (packages/cli/src/declared-config.mjs).
319
+ */
320
+ nicotineWarning?: boolean | NicotineWarningDeclaration;
277
321
  /** Short PACT-Act / no-ship-states / adult-signature notice (plain text). */
278
322
  shippingRestriction?: string;
279
323
  /**