@nextcommerce/campaigns-os 1.60.0 → 1.62.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 (161) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +244 -0
  3. package/README.md +86 -438
  4. package/agents/claude/CLAUDE.md +20 -0
  5. package/agents/codex/AGENTS.md +19 -0
  6. package/agents/copilot/copilot-instructions.md +19 -0
  7. package/agents/cursor/campaigns-os.mdc +21 -0
  8. package/campaign-spec/README.md +7 -0
  9. package/campaign-spec/dist/analytics-vendors.d.ts +40 -0
  10. package/campaign-spec/dist/analytics-vendors.js +44 -0
  11. package/campaign-spec/dist/checkout-flow.d.ts +18 -0
  12. package/campaign-spec/dist/checkout-flow.js +28 -0
  13. package/campaign-spec/dist/index.d.ts +4 -2
  14. package/campaign-spec/dist/index.js +5 -1
  15. package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +8 -0
  16. package/campaign-spec/dist/rules/analytics-contract-shape.js +39 -0
  17. package/campaign-spec/dist/rules/campaign-currencies.d.ts +29 -0
  18. package/campaign-spec/dist/rules/campaign-currencies.js +130 -0
  19. package/campaign-spec/dist/rules/checkout-path-has-packages.d.ts +31 -0
  20. package/campaign-spec/dist/rules/checkout-path-has-packages.js +79 -0
  21. package/campaign-spec/dist/rules/index.d.ts +2 -0
  22. package/campaign-spec/dist/rules/index.js +6 -0
  23. package/campaign-spec/dist/types.d.ts +43 -9
  24. package/compatibility.json +1 -1
  25. package/contracts/brand-theme-target-tokens.next-core.v0.json +95 -25
  26. package/contracts/campaign-cart-sdk-support-policy.v0.json +2 -2
  27. package/contracts/commerce-surface-catalog.json +68 -61
  28. package/contracts/effects.v1.json +549 -165
  29. package/contracts/fixtures/runtime-recipe/accept/current.json +3 -0
  30. package/contracts/fixtures/runtime-recipe/accept/minimal.json +3 -0
  31. package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
  32. package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +3 -0
  33. package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +3 -0
  34. package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +3 -0
  35. package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +3 -0
  36. package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +3 -0
  37. package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +3 -0
  38. package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +3 -0
  39. package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +3 -0
  40. package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +3 -0
  41. package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +3 -0
  42. package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +3 -0
  43. package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +3 -0
  44. package/contracts/release-ledger.json +1767 -0
  45. package/contracts/runtime-recipe.campaigns-os-node-v1.json +3 -0
  46. package/contracts/supported-surface.json +10 -10
  47. package/contracts/template-brand-contract.shared-commerce.v0.json +23 -1
  48. package/docs/access-model.md +20 -0
  49. package/docs/brand-theme-bridge.md +62 -3
  50. package/docs/build-packet.md +456 -36
  51. package/docs/campaign-build-brief.md +16 -0
  52. package/docs/campaign-standardization-report.md +4 -4
  53. package/docs/campaigns-os-build-flow.md +112 -2
  54. package/docs/campaignspec-authoring-examples.md +141 -0
  55. package/docs/design-source-package.md +64 -8
  56. package/docs/effects.md +17 -9
  57. package/docs/entry-points.md +188 -0
  58. package/docs/local-setup.md +1 -1
  59. package/docs/orientation-contract-reference.md +1 -1
  60. package/docs/polish-evidence.md +42 -2
  61. package/docs/progress-snapshots.md +7 -4
  62. package/docs/qa-and-test-orders.md +197 -3
  63. package/docs/quickstart.md +438 -0
  64. package/docs/readback.md +154 -6
  65. package/docs/runtime-readiness.md +6 -3
  66. package/docs/sdk-storage-compatibility.md +4 -2
  67. package/docs/skills-revision.md +10 -10
  68. package/docs/source-adapters.md +213 -0
  69. package/package.json +10 -3
  70. package/schemas/campaign-design-source-package.v0.schema.json +8 -0
  71. package/schemas/campaign-runtime-build-context.v0.schema.json +9 -0
  72. package/schemas/campaign-runtime-build-packet.v0.schema.json +9 -0
  73. package/schemas/campaign-spec.v4.schema.json +19 -3
  74. package/schemas/campaign-spec.v5.schema.json +19 -3
  75. package/schemas/campaigns-os-readback.v2.schema.json +238 -1
  76. package/schemas/source-html-manifest.v0.schema.json +3 -0
  77. package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
  78. package/skills/campaign-readback-classification/SKILL.md +3 -3
  79. package/skills/campaign-run-evidence/SKILL.md +3 -3
  80. package/skills/contribution-intake/SKILL.md +3 -3
  81. package/skills/next-campaigns-build/SKILL.md +5 -4
  82. package/skills/next-campaigns-os/SKILL.md +7 -4
  83. package/skills/next-campaigns-os-setup/SKILL.md +5 -3
  84. package/skills/next-campaigns-polish/SKILL.md +8 -10
  85. package/skills/next-campaigns-qa/SKILL.md +7 -4
  86. package/skills/next-campaigns-sdk-bump/SKILL.md +151 -25
  87. package/skills.json +11 -11
  88. package/src/brand-theme.mjs +415 -9
  89. package/src/brief-summary.mjs +6 -2
  90. package/src/browser-launch.mjs +2 -1
  91. package/src/build-brief.mjs +13 -3
  92. package/src/built-site-scope.mjs +2 -2
  93. package/src/campaign-workspace.mjs +288 -15
  94. package/src/change-scope.mjs +284 -0
  95. package/src/cli.mjs +358 -97
  96. package/src/commercial-journey.mjs +16 -3
  97. package/src/commercial-parity.mjs +15 -7
  98. package/src/content-residue.mjs +4 -2
  99. package/src/design-file-reference.mjs +93 -0
  100. package/src/design-source-package.mjs +91 -1
  101. package/src/design-source-publication.mjs +9 -6
  102. package/src/directory-lock.mjs +73 -19
  103. package/src/doctor/checks.mjs +87 -81
  104. package/src/doctor/ci.mjs +177 -0
  105. package/src/doctor/inspect.mjs +41 -6
  106. package/src/doctor/next-step.mjs +7 -0
  107. package/src/doctor/source-provenance.mjs +74 -0
  108. package/src/doctor-sidecar.mjs +3 -2
  109. package/src/evidence-debt.mjs +159 -0
  110. package/src/finding-cause.mjs +12 -11
  111. package/src/input-refresh.mjs +339 -32
  112. package/src/invocation.mjs +7 -1
  113. package/src/live-campaign-refs.mjs +7 -2
  114. package/src/local-preview-policy.mjs +35 -2
  115. package/src/page-kit-analytics-vendors.mjs +132 -0
  116. package/src/parity/compare.mjs +316 -0
  117. package/src/parity/design-access.mjs +54 -0
  118. package/src/parity/element-parity-record.mjs +569 -0
  119. package/src/parity/probe.mjs +205 -0
  120. package/src/parity/snapshot.mjs +110 -0
  121. package/src/png-header.mjs +87 -0
  122. package/src/polish-browser.mjs +189 -10
  123. package/src/polish-captures-record.mjs +52 -0
  124. package/src/polish-deadline.mjs +14 -0
  125. package/src/polish-gate.mjs +25 -6
  126. package/src/polish-node.mjs +81 -7
  127. package/src/polish-readability.mjs +221 -9
  128. package/src/polish-visual-fixtures.mjs +74 -0
  129. package/src/polish-visual.mjs +613 -0
  130. package/src/progress-node.mjs +3 -2
  131. package/src/qa-analytics-correctness.mjs +14 -2
  132. package/src/qa-analytics-parity.mjs +111 -6
  133. package/src/qa-browser.mjs +40 -4
  134. package/src/qa-commercial-parity.mjs +50 -3
  135. package/src/qa-node.mjs +235 -22
  136. package/src/qa-publish.mjs +9 -8
  137. package/src/qa-sidecar.mjs +16 -4
  138. package/src/qa-vendor-beacons.mjs +232 -0
  139. package/src/qa-vendor-endpoints.mjs +306 -0
  140. package/src/qc-accept.mjs +176 -6
  141. package/src/qc-check-registry.mjs +42 -2
  142. package/src/qc-results.mjs +477 -42
  143. package/src/readback.mjs +113 -21
  144. package/src/route-identity.mjs +10 -0
  145. package/src/run-record.mjs +18 -10
  146. package/src/runtime-state-ignore.mjs +6 -2
  147. package/src/sdk-attribute-index.mjs +11 -11
  148. package/src/sdk-markup.mjs +77 -2
  149. package/src/sdk-storage-compatibility.mjs +115 -6
  150. package/src/source-html-intake.mjs +15 -1
  151. package/src/source-html-manifest.mjs +7 -0
  152. package/src/source-html-server.mjs +129 -0
  153. package/src/spec-derive-campaign.mjs +216 -0
  154. package/src/spec-derive.mjs +473 -53
  155. package/src/spec-identity.mjs +14 -0
  156. package/src/stage-ledger.mjs +40 -1
  157. package/src/stage-record.mjs +100 -17
  158. package/src/target-lock.mjs +11 -1
  159. package/src/theme-gate.mjs +2 -2
  160. package/src/visual-diff.mjs +296 -0
  161. package/src/visual-reference.mjs +126 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * CheckoutPathHasPackages — every path from an entry page to a Checkout passes
