@reticlehq/browser 2.13.1 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/actions-dom.d.ts +0 -20
- package/dist/actions/actions-dom.js +0 -66
- package/dist/actions/actions-press.d.ts +10 -0
- package/dist/actions/actions-press.js +77 -0
- package/dist/actions/actions.d.ts +0 -89
- package/dist/actions/actions.js +24 -358
- package/dist/actions/appeared-text.d.ts +0 -20
- package/dist/actions/appeared-text.js +0 -36
- package/dist/actions/danger-context.d.ts +3 -0
- package/dist/actions/danger-context.js +39 -0
- package/dist/actions/scroll.d.ts +0 -9
- package/dist/actions/scroll.js +0 -7
- package/dist/actions/synthetic-input.d.ts +0 -7
- package/dist/actions/synthetic-input.js +0 -27
- package/dist/actions/value-input.d.ts +0 -30
- package/dist/actions/value-input.js +0 -33
- package/dist/commands/commands.d.ts +0 -2
- package/dist/commands/commands.js +0 -67
- package/dist/connect-options.d.ts +2 -96
- package/dist/dom/a11y.d.ts +0 -29
- package/dist/dom/a11y.js +1 -88
- package/dist/dom/auto-anchor.d.ts +0 -40
- package/dist/dom/auto-anchor.js +0 -33
- package/dist/dom/chart.d.ts +0 -61
- package/dist/dom/chart.js +0 -60
- package/dist/dom/desktop-capture.d.ts +0 -30
- package/dist/dom/desktop-capture.js +0 -72
- package/dist/dom/dom-ignore.d.ts +0 -30
- package/dist/dom/dom-ignore.js +0 -47
- package/dist/dom/occlusion.d.ts +0 -7
- package/dist/dom/occlusion.js +0 -7
- package/dist/dom/query.d.ts +0 -14
- package/dist/dom/query.js +48 -214
- package/dist/dom/realm.d.ts +0 -20
- package/dist/dom/realm.js +0 -28
- package/dist/dom/refs.d.ts +0 -76
- package/dist/dom/refs.js +2 -133
- package/dist/dom/shadow-registry.d.ts +0 -33
- package/dist/dom/shadow-registry.js +2 -72
- package/dist/dom/snapshot.d.ts +2 -43
- package/dist/dom/snapshot.js +26 -151
- package/dist/dom/source.d.ts +0 -44
- package/dist/dom/source.js +0 -45
- package/dist/dom/theme.d.ts +0 -37
- package/dist/dom/theme.js +2 -52
- package/dist/dom/virtualized.d.ts +0 -17
- package/dist/dom/virtualized.js +0 -27
- package/dist/edit-epoch.d.ts +0 -22
- package/dist/edit-epoch.js +0 -52
- package/dist/index.js +1 -23
- package/dist/observers/animation.d.ts +0 -1
- package/dist/observers/animation.js +0 -3
- package/dist/observers/blind-spots.d.ts +0 -26
- package/dist/observers/blind-spots.js +2 -61
- package/dist/observers/console.d.ts +1 -2
- package/dist/observers/console.js +41 -30
- package/dist/observers/context-open.d.ts +1 -11
- package/dist/observers/context-open.js +10 -14
- package/dist/observers/departure.d.ts +3 -0
- package/dist/observers/departure.js +52 -0
- package/dist/observers/dialogs.d.ts +2 -0
- package/dist/observers/dialogs.js +39 -0
- package/dist/observers/dom.js +0 -44
- package/dist/observers/download.d.ts +1 -2
- package/dist/observers/download.js +38 -55
- package/dist/observers/focus.d.ts +0 -6
- package/dist/observers/focus.js +0 -7
- package/dist/observers/frames.d.ts +0 -9
- package/dist/observers/frames.js +1 -28
- package/dist/observers/health.d.ts +0 -5
- package/dist/observers/health.js +1 -33
- package/dist/observers/install-all.d.ts +2 -0
- package/dist/observers/install-all.js +26 -27
- package/dist/observers/ipc.d.ts +0 -25
- package/dist/observers/ipc.js +0 -65
- package/dist/observers/navigation.d.ts +2 -0
- package/dist/observers/navigation.js +33 -0
- package/dist/observers/network-body.d.ts +5 -28
- package/dist/observers/network-body.js +11 -108
- package/dist/observers/network-redact.d.ts +1 -20
- package/dist/observers/network-redact.js +1 -80
- package/dist/observers/network-stream.d.ts +0 -21
- package/dist/observers/network-stream.js +1 -44
- package/dist/observers/network.d.ts +2 -26
- package/dist/observers/network.js +177 -294
- package/dist/observers/perf.d.ts +0 -10
- package/dist/observers/perf.js +0 -16
- package/dist/observers/route.d.ts +1 -2
- package/dist/observers/route.js +13 -19
- package/dist/observers/scroll.d.ts +0 -1
- package/dist/observers/scroll.js +0 -9
- package/dist/observers/sdk-failure.d.ts +1 -9
- package/dist/observers/sdk-failure.js +1 -30
- package/dist/observers/state.d.ts +1 -11
- package/dist/observers/state.js +13 -41
- package/dist/observers/storage.d.ts +0 -14
- package/dist/observers/storage.js +10 -34
- package/dist/observers/types.d.ts +0 -15
- package/dist/observers/types.js +0 -14
- package/dist/patching/capture-method.d.ts +0 -24
- package/dist/patching/capture-method.js +0 -26
- package/dist/presenter/overlay.d.ts +0 -1
- package/dist/presenter/overlay.js +0 -1
- package/dist/presenter/presenter-brand.d.ts +0 -19
- package/dist/presenter/presenter-brand.js +0 -21
- package/dist/presenter/presenter-config.d.ts +1 -105
- package/dist/presenter/presenter-config.js +2 -91
- package/dist/presenter/presenter-controls.d.ts +0 -29
- package/dist/presenter/presenter-controls.js +3 -131
- package/dist/presenter/presenter-dock-layout.d.ts +0 -3
- package/dist/presenter/presenter-dock-layout.js +0 -4
- package/dist/presenter/presenter-drag.d.ts +2 -24
- package/dist/presenter/presenter-drag.js +0 -26
- package/dist/presenter/presenter-effects.d.ts +0 -9
- package/dist/presenter/presenter-effects.js +0 -9
- package/dist/presenter/presenter-glow.d.ts +0 -21
- package/dist/presenter/presenter-glow.js +1 -27
- package/dist/presenter/presenter-heroicons-data.d.ts +0 -8
- package/dist/presenter/presenter-heroicons-data.js +0 -8
- package/dist/presenter/presenter-hud-chrome.d.ts +0 -10
- package/dist/presenter/presenter-hud-chrome.js +0 -10
- package/dist/presenter/presenter-icons.d.ts +0 -6
- package/dist/presenter/presenter-icons.js +0 -6
- package/dist/presenter/presenter-log.d.ts +0 -38
- package/dist/presenter/presenter-log.js +1 -45
- package/dist/presenter/presenter-report-copy.d.ts +5 -66
- package/dist/presenter/presenter-report-copy.js +5 -66
- package/dist/presenter/presenter-report-styles.d.ts +0 -6
- package/dist/presenter/presenter-report-styles.js +6 -6
- package/dist/presenter/presenter-report.d.ts +4 -7
- package/dist/presenter/presenter-report.js +18 -76
- package/dist/presenter/presenter-run-state.d.ts +0 -7
- package/dist/presenter/presenter-run-state.js +0 -5
- package/dist/presenter/presenter-settings-styles.d.ts +0 -1
- package/dist/presenter/presenter-settings-styles.js +0 -1
- package/dist/presenter/presenter-settings.d.ts +1 -23
- package/dist/presenter/presenter-settings.js +1 -31
- package/dist/presenter/presenter-shell-styles.js +2 -5
- package/dist/presenter/presenter-shell.d.ts +2 -18
- package/dist/presenter/presenter-shell.js +1 -73
- package/dist/presenter/presenter-styles.d.ts +0 -9
- package/dist/presenter/presenter-styles.js +0 -9
- package/dist/presenter/presenter-tally.d.ts +0 -5
- package/dist/presenter/presenter-tally.js +0 -4
- package/dist/presenter/presenter-test-helpers.d.ts +0 -2
- package/dist/presenter/presenter-test-helpers.js +0 -2
- package/dist/presenter/presenter-verbs.js +0 -7
- package/dist/presenter/presenter-workspace.d.ts +0 -4
- package/dist/presenter/presenter-workspace.js +1 -8
- package/dist/presenter/presenter.d.ts +0 -60
- package/dist/presenter/presenter.js +4 -118
- package/dist/recorder/recorder-styles.d.ts +0 -6
- package/dist/recorder/recorder-styles.js +0 -6
- package/dist/recorder/recorder.d.ts +0 -34
- package/dist/recorder/recorder.js +0 -49
- package/dist/registry/adapters.d.ts +0 -10
- package/dist/registry/adapters.js +0 -10
- package/dist/registry/auto-testids.d.ts +0 -19
- package/dist/registry/auto-testids.js +1 -21
- package/dist/registry/capabilities.d.ts +0 -31
- package/dist/registry/capabilities.js +1 -46
- package/dist/registry/commit-and-signal.d.ts +0 -6
- package/dist/registry/commit-and-signal.js +1 -7
- package/dist/registry/domains.d.ts +0 -8
- package/dist/registry/domains.js +0 -7
- package/dist/registry/emitter.d.ts +0 -14
- package/dist/registry/emitter.js +0 -10
- package/dist/registry/store-adapters.d.ts +0 -186
- package/dist/registry/store-adapters.js +0 -163
- package/dist/registry/stores.d.ts +0 -33
- package/dist/registry/stores.js +0 -59
- package/dist/reticle-presenter-helpers.d.ts +0 -13
- package/dist/reticle-presenter-helpers.js +0 -14
- package/dist/reticle.d.ts +1 -52
- package/dist/reticle.js +20 -145
- package/dist/review/annotator-styles.js +0 -7
- package/dist/review/annotator.d.ts +2 -13
- package/dist/review/annotator.js +0 -40
- package/dist/review/mark-anchor.d.ts +0 -11
- package/dist/review/mark-anchor.js +0 -8
- package/dist/security/serialization.d.ts +0 -20
- package/dist/security/serialization.js +1 -66
- package/dist/session-continuity.d.ts +0 -16
- package/dist/session-continuity.js +1 -20
- package/dist/timers/clock.d.ts +1 -14
- package/dist/timers/clock.js +59 -60
- package/dist/timers/native-console.js +0 -4
- package/dist/timers/native-timers.d.ts +0 -25
- package/dist/timers/native-timers.js +3 -33
- package/dist/transport/transport.d.ts +0 -51
- package/dist/transport/transport.js +3 -129
- package/dist/transport/unreachable-message.d.ts +0 -17
- package/dist/transport/unreachable-message.js +0 -17
- package/dist/util/captured-method.d.ts +0 -35
- package/dist/util/captured-method.js +0 -35
- package/package.json +2 -2
|
@@ -16,13 +16,8 @@ import { renderTally } from './presenter-tally.js';
|
|
|
16
16
|
import { CONTROLS_BANNER_HTML, CONTROLS_FLOWS_HTML, CONTROLS_FOOT_HTML, ENDED_FADE_MS, ControlPanel, } from './presenter-controls.js';
|
|
17
17
|
import { statusTheme, blockerHtml, getPresenterSettings, OutputDetail, syncPageBlocker, } from './presenter-settings.js';
|
|
18
18
|
import { Annotator } from '../review/annotator.js';
|
|
19
|
-
// Re-export the config surface so the public import path (`./presenter.js`) is unchanged.
|
|
20
19
|
export { GlowPhase } from './presenter-config.js';
|
|
21
20
|
export { LOG_KIND, LOG_RESULT } from './presenter-log.js';
|
|
22
|
-
// Presenter / transparency layer: a human watches the agent work. Glowing border while
|
|
23
|
-
// active, a synthetic cursor that flies to targets, click/hover/type effects, and a HUD that
|
|
24
|
-
// shows the current action + the agent's narrated intent. All nodes carry data-reticle-* attrs
|
|
25
|
-
// so they're excluded from snapshots/observers (see dom-ignore.ts).
|
|
26
21
|
export class Presenter {
|
|
27
22
|
#paceMs;
|
|
28
23
|
#root;
|
|
@@ -32,12 +27,9 @@ export class Presenter {
|
|
|
32
27
|
#hud;
|
|
33
28
|
#actLine;
|
|
34
29
|
#actStrip;
|
|
35
|
-
/** Label inside the minimised-chat capsule; mirrors the act strip. */
|
|
36
30
|
#chatPillText;
|
|
37
|
-
/** Age of the last action, shown in the capsule's own slot. */
|
|
38
31
|
#chatPillTime;
|
|
39
32
|
#chip;
|
|
40
|
-
/** Live verdict tally (✓N ✗M) in the header - the running testing score the human watches. */
|
|
41
33
|
#tally;
|
|
42
34
|
#tallied = { passes: 0, fails: 0 };
|
|
43
35
|
#mode = PresenterMode.IDLE;
|
|
@@ -45,25 +37,18 @@ export class Presenter {
|
|
|
45
37
|
#heartbeatMs;
|
|
46
38
|
#idleNoticeMs;
|
|
47
39
|
#borderMode;
|
|
48
|
-
/** The glow / activity state machine (border shimmer + cursor visibility from activity timing). */
|
|
49
40
|
#glowCtl;
|
|
50
|
-
/** Liveness: the most recent action text + a 1s ticker that ages it into an "idle · {dur}" clock. */
|
|
51
41
|
#lastActionText = '';
|
|
52
42
|
#heartbeatTimer;
|
|
53
|
-
/** Session lifecycle: idle-end window (tweakable), session id, start/end cursors, structured run log. */
|
|
54
43
|
#idleEndMs;
|
|
55
44
|
#sessionId;
|
|
56
45
|
#startMs;
|
|
57
46
|
#endMs;
|
|
58
47
|
#runLog = [];
|
|
59
|
-
/** Tracks sessionStart/sessionEnd so both are idempotent (no strobe / no spurious off-write). */
|
|
60
48
|
#sessionActive = false;
|
|
61
|
-
// v2: narration + action status accumulate in a persistent, timestamped, scrollable log.
|
|
62
49
|
#logMax;
|
|
63
50
|
#log;
|
|
64
|
-
/** now of the first row, the baseline for the +elapsed timestamps. */
|
|
65
51
|
#logBaseMs;
|
|
66
|
-
// Live-control panel: the two-way control surface (Pause/Resume + End + message Send).
|
|
67
52
|
#onControl;
|
|
68
53
|
#panel;
|
|
69
54
|
#shell;
|
|
@@ -110,26 +95,18 @@ export class Presenter {
|
|
|
110
95
|
},
|
|
111
96
|
});
|
|
112
97
|
}
|
|
113
|
-
/** Setter so reticle.ts can wire the control callback after construction. */
|
|
114
98
|
setControlHandler(handler) {
|
|
115
99
|
this.#onControl = handler;
|
|
116
100
|
}
|
|
117
|
-
/** Current live-control session state mirrored onto the panel (data-reticle-state). */
|
|
118
101
|
get state() {
|
|
119
102
|
return this.#panel.state;
|
|
120
103
|
}
|
|
121
|
-
/** Whether a run is currently being presented (false before the agent's first activity / after end). */
|
|
122
104
|
get sessionActive() {
|
|
123
105
|
return this.#sessionActive;
|
|
124
106
|
}
|
|
125
|
-
/** Drive the panel's live-control visual state (server-push / agent path; never emits). */
|
|
126
107
|
setState(state, text, tone) {
|
|
127
108
|
this.#panel.setState(state, text, tone);
|
|
128
109
|
}
|
|
129
|
-
/** Apply a bridge→browser presenter push: PRESENTER (state echo) or FLOWS (replay list, the human's
|
|
130
|
-
* no-agent replay surface). Owns the wire parsing so the SDK dispatcher stays a thin router;
|
|
131
|
-
* setState-only so an echo can't re-emit. */
|
|
132
|
-
/** Re-scope the replay-flow chips to the current page (called by the SDK on route change). */
|
|
133
110
|
refilterFlows() {
|
|
134
111
|
this.#panel.refilterFlows();
|
|
135
112
|
}
|
|
@@ -149,7 +126,6 @@ export class Presenter {
|
|
|
149
126
|
if (isSessionState(state))
|
|
150
127
|
this.setState(state, text, isPresenterTone(tone) ? tone : undefined);
|
|
151
128
|
}
|
|
152
|
-
/** Current cap on accumulated log rows. */
|
|
153
129
|
get logMax() {
|
|
154
130
|
return this.#logMax;
|
|
155
131
|
}
|
|
@@ -166,10 +142,6 @@ export class Presenter {
|
|
|
166
142
|
document.head.appendChild(style);
|
|
167
143
|
const root = document.createElement('div');
|
|
168
144
|
root.setAttribute('data-reticle-overlay', '');
|
|
169
|
-
// The mode rides ON the status row, next to the dot and the elapsed time it already reports.
|
|
170
|
-
// It used to be a sibling BELOW the strip, hidden except while reading or acting — so every
|
|
171
|
-
// single tool call popped a block into the panel and took it away again, which reads as a
|
|
172
|
-
// second UI flashing in rather than as the one status line changing what it says.
|
|
173
145
|
const actStrip = `<div class="reticle-act-strip" data-liveness="idle"><span class="reticle-act-dot" aria-hidden="true"></span><span class="reticle-act">${ACT_STRIP.READY}</span><span class="reticle-chip" data-reticle-chip></span></div>`;
|
|
174
146
|
root.innerHTML = `
|
|
175
147
|
${blockerHtml()}
|
|
@@ -195,12 +167,10 @@ export class Presenter {
|
|
|
195
167
|
this.#shell.mount(root);
|
|
196
168
|
syncPageBlocker(root, getPresenterSettings(), false);
|
|
197
169
|
this.#glowCtl.setElements(this.#glow, this.#cursor);
|
|
198
|
-
// The panel queries its refs, binds listeners, and paints the initial active state.
|
|
199
170
|
this.#panel.mount(root, this.#glow);
|
|
200
171
|
this.setMode(this.#mode);
|
|
201
172
|
this.#renderTally();
|
|
202
173
|
}
|
|
203
|
-
/** Wire annotation chrome; expanding the HUD enters annotate mode. */
|
|
204
174
|
bindAnnotator(annotator) {
|
|
205
175
|
this.#annotator = annotator;
|
|
206
176
|
const root = this.#root;
|
|
@@ -220,12 +190,7 @@ export class Presenter {
|
|
|
220
190
|
annotator.setAccent(statusTheme(getPresenterSettings().statusThemeId).active);
|
|
221
191
|
this.#syncAnnotator();
|
|
222
192
|
}
|
|
223
|
-
/** Annotate whenever the HUD is open and the person asked for it - agent or no agent. */
|
|
224
193
|
#syncAnnotator() {
|
|
225
|
-
// Two things have to agree, and both are the user's: the HUD is open, and annotate is switched
|
|
226
|
-
// on. Session state used to be a third, so the mode was refused the moment the agent
|
|
227
|
-
// disconnected - which is precisely when someone opens the HUD to record what they just saw.
|
|
228
|
-
// The button still lit up, so the refusal was invisible: no outline, no composer, no reason.
|
|
229
194
|
const live = !this.#shell.isCollapsed() && this.#shell.isAnnotateOn();
|
|
230
195
|
this.#annotator?.toggle(live);
|
|
231
196
|
if (this.#root !== undefined) {
|
|
@@ -246,19 +211,13 @@ export class Presenter {
|
|
|
246
211
|
document.querySelectorAll('style[data-reticle-overlay]').forEach((s) => s.remove());
|
|
247
212
|
this.#root = undefined;
|
|
248
213
|
}
|
|
249
|
-
/**
|
|
250
|
-
* Session start: in 'session' border mode this fades the base border IN and keeps it on until
|
|
251
|
-
* sessionEnd. Idempotent, and a no-op when unmounted or in 'busy' border mode.
|
|
252
|
-
*/
|
|
253
214
|
sessionStart() {
|
|
254
|
-
// Returning agent activity after the session ended (idle or explicit) revives it as a fresh run.
|
|
255
215
|
if (this.state === SessionState.ENDED) {
|
|
256
216
|
this.#revive();
|
|
257
217
|
return;
|
|
258
218
|
}
|
|
259
219
|
if (this.#sessionActive)
|
|
260
220
|
return;
|
|
261
|
-
// A bridge that answered supersedes whatever the failed connect left on screen.
|
|
262
221
|
if (UNREACHABLE_STATE === this.#root?.getAttribute(STATE_ATTR))
|
|
263
222
|
this.#root.removeAttribute(STATE_ATTR);
|
|
264
223
|
this.#sessionActive = true;
|
|
@@ -271,43 +230,25 @@ export class Presenter {
|
|
|
271
230
|
this.#shell.openChat();
|
|
272
231
|
}
|
|
273
232
|
}
|
|
274
|
-
/**
|
|
275
|
-
* The bridge never answered: show that, rather than showing nothing.
|
|
276
|
-
*
|
|
277
|
-
* An instrumented page with a dead bridge used to be indistinguishable from a page with no
|
|
278
|
-
* Reticle in it — overlay mounted, dock off, nothing on screen. The user cannot tell "I forgot to
|
|
279
|
-
* start the daemon" from "the install did not work", and the commonest cause is the cheapest to
|
|
280
|
-
* say: the port. So the HUD appears, states the URL it tried, and marks itself unreachable so it
|
|
281
|
-
* is never mistaken for a live session.
|
|
282
|
-
*
|
|
283
|
-
* Not an error dialog and not modal: the page is the user's, and a dev overlay that shouts is one
|
|
284
|
-
* they turn off. It is the same capsule they would have had, saying the one thing it knows.
|
|
285
|
-
*/
|
|
286
233
|
showUnreachable(url, attempts) {
|
|
287
234
|
if (this.#sessionActive)
|
|
288
235
|
return;
|
|
289
236
|
this.#root?.setAttribute(STATE_ATTR, UNREACHABLE_STATE);
|
|
290
|
-
// Dock and HUD, but NOT the border glow: the glow means the agent is working, and nothing is.
|
|
291
237
|
const dock = this.#root?.querySelector('[data-reticle-dock]');
|
|
292
238
|
dock?.setAttribute(DATA_ON, GLOW_ON);
|
|
293
239
|
this.#hud?.setAttribute(DATA_ON, GLOW_ON);
|
|
294
240
|
this.#lastActionText = unreachableStripText(url, attempts);
|
|
295
241
|
this.#paintActStrip(this.#lastActionText, true);
|
|
296
|
-
// The message IS the reason this state exists, and a collapsed capsule hides it. Same setting
|
|
297
|
-
// a live session honours, so a user who wants the bare toolbar still gets one.
|
|
298
242
|
if (getPresenterSettings().autoOpenChat)
|
|
299
243
|
this.#shell.openChat();
|
|
300
244
|
}
|
|
301
|
-
/** Turn the base border (session mode) + the HUD/log on - the visible "session is live" state. */
|
|
302
245
|
#showSession() {
|
|
303
246
|
const dock = this.#root?.querySelector('[data-reticle-dock]');
|
|
304
247
|
dock?.setAttribute(DATA_ON, GLOW_ON);
|
|
305
248
|
this.#hud?.setAttribute(DATA_ON, GLOW_ON);
|
|
306
|
-
// Base border persists in 'session' mode; 'busy' mode leaves it to the busy machine.
|
|
307
249
|
if (this.#borderMode === BorderMode.SESSION)
|
|
308
250
|
this.#glow?.setAttribute(DATA_ON, GLOW_ON);
|
|
309
251
|
}
|
|
310
|
-
/** Revive after an ended session (new agent activity): clear the ended state + glow back on. */
|
|
311
252
|
#revive() {
|
|
312
253
|
this.#panel.setState(SessionState.ACTIVE);
|
|
313
254
|
this.#endMs = undefined;
|
|
@@ -315,10 +256,6 @@ export class Presenter {
|
|
|
315
256
|
this.#glowCtl.resetActivity(this.#now());
|
|
316
257
|
this.#startHeartbeat();
|
|
317
258
|
}
|
|
318
|
-
/**
|
|
319
|
-
* Session end: hides the log/HUD and (in 'session' mode) clears the base border. Idempotent; a
|
|
320
|
-
* no-op without a prior sessionStart or when unmounted.
|
|
321
|
-
*/
|
|
322
259
|
sessionEnd() {
|
|
323
260
|
if (!this.#sessionActive)
|
|
324
261
|
return;
|
|
@@ -336,30 +273,18 @@ export class Presenter {
|
|
|
336
273
|
this.#glow?.setAttribute(DATA_BUSY, BUSY_OFF);
|
|
337
274
|
}
|
|
338
275
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Record agent activity. Idempotent while busy - only the first activity from idle/fading flips
|
|
341
|
-
* the glow on, so a burst never restarts the reticle-pulse animation (no strobe). Subsequent calls
|
|
342
|
-
* just refresh the last-activity timestamp and re-arm the idle check.
|
|
343
|
-
*/
|
|
344
276
|
markActivity() {
|
|
345
277
|
this.#glowCtl.markActivity();
|
|
346
278
|
}
|
|
347
|
-
/** Re-arm the quiet-window idle check (kept for reticle.ts's finally block). */
|
|
348
279
|
scheduleIdle() {
|
|
349
280
|
this.#glowCtl.scheduleIdle();
|
|
350
281
|
}
|
|
351
|
-
/** Test/diagnostic accessor for the current glow phase. */
|
|
352
282
|
glowPhase() {
|
|
353
283
|
return this.#glowCtl.phase();
|
|
354
284
|
}
|
|
355
|
-
/** Current intent (reading vs acting), exposed for tests + the watcher. */
|
|
356
285
|
get mode() {
|
|
357
286
|
return this.#mode;
|
|
358
287
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Set the presenter intent. READING shows a cyan scan + chip and hides the cursor; ACTING
|
|
361
|
-
* keeps the warm cursor/ripple + chip; IDLE clears the chip. Drives color via data-reticle-mode.
|
|
362
|
-
*/
|
|
363
288
|
setMode(mode) {
|
|
364
289
|
this.#mode = mode;
|
|
365
290
|
this.#root?.setAttribute('data-reticle-mode', mode);
|
|
@@ -381,7 +306,6 @@ export class Presenter {
|
|
|
381
306
|
this.#chip.appendChild(labelEl);
|
|
382
307
|
}
|
|
383
308
|
}
|
|
384
|
-
// READING has no real pointer to show (synthetic-hover pointer is native-only) - hide the cursor.
|
|
385
309
|
if (mode === PresenterMode.READING)
|
|
386
310
|
this.#cursor?.setAttribute(DATA_ON, GLOW_OFF);
|
|
387
311
|
}
|
|
@@ -392,13 +316,6 @@ export class Presenter {
|
|
|
392
316
|
this.#lastActionText = text;
|
|
393
317
|
this.#paintActStrip(text, false);
|
|
394
318
|
}
|
|
395
|
-
/**
|
|
396
|
-
* Sync act-strip text + the live/idle state.
|
|
397
|
-
*
|
|
398
|
-
* The liveness is mirrored onto the overlay root as well as the strip, because that is what the
|
|
399
|
-
* status COLOUR resolves against: the page glow, the collapsed FAB's halo and the minimised
|
|
400
|
-
* capsule all live outside the strip and still have to say whether the agent is working.
|
|
401
|
-
*/
|
|
402
319
|
#paintActStrip(text, idle) {
|
|
403
320
|
if (this.#actLine !== undefined)
|
|
404
321
|
this.#actLine.textContent = text;
|
|
@@ -407,9 +324,6 @@ export class Presenter {
|
|
|
407
324
|
this.#actStrip.setAttribute('data-liveness', liveness);
|
|
408
325
|
this.#root?.setAttribute(LIVENESS_ATTR, liveness);
|
|
409
326
|
if (this.#chatPillText !== undefined) {
|
|
410
|
-
// The capsule reads "logo | what the agent did | how long ago | expand", so it takes the
|
|
411
|
-
// ACTION, never the composed "idle · 12s since last action" line the strip shows: the age
|
|
412
|
-
// lives in its own slot next to it.
|
|
413
327
|
this.#chatPillText.textContent = idle
|
|
414
328
|
? this.#lastActionText !== ''
|
|
415
329
|
? this.#lastActionText
|
|
@@ -417,12 +331,6 @@ export class Presenter {
|
|
|
417
331
|
: text;
|
|
418
332
|
}
|
|
419
333
|
}
|
|
420
|
-
/**
|
|
421
|
-
* Liveness heartbeat (native 1s timer - never rAF, so it ticks in a foreground tab regardless of
|
|
422
|
-
* agent activity). Once the agent has been quiet for IDLE_NOTICE_MS, the act strip shows a LIVE,
|
|
423
|
-
* growing "◌ idle · {duration} since last action" - the signal that was missing when a stopped
|
|
424
|
-
* agent left the panel frozen and indistinguishable from one still thinking.
|
|
425
|
-
*/
|
|
426
334
|
#startHeartbeat() {
|
|
427
335
|
if (this.#heartbeatTimer !== undefined)
|
|
428
336
|
nativeClearTimeout(this.#heartbeatTimer);
|
|
@@ -436,20 +344,19 @@ export class Presenter {
|
|
|
436
344
|
if (!this.#sessionActive || this.#actLine === undefined)
|
|
437
345
|
return;
|
|
438
346
|
if (this.state === SessionState.ENDED)
|
|
439
|
-
return;
|
|
347
|
+
return;
|
|
440
348
|
const idleMs = this.#now() - this.#glowCtl.lastActivityMs();
|
|
441
349
|
if (idleMs >= this.#idleEndMs) {
|
|
442
|
-
this.#endIdle(idleMs);
|
|
350
|
+
this.#endIdle(idleMs);
|
|
443
351
|
return;
|
|
444
352
|
}
|
|
445
353
|
if (idleMs < this.#idleNoticeMs)
|
|
446
|
-
return;
|
|
354
|
+
return;
|
|
447
355
|
const since = this.#lastActionText !== '' ? ACT_STRIP.SINCE_LAST : '';
|
|
448
356
|
this.#paintActStrip(`${ACT_STRIP.IDLE_PREFIX}${humanDuration(idleMs)}${since}`, true);
|
|
449
357
|
if (this.#chatPillTime !== undefined)
|
|
450
358
|
this.#chatPillTime.textContent = humanDuration(idleMs);
|
|
451
359
|
}
|
|
452
|
-
/** Auto-end after the idle window: stamp the end, drive the panel to ENDED, stop the heartbeat. */
|
|
453
360
|
#endIdle(idleMs) {
|
|
454
361
|
this.#endMs = this.#now();
|
|
455
362
|
this.#panel.setState(SessionState.ENDED, `idle ${humanDuration(idleMs)}`);
|
|
@@ -458,17 +365,11 @@ export class Presenter {
|
|
|
458
365
|
this.#heartbeatTimer = undefined;
|
|
459
366
|
}
|
|
460
367
|
}
|
|
461
|
-
/** Agent-tunable idle-end window (reticle_session). Floored so it can't be set uselessly small. */
|
|
462
368
|
setIdleEndMs(ms) {
|
|
463
369
|
if (!Number.isFinite(ms))
|
|
464
370
|
return;
|
|
465
371
|
this.#idleEndMs = Math.max(IDLE_END_MIN_MS, Math.floor(ms));
|
|
466
372
|
}
|
|
467
|
-
/**
|
|
468
|
-
* The exported "run state" for the Copy/Export buttons - everything the page holds about this
|
|
469
|
-
* run: session id, url, duration, capability surface, per-kind counts, and the full activity log.
|
|
470
|
-
* (The full network/console ring-buffer lives server-side; this is the in-page run summary.)
|
|
471
|
-
*/
|
|
472
373
|
runState() {
|
|
473
374
|
const base = buildRunState({
|
|
474
375
|
sessionId: this.#sessionId,
|
|
@@ -518,11 +419,6 @@ export class Presenter {
|
|
|
518
419
|
syncPageBlocker(this.#root, settings, live);
|
|
519
420
|
}
|
|
520
421
|
}
|
|
521
|
-
/**
|
|
522
|
-
* Append an activity-log row. Accumulates (never overwrites): each call adds a timestamped row
|
|
523
|
-
* with a mode chip + text. Returns a handle to stamp the row's outcome glyph (✓/✗) later, or
|
|
524
|
-
* undefined when unmounted / when the text is empty after trimming.
|
|
525
|
-
*/
|
|
526
422
|
log(kind, text, result) {
|
|
527
423
|
const ms = this.#now();
|
|
528
424
|
this.#glowCtl.markActivity(ms);
|
|
@@ -532,7 +428,6 @@ export class Presenter {
|
|
|
532
428
|
if (0 === trimmed.length)
|
|
533
429
|
return undefined;
|
|
534
430
|
this.#logBaseMs ??= ms;
|
|
535
|
-
// Structured run-log entry (mirrors the DOM row) for the exported run state, capped like the DOM.
|
|
536
431
|
const entry = result !== undefined
|
|
537
432
|
? { at: ms - this.#logBaseMs, kind, text: trimmed, result }
|
|
538
433
|
: { at: ms - this.#logBaseMs, kind, text: trimmed };
|
|
@@ -546,16 +441,14 @@ export class Presenter {
|
|
|
546
441
|
if (this.#shell.isCollapsed())
|
|
547
442
|
this.#shell.pulseFab(true);
|
|
548
443
|
this.#renderTally();
|
|
549
|
-
// Wrap the handle so a later outcome stamp updates BOTH the DOM glyph and the run-log entry.
|
|
550
444
|
return {
|
|
551
445
|
result: (r) => {
|
|
552
446
|
handle.result(r);
|
|
553
447
|
entry.result = r;
|
|
554
|
-
this.#renderTally();
|
|
448
|
+
this.#renderTally();
|
|
555
449
|
},
|
|
556
450
|
};
|
|
557
451
|
}
|
|
558
|
-
/** Repaint the header verdict tally from the run log; the side that grew gets a one-shot pop. */
|
|
559
452
|
#renderTally() {
|
|
560
453
|
if (!getPresenterSettings().showTally) {
|
|
561
454
|
this.#tally?.setAttribute('hidden', '');
|
|
@@ -563,7 +456,6 @@ export class Presenter {
|
|
|
563
456
|
}
|
|
564
457
|
this.#tallied = renderTally(this.#tally, this.#runLog, this.#tallied);
|
|
565
458
|
}
|
|
566
|
-
/** Back-compat: narration appends to the live log (append-only, never overwrites). */
|
|
567
459
|
narrate(text, level = 'info') {
|
|
568
460
|
const line = 'info' === level ? text : `[${level}] ${text}`;
|
|
569
461
|
return this.log(LOG_KIND.NARRATION, line);
|
|
@@ -575,11 +467,6 @@ export class Presenter {
|
|
|
575
467
|
this.#log.firstElementChild?.remove();
|
|
576
468
|
}
|
|
577
469
|
}
|
|
578
|
-
/**
|
|
579
|
-
* Mirror the server's session.throttled state onto the HUD border. When throttled (tab
|
|
580
|
-
* backgrounded or stale), the border turns amber so the developer knows actions are no-oping -
|
|
581
|
-
* the same signal the agent already reads from result.session.throttled.
|
|
582
|
-
*/
|
|
583
470
|
setThrottled(throttled) {
|
|
584
471
|
this.#root?.setAttribute(THROTTLED_ATTR, throttled ? '1' : '0');
|
|
585
472
|
if (throttled && this.#actLine !== undefined) {
|
|
@@ -587,7 +474,6 @@ export class Presenter {
|
|
|
587
474
|
'Tab backgrounded - actions throttled. Bring tab to front or use `reticle drive`.';
|
|
588
475
|
}
|
|
589
476
|
}
|
|
590
|
-
/** Fly the cursor to an element, play the action's effect, then pace for the human. */
|
|
591
477
|
async beforeAct(refId, action, label) {
|
|
592
478
|
const el = refs.resolve(refId);
|
|
593
479
|
this.status(`${actionVerb(action)} ${label}`);
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inline style strings for the floating recorder toolbar. Browser-local UI
|
|
3
|
-
* text (never crosses the wire), split out of recorder.ts to keep that file under the 500-line cap
|
|
4
|
-
* (mirrors how presenter.ts extracts presenter-log.ts). All toolbar nodes also carry
|
|
5
|
-
* data-reticle-overlay, so they are snapshot-excluded via dom-ignore.ts regardless of these styles.
|
|
6
|
-
*/
|
|
7
1
|
export declare const TOOLBAR_CSS: string;
|
|
8
2
|
export declare const BTN_CSS: string;
|
|
9
3
|
export declare const NAME_CSS: string;
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inline style strings for the floating recorder toolbar. Browser-local UI
|
|
3
|
-
* text (never crosses the wire), split out of recorder.ts to keep that file under the 500-line cap
|
|
4
|
-
* (mirrors how presenter.ts extracts presenter-log.ts). All toolbar nodes also carry
|
|
5
|
-
* data-reticle-overlay, so they are snapshot-excluded via dom-ignore.ts regardless of these styles.
|
|
6
|
-
*/
|
|
7
1
|
export const TOOLBAR_CSS = [
|
|
8
2
|
'position:fixed',
|
|
9
3
|
'top:8px',
|
|
@@ -1,59 +1,25 @@
|
|
|
1
1
|
import { AnnotationKind, EventType, RecorderPhase, type FlowAnchor, type FlowFile, type FlowStep } from '@reticlehq/core';
|
|
2
|
-
/**
|
|
3
|
-
* The human recorder. A floating toolbar (Record / Stop / Annotate) lets a
|
|
4
|
-
* human click the golden path; capture-phase listeners map each real interaction to a SEMANTIC
|
|
5
|
-
* anchor (testid → role+name → degraded placeholder, NEVER a volatile ref — invariant #1) and
|
|
6
|
-
* append a FlowStep. Stop compiles a FlowFile in-page and emits ONE EventType.FLOW_RECORDED event
|
|
7
|
-
* the server persists. Four structured annotation kinds compile into expect/dynamic/success.
|
|
8
|
-
*
|
|
9
|
-
* FIRST CUT scope: structured annotations only (the 4 AnnotationKind values via a menu + a signal
|
|
10
|
-
* <select> drawn from registered capabilities). Explicitly FUTURE, not faked here:
|
|
11
|
-
* - NL annotation → predicate compiler (CO-OWNED-FLOWS invariant #2 best-UX path).
|
|
12
|
-
* - fixtures/preconditions (FlowFile.fixture stays a schema slot, never written).
|
|
13
|
-
* - wait-for / ignore-region annotation kinds (only the 4 requested ship).
|
|
14
|
-
*/
|
|
15
|
-
/** Browser-local UI text — never crosses the wire, mirrors presenter.ts CHIP_LABEL precedent. */
|
|
16
2
|
export declare const RECORDER_EMPTY_MSG = "recorded 0 steps";
|
|
17
3
|
interface RecorderDeps {
|
|
18
|
-
/** Emit a wire event (Reticle.#emit bound). */
|
|
19
4
|
emit: (type: EventType, data: Record<string, unknown>) => void;
|
|
20
|
-
/** Injected clock for FlowFile.createdAt — never Date.now in pure logic (rule 7). */
|
|
21
5
|
now: () => number;
|
|
22
|
-
/** Default flow name when the toolbar field is blank. */
|
|
23
6
|
defaultName?: string;
|
|
24
7
|
}
|
|
25
8
|
export interface RecorderHandle {
|
|
26
9
|
mount(): void;
|
|
27
10
|
destroy(): void;
|
|
28
|
-
/** Test seam: current phase. */
|
|
29
11
|
phase(): RecorderPhase;
|
|
30
|
-
/** Test seam: steps captured so far in the active span. */
|
|
31
12
|
steps(): FlowStep[];
|
|
32
13
|
}
|
|
33
|
-
/** A structured annotation captured while recording (compiled into flow fields at Stop). */
|
|
34
14
|
export interface Annotation {
|
|
35
15
|
kind: AnnotationKind;
|
|
36
16
|
anchor: FlowAnchor;
|
|
37
|
-
/** Required for ASSERT_SIGNAL / SUCCESS_STATE; the chosen capability signal name. */
|
|
38
17
|
signal?: string;
|
|
39
18
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Pure: map a clicked/typed element → semantic FlowAnchor. testid wins; else role+accessible-name;
|
|
42
|
-
* else the DEGRADED_ANCHOR_ROLE placeholder + degraded:true (never a ref — invariant #1).
|
|
43
|
-
*/
|
|
44
19
|
export declare function anchorFor(el: Element): {
|
|
45
20
|
anchor: FlowAnchor;
|
|
46
21
|
degraded: boolean;
|
|
47
22
|
};
|
|
48
|
-
/**
|
|
49
|
-
* Pure: assemble the in-page FlowFile from captured steps + annotations.
|
|
50
|
-
* - ASSERT_SIGNAL → the most-recent step's expect.signal.
|
|
51
|
-
* - ASSERT_VISIBLE → the most-recent step's expect.element (from the annotation anchor).
|
|
52
|
-
* - MARK_DYNAMIC → push the anchor into flow.dynamic[].
|
|
53
|
-
* - SUCCESS_STATE → flow.success = { signal }.
|
|
54
|
-
* (FUTURE: per-step annotation targeting beyond the most-recent step.)
|
|
55
|
-
*/
|
|
56
23
|
export declare function compileRecording(name: string, steps: FlowStep[], annotations: Annotation[], createdAt: number, startPath?: string): FlowFile;
|
|
57
|
-
/** Install the floating recorder toolbar. Default off; gated by the host (reticle.ts recorder flag). */
|
|
58
24
|
export declare function installRecorder(deps: RecorderDeps): RecorderHandle;
|
|
59
25
|
export {};
|
|
@@ -4,20 +4,6 @@ import { getAccessibleName, getRole, isSensitiveField } from '../dom/a11y.js';
|
|
|
4
4
|
import { isReticleOverlay } from '../dom/dom-ignore.js';
|
|
5
5
|
import { getCapabilities } from '../registry/capabilities.js';
|
|
6
6
|
import { TOOLBAR_CSS, BTN_CSS, NAME_CSS, STATUS_CSS, MENU_CSS } from './recorder-styles.js';
|
|
7
|
-
/**
|
|
8
|
-
* The human recorder. A floating toolbar (Record / Stop / Annotate) lets a
|
|
9
|
-
* human click the golden path; capture-phase listeners map each real interaction to a SEMANTIC
|
|
10
|
-
* anchor (testid → role+name → degraded placeholder, NEVER a volatile ref — invariant #1) and
|
|
11
|
-
* append a FlowStep. Stop compiles a FlowFile in-page and emits ONE EventType.FLOW_RECORDED event
|
|
12
|
-
* the server persists. Four structured annotation kinds compile into expect/dynamic/success.
|
|
13
|
-
*
|
|
14
|
-
* FIRST CUT scope: structured annotations only (the 4 AnnotationKind values via a menu + a signal
|
|
15
|
-
* <select> drawn from registered capabilities). Explicitly FUTURE, not faked here:
|
|
16
|
-
* - NL annotation → predicate compiler (CO-OWNED-FLOWS invariant #2 best-UX path).
|
|
17
|
-
* - fixtures/preconditions (FlowFile.fixture stays a schema slot, never written).
|
|
18
|
-
* - wait-for / ignore-region annotation kinds (only the 4 requested ship).
|
|
19
|
-
*/
|
|
20
|
-
/** Browser-local UI text — never crosses the wire, mirrors presenter.ts CHIP_LABEL precedent. */
|
|
21
7
|
export const RECORDER_EMPTY_MSG = 'recorded 0 steps';
|
|
22
8
|
const STATUS_RECORDING = 'recording…';
|
|
23
9
|
const STATUS_IDLE = 'ready';
|
|
@@ -38,16 +24,11 @@ const ANNOTATION_LABEL = {
|
|
|
38
24
|
[AnnotationKind.SUCCESS_STATE]: 'success state',
|
|
39
25
|
[AnnotationKind.INTENT]: 'intent',
|
|
40
26
|
};
|
|
41
|
-
/** Annotation kinds whose compilation needs a signal name (from the capabilities <select>). */
|
|
42
27
|
const NEEDS_SIGNAL = new Set([
|
|
43
28
|
AnnotationKind.ASSERT_SIGNAL,
|
|
44
29
|
AnnotationKind.SUCCESS_STATE,
|
|
45
30
|
]);
|
|
46
31
|
const DEFAULT_NAME = 'recorded-flow';
|
|
47
|
-
/**
|
|
48
|
-
* Pure: map a clicked/typed element → semantic FlowAnchor. testid wins; else role+accessible-name;
|
|
49
|
-
* else the DEGRADED_ANCHOR_ROLE placeholder + degraded:true (never a ref — invariant #1).
|
|
50
|
-
*/
|
|
51
32
|
export function anchorFor(el) {
|
|
52
33
|
const testid = el.getAttribute(TESTID_ATTR);
|
|
53
34
|
if (testid !== null && testid.length > 0) {
|
|
@@ -59,7 +40,6 @@ export function anchorFor(el) {
|
|
|
59
40
|
const anchor = name.length > 0 ? { kind: AnchorKind.ROLE, role, name } : { kind: AnchorKind.ROLE, role };
|
|
60
41
|
return { anchor, degraded: false };
|
|
61
42
|
}
|
|
62
|
-
// No resolvable testid/role/name — keep the step with a legible placeholder, never a ref.
|
|
63
43
|
return { anchor: { kind: AnchorKind.ROLE, role: DEGRADED_ANCHOR_ROLE }, degraded: true };
|
|
64
44
|
}
|
|
65
45
|
function isTextbox(el) {
|
|
@@ -86,14 +66,6 @@ function buildStep(el, action, args) {
|
|
|
86
66
|
step.degraded = true;
|
|
87
67
|
return step;
|
|
88
68
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Pure: assemble the in-page FlowFile from captured steps + annotations.
|
|
91
|
-
* - ASSERT_SIGNAL → the most-recent step's expect.signal.
|
|
92
|
-
* - ASSERT_VISIBLE → the most-recent step's expect.element (from the annotation anchor).
|
|
93
|
-
* - MARK_DYNAMIC → push the anchor into flow.dynamic[].
|
|
94
|
-
* - SUCCESS_STATE → flow.success = { signal }.
|
|
95
|
-
* (FUTURE: per-step annotation targeting beyond the most-recent step.)
|
|
96
|
-
*/
|
|
97
69
|
export function compileRecording(name, steps, annotations, createdAt, startPath) {
|
|
98
70
|
const out = steps.map((s) => ({ ...s }));
|
|
99
71
|
const dynamic = [];
|
|
@@ -137,12 +109,10 @@ function anchorToElement(anchor) {
|
|
|
137
109
|
}
|
|
138
110
|
return {};
|
|
139
111
|
}
|
|
140
|
-
/** Live recorder instance: holds the capture buffer, toolbar DOM, and the phase machine. */
|
|
141
112
|
class Recorder {
|
|
142
113
|
#deps;
|
|
143
114
|
#phase = RecorderPhase.IDLE;
|
|
144
115
|
#steps = [];
|
|
145
|
-
/** The pathname the recording began on, so replay can navigate here before step 1. */
|
|
146
116
|
#startPath;
|
|
147
117
|
#annotations = [];
|
|
148
118
|
#pendingFill;
|
|
@@ -150,7 +120,6 @@ class Recorder {
|
|
|
150
120
|
#root;
|
|
151
121
|
#statusEl;
|
|
152
122
|
#menuEl;
|
|
153
|
-
/** The annotation being assembled in ANNOTATING phase. */
|
|
154
123
|
#draft;
|
|
155
124
|
constructor(deps) {
|
|
156
125
|
this.#deps = deps;
|
|
@@ -183,7 +152,6 @@ class Recorder {
|
|
|
183
152
|
this.#draft = undefined;
|
|
184
153
|
this.#pendingFill = undefined;
|
|
185
154
|
}
|
|
186
|
-
// ---- capture ----
|
|
187
155
|
#installCapture() {
|
|
188
156
|
const onClick = (ev) => this.#onClick(ev);
|
|
189
157
|
const onInput = (ev) => this.#onInput(ev);
|
|
@@ -200,7 +168,6 @@ class Recorder {
|
|
|
200
168
|
document.removeEventListener('submit', onSubmit, true);
|
|
201
169
|
});
|
|
202
170
|
}
|
|
203
|
-
/** True if the event should be ignored (toolbar self-click or non-element target). */
|
|
204
171
|
#ignore(ev) {
|
|
205
172
|
const target = ev.target;
|
|
206
173
|
if (!(target instanceof Element))
|
|
@@ -219,9 +186,6 @@ class Recorder {
|
|
|
219
186
|
}
|
|
220
187
|
if (this.#phase !== RecorderPhase.RECORDING)
|
|
221
188
|
return;
|
|
222
|
-
// Controls that emit `change` (checkbox, radio, select) are recorded by #onChange as their
|
|
223
|
-
// semantic step (CHECK/UNCHECK/SELECT). Recording a CLICK here too would replay any onChange
|
|
224
|
-
// side effect twice (analytics, dependent-field resets), so skip the duplicate.
|
|
225
189
|
if ((target instanceof HTMLInputElement &&
|
|
226
190
|
('checkbox' === inputRole(target) || 'radio' === inputRole(target))) ||
|
|
227
191
|
target instanceof HTMLSelectElement) {
|
|
@@ -236,7 +200,6 @@ class Recorder {
|
|
|
236
200
|
const target = this.#ignore(ev);
|
|
237
201
|
if (target === undefined || !isTextbox(target))
|
|
238
202
|
return;
|
|
239
|
-
// Debounce per-keystroke input: keep the latest value, flush on change/blur/next-act.
|
|
240
203
|
this.#pendingFill = { el: target, value: target.value };
|
|
241
204
|
}
|
|
242
205
|
#onChange(ev) {
|
|
@@ -279,12 +242,9 @@ class Recorder {
|
|
|
279
242
|
if (pending === undefined)
|
|
280
243
|
return;
|
|
281
244
|
this.#pendingFill = undefined;
|
|
282
|
-
// Never persist a typed secret into a git-checked flow file: password/OTP/cc/sensitive-keyed
|
|
283
|
-
// fields record REDACTED_VALUE, the same redaction live snapshots apply (dom/a11y.ts getValue).
|
|
284
245
|
const value = isSensitiveField(pending.el) ? REDACTED_VALUE : pending.value;
|
|
285
246
|
this.#steps.push(buildStep(pending.el, ActionType.FILL, { value }));
|
|
286
247
|
}
|
|
287
|
-
// ---- annotation ----
|
|
288
248
|
#captureAnnotationTarget(target) {
|
|
289
249
|
const draft = this.#draft;
|
|
290
250
|
if (draft === undefined)
|
|
@@ -299,7 +259,6 @@ class Recorder {
|
|
|
299
259
|
this.#setPhase(RecorderPhase.RECORDING);
|
|
300
260
|
this.#closeMenu();
|
|
301
261
|
}
|
|
302
|
-
/** Annotate-on-prior: confirm the most-recent step as the target (no extra click needed). */
|
|
303
262
|
#confirmAnnotationOnPrior() {
|
|
304
263
|
const draft = this.#draft;
|
|
305
264
|
if (draft === undefined)
|
|
@@ -325,9 +284,7 @@ class Recorder {
|
|
|
325
284
|
const value = select?.value ?? '';
|
|
326
285
|
return value.length > 0 ? value : undefined;
|
|
327
286
|
}
|
|
328
|
-
// ---- toolbar lifecycle actions ----
|
|
329
287
|
#start() {
|
|
330
|
-
// A fresh span — no leakage from a previous Record→Stop cycle.
|
|
331
288
|
this.#steps = [];
|
|
332
289
|
this.#startPath = 'undefined' === typeof location ? undefined : location.pathname;
|
|
333
290
|
this.#annotations = [];
|
|
@@ -355,7 +312,6 @@ class Recorder {
|
|
|
355
312
|
#setPhase(phase) {
|
|
356
313
|
this.#phase = phase;
|
|
357
314
|
}
|
|
358
|
-
// ---- toolbar DOM (all nodes data-reticle-overlay → snapshot-excluded via dom-ignore.ts) ----
|
|
359
315
|
#buildToolbar() {
|
|
360
316
|
const root = document.createElement('div');
|
|
361
317
|
root.setAttribute('data-reticle-overlay', '');
|
|
@@ -396,9 +352,6 @@ class Recorder {
|
|
|
396
352
|
return;
|
|
397
353
|
menu.textContent = '';
|
|
398
354
|
for (const kind of Object.values(AnnotationKind)) {
|
|
399
|
-
// INTENT is free-text (the business goal), not a signal/testid pick — authored via
|
|
400
|
-
// reticle_annotate { kind:'intent', text }. The human toolbar's name field is signal/testid only,
|
|
401
|
-
// so intent is omitted from this menu rather than shipped as a control that can't capture text.
|
|
402
355
|
if (kind === AnnotationKind.INTENT)
|
|
403
356
|
continue;
|
|
404
357
|
const item = document.createElement('button');
|
|
@@ -426,7 +379,6 @@ class Recorder {
|
|
|
426
379
|
}
|
|
427
380
|
menu.appendChild(select);
|
|
428
381
|
}
|
|
429
|
-
// "Confirm on prior step" path (annotate-on-prior); or the next click selects the target.
|
|
430
382
|
const confirm = document.createElement('button');
|
|
431
383
|
confirm.setAttribute('data-reticle-action', 'annotate-confirm');
|
|
432
384
|
confirm.textContent = 'on prior step';
|
|
@@ -448,7 +400,6 @@ class Recorder {
|
|
|
448
400
|
return value.length > 0 ? value : undefined;
|
|
449
401
|
}
|
|
450
402
|
}
|
|
451
|
-
/** Install the floating recorder toolbar. Default off; gated by the host (reticle.ts recorder flag). */
|
|
452
403
|
export function installRecorder(deps) {
|
|
453
404
|
return new Recorder(deps);
|
|
454
405
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Framework adapter registry — enriches elements with component identity + source. */
|
|
2
1
|
export interface ComponentSource {
|
|
3
2
|
file: string;
|
|
4
3
|
line: number;
|
|
@@ -11,20 +10,11 @@ export interface ComponentInfo {
|
|
|
11
10
|
export interface ReticleAdapter {
|
|
12
11
|
name: string;
|
|
13
12
|
identify: (el: Element) => ComponentInfo | null;
|
|
14
|
-
/** Best-effort: read a component's hook state for a DOM element. */
|
|
15
13
|
readState?: (el: Element) => unknown;
|
|
16
|
-
/** Best-effort: does the element declare framework enter/leave handlers synthetic hover may not fire? */
|
|
17
14
|
hasHoverHandlers?: (el: Element) => boolean;
|
|
18
15
|
}
|
|
19
|
-
/** Called by @reticlehq/react (and future adapters) to register themselves. */
|
|
20
16
|
export declare function registerAdapter(adapter: ReticleAdapter): void;
|
|
21
|
-
/** First adapter that can identify the element wins. */
|
|
22
17
|
export declare function identifyComponent(el: Element): ComponentInfo | null;
|
|
23
|
-
/** First adapter that returns non-undefined component state for the element wins. */
|
|
24
18
|
export declare function readComponentState(el: Element): unknown;
|
|
25
|
-
/**
|
|
26
|
-
* True if any installed adapter reports framework enter/leave hover handlers on the element.
|
|
27
|
-
* No-op-safe: returns false when no adapter is installed or none implements the probe.
|
|
28
|
-
*/
|
|
29
19
|
export declare function elementHasHoverHandlers(el: Element): boolean;
|
|
30
20
|
export declare function adapterNames(): string[];
|