@reticlehq/browser 2.13.1 → 2.14.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/dist/actions/actions-dom.d.ts +0 -20
- package/dist/actions/actions-dom.js +0 -66
- package/dist/actions/actions-press.d.ts +10 -0
- package/dist/actions/actions-press.js +77 -0
- package/dist/actions/actions.d.ts +0 -89
- package/dist/actions/actions.js +24 -358
- package/dist/actions/appeared-text.d.ts +0 -20
- package/dist/actions/appeared-text.js +0 -36
- package/dist/actions/danger-context.d.ts +3 -0
- package/dist/actions/danger-context.js +39 -0
- package/dist/actions/scroll.d.ts +0 -9
- package/dist/actions/scroll.js +0 -7
- package/dist/actions/synthetic-input.d.ts +0 -7
- package/dist/actions/synthetic-input.js +0 -27
- package/dist/actions/value-input.d.ts +0 -30
- package/dist/actions/value-input.js +0 -33
- package/dist/commands/commands.d.ts +0 -2
- package/dist/commands/commands.js +0 -67
- package/dist/connect-options.d.ts +2 -96
- package/dist/dom/a11y.d.ts +0 -29
- package/dist/dom/a11y.js +1 -88
- package/dist/dom/auto-anchor.d.ts +0 -40
- package/dist/dom/auto-anchor.js +0 -33
- package/dist/dom/chart.d.ts +0 -61
- package/dist/dom/chart.js +0 -60
- package/dist/dom/desktop-capture.d.ts +0 -30
- package/dist/dom/desktop-capture.js +0 -72
- package/dist/dom/dom-ignore.d.ts +0 -30
- package/dist/dom/dom-ignore.js +0 -47
- package/dist/dom/occlusion.d.ts +0 -7
- package/dist/dom/occlusion.js +0 -7
- package/dist/dom/query.d.ts +0 -14
- package/dist/dom/query.js +48 -214
- package/dist/dom/realm.d.ts +0 -20
- package/dist/dom/realm.js +0 -28
- package/dist/dom/refs.d.ts +0 -76
- package/dist/dom/refs.js +2 -133
- package/dist/dom/shadow-registry.d.ts +0 -33
- package/dist/dom/shadow-registry.js +2 -72
- package/dist/dom/snapshot.d.ts +2 -43
- package/dist/dom/snapshot.js +26 -151
- package/dist/dom/source.d.ts +0 -44
- package/dist/dom/source.js +0 -45
- package/dist/dom/theme.d.ts +0 -37
- package/dist/dom/theme.js +2 -52
- package/dist/dom/virtualized.d.ts +0 -17
- package/dist/dom/virtualized.js +0 -27
- package/dist/edit-epoch.d.ts +0 -22
- package/dist/edit-epoch.js +0 -52
- package/dist/index.js +1 -23
- package/dist/observers/animation.d.ts +0 -1
- package/dist/observers/animation.js +0 -3
- package/dist/observers/blind-spots.d.ts +0 -26
- package/dist/observers/blind-spots.js +2 -61
- package/dist/observers/console.d.ts +1 -2
- package/dist/observers/console.js +41 -30
- package/dist/observers/context-open.d.ts +1 -11
- package/dist/observers/context-open.js +10 -14
- package/dist/observers/departure.d.ts +3 -0
- package/dist/observers/departure.js +52 -0
- package/dist/observers/dialogs.d.ts +2 -0
- package/dist/observers/dialogs.js +39 -0
- package/dist/observers/dom.js +0 -44
- package/dist/observers/download.d.ts +1 -2
- package/dist/observers/download.js +38 -55
- package/dist/observers/focus.d.ts +0 -6
- package/dist/observers/focus.js +0 -7
- package/dist/observers/frames.d.ts +0 -9
- package/dist/observers/frames.js +1 -28
- package/dist/observers/health.d.ts +0 -5
- package/dist/observers/health.js +1 -33
- package/dist/observers/install-all.d.ts +2 -0
- package/dist/observers/install-all.js +26 -27
- package/dist/observers/ipc.d.ts +0 -25
- package/dist/observers/ipc.js +0 -65
- package/dist/observers/navigation.d.ts +2 -0
- package/dist/observers/navigation.js +33 -0
- package/dist/observers/network-body.d.ts +5 -28
- package/dist/observers/network-body.js +11 -108
- package/dist/observers/network-redact.d.ts +1 -20
- package/dist/observers/network-redact.js +1 -80
- package/dist/observers/network-stream.d.ts +0 -21
- package/dist/observers/network-stream.js +1 -44
- package/dist/observers/network.d.ts +2 -26
- package/dist/observers/network.js +177 -294
- package/dist/observers/perf.d.ts +0 -10
- package/dist/observers/perf.js +0 -16
- package/dist/observers/route.d.ts +1 -2
- package/dist/observers/route.js +13 -19
- package/dist/observers/scroll.d.ts +0 -1
- package/dist/observers/scroll.js +0 -9
- package/dist/observers/sdk-failure.d.ts +1 -9
- package/dist/observers/sdk-failure.js +1 -30
- package/dist/observers/state.d.ts +1 -11
- package/dist/observers/state.js +13 -41
- package/dist/observers/storage.d.ts +0 -14
- package/dist/observers/storage.js +10 -34
- package/dist/observers/types.d.ts +0 -15
- package/dist/observers/types.js +0 -14
- package/dist/patching/capture-method.d.ts +0 -24
- package/dist/patching/capture-method.js +0 -26
- package/dist/presenter/overlay.d.ts +0 -1
- package/dist/presenter/overlay.js +0 -1
- package/dist/presenter/presenter-brand.d.ts +0 -19
- package/dist/presenter/presenter-brand.js +0 -21
- package/dist/presenter/presenter-config.d.ts +1 -105
- package/dist/presenter/presenter-config.js +2 -91
- package/dist/presenter/presenter-controls.d.ts +0 -29
- package/dist/presenter/presenter-controls.js +3 -131
- package/dist/presenter/presenter-dock-layout.d.ts +0 -3
- package/dist/presenter/presenter-dock-layout.js +0 -4
- package/dist/presenter/presenter-drag.d.ts +2 -24
- package/dist/presenter/presenter-drag.js +0 -26
- package/dist/presenter/presenter-effects.d.ts +0 -9
- package/dist/presenter/presenter-effects.js +0 -9
- package/dist/presenter/presenter-glow.d.ts +0 -21
- package/dist/presenter/presenter-glow.js +1 -27
- package/dist/presenter/presenter-heroicons-data.d.ts +0 -8
- package/dist/presenter/presenter-heroicons-data.js +0 -8
- package/dist/presenter/presenter-hud-chrome.d.ts +0 -10
- package/dist/presenter/presenter-hud-chrome.js +0 -10
- package/dist/presenter/presenter-icons.d.ts +0 -6
- package/dist/presenter/presenter-icons.js +0 -6
- package/dist/presenter/presenter-log.d.ts +0 -38
- package/dist/presenter/presenter-log.js +1 -45
- package/dist/presenter/presenter-report-copy.d.ts +5 -66
- package/dist/presenter/presenter-report-copy.js +5 -66
- package/dist/presenter/presenter-report-styles.d.ts +0 -6
- package/dist/presenter/presenter-report-styles.js +6 -6
- package/dist/presenter/presenter-report.d.ts +4 -7
- package/dist/presenter/presenter-report.js +18 -76
- package/dist/presenter/presenter-run-state.d.ts +0 -7
- package/dist/presenter/presenter-run-state.js +0 -5
- package/dist/presenter/presenter-settings-styles.d.ts +0 -1
- package/dist/presenter/presenter-settings-styles.js +0 -1
- package/dist/presenter/presenter-settings.d.ts +1 -23
- package/dist/presenter/presenter-settings.js +1 -31
- package/dist/presenter/presenter-shell-styles.js +2 -5
- package/dist/presenter/presenter-shell.d.ts +2 -18
- package/dist/presenter/presenter-shell.js +1 -73
- package/dist/presenter/presenter-styles.d.ts +0 -9
- package/dist/presenter/presenter-styles.js +0 -9
- package/dist/presenter/presenter-tally.d.ts +0 -5
- package/dist/presenter/presenter-tally.js +0 -4
- package/dist/presenter/presenter-test-helpers.d.ts +0 -2
- package/dist/presenter/presenter-test-helpers.js +0 -2
- package/dist/presenter/presenter-verbs.js +0 -7
- package/dist/presenter/presenter-workspace.d.ts +0 -4
- package/dist/presenter/presenter-workspace.js +1 -8
- package/dist/presenter/presenter.d.ts +0 -60
- package/dist/presenter/presenter.js +4 -118
- package/dist/recorder/recorder-styles.d.ts +0 -6
- package/dist/recorder/recorder-styles.js +0 -6
- package/dist/recorder/recorder.d.ts +0 -34
- package/dist/recorder/recorder.js +0 -49
- package/dist/registry/adapters.d.ts +0 -10
- package/dist/registry/adapters.js +0 -10
- package/dist/registry/auto-testids.d.ts +0 -19
- package/dist/registry/auto-testids.js +1 -21
- package/dist/registry/capabilities.d.ts +0 -31
- package/dist/registry/capabilities.js +1 -46
- package/dist/registry/commit-and-signal.d.ts +0 -6
- package/dist/registry/commit-and-signal.js +1 -7
- package/dist/registry/domains.d.ts +0 -8
- package/dist/registry/domains.js +0 -7
- package/dist/registry/emitter.d.ts +0 -14
- package/dist/registry/emitter.js +0 -10
- package/dist/registry/store-adapters.d.ts +0 -186
- package/dist/registry/store-adapters.js +0 -163
- package/dist/registry/stores.d.ts +0 -33
- package/dist/registry/stores.js +0 -59
- package/dist/reticle-presenter-helpers.d.ts +0 -13
- package/dist/reticle-presenter-helpers.js +0 -14
- package/dist/reticle.d.ts +1 -52
- package/dist/reticle.js +20 -145
- package/dist/review/annotator-styles.js +0 -7
- package/dist/review/annotator.d.ts +2 -13
- package/dist/review/annotator.js +0 -40
- package/dist/review/mark-anchor.d.ts +0 -11
- package/dist/review/mark-anchor.js +0 -8
- package/dist/security/serialization.d.ts +0 -20
- package/dist/security/serialization.js +1 -66
- package/dist/session-continuity.d.ts +0 -16
- package/dist/session-continuity.js +1 -20
- package/dist/timers/clock.d.ts +1 -14
- package/dist/timers/clock.js +59 -60
- package/dist/timers/native-console.js +0 -4
- package/dist/timers/native-timers.d.ts +0 -25
- package/dist/timers/native-timers.js +3 -33
- package/dist/transport/transport.d.ts +0 -51
- package/dist/transport/transport.js +3 -129
- package/dist/transport/unreachable-message.d.ts +0 -17
- package/dist/transport/unreachable-message.js +0 -17
- package/dist/util/captured-method.d.ts +0 -35
- package/dist/util/captured-method.js +0 -35
- package/package.json +2 -2
|
@@ -4,12 +4,6 @@ interface ClickGeometry {
|
|
|
4
4
|
scrolledIntoView: boolean;
|
|
5
5
|
}
|
|
6
6
|
export declare const NO_GEOMETRY: ClickGeometry;
|
|
7
|
-
/**
|
|
8
|
-
* Full click as a real user produces it: pointerdown -> mousedown -> focus -> pointerup -> mouseup
|
|
9
|
-
* -> click. A bare `click` event skips pointer- and focus-gated handlers. Returns the click event's
|
|
10
|
-
* `defaultPrevented` so the probe is unchanged. Focus only moves for focusable targets (tabIndex>=0),
|
|
11
|
-
* so a plain <div> click still reports focusMoved=null.
|
|
12
|
-
*/
|
|
13
7
|
export declare function fireClickSequence(el: HTMLElement, hold?: {
|
|
14
8
|
ms: number;
|
|
15
9
|
sleep: (ms: number) => Promise<void>;
|
|
@@ -18,22 +12,8 @@ export declare function fireClickSequence(el: HTMLElement, hold?: {
|
|
|
18
12
|
prevented: boolean;
|
|
19
13
|
heldMs: number;
|
|
20
14
|
}>;
|
|
21
|
-
/**
|
|
22
|
-
* Click-like geometry honesty: scroll an off-viewport target into view, then hit-test the click
|
|
23
|
-
* point. Synthetic dispatch always reaches the target regardless — this is purely so the agent
|
|
24
|
-
* learns when the target is off-screen or visually blocked instead of getting a false "it worked".
|
|
25
|
-
*/
|
|
26
15
|
export declare function clickGeometry(el: HTMLElement): ClickGeometry;
|
|
27
16
|
export declare function firePointer(el: Element, type: string, relatedTarget?: EventTarget | null): void;
|
|
28
|
-
/** Enter/leave pointer events are non-bubbling per spec; keep them so to avoid double-firing. */
|
|
29
17
|
export declare function firePointerNonBubbling(el: Element, type: string, relatedTarget?: EventTarget | null): void;
|
|
30
|
-
/**
|
|
31
|
-
* Pointer-based drag (dnd-kit / react-beautiful-dnd) + best-effort HTML5 DnD. Async: yields a
|
|
32
|
-
* frame between phases so React commits state between steps (fixes stale-closure handlers).
|
|
33
|
-
*
|
|
34
|
-
* Dispatches along a PATH with real client coordinates and a held button. Without those a
|
|
35
|
-
* geometry-based library resolves the drop target as the source, so the action reports success over
|
|
36
|
-
* an app that did not change — a false green produced by the tool rather than caught by it.
|
|
37
|
-
*/
|
|
38
18
|
export declare function dragElement(source: HTMLElement, target: HTMLElement | null, data: unknown): Promise<boolean>;
|
|
39
19
|
export {};
|
|
@@ -7,12 +7,6 @@ export const NO_GEOMETRY = {
|
|
|
7
7
|
occludedBy: null,
|
|
8
8
|
scrolledIntoView: false,
|
|
9
9
|
};
|
|
10
|
-
/**
|
|
11
|
-
* Full click as a real user produces it: pointerdown -> mousedown -> focus -> pointerup -> mouseup
|
|
12
|
-
* -> click. A bare `click` event skips pointer- and focus-gated handlers. Returns the click event's
|
|
13
|
-
* `defaultPrevented` so the probe is unchanged. Focus only moves for focusable targets (tabIndex>=0),
|
|
14
|
-
* so a plain <div> click still reports focusMoved=null.
|
|
15
|
-
*/
|
|
16
10
|
export async function fireClickSequence(el, hold) {
|
|
17
11
|
const doc = el.ownerDocument;
|
|
18
12
|
const from = doc.activeElement ?? doc.body;
|
|
@@ -20,14 +14,6 @@ export async function fireClickSequence(el, hold) {
|
|
|
20
14
|
asSyntheticInput(() => el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true })));
|
|
21
15
|
if (el.tabIndex >= 0 && 'function' === typeof el.focus)
|
|
22
16
|
el.focus();
|
|
23
|
-
// The gap that makes hold-to-confirm driveable. Everything between down and up used to be
|
|
24
|
-
// synchronous, so a control whose contract is "the button is down for N ms" could not be
|
|
25
|
-
// expressed at all — the reported case cancelled its own confirm on the mouseup that arrived 7ms
|
|
26
|
-
// later. `drag` already splits the pair across a gap; this is that shape with a timer.
|
|
27
|
-
//
|
|
28
|
-
// The ACHIEVED hold is measured and returned rather than echoed back: `holdMs: 1200` against a
|
|
29
|
-
// 1200ms animation is a race by construction, and a caller needs to tell "held 1200" from
|
|
30
|
-
// "held 1204". A backgrounded tab throttles timers, so this can legitimately overshoot by a lot.
|
|
31
17
|
let heldMs = 0;
|
|
32
18
|
if (hold !== undefined && hold.ms > 0) {
|
|
33
19
|
const startedAt = hold.now();
|
|
@@ -36,17 +22,12 @@ export async function fireClickSequence(el, hold) {
|
|
|
36
22
|
}
|
|
37
23
|
firePointer(el, 'pointerup', from);
|
|
38
24
|
asSyntheticInput(() => el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true })));
|
|
39
|
-
// Marked as ours so the annotator's capture-phase listener lets it through. Only the CHECK branch
|
|
40
|
-
// used to do this, so the ordinary click action — which is this function — was swallowed whole in
|
|
41
|
-
// annotate mode while still reporting `dispatched: true`.
|
|
42
25
|
const notPrevented = asSyntheticInput(() => el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })));
|
|
43
26
|
return { prevented: !notPrevented, heldMs };
|
|
44
27
|
}
|
|
45
|
-
/** A box has layout we can reason about (jsdom returns an all-zero box — nothing to hit-test). */
|
|
46
28
|
function isMeasurable(rect) {
|
|
47
29
|
return rect.width > 0 || rect.height > 0;
|
|
48
30
|
}
|
|
49
|
-
/** The center of `rect` falls outside the visible viewport. */
|
|
50
31
|
function isOffViewport(el, rect) {
|
|
51
32
|
const win = el.ownerDocument.defaultView;
|
|
52
33
|
if (null === win)
|
|
@@ -55,18 +36,12 @@ function isOffViewport(el, rect) {
|
|
|
55
36
|
const cy = rect.top + rect.height / 2;
|
|
56
37
|
return cx < 0 || cy < 0 || cx > win.innerWidth || cy > win.innerHeight;
|
|
57
38
|
}
|
|
58
|
-
/** Hit-test the center: occluded iff the top NON-Reticle element is a foreign subtree (not target/ancestor/descendant). */
|
|
59
39
|
function hitTest(el, rect) {
|
|
60
40
|
const top = hitTestOccluder(el, rect);
|
|
61
41
|
return null === top
|
|
62
42
|
? { occluded: false, occludedBy: null }
|
|
63
43
|
: { occluded: true, occludedBy: refs.refFor(top) };
|
|
64
44
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Click-like geometry honesty: scroll an off-viewport target into view, then hit-test the click
|
|
67
|
-
* point. Synthetic dispatch always reaches the target regardless — this is purely so the agent
|
|
68
|
-
* learns when the target is off-screen or visually blocked instead of getting a false "it worked".
|
|
69
|
-
*/
|
|
70
45
|
export function clickGeometry(el) {
|
|
71
46
|
if (typeof el.getBoundingClientRect !== 'function')
|
|
72
47
|
return NO_GEOMETRY;
|
|
@@ -91,7 +66,6 @@ export function firePointer(el, type, relatedTarget = null) {
|
|
|
91
66
|
}
|
|
92
67
|
});
|
|
93
68
|
}
|
|
94
|
-
/** Enter/leave pointer events are non-bubbling per spec; keep them so to avoid double-firing. */
|
|
95
69
|
export function firePointerNonBubbling(el, type, relatedTarget = null) {
|
|
96
70
|
asSyntheticInput(() => {
|
|
97
71
|
if ('function' === typeof PointerEvent) {
|
|
@@ -106,7 +80,6 @@ function makeDataTransfer(data) {
|
|
|
106
80
|
if (typeof DataTransfer !== 'function')
|
|
107
81
|
return null;
|
|
108
82
|
const dt = new DataTransfer();
|
|
109
|
-
// data: { mime, value } or [{ mime, value }, …]
|
|
110
83
|
const entries = Array.isArray(data) ? data : data !== undefined ? [data] : [];
|
|
111
84
|
for (const entry of entries) {
|
|
112
85
|
if ('object' === typeof entry && entry !== null) {
|
|
@@ -117,46 +90,20 @@ function makeDataTransfer(data) {
|
|
|
117
90
|
}
|
|
118
91
|
return dt;
|
|
119
92
|
}
|
|
120
|
-
/** `buttons` bitmask for the primary button: 1 while held, 0 once released. */
|
|
121
93
|
const BUTTON_HELD = 1;
|
|
122
94
|
const BUTTON_RELEASED = 0;
|
|
123
|
-
/**
|
|
124
|
-
* How many intermediate moves a drag emits.
|
|
125
|
-
*
|
|
126
|
-
* Enough to cross any realistic `activationConstraint: { distance: N }` while a sensor is watching,
|
|
127
|
-
* and few enough that a drag stays a handful of frames rather than an animation.
|
|
128
|
-
*/
|
|
129
95
|
const DRAG_STEPS = 5;
|
|
130
|
-
/** The centre of an element in client coordinates — what a real pointer would be over. */
|
|
131
96
|
function centreOf(el) {
|
|
132
97
|
const box = el.getBoundingClientRect();
|
|
133
98
|
return { x: box.left + box.width / 2, y: box.top + box.height / 2 };
|
|
134
99
|
}
|
|
135
|
-
/** A point `t` of the way from `a` to `b`, rounded — pointer coordinates are integers in practice. */
|
|
136
100
|
function lerp(a, b, t) {
|
|
137
101
|
return { x: Math.round(a.x + (b.x - a.x) * t), y: Math.round(a.y + (b.y - a.y) * t) };
|
|
138
102
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Pointer-based drag (dnd-kit / react-beautiful-dnd) + best-effort HTML5 DnD. Async: yields a
|
|
141
|
-
* frame between phases so React commits state between steps (fixes stale-closure handlers).
|
|
142
|
-
*
|
|
143
|
-
* Dispatches along a PATH with real client coordinates and a held button. Without those a
|
|
144
|
-
* geometry-based library resolves the drop target as the source, so the action reports success over
|
|
145
|
-
* an app that did not change — a false green produced by the tool rather than caught by it.
|
|
146
|
-
*/
|
|
147
103
|
export async function dragElement(source, target, data) {
|
|
148
104
|
const dest = target ?? source;
|
|
149
105
|
const from = centreOf(source);
|
|
150
106
|
const to = centreOf(dest);
|
|
151
|
-
/**
|
|
152
|
-
* Dispatch ONE pointer/mouse pair with real coordinates and button state.
|
|
153
|
-
*
|
|
154
|
-
* Everything here was missing before, and each omission breaks a different, standard library
|
|
155
|
-
* pattern: without coordinates a geometry-based collision resolver sees a zero delta and reports
|
|
156
|
-
* the source as its own drop target; without `buttons: 1` the usual "was the mouse released?"
|
|
157
|
-
* guard (`event.buttons === 0`) bails out mid-drag. Boundary events (`over`/`out` bubbling,
|
|
158
|
-
* `enter`/`leave` not) do not bubble exactly when a real pointer's would not.
|
|
159
|
-
*/
|
|
160
107
|
const fire = (el, type, at, buttons, related) => {
|
|
161
108
|
const bubbles = !(type.endsWith('enter') || type.endsWith('leave'));
|
|
162
109
|
const init = {
|
|
@@ -177,29 +124,18 @@ export async function dragElement(source, target, data) {
|
|
|
177
124
|
el.dispatchEvent(new MouseEvent(type, init));
|
|
178
125
|
}
|
|
179
126
|
};
|
|
180
|
-
/** Is this path point inside an element's box? Rects are cached; jsdom reports zeros otherwise. */
|
|
181
127
|
const sourceRect = source.getBoundingClientRect();
|
|
182
128
|
const destRect = target !== null ? dest.getBoundingClientRect() : null;
|
|
183
129
|
const crosses = (rect, p) => p.x >= rect.left && p.x <= rect.right && p.y >= rect.top && p.y <= rect.bottom;
|
|
184
130
|
fire(source, 'pointerdown', from, BUTTON_HELD);
|
|
185
131
|
fire(source, 'mousedown', from, BUTTON_HELD);
|
|
186
132
|
await nativeFrame();
|
|
187
|
-
// A path, not a jump. `activationConstraint: { distance: N }` is the standard way to keep a
|
|
188
|
-
// draggable card clickable, and a sensor only starts a drag once it has SEEN the pointer travel
|
|
189
|
-
// that far — which a single move from A to B never shows it.
|
|
190
|
-
//
|
|
191
|
-
// The crossing announces itself: the first step outside the source fires out/leave on it, and
|
|
192
|
-
// the first step inside the destination fires over/enter on it, both with the button still held.
|
|
193
|
-
// Without these, React never synthesises `onMouseEnter` on the destination (it derives that from
|
|
194
|
-
// delegated `mouseover`/`mouseout`), so drag-to-select grids never extend their selection.
|
|
195
133
|
let leftSource = false;
|
|
196
134
|
let enteredDest = false;
|
|
197
135
|
for (let step = 1; step <= DRAG_STEPS; step += 1) {
|
|
198
136
|
const at = lerp(from, to, step / DRAG_STEPS);
|
|
199
137
|
if (!leftSource && !crosses(sourceRect, at)) {
|
|
200
138
|
leftSource = true;
|
|
201
|
-
// relatedTarget is the element the pointer crossed TO/FROM; React's enter/leave synthesis
|
|
202
|
-
// reads it off the delegated over/out pair to know which boundary was crossed.
|
|
203
139
|
fire(source, 'pointerout', at, BUTTON_HELD, dest);
|
|
204
140
|
fire(source, 'mouseout', at, BUTTON_HELD, dest);
|
|
205
141
|
fire(source, 'pointerleave', at, BUTTON_HELD, dest);
|
|
@@ -216,8 +152,6 @@ export async function dragElement(source, target, data) {
|
|
|
216
152
|
fire(dest, 'mousemove', at, BUTTON_HELD);
|
|
217
153
|
await nativeFrame();
|
|
218
154
|
}
|
|
219
|
-
// A short hop whose sampled steps never land inside the destination box still crossed into it;
|
|
220
|
-
// announce the arrival rather than silently skipping the pair.
|
|
221
155
|
if (!enteredDest && destRect !== null) {
|
|
222
156
|
fire(dest, 'pointerover', to, BUTTON_HELD, source);
|
|
223
157
|
fire(dest, 'mouseover', to, BUTTON_HELD, source);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function pressKey(args: Record<string, unknown>): string;
|
|
2
|
+
export declare function pressModifiers(args: Record<string, unknown>): {
|
|
3
|
+
metaKey: boolean;
|
|
4
|
+
ctrlKey: boolean;
|
|
5
|
+
shiftKey: boolean;
|
|
6
|
+
altKey: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function pressCode(args: Record<string, unknown>, key: string): string;
|
|
9
|
+
export declare function focusedOrDocument(): HTMLElement;
|
|
10
|
+
export declare function isReflessDocumentPress(ref: string, action: string, args: Record<string, unknown>): boolean;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ActionType, isGlobalPress, pressKeyFromArgs } from '@reticlehq/core';
|
|
2
|
+
import { isHtmlElement } from '../dom/realm.js';
|
|
3
|
+
function asString(value, fallback = '') {
|
|
4
|
+
return 'string' === typeof value ? value : fallback;
|
|
5
|
+
}
|
|
6
|
+
export function pressKey(args) {
|
|
7
|
+
return pressKeyFromArgs(args);
|
|
8
|
+
}
|
|
9
|
+
export function pressModifiers(args) {
|
|
10
|
+
const raw = args['modifiers'];
|
|
11
|
+
const names = Array.isArray(raw) ? raw.map((m) => asString(m).toLowerCase()) : [];
|
|
12
|
+
const has = (...aliases) => aliases.some((a) => names.includes(a));
|
|
13
|
+
return {
|
|
14
|
+
metaKey: has('meta', 'cmd', 'command', 'super', 'win'),
|
|
15
|
+
ctrlKey: has('control', 'ctrl'),
|
|
16
|
+
shiftKey: has('shift'),
|
|
17
|
+
altKey: has('alt', 'option', 'opt'),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function pressCode(args, key) {
|
|
21
|
+
const explicit = args['code'];
|
|
22
|
+
if ('string' === typeof explicit && 0 < explicit.length)
|
|
23
|
+
return explicit;
|
|
24
|
+
if (' ' === key)
|
|
25
|
+
return 'Space';
|
|
26
|
+
if (1 === key.length) {
|
|
27
|
+
if (/[a-z]/i.test(key))
|
|
28
|
+
return `Key${key.toUpperCase()}`;
|
|
29
|
+
if (/[0-9]/.test(key))
|
|
30
|
+
return `Digit${key}`;
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
return /^[A-Z][A-Za-z0-9]*$/.test(key) && KNOWN_NAMED_KEYS.has(key) ? key : '';
|
|
34
|
+
}
|
|
35
|
+
const KNOWN_NAMED_KEYS = new Set([
|
|
36
|
+
'Enter',
|
|
37
|
+
'Escape',
|
|
38
|
+
'Tab',
|
|
39
|
+
'Backspace',
|
|
40
|
+
'Delete',
|
|
41
|
+
'Home',
|
|
42
|
+
'End',
|
|
43
|
+
'PageUp',
|
|
44
|
+
'PageDown',
|
|
45
|
+
'ArrowUp',
|
|
46
|
+
'ArrowDown',
|
|
47
|
+
'ArrowLeft',
|
|
48
|
+
'ArrowRight',
|
|
49
|
+
'Insert',
|
|
50
|
+
'F1',
|
|
51
|
+
'F2',
|
|
52
|
+
'F3',
|
|
53
|
+
'F4',
|
|
54
|
+
'F5',
|
|
55
|
+
'F6',
|
|
56
|
+
'F7',
|
|
57
|
+
'F8',
|
|
58
|
+
'F9',
|
|
59
|
+
'F10',
|
|
60
|
+
'F11',
|
|
61
|
+
'F12',
|
|
62
|
+
]);
|
|
63
|
+
const NO_DOCUMENT_TO_PRESS = 'no document to press into';
|
|
64
|
+
export function focusedOrDocument() {
|
|
65
|
+
const active = document.activeElement;
|
|
66
|
+
if (isHtmlElement(active) && document.body !== active)
|
|
67
|
+
return active;
|
|
68
|
+
if (isHtmlElement(document.body))
|
|
69
|
+
return document.body;
|
|
70
|
+
const root = document.documentElement;
|
|
71
|
+
if (isHtmlElement(root))
|
|
72
|
+
return root;
|
|
73
|
+
throw new Error(NO_DOCUMENT_TO_PRESS);
|
|
74
|
+
}
|
|
75
|
+
export function isReflessDocumentPress(ref, action, args) {
|
|
76
|
+
return 0 === ref.length && isGlobalPress(args) && ActionType.PRESS === action;
|
|
77
|
+
}
|
|
@@ -1,94 +1,30 @@
|
|
|
1
1
|
import { SettleReason } from '@reticlehq/core';
|
|
2
|
-
/**
|
|
3
|
-
* Best-effort evidence of whether/why an action landed, so the agent can separate
|
|
4
|
-
* "my action missed" vs "app didn't react" vs "tool didn't dispatch". All probes are
|
|
5
|
-
* cheap and best-effort (see docs/usage.md).
|
|
6
|
-
*/
|
|
7
2
|
interface ActionEffect {
|
|
8
|
-
/** We reached dispatch (no throw before it). Typed `true`: if we never dispatch we throw. */
|
|
9
3
|
dispatched: true;
|
|
10
|
-
/** Ref resolved to a still-connected element at read time. */
|
|
11
4
|
targetMatched: boolean;
|
|
12
|
-
/** a11y isVisible(el) at the start of the action. */
|
|
13
5
|
visible: boolean;
|
|
14
|
-
/** Not disabled / aria-disabled at the start of the action. */
|
|
15
6
|
enabled: boolean;
|
|
16
|
-
/** The primary cancelable event's dispatchEvent returned false (handler called preventDefault). */
|
|
17
7
|
defaultPrevented: boolean;
|
|
18
|
-
/** "<prevRef>-><newRef>" if document.activeElement changed, else null. body counts as null. */
|
|
19
8
|
focusMoved: string | null;
|
|
20
|
-
/** fill/type/clear only: input value before !== after; else false. */
|
|
21
9
|
valueChanged: boolean;
|
|
22
|
-
/** Mutation records counted by a short-lived MutationObserver (one microtask + rAF window). */
|
|
23
10
|
domMutatedWithin: number;
|
|
24
|
-
/**
|
|
25
|
-
* Text the action put on the page, truncated. OMITTED when it added none.
|
|
26
|
-
*
|
|
27
|
-
* `domMutatedWithin` says the click did SOMETHING; it cannot say the app rejected you. A login
|
|
28
|
-
* that fails returns ok/settled/mutated and reads exactly like one that succeeded, so an agent
|
|
29
|
-
* moves on and asserts against a page it misread. The observer already receives these records
|
|
30
|
-
* and was keeping only `.length` — the message was being thrown away, not gathered.
|
|
31
|
-
*
|
|
32
|
-
* Deliberately NOT a verdict: this is what appeared, not what it means. Absence means no text
|
|
33
|
-
* was added, which is why a silent class toggle omits the key rather than reporting "".
|
|
34
|
-
*/
|
|
35
11
|
appeared?: string;
|
|
36
|
-
/**
|
|
37
|
-
* Click-like only: the center hit-tested to a foreign element (an overlay is on top). Synthetic
|
|
38
|
-
* dispatch STILL delivered the event to the target, but a real user could not click it — treat
|
|
39
|
-
* the target as visually blocked. `false` when not click-like or not hit-testable (no layout).
|
|
40
|
-
*/
|
|
41
12
|
occluded: boolean;
|
|
42
|
-
/** Ref of the element actually on top at the click point when `occluded`, else null. */
|
|
43
13
|
occludedBy: string | null;
|
|
44
|
-
/** Click-like only: the target was off-viewport, so it was scrolled into view before dispatch. */
|
|
45
14
|
scrolledIntoView: boolean;
|
|
46
|
-
/**
|
|
47
|
-
* How long the pointer was ACTUALLY held down, for a click with `args.holdMs`. Absent otherwise.
|
|
48
|
-
*
|
|
49
|
-
* Measured, not echoed. `holdMs: 1200` against a 1200ms animation is a race by construction, so a
|
|
50
|
-
* caller needs to tell "held 1200" from "held 1204" — and a backgrounded tab throttles timers, so
|
|
51
|
-
* the achieved hold can legitimately be much longer than the one requested.
|
|
52
|
-
*/
|
|
53
15
|
heldMs?: number;
|
|
54
|
-
/**
|
|
55
|
-
* check/uncheck only: the control ALREADY read as the requested state, so NOTHING was dispatched.
|
|
56
|
-
* OMITTED otherwise (the uninformative default).
|
|
57
|
-
*
|
|
58
|
-
* Not clicking is right — clicking an already-checked box would uncheck it. But the branch that
|
|
59
|
-
* skips the click reads the DOM PROPERTY, and that is not evidence about what the APPLICATION
|
|
60
|
-
* holds. They come apart exactly when it matters: a default-checked input the app has not
|
|
61
|
-
* committed, a property written earlier by something else, a control read mid-render. Then `check`
|
|
62
|
-
* no-ops, the framework never hears, and every later read agrees because every later read is also
|
|
63
|
-
* reading the DOM — reported from the field as a form whose flag was unset in the database while
|
|
64
|
-
* Reticle confirmed it checked.
|
|
65
|
-
*
|
|
66
|
-
* So this is a caveat, not a failure: the requested state is what the DOM shows, and Reticle did
|
|
67
|
-
* not put it there. Assert the app's own state (a signal, a request, a derived control) rather than
|
|
68
|
-
* the box.
|
|
69
|
-
*/
|
|
70
16
|
alreadyAtValue?: true;
|
|
71
17
|
}
|
|
72
18
|
interface ActionResult {
|
|
73
19
|
ok: true;
|
|
74
20
|
ref: string;
|
|
75
21
|
action: string;
|
|
76
|
-
/** We reached dispatch without throwing. Mirror of effect.dispatched for top-level reads. */
|
|
77
22
|
dispatched: boolean;
|
|
78
|
-
/** A real animation frame fired within the settle budget (false = fallback timer fired). */
|
|
79
23
|
settled: boolean;
|
|
80
|
-
/** Why settle did not complete on a real frame: SettleReason.TIMEOUT when the budget fallback fired, else null. */
|
|
81
24
|
settleReason: SettleReason | null;
|
|
82
25
|
effect: ActionEffect;
|
|
83
|
-
/** data-testid of the resolved element, when present — lets the server compile a ref-stable replay step. */
|
|
84
26
|
testid?: string;
|
|
85
|
-
/**
|
|
86
|
-
* Auto-anchor fallback (attached ONLY when there is no testid): the element's nearest component
|
|
87
|
-
* name + source location, so the server can compile a stable `component` anchor instead of a
|
|
88
|
-
* degraded role one — addressing the element across replays with zero hand-added testids.
|
|
89
|
-
*/
|
|
90
27
|
component?: string;
|
|
91
|
-
/** Role + accessible name — the anchor that identifies an INSTANCE, not a JSX site. */
|
|
92
28
|
role?: string;
|
|
93
29
|
name?: string;
|
|
94
30
|
source?: {
|
|
@@ -96,28 +32,10 @@ interface ActionResult {
|
|
|
96
32
|
line: number;
|
|
97
33
|
column?: number;
|
|
98
34
|
};
|
|
99
|
-
/** Best-effort caveat the agent should heed (e.g. synthetic hover may not fire enter/leave). */
|
|
100
35
|
warning?: string;
|
|
101
36
|
}
|
|
102
|
-
/**
|
|
103
|
-
* Dispatch the action's events. Returns `defaultPrevented` of the primary cancelable event
|
|
104
|
-
* (false for actions whose primary event is non-cancelable or unobservable). Drag/hover-hold
|
|
105
|
-
* await internally; the probe wrapper builds the result once they resolve.
|
|
106
|
-
*/
|
|
107
|
-
/**
|
|
108
|
-
* The longest a single action may hold the pointer down, in milliseconds.
|
|
109
|
-
*
|
|
110
|
-
* An unbounded `holdMs` is a tool call that never returns, which is the failure the transport layer
|
|
111
|
-
* is shaped around. 30s is far above any real hold-to-confirm (the reported case was 1.2s) and far
|
|
112
|
-
* below a hang.
|
|
113
|
-
*/
|
|
114
37
|
export declare const MAX_HOLD_MS = 30000;
|
|
115
|
-
/**
|
|
116
|
-
* Execute a single action against a ref and probe for best-effort evidence of effect.
|
|
117
|
-
* Always async: the MutationObserver read needs a microtask + rAF after dispatch.
|
|
118
|
-
*/
|
|
119
38
|
export declare function executeAction(ref: string, action: string, args?: Record<string, unknown>): Promise<ActionResult>;
|
|
120
|
-
/** Best-effort WebMCP passthrough: call a navigator.modelContext tool if the site exposes one. */
|
|
121
39
|
export declare function dispatchWebMcp(tool: string, params: Record<string, unknown>, confirmDangerous?: boolean): Promise<unknown>;
|
|
122
40
|
export interface ActionStep {
|
|
123
41
|
ref: string;
|
|
@@ -127,15 +45,9 @@ export interface ActionStep {
|
|
|
127
45
|
interface SequenceStepResult {
|
|
128
46
|
ref: string;
|
|
129
47
|
action: string;
|
|
130
|
-
/** per-step dispatch/settle outcome (see ActionResult). */
|
|
131
48
|
dispatched: boolean;
|
|
132
49
|
settled: boolean;
|
|
133
50
|
settleReason: SettleReason | null;
|
|
134
|
-
/**
|
|
135
|
-
* The SAME anchors a single act reports. Reporting only the testid meant a testid-less app
|
|
136
|
-
* compiled every sub-step to a volatile ref, so the saved flow carried the degraded sentinel and
|
|
137
|
-
* every replay drifted.
|
|
138
|
-
*/
|
|
139
51
|
testid?: string;
|
|
140
52
|
component?: string;
|
|
141
53
|
role?: string;
|
|
@@ -145,7 +57,6 @@ interface SequenceStepResult {
|
|
|
145
57
|
line: number;
|
|
146
58
|
column?: number;
|
|
147
59
|
};
|
|
148
|
-
/** best-effort caveat for this step (e.g. synthetic hover may not fire enter/leave). */
|
|
149
60
|
warning?: string;
|
|
150
61
|
}
|
|
151
62
|
export declare function executeSequence(steps: ActionStep[]): Promise<{
|