@tokenoftrust/storefront-runner 3.12.1 → 3.12.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.
@@ -41,10 +41,12 @@ export const HOME_TENANT: TenantConfig = {
41
41
  siteType: "marketing",
42
42
  };
43
43
 
44
- // Generic SAMPLE tenant — a placeholder regulated store used to demonstrate the
45
- // compliance affordance (age-gate + nicotine warning) WITHOUT any real merchant.
46
- // Routed at /sample-store.example/. Content comes from the developer's grafted
47
- // checkout (tenants/sample-store.example/); with none present it degrades to the
44
+ // Generic SAMPLE tenant — the public fallback renderer's regulated store.
45
+ // Its page bands come from the same complianceSections() gates as a primary
46
+ // vape-nicotine-us tenant (nicotine warning, shipping restriction, age gate,
47
+ // excise disclosure). No real merchant. Routed at /sample-store.example/.
48
+ // Content comes from the developer's grafted checkout
49
+ // (tenants/sample-store.example/); with none present it degrades to the
48
50
  // default scope, and the compliance overlay still renders from this config.
49
51
  export const SAMPLE_TENANT: TenantConfig = {
50
52
  tenant_id: "sample-store.example",
@@ -62,14 +64,22 @@ export const SAMPLE_TENANT: TenantConfig = {
62
64
  repo: "sample-store.example",
63
65
  defaultBranch: "main",
64
66
  })],
65
- // Regulated-store demo: shows the 21+/PACT compliance gate is CONFIG-driven.
67
+ // Same section inputs as a primary vape-nicotine-us commerce tenant.
68
+ // pactAct stays unset: the primary page does not emit that band unless the
69
+ // flag is set. ruleProfile selects the checkout ruleset only.
66
70
  compliance: {
71
+ ruleProfile: "vape-nicotine-us",
67
72
  minAge: 21,
68
73
  nicotineWarning: true,
69
74
  shippingRestriction:
70
75
  "Adult signature (21+) required on delivery. Some destinations are restricted — verified at checkout.",
71
76
  },
72
- siteType: "marketing",
77
+ capabilities: {
78
+ cartCheckout: { enabled: true },
79
+ ageVerification: { enabled: true },
80
+ exciseTax: { enabled: true },
81
+ },
82
+ siteType: "commerce",
73
83
  };
74
84
 
75
85
  const REGISTRY: TenantConfig[] = [HOME_TENANT, SAMPLE_TENANT];
@@ -20,6 +20,7 @@ import PactActNotice from "@/components/compliance/storefront/compliance/PactAct
20
20
  import ShippingRestrictionNotice from "@/components/compliance/storefront/compliance/ShippingRestrictionNotice.astro";
21
21
  import ExciseTaxNotice from "@/components/compliance/storefront/compliance/ExciseTaxNotice.astro";
22
22
  import { EXCISE_TAX_DISCLOSURE_TEXT } from "@/lib/compliance/rawComplianceNotices";
23
+ import { complianceSections } from "@/lib/compliance/complianceSections";
23
24
  import IsolatedAgeGate from "@/components/compliance/storefront/islands/IsolatedAgeGate.tsx";
24
25
  import AuthBadge from "@/components/access/storefront/auth/AuthBadge.astro";
25
26
  import { referenceTheme } from "@/themes/reference";
@@ -215,6 +216,12 @@ if (chromeHtml && chromeHtml.includes(CHROME_BODY_MARKER)) {
215
216
  }
216
217
 
217
218
  const compliance = tenant.compliance;
219
+ const sections = complianceSections({
220
+ compliance,
221
+ ageVerificationEnabled: capabilities.ageVerification.enabled,
222
+ exciseTaxEnabled: capabilities.exciseTax.enabled,
223
+ catalog: features.catalog,
224
+ });
218
225
  const cartHref = managedCartHref(Astro.locals.commerce);
219
226
 
220
227
  // Site-chrome data resolution (chrome/* is the canonical, prop-driven
