@nextcommerce/campaigns-os 1.43.2 → 1.46.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/AGENTS.md +5 -0
- package/CHANGELOG.md +648 -5103
- package/README.md +32 -11
- package/agents/claude/CLAUDE.md +1 -1
- package/agents/codex/AGENTS.md +1 -1
- package/agents/copilot/copilot-instructions.md +1 -1
- package/agents/cursor/campaigns-os.mdc +1 -1
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +2 -2
- package/campaign-spec/dist/rules/analytics-contract-shape.js +2 -2
- package/campaign-spec/dist/rules/store-profile-shape.d.ts +5 -1
- package/campaign-spec/dist/rules/store-profile-shape.js +8 -10
- package/campaign-spec/dist/types.d.ts +2 -2
- package/contracts/archive/CHANGELOG.2026-09-30.md +5111 -0
- package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
- package/contracts/effects.v1.json +1176 -113
- package/contracts/orientation-reason-codes.v1.json +7 -0
- package/contracts/release-ledger.json +2345 -6087
- package/contracts/supported-surface.json +7 -4
- package/contracts/template-slot-manifest.shared-content-core.v0.json +403 -0
- package/docs/brand-theme-bridge.md +81 -0
- package/docs/build-packet.md +158 -21
- package/docs/campaigns-os-build-flow.md +3 -3
- package/docs/design-source-package.md +73 -0
- package/docs/effects.md +50 -8
- package/docs/gateway-login.md +3 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +42 -2
- package/docs/polish-evidence.md +74 -0
- package/docs/qa-and-test-orders.md +99 -13
- package/docs/release-ledger-authoring-guide.md +64 -4
- package/docs/runtime-readiness.md +1 -1
- package/docs/sdk-storage-compatibility.md +1 -1
- package/docs/skills-revision.md +10 -10
- package/docs/supported-surface.md +2 -2
- package/docs/versioning.md +4 -1
- package/package.json +1 -1
- package/schemas/campaigns-os-release-ledger.v1.schema.json +32 -2
- package/schemas/campaigns-os-tooling-orientation.v1.schema.json +1 -0
- package/skills/campaign-lifecycle-orientation/SKILL.md +16 -5
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +7 -6
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +7 -6
- package/skills/next-campaigns-os/SKILL.md +7 -7
- package/skills/next-campaigns-os/references/session-intake.md +9 -3
- package/skills/next-campaigns-os-setup/SKILL.md +5 -5
- package/skills/next-campaigns-polish/SKILL.md +28 -9
- package/skills/next-campaigns-qa/SKILL.md +7 -4
- package/skills.json +10 -10
- package/src/brand-theme.mjs +320 -20
- package/src/built-site-scope.mjs +16 -4
- package/src/cli.mjs +280 -46
- package/src/commercial-parity.mjs +48 -2
- package/src/deviation.mjs +13 -1
- package/src/diagnostic.mjs +5 -2
- package/src/doctor/checks.mjs +320 -81
- package/src/doctor/inspect.mjs +55 -13
- package/src/doctor/source-provenance.mjs +184 -0
- package/src/invocation.mjs +4 -0
- package/src/live-campaign-refs.mjs +466 -0
- package/src/login.mjs +2 -2
- package/src/page-kit-store-profile.mjs +69 -12
- package/src/page-kit-sync.mjs +31 -12
- package/src/progress-node.mjs +3 -1
- package/src/qa-browser.mjs +538 -28
- package/src/qa-commercial-parity.mjs +48 -5
- package/src/qa-node.mjs +122 -7
- package/src/qa-test-order-topology.mjs +148 -0
- package/src/sdk-markup.mjs +72 -8
- package/src/source-html-intake.mjs +116 -0
- package/src/stage-record.mjs +551 -0
- package/src/upsell-selector-scope.mjs +112 -2
|
@@ -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.
|
|
3
|
+
"surface_version": "1.46.0",
|
|
4
4
|
"package_exports": [
|
|
5
5
|
"./commercial-journey",
|
|
6
6
|
"./commercial-parity",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"prepare-build",
|
|
23
23
|
"build",
|
|
24
24
|
"polish",
|
|
25
|
+
"record",
|
|
25
26
|
"checkpoint",
|
|
26
27
|
"page-kit",
|
|
27
28
|
"spec",
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
"sha256": "22dc5b48f12337163bd36307510e619f49e77331c974f45292ecd6e72175570d"
|
|
93
94
|
},
|
|
94
95
|
"schemas/campaigns-os-release-ledger.v1.schema.json": {
|
|
95
|
-
"sha256": "
|
|
96
|
+
"sha256": "d7521fe0ab4deb62485f92286cb890ee548ea290793a6ce7444ecc42ba336d49"
|
|
96
97
|
},
|
|
97
98
|
"schemas/campaigns-os-run-record.v0.schema.json": {
|
|
98
99
|
"sha256": "00dc9bcec79c12ea22a27f636188bcc33c6811df2d646ac7b49d4cc076e97355"
|
|
@@ -101,7 +102,7 @@
|
|
|
101
102
|
"sha256": "f8a2ab5eadbb71fb6d5653b670ee71e4471c3df74dd04000d3dbd3d9ae9e71fc"
|
|
102
103
|
},
|
|
103
104
|
"schemas/campaigns-os-tooling-orientation.v1.schema.json": {
|
|
104
|
-
"sha256": "
|
|
105
|
+
"sha256": "8a2431b564041e8d2cbfaaf9d77ec272249268e54f5fc8952681537e0ad06e7c"
|
|
105
106
|
},
|
|
106
107
|
"schemas/campaigns-os-workflow-finding.v0.schema.json": {
|
|
107
108
|
"sha256": "41ff370de4395149781d7e8c4ad6d7d4e2f896eb2030944399d36f1237726a20"
|
|
@@ -119,7 +120,7 @@
|
|
|
119
120
|
"sha256": "dfc9abed38d456969e47a21f606d308a03bdf036f3466f7d6e47a602747dacf4"
|
|
120
121
|
},
|
|
121
122
|
"contracts/effects.v1.json": {
|
|
122
|
-
"sha256": "
|
|
123
|
+
"sha256": "794fe51952685e5afe6eee1988ce0230aae7a15a64fa1efdb350624158796f9c"
|
|
123
124
|
},
|
|
124
125
|
"schemas/campaigns-os-effects.v1.schema.json": {
|
|
125
126
|
"sha256": "3eadd22169ab98bc7c2f682af2751267605170581182158d96be035b0cfe44dc"
|
|
@@ -148,6 +149,8 @@
|
|
|
148
149
|
"AGENTS.md",
|
|
149
150
|
"CHANGELOG.md",
|
|
150
151
|
"contracts/release-ledger.json",
|
|
152
|
+
"contracts/archive/release-ledger.2026-09-30.json",
|
|
153
|
+
"contracts/archive/CHANGELOG.2026-09-30.md",
|
|
151
154
|
"contracts/agent-relevant-change-policy.v1.json",
|
|
152
155
|
"contracts/orientation-limits.v1.json",
|
|
153
156
|
"contracts/orientation-reason-codes.v1.json",
|
|
@@ -3932,6 +3932,36 @@
|
|
|
3932
3932
|
"description": "Page-kit page metadata \u2014 template-owned, assembly never touches.",
|
|
3933
3933
|
"source_policy": "template_static"
|
|
3934
3934
|
},
|
|
3935
|
+
{
|
|
3936
|
+
"key": "announcement_text",
|
|
3937
|
+
"kind": "copy",
|
|
3938
|
+
"role": "micro",
|
|
3939
|
+
"description": "Top announcement bar on the upsell page, e.g. 'Special Offer Unlocked - Do Not Close This Page'.",
|
|
3940
|
+
"source_policy": "generated",
|
|
3941
|
+
"length_band_chars": {
|
|
3942
|
+
"min": 20,
|
|
3943
|
+
"max": 90
|
|
3944
|
+
}
|
|
3945
|
+
},
|
|
3946
|
+
{
|
|
3947
|
+
"key": "upsell_hero",
|
|
3948
|
+
"kind": "copy",
|
|
3949
|
+
"role": "headline",
|
|
3950
|
+
"description": "Structured upsell hero: title plus offer/suboffer/heading/text lines and a presentation variant. Hint: {price}, {package_name}, {discount} and {first_name} are SDK-resolved tokens, so keeping them keeps the copy in sync with live pricing. The length band applies to each copy string, not the whole block.",
|
|
3951
|
+
"source_policy": "generated",
|
|
3952
|
+
"length_band_chars": {
|
|
3953
|
+
"min": 15,
|
|
3954
|
+
"max": 140
|
|
3955
|
+
}
|
|
3956
|
+
},
|
|
3957
|
+
{
|
|
3958
|
+
"key": "upsell_layout",
|
|
3959
|
+
"kind": "meta",
|
|
3960
|
+
"role": "page_meta",
|
|
3961
|
+
"owner": "template",
|
|
3962
|
+
"description": "Structured layout knobs for the shared upsell offer shell (style, boxed, media, media_position, media_align, show_* toggles, decline_position). Template-owned presentation, assembly never touches.",
|
|
3963
|
+
"source_policy": "template_static"
|
|
3964
|
+
},
|
|
3935
3965
|
{
|
|
3936
3966
|
"key": "upsell_offer",
|
|
3937
3967
|
"kind": "commerce",
|
|
@@ -4015,6 +4045,36 @@
|
|
|
4015
4045
|
"description": "Page-kit page metadata \u2014 template-owned, assembly never touches.",
|
|
4016
4046
|
"source_policy": "template_static"
|
|
4017
4047
|
},
|
|
4048
|
+
{
|
|
4049
|
+
"key": "announcement_text",
|
|
4050
|
+
"kind": "copy",
|
|
4051
|
+
"role": "micro",
|
|
4052
|
+
"description": "Top announcement bar on the upsell page, e.g. 'Special Offer Unlocked - Do Not Close This Page'.",
|
|
4053
|
+
"source_policy": "generated",
|
|
4054
|
+
"length_band_chars": {
|
|
4055
|
+
"min": 20,
|
|
4056
|
+
"max": 90
|
|
4057
|
+
}
|
|
4058
|
+
},
|
|
4059
|
+
{
|
|
4060
|
+
"key": "upsell_hero",
|
|
4061
|
+
"kind": "copy",
|
|
4062
|
+
"role": "headline",
|
|
4063
|
+
"description": "Structured upsell hero: title plus offer/suboffer/heading/text lines and a presentation variant. Hint: {price}, {package_name}, {discount} and {first_name} are SDK-resolved tokens, so keeping them keeps the copy in sync with live pricing. The length band applies to each copy string, not the whole block.",
|
|
4064
|
+
"source_policy": "generated",
|
|
4065
|
+
"length_band_chars": {
|
|
4066
|
+
"min": 15,
|
|
4067
|
+
"max": 140
|
|
4068
|
+
}
|
|
4069
|
+
},
|
|
4070
|
+
{
|
|
4071
|
+
"key": "upsell_layout",
|
|
4072
|
+
"kind": "meta",
|
|
4073
|
+
"role": "page_meta",
|
|
4074
|
+
"owner": "template",
|
|
4075
|
+
"description": "Structured layout knobs for the shared upsell offer shell (style, boxed, media, media_position, media_align, show_* toggles, decline_position). Template-owned presentation, assembly never touches.",
|
|
4076
|
+
"source_policy": "template_static"
|
|
4077
|
+
},
|
|
4018
4078
|
{
|
|
4019
4079
|
"key": "upsell_offer",
|
|
4020
4080
|
"kind": "commerce",
|
|
@@ -4106,6 +4166,36 @@
|
|
|
4106
4166
|
"description": "Page-kit page metadata \u2014 template-owned, assembly never touches.",
|
|
4107
4167
|
"source_policy": "template_static"
|
|
4108
4168
|
},
|
|
4169
|
+
{
|
|
4170
|
+
"key": "announcement_text",
|
|
4171
|
+
"kind": "copy",
|
|
4172
|
+
"role": "micro",
|
|
4173
|
+
"description": "Top announcement bar on the upsell page, e.g. 'Special Offer Unlocked - Do Not Close This Page'.",
|
|
4174
|
+
"source_policy": "generated",
|
|
4175
|
+
"length_band_chars": {
|
|
4176
|
+
"min": 20,
|
|
4177
|
+
"max": 90
|
|
4178
|
+
}
|
|
4179
|
+
},
|
|
4180
|
+
{
|
|
4181
|
+
"key": "upsell_hero",
|
|
4182
|
+
"kind": "copy",
|
|
4183
|
+
"role": "headline",
|
|
4184
|
+
"description": "Structured upsell hero: title plus offer/suboffer/heading/text lines and a presentation variant. Hint: {price}, {package_name}, {discount} and {first_name} are SDK-resolved tokens, so keeping them keeps the copy in sync with live pricing. The length band applies to each copy string, not the whole block.",
|
|
4185
|
+
"source_policy": "generated",
|
|
4186
|
+
"length_band_chars": {
|
|
4187
|
+
"min": 15,
|
|
4188
|
+
"max": 140
|
|
4189
|
+
}
|
|
4190
|
+
},
|
|
4191
|
+
{
|
|
4192
|
+
"key": "upsell_layout",
|
|
4193
|
+
"kind": "meta",
|
|
4194
|
+
"role": "page_meta",
|
|
4195
|
+
"owner": "template",
|
|
4196
|
+
"description": "Structured layout knobs for the shared upsell offer shell (style, boxed, media, media_position, media_align, show_* toggles, decline_position). Template-owned presentation, assembly never touches.",
|
|
4197
|
+
"source_policy": "template_static"
|
|
4198
|
+
},
|
|
4109
4199
|
{
|
|
4110
4200
|
"key": "upsell_offer",
|
|
4111
4201
|
"kind": "commerce",
|
|
@@ -4142,6 +4232,319 @@
|
|
|
4142
4232
|
}
|
|
4143
4233
|
]
|
|
4144
4234
|
},
|
|
4235
|
+
"upsell-single": {
|
|
4236
|
+
"sections": [
|
|
4237
|
+
{
|
|
4238
|
+
"section": "main",
|
|
4239
|
+
"slots": [
|
|
4240
|
+
{
|
|
4241
|
+
"key": "title",
|
|
4242
|
+
"kind": "copy",
|
|
4243
|
+
"role": "micro",
|
|
4244
|
+
"description": "Short copy slot.",
|
|
4245
|
+
"source_policy": "generated",
|
|
4246
|
+
"length_band_chars": {
|
|
4247
|
+
"min": 15,
|
|
4248
|
+
"max": 67
|
|
4249
|
+
},
|
|
4250
|
+
"demo_value": "Apollo \u2014 Upsell (single offer)"
|
|
4251
|
+
},
|
|
4252
|
+
{
|
|
4253
|
+
"key": "page_type",
|
|
4254
|
+
"kind": "meta",
|
|
4255
|
+
"role": "page_meta",
|
|
4256
|
+
"owner": "template",
|
|
4257
|
+
"description": "Page-kit page metadata \u2014 template-owned, assembly never touches.",
|
|
4258
|
+
"source_policy": "template_static"
|
|
4259
|
+
},
|
|
4260
|
+
{
|
|
4261
|
+
"key": "next_url",
|
|
4262
|
+
"kind": "link",
|
|
4263
|
+
"role": "routing",
|
|
4264
|
+
"description": "Route/link target.",
|
|
4265
|
+
"source_policy": "spec_truth"
|
|
4266
|
+
},
|
|
4267
|
+
{
|
|
4268
|
+
"key": "decline_url",
|
|
4269
|
+
"kind": "link",
|
|
4270
|
+
"role": "routing",
|
|
4271
|
+
"description": "Route/link target.",
|
|
4272
|
+
"source_policy": "spec_truth"
|
|
4273
|
+
},
|
|
4274
|
+
{
|
|
4275
|
+
"key": "scripts",
|
|
4276
|
+
"kind": "meta",
|
|
4277
|
+
"role": "page_meta",
|
|
4278
|
+
"owner": "template",
|
|
4279
|
+
"description": "Page-kit page metadata \u2014 template-owned, assembly never touches.",
|
|
4280
|
+
"source_policy": "template_static"
|
|
4281
|
+
},
|
|
4282
|
+
{
|
|
4283
|
+
"key": "announcement_text",
|
|
4284
|
+
"kind": "copy",
|
|
4285
|
+
"role": "micro",
|
|
4286
|
+
"description": "Top announcement bar on the upsell page, e.g. 'Special Offer Unlocked - Do Not Close This Page'.",
|
|
4287
|
+
"source_policy": "generated",
|
|
4288
|
+
"length_band_chars": {
|
|
4289
|
+
"min": 20,
|
|
4290
|
+
"max": 90
|
|
4291
|
+
}
|
|
4292
|
+
},
|
|
4293
|
+
{
|
|
4294
|
+
"key": "upsell_hero",
|
|
4295
|
+
"kind": "copy",
|
|
4296
|
+
"role": "headline",
|
|
4297
|
+
"description": "Structured upsell hero: title plus offer/suboffer/heading/text lines and a presentation variant. Hint: {price}, {package_name}, {discount} and {first_name} are SDK-resolved tokens, so keeping them keeps the copy in sync with live pricing. The length band applies to each copy string, not the whole block.",
|
|
4298
|
+
"source_policy": "generated",
|
|
4299
|
+
"length_band_chars": {
|
|
4300
|
+
"min": 15,
|
|
4301
|
+
"max": 140
|
|
4302
|
+
}
|
|
4303
|
+
},
|
|
4304
|
+
{
|
|
4305
|
+
"key": "countdown_timer",
|
|
4306
|
+
"kind": "copy",
|
|
4307
|
+
"role": "micro",
|
|
4308
|
+
"description": "Structured SDK countdown (text, highlight, duration, persistence_id). The length band applies to each copy string (text, highlight).",
|
|
4309
|
+
"source_policy": "generated",
|
|
4310
|
+
"length_band_chars": {
|
|
4311
|
+
"min": 6,
|
|
4312
|
+
"max": 40
|
|
4313
|
+
}
|
|
4314
|
+
},
|
|
4315
|
+
{
|
|
4316
|
+
"key": "upsell_layout",
|
|
4317
|
+
"kind": "meta",
|
|
4318
|
+
"role": "page_meta",
|
|
4319
|
+
"owner": "template",
|
|
4320
|
+
"description": "Structured layout knobs for the shared upsell offer shell (style, boxed, media, media_position, media_align, show_* toggles, decline_position). Template-owned presentation, assembly never touches.",
|
|
4321
|
+
"source_policy": "template_static"
|
|
4322
|
+
},
|
|
4323
|
+
{
|
|
4324
|
+
"key": "upsell_offer",
|
|
4325
|
+
"kind": "commerce",
|
|
4326
|
+
"role": "commerce_structure",
|
|
4327
|
+
"owner": "commerce",
|
|
4328
|
+
"description": "Structured commerce configuration \u2014 owned by the commerce-surface catalog and the family brand contract, not the content-slot layer.",
|
|
4329
|
+
"reference": "integrated_commerce_surface_ref"
|
|
4330
|
+
},
|
|
4331
|
+
{
|
|
4332
|
+
"key": "upsell_body",
|
|
4333
|
+
"kind": "copy",
|
|
4334
|
+
"role": "body",
|
|
4335
|
+
"description": "Structured upsell body sections (features, comparison, steps, reviews). The length band applies to each copy string, not the whole block.",
|
|
4336
|
+
"source_policy": "generated",
|
|
4337
|
+
"length_band_chars": {
|
|
4338
|
+
"min": 10,
|
|
4339
|
+
"max": 240
|
|
4340
|
+
}
|
|
4341
|
+
},
|
|
4342
|
+
{
|
|
4343
|
+
"key": "closing_cta",
|
|
4344
|
+
"kind": "copy",
|
|
4345
|
+
"role": "cta",
|
|
4346
|
+
"description": "Structured closing accept/decline block (variant, title, note, show_guarantees). The length band applies to each copy string (title, note).",
|
|
4347
|
+
"source_policy": "generated",
|
|
4348
|
+
"length_band_chars": {
|
|
4349
|
+
"min": 10,
|
|
4350
|
+
"max": 140
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
]
|
|
4354
|
+
}
|
|
4355
|
+
]
|
|
4356
|
+
},
|
|
4357
|
+
"upsell-vsl": {
|
|
4358
|
+
"sections": [
|
|
4359
|
+
{
|
|
4360
|
+
"section": "main",
|
|
4361
|
+
"slots": [
|
|
4362
|
+
{
|
|
4363
|
+
"key": "title",
|
|
4364
|
+
"kind": "copy",
|
|
4365
|
+
"role": "micro",
|
|
4366
|
+
"description": "Short copy slot.",
|
|
4367
|
+
"source_policy": "generated",
|
|
4368
|
+
"length_band_chars": {
|
|
4369
|
+
"min": 15,
|
|
4370
|
+
"max": 67
|
|
4371
|
+
},
|
|
4372
|
+
"demo_value": "Apollo \u2014 Upsell (VSL)"
|
|
4373
|
+
},
|
|
4374
|
+
{
|
|
4375
|
+
"key": "page_type",
|
|
4376
|
+
"kind": "meta",
|
|
4377
|
+
"role": "page_meta",
|
|
4378
|
+
"owner": "template",
|
|
4379
|
+
"description": "Page-kit page metadata \u2014 template-owned, assembly never touches.",
|
|
4380
|
+
"source_policy": "template_static"
|
|
4381
|
+
},
|
|
4382
|
+
{
|
|
4383
|
+
"key": "next_url",
|
|
4384
|
+
"kind": "link",
|
|
4385
|
+
"role": "routing",
|
|
4386
|
+
"description": "Route/link target.",
|
|
4387
|
+
"source_policy": "spec_truth"
|
|
4388
|
+
},
|
|
4389
|
+
{
|
|
4390
|
+
"key": "decline_url",
|
|
4391
|
+
"kind": "link",
|
|
4392
|
+
"role": "routing",
|
|
4393
|
+
"description": "Route/link target.",
|
|
4394
|
+
"source_policy": "spec_truth"
|
|
4395
|
+
},
|
|
4396
|
+
{
|
|
4397
|
+
"key": "styles",
|
|
4398
|
+
"kind": "meta",
|
|
4399
|
+
"role": "page_meta",
|
|
4400
|
+
"owner": "template",
|
|
4401
|
+
"description": "Page-kit page metadata \u2014 template-owned, assembly never touches.",
|
|
4402
|
+
"source_policy": "template_static"
|
|
4403
|
+
},
|
|
4404
|
+
{
|
|
4405
|
+
"key": "scripts",
|
|
4406
|
+
"kind": "meta",
|
|
4407
|
+
"role": "page_meta",
|
|
4408
|
+
"owner": "template",
|
|
4409
|
+
"description": "Page-kit page metadata \u2014 template-owned, assembly never touches.",
|
|
4410
|
+
"source_policy": "template_static"
|
|
4411
|
+
},
|
|
4412
|
+
{
|
|
4413
|
+
"key": "announcement_text",
|
|
4414
|
+
"kind": "copy",
|
|
4415
|
+
"role": "micro",
|
|
4416
|
+
"description": "Top announcement bar on the upsell page, e.g. 'Special Offer Unlocked - Do Not Close This Page'.",
|
|
4417
|
+
"source_policy": "generated",
|
|
4418
|
+
"length_band_chars": {
|
|
4419
|
+
"min": 20,
|
|
4420
|
+
"max": 90
|
|
4421
|
+
}
|
|
4422
|
+
},
|
|
4423
|
+
{
|
|
4424
|
+
"key": "announcement_variant",
|
|
4425
|
+
"kind": "meta",
|
|
4426
|
+
"role": "page_meta",
|
|
4427
|
+
"owner": "template",
|
|
4428
|
+
"description": "Announcement bar style knob (e.g. danger). Template-owned presentation, assembly never touches.",
|
|
4429
|
+
"source_policy": "template_static"
|
|
4430
|
+
},
|
|
4431
|
+
{
|
|
4432
|
+
"key": "header_bar",
|
|
4433
|
+
"kind": "meta",
|
|
4434
|
+
"role": "page_meta",
|
|
4435
|
+
"owner": "template",
|
|
4436
|
+
"description": "Header style knob for the upsell page (e.g. logo-only). Template-owned presentation, assembly never touches.",
|
|
4437
|
+
"source_policy": "template_static"
|
|
4438
|
+
},
|
|
4439
|
+
{
|
|
4440
|
+
"key": "upsell_hero",
|
|
4441
|
+
"kind": "copy",
|
|
4442
|
+
"role": "headline",
|
|
4443
|
+
"description": "Structured upsell hero: title plus offer/suboffer/heading/text lines and a presentation variant. Hint: {price}, {package_name}, {discount} and {first_name} are SDK-resolved tokens, so keeping them keeps the copy in sync with live pricing. The length band applies to each copy string, not the whole block.",
|
|
4444
|
+
"source_policy": "generated",
|
|
4445
|
+
"length_band_chars": {
|
|
4446
|
+
"min": 15,
|
|
4447
|
+
"max": 140
|
|
4448
|
+
}
|
|
4449
|
+
},
|
|
4450
|
+
{
|
|
4451
|
+
"key": "countdown_timer",
|
|
4452
|
+
"kind": "copy",
|
|
4453
|
+
"role": "micro",
|
|
4454
|
+
"description": "Structured SDK countdown (text, highlight, duration, persistence_id). The length band applies to each copy string (text, highlight).",
|
|
4455
|
+
"source_policy": "generated",
|
|
4456
|
+
"length_band_chars": {
|
|
4457
|
+
"min": 6,
|
|
4458
|
+
"max": 40
|
|
4459
|
+
}
|
|
4460
|
+
},
|
|
4461
|
+
{
|
|
4462
|
+
"key": "upsell_layout",
|
|
4463
|
+
"kind": "meta",
|
|
4464
|
+
"role": "page_meta",
|
|
4465
|
+
"owner": "template",
|
|
4466
|
+
"description": "Structured layout knobs for the shared upsell offer shell (style, boxed, media, media_position, media_align, show_* toggles, decline_position). Template-owned presentation, assembly never touches.",
|
|
4467
|
+
"source_policy": "template_static"
|
|
4468
|
+
},
|
|
4469
|
+
{
|
|
4470
|
+
"key": "upsell_offer",
|
|
4471
|
+
"kind": "commerce",
|
|
4472
|
+
"role": "commerce_structure",
|
|
4473
|
+
"owner": "commerce",
|
|
4474
|
+
"description": "Structured commerce configuration \u2014 owned by the commerce-surface catalog and the family brand contract, not the content-slot layer.",
|
|
4475
|
+
"reference": "integrated_commerce_surface_ref"
|
|
4476
|
+
},
|
|
4477
|
+
{
|
|
4478
|
+
"key": "upsell_video",
|
|
4479
|
+
"kind": "image",
|
|
4480
|
+
"role": "image",
|
|
4481
|
+
"description": "Structured upsell video (src, poster, title, autoplay, loop).",
|
|
4482
|
+
"source_policy": "merchant_asset_required"
|
|
4483
|
+
},
|
|
4484
|
+
{
|
|
4485
|
+
"key": "swiper_slides",
|
|
4486
|
+
"kind": "commerce",
|
|
4487
|
+
"role": "commerce_structure",
|
|
4488
|
+
"owner": "commerce",
|
|
4489
|
+
"description": "Structured commerce configuration \u2014 owned by the commerce-surface catalog and the family brand contract, not the content-slot layer.",
|
|
4490
|
+
"reference": "integrated_commerce_surface_ref"
|
|
4491
|
+
},
|
|
4492
|
+
{
|
|
4493
|
+
"key": "swiper_thumbs",
|
|
4494
|
+
"kind": "commerce",
|
|
4495
|
+
"role": "commerce_structure",
|
|
4496
|
+
"owner": "commerce",
|
|
4497
|
+
"description": "Structured commerce configuration \u2014 owned by the commerce-surface catalog and the family brand contract, not the content-slot layer.",
|
|
4498
|
+
"reference": "integrated_commerce_surface_ref"
|
|
4499
|
+
},
|
|
4500
|
+
{
|
|
4501
|
+
"key": "offer_bullets",
|
|
4502
|
+
"kind": "copy",
|
|
4503
|
+
"role": "body",
|
|
4504
|
+
"description": "Bullet list beside the upsell offer. The length band applies to each bullet.",
|
|
4505
|
+
"source_policy": "generated",
|
|
4506
|
+
"length_band_chars": {
|
|
4507
|
+
"min": 15,
|
|
4508
|
+
"max": 80
|
|
4509
|
+
}
|
|
4510
|
+
},
|
|
4511
|
+
{
|
|
4512
|
+
"key": "upsell_guarantees",
|
|
4513
|
+
"kind": "copy",
|
|
4514
|
+
"role": "body",
|
|
4515
|
+
"description": "Structured guarantee/trust cards (e.g. secure checkout, money-back terms). The length band applies to each copy string (title, text).",
|
|
4516
|
+
"source_policy": "spec_truth",
|
|
4517
|
+
"length_band_chars": {
|
|
4518
|
+
"min": 10,
|
|
4519
|
+
"max": 200
|
|
4520
|
+
}
|
|
4521
|
+
},
|
|
4522
|
+
{
|
|
4523
|
+
"key": "upsell_body",
|
|
4524
|
+
"kind": "copy",
|
|
4525
|
+
"role": "body",
|
|
4526
|
+
"description": "Structured upsell body sections (features, comparison, steps, reviews). The length band applies to each copy string, not the whole block.",
|
|
4527
|
+
"source_policy": "generated",
|
|
4528
|
+
"length_band_chars": {
|
|
4529
|
+
"min": 10,
|
|
4530
|
+
"max": 240
|
|
4531
|
+
}
|
|
4532
|
+
},
|
|
4533
|
+
{
|
|
4534
|
+
"key": "closing_cta",
|
|
4535
|
+
"kind": "copy",
|
|
4536
|
+
"role": "cta",
|
|
4537
|
+
"description": "Structured closing accept/decline block (variant, title, note, show_guarantees). The length band applies to each copy string (title, note).",
|
|
4538
|
+
"source_policy": "generated",
|
|
4539
|
+
"length_band_chars": {
|
|
4540
|
+
"min": 10,
|
|
4541
|
+
"max": 140
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
]
|
|
4545
|
+
}
|
|
4546
|
+
]
|
|
4547
|
+
},
|
|
4145
4548
|
"receipt": {
|
|
4146
4549
|
"sections": [
|
|
4147
4550
|
{
|
|
@@ -54,6 +54,65 @@ that still falls below the contract's `min_contrast_ratio` is emitted with a
|
|
|
54
54
|
confirmed. Derived-foreground confidence scales with the achieved contrast
|
|
55
55
|
(`>= 7:1` high, `>= 4.5:1` medium, otherwise low).
|
|
56
56
|
|
|
57
|
+
One exception keeps the design's own CTA label. The declared CTA foreground is
|
|
58
|
+
read from the selected source in this order:
|
|
59
|
+
|
|
60
|
+
1. the `color:` of the button rules whose `background` or `background-color`
|
|
61
|
+
is the CTA background (the design's own pairing), only when they all agree
|
|
62
|
+
on one colour; when they disagree, this step declares nothing and the next
|
|
63
|
+
step applies;
|
|
64
|
+
2. a `:root` inverse/on-colour text token: the name needs a `text` or
|
|
65
|
+
`foreground` part plus `inverse`, or `on` followed by `primary`, `cta`,
|
|
66
|
+
`brand`, `accent` or `dark` (`--text-inverse` first, then for example
|
|
67
|
+
`--text-color-inverse`, `--text-on-primary`, `--on-primary-text` or
|
|
68
|
+
`--foreground-on-dark`). `--border-on-primary`, `--overlay-on-dark` and a
|
|
69
|
+
bare `--on-primary` are not text and never qualify;
|
|
70
|
+
3. the `color:` of the other button rules that declare no background, only when
|
|
71
|
+
they all agree on one colour.
|
|
72
|
+
|
|
73
|
+
A button rule is one whose every selector ends in a compound selector that is
|
|
74
|
+
the `button` element, `input[type=submit]`, or a class starting with
|
|
75
|
+
`btn`, `button` or `cta` or having a `cta` part (`.btn-primary`, `.button`,
|
|
76
|
+
`.cta`, `.hero-cta`). An attribute alone does not make a button:
|
|
77
|
+
`[type=submit]`, `div[type="submit"]` and `.order-summary[type=submit]` do not
|
|
78
|
+
qualify. Only the compound's own element, class and attribute
|
|
79
|
+
selectors count: the value inside an attribute selector and the arguments of
|
|
80
|
+
`:not()`, `:is()`, `:where()` and `:has()` are not read, so
|
|
81
|
+
`.btn-primary[data-x]` and `button:not(.order-summary)` qualify while
|
|
82
|
+
`.order-summary[data-target=".btn"]`, `.order-summary:not(.btn)` and
|
|
83
|
+
`.cart:has(.button)` do not. Any other pseudo-class or pseudo-element
|
|
84
|
+
(`:hover`, `:disabled`, `::before`) disqualifies the selector. Selectors such
|
|
85
|
+
as `.order-summary` or `.cart-count`, and `.btn .icon`, never qualify. When that
|
|
86
|
+
colour reaches at least 3:1 on the CTA background (WCAG AA for large text),
|
|
87
|
+
`--brand--color--text-inverse` and `--brand--color--cta-foreground` use it
|
|
88
|
+
(`derivation.method: declared-cta-foreground`). White on `#dd4249` is 4.24:1,
|
|
89
|
+
so a design that declares white CTA text keeps white, although black scores
|
|
90
|
+
higher there. A declared colour under 3:1 is ignored and the luminance pick
|
|
91
|
+
applies, so a white scaffold default on a yellow CTA still resolves dark. A
|
|
92
|
+
declared colour between 3:1 and the contract's `min_contrast_ratio` (4.5:1) is
|
|
93
|
+
used and reported with `theme.foreground.low_contrast`. With no declared CTA
|
|
94
|
+
foreground, the output is unchanged. Declarations inside CSS comments are not
|
|
95
|
+
read, so a commented-out token or rule never supplies a CTA or body text colour.
|
|
96
|
+
|
|
97
|
+
### Body text prefers the darkest declared text token
|
|
98
|
+
|
|
99
|
+
A declared text token is a `:root` custom property in the selected source whose
|
|
100
|
+
name has a `text` part and whose value is a solid colour. Names that carry
|
|
101
|
+
another job are not counted: inverse/on-colour labels (`text` or `foreground`
|
|
102
|
+
with `inverse` in any order, or `on` followed by `primary`, `cta`, `brand`,
|
|
103
|
+
`accent` or `dark`; `--text-on-light` is ordinary copy), `secondary`, `muted` or
|
|
104
|
+
`subtle` copy, link, status and state colours (`link`, `error`, `danger`,
|
|
105
|
+
`success`, `warning`, `info`, `highlight`, `accent`, `placeholder`, `disabled`,
|
|
106
|
+
`selection`), `cta`/`button`/`btn` labels, and text `shadow`, `border`,
|
|
107
|
+
`outline`, `stroke`, `bg` or `background` values. When the source has a solid
|
|
108
|
+
body background (`--surface-bg`), `--brand--color--text-primary` and
|
|
109
|
+
`--brand--color--foreground` take the darkest declared text token that is
|
|
110
|
+
darker than that background and reaches 4.5:1 on it
|
|
111
|
+
(`derivation.method: darkest-declared-text-token`, with the replaced value
|
|
112
|
+
recorded, or `null` when the source yielded no `--text-primary`). This applies
|
|
113
|
+
whether or not the source yields a `--text-primary` of its own. If no token
|
|
114
|
+
qualifies, the existing pick (or its absence) stands.
|
|
115
|
+
|
|
57
116
|
> **Contract change (PR #117):** `--text-inverse` and the three `*-foreground`
|
|
58
117
|
> targets are no longer entries under `source_mappings` — they moved to
|
|
59
118
|
> `foreground_derivations`. A source `--text-inverse` token (or anything that
|
|
@@ -141,6 +200,28 @@ If a fresh `brand-theme.css` exists:
|
|
|
141
200
|
4. Record `report.theme.status`, `css_path`, `commerce_pages`, `load_order`,
|
|
142
201
|
and evidence.
|
|
143
202
|
|
|
203
|
+
### Where next-core.css belongs
|
|
204
|
+
|
|
205
|
+
`next-core.css` and the brand layer are needed only on pages where the
|
|
206
|
+
starter-template family's components render, because those components read
|
|
207
|
+
the `--brand--*` tokens. `next-core.css` also carries element resets (`li`,
|
|
208
|
+
`a`, headings, body letter-spacing) that restyle any markup on the page. On a
|
|
209
|
+
page whose upsell, downsell or receipt markup comes from the design rather
|
|
210
|
+
than from family components, loading it breaks that markup. Leave both
|
|
211
|
+
stylesheets off those pages and list them only in the frontmatter styles of
|
|
212
|
+
the pages that render family components, often just checkout.
|
|
213
|
+
|
|
214
|
+
`report.theme.commerce_pages` is the list of pages where the brand layer was
|
|
215
|
+
applied, recorded by the build. Record the pages you actually scoped, for
|
|
216
|
+
example `commerce_pages: ["checkout"]`. The theme gate does not compare this
|
|
217
|
+
list with the funnel. It passes on `report.theme.status: applied` with
|
|
218
|
+
`load_order: after-next-core`. The gate's own `commerce_pages` output is a
|
|
219
|
+
different field: every checkout, upsell, downsell, receipt or thank-you page
|
|
220
|
+
the campaign builds or declares. It decides whether the gate applies, not
|
|
221
|
+
which pages must load the brand layer. No check reads each built page's
|
|
222
|
+
stylesheet order, so the recorded list and the evidence are what a reviewer
|
|
223
|
+
sees.
|
|
224
|
+
|
|
144
225
|
Polish should verify token parity, load order after next-core, starter-logo
|
|
145
226
|
replacement when source assets expose a real brand mark, and SDK safety. If the
|
|
146
227
|
brand layer is repairable, record the first repair-loop defect.
|