@nextcommerce/campaigns-os 1.60.0 → 1.61.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/CHANGELOG.md +126 -0
- package/README.md +5 -5
- package/agents/claude/CLAUDE.md +15 -0
- package/agents/codex/AGENTS.md +15 -0
- package/agents/copilot/copilot-instructions.md +15 -0
- package/agents/cursor/campaigns-os.mdc +15 -0
- package/compatibility.json +1 -1
- package/contracts/brand-theme-target-tokens.next-core.v0.json +95 -25
- package/contracts/campaign-cart-sdk-support-policy.v0.json +2 -2
- package/contracts/commerce-surface-catalog.json +27 -26
- package/contracts/release-ledger.json +768 -0
- package/contracts/supported-surface.json +4 -4
- package/contracts/template-brand-contract.shared-commerce.v0.json +1 -1
- package/docs/access-model.md +20 -0
- package/docs/brand-theme-bridge.md +62 -3
- package/docs/build-packet.md +28 -1
- package/docs/campaign-build-brief.md +16 -0
- package/docs/campaigns-os-build-flow.md +61 -2
- package/docs/campaignspec-authoring-examples.md +116 -0
- package/docs/design-source-package.md +46 -2
- package/docs/entry-points.md +188 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/polish-evidence.md +40 -2
- package/docs/qa-and-test-orders.md +50 -2
- package/docs/quickstart.md +438 -0
- package/docs/readback.md +119 -6
- package/docs/runtime-readiness.md +1 -1
- package/docs/sdk-storage-compatibility.md +3 -1
- package/docs/skills-revision.md +10 -10
- package/docs/source-adapters.md +213 -0
- package/package.json +10 -3
- package/schemas/campaign-design-source-package.v0.schema.json +8 -0
- package/schemas/campaigns-os-readback.v2.schema.json +177 -1
- package/schemas/source-html-manifest.v0.schema.json +3 -0
- package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +3 -3
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +4 -4
- package/skills/next-campaigns-os/SKILL.md +4 -3
- package/skills/next-campaigns-os-setup/SKILL.md +3 -3
- package/skills/next-campaigns-polish/SKILL.md +6 -9
- package/skills/next-campaigns-qa/SKILL.md +3 -3
- package/skills/next-campaigns-sdk-bump/SKILL.md +117 -24
- package/skills.json +11 -11
- package/src/brand-theme.mjs +413 -8
- package/src/brief-summary.mjs +6 -2
- package/src/browser-launch.mjs +2 -1
- package/src/build-brief.mjs +9 -2
- package/src/built-site-scope.mjs +2 -2
- package/src/cli.mjs +52 -13
- package/src/design-file-reference.mjs +93 -0
- package/src/design-source-package.mjs +55 -1
- package/src/doctor/checks.mjs +8 -74
- package/src/doctor/ci.mjs +112 -0
- package/src/doctor/inspect.mjs +18 -0
- package/src/doctor/source-provenance.mjs +74 -0
- package/src/evidence-debt.mjs +119 -0
- package/src/parity/compare.mjs +316 -0
- package/src/parity/design-access.mjs +54 -0
- package/src/parity/element-parity-record.mjs +569 -0
- package/src/parity/probe.mjs +205 -0
- package/src/parity/snapshot.mjs +110 -0
- package/src/png-header.mjs +87 -0
- package/src/polish-browser.mjs +186 -9
- package/src/polish-captures-record.mjs +52 -0
- package/src/polish-deadline.mjs +14 -0
- package/src/polish-gate.mjs +25 -6
- package/src/polish-node.mjs +81 -7
- package/src/polish-visual-fixtures.mjs +74 -0
- package/src/polish-visual.mjs +606 -0
- package/src/qa-analytics-correctness.mjs +14 -2
- package/src/qa-analytics-parity.mjs +94 -5
- package/src/qa-node.mjs +39 -6
- package/src/qc-accept.mjs +148 -6
- package/src/qc-check-registry.mjs +37 -1
- package/src/qc-results.mjs +404 -16
- package/src/readback.mjs +57 -13
- package/src/sdk-attribute-index.mjs +11 -11
- package/src/sdk-markup.mjs +77 -2
- package/src/sdk-storage-compatibility.mjs +94 -4
- package/src/source-html-manifest.mjs +7 -0
- package/src/source-html-server.mjs +129 -0
- package/src/stage-record.mjs +45 -7
- package/src/theme-gate.mjs +2 -2
- package/src/visual-diff.mjs +296 -0
- package/src/visual-reference.mjs +126 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Entry Points
|
|
2
|
+
|
|
3
|
+
A CampaignSpec is the destination contract. Source HTML is the input
|
|
4
|
+
that gets composed against it. **Where source HTML comes from is the
|
|
5
|
+
entry point.** Different campaigns enter the build pipeline from
|
|
6
|
+
different shapes:
|
|
7
|
+
|
|
8
|
+
| Entry point | `design_source` on spec | Source-HTML manifest | What it looks like |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| Template-stock | unset | absent | Spec + filesystem of starter-template pages. No design overlay. |
|
|
11
|
+
| Figma-driven | `type: "figma"` | emitted by `figma-sections-export` | Spec + source HTML produced by the Figma exporter. |
|
|
12
|
+
| AI-generated | `type: "ai-generated"` | emitted by the producing agent | Spec + source HTML written by an LLM/agent. |
|
|
13
|
+
| Hand-authored | unset | absent | Spec + source HTML hand-written by a designer/dev. Same shape as template-stock from the consumer's view. |
|
|
14
|
+
| Mixed | per-page | one manifest enumerating every page | Spec + source HTML composed from multiple producers (figma export for landing, template stock for checkout, etc.). |
|
|
15
|
+
|
|
16
|
+
The build pipeline (`prepare-build` → doctor → setup → build → polish
|
|
17
|
+
→ deploy → QA) is **identical across all five entry points**. The
|
|
18
|
+
only thing that varies is how the source HTML arrives and how
|
|
19
|
+
`design_source` / the manifest get populated. Doctor reads both, the
|
|
20
|
+
rest of the pipeline reads the packet doctor produced.
|
|
21
|
+
|
|
22
|
+
This doc explains how each entry point is recognized, what
|
|
23
|
+
populates each input, and what the operator does for that path. The
|
|
24
|
+
[Source HTML Manifest Auto-Population](./build-packet.md#source-html-manifest-auto-population)
|
|
25
|
+
section in `build-packet.md` covers the manifest consumer
|
|
26
|
+
mechanics; this doc names which entry point produces which input.
|
|
27
|
+
|
|
28
|
+
## Template-stock
|
|
29
|
+
|
|
30
|
+
The campaign is being scaffolded against a starter template
|
|
31
|
+
(e.g. `olympus-mv-single-step`) with **no design overlay yet**. Spec
|
|
32
|
+
values fill the template's placeholders. Source HTML is whatever the
|
|
33
|
+
template family ships, possibly with light brand tweaks.
|
|
34
|
+
|
|
35
|
+
- **CampaignSpec:** every page is present but `design_source` is
|
|
36
|
+
unset on every page.
|
|
37
|
+
- **Source HTML:** the starter template's `src/` folder copied into
|
|
38
|
+
the target repo. Filenames match standard page-type conventions
|
|
39
|
+
(`landing.html`, `checkout.html`, `upsell.html`, `receipt.html`).
|
|
40
|
+
- **Manifest:** absent.
|
|
41
|
+
- **`prepare-build` behavior:** falls back to filesystem matching
|
|
42
|
+
(slug-based filename → CampaignSpec page mapping).
|
|
43
|
+
- **Doctor behavior:** any page with no source mapping fires the
|
|
44
|
+
generic `source_html.pages.coverage` error (no Figma-specific hint
|
|
45
|
+
because `design_source` is unset).
|
|
46
|
+
- **When to use:** initial scaffold, A/B variant testing on top of
|
|
47
|
+
an existing template, no-design-yet builds where the team will add
|
|
48
|
+
design later.
|
|
49
|
+
- **Realistic example:** a single-product scaffold pass — clone
|
|
50
|
+
`olympus-mv-single-step`, fill spec, ship. Design overlay comes in
|
|
51
|
+
a separate later pass.
|
|
52
|
+
|
|
53
|
+
## Figma-driven
|
|
54
|
+
|
|
55
|
+
Design lives in Figma. `figma-sections-export` produces source HTML
|
|
56
|
+
plus a manifest at handoff.
|
|
57
|
+
|
|
58
|
+
- **CampaignSpec:** the pages that need design carry
|
|
59
|
+
`design_source.type === "figma"` with `file_url` plus
|
|
60
|
+
`breakpoints.{desktop, tablet, mobile}` per page.
|
|
61
|
+
- **Source HTML:** `figma-sections-export` writes
|
|
62
|
+
`<campaign>/landing.html`, `<campaign>/_includes/landing/*.html`,
|
|
63
|
+
and/or `<campaign>/presell.html` into the source root.
|
|
64
|
+
- **Manifest:** `<source>/.campaigns-os/source-html-manifest.json`
|
|
65
|
+
(or the file named by `--design-manifest <path>`) with
|
|
66
|
+
`generator: "figma-sections-export@<version>"`. Lists the
|
|
67
|
+
pages produced.
|
|
68
|
+
- **`prepare-build` behavior:** reads the manifest, populates
|
|
69
|
+
`packet.source_html.pages[]` directly from it (bypasses filesystem
|
|
70
|
+
matching).
|
|
71
|
+
- **Doctor behavior:** any spec page with `design_source` but no
|
|
72
|
+
source mapping fires a design_source-aware error naming the
|
|
73
|
+
Figma file and recommending
|
|
74
|
+
`npm run handoff -- <slug>` in figma-sections-export.
|
|
75
|
+
- **When to use:** designer authors in Figma, ships the export at
|
|
76
|
+
handoff, build picks up automatically.
|
|
77
|
+
|
|
78
|
+
## AI-generated
|
|
79
|
+
|
|
80
|
+
An LLM or agent (Claude, Codex, etc.) writes the source HTML
|
|
81
|
+
directly. The contract is the same as figma-driven from the
|
|
82
|
+
consumer's view: a manifest at the agreed location with the agreed
|
|
83
|
+
schema.
|
|
84
|
+
|
|
85
|
+
- **CampaignSpec:** pages that were AI-produced carry
|
|
86
|
+
`design_source.type === "ai-generated"` so doctor can produce a
|
|
87
|
+
matching error message ("AI run hasn't completed" vs "designer
|
|
88
|
+
hasn't exported").
|
|
89
|
+
- **Source HTML:** written by the AI agent into the source root,
|
|
90
|
+
with the same page-kit-ready conversion conventions documented in
|
|
91
|
+
[`source-adapters.md`](./source-adapters.md).
|
|
92
|
+
- **Manifest:** same shape as the figma-driven case
|
|
93
|
+
(`source-html-manifest/v0`), with `generator: "<agent>@<version>"`.
|
|
94
|
+
- **`prepare-build` behavior:** identical to figma-driven —
|
|
95
|
+
consumer doesn't care about provenance.
|
|
96
|
+
- **Doctor behavior:** when an AI-marked page has no source
|
|
97
|
+
mapping, the design_source-aware error variant for `ai-generated`
|
|
98
|
+
fires (recommends re-running the AI agent rather than re-exporting
|
|
99
|
+
from Figma).
|
|
100
|
+
- **When to use:** AI-generated landing/presell while
|
|
101
|
+
checkout/upsell stay on template stock. Pre-existing static HTML
|
|
102
|
+
pipelines that emit the manifest at handoff.
|
|
103
|
+
|
|
104
|
+
## Hand-authored
|
|
105
|
+
|
|
106
|
+
Designer or developer hand-writes the HTML directly. From the
|
|
107
|
+
consumer's view this is shaped identically to template-stock: spec
|
|
108
|
+
without `design_source`, no manifest, files on disk.
|
|
109
|
+
|
|
110
|
+
- **CampaignSpec:** `design_source` unset on the hand-authored
|
|
111
|
+
pages.
|
|
112
|
+
- **Source HTML:** the designer/dev places `.html` files in the
|
|
113
|
+
source root (any name; filesystem matching uses page-type slugs).
|
|
114
|
+
- **Manifest:** absent.
|
|
115
|
+
- **`prepare-build` behavior:** filesystem matching, same as
|
|
116
|
+
template-stock.
|
|
117
|
+
- **Doctor behavior:** generic coverage error for missing pages.
|
|
118
|
+
- **When to use:** one-off campaigns, presell article pages with
|
|
119
|
+
bespoke layouts, demos.
|
|
120
|
+
|
|
121
|
+
## Mixed
|
|
122
|
+
|
|
123
|
+
A single campaign with **per-page** entry points. Landing from
|
|
124
|
+
Figma export, checkout from template stock, presell from an AI run.
|
|
125
|
+
Per-page granularity is already in the schema (`design_source` is
|
|
126
|
+
per-page; `source_html.pages[]` is per-page).
|
|
127
|
+
|
|
128
|
+
- **CampaignSpec:** each page carries its own (or no)
|
|
129
|
+
`design_source`. There's no campaign-level "entry point" field —
|
|
130
|
+
per-page is the unit.
|
|
131
|
+
- **Source HTML:** one source root, with files placed by whichever
|
|
132
|
+
producer owns that page. Recommend per-page subdirectories
|
|
133
|
+
(`landing/`, `checkout/`, `presell/`) when producers don't
|
|
134
|
+
coordinate filenames.
|
|
135
|
+
- **Manifest:** one combined manifest at the source root listing
|
|
136
|
+
every page, with each entry's `path` pointing at its producer's
|
|
137
|
+
output location. Different producers write to different
|
|
138
|
+
subdirectories; the manifest unifies them. Manifest paths are
|
|
139
|
+
relative to the source root (`checkout/index.html`), and the
|
|
140
|
+
reference producer only auto-derives nested `index.html` files from
|
|
141
|
+
their parent directory. Use explicit `--page page_id=path` mappings
|
|
142
|
+
when multiple files would infer the same page id.
|
|
143
|
+
- **`prepare-build` behavior:** when a manifest is present it
|
|
144
|
+
governs every mapping — including pages whose path points into a
|
|
145
|
+
template-stock subdirectory. Operators with mixed sources should
|
|
146
|
+
always write a manifest rather than relying on filesystem
|
|
147
|
+
fallback (which is all-or-nothing). The manifest path remains source
|
|
148
|
+
provenance; `source_html.pages[].page_kit` carries the target Page Kit
|
|
149
|
+
file, route, CPK `page_type`, and frontmatter projection.
|
|
150
|
+
- **Doctor behavior:** per-page error variants fire based on each
|
|
151
|
+
page's `design_source.type`. A figma-marked landing page with no
|
|
152
|
+
manifest entry gets the Figma error; a template-stock checkout
|
|
153
|
+
page (no `design_source`) gets the generic error.
|
|
154
|
+
- **When to use:** the realistic case for most live campaigns once
|
|
155
|
+
the design-Figma + template-stock-commerce-surfaces split is
|
|
156
|
+
routine.
|
|
157
|
+
|
|
158
|
+
## Existing campaigns
|
|
159
|
+
|
|
160
|
+
A campaign repository that already exists — built or not, with or
|
|
161
|
+
without Campaigns OS artifacts — enters through
|
|
162
|
+
`campaigns-os standardize --target <repo>`, not through the build
|
|
163
|
+
pipeline. It is a read-only audit: it classifies each campaign root,
|
|
164
|
+
inventories source and runtime contracts, judges SDK versions and
|
|
165
|
+
checkout bindings against the bundled contracts, and, when a built
|
|
166
|
+
`_site` and a known template family exist, runs the built-output
|
|
167
|
+
doctor. The flags, exit codes and the slug-resolution rules are in
|
|
168
|
+
[campaign-standardization-report.md](./campaign-standardization-report.md).
|
|
169
|
+
Its findings name the entry point above the campaign should re-enter
|
|
170
|
+
through when repair is needed.
|
|
171
|
+
|
|
172
|
+
## Out of the consumer's hands
|
|
173
|
+
|
|
174
|
+
The build pipeline doesn't care which agent wrote the source HTML;
|
|
175
|
+
it only cares that the contract (filesystem location, manifest
|
|
176
|
+
shape, `design_source` shape) is honored. New entry points (Penpot,
|
|
177
|
+
Sketch, plain Markdown converters, etc.) slot in by:
|
|
178
|
+
|
|
179
|
+
1. Adding a `design_source.type` value (open-string, accepted today).
|
|
180
|
+
2. Emitting source HTML at the source root.
|
|
181
|
+
3. Emitting a `source-html-manifest/v0` manifest if the entry point
|
|
182
|
+
wants doctor's manifest-aware code path; otherwise relying on
|
|
183
|
+
filesystem fallback like template-stock and hand-authored.
|
|
184
|
+
|
|
185
|
+
No campaigns-os code change is required to add a new entry point if
|
|
186
|
+
the producer honors the existing contracts. Doctor's error wording
|
|
187
|
+
gets an additional `design_source.type` variant when a new entry
|
|
188
|
+
point ships, but that's a polish step, not a gate.
|
package/docs/local-setup.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
For a new campaign, create an empty working folder and run this from it:
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm init -y && npm install --save-exact next-campaign-page-kit@0.3.0 && npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.
|
|
6
|
+
npm init -y && npm install --save-exact next-campaign-page-kit@0.3.0 && npm install --save-dev --save-exact @nextcommerce/campaigns-os@1.61.0 && npx --no-install campaigns-os tooling setup --target . --platform claude
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
`npm init -y` gives the folder its own `package.json`. Without one, npm
|
|
@@ -26,7 +26,7 @@ Ledger schema id: `campaigns-os-release-ledger/v1`
|
|
|
26
26
|
Change policy version: `1.0.0`
|
|
27
27
|
Reason-code vocabulary version: `1.0.0`
|
|
28
28
|
Limits version: `1.0.0`
|
|
29
|
-
Supported surface at generation time: `1.
|
|
29
|
+
Supported surface at generation time: `1.61.0`
|
|
30
30
|
|
|
31
31
|
## Forward compatibility
|
|
32
32
|
|
package/docs/polish-evidence.md
CHANGED
|
@@ -54,7 +54,7 @@ per-field problem line.
|
|
|
54
54
|
|
|
55
55
|
| Field | Accepted shape (presence check) |
|
|
56
56
|
|---|---|
|
|
57
|
-
| `visual_review` | **Object
|
|
57
|
+
| `visual_review` | **Object.** `record polish` needs the package capture record `captures`, which every `polish capture` run at this version writes. That record must be package-produced, bound to the current build and intact, and every image it claims must be a full-page capture at the capture's own viewport width at pixel ratio 1. Every planned route must have at least one rendered build cell with a captured actual shot; an all-skipped or all-failed build side does not qualify. Screenshot paths written by an agent are not read. After upgrading, run `polish capture` again before `record polish`. A Polish record written by an earlier version, without `captures`, is still read as before: a `screenshots` array (aliases `screenshot_paths`, `paths`, `urls`) with at least one non-empty string. Never hand-author `page_load` or `captures`. |
|
|
58
58
|
| `brand_review` | Non-empty object (semantic checks in §3 apply: `favicon`, `brand_bleed`). |
|
|
59
59
|
| `checkout_review` | Non-empty object (semantic checks in §3 apply: `field_labels`, `bump_compare_price_rule`). |
|
|
60
60
|
| `template_residue_review` | Non-empty object/array/string (semantic check on `starter_favicon` in §3). |
|
|
@@ -121,6 +121,40 @@ The size check covers `<img>` elements in the page, including those inside open
|
|
|
121
121
|
|
|
122
122
|
**Readability.** `polish capture` also measures text contrast on every page under `_site/<slug>/` (up to 128 pages per run), at desktop (1440×1200) and mobile (390×844) widths, device pixel ratio 1, including campaigns whose pages are all template stock: the Polish route plan now covers the mapped pages plus every built page, while the page-load capture keeps its own page set. On a campaign with no mapped pages, `polish capture` measures readability, prints the page-load message as a warning, and exits successfully. The text it reads: purchase, accept and decline controls; submit buttons and submit-type inputs; SDK action controls; bundle cards (names, prices, badges and the card selected at load); order bumps; prices; checkout labels and placeholder hints; and other body text. Ratios follow WCAG 2.x and are compared unrounded: 4.5:1 for normal text, 3:1 for large text (at least 24 CSS px, or at least 18.66 CSS px at weight 700 or more; semibold does not qualify). Colours in any CSS Color 4 form the browser reports are converted to sRGB; values outside sRGB are clipped and marked. Backgrounds are composited through transparent layers. SVG text is measured with its fill colour, and placeholder hints with their own colour, size and weight. Gradients, background images, pseudo-element backgrounds, a `::first-line` or `::first-letter` style that changes the text's colour, background or opacity, overlapping positioned layers or images (and the shapes of an SVG drawing under its text), text clipped to a background, colours that cannot be read (including a text fill colour, and an SVG fill that is not a plain colour or is combined with a stroke), and controls marked disabled only by the SDK's `next-disabled` class produce a review result with a reason and a crop; so do opacity, filters, blend modes and masks on the text or on any element around it. `clip-path` is ignored. Disabled controls (`disabled` or `aria-disabled="true"`) are listed as excluded (inactive control) and are never accepted; `readonly` is not disabled. Polish measures the states present when the page loads — default, selected, active, and open panels — without clicking, scrolling or typing. Text that has not rendered, controls still loading, controls hidden at load, text supplied by CSS `content`, selection states and collapsed panels not present at load, text inside closed shadow roots or cross-origin frames, pages measured before the SDK, stylesheets or fonts were ready, and pages not captured are listed as unexercised. Overlapping layers are detected by box intersection: a layer the page paints over text in another way is not detected. A ratio under the requirement is a warning. Warnings never block Polish or change readiness. The probe reads the page without changing it, and adds at most about five minutes of work per run; pages it could not reach in that time are listed as unexercised. The stored measurements are re-derived on every read, and the page set, widths and campaign are checked against the current build; but the colours the browser reported are tamper-evident only, like other Polish captures. Readability and media weight results read as stale when the built output differs from the recorded build, even before a new `record build`.
|
|
123
123
|
|
|
124
|
+
**Element parity.** For each page built from provided source HTML, `polish capture` reads the source HTML render and the built page that the package capture already makes, with the same browser at the same width, height and pixel ratio (desktop 1440×1200 and mobile 390×844). On both it reads every visible element that holds text: its box, its text, its computed font family, size, weight, style, line height, letter spacing and text transform, and its text and background colour. Elements are matched by their text: exact matches that occur once on each side first, then the nearest exact match after removing the page's median offset, then text split across child elements, then close wording (at least 60% of words shared). A design text of four or more characters is reported missing only when it appears nowhere in the built page's visible text; where part of the built page could not be read (very long text, closed components, frames), it is listed for review instead. Differences become warnings when they exceed: 6 px for horizontal position, width and height, and for the vertical gap to the previous matched element (so one inserted band reports once, where the layout changed); 0.6 px font size; 2 px line height; 0.2 px letter spacing; 3 of 255 in any colour channel; any change of font family, weight, style or text transform. A web font that loaded for the source but not for the build is a warning even when the CSS names the same family. The font each glyph was actually drawn with is not read, and the handoff says so. Colour that cannot be measured on either side is listed for review. A source with no text, a source or build that could not be rendered or was not ready in time, a width or pixel-ratio mismatch (never resized), a source file that differs from the one recorded at intake, pages over the run's limits (1 500 elements per page, 32 pages, 200 font faces, about one minute of probing), and pages with unread parts are listed as unexercised or for review, never as a match. The probe's time counts against the package capture's own time budget. Both measurements are saved as local, git-ignored files, hashed and bound to the record. That makes an edit to part of the evidence detectable, but a process that rewrites the files and both records consistently is not detected. The source is measured again on every capture, so changed stylesheets or fonts are always seen; an unchanged source produces the same saved file. Warnings can be accepted with `checkpoint accept` and lapse when the measured values change; they never block Polish or change readiness. The comparison uses the HTML export as the design; a design file recorded for the page is not read by this version.
|
|
125
|
+
|
|
126
|
+
### Package captures and design comparison
|
|
127
|
+
|
|
128
|
+
After its existing page-load and readability work has finished and been recorded, `polish capture` takes full-page captures of every mapped page at desktop (1440×1200) and mobile (390×844), device pixel ratio 1, in a freshly loaded state with no scrolling or interaction. It also renders each page's source HTML from the source folder with the same browser at the same size. This visual pass uses its own browser instances, so its failures never change page-load or readability evidence.
|
|
129
|
+
|
|
130
|
+
The source is served read-only from a local address, in a browser instance that can reach only that address. Requests and WebSocket connections to anything else are blocked and counted, other connections are refused by the local server, and no host name is looked up. Nothing from the source render leaves the machine.
|
|
131
|
+
|
|
132
|
+
Each render waits for web fonts (up to 3 seconds) and for `[data-next-await]` elements to clear (up to 5 seconds) before either capture. The first capture is unchanged. The second differs only by its stabilization treatment: it stops animations and transitions, hides the caret, and masks SDK timers and prices. Timeouts are recorded, never hidden.
|
|
133
|
+
|
|
134
|
+
Images are stored under `.campaign-runtime/polish/captures/` and `.campaign-runtime/polish/diff/`, named by their SHA-256. Each folder gets its own `.gitignore` the first time an image is written, so the images stay out of the repository even in a campaign set up by an earlier version. Old images are not deleted.
|
|
135
|
+
|
|
136
|
+
Source screenshots recorded in the Design Source Package are opened and checked. The PNG structure is read without dependencies, and the image is fully decoded when the optional `pngjs` package is installed. An image whose declared size exceeds 17.28 million pixels (1440 × 12,000) is not decoded and is reported as not verifiable. Each screenshot is recorded as header-checked, decoded, or not verifiable, with its SHA-256 (computed when none was recorded) and identity.
|
|
137
|
+
|
|
138
|
+
A reference or source page that is the build's own output is reported as self-referential. That means a file inside `_site/` or `.campaign-runtime/`, a file identical to a Polish capture or a built page, or the campaign's own Page Kit page. Accepting that warning records the output as a revision baseline, not as proof that the page matches the design. A self-referential file that also fails verification cannot be accepted. A reference whose origin cannot be established is listed for review.
|
|
139
|
+
|
|
140
|
+
Compatible pairs are compared with the optional `pixelmatch` package:
|
|
141
|
+
- images are padded, never resized;
|
|
142
|
+
- width and height differences are reported;
|
|
143
|
+
- a heatmap is written;
|
|
144
|
+
- the most different areas are listed per section.
|
|
145
|
+
|
|
146
|
+
Sections are elements marked with `data-section`, or, when a page has none, `section`, `header` and `footer` elements with an `id`. Each section is cut from each image at its own position, so a taller section above it does not shift the comparison. A section missing or repeated on one side is reported as not comparable. Areas masked on either side are excluded from both sides. When a page is taller than the 12,000-pixel capture limit, the whole-page figure is not computed, and only sections inside both captures are compared.
|
|
147
|
+
|
|
148
|
+
Source screenshots are compared as whole images only, and only when their width and recorded `scale_factor` match the capture. Otherwise the comparison is reported as not supported. Their stabilization, state, crop and viewport height are not recorded, and each comparison says so.
|
|
149
|
+
|
|
150
|
+
The comparison is a diagnostic for a reviewer: it is listed for review, it never passes or fails Polish, and it never changes readiness. When `pngjs` or `pixelmatch` is not installed, captures are still taken and the comparison is listed as unavailable. Animations driven by script and content below the fold that loads on scroll are limits of the comparison. The visual pass adds at most five minutes per run, and pages it could not reach in that time are listed as unexercised.
|
|
151
|
+
|
|
152
|
+
The stored comparison figures are tamper-evident only, like other Polish captures. The images are re-checked on every read against their SHA-256 and against the pixel size recorded for them.
|
|
153
|
+
|
|
154
|
+
Template-stock pages are outside the mapped capture plan and produce no visual rows.
|
|
155
|
+
|
|
156
|
+
Hand-deleting `captures` reverts later reads to the legacy rule, but a new completed `record polish` still refuses. A fully reconstructed, internally consistent record can pass; the checksum is tamper evidence, not authorship proof. Pixel metrics and geometry are range-checked rather than recomputed on every read.
|
|
157
|
+
|
|
124
158
|
### Collector response records (the producer's wire form)
|
|
125
159
|
|
|
126
160
|
Inside the producer, the browser adapter's CDP collector emits one
|
|
@@ -469,6 +503,8 @@ deliberately). Waived runs pass with status `waived`, never silently.
|
|
|
469
503
|
|
|
470
504
|
## 6. Complete passing example
|
|
471
505
|
|
|
506
|
+
Run `polish capture` first to attach `page_load` and `captures`; the example omits these package-owned records. Screenshot paths below document review locations and cannot satisfy a new completed record.
|
|
507
|
+
|
|
472
508
|
The hand-authored portion below is completed first. Run `campaigns-os polish
|
|
473
509
|
capture` to attach the versioned `visual_review.page_load` object; that package
|
|
474
510
|
artifact is intentionally not reproduced as editable JSON here.
|
|
@@ -520,6 +556,8 @@ stack).
|
|
|
520
556
|
|
|
521
557
|
## 7. Recording with `record polish`
|
|
522
558
|
|
|
559
|
+
After upgrading, run `polish capture` again. A completed record requires its current `captures` record; leave it out of the evidence file so the captured value is carried forward. Existing records without `captures` retain the legacy read rule.
|
|
560
|
+
|
|
523
561
|
```bash
|
|
524
562
|
campaigns-os record polish --packet <campaign-runtime.build.json> --evidence <polish-evidence.json> [--report <json>] [--dry-run] [--json]
|
|
525
563
|
```
|
|
@@ -531,7 +569,7 @@ any other key is refused:
|
|
|
531
569
|
| Key | Required | Written to |
|
|
532
570
|
|---|---|---|
|
|
533
571
|
| `status` | no (default `completed`) | `stages.polish.status`; `completed`, `completed_with_warnings`, `blocked` or `skipped` |
|
|
534
|
-
| `evidence` | for `completed*` | `stages.polish.evidence`, the seven categories of §2. Leave out `visual_review.page_load`: the value `polish capture` recorded is kept, and a file that carries one is refused. Without it, a `blocked` or `skipped` record keeps the evidence already on the report. |
|
|
572
|
+
| `evidence` | for `completed*` | `stages.polish.evidence`, the seven categories of §2. Leave out `visual_review.page_load` and `visual_review.captures`: the value `polish capture` recorded is kept, and a file that carries one is refused. Without it, a `blocked` or `skipped` record keeps the evidence already on the report. |
|
|
535
573
|
| `blockers` | for `blocked` only | `stages.polish.blockers`: a non-empty array of `{"code", "message"}` objects. |
|
|
536
574
|
| `skip_reason` | for `skipped` only | `stages.polish.skip_reason`: a non-empty string. |
|
|
537
575
|
| `repair_loop_defect` | no | `report.theme.repair_loop_defect`: `null`, or the first brand-layer repair-loop defect as an object (`code`, `message`, `path`, `detail`). A non-null defect needs a recorded `report.theme`. |
|
|
@@ -381,6 +381,14 @@ default currency. The persisted verdict redacts every URL query, so the
|
|
|
381
381
|
currency is recorded as `evidence.currency` on the entry page's
|
|
382
382
|
`http:<page_id>` assertion; an entry URL that does not parse gets no currency,
|
|
383
383
|
and its row records `evidence.currency_not_applied` with the reason instead.
|
|
384
|
+
`--currency` sets the currency only. Test orders still ship to the address the
|
|
385
|
+
`--test-*` flags give, a US address by default (see
|
|
386
|
+
[Test customer and shipping address](#test-customer-and-shipping-address)). A
|
|
387
|
+
non-USD `--currency` on a run that places orders, with no `--test-country`,
|
|
388
|
+
prints a warning on stderr and the run goes ahead. Only the missing flag is
|
|
389
|
+
checked: a currency such as EUR covers many countries, so an explicit
|
|
390
|
+
`--test-country` is taken as meant. `qa run --site` takes `--currency` the same
|
|
391
|
+
way and tags the slug's entry page.
|
|
384
392
|
|
|
385
393
|
### Automatic commercial parity
|
|
386
394
|
|
|
@@ -1144,7 +1152,7 @@ What the diff asserts (BLOCKER unless noted):
|
|
|
1144
1152
|
For a real SDK 0.4 migration example that required typed-card post-purchase
|
|
1145
1153
|
traversal, persisted-order price verification, receipt-context analytics, and
|
|
1146
1154
|
independent order readback, see
|
|
1147
|
-
[SDK 0.4 Migration Proof Case Study](sdk04-migration-proof-case-study.md).
|
|
1155
|
+
[SDK 0.4 Migration Proof Case Study](https://github.com/NextCommerceCo/campaigns-os/blob/main/docs/sdk04-migration-proof-case-study.md).
|
|
1148
1156
|
|
|
1149
1157
|
## Parity capture (fixture-driven migration proof)
|
|
1150
1158
|
|
|
@@ -1959,6 +1967,41 @@ that died mid-submit. QA reports it on the path's `browser-test-order` row as
|
|
|
1959
1967
|
(or `--test-email`), or wait. The shipping address is not part of the match, so
|
|
1960
1968
|
changing `--test-address1` does not help.
|
|
1961
1969
|
|
|
1970
|
+
### Test customer and shipping address
|
|
1971
|
+
|
|
1972
|
+
The runner fills the checkout as one test customer. Every field has a default,
|
|
1973
|
+
and each has a flag:
|
|
1974
|
+
|
|
1975
|
+
| Flag | Default | Field |
|
|
1976
|
+
|---|---|---|
|
|
1977
|
+
| `--test-first-name` | `QA` | first name (shipping and billing) |
|
|
1978
|
+
| `--test-last-name` | `Playwright` | last name |
|
|
1979
|
+
| `--test-phone` | empty | phone, filled only when the checkout has the field |
|
|
1980
|
+
| `--test-email` | see above | email |
|
|
1981
|
+
| `--test-country` | `US` | country select (value or label, such as `GB`) |
|
|
1982
|
+
| `--test-address1` | `1600 Amphitheatre Pkwy` | street address |
|
|
1983
|
+
| `--test-city` | `Mountain View` | city |
|
|
1984
|
+
| `--test-province` | `CA` | state, province or region select (value or label) |
|
|
1985
|
+
| `--test-postal` | `94043` | postal code |
|
|
1986
|
+
|
|
1987
|
+
The billing fields get the same values when the checkout shows them. To order
|
|
1988
|
+
in another market, pass `--test-country` and all four address flags for a real
|
|
1989
|
+
address in that country, since the address defaults are a US one, and pass
|
|
1990
|
+
`--currency` for that market's currency: the country flag does not change the
|
|
1991
|
+
currency. A UK run looks like this:
|
|
1992
|
+
|
|
1993
|
+
```bash
|
|
1994
|
+
npm run campaigns-os -- qa run --packet campaign-runtime.build.json \
|
|
1995
|
+
--currency GBP --browser --test-order common \
|
|
1996
|
+
--test-country GB --test-address1 "<street>" --test-city "<city>" \
|
|
1997
|
+
--test-province "<option from the province select>" --test-postal "<postcode>"
|
|
1998
|
+
```
|
|
1999
|
+
|
|
2000
|
+
The province select is a required step. A checkout that shows no province
|
|
2001
|
+
select for the chosen country fails the path at that field
|
|
2002
|
+
(`Missing checkout select: province`); place those orders by hand on the
|
|
2003
|
+
market-pinned page (`?currency=<code>&country=<code>`).
|
|
2004
|
+
|
|
1962
2005
|
The browser driver intentionally behaves like a user:
|
|
1963
2006
|
|
|
1964
2007
|
- package selection uses rendered `[data-next-package-id]` controls when
|
|
@@ -2116,10 +2159,15 @@ npm run campaigns-os -- doctor --built ../my-campaign-repo --family arjuna --emi
|
|
|
2116
2159
|
|
|
2117
2160
|
# QA a built, served campaign with no packet/spec.
|
|
2118
2161
|
npm run campaigns-os -- qa run --site ../my-campaign-repo --base-url http://localhost:8080 --family arjuna --browser
|
|
2162
|
+
|
|
2163
|
+
# The same, entering the slug's first page in GBP.
|
|
2164
|
+
npm run campaigns-os -- qa run --site ../my-campaign-repo --base-url http://localhost:8080 --family arjuna --slug my-slug --currency GBP
|
|
2119
2165
|
```
|
|
2120
2166
|
|
|
2121
2167
|
`--family` is required (the residue gates need the family's brand contract).
|
|
2122
|
-
`--slug` selects the campaign when `_site/` holds more than one.
|
|
2168
|
+
`--slug` selects the campaign when `_site/` holds more than one. `--base-url`
|
|
2169
|
+
can be the served site origin or the campaign root: QA adds the campaign's slug
|
|
2170
|
+
when the URL does not already end in it, so pages resolve under `/<slug>/`. With no theme
|
|
2123
2171
|
artifacts the theme gate resolves to `not_applicable` (non-blocking), so the
|
|
2124
2172
|
placeholder-text blocker and the other residue gates still run. The emitted
|
|
2125
2173
|
minimal packet is marked `_synthesized` — it points doctor/QA at the built
|