3
+ * at least one page that sells a main-cart package (campaigns-os#711, #206).
4
+ *
5
+ * A Checkout the shopper reaches with nothing in the cart cannot take an
6
+ * order, and until this rule nothing said so before a test order failed. The
7
+ * selling page may be any page on the path up to and including the Checkout:
8
+ * a select page's bundle picker, a landing page's package cards, a checkout
9
+ * step, or the Checkout's own selector. So the rule counts per path, never per
10
+ * page: a Checkout with no packages of its own passes when every path into it
11
+ * sells first.
12
+ *
13
+ * "Main-cart package" is the ratified #206 contract (checkout-flow.ts
14
+ * `isMainCartPackage`, docs/template-family-contracts.md "Main-cart entry"):
15
+ * an entry with neither `is_upsell: true` nor `is_order_bump: true`. Bump rows
16
+ * alone sell nothing, `default_selected` never changes the role, and entries
17
+ * on upsell, downsell or thank-you pages never count.
18
+ *
19
+ * Paths are walked the way OneCheckoutPerPath walks them: both edges a page
20
+ * can traverse (routing.ts), starting at the funnel's entry pages
21
+ * (checkout-flow.ts `entryPages`). No page order or name is consulted. The
22
+ * search runs, per entry page, over (page, sold yet) states and stops at the
23
+ * first Checkout, so each empty Checkout is reported once per entry however
24
+ * many of that entry's paths reach it. A second Checkout on one path is
25
+ * OneCheckoutPerPath's to report.
26
+ *
27
+ * With build_scope.mode === 'partial' the finding is a warning: a partial
28
+ * build may ship a Checkout that a page outside the build fills.
29
+ */
30
+ import { outgoingEdgeIds } from "../routing.js";
31
+ import { entryPages, isPaymentPage, resolveRouteTargetPage, sellsMainCartPackage } from "../checkout-flow.js";
32
+ export const CheckoutPathHasPackages = {
33
+ id: 'CheckoutPathHasPackages',
34
+ severity: 'error',
35
+ tags: ['structure', 'spec-only'],
36
+ check(spec) {
37
+ const violations = [];
38
+ const partial = spec.build_scope?.mode === 'partial';
39
+ spec.funnels.forEach((funnel, funnelIdx) => {
40
+ const pages = (funnel.pages ?? []).filter((page) => page && page.enabled !== false);
41
+ const indexOf = new Map();
42
+ (funnel.pages ?? []).forEach((page, idx) => indexOf.set(page, idx));
43
+ for (const entry of entryPages(funnel)) {
44
+ const seen = new Set();
45
+ const reported = new Set();
46
+ const stack = [[entry, false]];
47
+ while (stack.length) {
48
+ const [page, before] = stack.pop();
49
+ const sold = before || sellsMainCartPackage(page);
50
+ const key = `${indexOf.get(page)}:${sold}`;
51
+ if (seen.has(key))
52
+ continue;
53
+ seen.add(key);
54
+ if (isPaymentPage(page)) {
55
+ if (!sold && !reported.has(page)) {
56
+ reported.add(page);
57
+ violations.push({
58
+ ruleId: 'CheckoutPathHasPackages',
59
+ severity: partial ? 'warning' : 'error',
60
+ message: `A path from entry page "${entry.id}" reaches Checkout "${page.id}" without passing a page that sells a main-cart package, so the cart is empty and no order can be placed. ` +
61
+ 'Add a package entry not marked is_upsell or is_order_bump to the Checkout or a page before it on this path.' +
62
+ (partial ? ' Valid for partial builds when a page outside this build fills the cart.' : ''),
63
+ path: `/funnels/${funnelIdx}/pages/${indexOf.get(page)}`,
64
+ data: { pageId: page.id, entryPageId: entry.id, isPartialScope: partial },
65
+ });
66
+ }
67
+ continue;
68
+ }
69
+ for (const target of outgoingEdgeIds(page)) {
70
+ const next = resolveRouteTargetPage(pages, target);
71
+ if (next)
72
+ stack.push([next, sold]);
73
+ }
74
+ }
75
+ }
76
+ });
77
+ return violations;
78
+ },
79
+ };
@@ -35,11 +35,13 @@ export { CheckoutForwardTarget } from './checkout-forward-target.ts';
35
35
  export { PrePaymentForwardTarget } from './pre-payment-forward-target.ts';
