@nextcommerce/campaigns-os 1.54.0 → 1.57.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 +148 -0
- package/CONTEXT.md +19 -5
- package/README.md +26 -12
- package/campaign-spec/README.md +15 -1
- package/campaign-spec/dist/checkout-flow.d.ts +90 -0
- package/campaign-spec/dist/checkout-flow.js +229 -0
- package/campaign-spec/dist/index.d.ts +1 -0
- package/campaign-spec/dist/index.js +6 -1
- package/campaign-spec/dist/normalize.d.ts +3 -2
- package/campaign-spec/dist/normalize.js +7 -5
- package/campaign-spec/dist/routing.d.ts +6 -3
- package/campaign-spec/dist/routing.js +6 -3
- package/campaign-spec/dist/rules/checkout-forward-target.d.ts +17 -0
- package/campaign-spec/dist/rules/checkout-forward-target.js +48 -0
- package/campaign-spec/dist/rules/checkout-step-reaches-checkout.d.ts +12 -0
- package/campaign-spec/dist/rules/checkout-step-reaches-checkout.js +37 -0
- package/campaign-spec/dist/rules/exit-intent-validation.js +2 -1
- package/campaign-spec/dist/rules/index.d.ts +4 -0
- package/campaign-spec/dist/rules/index.js +12 -0
- package/campaign-spec/dist/rules/one-checkout-per-path.d.ts +25 -0
- package/campaign-spec/dist/rules/one-checkout-per-path.js +84 -0
- package/campaign-spec/dist/rules/pre-payment-forward-target.d.ts +12 -0
- package/campaign-spec/dist/rules/pre-payment-forward-target.js +42 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.js +2 -1
- package/campaign-spec/dist/rules/schema-version.d.ts +9 -4
- package/campaign-spec/dist/rules/schema-version.js +10 -5
- package/campaign-spec/dist/types.d.ts +20 -3
- package/campaign-spec/dist/types.js +3 -2
- package/compatibility.json +2 -2
- package/contracts/fixtures/runtime-recipe/accept/current.json +6 -1
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +6 -1
- package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +1 -1
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +5 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +6 -1
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +6 -1
- package/contracts/release-ledger.json +1146 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +6 -1
- package/contracts/supported-surface.json +9 -5
- package/docs/brand-theme-bridge.md +10 -0
- package/docs/build-packet.md +86 -26
- package/docs/campaigns-os-build-flow.md +1 -1
- package/docs/effects.md +2 -5
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/qa-and-test-orders.md +133 -29
- package/docs/runtime-readiness.md +8 -3
- package/docs/skills-revision.md +10 -10
- package/docs/supported-surface.md +1 -1
- package/docs/versioning.md +4 -1
- package/docs/workflow-findings-sidecar.md +57 -2
- package/package.json +4 -1
- package/schemas/campaign-runtime-build-context.v0.schema.json +3 -1
- package/schemas/campaign-runtime-build-packet.v0.schema.json +3 -1
- package/schemas/campaign-spec.v5.schema.json +586 -0
- package/schemas/campaigns-os-run-record.v0.schema.json +20 -15
- package/skills/campaign-lifecycle-orientation/SKILL.md +10 -8
- 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 +6 -4
- package/skills/next-campaigns-os/SKILL.md +3 -3
- package/skills/next-campaigns-os-setup/SKILL.md +3 -3
- package/skills/next-campaigns-polish/SKILL.md +21 -6
- package/skills/next-campaigns-qa/SKILL.md +5 -4
- package/skills.json +10 -10
- package/src/adapter-decision-contract.mjs +27 -13
- package/src/brand-theme.mjs +44 -8
- package/src/build-brief.mjs +14 -5
- package/src/campaign-identity.mjs +33 -4
- package/src/cli-helpers.mjs +129 -0
- package/src/cli.mjs +156 -111
- package/src/commercial-journey.mjs +25 -2
- package/src/consent.mjs +3 -2
- package/src/design-source-package.mjs +1 -1
- package/src/design-source-publication.mjs +8 -8
- package/src/deviation.mjs +2 -1
- package/src/doctor/checks.mjs +212 -19
- package/src/doctor/inspect.mjs +4 -2
- package/src/doctor/next-step.mjs +46 -16
- package/src/doctor-sidecar.mjs +5 -3
- package/src/gate-actions.mjs +21 -1
- package/src/input-refresh.mjs +6 -6
- package/src/invocation.mjs +4 -3
- package/src/lifecycle.mjs +88 -6
- package/src/local-preview-policy.mjs +116 -12
- package/src/login.mjs +2 -1
- package/src/polish-browser.mjs +8 -2
- package/src/polish-node.mjs +9 -4
- package/src/polish-readability.mjs +2 -2
- package/src/qa-browser.mjs +741 -120
- package/src/qa-cart-entry.mjs +107 -20
- package/src/qa-node.mjs +179 -72
- package/src/qa-test-order-topology.mjs +118 -1
- package/src/qa-verdict-discovery.mjs +21 -4
- package/src/qa-verdict-publish.mjs +2 -1
- package/src/run-record.mjs +46 -34
- package/src/runtime-state-ignore.mjs +7 -5
- package/src/sdk-markup.mjs +41 -1
- package/src/source-html-intake.mjs +46 -4
- package/src/stage-ledger.mjs +18 -4
- package/src/stage-record.mjs +53 -16
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Preview policy: one place that decides which missing evidence a preview
|
|
2
|
+
// carries forward as a warning instead of a blocker.
|
|
3
3
|
//
|
|
4
4
|
// The local preview is a packet whose deploy target is `local-serve`, served
|
|
5
5
|
// from a loopback host. There a campaign must still prove its commerce (store
|
|
@@ -8,16 +8,20 @@
|
|
|
8
8
|
// evidence is reported as missing, never as passed: a carried-forward gate is
|
|
9
9
|
// a warning in doctor, `next` does not stop on it, and QA records it as a
|
|
10
10
|
// `warn` row, so the verdict is at best `ready_with_exceptions`. A hosted
|
|
11
|
-
// preview
|
|
12
|
-
//
|
|
11
|
+
// preview carries forward only the all-template no-capturable-routes
|
|
12
|
+
// shape and the missing Polish report/evidence it owns. QA against the
|
|
13
|
+
// packet's production URL stays strict. Every other check keeps its meaning.
|
|
13
14
|
//
|
|
14
15
|
// Doctor and QA apply this to the gates they evaluate; `next` reads doctor's
|
|
15
16
|
// gates. Recording a stage (`record polish`) and the waiver commands keep the
|
|
16
17
|
// strict gates; `record deploy` follows next past a carried-forward polish.
|
|
17
18
|
import { isLocalServePacket } from "./local-proof.mjs";
|
|
18
19
|
import { isLoopbackHostname } from "./remit.mjs";
|
|
20
|
+
import { hostedTemplateQaAction, HOSTED_TEMPLATE_PREVIEW_URL_ACTION } from "./gate-actions.mjs";
|
|
21
|
+
import { normalizePublicRouteSlug } from "./route-identity.mjs";
|
|
19
22
|
|
|
20
23
|
export const LOCAL_PREVIEW_POLICY = "local_preview";
|
|
24
|
+
export const HOSTED_TEMPLATE_PREVIEW_POLICY = "hosted_template_preview";
|
|
21
25
|
export const CARRIED_FORWARD = "carried_forward";
|
|
22
26
|
|
|
23
27
|
// The checks carried forward, one by one. Each is evidence the campaign does
|
|
@@ -30,6 +34,9 @@ const CARRIED_POLISH_CODES = Object.freeze(new Set([
|
|
|
30
34
|
"polish.report_missing",
|
|
31
35
|
"polish.evidence_missing",
|
|
32
36
|
]));
|
|
37
|
+
export function polishGateCanCarryPreview(gate) {
|
|
38
|
+
return Boolean(gate?.owned_checkpoint_only || CARRIED_POLISH_CODES.has(gate?.code));
|
|
39
|
+
}
|
|
33
40
|
export const NO_CAPTURABLE_ROUTES_CODE = "polish.hidden_eager_media.no_capturable_routes";
|
|
34
41
|
const MISSING_CAPTURE_CODE = "polish.hidden_eager_media.capture_malformed";
|
|
35
42
|
|
|
@@ -53,21 +60,96 @@ export function isLocalPreview(packet, { baseUrl = null } = {}) {
|
|
|
53
60
|
&& loopbackOrAbsent(baseUrl);
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
function comparableUrl(value) {
|
|
64
|
+
try {
|
|
65
|
+
const url = new URL(String(value));
|
|
66
|
+
if (!/^https?:$/.test(url.protocol)) return null;
|
|
67
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "") || "/"}`;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function ensureUrlTrailingSlash(value) {
|
|
74
|
+
try {
|
|
75
|
+
const url = new URL(value);
|
|
76
|
+
if (!url.pathname.endsWith("/")) url.pathname += "/";
|
|
77
|
+
return url.toString();
|
|
78
|
+
} catch {
|
|
79
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// QA composes a campaign slug onto a site root before testing it. Preview
|
|
84
|
+
// policy compares those same targets, ignoring only query and fragment.
|
|
85
|
+
export function normalizeQaBaseUrl(value, publicRouteSlug) {
|
|
86
|
+
const baseUrl = typeof value === "string" && value.trim() ? value.trim() : null;
|
|
87
|
+
if (!baseUrl) return null;
|
|
88
|
+
const slug = normalizePublicRouteSlug(publicRouteSlug);
|
|
89
|
+
if (!slug) return ensureUrlTrailingSlash(baseUrl);
|
|
90
|
+
try {
|
|
91
|
+
const url = new URL(ensureUrlTrailingSlash(baseUrl));
|
|
92
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
93
|
+
if (segments.at(-1) === slug) return ensureUrlTrailingSlash(url.toString());
|
|
94
|
+
return new URL(`${slug}/`, url).toString();
|
|
95
|
+
} catch {
|
|
96
|
+
return ensureUrlTrailingSlash(baseUrl);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function comparableQaTarget(value, packet) {
|
|
101
|
+
const slug = packet?.campaign?.public_route_slug || packet?.deploy?.live_url_path;
|
|
102
|
+
return comparableUrl(normalizeQaBaseUrl(value, slug));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isHostedTemplatePreview(packet, { baseUrl = null } = {}) {
|
|
106
|
+
const preview = comparableQaTarget(packet?.deploy?.preview_url, packet);
|
|
107
|
+
const production = comparableQaTarget(packet?.deploy?.production_url, packet);
|
|
108
|
+
const tested = baseUrl == null ? preview : comparableQaTarget(baseUrl, packet);
|
|
109
|
+
return Boolean(packet?.deploy?.target && packet.deploy.target !== "local-serve"
|
|
110
|
+
&& preview && tested === preview && preview !== production
|
|
111
|
+
&& !isLoopbackHostname(new URL(preview).hostname));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function hostedTemplateNeedsUsablePreview(packet, gate) {
|
|
115
|
+
return gate?.code === NO_CAPTURABLE_ROUTES_CODE
|
|
116
|
+
&& packet?.deploy?.target && packet.deploy.target !== "local-serve"
|
|
117
|
+
&& !isHostedTemplatePreview(packet);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function hostedTemplateNeedsPreviewUrl(packet, gate) {
|
|
121
|
+
return gate?.code === NO_CAPTURABLE_ROUTES_CODE
|
|
122
|
+
&& packet?.deploy?.target && packet.deploy.target !== "local-serve"
|
|
123
|
+
&& !packet.deploy.preview_url;
|
|
124
|
+
}
|
|
125
|
+
|
|
56
126
|
function pageLoadRecorded(report) {
|
|
57
127
|
return report?.stages?.polish?.evidence?.visual_review?.page_load != null;
|
|
58
128
|
}
|
|
59
129
|
|
|
60
|
-
function carried(gate) {
|
|
130
|
+
export function carried(gate, policy = LOCAL_PREVIEW_POLICY, packet = null) {
|
|
131
|
+
const previewUrl = packet?.deploy?.preview_url;
|
|
132
|
+
if (policy === HOSTED_TEMPLATE_PREVIEW_POLICY && !comparableUrl(previewUrl)) return gate;
|
|
61
133
|
return {
|
|
62
134
|
...gate,
|
|
63
135
|
status: CARRIED_FORWARD,
|
|
64
|
-
|
|
136
|
+
...(policy === HOSTED_TEMPLATE_PREVIEW_POLICY
|
|
137
|
+
? { required_actions: [hostedTemplateQaAction(previewUrl)] }
|
|
138
|
+
: {}),
|
|
139
|
+
carried_forward: { policy, from_status: gate.status, evidence: "missing" },
|
|
65
140
|
};
|
|
66
141
|
}
|
|
67
142
|
|
|
68
143
|
// The hidden eager-media checkpoint, evaluated on its own.
|
|
69
144
|
export function applyLocalPreviewToCheckpoint(gate, { packet, report, baseUrl = null } = {}) {
|
|
70
|
-
if (gate?.status !== "blocked"
|
|
145
|
+
if (gate?.status !== "blocked") return gate;
|
|
146
|
+
if (hostedTemplateNeedsUsablePreview(packet, gate)) {
|
|
147
|
+
return { ...gate, required_actions: [HOSTED_TEMPLATE_PREVIEW_URL_ACTION] };
|
|
148
|
+
}
|
|
149
|
+
if (gate.code === NO_CAPTURABLE_ROUTES_CODE && isHostedTemplatePreview(packet, { baseUrl })) {
|
|
150
|
+
return carried(gate, HOSTED_TEMPLATE_PREVIEW_POLICY, packet);
|
|
151
|
+
}
|
|
152
|
+
if (!isLocalPreview(packet, { baseUrl })) return gate;
|
|
71
153
|
const missing = gate.code === NO_CAPTURABLE_ROUTES_CODE
|
|
72
154
|
|| (gate.code === MISSING_CAPTURE_CODE && !pageLoadRecorded(report));
|
|
73
155
|
return missing ? carried(gate) : gate;
|
|
@@ -75,12 +157,31 @@ export function applyLocalPreviewToCheckpoint(gate, { packet, report, baseUrl =
|
|
|
75
157
|
|
|
76
158
|
// The polish gate folds the checkpoint in, so it is carried forward only when
|
|
77
159
|
// whatever it owns from the checkpoint was carried forward too.
|
|
160
|
+
export function polishCarriedForwardForLadder(report, gate) {
|
|
161
|
+
return gate?.status === CARRIED_FORWARD
|
|
162
|
+
&& !report?.theme?.waiver
|
|
163
|
+
&& !report?.stages?.polish?.evidence?.visual_review?.page_load;
|
|
164
|
+
}
|
|
165
|
+
|
|
78
166
|
export function applyLocalPreviewToPolishGate(gate, { packet, checkpointGate = null, baseUrl = null } = {}) {
|
|
79
|
-
if (gate?.status !== "blocked"
|
|
167
|
+
if (gate?.status !== "blocked") return gate;
|
|
168
|
+
// A template-stock map has no route that capture can measure, even when
|
|
169
|
+
// the preview is strict. Do not keep an impossible recapture action.
|
|
170
|
+
const actionableGate = checkpointGate?.code === NO_CAPTURABLE_ROUTES_CODE
|
|
171
|
+
? { ...gate, required_actions: (gate.required_actions || []).filter((action) => action.id !== "polish.hidden_eager_media.capture") }
|
|
172
|
+
: gate;
|
|
173
|
+
if (polishGateCanCarryPreview(gate) && hostedTemplateNeedsUsablePreview(packet, checkpointGate)) {
|
|
174
|
+
return { ...actionableGate, required_actions: [HOSTED_TEMPLATE_PREVIEW_URL_ACTION] };
|
|
175
|
+
}
|
|
176
|
+
const hostedTemplate = checkpointGate?.code === NO_CAPTURABLE_ROUTES_CODE
|
|
177
|
+
&& checkpointGate?.carried_forward?.policy === HOSTED_TEMPLATE_PREVIEW_POLICY
|
|
178
|
+
&& isHostedTemplatePreview(packet, { baseUrl });
|
|
179
|
+
if (!hostedTemplate && !isLocalPreview(packet, { baseUrl })) return actionableGate;
|
|
80
180
|
const checkpointCarried = checkpointGate?.status === CARRIED_FORWARD;
|
|
81
|
-
if (gate.owned_checkpoint_status === "blocked" && !checkpointCarried) return
|
|
82
|
-
|
|
83
|
-
|
|
181
|
+
if (gate.owned_checkpoint_status === "blocked" && !checkpointCarried) return actionableGate;
|
|
182
|
+
const policy = hostedTemplate ? HOSTED_TEMPLATE_PREVIEW_POLICY : LOCAL_PREVIEW_POLICY;
|
|
183
|
+
if (gate.owned_checkpoint_only) return checkpointCarried ? carried(actionableGate, policy, packet) : actionableGate;
|
|
184
|
+
return CARRIED_POLISH_CODES.has(gate.code) ? carried(actionableGate, policy, packet) : actionableGate;
|
|
84
185
|
}
|
|
85
186
|
|
|
86
187
|
export function starterResidueIsExpected(themeGate, { packet, baseUrl = null } = {}) {
|
|
@@ -88,5 +189,8 @@ export function starterResidueIsExpected(themeGate, { packet, baseUrl = null } =
|
|
|
88
189
|
}
|
|
89
190
|
|
|
90
191
|
export function carriedForwardMessage(gate) {
|
|
91
|
-
|
|
192
|
+
if (gate?.carried_forward?.policy === HOSTED_TEMPLATE_PREVIEW_POLICY) {
|
|
193
|
+
return `Carried forward on the hosted preview: ${gate.reason} This evidence is missing, not passed; production QA still requires it.`;
|
|
194
|
+
}
|
|
195
|
+
return `Carried forward on the local preview: ${gate.reason} This evidence is missing, not passed; production QA still requires it.`;
|
|
92
196
|
}
|
package/src/login.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createCredentialStore } from './credential-store.mjs';
|
|
2
|
+
import { timeOperatorWait } from './lifecycle.mjs';
|
|
2
3
|
|
|
3
4
|
export const GATEWAY = 'https://mcp.nextcommerce.com';
|
|
4
5
|
export const CLIENT_ID = 'campaigns-os-owned-store-pilot';
|
|
@@ -27,7 +28,7 @@ export function authenticationArguments(argv) {
|
|
|
27
28
|
async function askStore() {
|
|
28
29
|
const { createInterface } = await import('node:readline/promises');
|
|
29
30
|
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
-
try { return await prompt.question('Store subdomain (or network domain): '); }
|
|
31
|
+
try { return await timeOperatorWait(() => prompt.question('Store subdomain (or network domain): ')); }
|
|
31
32
|
finally { prompt.close(); }
|
|
32
33
|
}
|
|
33
34
|
const bindingFor = store => ({ gateway: GATEWAY, client_id: CLIENT_ID, store });
|
package/src/polish-browser.mjs
CHANGED
|
@@ -1012,6 +1012,10 @@ function polishBrowserMissing(kind, error) {
|
|
|
1012
1012
|
].join(" "));
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
1015
|
+
// `networkIdleTimeoutMs`, like the deadlines, can only shorten its bound: the
|
|
1016
|
+
// wait for network idle after each navigation (NETWORKIDLE_TIMEOUT_MS). The
|
|
1017
|
+
// CLI never passes it; a test whose page holds a transfer open passes a
|
|
1018
|
+
// shorter window rather than wait out the production one on every cell.
|
|
1015
1019
|
export async function createPolishBrowserAdapter({
|
|
1016
1020
|
headed = false,
|
|
1017
1021
|
authCookie = null,
|
|
@@ -1019,6 +1023,7 @@ export async function createPolishBrowserAdapter({
|
|
|
1019
1023
|
cellDeadlineMs,
|
|
1020
1024
|
cleanupDeadlineMs,
|
|
1021
1025
|
startupDeadlineMs,
|
|
1026
|
+
networkIdleTimeoutMs,
|
|
1022
1027
|
} = {}) {
|
|
1023
1028
|
const authCookies = parseAuthCookie(authCookie);
|
|
1024
1029
|
const boundedCellDeadlineMs = boundedPolishDeadline(cellDeadlineMs, POLISH_BROWSER_CELL_DEADLINE_MS);
|
|
@@ -1030,6 +1035,7 @@ export async function createPolishBrowserAdapter({
|
|
|
1030
1035
|
startupDeadlineMs,
|
|
1031
1036
|
POLISH_BROWSER_STARTUP_DEADLINE_MS,
|
|
1032
1037
|
);
|
|
1038
|
+
const networkIdleMs = boundedPolishDeadline(networkIdleTimeoutMs, NETWORKIDLE_TIMEOUT_MS);
|
|
1033
1039
|
let browser;
|
|
1034
1040
|
let startupTimedOut = false;
|
|
1035
1041
|
const startupAbort = new AbortController();
|
|
@@ -1234,7 +1240,7 @@ export async function createPolishBrowserAdapter({
|
|
|
1234
1240
|
const initialMediaElements = await awaitActive(collectMediaElements(page));
|
|
1235
1241
|
let networkidle;
|
|
1236
1242
|
try {
|
|
1237
|
-
await awaitActive(page.waitForLoadState("networkidle", { timeout:
|
|
1243
|
+
await awaitActive(page.waitForLoadState("networkidle", { timeout: networkIdleMs }));
|
|
1238
1244
|
networkidle = { status: "settled", duration_ms: durationSince(navigationStartedAt) };
|
|
1239
1245
|
} catch (error) {
|
|
1240
1246
|
if (!timeoutError(error)) throw error;
|
|
@@ -1327,7 +1333,7 @@ export async function createPolishBrowserAdapter({
|
|
|
1327
1333
|
if (initialTree === BOUND_ENDED) return unmeasured(boundEnded());
|
|
1328
1334
|
if (initialTree.error) return unmeasured("document_changed");
|
|
1329
1335
|
const initialMainFrame = initialTree.value?.frameTree?.frame;
|
|
1330
|
-
const idleTimeout = Math.max(1, Math.min(
|
|
1336
|
+
const idleTimeout = Math.max(1, Math.min(networkIdleMs, Math.floor(addedLeft()), Math.floor(cellLeftMs())));
|
|
1331
1337
|
const idle = await awaitActive(steps.step(() => page.waitForLoadState("networkidle", { timeout: idleTimeout })));
|
|
1332
1338
|
if (idle === BOUND_ENDED) return unmeasured(boundEnded());
|
|
1333
1339
|
if (idle.error && !timeoutError(idle.error)) throw idle.error;
|
package/src/polish-node.mjs
CHANGED
|
@@ -160,7 +160,7 @@ export function planPolishCapture({ packet, baseUrl } = {}) {
|
|
|
160
160
|
if (routes.length === 0) {
|
|
161
161
|
const error = new Error(
|
|
162
162
|
"polish capture has no mapped non-skipped routes to capture: every mapped page is template stock (skip_reason), so there is no design route to compare."
|
|
163
|
-
+ " On a local
|
|
163
|
+
+ " On a local or hosted preview, missing polish evidence is carried forward as a warning; run `next` for the next stage.",
|
|
164
164
|
);
|
|
165
165
|
error.code = NO_CAPTURABLE_ROUTES_ERROR;
|
|
166
166
|
throw error;
|
|
@@ -226,13 +226,13 @@ function recordedAuthorityBlock({ packet, report, plan = null, now } = {}) {
|
|
|
226
226
|
|
|
227
227
|
// Every mapped page is template stock: there is nothing for polish capture to
|
|
228
228
|
// measure, which is missing evidence rather than a malformed packet. It stays
|
|
229
|
-
// a non-waivable block; the
|
|
229
|
+
// a non-waivable block; the preview policy carries it forward.
|
|
230
230
|
function noCapturableRoutesBlock({ packet, report, now } = {}) {
|
|
231
231
|
const block = recordedAuthorityBlock({ packet, report, now });
|
|
232
232
|
return {
|
|
233
233
|
...block,
|
|
234
234
|
code: NO_CAPTURABLE_ROUTES_CODE,
|
|
235
|
-
reason: "Every mapped page is template stock (skip_reason), so
|
|
235
|
+
reason: "Every mapped page is template stock (skip_reason), so this build has no design route to compare and no page-load evidence.",
|
|
236
236
|
required_actions: [HIDDEN_EAGER_MEDIA_ACTIONS.map_design_route],
|
|
237
237
|
};
|
|
238
238
|
}
|
|
@@ -669,6 +669,7 @@ async function runPolishCapture({
|
|
|
669
669
|
captureCellDeadlineMs,
|
|
670
670
|
adapterCloseDeadlineMs,
|
|
671
671
|
probeClock = null,
|
|
672
|
+
mediaProbeRunBudgetMs,
|
|
672
673
|
targetRepo = null,
|
|
673
674
|
now = new Date(),
|
|
674
675
|
} = {}) {
|
|
@@ -687,6 +688,10 @@ async function runPolishCapture({
|
|
|
687
688
|
adapterCloseDeadlineMs,
|
|
688
689
|
POLISH_CAPTURE_CLOSE_DEADLINE_MS,
|
|
689
690
|
);
|
|
691
|
+
// The 1.3 image probe's run budget. Like the deadlines it can only shorten:
|
|
692
|
+
// the CLI never passes it, and a test that spends the budget passes a
|
|
693
|
+
// smaller one so that fewer slow cells spend it.
|
|
694
|
+
const probeRunBudgetMs = boundedPolishDeadline(mediaProbeRunBudgetMs, MEDIA_PROBE_LIMITS.runBudgetMs);
|
|
690
695
|
|
|
691
696
|
let adapter = null;
|
|
692
697
|
// Failure is tracked apart from its reason: a rejection may carry any value,
|
|
@@ -806,7 +811,7 @@ async function runPolishCapture({
|
|
|
806
811
|
const abortController = new AbortController();
|
|
807
812
|
const imageProbe = {
|
|
808
813
|
...(isProbeClock(probeClock) ? { clock: probeClock } : {}),
|
|
809
|
-
remainingMs:
|
|
814
|
+
remainingMs: probeRunBudgetMs - probeSpentMs,
|
|
810
815
|
cellBoundMs: MEDIA_PROBE_LIMITS.cellBoundMs,
|
|
811
816
|
imageCap: MEDIA_PROBE_LIMITS.imageCap,
|
|
812
817
|
};
|
|
@@ -433,8 +433,8 @@ export function readabilityProbe(toolkit, limits, closedRoots, generatedText) {
|
|
|
433
433
|
// ancestor matches. checkout_label and checkout_hint are read separately.
|
|
434
434
|
const ROLES = [
|
|
435
435
|
["add_to_cart", "[data-next-action=\"add-to-cart\"]"],
|
|
436
|
-
["upsell_accept", "[data-next-upsell-action=\"add\"]"],
|
|
437
|
-
["upsell_decline", "[data-next-upsell-action=\"skip\"]"],
|
|
436
|
+
["upsell_accept", "[data-next-upsell-action=\"add\"], [data-next-upsell-action=\"accept\"]"],
|
|
437
|
+
["upsell_decline", "[data-next-upsell-action=\"skip\"], [data-next-upsell-action=\"decline\"]"],
|
|
438
438
|
["submit_control", "button.submit-button[os-checkout-payment=\"combo\"], button[os-checkout-payment=\"combo\"], button[type=\"submit\"], input[type=\"submit\"], input[type=\"button\"]"],
|
|
439
439
|
["sdk_action", "[data-next-action]"],
|
|
440
440
|
["bundle_card", "[data-next-bundle-card], [data-next-selector-card], [data-next-package-id], [data-next-bundle-id]"],
|