@reticlehq/browser 2.13.1 → 3.1.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 +8 -20
- package/dist/actions/actions-dom.js +70 -69
- package/dist/actions/actions-press.d.ts +14 -0
- package/dist/actions/actions-press.js +123 -0
- package/dist/actions/actions.d.ts +1 -89
- package/dist/actions/actions.js +64 -365
- package/dist/actions/appeared-text.d.ts +0 -20
- package/dist/actions/appeared-text.js +6 -36
- package/dist/actions/danger-context.d.ts +3 -0
- package/dist/actions/danger-context.js +39 -0
- package/dist/actions/scroll.d.ts +8 -10
- package/dist/actions/scroll.js +28 -17
- package/dist/actions/synthetic/synthetic-input.d.ts +2 -0
- package/dist/actions/synthetic/synthetic-input.js +13 -0
- 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 +10 -70
- package/dist/connect-options.d.ts +2 -96
- package/dist/dom/a11y.d.ts +0 -29
- package/dist/dom/a11y.js +3 -90
- package/dist/dom/addressing/auto-anchor.d.ts +25 -0
- package/dist/dom/addressing/auto-anchor.js +41 -0
- package/dist/dom/addressing/refs.d.ts +14 -0
- package/dist/dom/addressing/refs.js +114 -0
- package/dist/dom/addressing/source.d.ts +9 -0
- package/dist/dom/addressing/source.js +29 -0
- 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 +1 -73
- package/dist/dom/dom-ignore.d.ts +0 -30
- package/dist/dom/dom-ignore.js +1 -48
- package/dist/dom/occlusion.d.ts +0 -7
- package/dist/dom/occlusion.js +31 -12
- package/dist/dom/paint-context.d.ts +6 -0
- package/dist/dom/paint-context.js +38 -0
- package/dist/dom/query.d.ts +0 -14
- package/dist/dom/query.js +50 -216
- package/dist/dom/realm.d.ts +0 -20
- package/dist/dom/realm.js +0 -28
- 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 +27 -152
- 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 +1 -23
- package/dist/edit-epoch.js +1 -53
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -28
- package/dist/observers/animation.d.ts +0 -1
- package/dist/observers/animation.js +1 -4
- package/dist/observers/blind-spots.d.ts +0 -26
- package/dist/observers/blind-spots.js +3 -62
- 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 +28 -52
- package/dist/observers/download.d.ts +1 -2
- package/dist/observers/download.js +39 -56
- 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 +2 -34
- 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/net-detail/network-body.d.ts +12 -0
- package/dist/observers/net-detail/network-body.js +71 -0
- package/dist/observers/net-detail/network-redact.d.ts +1 -0
- package/dist/observers/net-detail/network-redact.js +1 -0
- package/dist/observers/network-stream.d.ts +0 -21
- package/dist/observers/network-stream.js +3 -46
- package/dist/observers/network.d.ts +3 -27
- package/dist/observers/network.js +178 -295
- 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 +2 -11
- 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/chrome/layers.d.ts +2 -0
- package/dist/presenter/chrome/layers.js +2 -0
- package/dist/presenter/chrome/log-kinds.d.ts +12 -0
- package/dist/presenter/chrome/log-kinds.js +7 -0
- package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -1
- package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -1
- package/dist/presenter/chrome/presenter-brand.d.ts +3 -0
- package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -21
- package/dist/presenter/chrome/presenter-effects.d.ts +4 -0
- package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -10
- package/dist/presenter/chrome/presenter-hud-chrome.d.ts +7 -0
- package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -10
- package/dist/presenter/chrome/presenter-log.d.ts +17 -0
- package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +5 -54
- package/dist/presenter/chrome/presenter-report-copy.d.ts +51 -0
- package/dist/presenter/chrome/presenter-report-copy.js +95 -0
- package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
- package/dist/presenter/chrome/presenter-safe-html.js +16 -0
- package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -5
- package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -5
- package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -7
- package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -8
- package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -8
- package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -6
- package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -6
- package/dist/presenter/presenter-account-styles.d.ts +1 -0
- package/dist/presenter/presenter-account-styles.js +99 -0
- package/dist/presenter/presenter-account.d.ts +40 -0
- package/dist/presenter/presenter-account.js +178 -0
- package/dist/presenter/presenter-config.d.ts +10 -107
- package/dist/presenter/presenter-config.js +5 -92
- package/dist/presenter/presenter-controls.d.ts +1 -35
- package/dist/presenter/presenter-controls.js +10 -133
- package/dist/presenter/presenter-dock-layout.d.ts +0 -3
- package/dist/presenter/presenter-dock-layout.js +1 -5
- package/dist/presenter/presenter-drag.d.ts +2 -42
- package/dist/presenter/presenter-drag.js +3 -76
- package/dist/presenter/presenter-glow.d.ts +0 -21
- package/dist/presenter/presenter-glow.js +2 -28
- package/dist/presenter/presenter-hud-position.d.ts +18 -0
- package/dist/presenter/presenter-hud-position.js +50 -0
- package/dist/presenter/presenter-report-styles.d.ts +0 -6
- package/dist/presenter/presenter-report-styles.js +22 -7
- package/dist/presenter/presenter-report.d.ts +5 -7
- package/dist/presenter/presenter-report.js +44 -95
- package/dist/presenter/presenter-run-state.d.ts +1 -8
- package/dist/presenter/presenter-run-state.js +1 -6
- package/dist/presenter/presenter-settings-styles.d.ts +0 -1
- package/dist/presenter/presenter-settings-styles.js +2 -2
- package/dist/presenter/presenter-settings.d.ts +5 -23
- package/dist/presenter/presenter-settings.js +21 -33
- package/dist/presenter/presenter-shell-styles.js +23 -20
- package/dist/presenter/presenter-shell.d.ts +6 -18
- package/dist/presenter/presenter-shell.js +26 -77
- package/dist/presenter/presenter-styles.d.ts +2 -10
- package/dist/presenter/presenter-styles.js +6 -12
- package/dist/presenter/presenter-workspace.d.ts +4 -4
- package/dist/presenter/presenter-workspace.js +30 -10
- package/dist/presenter/presenter.d.ts +3 -63
- package/dist/presenter/presenter.js +29 -132
- package/dist/presenter/tour/tour-view.d.ts +33 -0
- package/dist/presenter/tour/tour-view.js +188 -0
- package/dist/presenter/tour/tour.d.ts +25 -0
- package/dist/presenter/tour/tour.js +245 -0
- 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 +6 -49
- 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/declared-channels.d.ts +2 -0
- package/dist/registry/declared-channels.js +14 -0
- package/dist/registry/declared-platform.d.ts +4 -0
- package/dist/registry/declared-platform.js +34 -0
- 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 +1 -11
- package/dist/registry/store-adapters.d.ts +0 -186
- package/dist/registry/store-adapters.js +1 -164
- package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -10
- package/dist/registry/{adapters.js → stores/adapters.js} +0 -10
- package/dist/registry/stores.d.ts +0 -33
- package/dist/registry/stores.js +1 -60
- package/dist/reticle-presenter-helpers.d.ts +0 -13
- package/dist/reticle-presenter-helpers.js +10 -20
- package/dist/reticle-singleton.d.ts +2 -0
- package/dist/reticle-singleton.js +3 -0
- package/dist/reticle.d.ts +1 -52
- package/dist/reticle.js +115 -180
- package/dist/review/annotator-styles.js +1 -8
- package/dist/review/annotator.d.ts +2 -13
- package/dist/review/annotator.js +2 -42
- package/dist/review/mark-anchor.d.ts +0 -11
- package/dist/review/mark-anchor.js +3 -11
- 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/console/native-console.js +5 -0
- package/dist/timers/native/native-timers.d.ts +11 -0
- package/dist/timers/native/native-timers.js +48 -0
- package/dist/transport/transport.d.ts +0 -51
- package/dist/transport/transport.js +5 -131
- 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 +5 -4
- package/dist/actions/synthetic-input.d.ts +0 -9
- package/dist/actions/synthetic-input.js +0 -40
- package/dist/dom/auto-anchor.d.ts +0 -65
- package/dist/dom/auto-anchor.js +0 -74
- package/dist/dom/refs.d.ts +0 -90
- package/dist/dom/refs.js +0 -245
- package/dist/dom/source.d.ts +0 -53
- package/dist/dom/source.js +0 -74
- package/dist/observers/network-body.d.ts +0 -35
- package/dist/observers/network-body.js +0 -168
- package/dist/observers/network-redact.d.ts +0 -20
- package/dist/observers/network-redact.js +0 -80
- package/dist/presenter/presenter-brand.d.ts +0 -22
- package/dist/presenter/presenter-effects.d.ts +0 -13
- package/dist/presenter/presenter-hud-chrome.d.ts +0 -17
- package/dist/presenter/presenter-log.d.ts +0 -64
- package/dist/presenter/presenter-report-copy.d.ts +0 -112
- package/dist/presenter/presenter-report-copy.js +0 -156
- package/dist/presenter/presenter-test-helpers.d.ts +0 -16
- package/dist/presenter/presenter-test-helpers.js +0 -41
- package/dist/timers/native-console.js +0 -9
- package/dist/timers/native-timers.d.ts +0 -36
- package/dist/timers/native-timers.js +0 -78
- /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
- /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
|
@@ -1,26 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gathers the text an action put on the page, from mutation records the observer already receives.
|
|
3
|
-
*
|
|
4
|
-
* `domMutatedWithin` counts records and throws their content away, so a failed login reports
|
|
5
|
-
* `ok / settled / mutated` and reads exactly like a successful one. The message the app rendered
|
|
6
|
-
* was passing through this callback the whole time.
|
|
7
|
-
*
|
|
8
|
-
* Deliberately reports what appeared, never what it means: no error/success classification, no
|
|
9
|
-
* guessing which fragment matters. Truncated, because a route change can add a whole page.
|
|
10
|
-
*/
|
|
11
1
|
export declare class AppearedText {
|
|
12
2
|
#private;
|
|
13
3
|
collect(records: readonly MutationRecord[]): void;
|
|
14
|
-
/**
|
|
15
|
-
* `{ appeared }` when the APP added text, `{}` otherwise — an absent key means it added none.
|
|
16
|
-
*
|
|
17
|
-
* `wrote` is the value the action itself just set, and is excluded: a textarea carries its value
|
|
18
|
-
* in a child text node, so a controlled one re-rendering after the write mutates characterData
|
|
19
|
-
* with the caller's own string. Handing that back is noise wearing the name of evidence, and
|
|
20
|
-
* `valueChanged` already reports that the write landed. Exact-match only, so an app that quotes
|
|
21
|
-
* your input inside a sentence of its own ("No results for zzz") is still reported — that is
|
|
22
|
-
* the app talking.
|
|
23
|
-
*/
|
|
24
4
|
effect(wrote?: string): {
|
|
25
5
|
appeared?: string;
|
|
26
6
|
};
|
|
@@ -1,34 +1,15 @@
|
|
|
1
1
|
import { isIgnored } from '../dom/dom-ignore.js';
|
|
2
|
-
/** Cap on the reported text. Enough for an error message, far short of a re-rendered page. */
|
|
3
2
|
const APPEARED_MAX = 200;
|
|
4
|
-
/** Separator between distinct fragments that appeared in the same window. */
|
|
5
3
|
const JOIN = ' | ';
|
|
6
4
|
const TEXT_NODE = 3;
|
|
7
|
-
/** Any letter, in any script — Latin, Cyrillic, CJK, Arabic. */
|
|
8
5
|
const HAS_LETTER = /\p{L}/u;
|
|
9
|
-
/**
|
|
10
|
-
* A fragment with no letters at all is not the app saying something.
|
|
11
|
-
*
|
|
12
|
-
* The Hostile fixture mutates a counter every 16ms, and clicking a button on that page reported
|
|
13
|
-
* `appeared: "409"` — the ticker, not the action's effect. A count-up animation is exactly what
|
|
14
|
-
* emits a bare number into the settle window, and a bare number carries no message a reader can
|
|
15
|
-
* act on. Deliberately conservative: it drops only fragments with NO letters, so "status 500",
|
|
16
|
-
* "3 items deleted" and "Could not save" all survive. An app whose only feedback is a naked
|
|
17
|
-
* numeral loses it here — the snapshot still shows it, and that is the cheaper mistake.
|
|
18
|
-
*/
|
|
19
6
|
function saysSomething(text) {
|
|
20
7
|
return HAS_LETTER.test(text);
|
|
21
8
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
* `ok / settled / mutated` and reads exactly like a successful one. The message the app rendered
|
|
27
|
-
* was passing through this callback the whole time.
|
|
28
|
-
*
|
|
29
|
-
* Deliberately reports what appeared, never what it means: no error/success classification, no
|
|
30
|
-
* guessing which fragment matters. Truncated, because a route change can add a whole page.
|
|
31
|
-
*/
|
|
9
|
+
const MAX_MESSAGE_ELEMENTS = 3;
|
|
10
|
+
function isRenderedView(node) {
|
|
11
|
+
return node instanceof Element && node.querySelectorAll('*').length > MAX_MESSAGE_ELEMENTS;
|
|
12
|
+
}
|
|
32
13
|
export class AppearedText {
|
|
33
14
|
#seen = new Set();
|
|
34
15
|
#length = 0;
|
|
@@ -37,28 +18,19 @@ export class AppearedText {
|
|
|
37
18
|
if (this.#full())
|
|
38
19
|
return;
|
|
39
20
|
if ('characterData' === record.type) {
|
|
40
|
-
// The NEW value; oldValue is not requested, so this is what the reader now sees.
|
|
41
21
|
this.#add(record.target.textContent, record.target.parentElement);
|
|
42
22
|
continue;
|
|
43
23
|
}
|
|
44
24
|
for (const node of record.addedNodes) {
|
|
45
25
|
if (this.#full())
|
|
46
26
|
return;
|
|
27
|
+
if (isRenderedView(node))
|
|
28
|
+
continue;
|
|
47
29
|
const owner = TEXT_NODE === node.nodeType ? node.parentElement : elementOf(node);
|
|
48
30
|
this.#add(node.textContent, owner);
|
|
49
31
|
}
|
|
50
32
|
}
|
|
51
33
|
}
|
|
52
|
-
/**
|
|
53
|
-
* `{ appeared }` when the APP added text, `{}` otherwise — an absent key means it added none.
|
|
54
|
-
*
|
|
55
|
-
* `wrote` is the value the action itself just set, and is excluded: a textarea carries its value
|
|
56
|
-
* in a child text node, so a controlled one re-rendering after the write mutates characterData
|
|
57
|
-
* with the caller's own string. Handing that back is noise wearing the name of evidence, and
|
|
58
|
-
* `valueChanged` already reports that the write landed. Exact-match only, so an app that quotes
|
|
59
|
-
* your input inside a sentence of its own ("No results for zzz") is still reported — that is
|
|
60
|
-
* the app talking.
|
|
61
|
-
*/
|
|
62
34
|
effect(wrote) {
|
|
63
35
|
const said = [...this.#seen].filter((text) => text !== wrote);
|
|
64
36
|
if (0 === said.length)
|
|
@@ -72,8 +44,6 @@ export class AppearedText {
|
|
|
72
44
|
return this.#length > APPEARED_MAX;
|
|
73
45
|
}
|
|
74
46
|
#add(raw, owner) {
|
|
75
|
-
// Reticle's own overlay mutates constantly (the "live" panel); reporting it as the app's
|
|
76
|
-
// response would be worse than reporting nothing.
|
|
77
47
|
if (null !== owner && isIgnored(owner))
|
|
78
48
|
return;
|
|
79
49
|
const text = (raw ?? '').replace(/\s+/g, ' ').trim();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isDangerousActionText } from '@reticlehq/core';
|
|
2
|
+
import { getAccessibleName } from '../dom/a11y.js';
|
|
3
|
+
const LABEL_VALUED_INPUT_TYPES = new Set([
|
|
4
|
+
'submit',
|
|
5
|
+
'button',
|
|
6
|
+
'reset',
|
|
7
|
+
'image',
|
|
8
|
+
]);
|
|
9
|
+
function holdsUserText(el) {
|
|
10
|
+
if (el.isContentEditable)
|
|
11
|
+
return true;
|
|
12
|
+
if (el instanceof HTMLTextAreaElement)
|
|
13
|
+
return true;
|
|
14
|
+
if (el instanceof HTMLInputElement)
|
|
15
|
+
return !LABEL_VALUED_INPUT_TYPES.has(el.type.toLowerCase());
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
export function dangerousActionContext(el) {
|
|
19
|
+
const form = el.closest('form');
|
|
20
|
+
const ownText = holdsUserText(el)
|
|
21
|
+
? ['', '']
|
|
22
|
+
: [el.textContent ?? '', el.getAttribute('value') ?? ''];
|
|
23
|
+
return [
|
|
24
|
+
getAccessibleName(el),
|
|
25
|
+
...ownText,
|
|
26
|
+
el.getAttribute('title') ?? '',
|
|
27
|
+
el.getAttribute('aria-label') ?? '',
|
|
28
|
+
el.getAttribute('href') ?? '',
|
|
29
|
+
form?.getAttribute('action') ?? '',
|
|
30
|
+
].join(' ');
|
|
31
|
+
}
|
|
32
|
+
const SUBMIT_CONTROL_SELECTOR = 'button[type="submit"], input[type="submit"], button:not([type]):not([type=""])';
|
|
33
|
+
export function submitControlFor(el) {
|
|
34
|
+
const found = el.closest('form')?.querySelector(SUBMIT_CONTROL_SELECTOR);
|
|
35
|
+
return found instanceof HTMLElement ? found : null;
|
|
36
|
+
}
|
|
37
|
+
export function requiresDangerousConfirmation(text, role) {
|
|
38
|
+
return isDangerousActionText(text, role);
|
|
39
|
+
}
|
package/dist/actions/scroll.d.ts
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
/** Outcome of one container scroll — enough for the server to drive a find loop. */
|
|
2
1
|
interface ScrollResult {
|
|
3
|
-
/** scrollTop actually moved (false ⇒ already at the end / not scrollable). */
|
|
4
2
|
scrolled: boolean;
|
|
5
3
|
scrollTop: number;
|
|
6
4
|
scrollHeight: number;
|
|
7
5
|
clientHeight: number;
|
|
8
|
-
/** At (or within 1px of) the bottom — no more rows to reveal by scrolling down. */
|
|
9
6
|
atEnd: boolean;
|
|
7
|
+
atStart: boolean;
|
|
8
|
+
scrollLeft: number;
|
|
9
|
+
scrollWidth: number;
|
|
10
|
+
clientWidth: number;
|
|
11
|
+
atLeftEnd: boolean;
|
|
12
|
+
atRightEnd: boolean;
|
|
10
13
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* virtualized list mounts its next window of rows. If `fraction` (0–1) is supplied, jumps
|
|
14
|
-
* directly to that fraction of scrollHeight instead of stepping — enables bisection for large
|
|
15
|
-
* lists where a specific row index is known (targetIndex / totalCount = fraction).
|
|
16
|
-
*/
|
|
17
|
-
export declare function scrollContainer(ref: string | undefined, dy: number | undefined, fraction?: number): ScrollResult;
|
|
14
|
+
export declare function scrollContainer(ref: string | undefined, dy: number | undefined, fraction?: number, dx?: number): ScrollResult;
|
|
15
|
+
export declare function scrollFrom(base: Element | undefined, dy: number | undefined, fraction?: number, dx?: number): ScrollResult;
|
|
18
16
|
export {};
|
package/dist/actions/scroll.js
CHANGED
|
@@ -1,43 +1,54 @@
|
|
|
1
|
-
import { refs } from '../dom/refs.js';
|
|
1
|
+
import { refs } from '../dom/addressing/refs.js';
|
|
2
2
|
const FALLBACK_STEP_PX = 400;
|
|
3
3
|
const VIEWPORT_FRACTION = 0.8;
|
|
4
|
-
|
|
5
|
-
function nearestScrollable(el) {
|
|
4
|
+
function nearestScrollable(el, axis) {
|
|
6
5
|
let cur = el;
|
|
7
6
|
while (cur !== null && cur !== document.body) {
|
|
8
|
-
const
|
|
9
|
-
|
|
7
|
+
const style = getComputedStyle(cur);
|
|
8
|
+
const overflow = 'x' === axis ? style.overflowX : style.overflowY;
|
|
9
|
+
const scrollable = 'x' === axis ? cur.scrollWidth > cur.clientWidth : cur.scrollHeight > cur.clientHeight;
|
|
10
|
+
if (('auto' === overflow || 'scroll' === overflow) && scrollable)
|
|
10
11
|
return cur;
|
|
11
12
|
cur = cur.parentElement;
|
|
12
13
|
}
|
|
13
14
|
return document.scrollingElement ?? document.documentElement;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
* Scroll the container of `ref` (or the document) down by `dy` (default ~one viewport) so a
|
|
17
|
-
* virtualized list mounts its next window of rows. If `fraction` (0–1) is supplied, jumps
|
|
18
|
-
* directly to that fraction of scrollHeight instead of stepping — enables bisection for large
|
|
19
|
-
* lists where a specific row index is known (targetIndex / totalCount = fraction).
|
|
20
|
-
*/
|
|
21
|
-
export function scrollContainer(ref, dy, fraction) {
|
|
16
|
+
export function scrollContainer(ref, dy, fraction, dx) {
|
|
22
17
|
const base = ref !== undefined ? refs.resolve(ref) : null;
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
return scrollFrom(base instanceof Element ? base : undefined, dy, fraction, dx);
|
|
19
|
+
}
|
|
20
|
+
export function scrollFrom(base, dy, fraction, dx) {
|
|
21
|
+
const axis = dx !== undefined && dx !== 0 && (dy === undefined || 0 === dy) ? 'x' : 'y';
|
|
22
|
+
const target = base !== undefined
|
|
23
|
+
? nearestScrollable(base, axis)
|
|
25
24
|
: (document.scrollingElement ?? document.documentElement);
|
|
26
25
|
const before = target.scrollTop;
|
|
26
|
+
const beforeLeft = target.scrollLeft;
|
|
27
27
|
if (fraction !== undefined && fraction >= 0 && fraction <= 1) {
|
|
28
28
|
target.scrollTop = Math.round(target.scrollHeight * fraction);
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
if (dx !== undefined && dx !== 0)
|
|
32
|
+
target.scrollLeft = beforeLeft + dx;
|
|
33
|
+
if (dx === undefined || (dy !== undefined && 0 !== dy)) {
|
|
34
|
+
const step = dy ?? (Math.round(target.clientHeight * VIEWPORT_FRACTION) || FALLBACK_STEP_PX);
|
|
35
|
+
target.scrollTop = before + step;
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
target.dispatchEvent(new Event('scroll', { bubbles: false }));
|
|
35
39
|
const after = target.scrollTop;
|
|
40
|
+
const afterLeft = target.scrollLeft;
|
|
36
41
|
return {
|
|
37
|
-
scrolled: after !== before,
|
|
42
|
+
scrolled: after !== before || afterLeft !== beforeLeft,
|
|
38
43
|
scrollTop: after,
|
|
39
44
|
scrollHeight: target.scrollHeight,
|
|
40
45
|
clientHeight: target.clientHeight,
|
|
41
46
|
atEnd: after + target.clientHeight >= target.scrollHeight - 1,
|
|
47
|
+
atStart: after <= 1,
|
|
48
|
+
scrollLeft: afterLeft,
|
|
49
|
+
scrollWidth: target.scrollWidth,
|
|
50
|
+
clientWidth: target.clientWidth,
|
|
51
|
+
atLeftEnd: afterLeft <= 1,
|
|
52
|
+
atRightEnd: afterLeft + target.clientWidth >= target.scrollWidth - 1,
|
|
42
53
|
};
|
|
43
54
|
}
|
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Writing a value into a field, and the two preconditions that make it honest.
|
|
3
|
-
*
|
|
4
|
-
* Lifted out of actions.ts when that file hit the 600-line cap. It is a cohesive unit: every export
|
|
5
|
-
* here exists so that a value write either lands as a real user's edit would, or is refused with a
|
|
6
|
-
* reason — never forced through while reporting success.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Set a value on a controlled input the way React expects (native setter + input event).
|
|
10
|
-
* Returns the `input` event's dispatchEvent result (defaultPrevented of the primary event).
|
|
11
|
-
*/
|
|
12
1
|
export declare function setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string): boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Refuse to write to a field a person could not edit.
|
|
15
|
-
*
|
|
16
|
-
* `readonly` and `disabled` block USER input, not scripted assignment, so the prototype value setter
|
|
17
|
-
* writes straight through both. Measured: filling a readonly input reported `ok:true` and
|
|
18
|
-
* `valueChanged:true` with nothing in the effect block marking it read-only. That leaves the app in a
|
|
19
|
-
* state no user could reach, and every conclusion drawn from what follows is about software nobody
|
|
20
|
-
* can operate — the same reason the click path reports `occluded`, and the same choice Playwright
|
|
21
|
-
* makes when it refuses to fill a non-editable element.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* A rich-text target is UNSUPPORTED, not a mistake by the caller.
|
|
25
|
-
*
|
|
26
|
-
* Every rich-text editor (TipTap, Quill, ProseMirror, Slate, Lexical) is `[contenteditable]`, and
|
|
27
|
-
* these actions handle only input/textarea. `cannot fill a <div>` reads as "you picked the wrong
|
|
28
|
-
* element" and sends the reader hunting for a better selector that does not exist. Support is not
|
|
29
|
-
* faked: assigning textContent would update the DOM while the editor's own model kept the old value,
|
|
30
|
-
* so the tool would report success for content the app will never submit.
|
|
31
|
-
*/
|
|
32
2
|
export declare function assertNotRichText(el: HTMLElement, action: string): void;
|
|
33
3
|
export declare function assertEditable(el: HTMLInputElement | HTMLTextAreaElement, action: string): void;
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import { captureValueSetter } from '../patching/capture-method.js';
|
|
2
2
|
import { valuePrototypeOf } from '../dom/realm.js';
|
|
3
|
-
/**
|
|
4
|
-
* Writing a value into a field, and the two preconditions that make it honest.
|
|
5
|
-
*
|
|
6
|
-
* Lifted out of actions.ts when that file hit the 600-line cap. It is a cohesive unit: every export
|
|
7
|
-
* here exists so that a value write either lands as a real user's edit would, or is refused with a
|
|
8
|
-
* reason — never forced through while reporting success.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Set a value on a controlled input the way React expects (native setter + input event).
|
|
12
|
-
* Returns the `input` event's dispatchEvent result (defaultPrevented of the primary event).
|
|
13
|
-
*/
|
|
14
3
|
export function setNativeValue(el, value) {
|
|
15
|
-
// Realm-aware: a controlled input inside a same-origin frame has its own prototype.
|
|
16
4
|
const proto = valuePrototypeOf(el);
|
|
17
5
|
const setter = proto === undefined ? undefined : captureValueSetter(proto);
|
|
18
6
|
if (setter !== undefined) {
|
|
@@ -25,28 +13,7 @@ export function setNativeValue(el, value) {
|
|
|
25
13
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
26
14
|
return !notPrevented;
|
|
27
15
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Refuse to write to a field a person could not edit.
|
|
30
|
-
*
|
|
31
|
-
* `readonly` and `disabled` block USER input, not scripted assignment, so the prototype value setter
|
|
32
|
-
* writes straight through both. Measured: filling a readonly input reported `ok:true` and
|
|
33
|
-
* `valueChanged:true` with nothing in the effect block marking it read-only. That leaves the app in a
|
|
34
|
-
* state no user could reach, and every conclusion drawn from what follows is about software nobody
|
|
35
|
-
* can operate — the same reason the click path reports `occluded`, and the same choice Playwright
|
|
36
|
-
* makes when it refuses to fill a non-editable element.
|
|
37
|
-
*/
|
|
38
|
-
/**
|
|
39
|
-
* A rich-text target is UNSUPPORTED, not a mistake by the caller.
|
|
40
|
-
*
|
|
41
|
-
* Every rich-text editor (TipTap, Quill, ProseMirror, Slate, Lexical) is `[contenteditable]`, and
|
|
42
|
-
* these actions handle only input/textarea. `cannot fill a <div>` reads as "you picked the wrong
|
|
43
|
-
* element" and sends the reader hunting for a better selector that does not exist. Support is not
|
|
44
|
-
* faked: assigning textContent would update the DOM while the editor's own model kept the old value,
|
|
45
|
-
* so the tool would report success for content the app will never submit.
|
|
46
|
-
*/
|
|
47
16
|
export function assertNotRichText(el, action) {
|
|
48
|
-
// The ATTRIBUTE, not `isContentEditable`: jsdom does not implement the property, so a test would
|
|
49
|
-
// pass against a guard that never fires in the environment the unit suite runs in.
|
|
50
17
|
const flag = el.getAttribute('contenteditable');
|
|
51
18
|
if (el.isContentEditable || (flag !== null && flag !== 'false')) {
|
|
52
19
|
throw new Error(`cannot ${action} a contenteditable element — rich-text editors keep their own document model, ` +
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export type CommandHandler = (args: Record<string, unknown>) => unknown;
|
|
2
|
-
/** Query param appended on a hard reload to bypass the browser cache. */
|
|
3
2
|
export declare const RELOAD_CACHE_BUST_PARAM = "_reticle_reload";
|
|
4
3
|
export declare function resolveNavigationUrl(rawUrl: string, baseUrl: string): string | null;
|
|
5
|
-
/** Map browser command names to handlers. Used by the transport on each COMMAND. */
|
|
6
4
|
export declare function createCommandRegistry(): Map<string, CommandHandler>;
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { ActionType, ComponentStateReason, DANGEROUS_ACTION_CONFIRM_ARG, ElementQuerySchema, ReticleCommand, SnapshotMode, TRANSPORT_LIMITS, selectPath, capDepth, } from '@reticlehq/core';
|
|
2
2
|
import { buildSnapshot } from '../dom/snapshot.js';
|
|
3
|
+
import { paintContextOf } from '../dom/paint-context.js';
|
|
3
4
|
import { matchQuery, runQuery } from '../dom/query.js';
|
|
4
5
|
import { executeAction, executeSequence, dispatchWebMcp, } from '../actions/actions.js';
|
|
5
6
|
import { describe } from '../dom/a11y.js';
|
|
6
|
-
import { documentHasSourceStamps, sourceFor, formatSource } from '../dom/source.js';
|
|
7
|
+
import { documentHasSourceStamps, sourceFor, formatSource } from '../dom/addressing/source.js';
|
|
7
8
|
import { themeReport } from '../dom/theme.js';
|
|
8
|
-
import { refs } from '../dom/refs.js';
|
|
9
|
+
import { refs } from '../dom/addressing/refs.js';
|
|
9
10
|
import { editEpoch } from '../edit-epoch.js';
|
|
10
11
|
import { isButton, isInput } from '../dom/realm.js';
|
|
11
12
|
import { hitTestOccluder } from '../dom/occlusion.js';
|
|
12
13
|
import { readStorage } from '../observers/storage.js';
|
|
13
14
|
import { captureDesktopWindow } from '../dom/desktop-capture.js';
|
|
14
|
-
import { identifyComponent, readComponentState } from '../registry/adapters.js';
|
|
15
|
+
import { identifyComponent, readComponentState } from '../registry/stores/adapters.js';
|
|
15
16
|
import { readStoresWithTruncation, readStoresRaw, storeNames } from '../registry/stores.js';
|
|
16
17
|
import { sanitizeWithReport } from '../security/serialization.js';
|
|
17
18
|
import { getCapabilities } from '../registry/capabilities.js';
|
|
18
19
|
import { freezeClock, advanceClock, resetClock, isClockFrozen } from '../timers/clock.js';
|
|
19
20
|
import { scrollContainer } from '../actions/scroll.js';
|
|
20
|
-
/** Query param appended on a hard reload to bypass the browser cache. */
|
|
21
21
|
export const RELOAD_CACHE_BUST_PARAM = '_reticle_reload';
|
|
22
22
|
function str(value) {
|
|
23
23
|
return 'string' === typeof value ? value : undefined;
|
|
@@ -49,10 +49,6 @@ function queryFromArgs(args) {
|
|
|
49
49
|
alt: str(args['alt']),
|
|
50
50
|
component: str(args['component']),
|
|
51
51
|
scope: str(args['scope']),
|
|
52
|
-
// The THIRD allowlist a query input has to appear in — tool schema, server forward, and here.
|
|
53
|
-
// `self` was in the first two and missing from this one, so a live call returned zero matches on
|
|
54
|
-
// an element that was plainly on the page, with no error to explain it. Same shape as the
|
|
55
|
-
// `attrs` drop below it.
|
|
56
52
|
self: true === args['self'] ? true : undefined,
|
|
57
53
|
attrs: Array.isArray(args['attrs'])
|
|
58
54
|
? args['attrs'].filter((a) => 'string' === typeof a)
|
|
@@ -62,20 +58,12 @@ function queryFromArgs(args) {
|
|
|
62
58
|
}
|
|
63
59
|
function inspect(ref) {
|
|
64
60
|
const el = refs.resolve(ref);
|
|
65
|
-
// THROW, exactly as executeAction does. Returning an error payload made it a SUCCESSFUL command
|
|
66
|
-
// result, which the server then handed to reticle_inspect's outputSchema (ref/role/name/states/
|
|
67
|
-
// visible all required) — so the MCP layer answered -32602 Output validation error for the most
|
|
68
|
-
// ordinary thing that follows a click. The wording matches actions.ts because the server's
|
|
69
|
-
// recovery table keys off /no longer resolves to an element/ to attach the stale-ref recovery.
|
|
70
61
|
if (null === el)
|
|
71
62
|
throw new Error(editEpoch.staleRefMessage(ref));
|
|
72
63
|
const rect = el.getBoundingClientRect();
|
|
73
64
|
const component = identifyComponent(el);
|
|
74
65
|
const view = el.ownerDocument.defaultView;
|
|
75
66
|
const cs = view !== null ? view.getComputedStyle(el) : null;
|
|
76
|
-
// Computed style the a11y tree is blind to — `cursor` (does it look interactive?), display/
|
|
77
|
-
// visibility, and color/opacity — so a UI bug that leaves the element "present but unusable"
|
|
78
|
-
// (dead cursor, invisible, recolored) is observable in one inspect call.
|
|
79
67
|
const styles = cs !== null
|
|
80
68
|
? {
|
|
81
69
|
color: cs.color,
|
|
@@ -86,16 +74,7 @@ function inspect(ref) {
|
|
|
86
74
|
visibility: cs.visibility,
|
|
87
75
|
}
|
|
88
76
|
: null;
|
|
89
|
-
// `describe()` carries the CHEAP source (the nearest babel-stamped ancestor), because it runs per
|
|
90
|
-
// element on paths that describe hundreds at once. inspect is a single-element path, so it takes
|
|
91
|
-
// the adapter's answer first — the component that actually RENDERED this element, not the nearest
|
|
92
|
-
// stamped host. `act` already did this; inspect did not, so the two tools disagreed about the same
|
|
93
|
-
// ref and inspect — the tool you reach for to ask where something lives — was the one saying null.
|
|
94
77
|
const source = formatSource(sourceFor(el, component?.source));
|
|
95
|
-
// Say WHY the source is missing rather than omitting the field and leaving the caller to guess.
|
|
96
|
-
// No stamp anywhere in the document is decisive: the loader is not running, so no element will
|
|
97
|
-
// have one this session, and the fix is a build-config change rather than anything about this
|
|
98
|
-
// element. Only computed when `source` is already absent, so the ordinary path pays nothing.
|
|
99
78
|
const sourceUnavailable = source !== undefined
|
|
100
79
|
? undefined
|
|
101
80
|
: documentHasSourceStamps(el.ownerDocument)
|
|
@@ -106,31 +85,32 @@ function inspect(ref) {
|
|
|
106
85
|
scrollHeight: el.scrollHeight,
|
|
107
86
|
clientHeight: el.clientHeight,
|
|
108
87
|
overflowY: cs?.overflowY ?? 'visible',
|
|
88
|
+
scrollLeft: el.scrollLeft,
|
|
89
|
+
scrollWidth: el.scrollWidth,
|
|
90
|
+
clientWidth: el.clientWidth,
|
|
91
|
+
overflowX: cs?.overflowX ?? 'visible',
|
|
109
92
|
};
|
|
93
|
+
const paintContext = paintContextOf(el);
|
|
110
94
|
return {
|
|
111
95
|
...describe(el),
|
|
112
96
|
...(source !== undefined ? { source } : {}),
|
|
97
|
+
...(paintContext.length > 0 ? { paintContext } : {}),
|
|
113
98
|
...(sourceUnavailable !== undefined ? { sourceUnavailable } : {}),
|
|
114
99
|
tag: el.tagName.toLowerCase(),
|
|
115
100
|
href: el.getAttribute('href') ?? undefined,
|
|
116
101
|
formAction: isButton(el) || isInput(el) ? (el.form?.getAttribute('action') ?? undefined) : undefined,
|
|
117
102
|
formText: isButton(el) || isInput(el) ? (el.form?.textContent ?? undefined) : undefined,
|
|
118
103
|
box: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
119
|
-
// True when another element sits over this one's center point — the click would hit the overlay,
|
|
120
|
-
// not this control (a z-index/overlay bug the DOM tree cannot show).
|
|
121
104
|
occluded: isOccluded(el, rect),
|
|
122
105
|
styles,
|
|
123
106
|
scroll,
|
|
124
|
-
// Theme compliance vs the app's design tokens (off-theme colors a DOM tool can't judge).
|
|
125
107
|
theme: cs !== null ? themeReport(cs) : null,
|
|
126
108
|
component,
|
|
127
109
|
};
|
|
128
110
|
}
|
|
129
|
-
/** Whether a NON-Reticle element covers this one's center point (a transparent overlay / z-index bug). */
|
|
130
111
|
function isOccluded(el, rect) {
|
|
131
112
|
return hitTestOccluder(el, rect) !== null;
|
|
132
113
|
}
|
|
133
|
-
/** Narrowing guard: an adapter returned a ComponentStateResult (has a boolean `ok`). */
|
|
134
114
|
function isComponentStateResult(value) {
|
|
135
115
|
return ('object' === typeof value && value !== null && 'ok' in value && 'boolean' === typeof value.ok);
|
|
136
116
|
}
|
|
@@ -138,21 +118,8 @@ const COMPONENT_UNAVAILABLE = {
|
|
|
138
118
|
ok: false,
|
|
139
119
|
reason: ComponentStateReason.UNAVAILABLE,
|
|
140
120
|
};
|
|
141
|
-
/**
|
|
142
|
-
* Stores are the reliable path. The `ref` component read is bounded: a stale ref, no
|
|
143
|
-
* adapter, or an adapter returning a non-conforming value all collapse to a structured
|
|
144
|
-
* `{ ok: false, reason }` — never a raw (possibly circular) object that could hang serialization.
|
|
145
|
-
*
|
|
146
|
-
* `path`/`depth` are applied HERE, in the page, BEFORE the value crosses the transport — so a scoped
|
|
147
|
-
* read of a huge store (e.g. `deployments.0.status` on a 500-row store) returns only the small
|
|
148
|
-
* sub-tree and is never truncated. (Previously selection ran server-side, AFTER the whole store had
|
|
149
|
-
* already been size-capped in transit, which silently lost any field after a large array.)
|
|
150
|
-
*/
|
|
151
121
|
function readState(ref, store, path, depth) {
|
|
152
122
|
const names = storeNames();
|
|
153
|
-
// Scoped read: walk `path` into the RAW (uncapped) store, then sanitize only the selected sub-tree.
|
|
154
|
-
// Selecting before the transport cap is what lets a deep/large path (row 250 of a 500-row array)
|
|
155
|
-
// resolve — capping first would truncate the store before selection ever reached the row.
|
|
156
123
|
if (path !== undefined || depth !== undefined) {
|
|
157
124
|
const rawStores = readStoresRaw(store);
|
|
158
125
|
const base = store !== undefined ? rawStores[store] : { stores: rawStores, storeNames: names };
|
|
@@ -164,13 +131,8 @@ function readState(ref, store, path, depth) {
|
|
|
164
131
|
path,
|
|
165
132
|
found: selection.found,
|
|
166
133
|
value: projected.value,
|
|
167
|
-
// Even a SCOPED read can hit the caps when the selected sub-tree is itself large; saying so is
|
|
168
|
-
// what keeps "the list is short" distinguishable from "I shortened the list".
|
|
169
134
|
...(projected.truncation === undefined ? {} : { truncation: projected.truncation }),
|
|
170
135
|
...('availableKeys' in selection ? { availableKeys: selection.availableKeys } : {}),
|
|
171
|
-
// How many keys there REALLY were, when the near-miss list is a sample. Without it, 50 names
|
|
172
|
-
// and no marker reads as "the key you asked for does not exist" — the strongest negative
|
|
173
|
-
// signal there is, and a false one when the key is simply number 51.
|
|
174
136
|
...('totalKeys' in selection ? { totalKeys: selection.totalKeys } : {}),
|
|
175
137
|
storeNames: names,
|
|
176
138
|
};
|
|
@@ -180,8 +142,6 @@ function readState(ref, store, path, depth) {
|
|
|
180
142
|
stores,
|
|
181
143
|
storeNames: names,
|
|
182
144
|
};
|
|
183
|
-
// The whole-store read is where a large store silently became a small one. Present only when a cap
|
|
184
|
-
// actually fired, so an intact read is unchanged and the field's presence is the warning.
|
|
185
145
|
if (truncation !== undefined)
|
|
186
146
|
result.truncation = truncation;
|
|
187
147
|
if (ref !== undefined && ref.length > 0) {
|
|
@@ -196,19 +156,6 @@ function readState(ref, store, path, depth) {
|
|
|
196
156
|
}
|
|
197
157
|
return result;
|
|
198
158
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Every animation, and WHICH element it drives.
|
|
201
|
-
*
|
|
202
|
-
* Reported from the field: a page with nine concurrent animations returned nine indistinguishable
|
|
203
|
-
* `{playState, currentTime, duration}` rows. `a.effect` was read only for `getTiming()`, never for
|
|
204
|
-
* its `target` — so the tool advertised "targets/timing" and shipped timing alone. A list you cannot
|
|
205
|
-
* index is not a list, and the question this tool exists to answer is "is THAT thing still
|
|
206
|
-
* animating".
|
|
207
|
-
*
|
|
208
|
-
* `describe()` is reused rather than inventing a target shape: it is what `reticle_query` returns,
|
|
209
|
-
* so the descriptor carries a `ref` the agent can pass straight to `reticle_act` — a target you
|
|
210
|
-
* cannot act on would be half an answer.
|
|
211
|
-
*/
|
|
212
159
|
function listAnimations() {
|
|
213
160
|
const doc = document;
|
|
214
161
|
if (typeof doc.getAnimations !== 'function')
|
|
@@ -216,9 +163,6 @@ function listAnimations() {
|
|
|
216
163
|
const animations = doc.getAnimations().map((a) => {
|
|
217
164
|
const effect = a.effect;
|
|
218
165
|
const timing = effect?.getTiming();
|
|
219
|
-
// A KeyframeEffect's target is legitimately nullable, and only KeyframeEffect has one at all.
|
|
220
|
-
// `null` is reported explicitly: omitting the key would make "this animation has no element"
|
|
221
|
-
// indistinguishable from the bug above, which is the distinction the fix exists to draw.
|
|
222
166
|
const target = effect?.target ?? null;
|
|
223
167
|
return {
|
|
224
168
|
playState: a.playState,
|
|
@@ -240,14 +184,11 @@ export function resolveNavigationUrl(rawUrl, baseUrl) {
|
|
|
240
184
|
return null;
|
|
241
185
|
}
|
|
242
186
|
}
|
|
243
|
-
/** Map browser command names to handlers. Used by the transport on each COMMAND. */
|
|
244
187
|
export function createCommandRegistry() {
|
|
245
188
|
const reg = new Map();
|
|
246
189
|
reg.set(ReticleCommand.SNAPSHOT, (args) => buildSnapshot({
|
|
247
190
|
scope: str(args['scope']),
|
|
248
191
|
mode: str(args['mode']) ?? SnapshotMode.FULL,
|
|
249
|
-
// Only the completion re-read sets this: it re-reads a branch the truncated walk stopped
|
|
250
|
-
// BEFORE emitting, so that branch's own line has to come back with its descendants.
|
|
251
192
|
includeRoot: true === args['includeRoot'],
|
|
252
193
|
}));
|
|
253
194
|
reg.set(ReticleCommand.QUERY, (args) => {
|
|
@@ -300,7 +241,6 @@ export function createCommandRegistry() {
|
|
|
300
241
|
});
|
|
301
242
|
reg.set(ReticleCommand.REFRESH, (args) => {
|
|
302
243
|
if (true === args['hard']) {
|
|
303
|
-
// Hard reload: navigate to self with a cache-busting param then replace history.
|
|
304
244
|
const url = new URL(window.location.href);
|
|
305
245
|
url.searchParams.set(RELOAD_CACHE_BUST_PARAM, String(Date.now()));
|
|
306
246
|
window.location.replace(url.toString());
|