36
36
  export { CheckoutStepReachesCheckout } from './checkout-step-reaches-checkout.ts';
37
37
  export { OneCheckoutPerPath } from './one-checkout-per-path.ts';
38
+ export { CheckoutPathHasPackages } from './checkout-path-has-packages.ts';
38
39
  export { UpsellWithoutCheckout } from './upsell-without-checkout.ts';
39
40
  export { DownsellWithoutUpsell } from './downsell-without-upsell.ts';
40
41
  export { PackagePricingSanity } from './package-pricing-sanity.ts';
41
42
  export { ShippingMethodsPresent } from './shipping-methods-present.ts';
42
43
  export { ShippingCountriesShape } from './shipping-countries-shape.ts';
44
+ export { CampaignCurrencies } from './campaign-currencies.ts';
43
45
  export { OfferRefIntegrity } from './offer-ref-integrity.ts';
44
46
  export { ExitIntentValidation } from './exit-intent-validation.ts';
45
47
  export { PromoCodeInputValidation } from './promo-code-input-validation.ts';
@@ -34,11 +34,13 @@ import { CheckoutForwardTarget } from "./checkout-forward-target.js";
34
34
  import { PrePaymentForwardTarget } from "./pre-payment-forward-target.js";
35
35
  import { CheckoutStepReachesCheckout } from "./checkout-step-reaches-checkout.js";
