@reticlehq/browser 2.13.0 → 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
package/dist/reticle.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Transport } from './transport/transport.js';
|
|
|
7
7
|
import { unreachableMessage } from './transport/unreachable-message.js';
|
|
8
8
|
import { adapterNames } from './registry/adapters.js';
|
|
9
9
|
import { registerCapabilities, setCapabilitiesListener, hasCapabilities, } from './registry/capabilities.js';
|
|
10
|
-
import { installAllObservers } from './observers/install-all.js';
|
|
10
|
+
import { installAllObservers, runTeardowns } from './observers/install-all.js';
|
|
11
11
|
import { installOverlay } from './presenter/overlay.js';
|
|
12
12
|
import { Presenter, LOG_KIND, LOG_RESULT, } from './presenter/presenter.js';
|
|
13
13
|
import { getPresenterSettings } from './presenter/presenter-settings.js';
|
|
@@ -17,18 +17,10 @@ import { resetClock } from './timers/clock.js';
|
|
|
17
17
|
import { nativeWarn } from './timers/native-console.js';
|
|
18
18
|
import { installRecorder } from './recorder/recorder.js';
|
|
19
19
|
import { Annotator } from './review/annotator.js';
|
|
20
|
-
/**
|
|
21
|
-
* Runtime backstop for the dev-only SDK: block connecting when the build reports production, unless
|
|
22
|
-
* explicitly overridden. Pure so it's testable; connect reads NODE_ENV safely (process may be absent
|
|
23
|
-
* in a raw browser). This is defense-in-depth - the primary guard is the consumer gating the import
|
|
24
|
-
* behind `import.meta.env.DEV` so the SDK is dead-code-eliminated from prod bundles entirely.
|
|
25
|
-
*/
|
|
26
20
|
export function shouldBlockProduction(nodeEnv, allowInProduction) {
|
|
27
21
|
return 'production' === nodeEnv && !allowInProduction;
|
|
28
22
|
}
|
|
29
|
-
export function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, token,
|
|
30
|
-
/** The page's protocol. Desktop webviews (file:, app:, tauri:) are local despite a non-loopback host. */
|
|
31
|
-
pageProtocol = 'http:') {
|
|
23
|
+
export function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, token, pageProtocol = 'http:') {
|
|
32
24
|
let bridge;
|
|
33
25
|
try {
|
|
34
26
|
bridge = new URL(bridgeUrl);
|
|
@@ -63,19 +55,11 @@ pageProtocol = 'http:') {
|
|
|
63
55
|
}
|
|
64
56
|
return { allowed: true };
|
|
65
57
|
}
|
|
66
|
-
/** HUD summary when the SDK self-ends a session because the bridge (server/agent) became unreachable. */
|
|
67
58
|
const BRIDGE_LOST_SUMMARY = 'Session ended - lost connection to Reticle (the agent is no longer running).';
|
|
68
|
-
/**
|
|
69
|
-
* Resolve the session label. An absent label or the `auto` sentinel yields a fresh per-tab id (via
|
|
70
|
-
* the injected generator) so multi-tab / new-tab routes never collide; any other label is used
|
|
71
|
-
* verbatim so tabs can intentionally share a session. `gen` is injected to keep this clock-free.
|
|
72
|
-
*/
|
|
73
59
|
export function resolveSessionLabel(option, gen) {
|
|
74
60
|
return option === undefined || option === SESSION_AUTO ? gen() : option;
|
|
75
61
|
}
|
|
76
|
-
// Re-exported from the protocol (the wire contract) so callers/tests can import it from the SDK too.
|
|
77
62
|
export { RETICLE_URL_PARAM };
|
|
78
|
-
/** Remove the `_reticle_reload` cache-buster a hard REFRESH left behind, via a native replaceState. */
|
|
79
63
|
function stripReloadCacheBustParam() {
|
|
80
64
|
try {
|
|
81
65
|
const current = new URL(window.location.href);
|
|
@@ -85,13 +69,8 @@ function stripReloadCacheBustParam() {
|
|
|
85
69
|
window.history.replaceState(window.history.state, '', current.toString());
|
|
86
70
|
}
|
|
87
71
|
catch {
|
|
88
|
-
/* best-effort URL hygiene - never block connect() on it */
|
|
89
72
|
}
|
|
90
73
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Extract Reticle identity overrides from a `location.search` string. Pure (takes the string, not the
|
|
93
|
-
* window) so it's testable without a DOM. Explicit connect options still win over these.
|
|
94
|
-
*/
|
|
95
74
|
export function reticleParamsFromSearch(search) {
|
|
96
75
|
const params = new URLSearchParams(search);
|
|
97
76
|
const out = {};
|
|
@@ -103,12 +82,6 @@ export function reticleParamsFromSearch(search) {
|
|
|
103
82
|
out.projectId = projectId;
|
|
104
83
|
return out;
|
|
105
84
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Resolve the session + project identity for a connection: an explicit, non-`auto` option wins;
|
|
108
|
-
* otherwise a launcher-stamped URL param is used; otherwise undefined (caller generates a per-tab id).
|
|
109
|
-
* Crucially `auto` is treated like "unset" so an app that passes the auto sentinel still lets a pooled
|
|
110
|
-
* launcher correlate the lease via __reticle_session. Pure for testability.
|
|
111
|
-
*/
|
|
112
85
|
export function resolveConnectIdentity(options, search) {
|
|
113
86
|
const url = reticleParamsFromSearch(search);
|
|
114
87
|
const explicitSession = options.session !== undefined && options.session !== SESSION_AUTO ? options.session : undefined;
|
|
@@ -118,11 +91,6 @@ export function resolveConnectIdentity(options, search) {
|
|
|
118
91
|
projectId: projectId !== undefined && projectId.length > 0 ? projectId : undefined,
|
|
119
92
|
};
|
|
120
93
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Assemble an event envelope. Pure: `seq` (monotonic per session) and `t` (elapsed clock) are
|
|
123
|
-
* injected, never read here - so it is unit-testable and honors the clock-injection rule. The
|
|
124
|
-
* causing action's id is attributed server-side by the settle window, so it is not stamped here.
|
|
125
|
-
*/
|
|
126
94
|
export function buildEvent(args) {
|
|
127
95
|
return {
|
|
128
96
|
t: args.t,
|
|
@@ -130,34 +98,17 @@ export function buildEvent(args) {
|
|
|
130
98
|
type: args.type,
|
|
131
99
|
sessionId: args.sessionId,
|
|
132
100
|
ref: args.ref,
|
|
133
|
-
// Which document this was observed under, so the server can refuse evidence minted before a
|
|
134
|
-
// navigation replaced the page. Stamped HERE because it is the one place every event passes
|
|
135
|
-
// through: an observer added later would otherwise emit unstamped events, which read as
|
|
136
|
-
// "current" by design and would reintroduce the defect silently for one event type.
|
|
137
101
|
documentId: args.documentId,
|
|
138
|
-
// Which round of source edits this was observed under. Stamped HERE for exactly the reason
|
|
139
|
-
// documentId is, and omitted while nothing has hot-updated: absence already reads as "current"
|
|
140
|
-
// downstream, so `NO_EDITS_OBSERVED` on the wire would be bytes spent saying "unknown".
|
|
141
102
|
editEpoch: NO_EDITS_OBSERVED === args.editEpoch ? undefined : args.editEpoch,
|
|
142
103
|
data: args.data,
|
|
143
104
|
};
|
|
144
105
|
}
|
|
145
|
-
/**
|
|
146
|
-
* The browser-side orchestrator. Wires observers -> events -> bridge, and bridge
|
|
147
|
-
* commands -> handlers. Embedded in the host app (dev only).
|
|
148
|
-
*/
|
|
149
106
|
export class Reticle {
|
|
150
107
|
#transport;
|
|
151
108
|
#registry = new Map();
|
|
152
109
|
#teardowns = [];
|
|
153
110
|
#connected = false;
|
|
154
111
|
#session = 'default';
|
|
155
|
-
/**
|
|
156
|
-
* Minted once, here, because this class is constructed once per document: a full navigation tears
|
|
157
|
-
* down the JavaScript context and builds a new one, so the id dies with the document it names. An
|
|
158
|
-
* SPA route change does not reconstruct it, which is correct — same context, same in-flight
|
|
159
|
-
* requests, same evidence.
|
|
160
|
-
*/
|
|
161
112
|
#documentId = newDocumentId(Math.random);
|
|
162
113
|
#start = 0;
|
|
163
114
|
#overlay;
|
|
@@ -167,18 +118,16 @@ export class Reticle {
|
|
|
167
118
|
#eventCount = 0;
|
|
168
119
|
#token;
|
|
169
120
|
#sdkVersion;
|
|
121
|
+
#captureBodies = false;
|
|
122
|
+
#sourceMapping = undefined;
|
|
170
123
|
#projectId;
|
|
171
|
-
/** App-declared extra redaction keys, announced in hello so the driven path honours them too. */
|
|
172
124
|
#redactKeys = [];
|
|
173
|
-
/** Act-row log handle for the in-flight act/act_sequence, so its outcome stamps the right row. */
|
|
174
125
|
#actHandle;
|
|
175
126
|
connect(options = {}) {
|
|
176
127
|
if (this.#connected)
|
|
177
128
|
return;
|
|
178
129
|
if ('undefined' === typeof window || 'undefined' === typeof document)
|
|
179
130
|
return;
|
|
180
|
-
// Dev-only backstop: refuse to activate in a production build (SSR healthcheck, prod bundle opened
|
|
181
|
-
// on localhost). `process` may not exist in a raw browser, so read NODE_ENV off globalThis.
|
|
182
131
|
const proc = globalThis.process;
|
|
183
132
|
const nodeEnv = proc?.env?.['NODE_ENV'];
|
|
184
133
|
if (shouldBlockProduction(nodeEnv, true === options.allowInProduction)) {
|
|
@@ -186,9 +135,6 @@ export class Reticle {
|
|
|
186
135
|
'import.meta.env.DEV, or pass allowInProduction:true to override.');
|
|
187
136
|
return;
|
|
188
137
|
}
|
|
189
|
-
// A `hard` REFRESH left a cache-busting `_reticle_reload=<nonce>` in the address bar. Strip it now
|
|
190
|
-
// (before the route observer installs, so it emits no spurious ROUTE_CHANGE) - otherwise every hard
|
|
191
|
-
// reload permanently pollutes the URL the app and the agent see.
|
|
192
138
|
stripReloadCacheBustParam();
|
|
193
139
|
const url = options.url ?? bridgeWsUrl(RETICLE_DEFAULT_PORT);
|
|
194
140
|
const policy = connectionPolicy(window.location.hostname, url, true === options.allowNonLocalhost, options.token, window.location.protocol);
|
|
@@ -196,24 +142,15 @@ export class Reticle {
|
|
|
196
142
|
globalThis.console.warn(`[Reticle] ${policy.reason ?? 'connection blocked'}`);
|
|
197
143
|
return;
|
|
198
144
|
}
|
|
199
|
-
// Publish the project root for the adapters' source relativisation. Done HERE, in library code,
|
|
200
|
-
// so the app-side connect stays plain JavaScript - see ReticleConnectOptions.root.
|
|
201
145
|
if (options.root !== undefined && options.root.length > 0) {
|
|
202
146
|
globalThis[RETICLE_ROOT_GLOBAL] = options.root;
|
|
203
147
|
}
|
|
204
|
-
// The build plugin defines this; a hand-wired connect can pass it explicitly. Either way an
|
|
205
|
-
// absent value means UNKNOWN, so the bridge never reports an unknown pair as in sync.
|
|
206
148
|
const declaredVersion = options.sdkVersion ?? globalThis[RETICLE_SDK_VERSION_GLOBAL];
|
|
207
149
|
this.#sdkVersion =
|
|
208
150
|
'string' === typeof declaredVersion && declaredVersion.length > 0
|
|
209
151
|
? declaredVersion
|
|
210
152
|
: undefined;
|
|
211
|
-
// A pooled/headless launcher can stamp identity via namespaced URL params; explicit (non-auto)
|
|
212
|
-
// options win, but the `auto` sentinel defers to the URL param so leases correlate.
|
|
213
153
|
const identity = resolveConnectIdentity(options, window.location.search);
|
|
214
|
-
// Remembered per TAB, so a reload rejoins the same session instead of appearing as a new one and
|
|
215
|
-
// stranding the agent's handle - see session-continuity. An explicit id (connect option, or the
|
|
216
|
-
// lease's URL param) still wins.
|
|
217
154
|
const explicitSession = resolveSessionLabel(identity.session, () => '');
|
|
218
155
|
this.#session = rememberSessionLabel(explicitSession.length > 0 ? explicitSession : undefined, 'undefined' === typeof globalThis.sessionStorage ? undefined : globalThis.sessionStorage, () => 'function' === typeof globalThis.crypto?.randomUUID
|
|
219
156
|
? `s${globalThis.crypto.randomUUID()}`
|
|
@@ -221,9 +158,6 @@ export class Reticle {
|
|
|
221
158
|
this.#token =
|
|
222
159
|
options.token !== undefined && options.token.length > 0 ? options.token : undefined;
|
|
223
160
|
this.#projectId = identity.projectId;
|
|
224
|
-
// Install the redaction rule BEFORE anything can emit. Every observer, the serializer and the
|
|
225
|
-
// snapshot path read it ambiently, so a policy set even one line after installAllObservers would
|
|
226
|
-
// leave the first events of the session redacted by a different rule than the rest.
|
|
227
161
|
this.#applyRedaction(options.redact);
|
|
228
162
|
this.#start = performance.now();
|
|
229
163
|
this.#registry = createCommandRegistry();
|
|
@@ -231,42 +165,29 @@ export class Reticle {
|
|
|
231
165
|
url,
|
|
232
166
|
hello: () => this.#hello(),
|
|
233
167
|
handleCommand: (command) => this.#handleCommand(command),
|
|
234
|
-
// Show the presenter HUD as soon as the agent bridge connects - the user immediately sees
|
|
235
|
-
// the glow border and narration panel, even before the first tool call lands.
|
|
236
168
|
onConnected: () => this.#presenter?.sessionStart(),
|
|
237
|
-
// Liveness fallback: if the bridge stays unreachable (the agent killed the server process),
|
|
238
|
-
// no server-pushed end can arrive - so end the run we're presenting ourselves. A returning
|
|
239
|
-
// agent revives it via the normal sessionStart path on its next command.
|
|
240
169
|
onConnectionLost: () => {
|
|
241
|
-
// Restore real timers. A frozen clock is driven by the agent through the bridge; once the
|
|
242
|
-
// bridge is gone nothing can ever advance it, so leaving it installed pins Date.now and
|
|
243
|
-
// queues every setTimeout into a scheduler that will never run. Concretely that kills every
|
|
244
|
-
// lodash debounce/throttle in the app (now - lastCall stays 0), so search boxes, autosave
|
|
245
|
-
// and resize handlers stop firing until a reload - with nothing on screen explaining why.
|
|
246
170
|
resetClock();
|
|
247
171
|
if (true === this.#presenter?.sessionActive) {
|
|
248
172
|
this.#presenter.setState(SessionState.ENDED, BRIDGE_LOST_SUMMARY);
|
|
249
173
|
}
|
|
250
174
|
},
|
|
251
|
-
// First-connect never succeeded ⇒ this page's socket is not opening at this URL. Say what the
|
|
252
|
-
// page observed rather than guessing at the daemon, which it cannot see from in here.
|
|
253
175
|
onUnreachable: ({ url: tried, attempts }) => {
|
|
254
176
|
nativeWarn(unreachableMessage(tried, attempts));
|
|
255
|
-
// And on screen, not only in a console nobody has open. An instrumented page with a dead
|
|
256
|
-
// bridge looked exactly like a page with no Reticle in it, so the user could not tell a
|
|
257
|
-
// daemon they forgot to start from an install that did not work.
|
|
258
177
|
this.#presenter?.showUnreachable(tried, attempts);
|
|
259
178
|
},
|
|
260
179
|
});
|
|
261
|
-
// Capabilities registered AFTER connect (which is when they are registered, by design) must
|
|
262
|
-
// reach the bridge, or an app that declared its whole testable surface reads as having none.
|
|
263
180
|
setCapabilitiesListener(() => this.#transport?.reannounce());
|
|
264
|
-
// Contributors only, and reported in capabilities so a verdict drawn with it open is never
|
|
265
|
-
// mistaken for an ordinary one. See connect-options.ts.
|
|
266
181
|
setPresenterVisible(true === options.exposePresenter);
|
|
267
182
|
const emit = this.#emit;
|
|
183
|
+
this.#captureBodies = true === options.captureNetworkBodies;
|
|
184
|
+
this.#sourceMapping =
|
|
185
|
+
'boolean' === typeof options.sourceMapping ? options.sourceMapping : undefined;
|
|
268
186
|
this.#teardowns = installAllObservers(emit, {
|
|
269
|
-
captureBodies:
|
|
187
|
+
captureBodies: this.#captureBodies,
|
|
188
|
+
...(options.captureErrorBodies === undefined
|
|
189
|
+
? {}
|
|
190
|
+
: { captureErrorBodies: options.captureErrorBodies }),
|
|
270
191
|
});
|
|
271
192
|
if (true === options.overlay) {
|
|
272
193
|
this.#overlay = installOverlay();
|
|
@@ -288,21 +209,16 @@ export class Reticle {
|
|
|
288
209
|
if (options.idleEndMs !== undefined)
|
|
289
210
|
presenterOptions.idleEndMs = options.idleEndMs;
|
|
290
211
|
presenterOptions.sessionId = this.#session;
|
|
291
|
-
// The panel calls this when the human pauses/resumes/ends or sends a message. We emit a
|
|
292
|
-
// HUMAN_CONTROL event over the existing transport; #emit stamps `t` from the elapsed clock.
|
|
293
212
|
presenterOptions.onControl = (intent) => this.#emit(EventType.HUMAN_CONTROL, intent.text !== undefined
|
|
294
213
|
? { kind: intent.kind, text: intent.text }
|
|
295
214
|
: { kind: intent.kind });
|
|
296
215
|
this.#presenter = new Presenter(presenterOptions);
|
|
297
|
-
// Mount the overlay. The session (glow + HUD) activates on bridge connect via onConnected,
|
|
298
|
-
// so the presenter is visible as soon as the agent is reachable - not just on first command.
|
|
299
216
|
this.#presenter.mount();
|
|
300
217
|
}
|
|
301
218
|
if (true === options.recorder) {
|
|
302
219
|
this.#recorder = installRecorder({ emit, now: () => Date.now() });
|
|
303
220
|
this.#recorder.mount();
|
|
304
221
|
}
|
|
305
|
-
// The page annotator rides with the presenter (the human surface) unless explicitly off.
|
|
306
222
|
if (options.annotate ?? options.present !== false) {
|
|
307
223
|
const presenter = this.#presenter;
|
|
308
224
|
this.#annotator = new Annotator({
|
|
@@ -317,65 +233,45 @@ export class Reticle {
|
|
|
317
233
|
this.#transport.connect();
|
|
318
234
|
this.#connected = true;
|
|
319
235
|
}
|
|
320
|
-
/** Whether the in-page SDK is connected to the bridge (read by createReticleEmitter, P5a). */
|
|
321
236
|
get connected() {
|
|
322
237
|
return this.#connected;
|
|
323
238
|
}
|
|
324
|
-
/** Surface an arbitrary app-domain observation the DOM can't express. */
|
|
325
239
|
signal(name, data = {}) {
|
|
326
240
|
this.#emit(EventType.SIGNAL, { name, data });
|
|
327
241
|
}
|
|
328
|
-
/** Report a framework/store state change the agent can observe and assert on. */
|
|
329
242
|
state(name, value) {
|
|
330
243
|
this.#emit(EventType.STATE_CHANGE, { name, value });
|
|
331
244
|
}
|
|
332
|
-
/**
|
|
333
|
-
* Report an aggregated count of React commits (the @reticlehq/react render meter calls this on a
|
|
334
|
-
* throttle). Emits a single RENDER_COMMIT event per window so commit storms are observable without a
|
|
335
|
-
* per-render flood. Dev-only, like the whole SDK.
|
|
336
|
-
*/
|
|
337
245
|
renderCommit(commits) {
|
|
338
246
|
if (commits > 0)
|
|
339
247
|
this.#emit(EventType.RENDER_COMMIT, { commits });
|
|
340
248
|
}
|
|
341
|
-
/** Advertise the app's testable surface so the agent learns it without reading source. */
|
|
342
249
|
describe(input) {
|
|
343
|
-
registerCapabilities(input);
|
|
250
|
+
registerCapabilities(input);
|
|
344
251
|
}
|
|
345
|
-
/** Live-control: end the session programmatically from the host app (drives the panel to ended). */
|
|
346
252
|
endSession() {
|
|
347
253
|
this.#presenter?.setState(SessionState.ENDED);
|
|
348
254
|
}
|
|
349
|
-
/**
|
|
350
|
-
* Hand the SDK the page's hot-update channel, so a stale ref can say the code changed underneath it.
|
|
351
|
-
*
|
|
352
|
-
* `unknown` on purpose: the only caller that has one is the build integration, the shape it passes
|
|
353
|
-
* is Vite's `import.meta.hot`, and the SDK must not depend on Vite — it ships to Next, Electron,
|
|
354
|
-
* Tauri and plain pages. Anything that is not a subscribable channel is ignored, which is the
|
|
355
|
-
* normal case: with no channel the epoch stays at `NO_EDITS_OBSERVED`, meaning "no edits OBSERVED",
|
|
356
|
-
* never "no edits happened".
|
|
357
|
-
*/
|
|
358
255
|
observeHotUpdates(hot) {
|
|
359
256
|
editEpoch.observe(hot);
|
|
360
257
|
}
|
|
361
258
|
disconnect() {
|
|
362
259
|
if (!this.#connected)
|
|
363
260
|
return;
|
|
364
|
-
|
|
365
|
-
teardown();
|
|
261
|
+
runTeardowns(this.#emit, this.#teardowns);
|
|
366
262
|
this.#teardowns = [];
|
|
367
263
|
this.#transport?.close();
|
|
368
264
|
this.#transport = undefined;
|
|
369
265
|
this.#overlay?.destroy();
|
|
370
266
|
this.#overlay = undefined;
|
|
371
|
-
this.#presenter?.sessionEnd();
|
|
267
|
+
this.#presenter?.sessionEnd();
|
|
372
268
|
this.#presenter?.destroy();
|
|
373
269
|
this.#presenter = undefined;
|
|
374
270
|
this.#recorder?.destroy();
|
|
375
271
|
this.#recorder = undefined;
|
|
376
272
|
this.#annotator?.destroy();
|
|
377
273
|
this.#annotator = undefined;
|
|
378
|
-
resetClock();
|
|
274
|
+
resetClock();
|
|
379
275
|
this.#connected = false;
|
|
380
276
|
}
|
|
381
277
|
#emit = (type, data, ref) => {
|
|
@@ -389,29 +285,16 @@ export class Reticle {
|
|
|
389
285
|
data,
|
|
390
286
|
ref,
|
|
391
287
|
});
|
|
392
|
-
// Guarded because #emit runs INLINE IN THE APP'S CALL STACK: every monkey-patch calls it from
|
|
393
|
-
// inside the function it replaced. An exception here does not surface as an SDK error - it
|
|
394
|
-
// propagates out of history.pushState (crashing a router's navigate), out of localStorage.setItem
|
|
395
|
-
// after the write already succeeded, or out of console.log before the message reaches the console.
|
|
396
|
-
// A dev-only observability SDK must never be able to break the app it is observing.
|
|
397
288
|
try {
|
|
398
289
|
this.#transport?.sendEvent(event);
|
|
399
290
|
this.#eventCount += 1;
|
|
400
291
|
this.#overlay?.update({ connected: true, events: this.#eventCount });
|
|
401
|
-
// On a route change, re-scope the HUD's replay-flow chips to the page we're now on.
|
|
402
292
|
if (type === EventType.ROUTE_CHANGE)
|
|
403
293
|
this.#presenter?.refilterFlows();
|
|
404
294
|
}
|
|
405
295
|
catch {
|
|
406
|
-
/* observation is best-effort; the host app's control flow is not */
|
|
407
296
|
}
|
|
408
297
|
};
|
|
409
|
-
/**
|
|
410
|
-
* Resolve the app's redaction config into the ambient policy, and remember the part of it that
|
|
411
|
-
* travels. A config of `undefined` is not "no policy" - it is the DEFAULT policy, installed
|
|
412
|
-
* explicitly so a second connect() in the same page (HMR, a re-mount) cannot inherit a rule the
|
|
413
|
-
* previous one set.
|
|
414
|
-
*/
|
|
415
298
|
#applyRedaction(config) {
|
|
416
299
|
setActiveRedactionPolicy(buildRedactionPolicy(config, nativeWarn));
|
|
417
300
|
this.#redactKeys = wireRedactionKeys(config);
|
|
@@ -427,31 +310,25 @@ export class Reticle {
|
|
|
427
310
|
adapters: adapterNames(),
|
|
428
311
|
...(this.#token === undefined ? {} : { token: this.#token }),
|
|
429
312
|
hasCapabilities: hasCapabilities(),
|
|
430
|
-
|
|
313
|
+
captureBodies: this.#captureBodies,
|
|
314
|
+
...(this.#sourceMapping === undefined ? {} : { sourceMapping: this.#sourceMapping }),
|
|
431
315
|
...(this.#sdkVersion === undefined ? {} : { sdkVersion: this.#sdkVersion }),
|
|
432
|
-
// Always present: derived from THIS build's core, so it needs no build plugin to supply it.
|
|
433
|
-
// It is the half of the skew check that works on a hand-wired connect.
|
|
434
316
|
contract: CONTRACT_FINGERPRINT,
|
|
435
317
|
...(0 === this.#redactKeys.length ? {} : { redactKeys: this.#redactKeys }),
|
|
436
318
|
};
|
|
437
319
|
}
|
|
438
320
|
async #handleCommand(command) {
|
|
439
|
-
// NARRATE: the agent tells the human what it's about to do / decide (presenter HUD).
|
|
440
321
|
if (command.name === ReticleCommand.NARRATE) {
|
|
441
|
-
this.#presenter?.sessionStart();
|
|
322
|
+
this.#presenter?.sessionStart();
|
|
442
323
|
this.#presenter?.narrate(str(command.args['text']), str(command.args['level'], 'info'));
|
|
443
324
|
return { ok: true, result: { shown: this.#presenter !== undefined } };
|
|
444
325
|
}
|
|
445
|
-
// SESSION_CONFIG: the agent tunes the session for the app (currently the idle-end window).
|
|
446
326
|
if (command.name === ReticleCommand.SESSION_CONFIG) {
|
|
447
327
|
const idleEndMs = command.args['idleEndMs'];
|
|
448
328
|
if ('number' === typeof idleEndMs)
|
|
449
329
|
this.#presenter?.setIdleEndMs(idleEndMs);
|
|
450
330
|
return { ok: true, result: { applied: this.#presenter !== undefined, idleEndMs } };
|
|
451
331
|
}
|
|
452
|
-
// Bridge → browser presenter pushes (PRESENTER state echo / FLOWS replay list). The presenter owns
|
|
453
|
-
// the parsing; here we only report whether a panel was mounted to apply it. setState-only, so a
|
|
454
|
-
// PRESENTER echo of a HUMAN_CONTROL can't loop back into a re-emit.
|
|
455
332
|
if (command.name === ReticleCommand.PRESENTER ||
|
|
456
333
|
command.name === ReticleCommand.FLOWS ||
|
|
457
334
|
command.name === ReticleCommand.IMPACT) {
|
|
@@ -462,7 +339,7 @@ export class Reticle {
|
|
|
462
339
|
if (handler === undefined) {
|
|
463
340
|
return { ok: false, error: `unknown command '${command.name}'` };
|
|
464
341
|
}
|
|
465
|
-
this.#presenter?.sessionStart();
|
|
342
|
+
this.#presenter?.sessionStart();
|
|
466
343
|
await this.#presentBefore(command);
|
|
467
344
|
try {
|
|
468
345
|
const result = await handler(command.args);
|
|
@@ -478,12 +355,11 @@ export class Reticle {
|
|
|
478
355
|
this.#presenter?.scheduleIdle();
|
|
479
356
|
}
|
|
480
357
|
}
|
|
481
|
-
/** Drive the presenter (cursor/effects/status) before the real action runs. */
|
|
482
358
|
async #presentBefore(command) {
|
|
483
359
|
const p = this.#presenter;
|
|
484
360
|
if (p === undefined)
|
|
485
361
|
return;
|
|
486
|
-
p.setMode(modeForCommand(command.name));
|
|
362
|
+
p.setMode(modeForCommand(command.name));
|
|
487
363
|
this.#actHandle = undefined;
|
|
488
364
|
if (command.name === ReticleCommand.ACT) {
|
|
489
365
|
const ref = str(command.args['ref']);
|
|
@@ -497,7 +373,6 @@ export class Reticle {
|
|
|
497
373
|
const s = step;
|
|
498
374
|
const ref = str(s.ref);
|
|
499
375
|
const label = refLabel(ref);
|
|
500
|
-
// one log row per step; the last handle carries the sequence outcome glyph
|
|
501
376
|
this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(s.action))} ${label}`);
|
|
502
377
|
await p.beforeAct(ref, str(s.action), label);
|
|
503
378
|
}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { Z_OVERLAY } from '../presenter/presenter-styles.js';
|
|
2
|
-
/** Markup + CSS for the in-page annotation HUD (markers, hover box, composer). */
|
|
3
2
|
const MARK_ATTR = 'data-reticle-mark';
|
|
4
3
|
const sel = (role) => `[${MARK_ATTR}="${role}"]`;
|
|
5
4
|
export const MARK_PLACEHOLDER = 'What should change?';
|
|
6
5
|
export const MARK_SUBMIT = 'Add';
|
|
7
6
|
export const MARK_CANCEL = 'Cancel';
|
|
8
7
|
export const MARK_PENDING_GLYPH = '+';
|
|
9
|
-
/**
|
|
10
|
-
* ABOVE the presenter overlay, because the overlay contains the page blocker annotate mode raises.
|
|
11
|
-
* One below it, the shield painted over the composer: every click on Cancel or Add landed on the
|
|
12
|
-
* blocker instead of the button, so the note could be neither saved nor dismissed. The root here is
|
|
13
|
-
* pointer-events:none, so only the marks and the composer sit above the HUD - not a new shield.
|
|
14
|
-
*/
|
|
15
8
|
const Z_MARK = Z_OVERLAY + 1;
|
|
16
9
|
export const ANNOTATOR_CSS = `
|
|
17
10
|
${sel('root')}{position:fixed;inset:0;pointer-events:none;z-index:${String(Z_MARK)};}
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { EventType } from '@reticlehq/core';
|
|
2
|
-
|
|
3
|
-
* What the HUD is told about a mark the moment it is placed.
|
|
4
|
-
*
|
|
5
|
-
* The row used to get the note and the anchor's label - "generic: my feedback" - which names
|
|
6
|
-
* neither the element nor which of several marks it is. The pin on the page is numbered, so the
|
|
7
|
-
* row carries the same number, plus the source the anchor already resolved.
|
|
8
|
-
*/
|
|
9
|
-
export interface MarkReport {
|
|
2
|
+
interface MarkReport {
|
|
10
3
|
note: string;
|
|
11
|
-
/** The anchor the agent will look the element up by, e.g. `[data-testid="deploy-submit"]`. */
|
|
12
4
|
anchor: string;
|
|
13
5
|
label: string;
|
|
14
|
-
/** 1-based, matching the number drawn in the pin. */
|
|
15
6
|
index: number;
|
|
16
|
-
/** file:line, when the app is source-stamped. */
|
|
17
7
|
source?: string;
|
|
18
8
|
}
|
|
19
9
|
export interface AnnotatorDeps {
|
|
@@ -34,9 +24,7 @@ export declare class Annotator {
|
|
|
34
24
|
get active(): boolean;
|
|
35
25
|
get markCount(): number;
|
|
36
26
|
mount(): void;
|
|
37
|
-
/** Drive marker/highlight chrome from the HUD accent swatch. */
|
|
38
27
|
setAccent(hex: string): void;
|
|
39
|
-
/** @deprecated HUD expand/collapse owns annotate mode; chrome buttons are hide/clear. */
|
|
40
28
|
attachFlagButton(btn: HTMLElement): void;
|
|
41
29
|
attachChrome(chrome: AnnotatorChrome): void;
|
|
42
30
|
destroy(): void;
|
|
@@ -45,3 +33,4 @@ export declare class Annotator {
|
|
|
45
33
|
syncAnchors(): void;
|
|
46
34
|
}
|
|
47
35
|
export declare function installAnnotator(deps: AnnotatorDeps): Annotator;
|
|
36
|
+
export {};
|
package/dist/review/annotator.js
CHANGED
|
@@ -4,7 +4,6 @@ import { isReticleUi, isReticleOverlay } from '../dom/dom-ignore.js';
|
|
|
4
4
|
import { resolveMarkAnchor } from './mark-anchor.js';
|
|
5
5
|
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
|
|
6
6
|
import { ANNOTATOR_CSS, ANNOTATOR_ROOT_HTML, MARK_CANCEL, MARK_PENDING_GLYPH, MARK_PLACEHOLDER, MARK_SUBMIT, } from './annotator-styles.js';
|
|
7
|
-
/** The cursor must rest this long before the outline boxes the element under it - calm, not jumpy. */
|
|
8
7
|
const HIGHLIGHT_REST_MS = 130;
|
|
9
8
|
const SHAKE_MS = 250;
|
|
10
9
|
const POP_W = 280;
|
|
@@ -85,12 +84,10 @@ export class Annotator {
|
|
|
85
84
|
if (undefined !== this.#accent)
|
|
86
85
|
this.setAccent(this.#accent);
|
|
87
86
|
}
|
|
88
|
-
/** Drive marker/highlight chrome from the HUD accent swatch. */
|
|
89
87
|
setAccent(hex) {
|
|
90
88
|
this.#accent = hex;
|
|
91
89
|
this.#root?.style.setProperty('--reticle-mark-accent', hex);
|
|
92
90
|
}
|
|
93
|
-
/** @deprecated HUD expand/collapse owns annotate mode; chrome buttons are hide/clear. */
|
|
94
91
|
attachFlagButton(btn) {
|
|
95
92
|
this.attachChrome({ markersBtn: btn });
|
|
96
93
|
}
|
|
@@ -211,18 +208,11 @@ export class Annotator {
|
|
|
211
208
|
#handleClick(ev) {
|
|
212
209
|
if (!this.#active)
|
|
213
210
|
return;
|
|
214
|
-
// A click Reticle dispatched is the agent driving the app, not a person placing a mark. Without
|
|
215
|
-
// this, expanding the HUD silently disabled every `reticle_act` click while the action still
|
|
216
|
-
// reported success — a false green in our own UI. See synthetic-input.ts.
|
|
217
211
|
if (isSyntheticInput())
|
|
218
212
|
return;
|
|
219
213
|
const raw = ev.target;
|
|
220
214
|
if (!(raw instanceof Element))
|
|
221
215
|
return;
|
|
222
|
-
// The page blocker is a full-viewport shield Reticle puts up WHILE annotating, so with it on
|
|
223
|
-
// every click lands on Reticle's own UI and was discarded as such — annotate mode looked
|
|
224
|
-
// completely dead, because the one thing standing between the user and the page is the thing
|
|
225
|
-
// annotate mode turns on. Resolve what is underneath instead of giving up on the click.
|
|
226
216
|
const target = raw.hasAttribute(BLOCKER_ATTR_NAME)
|
|
227
217
|
? pageElementAt(ev.clientX, ev.clientY)
|
|
228
218
|
: raw;
|
|
@@ -274,9 +264,6 @@ export class Annotator {
|
|
|
274
264
|
if (this.#hi === undefined)
|
|
275
265
|
return;
|
|
276
266
|
const raw = ev.target;
|
|
277
|
-
// Same shield as in #handleClick: while annotating, a full-viewport blocker sits over the page,
|
|
278
|
-
// so EVERY mousemove reports it as the target. Resolving only there left the outline suppressed
|
|
279
|
-
// on every element - a crosshair with no indication of what it was on. See #handleClick.
|
|
280
267
|
const target = raw instanceof Element && raw.hasAttribute(BLOCKER_ATTR_NAME)
|
|
281
268
|
? pageElementAt(ev.clientX, ev.clientY)
|
|
282
269
|
: raw;
|
|
@@ -315,22 +302,11 @@ export class Annotator {
|
|
|
315
302
|
this.#pendingKey = mark.anchor;
|
|
316
303
|
this.#pendingTarget = mark.target;
|
|
317
304
|
this.#hideHighlight();
|
|
318
|
-
// Where the element is NOW, not where it was when the mark was made. The popover is
|
|
319
|
-
// position:fixed, so it is placed in viewport coordinates, and the stored clientX/clientY are
|
|
320
|
-
// viewport coordinates from an earlier scroll position. Reopening a mark after scrolling put the
|
|
321
|
-
// note somewhere unrelated to the thing it annotates — further away the further you had scrolled.
|
|
322
305
|
const at = this.#currentPoint(mark);
|
|
323
306
|
this.#mountPopover(mark.label, at.x, at.y, mark.note);
|
|
324
307
|
this.#dropPending(at.x, at.y);
|
|
325
308
|
this.#paintSelection();
|
|
326
309
|
}
|
|
327
|
-
/**
|
|
328
|
-
* The viewport point a mark's popover should open at.
|
|
329
|
-
*
|
|
330
|
-
* Prefers the element's live box, because that is what the note is ABOUT. Falls back to the stored
|
|
331
|
-
* coordinates only when the element is gone, where there is nothing better and the mark is already
|
|
332
|
-
* shown as stale.
|
|
333
|
-
*/
|
|
334
310
|
#currentPoint(mark) {
|
|
335
311
|
const el = mark.target;
|
|
336
312
|
if (el !== undefined && el.isConnected) {
|
|
@@ -471,7 +447,6 @@ export class Annotator {
|
|
|
471
447
|
this.#dropPin(resolved, note, x, y);
|
|
472
448
|
this.#report(note, resolved.anchor, resolved.label, this.#marks.length, sourceLabel(resolved));
|
|
473
449
|
}
|
|
474
|
-
/** Hand the HUD everything it needs to name this mark in one row. */
|
|
475
450
|
#report(note, anchor, label, index, source) {
|
|
476
451
|
const mark = { note, anchor, label, index };
|
|
477
452
|
if (source !== undefined)
|
|
@@ -560,12 +535,6 @@ export class Annotator {
|
|
|
560
535
|
this.#repositionPopover();
|
|
561
536
|
this.#paintSelection();
|
|
562
537
|
}
|
|
563
|
-
/**
|
|
564
|
-
* Keep an OPEN popover attached to its element while the page scrolls.
|
|
565
|
-
*
|
|
566
|
-
* The pins were repositioned here and the popover was not, so scrolling with a note open slid the
|
|
567
|
-
* note off its target and left it floating over unrelated content.
|
|
568
|
-
*/
|
|
569
538
|
#repositionPopover() {
|
|
570
539
|
const pop = this.#pop;
|
|
571
540
|
const mark = this.#editing;
|
|
@@ -589,7 +558,6 @@ export class Annotator {
|
|
|
589
558
|
this.#selBox.hidden = true;
|
|
590
559
|
}
|
|
591
560
|
}
|
|
592
|
-
/** "file:line" for a resolved anchor, when the app carries source stamps. */
|
|
593
561
|
function sourceLabel(resolved) {
|
|
594
562
|
return resolved.source === undefined
|
|
595
563
|
? undefined
|
|
@@ -607,15 +575,7 @@ function isElementFixed(el) {
|
|
|
607
575
|
const pos = getComputedStyle(el).position;
|
|
608
576
|
return 'fixed' === pos || 'sticky' === pos;
|
|
609
577
|
}
|
|
610
|
-
/** The blocker Reticle raises over the page while annotating; see the click handler. */
|
|
611
578
|
const BLOCKER_ATTR_NAME = 'data-reticle-blocker';
|
|
612
|
-
/**
|
|
613
|
-
* The topmost element of the HOST PAGE at a point, seeing past Reticle's own overlays.
|
|
614
|
-
*
|
|
615
|
-
* `elementsFromPoint` returns the whole stack topmost-first, so the page element is simply the first
|
|
616
|
-
* entry that is not ours. Returns undefined where the API is unavailable (jsdom) or nothing but
|
|
617
|
-
* Reticle UI is there, and the caller drops the click rather than guessing at a target.
|
|
618
|
-
*/
|
|
619
579
|
function pageElementAt(x, y) {
|
|
620
580
|
const from = document.elementsFromPoint.bind(document);
|
|
621
581
|
if (from === undefined)
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import { MarkAnchorStrategy } from '@reticlehq/core';
|
|
2
|
-
/**
|
|
3
|
-
* The element address carried by a human review mark: a re-resolvable anchor (auto-anchor's most
|
|
4
|
-
* durable tier for this element), its durability strategy, a human-legible label, and — crucially —
|
|
5
|
-
* the source file:line the agent should open. Source is reported INDEPENDENTLY of the anchor tier:
|
|
6
|
-
* a role-anchored element still carries its babel-stamped source so the agent can jump to the code.
|
|
7
|
-
*/
|
|
8
2
|
export interface MarkAnchor {
|
|
9
3
|
anchor: string;
|
|
10
4
|
strategy: MarkAnchorStrategy;
|
|
@@ -14,9 +8,4 @@ export interface MarkAnchor {
|
|
|
14
8
|
line: number;
|
|
15
9
|
};
|
|
16
10
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Resolve a clicked element into the address a human review mark carries. Reuses the same
|
|
19
|
-
* auto-anchor machinery a recorded flow uses, so a mark pins to the element the way Reticle addresses
|
|
20
|
-
* it everywhere else — and always reports the source file:line when one is available.
|
|
21
|
-
*/
|
|
22
11
|
export declare function resolveMarkAnchor(el: Element): MarkAnchor;
|
|
@@ -3,26 +3,18 @@ import { AnchorStrategy, synthesizeAnchor } from '../dom/auto-anchor.js';
|
|
|
3
3
|
import { getAccessibleName, getRole } from '../dom/a11y.js';
|
|
4
4
|
import { identifyComponent } from '../registry/adapters.js';
|
|
5
5
|
import { sourceFor } from '../dom/source.js';
|
|
6
|
-
/** Attribute names — defined locally per the recorder/query convention (no shared free string). */
|
|
7
6
|
const TESTID_ATTR = 'data-testid';
|
|
8
|
-
/** AnchorStrategy and MarkAnchorStrategy share string values; map explicitly (no cross-cast). */
|
|
9
7
|
const STRATEGY = {
|
|
10
8
|
[AnchorStrategy.TESTID]: MarkAnchorStrategy.TESTID,
|
|
11
9
|
[AnchorStrategy.COMPONENT]: MarkAnchorStrategy.COMPONENT,
|
|
12
10
|
[AnchorStrategy.ROLE]: MarkAnchorStrategy.ROLE,
|
|
13
11
|
[AnchorStrategy.POSITION]: MarkAnchorStrategy.POSITION,
|
|
14
12
|
};
|
|
15
|
-
/** A short, human-legible label for the element: `role "name"`, falling back to role / tag name. */
|
|
16
13
|
function labelFor(el, role, name) {
|
|
17
14
|
if (name.length > 0)
|
|
18
15
|
return role.length > 0 ? `${role} "${name}"` : `"${name}"`;
|
|
19
16
|
return role.length > 0 ? role : el.tagName.toLowerCase();
|
|
20
17
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Resolve a clicked element into the address a human review mark carries. Reuses the same
|
|
23
|
-
* auto-anchor machinery a recorded flow uses, so a mark pins to the element the way Reticle addresses
|
|
24
|
-
* it everywhere else — and always reports the source file:line when one is available.
|
|
25
|
-
*/
|
|
26
18
|
export function resolveMarkAnchor(el) {
|
|
27
19
|
const testid = el.getAttribute(TESTID_ATTR) ?? undefined;
|
|
28
20
|
const info = identifyComponent(el);
|