autokap 1.0.7 → 1.0.8
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/assets/cursors/macos.svg +4 -0
- package/assets/cursors/windows.svg +15 -0
- package/assets/skill/OPCODE-REFERENCE.md +607 -0
- package/assets/skill/README.md +39 -0
- package/assets/skill/SKILL.md +453 -468
- package/assets/skill/STUDIO-SKILL.md +476 -0
- package/assets/skill/references/examples.md +104 -0
- package/assets/skill/references/interactive-demo.md +225 -0
- package/assets/skill/references/mock-data.md +178 -0
- package/dist/action-verifier.d.ts +29 -0
- package/dist/action-verifier.js +133 -0
- package/dist/agent-action-recovery.d.ts +45 -0
- package/dist/agent-action-recovery.js +370 -0
- package/dist/agent-message-utils.d.ts +21 -0
- package/dist/agent-message-utils.js +77 -0
- package/dist/agent-url-utils.d.ts +30 -0
- package/dist/agent-url-utils.js +138 -0
- package/dist/agent.d.ts +92 -8
- package/dist/agent.js +2936 -781
- package/dist/ak-tree.d.ts +39 -0
- package/dist/ak-tree.js +368 -0
- package/dist/alt-text.d.ts +26 -0
- package/dist/alt-text.js +55 -0
- package/dist/auth-capture.d.ts +17 -0
- package/dist/auth-capture.js +164 -0
- package/dist/benchmark.d.ts +59 -0
- package/dist/benchmark.js +135 -0
- package/dist/browser-bar.d.ts +14 -6
- package/dist/browser-bar.js +145 -8
- package/dist/browser-pool.d.ts +7 -0
- package/dist/browser-pool.js +15 -5
- package/dist/browser-utils.d.ts +31 -0
- package/dist/browser-utils.js +97 -0
- package/dist/browser.d.ts +51 -1
- package/dist/browser.js +1481 -31
- package/dist/capture-alt-text.js +2 -1
- package/dist/capture-language-preflight.js +14 -0
- package/dist/capture-llm-page-identity.js +22 -10
- package/dist/capture-page-identity.d.ts +5 -7
- package/dist/capture-page-identity.js +211 -78
- package/dist/capture-preset-credentials.d.ts +50 -0
- package/dist/capture-preset-credentials.js +127 -0
- package/dist/capture-request-plan.d.ts +2 -2
- package/dist/capture-request-plan.js +64 -16
- package/dist/capture-run-optimizer.js +48 -33
- package/dist/capture-selector-memory.d.ts +5 -0
- package/dist/capture-selector-memory.js +18 -0
- package/dist/capture-strategy.d.ts +36 -0
- package/dist/capture-strategy.js +95 -0
- package/dist/capture-studio-sync.d.ts +1 -0
- package/dist/capture-studio-sync.js +9 -3
- package/dist/capture-surface-contract.d.ts +36 -0
- package/dist/capture-surface-contract.js +299 -0
- package/dist/capture-transition-engine.d.ts +28 -0
- package/dist/capture-transition-engine.js +292 -0
- package/dist/capture-variant-state.d.ts +2 -0
- package/dist/capture-variant-state.js +26 -0
- package/dist/capture-verification.d.ts +35 -0
- package/dist/capture-verification.js +95 -0
- package/dist/capture-viewport-lock.d.ts +48 -0
- package/dist/capture-viewport-lock.js +74 -0
- package/dist/circuit-breaker.d.ts +42 -0
- package/dist/circuit-breaker.js +119 -0
- package/dist/cli-config.d.ts +8 -1
- package/dist/cli-config.js +62 -6
- package/dist/cli-contract.d.ts +15 -0
- package/dist/cli-contract.js +167 -0
- package/dist/cli-runner-local.d.ts +12 -0
- package/dist/cli-runner-local.js +102 -0
- package/dist/cli-runner.d.ts +34 -0
- package/dist/cli-runner.js +433 -0
- package/dist/cli-utils.d.ts +0 -1
- package/dist/cli-utils.js +2 -5
- package/dist/cli.js +1005 -267
- package/dist/clip-orchestrator.js +9 -2
- package/dist/clip-postprocess.js +25 -16
- package/dist/cookie-dismiss.d.ts +2 -0
- package/dist/cookie-dismiss.js +48 -13
- package/dist/cost-logging.d.ts +8 -0
- package/dist/cost-logging.js +160 -46
- package/dist/cost-resolution-monitor.d.ts +16 -0
- package/dist/cost-resolution-monitor.js +34 -0
- package/dist/credential-templates.js +2 -2
- package/dist/cursor-overlay-script.d.ts +6 -0
- package/dist/cursor-overlay-script.js +169 -0
- package/dist/dom-css-purger.d.ts +65 -0
- package/dist/dom-css-purger.js +333 -0
- package/dist/dom-font-inliner.d.ts +45 -0
- package/dist/dom-font-inliner.js +148 -0
- package/dist/dom-patch-resolver.d.ts +52 -0
- package/dist/dom-patch-resolver.js +242 -0
- package/dist/dom-serializer.d.ts +82 -0
- package/dist/dom-serializer.js +378 -0
- package/dist/element-capture.d.ts +1 -41
- package/dist/element-capture.js +202 -446
- package/dist/env-validation.d.ts +5 -0
- package/dist/env-validation.js +29 -0
- package/dist/execution-schema.d.ts +4423 -0
- package/dist/execution-schema.js +507 -0
- package/dist/execution-types.d.ts +886 -0
- package/dist/execution-types.js +65 -0
- package/dist/fonts-loader.d.ts +14 -0
- package/dist/fonts-loader.js +55 -0
- package/dist/hybrid-navigator.js +12 -12
- package/dist/index.d.ts +9 -6
- package/dist/index.js +10 -4
- package/dist/legacy/agent-action-recovery.d.ts +45 -0
- package/dist/legacy/agent-action-recovery.js +370 -0
- package/dist/legacy/agent-message-utils.d.ts +21 -0
- package/dist/legacy/agent-message-utils.js +77 -0
- package/dist/legacy/agent-url-utils.d.ts +30 -0
- package/dist/legacy/agent-url-utils.js +138 -0
- package/dist/legacy/agent.d.ts +226 -0
- package/dist/legacy/agent.js +6666 -0
- package/dist/legacy/clip-orchestrator.d.ts +148 -0
- package/dist/legacy/clip-orchestrator.js +957 -0
- package/dist/legacy/credential-templates.d.ts +5 -0
- package/dist/legacy/credential-templates.js +60 -0
- package/dist/legacy/hybrid-navigator.d.ts +138 -0
- package/dist/legacy/hybrid-navigator.js +468 -0
- package/dist/legacy/llm-usage.d.ts +17 -0
- package/dist/legacy/llm-usage.js +45 -0
- package/dist/legacy/prompt-cache.d.ts +10 -0
- package/dist/legacy/prompt-cache.js +24 -0
- package/dist/legacy/prompts.d.ts +175 -0
- package/dist/legacy/prompts.js +1038 -0
- package/dist/legacy/tools.d.ts +4 -0
- package/dist/legacy/tools.js +216 -0
- package/dist/legacy/video-agent.d.ts +143 -0
- package/dist/legacy/video-agent.js +4788 -0
- package/dist/legacy/video-observation.d.ts +36 -0
- package/dist/legacy/video-observation.js +192 -0
- package/dist/legacy/video-planner.d.ts +12 -0
- package/dist/legacy/video-planner.js +501 -0
- package/dist/legacy/video-prompts.d.ts +37 -0
- package/dist/legacy/video-prompts.js +569 -0
- package/dist/legacy/video-tools.d.ts +3 -0
- package/dist/legacy/video-tools.js +59 -0
- package/dist/legacy/video-variant-state.d.ts +29 -0
- package/dist/legacy/video-variant-state.js +80 -0
- package/dist/legacy/vision-model.d.ts +17 -0
- package/dist/legacy/vision-model.js +74 -0
- package/dist/llm-healer.d.ts +63 -0
- package/dist/llm-healer.js +166 -0
- package/dist/llm-provider.d.ts +29 -0
- package/dist/llm-provider.js +80 -0
- package/dist/logger.d.ts +6 -2
- package/dist/logger.js +15 -1
- package/dist/mockup-html.js +35 -25
- package/dist/mockup.d.ts +95 -2
- package/dist/mockup.js +427 -166
- package/dist/mouse-animation.d.ts +2 -2
- package/dist/mouse-animation.js +34 -20
- package/dist/opcode-actions.d.ts +42 -0
- package/dist/opcode-actions.js +511 -0
- package/dist/opcode-runner.d.ts +51 -0
- package/dist/opcode-runner.js +770 -0
- package/dist/openrouter-client.d.ts +40 -0
- package/dist/openrouter-client.js +16 -0
- package/dist/overlay-engine.d.ts +24 -0
- package/dist/overlay-engine.js +176 -0
- package/dist/postcondition.d.ts +16 -0
- package/dist/postcondition.js +269 -0
- package/dist/program-patcher.d.ts +25 -0
- package/dist/program-patcher.js +44 -0
- package/dist/prompts.d.ts +13 -5
- package/dist/prompts.js +224 -351
- package/dist/provider-config.d.ts +12 -0
- package/dist/provider-config.js +15 -0
- package/dist/recovery-chain.d.ts +37 -0
- package/dist/recovery-chain.js +350 -0
- package/dist/remote-browser.d.ts +28 -4
- package/dist/remote-browser.js +60 -5
- package/dist/safari-browser-bar.d.ts +15 -0
- package/dist/safari-browser-bar.js +95 -0
- package/dist/safari-toolbar-asset.d.ts +15 -0
- package/dist/safari-toolbar-asset.js +12 -0
- package/dist/security.d.ts +2 -1
- package/dist/security.js +49 -10
- package/dist/selector-resolver.d.ts +34 -0
- package/dist/selector-resolver.js +181 -0
- package/dist/semantic-resolver.d.ts +35 -0
- package/dist/semantic-resolver.js +161 -0
- package/dist/server-capture-runtime.d.ts +5 -3
- package/dist/server-capture-runtime.js +42 -95
- package/dist/server-credit-usage.d.ts +2 -2
- package/dist/server-project-webhooks.d.ts +15 -1
- package/dist/server-project-webhooks.js +34 -8
- package/dist/server-screenshot-watermark.js +27 -5
- package/dist/session-profile.js +164 -1
- package/dist/sf-pro-symbols.d.ts +1 -0
- package/dist/sf-pro-symbols.js +55 -0
- package/dist/skill-packaging.d.ts +28 -0
- package/dist/skill-packaging.js +169 -0
- package/dist/smart-wait.d.ts +27 -0
- package/dist/smart-wait.js +81 -0
- package/dist/status-bar-render.d.ts +20 -0
- package/dist/status-bar-render.js +410 -0
- package/dist/status-bar.d.ts +9 -0
- package/dist/status-bar.js +298 -14
- package/dist/svg-browser-bar.d.ts +33 -0
- package/dist/svg-browser-bar.js +206 -0
- package/dist/svg-status-bar.d.ts +36 -0
- package/dist/svg-status-bar.js +597 -0
- package/dist/svg-text.d.ts +61 -0
- package/dist/svg-text.js +118 -0
- package/dist/tools.js +89 -451
- package/dist/types.d.ts +240 -5
- package/dist/types.js +23 -1
- package/dist/v2/action-verifier.d.ts +29 -0
- package/dist/v2/action-verifier.js +133 -0
- package/dist/v2/alt-text.d.ts +26 -0
- package/dist/v2/alt-text.js +55 -0
- package/dist/v2/benchmark.d.ts +59 -0
- package/dist/v2/benchmark.js +135 -0
- package/dist/v2/capture-strategy.d.ts +30 -0
- package/dist/v2/capture-strategy.js +67 -0
- package/dist/v2/capture-verification.d.ts +35 -0
- package/dist/v2/capture-verification.js +95 -0
- package/dist/v2/circuit-breaker.d.ts +42 -0
- package/dist/v2/circuit-breaker.js +119 -0
- package/dist/v2/cli-runner-local.d.ts +11 -0
- package/dist/v2/cli-runner-local.js +91 -0
- package/dist/v2/cli-runner.d.ts +34 -0
- package/dist/v2/cli-runner.js +300 -0
- package/dist/v2/compiler-prompts.d.ts +27 -0
- package/dist/v2/compiler-prompts.js +123 -0
- package/dist/v2/compiler.d.ts +37 -0
- package/dist/v2/compiler.js +147 -0
- package/dist/v2/explorer.d.ts +41 -0
- package/dist/v2/explorer.js +56 -0
- package/dist/v2/index.d.ts +37 -0
- package/dist/v2/index.js +31 -0
- package/dist/v2/llm-healer.d.ts +62 -0
- package/dist/v2/llm-healer.js +166 -0
- package/dist/v2/llm-provider.d.ts +29 -0
- package/dist/v2/llm-provider.js +80 -0
- package/dist/v2/opcode-runner.d.ts +47 -0
- package/dist/v2/opcode-runner.js +634 -0
- package/dist/v2/overlay-engine.d.ts +24 -0
- package/dist/v2/overlay-engine.js +150 -0
- package/dist/v2/postcondition.d.ts +16 -0
- package/dist/v2/postcondition.js +249 -0
- package/dist/v2/program-patcher.d.ts +25 -0
- package/dist/v2/program-patcher.js +44 -0
- package/dist/v2/recovery-chain.d.ts +30 -0
- package/dist/v2/recovery-chain.js +368 -0
- package/dist/v2/schema.d.ts +2580 -0
- package/dist/v2/schema.js +295 -0
- package/dist/v2/selector-resolver.d.ts +34 -0
- package/dist/v2/selector-resolver.js +181 -0
- package/dist/v2/semantic-resolver.d.ts +35 -0
- package/dist/v2/semantic-resolver.js +161 -0
- package/dist/v2/smart-wait.d.ts +27 -0
- package/dist/v2/smart-wait.js +81 -0
- package/dist/v2/types.d.ts +444 -0
- package/dist/v2/types.js +19 -0
- package/dist/v2/web-playwright-local.d.ts +69 -0
- package/dist/v2/web-playwright-local.js +392 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/video-agent.js +18 -13
- package/dist/video-planner.js +2 -1
- package/dist/video-prompts.js +3 -3
- package/dist/web-playwright-local.d.ts +126 -0
- package/dist/web-playwright-local.js +819 -0
- package/dist/ws-auth.js +4 -1
- package/dist/ws-broadcast.d.ts +34 -0
- package/dist/ws-broadcast.js +85 -0
- package/dist/ws-connection-limits.d.ts +12 -0
- package/dist/ws-connection-limits.js +44 -0
- package/dist/ws-handler-utils.d.ts +32 -0
- package/dist/ws-handler-utils.js +139 -0
- package/dist/ws-handler.js +294 -164
- package/dist/ws-metrics-server.d.ts +9 -0
- package/dist/ws-metrics-server.js +31 -0
- package/dist/ws-server.js +41 -1
- package/package.json +51 -34
|
@@ -6,8 +6,8 @@ export interface BezierMoveOptions {
|
|
|
6
6
|
steps?: number;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Move the mouse from `from` to `to` along a cubic Bezier curve with
|
|
10
|
-
*
|
|
9
|
+
* Move the mouse from `from` to `to` along a cubic Bezier curve with
|
|
10
|
+
* smooth controlled motion: ease-in-out timing, gentle proportional curves.
|
|
11
11
|
*/
|
|
12
12
|
export declare function moveMouse(page: Page, from: {
|
|
13
13
|
x: number;
|
package/dist/mouse-animation.js
CHANGED
|
@@ -14,8 +14,8 @@ function randomOffset(scale) {
|
|
|
14
14
|
return (Math.random() - 0.5) * 2 * scale;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Move the mouse from `from` to `to` along a cubic Bezier curve with
|
|
18
|
-
*
|
|
17
|
+
* Move the mouse from `from` to `to` along a cubic Bezier curve with
|
|
18
|
+
* smooth controlled motion: ease-in-out timing, gentle proportional curves.
|
|
19
19
|
*/
|
|
20
20
|
export async function moveMouse(page, from, to, options = {}) {
|
|
21
21
|
const dx = to.x - from.x;
|
|
@@ -23,35 +23,49 @@ export async function moveMouse(page, from, to, options = {}) {
|
|
|
23
23
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
24
24
|
if (distance < 2)
|
|
25
25
|
return; // Already there
|
|
26
|
-
const
|
|
27
|
-
const
|
|
26
|
+
const durationMs = options.durationMs ?? Math.min(700, Math.max(80, Math.sqrt(distance) * 28));
|
|
27
|
+
const steps = options.steps ?? Math.max(4, Math.ceil(durationMs / 16));
|
|
28
28
|
const msPerStep = durationMs / steps;
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
x: from.x + dx * 0.
|
|
34
|
-
y: from.y + dy * 0.
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
// Control points: straight line for short moves, gentle proportional arc for longer ones.
|
|
30
|
+
let p1;
|
|
31
|
+
let p2;
|
|
32
|
+
if (distance < 30) {
|
|
33
|
+
p1 = { x: from.x + dx * 0.33, y: from.y + dy * 0.33 };
|
|
34
|
+
p2 = { x: from.x + dx * 0.67, y: from.y + dy * 0.67 };
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const perpScale = Math.min(distance * 0.12, 60);
|
|
38
|
+
p1 = {
|
|
39
|
+
x: from.x + dx * 0.25 + randomOffset(perpScale),
|
|
40
|
+
y: from.y + dy * 0.25 + randomOffset(perpScale),
|
|
41
|
+
};
|
|
42
|
+
p2 = {
|
|
43
|
+
x: from.x + dx * 0.75 + randomOffset(perpScale),
|
|
44
|
+
y: from.y + dy * 0.75 + randomOffset(perpScale),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
40
47
|
for (let i = 1; i <= steps; i++) {
|
|
41
48
|
const linearT = i / steps;
|
|
42
49
|
const t = easeInOut(linearT);
|
|
43
50
|
const point = cubicBezier(from, p1, p2, to, t);
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
await page.
|
|
51
|
+
const mx = Math.round(point.x);
|
|
52
|
+
const my = Math.round(point.y);
|
|
53
|
+
await page.mouse.move(mx, my);
|
|
54
|
+
// CDP mouse.move doesn't fire DOM mousemove — update overlay directly
|
|
55
|
+
await page.evaluate(({ x, y }) => {
|
|
56
|
+
if (typeof window.__akMoveCursor === 'function')
|
|
57
|
+
window.__akMoveCursor(x, y);
|
|
58
|
+
}, { x: mx, y: my }).catch(() => { });
|
|
49
59
|
if (msPerStep > 1) {
|
|
50
60
|
await page.waitForTimeout(msPerStep);
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
// Final move to exact destination (no jitter)
|
|
54
64
|
await page.mouse.move(to.x, to.y);
|
|
65
|
+
await page.evaluate(({ x, y }) => {
|
|
66
|
+
if (typeof window.__akMoveCursor === 'function')
|
|
67
|
+
window.__akMoveCursor(x, y);
|
|
68
|
+
}, { x: Math.round(to.x), y: Math.round(to.y) }).catch(() => { });
|
|
55
69
|
}
|
|
56
70
|
/**
|
|
57
71
|
* Move the mouse to `target` with Bezier curve animation and click.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture Agent — Shared Opcode Actions
|
|
3
|
+
*
|
|
4
|
+
* Deterministic action dispatch shared by the main opcode runner and the
|
|
5
|
+
* recovery chain so both paths execute the same behavior.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExecutionOpcode, MockDataGroup, RuntimeAdapter, VariantSpec } from './execution-types.js';
|
|
8
|
+
export interface OpcodeActionContext {
|
|
9
|
+
currentVariant?: VariantSpec;
|
|
10
|
+
/** Mock data groups available to INJECT_MOCK_DATA. Plumbed from ExecutionProgram. */
|
|
11
|
+
mockDataGroups?: MockDataGroup[];
|
|
12
|
+
/**
|
|
13
|
+
* Live credentials used to substitute {{email}}/{{password}}/{{loginUrl}}
|
|
14
|
+
* placeholders in TYPE / NAVIGATE opcodes. Plumbed from
|
|
15
|
+
* `program.preconditions.credentials`.
|
|
16
|
+
*/
|
|
17
|
+
credentials?: {
|
|
18
|
+
email?: string;
|
|
19
|
+
password?: string;
|
|
20
|
+
loginUrl?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Substitute credential placeholders inside opcode text fields.
|
|
25
|
+
* Only the {{email}}, {{password}} and {{loginUrl}} tokens are replaced.
|
|
26
|
+
* No-op if no credentials are present or the text contains no placeholder.
|
|
27
|
+
*/
|
|
28
|
+
export declare function substituteCredentialPlaceholders(text: string, credentials?: OpcodeActionContext['credentials']): string;
|
|
29
|
+
/**
|
|
30
|
+
* Returns the list of credential placeholders (`{{email}}`, `{{password}}`,
|
|
31
|
+
* `{{loginUrl}}`) that the input string references but the provided
|
|
32
|
+
* `credentials` object does NOT have a non-empty value for. Used by the
|
|
33
|
+
* opcode dispatcher to fail loudly with a clear message when a NAVIGATE /
|
|
34
|
+
* TYPE / etc. would otherwise resolve to an empty string and crash
|
|
35
|
+
* Playwright with a confusing "Cannot navigate to invalid URL" error.
|
|
36
|
+
*/
|
|
37
|
+
export declare function findUnresolvedCredentialPlaceholders(text: string, credentials?: OpcodeActionContext['credentials']): string[];
|
|
38
|
+
export interface OpcodeActionResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function executeOpcodeCoreAction(opcode: ExecutionOpcode, adapter: RuntimeAdapter, context?: OpcodeActionContext): Promise<OpcodeActionResult>;
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture Agent — Shared Opcode Actions
|
|
3
|
+
*
|
|
4
|
+
* Deterministic action dispatch shared by the main opcode runner and the
|
|
5
|
+
* recovery chain so both paths execute the same behavior.
|
|
6
|
+
*/
|
|
7
|
+
import { VARIANT_PLACEHOLDER } from './execution-types.js';
|
|
8
|
+
import { dismissAllOverlays } from './overlay-engine.js';
|
|
9
|
+
/**
|
|
10
|
+
* Substitute credential placeholders inside opcode text fields.
|
|
11
|
+
* Only the {{email}}, {{password}} and {{loginUrl}} tokens are replaced.
|
|
12
|
+
* No-op if no credentials are present or the text contains no placeholder.
|
|
13
|
+
*/
|
|
14
|
+
export function substituteCredentialPlaceholders(text, credentials) {
|
|
15
|
+
if (typeof text !== 'string' || !text.includes('{{')) {
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
return text
|
|
19
|
+
.replaceAll('{{email}}', credentials?.email ?? '')
|
|
20
|
+
.replaceAll('{{password}}', credentials?.password ?? '')
|
|
21
|
+
.replaceAll('{{loginUrl}}', credentials?.loginUrl ?? '');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Returns the list of credential placeholders (`{{email}}`, `{{password}}`,
|
|
25
|
+
* `{{loginUrl}}`) that the input string references but the provided
|
|
26
|
+
* `credentials` object does NOT have a non-empty value for. Used by the
|
|
27
|
+
* opcode dispatcher to fail loudly with a clear message when a NAVIGATE /
|
|
28
|
+
* TYPE / etc. would otherwise resolve to an empty string and crash
|
|
29
|
+
* Playwright with a confusing "Cannot navigate to invalid URL" error.
|
|
30
|
+
*/
|
|
31
|
+
export function findUnresolvedCredentialPlaceholders(text, credentials) {
|
|
32
|
+
if (typeof text !== 'string' || !text.includes('{{'))
|
|
33
|
+
return [];
|
|
34
|
+
const missing = [];
|
|
35
|
+
if (text.includes('{{email}}') && !credentials?.email?.trim()) {
|
|
36
|
+
missing.push('{{email}}');
|
|
37
|
+
}
|
|
38
|
+
if (text.includes('{{password}}') && !credentials?.password) {
|
|
39
|
+
missing.push('{{password}}');
|
|
40
|
+
}
|
|
41
|
+
if (text.includes('{{loginUrl}}') && !credentials?.loginUrl?.trim()) {
|
|
42
|
+
missing.push('{{loginUrl}}');
|
|
43
|
+
}
|
|
44
|
+
return missing;
|
|
45
|
+
}
|
|
46
|
+
export async function executeOpcodeCoreAction(opcode, adapter, context = {}) {
|
|
47
|
+
try {
|
|
48
|
+
switch (opcode.kind) {
|
|
49
|
+
case 'NAVIGATE': {
|
|
50
|
+
const missing = findUnresolvedCredentialPlaceholders(opcode.url, context.credentials);
|
|
51
|
+
if (missing.length > 0) {
|
|
52
|
+
throw new Error(`NAVIGATE url contains placeholder(s) ${missing.join(', ')} but the preset's credentials are missing the matching value(s). ` +
|
|
53
|
+
`Open the preset editor and fill in the credentials (${missing.map((p) => p.replace(/[{}]/g, '')).join(', ')}), or remove the placeholder from the program.`);
|
|
54
|
+
}
|
|
55
|
+
const url = substituteCredentialPlaceholders(opcode.url, context.credentials);
|
|
56
|
+
if (!url.trim()) {
|
|
57
|
+
throw new Error(`NAVIGATE url resolved to an empty string (original: "${opcode.url}")`);
|
|
58
|
+
}
|
|
59
|
+
await adapter.navigate(url);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'DISMISS_OVERLAYS':
|
|
63
|
+
await dismissAllOverlays(adapter);
|
|
64
|
+
break;
|
|
65
|
+
case 'CLICK':
|
|
66
|
+
try {
|
|
67
|
+
await adapter.click(opcode.selector, opcode.button ? { button: opcode.button } : undefined);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (!opcode.target || !adapter.clickByTarget)
|
|
71
|
+
throw error;
|
|
72
|
+
await adapter.clickByTarget({
|
|
73
|
+
selector: opcode.selector,
|
|
74
|
+
target: opcode.target,
|
|
75
|
+
selectorAlternates: opcode.selectorAlternates,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
case 'TYPE': {
|
|
80
|
+
const rawText = (opcode.textByLocale && context.currentVariant?.locale
|
|
81
|
+
? opcode.textByLocale[context.currentVariant.locale] ?? opcode.text
|
|
82
|
+
: opcode.text);
|
|
83
|
+
const text = substituteCredentialPlaceholders(rawText, context.credentials);
|
|
84
|
+
try {
|
|
85
|
+
await adapter.type(opcode.selector, text, opcode.clearFirst);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (!opcode.target || !adapter.typeByTarget)
|
|
89
|
+
throw error;
|
|
90
|
+
await adapter.typeByTarget({
|
|
91
|
+
selector: opcode.selector,
|
|
92
|
+
target: opcode.target,
|
|
93
|
+
selectorAlternates: opcode.selectorAlternates,
|
|
94
|
+
}, text, opcode.clearFirst);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'PRESS_KEY':
|
|
99
|
+
await adapter.pressKey(opcode.key);
|
|
100
|
+
break;
|
|
101
|
+
case 'WAIT_FOR': {
|
|
102
|
+
const waitSelector = opcode.selector;
|
|
103
|
+
if (waitSelector) {
|
|
104
|
+
const found = await adapter.waitFor({
|
|
105
|
+
selector: waitSelector,
|
|
106
|
+
state: opcode.state,
|
|
107
|
+
timeoutMs: opcode.timeoutMs,
|
|
108
|
+
});
|
|
109
|
+
if (!found) {
|
|
110
|
+
return { success: false, error: `element "${waitSelector}" not found within timeout` };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (opcode.target && adapter.waitForTarget) {
|
|
114
|
+
const found = await adapter.waitForTarget({ target: opcode.target }, opcode.timeoutMs);
|
|
115
|
+
if (!found) {
|
|
116
|
+
return { success: false, error: `target "${opcode.target.text ?? opcode.target.role ?? 'unknown'}" not found within timeout` };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
return { success: false, error: 'WAIT_FOR needs a selector or target' };
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'SET_LOCALE':
|
|
125
|
+
return applyLocaleOpcode(opcode, adapter, context.currentVariant);
|
|
126
|
+
case 'SET_THEME':
|
|
127
|
+
return applyThemeOpcode(opcode, adapter, context.currentVariant);
|
|
128
|
+
case 'SCROLL':
|
|
129
|
+
if (opcode.targetSelector) {
|
|
130
|
+
await adapter.scrollIntoView(opcode.targetSelector);
|
|
131
|
+
}
|
|
132
|
+
else if (opcode.target && adapter.scrollIntoViewByTarget) {
|
|
133
|
+
await adapter.scrollIntoViewByTarget({ target: opcode.target });
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
await adapter.scroll(opcode.direction, opcode.amount);
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
case 'HOVER':
|
|
140
|
+
if (!adapter.hover)
|
|
141
|
+
return { success: false, error: 'adapter does not support HOVER' };
|
|
142
|
+
try {
|
|
143
|
+
await adapter.hover(opcode.selector);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (!opcode.target || !adapter.hoverByTarget)
|
|
147
|
+
throw error;
|
|
148
|
+
await adapter.hoverByTarget({
|
|
149
|
+
selector: opcode.selector,
|
|
150
|
+
target: opcode.target,
|
|
151
|
+
selectorAlternates: opcode.selectorAlternates,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
case 'SELECT_OPTION':
|
|
156
|
+
if (!adapter.selectOption)
|
|
157
|
+
return { success: false, error: 'adapter does not support SELECT_OPTION' };
|
|
158
|
+
await adapter.selectOption(opcode.selector, {
|
|
159
|
+
label: opcode.optionLabel,
|
|
160
|
+
value: opcode.optionValue,
|
|
161
|
+
index: opcode.optionIndex,
|
|
162
|
+
});
|
|
163
|
+
break;
|
|
164
|
+
case 'CHECK':
|
|
165
|
+
if (!adapter.check)
|
|
166
|
+
return { success: false, error: 'adapter does not support CHECK' };
|
|
167
|
+
await adapter.check(opcode.selector, opcode.checked);
|
|
168
|
+
break;
|
|
169
|
+
case 'DOUBLE_CLICK':
|
|
170
|
+
if (!adapter.doubleClick)
|
|
171
|
+
return { success: false, error: 'adapter does not support DOUBLE_CLICK' };
|
|
172
|
+
await adapter.doubleClick(opcode.selector);
|
|
173
|
+
break;
|
|
174
|
+
case 'CLONE_ELEMENT':
|
|
175
|
+
if (!adapter.cloneElement) {
|
|
176
|
+
return { success: false, error: 'adapter does not support CLONE_ELEMENT' };
|
|
177
|
+
}
|
|
178
|
+
await adapter.cloneElement({
|
|
179
|
+
sourceSelector: opcode.sourceSelector,
|
|
180
|
+
containerSelector: opcode.containerSelector,
|
|
181
|
+
count: opcode.count,
|
|
182
|
+
removeSource: opcode.removeSource,
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
case 'REMOVE_ELEMENT':
|
|
186
|
+
if (!adapter.removeElement) {
|
|
187
|
+
return { success: false, error: 'adapter does not support REMOVE_ELEMENT' };
|
|
188
|
+
}
|
|
189
|
+
await adapter.removeElement({ selector: opcode.selector });
|
|
190
|
+
break;
|
|
191
|
+
case 'SET_ATTRIBUTE':
|
|
192
|
+
if (!adapter.setAttribute) {
|
|
193
|
+
return { success: false, error: 'adapter does not support SET_ATTRIBUTE' };
|
|
194
|
+
}
|
|
195
|
+
await adapter.setAttribute({
|
|
196
|
+
selector: opcode.selector,
|
|
197
|
+
attribute: opcode.attribute,
|
|
198
|
+
value: opcode.value,
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
case 'INJECT_MOCK_DATA':
|
|
202
|
+
return applyInjectMockDataOpcode(opcode, adapter, context);
|
|
203
|
+
case 'CAPTURE_SCREENSHOT':
|
|
204
|
+
case 'CAPTURE_DOM':
|
|
205
|
+
case 'CAPTURE_FRAGMENT':
|
|
206
|
+
case 'BEGIN_CLIP':
|
|
207
|
+
case 'END_CLIP':
|
|
208
|
+
case 'ASSERT_ROUTE':
|
|
209
|
+
case 'ASSERT_SURFACE':
|
|
210
|
+
break;
|
|
211
|
+
default: {
|
|
212
|
+
const _exhaustive = opcode;
|
|
213
|
+
return { success: false, error: `unknown opcode kind: ${opcode.kind}` };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { success: true };
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
error: err instanceof Error ? err.message : String(err),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function resolveVariantPlaceholder(value, replacement, fieldName) {
|
|
226
|
+
if (value !== VARIANT_PLACEHOLDER)
|
|
227
|
+
return { resolved: value };
|
|
228
|
+
if (!replacement) {
|
|
229
|
+
return { error: `${fieldName} uses ${VARIANT_PLACEHOLDER} but no variant value is available` };
|
|
230
|
+
}
|
|
231
|
+
return { resolved: replacement };
|
|
232
|
+
}
|
|
233
|
+
function resolveStorageHintValues(hints, replacement, fieldName) {
|
|
234
|
+
const resolvedHints = [];
|
|
235
|
+
for (const hint of hints) {
|
|
236
|
+
const result = resolveVariantPlaceholder(hint.value, replacement, fieldName);
|
|
237
|
+
if ('error' in result) {
|
|
238
|
+
return { error: result.error };
|
|
239
|
+
}
|
|
240
|
+
resolvedHints.push({ ...hint, value: result.resolved });
|
|
241
|
+
}
|
|
242
|
+
return { resolvedHints };
|
|
243
|
+
}
|
|
244
|
+
async function applyLocaleOpcode(opcode, adapter, currentVariant) {
|
|
245
|
+
const localeResult = resolveVariantPlaceholder(opcode.locale, currentVariant?.locale, 'SET_LOCALE.locale');
|
|
246
|
+
if ('error' in localeResult)
|
|
247
|
+
return { success: false, error: localeResult.error };
|
|
248
|
+
const resolvedLocale = localeResult.resolved;
|
|
249
|
+
if (opcode.method === 'browser_context') {
|
|
250
|
+
await adapter.setLocale(resolvedLocale);
|
|
251
|
+
if (adapter.reloadPage) {
|
|
252
|
+
await adapter.reloadPage();
|
|
253
|
+
}
|
|
254
|
+
return { success: true };
|
|
255
|
+
}
|
|
256
|
+
if (opcode.method === 'ui_interaction') {
|
|
257
|
+
if (!opcode.selector) {
|
|
258
|
+
return { success: false, error: 'SET_LOCALE ui_interaction requires selector' };
|
|
259
|
+
}
|
|
260
|
+
await adapter.click(opcode.selector);
|
|
261
|
+
return { success: true };
|
|
262
|
+
}
|
|
263
|
+
if (!opcode.storageHints || opcode.storageHints.length === 0 || !adapter.writeStorageHint) {
|
|
264
|
+
return { success: false, error: 'SET_LOCALE storage mode requires storageHints and adapter storage support' };
|
|
265
|
+
}
|
|
266
|
+
const resolvedHints = resolveStorageHintValues(opcode.storageHints, currentVariant?.locale, 'SET_LOCALE.storageHints.value');
|
|
267
|
+
if ('error' in resolvedHints) {
|
|
268
|
+
return { success: false, error: resolvedHints.error };
|
|
269
|
+
}
|
|
270
|
+
const writes = await Promise.all(resolvedHints.resolvedHints.map((hint) => adapter.writeStorageHint({
|
|
271
|
+
storage: hint.storage,
|
|
272
|
+
key: hint.key,
|
|
273
|
+
value: hint.value,
|
|
274
|
+
kind: 'locale',
|
|
275
|
+
})));
|
|
276
|
+
if (!writes.some(Boolean)) {
|
|
277
|
+
return { success: false, error: 'SET_LOCALE storage hints did not match any writable locale keys' };
|
|
278
|
+
}
|
|
279
|
+
if (adapter.reloadPage) {
|
|
280
|
+
await adapter.reloadPage();
|
|
281
|
+
}
|
|
282
|
+
return { success: true };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Orchestrates mock data injection for one group. Applies BOTH delivery
|
|
286
|
+
* mechanisms additively when their fields are provided:
|
|
287
|
+
*
|
|
288
|
+
* 1. **Clone**: clones a template DOM element N times and writes each slot
|
|
289
|
+
* value into the cloned descendants. Provides instant visual feedback
|
|
290
|
+
* even before the app re-renders.
|
|
291
|
+
*
|
|
292
|
+
* 2. **Trigger**: writes the JSON-encoded `defaultValues` into a hidden
|
|
293
|
+
* input the user's app exposed, then clicks a hidden trigger button. The
|
|
294
|
+
* user's `onClick` handler reads the input and re-renders the widget.
|
|
295
|
+
* Survives React re-renders that would otherwise clobber clones.
|
|
296
|
+
*
|
|
297
|
+
* The two mechanisms are independent and complementary. An opcode typically
|
|
298
|
+
* carries fields for both. Each runs in isolation: a failure of one does not
|
|
299
|
+
* prevent the other from running. As long as AT LEAST ONE produces a
|
|
300
|
+
* successful application, the group is recorded as 'applied'. If both fail
|
|
301
|
+
* (or neither was wired), the opcode returns failure and the runner's
|
|
302
|
+
* soft-skip branch converts it to `status: 'skipped'`.
|
|
303
|
+
*/
|
|
304
|
+
async function applyInjectMockDataOpcode(opcode, adapter, context) {
|
|
305
|
+
const group = context.mockDataGroups?.find((g) => g.name === opcode.groupName);
|
|
306
|
+
if (!group) {
|
|
307
|
+
return {
|
|
308
|
+
success: false,
|
|
309
|
+
error: `INJECT_MOCK_DATA: group "${opcode.groupName}" not found in program.mockDataGroups`,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (group.defaultValues.length === 0) {
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
error: `INJECT_MOCK_DATA: group "${opcode.groupName}" has no defaultValues`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const hasCloneFields = Boolean(opcode.containerSelector && opcode.slotMappings && opcode.slotMappings.length > 0);
|
|
319
|
+
const hasTriggerFields = Boolean(opcode.inputSelector && opcode.triggerSelector);
|
|
320
|
+
if (!hasCloneFields && !hasTriggerFields) {
|
|
321
|
+
return {
|
|
322
|
+
success: false,
|
|
323
|
+
error: `INJECT_MOCK_DATA "${opcode.groupName}": no clone fields and no trigger fields provided`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const errors = [];
|
|
327
|
+
let anySucceeded = false;
|
|
328
|
+
// Apply clone first so the user sees an instant paint of the mocked DOM,
|
|
329
|
+
// even before the app's state-driven re-render fires.
|
|
330
|
+
if (hasCloneFields) {
|
|
331
|
+
try {
|
|
332
|
+
const cloneResult = await applyInjectMockDataCloneStrategy(opcode, adapter, group);
|
|
333
|
+
if (cloneResult.success) {
|
|
334
|
+
anySucceeded = true;
|
|
335
|
+
}
|
|
336
|
+
else if (cloneResult.error) {
|
|
337
|
+
errors.push(`clone: ${cloneResult.error}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
errors.push(`clone: ${err instanceof Error ? err.message : String(err)}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Apply trigger second so the app's state takes over and survives any
|
|
345
|
+
// future re-renders that would clobber the cloned DOM.
|
|
346
|
+
if (hasTriggerFields) {
|
|
347
|
+
try {
|
|
348
|
+
const triggerResult = await applyInjectMockDataTriggerStrategy(opcode, adapter, group);
|
|
349
|
+
if (triggerResult.success) {
|
|
350
|
+
anySucceeded = true;
|
|
351
|
+
}
|
|
352
|
+
else if (triggerResult.error) {
|
|
353
|
+
errors.push(`trigger: ${triggerResult.error}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
errors.push(`trigger: ${err instanceof Error ? err.message : String(err)}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (anySucceeded) {
|
|
361
|
+
return { success: true };
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
success: false,
|
|
365
|
+
error: `INJECT_MOCK_DATA "${opcode.groupName}" failed: ${errors.join('; ')}`,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async function applyInjectMockDataCloneStrategy(opcode, adapter, group) {
|
|
369
|
+
if (!adapter.cloneElement || !adapter.setAttribute || !adapter.setTextContent) {
|
|
370
|
+
return {
|
|
371
|
+
success: false,
|
|
372
|
+
error: 'adapter does not support cloneElement / setAttribute / setTextContent',
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (!opcode.containerSelector) {
|
|
376
|
+
return {
|
|
377
|
+
success: false,
|
|
378
|
+
error: 'containerSelector is required',
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
if (!opcode.slotMappings || opcode.slotMappings.length === 0) {
|
|
382
|
+
return {
|
|
383
|
+
success: false,
|
|
384
|
+
error: 'slotMappings are required',
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const rowCount = opcode.count ?? group.defaultValues.length;
|
|
388
|
+
const templateSelector = opcode.templateSelector ?? `${opcode.containerSelector} > *:first-child`;
|
|
389
|
+
// Clone N copies of the template element. The runner short-circuits to
|
|
390
|
+
// 'skipped' if cloneElement throws (selector miss).
|
|
391
|
+
await adapter.cloneElement({
|
|
392
|
+
sourceSelector: templateSelector,
|
|
393
|
+
containerSelector: opcode.containerSelector,
|
|
394
|
+
count: rowCount,
|
|
395
|
+
removeSource: opcode.removeTemplate,
|
|
396
|
+
});
|
|
397
|
+
// If the group is configured to replace existing data, remove every child
|
|
398
|
+
// of the container that is NOT a clone (placeholders, real items, empty
|
|
399
|
+
// states). This must run AFTER cloning so we know which children to keep.
|
|
400
|
+
if (group.replaceExisting && adapter.removeElement) {
|
|
401
|
+
try {
|
|
402
|
+
await adapter.removeElement({
|
|
403
|
+
selector: `${opcode.containerSelector} > *:not([data-ak-mock-clone])`,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
// non-blocking — if nothing to remove, skip
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Each clone is tagged by cloneElement with `data-ak-mock-clone="<i>"`,
|
|
411
|
+
// so we address them by that attribute instead of nth-child math. This
|
|
412
|
+
// is robust against the container holding non-template siblings (e.g.
|
|
413
|
+
// empty-state placeholders) that would otherwise shift indices.
|
|
414
|
+
for (let i = 0; i < rowCount; i++) {
|
|
415
|
+
const row = group.defaultValues[i % group.defaultValues.length];
|
|
416
|
+
const rowSelector = `${opcode.containerSelector} > [data-ak-mock-clone="${i}"]`;
|
|
417
|
+
for (const mapping of opcode.slotMappings) {
|
|
418
|
+
const value = row[mapping.slot];
|
|
419
|
+
if (value === undefined)
|
|
420
|
+
continue;
|
|
421
|
+
const targetSelector = `${rowSelector} ${mapping.selector}`;
|
|
422
|
+
if (mapping.attribute) {
|
|
423
|
+
await adapter.setAttribute({
|
|
424
|
+
selector: targetSelector,
|
|
425
|
+
attribute: mapping.attribute,
|
|
426
|
+
value,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
await adapter.setTextContent({
|
|
431
|
+
selector: targetSelector,
|
|
432
|
+
text: value,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { success: true };
|
|
438
|
+
}
|
|
439
|
+
async function applyInjectMockDataTriggerStrategy(opcode, adapter, group) {
|
|
440
|
+
if (!adapter.setInputValue || !adapter.clickHidden) {
|
|
441
|
+
return {
|
|
442
|
+
success: false,
|
|
443
|
+
error: 'adapter does not support setInputValue / clickHidden',
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
if (!opcode.inputSelector) {
|
|
447
|
+
return {
|
|
448
|
+
success: false,
|
|
449
|
+
error: 'inputSelector is required',
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
if (!opcode.triggerSelector) {
|
|
453
|
+
return {
|
|
454
|
+
success: false,
|
|
455
|
+
error: 'triggerSelector is required',
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
// Serialize the seed values. The user's onClick handler will JSON.parse this
|
|
459
|
+
// and feed it into its render function (e.g. setChartData(parsed)).
|
|
460
|
+
const valueJson = JSON.stringify(group.defaultValues);
|
|
461
|
+
// Write into the hidden input via the React-aware native setter so the
|
|
462
|
+
// app's controlled-component bookkeeping picks up the change.
|
|
463
|
+
await adapter.setInputValue({
|
|
464
|
+
selector: opcode.inputSelector,
|
|
465
|
+
value: valueJson,
|
|
466
|
+
});
|
|
467
|
+
// Click the hidden trigger via the native .click() method so React's
|
|
468
|
+
// onClick handler fires even if the button is display:none.
|
|
469
|
+
await adapter.clickHidden({
|
|
470
|
+
selector: opcode.triggerSelector,
|
|
471
|
+
});
|
|
472
|
+
return { success: true };
|
|
473
|
+
}
|
|
474
|
+
async function applyThemeOpcode(opcode, adapter, currentVariant) {
|
|
475
|
+
const themeResult = resolveVariantPlaceholder(opcode.theme, currentVariant?.theme, 'SET_THEME.theme');
|
|
476
|
+
if ('error' in themeResult)
|
|
477
|
+
return { success: false, error: themeResult.error };
|
|
478
|
+
const resolvedTheme = themeResult.resolved;
|
|
479
|
+
if (opcode.method === 'color_scheme') {
|
|
480
|
+
await adapter.setColorScheme(resolvedTheme);
|
|
481
|
+
return { success: true };
|
|
482
|
+
}
|
|
483
|
+
if (opcode.method === 'ui_interaction') {
|
|
484
|
+
if (!opcode.selector) {
|
|
485
|
+
return { success: false, error: 'SET_THEME ui_interaction requires selector' };
|
|
486
|
+
}
|
|
487
|
+
await adapter.click(opcode.selector);
|
|
488
|
+
return { success: true };
|
|
489
|
+
}
|
|
490
|
+
if (!opcode.storageHints || opcode.storageHints.length === 0 || !adapter.writeStorageHint) {
|
|
491
|
+
return { success: false, error: 'SET_THEME storage mode requires storageHints and adapter storage support' };
|
|
492
|
+
}
|
|
493
|
+
const resolvedHints = resolveStorageHintValues(opcode.storageHints, currentVariant?.theme, 'SET_THEME.storageHints.value');
|
|
494
|
+
if ('error' in resolvedHints) {
|
|
495
|
+
return { success: false, error: resolvedHints.error };
|
|
496
|
+
}
|
|
497
|
+
const writes = await Promise.all(resolvedHints.resolvedHints.map((hint) => adapter.writeStorageHint({
|
|
498
|
+
storage: hint.storage,
|
|
499
|
+
key: hint.key,
|
|
500
|
+
value: hint.value,
|
|
501
|
+
kind: 'theme',
|
|
502
|
+
})));
|
|
503
|
+
if (!writes.some(Boolean)) {
|
|
504
|
+
return { success: false, error: 'SET_THEME storage hints did not match any writable theme keys' };
|
|
505
|
+
}
|
|
506
|
+
if (adapter.reloadPage) {
|
|
507
|
+
await adapter.reloadPage();
|
|
508
|
+
}
|
|
509
|
+
return { success: true };
|
|
510
|
+
}
|
|
511
|
+
//# sourceMappingURL=opcode-actions.js.map
|