36
36
  import { OneCheckoutPerPath } from "./one-checkout-per-path.js";
37
+ import { CheckoutPathHasPackages } from "./checkout-path-has-packages.js";
37
38
  import { UpsellWithoutCheckout } from "./upsell-without-checkout.js";
38
39
  import { DownsellWithoutUpsell } from "./downsell-without-upsell.js";
39
40
  import { CampaignMetadata } from "./campaign-metadata.js";
40
41
  import { ShippingMethodsPresent } from "./shipping-methods-present.js";
41
42
  import { ShippingCountriesShape } from "./shipping-countries-shape.js";
43
+ import { CampaignCurrencies } from "./campaign-currencies.js";
42
44
  import { PackagePricingSanity } from "./package-pricing-sanity.js";
43
45
  import { OfferRefIntegrity } from "./offer-ref-integrity.js";
44
46
  import { ExitIntentValidation } from "./exit-intent-validation.js";
@@ -69,11 +71,13 @@ export { CheckoutForwardTarget } from "./checkout-forward-target.js";
69
71
  export { PrePaymentForwardTarget } from "./pre-payment-forward-target.js";
70
72
  export { CheckoutStepReachesCheckout } from "./checkout-step-reaches-checkout.js";
71
73
  export { OneCheckoutPerPath } from "./one-checkout-per-path.js";
74
+ export { CheckoutPathHasPackages } from "./checkout-path-has-packages.js";
72
75
  export { UpsellWithoutCheckout } from "./upsell-without-checkout.js";
73
76
  export { DownsellWithoutUpsell } from "./downsell-without-upsell.js";
74
77
  export { PackagePricingSanity } from "./package-pricing-sanity.js";
75
78
  export { ShippingMethodsPresent } from "./shipping-methods-present.js";
76
79
  export { ShippingCountriesShape } from "./shipping-countries-shape.js";
