@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
@@ -105,6 +105,7 @@
105
105
  "_note": "The complete input set, enumerated explicitly rather than globbed. The compiler's configured include globs are NOT the input set: two root modules enter the compilation transitively through imports from the entry module and are emitted, so a fingerprint derived from the globs would cover 36 of the 38 compiled sources and miss one of the larger emitted surfaces. Test fixtures and the package's own tests are not inputs; nothing under them is emitted. A checker resolves the compiler's actual file list and asserts it equals this enumeration, so this list cannot rot silently.",
106
106
  "fingerprint_algorithm": "sha256",
107
107
  "files": [
108
+ "campaign-spec/analytics-vendors.ts",
108
109
  "campaign-spec/analytics-vocabulary.ts",
109
110
  "campaign-spec/checkout-flow.ts",
110
111
  "campaign-spec/index.ts",
@@ -113,9 +114,11 @@
113
114
  "campaign-spec/routing.ts",
114
115
  "campaign-spec/rules/analytics-contract-shape.ts",
115
116
  "campaign-spec/rules/assembly-hints-shape.ts",
117
+ "campaign-spec/rules/campaign-currencies.ts",
116
118
  "campaign-spec/rules/campaign-metadata.ts",
117
119
  "campaign-spec/rules/checkout-forward-target.ts",
118
120
  "campaign-spec/rules/checkout-has-success-url.ts",
121
+ "campaign-spec/rules/checkout-path-has-packages.ts",
119
122
  "campaign-spec/rules/checkout-step-reaches-checkout.ts",
120
123
  "campaign-spec/rules/cycle-detection.ts",
121
124
  "campaign-spec/rules/design-source-shape.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "_note": "The downstream contract manifest. Everything listed here is SUPPORTED SURFACE: consumers (campaigns-agent, campaign-builder, the private ops repo, page-kit campaign repos) may depend on it, and changing it is a deliberate act — hashed entries require a surface_version bump in the same change (check-supported-surface.mjs --base, mirroring the skills.json bump gate), named entries must keep existing at their path, cli_commands must keep resolving in the CLI dispatch, package_exports must stay exported, and every entry must ship in the npm pack (files[] coverage). Anything NOT listed here — src/** internals, scripts/** checkers, examples/**, prompts/**, contracts/** other than this file and the entries named[] below (the orientation contract, the release ledger, and the consumer-facing orientation fixtures) — is implementation: consumers may read it for context but must not build on it, and it can change without notice. Rationale and the compatibility promise: docs/supported-surface.md.",
3
- "surface_version": "1.60.0",
3
+ "surface_version": "1.62.0",
4
4
  "package_exports": [
5
5
  "./commercial-journey",
6
6
  "./commercial-parity",
@@ -52,28 +52,28 @@
52
52
  "sha256": "53269453dfdaac4b286124d445b60b0c46205dfbaf8037ac61b30254a32df53a"
53
53
  },
54
54
  "contracts/runtime-recipe.campaigns-os-node-v1.json": {
55
- "sha256": "ab0537a4756310d3a35b400d3932e46695a820f1b8b398f52f6fd0906098bc1d"
55
+ "sha256": "3a960f347a8b7de7d9f223fb2bc1ae35091c943d480bca931bf8afa5e70fa83f"
56
56
  },
57
57
  "schemas/campaign-build-brief.v1.schema.json": {
58
58
  "sha256": "d524003184131398dc15fdb84385af1be0de0c69d6c5ede7278689a5b6101b81"
59
59
  },
60
60
  "schemas/campaign-design-source-package.v0.schema.json": {
61
- "sha256": "65bcd8b5f41f1e9ecfc36f3b8838cd4ed531bd74c5a318ee5147131f6c9b4a1c"
61
+ "sha256": "ecb8fd534977826541aa7110dad3483b7b9215dd60ed30b6370083848b957f13"
62
62
  },
63
63
  "schemas/campaign-runtime-assembly-report.v0.schema.json": {
64
64
  "sha256": "625a6f2ed6bee726f9cddad750e64a54a7e03744c777f5a70afe4a39afdd9848"
65
65
  },
66
66
  "schemas/campaign-runtime-build-context.v0.schema.json": {
67
- "sha256": "2aa8f652f6bb602cdbadada34b5793da1bb8842223fcae2fd24700aac14f4de6"
67
+ "sha256": "501867cf95eca9b3d9ce8f2255ca8dfbb0c9e073a270794ab5e4203ed79e4658"
68
68
  },
69
69
  "schemas/campaign-runtime-build-packet.v0.schema.json": {
70
- "sha256": "e57d31f190a7ddfbf63afb39bc67762a09dff25707c841e84a4e1c6ee9afd9ed"
70
+ "sha256": "1e309c5dad8c5eeef26ef6fd3b6b90b72440c5104319a161002b17f1359fbd76"
71
71
  },
72
72
  "schemas/campaign-spec.v4.schema.json": {
73
- "sha256": "af56c3de638d3c4e1fd5df8c2d733754d9ad16caa23b5bec92ce3a6f71bd0271"
73
+ "sha256": "587df2fdd5707b515a9b8685d30070bcf4bdac9e15de7a94df82224ef4fd91b6"
74
74
  },
75
75
  "schemas/campaign-spec.v5.schema.json": {
76
- "sha256": "2923382e94ac51f6a19a3f9dfd0993d8c30fab33cd9be3d6901f314e02f5d960"
76
+ "sha256": "c606453dc57d210a5a5ed8a099bf868546ae8f5996cf9ba0f8e71a50b2b03fa4"
77
77
  },
78
78
  "schemas/campaigns-os-legacy-migration-inventory.v0.schema.json": {
79
79
  "sha256": "d6edc4572900d15fdaf6b8c59916b0d885303c1d398bdadeb7c58285522c9049"
@@ -112,7 +112,7 @@
112
112
  "sha256": "41ff370de4395149781d7e8c4ad6d7d4e2f896eb2030944399d36f1237726a20"
113
113
  },
114
114
  "schemas/source-html-manifest.v0.schema.json": {
115
- "sha256": "841c1f884260ce3eb3ddb518960a127bc915992a9539c818e5986ce156acff1f"
115
+ "sha256": "58bc98b3fd07c87c6ba92d29b26941ca97200ea433d73f7418966f679841df49"
116
116
  },
117
117
  "schemas/campaigns-os-progress-snapshot.v0.schema.json": {
118
118
  "sha256": "ef31b4f20e39dfc1614dd4b45e4cf1692a51760b008cb51d133659307bd2a954"
@@ -121,10 +121,10 @@
121
121
  "sha256": "2e440be6513fe06b351c3a72c35424a04f807792621cb01e89803441fd925e06"
122
122
  },
123
123
  "schemas/campaigns-os-readback.v2.schema.json": {
124
- "sha256": "dfc9abed38d456969e47a21f606d308a03bdf036f3466f7d6e47a602747dacf4"
124
+ "sha256": "b397b417b0456cafc03346a2e882a34c19fa0500ea92594afca2f9fa1d7f2c3b"
125
125
  },
126
126
  "contracts/effects.v1.json": {
127
- "sha256": "9c1d69e44eab7403ca3ff80f850214d89b0a02fdc51544beef37dbd0a653eeaa"
127
+ "sha256": "7aca3aa2208ccb700a74bc9554725a23b1f6c413e7ea7dc9ec442e884a8d72c3"
128
128
  },
129
129
  "schemas/campaigns-os-effects.v1.schema.json": {
130
130
  "sha256": "3eadd22169ab98bc7c2f682af2751267605170581182158d96be035b0cfe44dc"
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "asset_pin": {
57
57
  "repo": "NextCommerceCo/campaign-cart-starter-templates",
58
- "sha": "3987b18a356cb961b06c94b2035622bafe938316",
58
+ "sha": "5711e22345756c655f41c0ae71444478e4a8999e",
59
59
  "path": "src/<family>/assets/<asset>",
60
60
  "note": "asset_sha256 is the sha256 of each shipped starter asset at this commit (the commerce-surface-catalog _synced_from_sha pin; the bytes are identical across every family). A served asset whose bytes hash to the shipped value is the untouched starter asset and counts as residue even though its markup names no method; different bytes mean it was edited in place. check-template-doctrine verifies these hashes against the pinned checkout."
61
61
  },
@@ -186,5 +186,27 @@
186
186
  "Apply My Discount Coupon"
187
187
  ],
188
188
  "rule": "If an exit-intent include or default widget ships without CampaignSpec checkout exit_intent or promo_code_input behavior, strip it or record a hard warning. If present, it must apply the mapped offer through the SDK/API coupon path and use cart.hasCoupon(\"CODE\") style conditionals for applied-state presentation."
189
+ },
190
+ "layout_conventions": {
191
+ "rule": "Design-independent layout conventions. polish capture measures them on every built page, in the same pass as readability, and reports each as a layout.conventions warning that checkpoint accept can accept. They never block Polish and never read or judge the merchant's copy. Polish reads this block from the shared contract; a family contract does not override it.",
192
+ "floating_cta": {
193
+ "key": "floating_cta",
194
+ "reason_code": "floating_cta_extra_content",
195
+ "viewports": ["mobile"],
196
+ "positions": ["fixed", "sticky"],
197
+ "control_selector": "a[href], button, input[type=\"submit\"], input[type=\"button\"], [role=\"button\"]",
198
+ "exclude_selector": "header, nav, dialog, [role=\"banner\"], [role=\"navigation\"], [role=\"dialog\"], [aria-modal=\"true\"], .announcement",
199
+ "form_field_selector": "input:not([type=\"hidden\"]):not([type=\"submit\"]):not([type=\"button\"]), select, textarea",
200
+ "max_viewport_height_share": 0.5,
201
+ "rule": "A floating CTA container holds the control alone: one link or button and no other visible text. A floating CTA container is the nearest box around a visible control (control_selector) whose computed position is one of positions. Boxes that match or sit inside exclude_selector, boxes that hold a visible form field (form_field_selector), and boxes taller than max_viewport_height_share of the viewport are not floating CTA containers. Visible text is text the browser renders, including CSS generated text; text inside the control does not count."
202
+ },
203
+ "cta_label": {
204
+ "key": "cta_label_size",
205
+ "reason_code": "cta_label_too_small",
206
+ "viewports": ["mobile"],
207
+ "roles": ["add_to_cart", "upsell_accept", "submit_control"],
208
+ "min_font_size_px": 16,
209
+ "rule": "The primary CTA label is at least min_font_size_px CSS px at the mobile viewport. The label is every rendered, enabled text element the readability record lists under one of roles (purchase and accept controls, and submit buttons)."
210
+ }
189
211
  }
190
212
  }
@@ -0,0 +1,20 @@
1
+ # Access Model
2
+
3
+ Campaign developers should not need access to the private Campaigns OS runtime development repo.
4
+
5
+ This repo is the developer-facing surface:
6
+
7
+ - docs
8
+ - schemas
9
+ - examples
10
+ - starter-template contract snapshots
11
+ - agent skills
12
+ - Build Packet doctor
13
+ - checkpoint wrappers
14
+ - issue templates
15
+
16
+ Private platform/runtime development remains outside this repo. Do not copy private repo paths, client-specific examples, secrets, dashboard internals, or unpublished platform mutation procedures here.
17
+
18
+ ## Distribution
19
+
20
+ This repo should stay suitable for public or partner access. Private runtime details, private repo paths, secrets, and client-specific implementation notes belong outside this surface.
@@ -37,12 +37,66 @@ the accent fallback and the warning asks the operator to confirm it.
37
37
  ```text
38
38
  .campaign-runtime/theme/theme-report.json
39
39
  .campaign-runtime/theme/brand-theme.css
40
+ .campaign-runtime/theme/brand-fonts.css (only when the source ships brand faces)
40
41
  ```
41
42
 
42
43
  The generated CSS is v0 root-variable-only. It can override next-core custom
43
44
  properties such as `--brand--color--primary` and
44
45
  `--brand--color--cta-primary`, but it must not emit selectors, `data-next`
45
46
  selectors, payment/package/cart selectors, JavaScript, or remote URL fetches.
47
+ The one exception is a `:root, body` block for the font-family tokens (see
48
+ Typography below).
49
+
50
+ ### Typography
51
+
52
+ Commerce pages keep the design's type, not the template's system stack.
53
+ Inspection reads every CSS file the source pages link:
54
+
55
+ - the `@font-face` rules the source ships;
56
+ - which family its headings use (`h1`–`h3`, then `heading`/`title` classes);
57
+ - which family its body copy uses (`html`, `body`, `p`, or the class a
58
+ stylesheet scopes itself under, such as the importer's `.cc-import`);
59
+ - which family its buttons use, and the button `border-radius`.
60
+
61
+ `var()` references resolve through custom properties on `:root`, `html`, `body`
62
+ and that scope class, where Shopify themes keep `--font-body-family` and
63
+ `--buttons-radius`. A family is mapped only when the source ships a face for
64
+ it: a family the page can't load would only change the fallback, so it is
65
+ left out, reported as a `theme.typography.face_unavailable` warning, and
66
+ listed in the theme report's `typography.unavailable`.
67
+
68
+ | Source | next-core token |
69
+ | --- | --- |
70
+ | heading family, then the generic the source gave it | `--_text---font-family--heading` |
71
+ | body family (else the button family), then its generic | `--_text---font-family--primary` |
72
+ | button radius (`0`, or one to four lengths or percentages) | `--radius-size--buttons` |
73
+
74
+ A button radius in another form (`10px / 20px`, an unresolved `var()`) is not
75
+ mapped and is listed in `typography.radius_rejected`.
76
+
77
+ The targets are in the `typography` block of
78
+ `contracts/brand-theme-target-tokens.next-core.v0.json`. next-core re-declares
79
+ both font-family tokens on `body` inside its 991px, 767px and 479px media
80
+ queries, so a `:root` value alone holds only on desktop. The generator emits
81
+ them in a `:root, body` block. `brand-theme.css` loads after next-core, so its
82
+ `body` declaration wins at every width.
83
+
84
+ `brand-fonts.css` holds only `@font-face` rules for the mapped families. It
85
+ always ships `font-display: swap`, whatever the source declared. A face the
86
+ source declares once per format keeps every file, in source order. Its URLs are relative to `assets/css/`, pointing at the
87
+ source's own font files, which build copies into the campaign's `assets/`
88
+ tree with the rest of the source assets. A face whose file is remote, a
89
+ `data:` URL, root-relative, outside the source root or missing is left out with
90
+ `theme.typography.face_src_dropped`; containment is checked on real paths, so
91
+ a symlink out of the source root is dropped. Family names are stripped of
92
+ characters that could not appear in a quoted family. Button radius is mapped only alongside a
93
+ mapped family, so a source with no brand faces generates exactly what it did
94
+ before.
95
+
96
+ Typography covers family and button radius only, because those are the only
97
+ type and shape tokens next-core exposes. Heading weight, letter-spacing and
98
+ text-transform stay at next-core's values: next-core renders headings at 700,
99
+ so a design that sets headings at 600 shows its bold face on commerce pages.
46
100
 
47
101
  ### Foreground tokens are derived from background luminance
48
102
 
@@ -205,8 +259,10 @@ downsell, or receipt pages.
205
259
 
206
260
  If a fresh `brand-theme.css` exists:
207
261
 
208
- 1. Copy it into the campaign asset tree.
209
- 2. Add it to commerce page frontmatter styles after `next-core.css`.
262
+ 1. Copy it into the campaign asset tree (`assets/css/`), with
263
+ `brand-fonts.css` beside it when theme generate wrote one.
264
+ 2. Add it to commerce page frontmatter styles after `next-core.css`. List
265
+ `brand-fonts.css` on the same pages; its position does not matter.
210
266
  3. Preserve SDK-owned runtime surfaces: `data-next-*`, package selectors,
211
267
  payment fields, totals, submit controls, receipt templates, route meta tags,
212
268
  and SDK JavaScript.
@@ -215,7 +271,10 @@ If a fresh `brand-theme.css` exists:
215
271
  page's stylesheet links and records `report.theme.status`, `css_path`,
216
272
  `commerce_pages`, `load_order` and evidence. It refuses, writing nothing,
217
273
  when a page that loads `next-core.css` does not load the brand layer after
218
- it, or when no built commerce page loads `next-core.css`.
274
+ it, when theme generate wrote `brand-fonts.css` (at the Build Context's
275
+ `theme.generated.fonts_css_path`) and a styled page does not load an
276
+ identical copy from inside the built site, or when no built commerce page
277
+ loads `next-core.css`.
219
278
 
220
279
  ### Where next-core.css belongs
221
280