@@ -368,7 +375,7 @@ const footerWidgets = await renderWidgetSlot(
368
375
 
369
376
  <AuthBadge />
370
377
 
371
- {compliance?.nicotineWarning && <NicotineWarning variant="bar" declaration={compliance.nicotineWarning} />}
378
+ {sections.includes("nicotine-warning") && compliance?.nicotineWarning && <NicotineWarning variant="bar" declaration={compliance.nicotineWarning} />}
372
379
 
373
380
  {sharedChrome ? (
374
381
  <Fragment set:html={sharedChrome.header} />
@@ -406,8 +413,12 @@ const footerWidgets = await renderWidgetSlot(
406
413
  <slot />
407
414
  </main>
408
415
 
409
- <PactActNotice enabled={compliance?.pactAct} minAge={compliance?.minAge} variant="footer" />
410
- <ShippingRestrictionNotice text={compliance?.shippingRestriction} />
416
+ {sections.includes("pact-act") && (
417
+ <PactActNotice enabled={compliance?.pactAct} minAge={compliance?.minAge} variant="footer" />
418
+ )}
419
+ {sections.includes("shipping-restriction") && (
420
+ <ShippingRestrictionNotice text={compliance?.shippingRestriction} />
421
+ )}
411
422
 
412
423
  {sharedChrome ? (
413
424
  <>
@@ -460,7 +471,7 @@ const footerWidgets = await renderWidgetSlot(
460
471
  </div>
461
472
  ))}
462
473
 
463
- {capabilities.ageVerification.enabled && compliance?.minAge && (
474
+ {sections.includes("age-gate") && capabilities.ageVerification.enabled && compliance?.minAge && (
464
475
  <IsolatedAgeGate
465
476
  client:only="preact"
466
477
  minAge={compliance.minAge}
@@ -477,7 +488,7 @@ const footerWidgets = await renderWidgetSlot(
477
488
  {/* Excise tax (Track B) — when enabled for this environment, the excise line
478
489
  is calculated + added inside the managed cart. This is the storefront's
479
490
  honest shopper-facing signal of it. */}
480
- {capabilities.exciseTax.enabled && features.catalog && (
491
+ {sections.includes("excise-tax") && capabilities.exciseTax.enabled && features.catalog && (
481
492
  <ExciseTaxNotice variant="footer" config={{ note: EXCISE_TAX_DISCLOSURE_TEXT }} />
482
493
  )}
483
494
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The page compliance bands, in document order.
3
+ *
4
+ * Layout.astro (primary) and the public fallback renderer (the sanitized
5
+ * sample tenant in the published runner) both decide which bands to emit from
6
+ * this list. `ruleProfile` selects the checkout ruleset; it does not add a
7
+ * band. A vape-nicotine-us store shows pact-act only when `compliance.pactAct`
8
+ * is set, the same gate the primary tenants use today.
9
+ */
10
+ import {
11
+ resolveCapabilities,
12
+ resolveTenantFeatures,
13
+ type ComplianceConfig,
14
+ type TenantConfig,
15
+ } from "@tot/public-runtime";
16
+ import {
17
+ exciseTaxNoticeHtml,
18
+ nicotineWarningBarHtml,
19
+ pactActNoticeHtml,
20
+ shippingRestrictionNoticeHtml,
21
+ type RawComplianceChromeInput,
22
+ } from "./rawComplianceNotices";
23
+
24
+ export const COMPLIANCE_SECTION_IDS = [
25
+ "nicotine-warning",
26
+ "pact-act",
27
+ "shipping-restriction",
28
+ "age-gate",
29
+ "excise-tax",
30
+ ] as const;
31
+
32
+ export type ComplianceSectionId = (typeof COMPLIANCE_SECTION_IDS)[number];
33
+
34
+ export interface ComplianceSectionInput {
35
+ compliance?: ComplianceConfig | null;
36
+ ageVerificationEnabled: boolean;
37
+ exciseTaxEnabled: boolean;
38
+ catalog: boolean;
39
+ }
40
+
41
+ /** Bands Layout.astro mounts, under the same conditions, in document order. */
42
+ export function complianceSections(input: ComplianceSectionInput): ComplianceSectionId[] {
43
+ const compliance = input.compliance;
44
+ const sections: ComplianceSectionId[] = [];
45
+ if (compliance?.nicotineWarning) sections.push("nicotine-warning");
46
+ if (compliance?.pactAct) sections.push("pact-act");
47
+ if (compliance?.shippingRestriction) sections.push("shipping-restriction");
48
+ if (input.ageVerificationEnabled && compliance?.minAge) sections.push("age-gate");
49
+ if (input.exciseTaxEnabled && input.catalog) sections.push("excise-tax");
50
+ return sections;
51
+ }
52
+
53
+ /**
54
+ * A primary vape-nicotine-us tenant as the registry declares one: the profile,
55
+ * 21+, the nicotine warning, a shipping line, commerce (catalog on), and the
56
+ * three capabilities. `pactAct` stays unset, so the list has no pact-act band.
57
+ */
58
+ export const VAPE_NICOTINE_US_PRIMARY: ComplianceSectionInput = {
59
+ compliance: {
60
+ ruleProfile: "vape-nicotine-us",
61
+ minAge: 21,
62
+ nicotineWarning: true,
63
+ shippingRestriction:
64
+ "Adult signature (21+) required on delivery. Some destinations are restricted — verified at checkout.",
65
+ },
66
+ ageVerificationEnabled: true,
67
+ exciseTaxEnabled: true,
68
+ catalog: true,
69
+ };
70
+
71
+ export function vapeNicotineUsSections(): ComplianceSectionId[] {
72
+ return complianceSections(VAPE_NICOTINE_US_PRIMARY);
73
+ }
74
+
75
+ /** Raw-HTML injection for the bands in `sections`. Age-gate is not injectable. */
76
+ export function rawComplianceChrome(
77
+ sections: readonly ComplianceSectionId[],
78
+ notices: RawComplianceChromeInput,
79
+ ): { top: string; bottom: string } {
80
+ const on = (id: ComplianceSectionId) => sections.includes(id);
81
+ const top = on("nicotine-warning") && notices.nicotineWarning
82
+ ? nicotineWarningBarHtml(notices.nicotineWarning)
83
+ : "";
84
+ let bottom = "";
85
+ if (on("pact-act") && notices.pactAct) bottom += pactActNoticeHtml(notices.minAge);
86
+ if (on("shipping-restriction")) bottom += shippingRestrictionNoticeHtml(notices.shippingRestriction);
87
+ if (on("excise-tax") && notices.exciseTaxEnabled) bottom += exciseTaxNoticeHtml();
88
+ return { top, bottom };
89
+ }
90
+
91
+ /** The same bands Layout would mount for this tenant, as raw-HTML chrome. */
92
+ export function complianceChromeForTenant(
93
+ tenant: Pick<TenantConfig, "compliance" | "siteType" | "capabilities" | "features">,
94
+ ): { top: string; bottom: string } {
95
+ const capabilities = resolveCapabilities(tenant);
96
+ const features = resolveTenantFeatures(tenant);
97
+ const notices: RawComplianceChromeInput = {
98
+ nicotineWarning: tenant.compliance?.nicotineWarning,
99
+ pactAct: !!tenant.compliance?.pactAct,
100
+ minAge: tenant.compliance?.minAge,
101
+ shippingRestriction: tenant.compliance?.shippingRestriction,
102
+ exciseTaxEnabled: capabilities.exciseTax.enabled && features.catalog,
103
+ };
104
+ return rawComplianceChrome(
105
+ complianceSections({
106
+ compliance: tenant.compliance,
107
+ ageVerificationEnabled: capabilities.ageVerification.enabled,
108
+ exciseTaxEnabled: capabilities.exciseTax.enabled,
109
+ catalog: features.catalog,
110
+ }),
111
+ notices,
112
+ );
113
+ }
@@ -14,7 +14,7 @@ import { breadcrumbLd } from "@/lib/content/shared/jsonld";
14
14
  import { truncate } from "@/lib/content/shared/format";
15
15
  import { faqFromHtml, faqFromSections } from "@/lib/content/seo/faqItems";
16
16
  import { rawHtmlPolicy, rawHtmlResponse, rawPageCanonicalUrl } from "@/lib/content/shared/rawMarketingHtml";
17
- import { renderRawComplianceTop, renderRawComplianceBottom } from "@/lib/compliance/rawComplianceNotices";
17
+ import { complianceSections, rawComplianceChrome } from "@/lib/compliance/complianceSections";
18
18
  import { styleGuideAliasRewrite } from "@/lib/content/shared/styleGuidePaths";
19
19
  import { getBlogSettings } from "@/lib/content/blog/provider.js";
20
20
  import { loadBlogPostViewV1, type BlogPostViewPropsV1 } from "@/lib/content/blog/postView.js";
@@ -85,6 +85,12 @@ if (!blogPostProps) {
85
85
  shippingRestriction: tenant.compliance?.shippingRestriction,
86
86
  exciseTaxEnabled: capabilities.exciseTax.enabled && features.catalog,
87
87
  };
88
+ const complianceChrome = rawComplianceChrome(complianceSections({
89
+ compliance: tenant.compliance,
90
+ ageVerificationEnabled: capabilities.ageVerification.enabled,
91
+ exciseTaxEnabled: capabilities.exciseTax.enabled,
92
+ catalog: features.catalog,
93
+ }), notices);
88
94
  return rawHtmlResponse(rawHtml, {
89
95
  nonce: Astro.locals.cspNonce,
90
96
  basePath,
@@ -93,8 +99,8 @@ if (!blogPostProps) {
93
99
  plan: Astro.locals.integrationPlan,
94
100
  consentSnapshot: Astro.locals.consentSnapshot,
95
101
  canonicalUrl: rawPageCanonicalUrl(canonicalBase, slug),
96
- complianceTop: rawPolicy.regulated ? renderRawComplianceTop(notices) : undefined,
97
- complianceBottom: rawPolicy.regulated ? renderRawComplianceBottom(notices) : undefined,
102
+ complianceTop: complianceChrome.top || undefined,
103
+ complianceBottom: complianceChrome.bottom || undefined,
98
104
  });
99
105
  }
100
106
 
@@ -20,7 +20,7 @@ import MarketingBlocks from "@/components/content/storefront/marketing/Marketing
20
20
  import TierCards from "@/components/membership/storefront/membership/TierCards.astro";
21
21
  import { withBase } from "@/lib/publication/shared/basePath";
22
22
  import { rawHtmlPolicy, rawHtmlResponse } from "@/lib/content/shared/rawMarketingHtml";
23
- import { renderRawComplianceTop, renderRawComplianceBottom } from "@/lib/compliance/rawComplianceNotices";
23
+ import { complianceSections, rawComplianceChrome } from "@/lib/compliance/complianceSections";
24
24
  import AppWidgetFrame from "@/components/integrations/storefront/apps/AppWidgetFrame.astro";
25
25
  import { getRegistryService } from "@/lib/integrations/apps/registryAccess.js";
26
26
  import { renderWidgetSlot } from "@/lib/integrations/apps/widgets/renderSlot.js";
@@ -73,6 +73,12 @@ if (rawHtml && rawPolicy.allowed) {
73
73
  shippingRestriction: tenant.compliance?.shippingRestriction,
74
74
  exciseTaxEnabled: capabilities.exciseTax.enabled && features.catalog,
75
75
  };
76
+ const complianceChrome = rawComplianceChrome(complianceSections({
77
+ compliance: tenant.compliance,
78
+ ageVerificationEnabled: capabilities.ageVerification.enabled,
79
+ exciseTaxEnabled: capabilities.exciseTax.enabled,
80
+ catalog: features.catalog,
81
+ }), notices);
76
82
  return rawHtmlResponse(rawHtml, {
77
83
  nonce: Astro.locals.cspNonce,
78
84
  basePath,
@@ -81,8 +87,8 @@ if (rawHtml && rawPolicy.allowed) {
81
87
  plan: Astro.locals.integrationPlan,
82
88
  consentSnapshot: Astro.locals.consentSnapshot,
83
89
  canonicalUrl: new URL("/", canonicalBase + "/").toString(),
84
- complianceTop: rawPolicy.regulated ? renderRawComplianceTop(notices) : undefined,
85
- complianceBottom: rawPolicy.regulated ? renderRawComplianceBottom(notices) : undefined,
90
+ complianceTop: complianceChrome.top || undefined,
91
+ complianceBottom: complianceChrome.bottom || undefined,
86
92
  });
87
93
  }
88
94
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "3.12.1",
3
+ "version": "3.12.2",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
6
6
  "packageManager": "pnpm@11.9.0",