80
+ export { CampaignCurrencies } from "./campaign-currencies.js";
77
81
  export { OfferRefIntegrity } from "./offer-ref-integrity.js";
78
82
  export { ExitIntentValidation } from "./exit-intent-validation.js";
79
83
  export { PromoCodeInputValidation } from "./promo-code-input-validation.js";
@@ -114,11 +118,13 @@ export const allRules = [
114
118
  PrePaymentForwardTarget,
115
119
  CheckoutStepReachesCheckout,
116
120
  OneCheckoutPerPath,
121
+ CheckoutPathHasPackages,
117
122
  UpsellWithoutCheckout,
118
123
  DownsellWithoutUpsell,
119
124
  PackagePricingSanity,
120
125
  ShippingMethodsPresent,
121
126
  ShippingCountriesShape,
127
+ CampaignCurrencies,
122
128
  OfferRefIntegrity,
123
129
  ExitIntentValidation,
124
130
  PromoCodeInputValidation,
@@ -519,12 +519,17 @@ export interface Campaign {
519
519
  *
520
520
  * Modeled on real production-funnel usage, NOT the idealized "SDK fires the
521
521
  * canonical dl_* set" view. The field reality the block must express:
522
- * - Events fire from three sources: SDK auto, SDK-blocked-then-manual
523
- * (`blockedEvents` suppresses the SDK event, a side script re-fires it via
524
- * raw fbq/gtag to control timing), and fully out-of-band pixels loaded via
525
- * GTM (Everflow / TriplePixel / Northbeam / RudderStack) that never touch
526
- * the dataLayer. So outbound pixel fires — not dataLayer events — are the
527
- * QA source of truth.
522
+ * - Events reach vendors four ways: SDK providers (`providers`: GTM, Meta,
523
+ * RudderStack, a custom endpoint, configured through config.js);
524
+ * SDK-blocked-then-manual (`blockedEvents` suppresses the SDK event, a
525
+ * side script re-fires it via raw fbq/gtag to control timing); the starter
526
+ * templates' vendor adapters (`vendors`: GA4, Axon, Taboola, Triple Whale,
527
+ * TikTok, Northbeam, Snapchat, Pinterest, switched on by a campaigns.json
528
+ * key, riding the SDK's dl_* stream through the template forwarder); and
529
+ * out-of-band pixels a merchant loads some other way, typically through
530
+ * their own GTM container (`out_of_band_pixels`, e.g. Everflow), which
531
+ * never touch the dataLayer. So outbound pixel fires, not dataLayer
532
+ * events, are the QA source of truth.
528
533
  * - A custom provider may carry an endpoint + transform (cookie injection).
529
534
  * - Querystring params split into two classes: CONTENT (param.* → visibility
530
535
  * via data-next-hide) and TRACKING (utm params, gclid, fbclid, subN, click-ids that
@@ -549,8 +554,33 @@ export interface AnalyticsProvider {
549
554
  blockedEvents?: string[];
550
555
  [key: string]: unknown;
551
556
  }
552
- /** A pixel/tag fired OUTSIDE the SDK (via GTM or a raw snippet) — QA must still
553
- * expect it on a live run even though it never appears in the dataLayer. */
557
+ /**
558
+ * One starter-template vendor adapter, keyed in `analytics.vendors` by the
559
+ * vendor key (ga4 | axon | taboola | triplewhale | tiktok | northbeam | snap |
560
+ * pinterest; registry: analytics-vendors.ts). This is the shape the Map
561
+ * Builder emits; its spec compiler writes the matching flat keys into the
562
+ * campaign's `_data/campaigns.json` entry, and `spec derive` mirrors the id
563
+ * back from there.
564
+ */
565
+ export interface AnalyticsVendor {
566
+ /** false turns the vendor off (no campaigns.json key is compiled). */
567
+ enabled?: boolean;
568
+ /** The vendor id: the value of the vendor's campaigns.json id key (`ga4_id`, `triplewhale_name`, …). */
569
+ id?: string;
570
+ /** Triple Whale only: `triplewhale_platform` (e.g. SHOPIFY). The template default, custom-msp, is omitted. */
571
+ platform?: string;
572
+ /** Turns on the vendor's identity / advanced-matching flag (raw email and phone; the `*_enabled` key). */
573
+ identityEnabled?: boolean;
574
+ /** dl_* events the adapter forwards (`<vendor>_allowed_events`); absent means all. */
575
+ allowedEvents?: string[];
576
+ /** dl_* events the adapter drops (`<vendor>_blocked_events`), e.g. purchases on a Shop Sync store. */
577
+ blockedEvents?: string[];
578
+ [key: string]: unknown;
579
+ }
580
+ /** A pixel/tag fired OUTSIDE the SDK and the template adapters (via the
581
+ * merchant's GTM container or a raw snippet). QA must still expect it on a
582
+ * live run even though it never appears in the dataLayer. A vendor the starter
583
+ * templates ship an adapter for belongs in `vendors`, not here. */
554
584
  export interface OutOfBandPixel {
555
585
  vendor: string;
556
586
  loaded_via?: 'gtm' | 'script' | (string & {});
@@ -595,9 +625,13 @@ export interface UtmTransfer {
595
625
  excludedDomains?: string[];
596
626
  }
597
627
  export interface AnalyticsContract {
628
+ /** false turns the whole contract off, as mode "disabled" does. */
629
+ enabled?: boolean;
598
630
  mode?: AnalyticsMode;
599
- /** Keyed by provider kind: gtm | facebook | rudderstack | custom | … */
631
+ /** SDK providers, keyed by provider kind: gtm | facebook | rudderstack | custom | … */
600
632
  providers?: Record<string, AnalyticsProvider>;
633
+ /** Starter-template vendor adapters, keyed by vendor key (see AnalyticsVendor). */
634
+ vendors?: Record<string, AnalyticsVendor>;
601
635
  out_of_band_pixels?: OutOfBandPixel[];
602
636
  manual_events?: ManualEvent[];
603
637
  utmTransfer?: UtmTransfer;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@nextcommerce/campaigns-os",
3
- "version": "1.60.0",
3
+ "version": "1.62.0",
4
4
  "status": "developer-preview",
5
5
  "contracts": {
6
6
  "campaign_spec": "4.2-5.0",
@@ -24,42 +24,112 @@
24
24
  "--brand--color--rating-star",
25
25
  "--component--color--outline",
26
26
  "--system-colors--success",
27
- "--system-colors--destructive"
27
+ "--system-colors--destructive",
28
+ "--_text---font-family--heading",
29
+ "--_text---font-family--primary",
30
+ "--radius-size--buttons"
28
31
  ],
29
32
  "source_mappings": {
30
- "--brand-primary": ["--brand--color--primary"],
31
- "--brand-accent": ["--brand--color--accent"],
32
- "--brand-secondary": ["--brand--color--secondary"],
33
- "--surface-bg": ["--brand--color--background"],
34
- "--surface-card": ["--brand--color--surface"],
35
- "--text-primary": ["--brand--color--text-primary", "--brand--color--foreground"],
36
- "--text-secondary": ["--brand--color--text-secondary"],
37
- "--border-default": ["--brand--color--border", "--component--color--outline"],
38
- "--rating-star": ["--brand--color--rating-star"],
39
- "--state-success": ["--system-colors--success"],
40
- "--ui-success": ["--system-colors--success"],
41
- "--state-error": ["--system-colors--destructive"],
42
- "--ui-danger": ["--system-colors--destructive"],
43
- "--brand-cta": ["--brand--color--cta-primary"],
44
- "--cta-primary": ["--brand--color--cta-primary"],
45
- "--button-primary-bg": ["--brand--color--cta-primary"]
33
+ "--brand-primary": [
34
+ "--brand--color--primary"
35
+ ],
36
+ "--brand-accent": [
37
+ "--brand--color--accent"
38
+ ],
39
+ "--brand-secondary": [
40
+ "--brand--color--secondary"
41
+ ],
42
+ "--surface-bg": [
43
+ "--brand--color--background"
44
+ ],
45
+ "--surface-card": [
46
+ "--brand--color--surface"
47
+ ],
48
+ "--text-primary": [
49
+ "--brand--color--text-primary",
50
+ "--brand--color--foreground"
51
+ ],
52
+ "--text-secondary": [
53
+ "--brand--color--text-secondary"
54
+ ],
55
+ "--border-default": [
56
+ "--brand--color--border",
57
+ "--component--color--outline"
58
+ ],
59
+ "--rating-star": [
60
+ "--brand--color--rating-star"
61
+ ],
62
+ "--state-success": [
63
+ "--system-colors--success"
64
+ ],
65
+ "--ui-success": [
66
+ "--system-colors--success"
67
+ ],
68
+ "--state-error": [
69
+ "--system-colors--destructive"
70
+ ],
71
+ "--ui-danger": [
72
+ "--system-colors--destructive"
73
+ ],
74
+ "--brand-cta": [
75
+ "--brand--color--cta-primary"
76
+ ],
77
+ "--cta-primary": [
78
+ "--brand--color--cta-primary"
79
+ ],
80
+ "--button-primary-bg": [
81
+ "--brand--color--cta-primary"
82
+ ]
46
83
  },
47
84
  "derived_mappings": {
48
85
  "--brand-primary": {
49
- "--brand--color--primary-dark": { "method": "mix-black", "amount": 0.18 },
50
- "--brand--color--primary-light": { "method": "mix-white", "amount": 0.2 },
51
- "--brand--color--primary-lighter": { "method": "mix-white", "amount": 0.86 }
86
+ "--brand--color--primary-dark": {
87
+ "method": "mix-black",
88
+ "amount": 0.18
89
+ },
90
+ "--brand--color--primary-light": {
91
+ "method": "mix-white",
92
+ "amount": 0.2
93
+ },
94
+ "--brand--color--primary-lighter": {
95
+ "method": "mix-white",
96
+ "amount": 0.86
97
+ }
52
98
  }
53
99
  },
54
100
  "foreground_derivations": {
55
101
  "description": "Foreground/on-color tokens are derived from the luminance of the background they sit on, never copied blindly from a source --text-inverse token (which defaults to white and renders illegible on a light brand, e.g. white-on-yellow CTAs). Each foreground target picks the first paired background that was mapped and emits the readable choice from foreground_choices. next-core .button/.submit-button render their label with --brand--color--text-inverse, so text-inverse pairs with the CTA background first.",
56
- "foreground_choices": { "dark": "#0a0a0a", "light": "#ffffff" },
102
+ "foreground_choices": {
103
+ "dark": "#0a0a0a",
104
+ "light": "#ffffff"
105
+ },
57
106
  "min_contrast_ratio": 4.5,
58
107
  "derivations": {
59
- "--brand--color--cta-foreground": ["--brand--color--cta-primary"],
60
- "--brand--color--text-inverse": ["--brand--color--cta-primary", "--brand--color--primary"],
61
- "--brand--color--primary-foreground": ["--brand--color--primary"],
62
- "--brand--color--accent-foreground": ["--brand--color--accent"]
108
+ "--brand--color--cta-foreground": [
109
+ "--brand--color--cta-primary"
110
+ ],
111
+ "--brand--color--text-inverse": [
112
+ "--brand--color--cta-primary",
113
+ "--brand--color--primary"
114
+ ],
115
+ "--brand--color--primary-foreground": [
116
+ "--brand--color--primary"
117
+ ],
118
+ "--brand--color--accent-foreground": [
119
+ "--brand--color--accent"
120
+ ]
63
121
  }
122
+ },
123
+ "typography": {
124
+ "description": "Typography targets. Font families are mapped only when the design source ships an @font-face for the family; the faces are written to brand-fonts.css beside brand-theme.css. next-core re-declares the font-family tokens on body inside its 991px, 767px and 479px media queries, so those tokens are emitted on `:root, body` to hold at every width.",
125
+ "targets": {
126
+ "heading_family": "--_text---font-family--heading",
127
+ "body_family": "--_text---font-family--primary",
128
+ "button_radius": "--radius-size--buttons"
129
+ },
130
+ "body_scoped_tokens": [
131
+ "--_text---font-family--heading",
132
+ "--_text---font-family--primary"
133
+ ]
64
134
  }
65
135
  }
@@ -2,8 +2,8 @@
2
2
  "schema_version": "campaign-cart-sdk-support-policy/v0",
3
3
  "provenance": {
4
4
  "source": "NextCommerceCo/campaign-cart release tags",
5
- "latest_known_release": "0.4.42",
6
- "captured_at": "2026-10-09"
5
+ "latest_known_release": "0.4.43",
6
+ "captured_at": "2026-10-22"
7
7
  },
8
8
  "source": "contracts/campaign-cart-sdk-support-policy.v0.json",
9
9
  "minimum_supported": "0.4.20",