@reticlehq/browser 3.1.0 → 3.3.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.
Files changed (71) hide show
  1. package/dist/actions/actions-dom.d.ts +2 -1
  2. package/dist/actions/actions-dom.js +28 -41
  3. package/dist/actions/actions.d.ts +0 -1
  4. package/dist/actions/actions.js +13 -7
  5. package/dist/actions/value-input.js +14 -5
  6. package/dist/connect-options.d.ts +1 -0
  7. package/dist/dom/addressing/source.d.ts +2 -1
  8. package/dist/dom/dom-ignore.d.ts +1 -0
  9. package/dist/dom/dom-ignore.js +13 -0
  10. package/dist/dom/query.js +28 -9
  11. package/dist/dom/snapshot.d.ts +1 -1
  12. package/dist/observers/console.js +38 -13
  13. package/dist/observers/departure.js +16 -8
  14. package/dist/observers/dom.js +3 -1
  15. package/dist/observers/download.js +3 -2
  16. package/dist/observers/error-repeats.d.ts +18 -0
  17. package/dist/observers/error-repeats.js +41 -0
  18. package/dist/observers/field.d.ts +2 -0
  19. package/dist/observers/field.js +86 -0
  20. package/dist/observers/frames.js +3 -4
  21. package/dist/observers/install-all.js +2 -0
  22. package/dist/observers/net-detail/network-body.d.ts +2 -0
  23. package/dist/observers/net-detail/network-body.js +24 -7
  24. package/dist/observers/network.js +49 -8
  25. package/dist/observers/route.js +4 -4
  26. package/dist/observers/state.js +18 -8
  27. package/dist/presenter/carousel/carousel.d.ts +13 -0
  28. package/dist/presenter/carousel/carousel.js +126 -0
  29. package/dist/presenter/carousel/offer-card.d.ts +21 -0
  30. package/dist/presenter/carousel/offer-card.js +55 -0
  31. package/dist/presenter/carousel/panel-slides.d.ts +7 -0
  32. package/dist/presenter/carousel/panel-slides.js +10 -0
  33. package/dist/presenter/carousel/talk-card.d.ts +8 -0
  34. package/dist/presenter/carousel/talk-card.js +20 -0
  35. package/dist/presenter/chrome/presenter-log.d.ts +2 -1
  36. package/dist/presenter/chrome/presenter-log.js +14 -3
  37. package/dist/presenter/chrome/presenter-report-copy.d.ts +0 -1
  38. package/dist/presenter/chrome/presenter-report-copy.js +1 -1
  39. package/dist/presenter/hud-telemetry.d.ts +3 -0
  40. package/dist/presenter/hud-telemetry.js +87 -0
  41. package/dist/presenter/presenter-account.js +1 -1
  42. package/dist/presenter/presenter-config.d.ts +5 -1
  43. package/dist/presenter/presenter-config.js +4 -1
  44. package/dist/presenter/presenter-controls.d.ts +1 -0
  45. package/dist/presenter/presenter-controls.js +12 -1
  46. package/dist/presenter/presenter-report-styles.js +4 -0
  47. package/dist/presenter/presenter-report.js +8 -7
  48. package/dist/presenter/presenter-settings-styles.js +2 -1
  49. package/dist/presenter/presenter-settings.d.ts +6 -4
  50. package/dist/presenter/presenter-settings.js +51 -5
  51. package/dist/presenter/presenter-shell-styles.js +16 -0
  52. package/dist/presenter/presenter-shell.d.ts +7 -0
  53. package/dist/presenter/presenter-shell.js +65 -5
  54. package/dist/presenter/presenter-styles.js +6 -0
  55. package/dist/presenter/presenter-workspace.d.ts +0 -7
  56. package/dist/presenter/presenter-workspace.js +7 -7
  57. package/dist/presenter/presenter.js +46 -10
  58. package/dist/presenter/tour/tour-view.js +7 -3
  59. package/dist/presenter/tour/tour.d.ts +3 -0
  60. package/dist/presenter/tour/tour.js +26 -2
  61. package/dist/recorder/recorder.js +25 -22
  62. package/dist/reticle-inject.d.ts +2 -0
  63. package/dist/reticle-inject.js +1337 -0
  64. package/dist/reticle.js +11 -1
  65. package/dist/review/annotator.d.ts +3 -0
  66. package/dist/review/annotator.js +37 -37
  67. package/dist/review/marks-for-agent.d.ts +9 -0
  68. package/dist/review/marks-for-agent.js +12 -0
  69. package/dist/util/captured-method.d.ts +0 -1
  70. package/dist/util/captured-method.js +1 -1
  71. package/package.json +8 -4
@@ -1,10 +1,14 @@
1
+ import { appModalOpen } from '../dom/dom-ignore.js';
1
2
  import { PresenterReport, reportPanelHtml } from './presenter-report.js';
2
3
  import { paintToolbarAccount, TOOLBAR_ACCOUNT_ATTR } from './presenter-workspace.js';
3
4
  import { mountAccountControl } from './presenter-account.js';
4
5
  import { paintSettingsAccount } from './presenter-settings.js';
5
- import { CHAT_MIN_ATTR, CHAT_PILL_ATTR, REPORT_ATTR, REPORT_BTN_ATTR, ANNOTATE_BTN_ATTR, CHAT_ATTR, CHAT_TOGGLE_ATTR, CHAT_PANEL_ATTR, DOCK_ATTR, FAB_ATTR, MIN_ATTR, SETTINGS_ATTR, SETTINGS_BTN_ATTR, } from './presenter-config.js';
6
+ import { CHAT_MIN_ATTR, CHAT_PILL_ATTR, REPORT_ATTR, REPORT_BTN_ATTR, ANNOTATE_BTN_ATTR, CHAT_ATTR, CHAT_TOGGLE_ATTR, CHAT_PANEL_ATTR, DOCK_ATTR, FAB_ATTR, MIN_ATTR, SETTINGS_ATTR, SETTINGS_BTN_ATTR, MINIMISED_STORAGE_KEY, } from './presenter-config.js';
7
+ import { offerDismissed } from './carousel/offer-card.js';
8
+ import { paintCarousel } from './carousel/carousel.js';
9
+ import { panelSlides } from './carousel/panel-slides.js';
6
10
  import { BRAND_NAME, FAB_TOGGLE_HTML, MARK_SVG } from './chrome/presenter-brand.js';
7
- import { settleLogAtLatest } from './chrome/presenter-log.js';
11
+ import { DATA_RETICLE_LOG, settleLogAtLatest } from './chrome/presenter-log.js';
8
12
  import { installHudDragHandles, installHudPositionGuards } from './presenter-drag.js';
9
13
  import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
10
14
  import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './icons/presenter-icons.js';
@@ -45,6 +49,9 @@ export class HudShell {
45
49
  #callbacks;
46
50
  #pushedAccount;
47
51
  #accountTeardown;
52
+ paintHarness(config) {
53
+ this.#settings.paintHarness(config);
54
+ }
48
55
  paintAccount(account, dashboardUrl, details = {}) {
49
56
  this.#pushedAccount = { account, dashboardUrl, details };
50
57
  if (this.#root === undefined)
@@ -52,6 +59,34 @@ export class HudShell {
52
59
  paintToolbarAccount(this.#root, account, dashboardUrl, details);
53
60
  paintSettingsAccount(this.#root, account, dashboardUrl, details);
54
61
  }
62
+ #pushedOffer;
63
+ paintOffer(offer) {
64
+ this.#pushedOffer = offer;
65
+ this.#paintCarousel();
66
+ }
67
+ #paintCarousel() {
68
+ const log = this.#root?.querySelector(`[${DATA_RETICLE_LOG}]`);
69
+ if (!(log instanceof HTMLElement))
70
+ return;
71
+ const declined = offerDismissed(this.#storage());
72
+ paintCarousel(log, panelSlides(this.#pushedOffer, declined), this.#sessionStorage());
73
+ }
74
+ #sessionStorage() {
75
+ try {
76
+ return globalThis.sessionStorage;
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
82
+ #storage() {
83
+ try {
84
+ return globalThis.localStorage;
85
+ }
86
+ catch {
87
+ return undefined;
88
+ }
89
+ }
55
90
  constructor(callbacks = {}) {
56
91
  this.#callbacks = callbacks;
57
92
  this.#settings = new PresenterSettingsPanel({
@@ -228,6 +263,7 @@ export class HudShell {
228
263
  const pushed = this.#pushedAccount;
229
264
  this.paintAccount(pushed.account, pushed.dashboardUrl, pushed.details);
230
265
  }
266
+ this.#paintCarousel();
231
267
  }
232
268
  teardown() {
233
269
  this.#accountTeardown?.();
@@ -261,6 +297,7 @@ export class HudShell {
261
297
  return;
262
298
  this.#lockTransition();
263
299
  this.#root.setAttribute(MIN_ATTR, '0');
300
+ rememberMinimised(false);
264
301
  if (this.#fab !== undefined)
265
302
  this.#fab.setAttribute('aria-expanded', 'true');
266
303
  this.#callbacks.onExpand?.();
@@ -275,6 +312,7 @@ export class HudShell {
275
312
  this.closeChat();
276
313
  this.#settings.close();
277
314
  this.#root.setAttribute(MIN_ATTR, '1');
315
+ rememberMinimised(true);
278
316
  if (this.#fab !== undefined)
279
317
  this.#fab.setAttribute('aria-expanded', 'false');
280
318
  this.#callbacks.onCollapse?.();
@@ -345,6 +383,10 @@ export class HudShell {
345
383
  #onKeyDown = (e) => {
346
384
  if ('Escape' !== e.key || this.#root === undefined)
347
385
  return;
386
+ if (e.defaultPrevented)
387
+ return;
388
+ if (appModalOpen(document))
389
+ return;
348
390
  const target = e.target;
349
391
  if (target instanceof HTMLElement &&
350
392
  ('INPUT' === target.tagName ||
@@ -353,18 +395,36 @@ export class HudShell {
353
395
  return;
354
396
  }
355
397
  if (this.isChatOpen()) {
356
- e.preventDefault();
357
398
  this.closeChat();
358
399
  return;
359
400
  }
360
401
  if (this.#settings.isOpen()) {
361
- e.preventDefault();
362
402
  this.#settings.close();
363
403
  return;
364
404
  }
365
405
  if (!this.isCollapsed()) {
366
- e.preventDefault();
367
406
  this.collapse();
368
407
  }
369
408
  };
370
409
  }
410
+ export function wasMinimised() {
411
+ try {
412
+ return '1' === globalThis.sessionStorage.getItem(MINIMISED_STORAGE_KEY);
413
+ }
414
+ catch {
415
+ return false;
416
+ }
417
+ }
418
+ export function rememberMinimised(minimised) {
419
+ try {
420
+ if (minimised)
421
+ globalThis.sessionStorage.setItem(MINIMISED_STORAGE_KEY, '1');
422
+ else
423
+ globalThis.sessionStorage.removeItem(MINIMISED_STORAGE_KEY);
424
+ }
425
+ catch {
426
+ }
427
+ }
428
+ export function shouldAutoOpenChat(autoOpenChat) {
429
+ return autoOpenChat && !wasMinimised();
430
+ }
@@ -6,6 +6,9 @@ import { SETTINGS_CSS } from './presenter-settings-styles.js';
6
6
  import { ACCOUNT_CSS } from './presenter-account-styles.js';
7
7
  import { HUD_CHROME_CSS, HUD_LOG_WELL_CSS } from './chrome/presenter-hud-chrome.js';
8
8
  import { REPORT_CSS } from './presenter-report-styles.js';
9
+ import { OFFER_CSS } from './carousel/offer-card.js';
10
+ import { TALK_CSS } from './carousel/talk-card.js';
11
+ import { CAROUSEL_CSS } from './carousel/carousel.js';
9
12
  export { Z_OVERLAY };
10
13
  export const PRESENTER_CSS = `
11
14
  [data-reticle-overlay]{position:fixed;inset:0;pointer-events:none;z-index:${String(Z_OVERLAY)};}
@@ -63,5 +66,8 @@ ${SHELL_CSS}
63
66
  ${SETTINGS_CSS}
64
67
  ${REPORT_CSS}
65
68
  ${ACCOUNT_CSS}
69
+ ${OFFER_CSS}
70
+ ${TALK_CSS}
71
+ ${CAROUSEL_CSS}
66
72
  ${LOG_CSS}
67
73
  ${CONTROLS_CSS}`;
@@ -1,14 +1,7 @@
1
1
  import type { AccountState } from '@reticlehq/core';
2
2
  import { type AccountDetails } from './presenter-account.js';
3
3
  export declare const TOOLBAR_ACCOUNT_ATTR = "data-reticle-toolbar-account";
4
- export declare function readWorkspaceRoot(): string | undefined;
5
4
  export declare function workspaceFolderLabel(root: string): string;
6
- export declare function readProjectIdFromUrl(): string | undefined;
7
- export declare function workspaceSummary(): {
8
- folder: string;
9
- root?: string;
10
- projectId?: string;
11
- };
12
5
  export declare function workspaceRowHtml(): string;
13
6
  export declare function paintWorkspace(root: HTMLElement): void;
14
7
  export declare function mountWorkspaceSelector(root: HTMLElement): () => void;
@@ -14,7 +14,7 @@ const PROJECT_LABEL = 'Project';
14
14
  const COPY_PATH_LABEL = 'Copy path';
15
15
  const COPIED_PATH_LABEL = 'Copied';
16
16
  const WORKSPACE_FALLBACK = 'This page';
17
- export function readWorkspaceRoot() {
17
+ function readWorkspaceRoot() {
18
18
  const value = globalThis[RETICLE_ROOT_GLOBAL];
19
19
  return 'string' === typeof value && value.length > 0 ? value : undefined;
20
20
  }
@@ -24,13 +24,13 @@ export function workspaceFolderLabel(root) {
24
24
  const last = parts[parts.length - 1];
25
25
  return last !== undefined && last.length > 0 ? last : WORKSPACE_FALLBACK;
26
26
  }
27
- export function readProjectIdFromUrl() {
27
+ function readProjectIdFromUrl() {
28
28
  if ('undefined' === typeof window)
29
29
  return undefined;
30
30
  const id = new URLSearchParams(window.location.search).get(RETICLE_URL_PARAM.PROJECT);
31
31
  return id !== null && id.length > 0 ? id : undefined;
32
32
  }
33
- export function workspaceSummary() {
33
+ function workspaceSummary() {
34
34
  const root = readWorkspaceRoot();
35
35
  const projectId = readProjectIdFromUrl();
36
36
  const folder = root !== undefined ? workspaceFolderLabel(root) : WORKSPACE_FALLBACK;
@@ -106,6 +106,7 @@ export function paintWorkspace(root) {
106
106
  }
107
107
  }
108
108
  export function mountWorkspaceSelector(root) {
109
+ const ac = new AbortController();
109
110
  paintWorkspace(root);
110
111
  const btn = root.querySelector(`[${WORKSPACE_BTN_ATTR}]`);
111
112
  const menu = root.querySelector(`[${WORKSPACE_MENU_ATTR}]`);
@@ -183,13 +184,12 @@ export function mountWorkspaceSelector(root) {
183
184
  btn.addEventListener('click', onBtnClick);
184
185
  menu.addEventListener('pointerdown', (e) => e.stopPropagation());
185
186
  copyBtn?.addEventListener('click', onCopy);
186
- document.addEventListener('pointerdown', onDocPointer);
187
- document.addEventListener('keydown', onKeyDown);
187
+ document.addEventListener('pointerdown', onDocPointer, { signal: ac.signal });
188
+ document.addEventListener('keydown', onKeyDown, { signal: ac.signal });
188
189
  return () => {
189
190
  btn.removeEventListener('click', onBtnClick);
190
191
  copyBtn?.removeEventListener('click', onCopy);
191
- document.removeEventListener('pointerdown', onDocPointer);
192
- document.removeEventListener('keydown', onKeyDown);
192
+ ac.abort();
193
193
  };
194
194
  }
195
195
  export function paintToolbarAccount(root, account, dashboardUrl, details = {}) {
@@ -3,19 +3,22 @@ import { refs } from '../dom/addressing/refs.js';
3
3
  import { unreachableStripText } from '../transport/unreachable-message.js';
4
4
  import { actionVerb } from './chrome/presenter-verbs.js';
5
5
  import { nativeSetTimeout, nativeClearTimeout, nativeNow } from '../timers/native/native-timers.js';
6
- import { LOG_KIND, CHIP_LABEL, DATA_RETICLE_LOG, clampLogMax, formatElapsed, humanDuration, appendLogRow, } from './chrome/presenter-log.js';
6
+ import { LOG_KIND, CHIP_LABEL, DATA_RETICLE_LOG, clampLogMax, formatElapsed, humanDuration, appendLogRow, clearLogRows, trimLogRows, } from './chrome/presenter-log.js';
7
7
  import { PRESENTER_CSS } from './presenter-styles.js';
8
8
  import { HudShell } from './presenter-shell.js';
9
9
  import { parseImpactSnapshot } from './chrome/presenter-report-copy.js';
10
- import { BorderMode, DEFAULT_BORDER_MODE, DATA_BUSY, BUSY_OFF, effectivePaceMs, IDLE_AFTER_MS, HEARTBEAT_MS, IDLE_NOTICE_MS, IDLE_END_MS, IDLE_END_MIN_MS, ACT_STRIP, STATE_ATTR, UNREACHABLE_STATE, GLOW_FADE_MS, GLOW_ON, GLOW_OFF, DATA_ON, THROTTLED_ATTR, LIVENESS_ATTR, MARKERS_BTN_ATTR, CLEAR_MARKS_ATTR, MARK_COUNT_ATTR, } from './presenter-config.js';
10
+ import { BorderMode, DEFAULT_BORDER_MODE, DATA_BUSY, BUSY_OFF, effectivePaceMs, IDLE_AFTER_MS, HEARTBEAT_MS, IDLE_NOTICE_MS, IDLE_END_MS, IDLE_END_MIN_MS, ACT_STRIP, STATE_ATTR, UNREACHABLE_STATE, GLOW_FADE_MS, GLOW_ON, GLOW_OFF, DATA_ON, THROTTLED_ATTR, LIVENESS_ATTR, MARKERS_BTN_ATTR, CLEAR_MARKS_ATTR, COPY_MARKS_ATTR, MARKS_ROW_ATTR, MARK_COUNT_ATTR, } from './presenter-config.js';
11
11
  import { buildRunState } from './presenter-run-state.js';
12
12
  import { moveCursor, ringAround, spawnRipple, pace } from './chrome/presenter-effects.js';
13
13
  import { GlowController } from './presenter-glow.js';
14
14
  import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon } from './icons/presenter-icons.js';
15
15
  import { renderTally } from './chrome/presenter-tally.js';
16
- import { CONTROLS_BANNER_HTML, CONTROLS_FLOWS_HTML, CONTROLS_FOOT_HTML, ENDED_FADE_MS, ControlPanel, } from './presenter-controls.js';
16
+ import { CONTROLS_BANNER_HTML, CONTROLS_FLOWS_HTML, CONTROLS_MARKS_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
+ import { shouldAutoOpenChat } from './presenter-shell.js';
20
+ import { installHudTelemetry } from './hud-telemetry.js';
21
+ const COPIED_FLASH_MS = 1600;
19
22
  export { GlowPhase } from './presenter-config.js';
20
23
  export { LOG_KIND, LOG_RESULT } from './chrome/presenter-log.js';
21
24
  export class Presenter {
@@ -50,6 +53,8 @@ export class Presenter {
50
53
  #log;
51
54
  #logBaseMs;
52
55
  #onControl;
56
+ #onHudUse;
57
+ #hudTelemetryTeardown;
53
58
  #panel;
54
59
  #shell;
55
60
  #annotator;
@@ -70,6 +75,7 @@ export class Presenter {
70
75
  });
71
76
  this.#logMax = clampLogMax(options.logMax);
72
77
  this.#onControl = options.onControl;
78
+ this.#onHudUse = options.onHudUse;
73
79
  this.#panel = new ControlPanel({
74
80
  emit: (kind, text) => this.#onControl?.(text !== undefined ? { kind, text } : { kind }),
75
81
  logHuman: (text) => {
@@ -93,6 +99,7 @@ export class Presenter {
93
99
  },
94
100
  onHideUntilRestart: () => this.#applyHideUntilRestart(),
95
101
  onSettingsChange: (s) => this.#onSettingsChange(s),
102
+ onHarness: (enabled) => this.#onControl?.({ kind: HumanControlKind.HARNESS, text: enabled ? 'on' : 'off' }),
96
103
  },
97
104
  });
98
105
  }
@@ -119,6 +126,8 @@ export class Presenter {
119
126
  const snapshot = parseImpactSnapshot(a['snapshot']);
120
127
  if (snapshot !== undefined) {
121
128
  this.#shell.report.setSnapshot(snapshot);
129
+ this.#shell.paintOffer(snapshot.harnessOffer);
130
+ this.#shell.paintHarness(snapshot.harnessConfig);
122
131
  this.#shell.paintAccount(snapshot.account, snapshot.dashboardUrl, {
123
132
  projectName: snapshot.projectName,
124
133
  dashboardUrl: snapshot.dashboardUrl,
@@ -156,7 +165,7 @@ export class Presenter {
156
165
  <div data-reticle-glow></div>
157
166
  <div data-reticle-cursor></div>
158
167
  <div data-reticle-ring></div>
159
- ${HudShell.dockHtml(actStrip, CONTROLS_BANNER_HTML, DATA_RETICLE_LOG, CONTROLS_FLOWS_HTML, CONTROLS_FOOT_HTML)}`;
168
+ ${HudShell.dockHtml(actStrip, CONTROLS_BANNER_HTML, DATA_RETICLE_LOG, CONTROLS_MARKS_HTML + CONTROLS_FLOWS_HTML, CONTROLS_FOOT_HTML)}`;
160
169
  document.body.appendChild(root);
161
170
  this.#root = root;
162
171
  this.#glow = root.querySelector('[data-reticle-glow]') ?? undefined;
@@ -176,6 +185,9 @@ export class Presenter {
176
185
  syncPageBlocker(root, getPresenterSettings(), false);
177
186
  this.#glowCtl.setElements(this.#glow, this.#cursor);
178
187
  this.#panel.mount(root, this.#glow);
188
+ if (this.#onHudUse !== undefined) {
189
+ this.#hudTelemetryTeardown = installHudTelemetry(document, root, this.#onHudUse);
190
+ }
179
191
  this.setMode(this.#mode);
180
192
  this.#renderTally();
181
193
  }
@@ -187,7 +199,23 @@ export class Presenter {
187
199
  const markers = root.querySelector(`[${MARKERS_BTN_ATTR}]`);
188
200
  const clear = root.querySelector(`[${CLEAR_MARKS_ATTR}]`);
189
201
  const count = root.querySelector(`[${MARK_COUNT_ATTR}]`);
202
+ const copy = root.querySelector(`[${COPY_MARKS_ATTR}]`);
203
+ const row = root.querySelector(`[${MARKS_ROW_ATTR}]`);
190
204
  const chrome = {};
205
+ if (row instanceof HTMLElement)
206
+ chrome.copyRow = row;
207
+ let marks = annotator.markCount;
208
+ chrome.onCount = (n) => {
209
+ if (0 === marks && n > 0)
210
+ this.#shell.openChat();
211
+ marks = n;
212
+ };
213
+ if (copy instanceof HTMLElement) {
214
+ copy.addEventListener('click', (e) => {
215
+ e.stopPropagation();
216
+ this.#copyMarks(annotator, copy);
217
+ });
218
+ }
191
219
  if (markers instanceof HTMLElement)
192
220
  chrome.markersBtn = markers;
193
221
  if (clear instanceof HTMLElement)
@@ -198,6 +226,14 @@ export class Presenter {
198
226
  annotator.setAccent(statusTheme(getPresenterSettings().statusThemeId).active);
199
227
  this.#syncAnnotator();
200
228
  }
229
+ #copyMarks(annotator, btn) {
230
+ const text = annotator.agentPrompt(window.location.href);
231
+ void navigator.clipboard?.writeText(text).catch(() => undefined);
232
+ btn.setAttribute('data-copied', '1');
233
+ nativeSetTimeout(() => btn.removeAttribute('data-copied'), COPIED_FLASH_MS);
234
+ if (getPresenterSettings().clearOnCopy)
235
+ annotator.clearAll();
236
+ }
201
237
  #syncAnnotator() {
202
238
  const live = !this.#shell.isCollapsed() && this.#shell.isAnnotateOn();
203
239
  this.#annotator?.toggle(live);
@@ -210,6 +246,8 @@ export class Presenter {
210
246
  if (this.#heartbeatTimer !== undefined)
211
247
  nativeClearTimeout(this.#heartbeatTimer);
212
248
  this.#heartbeatTimer = undefined;
249
+ this.#hudTelemetryTeardown?.();
250
+ this.#hudTelemetryTeardown = undefined;
213
251
  this.#shell.teardown();
214
252
  this.#panel.teardown();
215
253
  this.#sessionActive = false;
@@ -234,7 +272,7 @@ export class Presenter {
234
272
  this.#showSession();
235
273
  this.#glowCtl.resetActivity(this.#now());
236
274
  this.#startHeartbeat();
237
- if (getPresenterSettings().autoOpenChat) {
275
+ if (shouldAutoOpenChat(getPresenterSettings().autoOpenChat)) {
238
276
  this.#shell.openChat();
239
277
  }
240
278
  }
@@ -247,7 +285,7 @@ export class Presenter {
247
285
  this.#hud?.setAttribute(DATA_ON, GLOW_ON);
248
286
  this.#lastActionText = unreachableStripText(url, attempts);
249
287
  this.#paintActStrip(this.#lastActionText, true);
250
- if (getPresenterSettings().autoOpenChat)
288
+ if (shouldAutoOpenChat(getPresenterSettings().autoOpenChat))
251
289
  this.#shell.openChat();
252
290
  }
253
291
  #showSession() {
@@ -410,7 +448,7 @@ export class Presenter {
410
448
  this.#runLog = [];
411
449
  this.#tallied = { passes: 0, fails: 0 };
412
450
  if (this.#log !== undefined)
413
- this.#log.replaceChildren();
451
+ clearLogRows(this.#log);
414
452
  this.#renderTally();
415
453
  }
416
454
  #applyHideUntilRestart() {
@@ -474,9 +512,7 @@ export class Presenter {
474
512
  #pruneLog() {
475
513
  if (this.#log === undefined)
476
514
  return;
477
- while (this.#log.childElementCount > this.#logMax) {
478
- this.#log.firstElementChild?.remove();
479
- }
515
+ trimLogRows(this.#log, this.#logMax);
480
516
  }
481
517
  setThrottled(throttled) {
482
518
  this.#root?.setAttribute(THROTTLED_ATTR, throttled ? '1' : '0');
@@ -111,9 +111,13 @@ export const TOUR_CSS = `
111
111
  [${TOUR_ATTR}] .reticle-tour-blocker{position:absolute;pointer-events:auto;z-index:0;}
112
112
  /* Ringed slides dim through the ring's own 9999px shadow, which leaves a hole over the thing being
113
113
  pointed at. The scrim would cover that hole -- dimming the HUD the slide is ABOUT, and washing
114
- the page twice (~80% rather than 55%). Cleared, not removed: it is also what swallows clicks
115
- meant for the app underneath, and a tour that lets you click through is not a tour. */
116
- [${TOUR_ATTR}] .reticle-tour-scrim.is-clear{background:transparent;backdrop-filter:none;}
114
+ the page twice (~80% rather than 55%).
115
+ It is cleared for the clicks too, and that half was missing. A scrim blocks only while it is
116
+ VISIBLY blocking: reported from the field on 3.1.0 by several users, one of whom removed the
117
+ plugin, because a cleared scrim went on eating every press on a page that looked lit and
118
+ reachable. An overlay that looks transparent and behaves opaque reads as the app being broken. */
119
+ [${TOUR_ATTR}] .reticle-tour-scrim.is-clear{background:transparent;backdrop-filter:none;
120
+ pointer-events:none;}
117
121
  /* Above the ring, and the z-index is the whole point.
118
122
  The ring is appended after the card, and both are positioned with an auto z-index, so the ring
119
123
  painted on top of it -- and the ring's dimming IS a 9999px shadow, which therefore fell across the
@@ -11,6 +11,9 @@ export interface TourDeps {
11
11
  readonly projectId: string;
12
12
  readonly isDriving: () => boolean;
13
13
  readonly search?: string;
14
+ readonly navigator?: {
15
+ readonly webdriver?: boolean;
16
+ };
14
17
  readonly copy?: (text: string) => Promise<boolean> | boolean;
15
18
  readonly select?: (element: Element) => void;
16
19
  }
@@ -1,6 +1,7 @@
1
1
  import { COPIED_LABEL, COPY_FLASH_MS, COPY_LABEL, COPY_MANUAL_LABEL, TOUR_ATTR, TOUR_CSS, TOUR_PROMPT_ATTR, TOUR_TARGET_ATTR, holeRects, isInteractive, isLastSlide, nextIndex, slideHtml, tourSlides, } from './tour-view.js';
2
2
  import { TourAnchor } from '@reticlehq/core/tour';
3
3
  import { RETICLE_URL_PARAM } from '@reticlehq/core';
4
+ import { appModalOpen } from '../../dom/dom-ignore.js';
4
5
  export const TOUR_SEEN_KEY_PREFIX = 'reticle.tour.seen.';
5
6
  const APP_SELECTORS = ['main', '#root', '#app'];
6
7
  const HUD_PARTS = ['[data-reticle-hud]', '[data-reticle-chat-panel]'];
@@ -79,6 +80,8 @@ export function mountTour(deps) {
79
80
  return undefined;
80
81
  if (openedByReticle(deps.search))
81
82
  return undefined;
83
+ if (true === deps.navigator?.webdriver)
84
+ return undefined;
82
85
  if (tourAlreadySeen(deps.storage, deps.projectId))
83
86
  return undefined;
84
87
  const doc = deps.document;
@@ -92,15 +95,27 @@ export function mountTour(deps) {
92
95
  let index = 0;
93
96
  let open = true;
94
97
  let detachKeys = () => undefined;
95
- const close = () => {
98
+ let stopWatch = () => undefined;
99
+ const remove = () => {
96
100
  if (!open)
97
101
  return;
98
102
  open = false;
99
103
  detachKeys();
100
- markSeen(deps.storage, deps.projectId);
104
+ stopWatch();
101
105
  root.remove();
102
106
  style.remove();
103
107
  };
108
+ const close = () => {
109
+ if (!open)
110
+ return;
111
+ markSeen(deps.storage, deps.projectId);
112
+ remove();
113
+ };
114
+ const yieldToDrive = () => {
115
+ if (!deps.isDriving())
116
+ return;
117
+ remove();
118
+ };
104
119
  const draw = () => {
105
120
  const slide = slides[index];
106
121
  if (undefined === slide)
@@ -208,6 +223,8 @@ export function mountTour(deps) {
208
223
  const onKey = (event) => {
209
224
  if (!open)
210
225
  return;
226
+ if (appModalOpen(doc))
227
+ return;
211
228
  const step = 'ArrowRight' === event.key ? 1 : 'ArrowLeft' === event.key ? -1 : 0;
212
229
  if ('Escape' === event.key) {
213
230
  event.preventDefault();
@@ -229,6 +246,13 @@ export function mountTour(deps) {
229
246
  };
230
247
  draw();
231
248
  doc.body.appendChild(root);
249
+ const overlay = doc.querySelector('[data-reticle-overlay]');
250
+ if (null !== overlay) {
251
+ const observer = new MutationObserver(yieldToDrive);
252
+ observer.observe(overlay, { attributes: true, attributeFilter: ['data-reticle-mode'] });
253
+ stopWatch = () => observer.disconnect();
254
+ yieldToDrive();
255
+ }
232
256
  return {
233
257
  destroy: close,
234
258
  isOpen: () => open,
@@ -1,4 +1,4 @@
1
- import { ActionType, AnchorKind, AnnotationKind, DEGRADED_ANCHOR_ROLE, EventType, FLOW_FILE_VERSION, FlowStepTool, RecorderPhase, } from '@reticlehq/core';
1
+ import { ActionType, AnchorKind, AnnotationKind, DEGRADED_ANCHOR_ROLE, EventType, FLOW_FILE_VERSION, FlowStepTool, RecorderPhase, PredicateKind, } from '@reticlehq/core';
2
2
  import { REDACTED_VALUE } from '@reticlehq/core';
3
3
  import { sourceFromDom } from '../dom/addressing/source.js';
4
4
  import { getAccessibleName, getRole, isSensitiveField } from '../dom/a11y.js';
@@ -83,17 +83,20 @@ export function compileRecording(name, steps, annotations, createdAt, startPath)
83
83
  }
84
84
  if (ann.kind === AnnotationKind.SUCCESS_STATE) {
85
85
  if (ann.signal !== undefined)
86
- success = { signal: ann.signal };
86
+ success = { kind: PredicateKind.SIGNAL, name: ann.signal };
87
87
  continue;
88
88
  }
89
89
  const target = out.at(-1);
90
90
  if (target === undefined)
91
91
  continue;
92
92
  if (ann.kind === AnnotationKind.ASSERT_SIGNAL && ann.signal !== undefined) {
93
- target.expect = { ...target.expect, signal: ann.signal };
93
+ target.expect = alsoAssert(target.expect, { kind: PredicateKind.SIGNAL, name: ann.signal });
94
94
  }
95
95
  else if (ann.kind === AnnotationKind.ASSERT_VISIBLE) {
96
- target.expect = { ...target.expect, element: anchorToElement(ann.anchor) };
96
+ target.expect = alsoAssert(target.expect, {
97
+ kind: PredicateKind.ELEMENT,
98
+ query: anchorToElement(ann.anchor),
99
+ });
97
100
  }
98
101
  }
99
102
  const flow = { version: FLOW_FILE_VERSION, name, createdAt, steps: out };
@@ -105,6 +108,14 @@ export function compileRecording(name, steps, annotations, createdAt, startPath)
105
108
  flow.success = success;
106
109
  return flow;
107
110
  }
111
+ function alsoAssert(existing, clause) {
112
+ if (existing === undefined)
113
+ return clause;
114
+ if (existing.kind === PredicateKind.ALL_OF) {
115
+ return { kind: PredicateKind.ALL_OF, predicates: [...existing.predicates, clause] };
116
+ }
117
+ return { kind: PredicateKind.ALL_OF, predicates: [existing, clause] };
118
+ }
108
119
  function anchorToElement(anchor) {
109
120
  if (anchor.kind === AnchorKind.TESTID)
110
121
  return { testid: anchor.value };
@@ -122,7 +133,7 @@ class Recorder {
122
133
  #startPath;
123
134
  #annotations = [];
124
135
  #pendingFill;
125
- #teardowns = [];
136
+ #captureAbort;
126
137
  #root;
127
138
  #statusEl;
128
139
  #menuEl;
@@ -145,9 +156,8 @@ class Recorder {
145
156
  this.#installCapture();
146
157
  }
147
158
  destroy() {
148
- for (const t of this.#teardowns)
149
- t();
150
- this.#teardowns = [];
159
+ this.#captureAbort?.abort();
160
+ this.#captureAbort = undefined;
151
161
  this.#root?.remove();
152
162
  this.#root = undefined;
153
163
  this.#statusEl = undefined;
@@ -159,20 +169,13 @@ class Recorder {
159
169
  this.#pendingFill = undefined;
160
170
  }
161
171
  #installCapture() {
162
- const onClick = (ev) => this.#onClick(ev);
163
- const onInput = (ev) => this.#onInput(ev);
164
- const onChange = (ev) => this.#onChange(ev);
165
- const onSubmit = (ev) => this.#onSubmit(ev);
166
- document.addEventListener('click', onClick, true);
167
- document.addEventListener('input', onInput, true);
168
- document.addEventListener('change', onChange, true);
169
- document.addEventListener('submit', onSubmit, true);
170
- this.#teardowns.push(() => {
171
- document.removeEventListener('click', onClick, true);
172
- document.removeEventListener('input', onInput, true);
173
- document.removeEventListener('change', onChange, true);
174
- document.removeEventListener('submit', onSubmit, true);
175
- });
172
+ const ac = new AbortController();
173
+ this.#captureAbort = ac;
174
+ const opts = { capture: true, signal: ac.signal };
175
+ document.addEventListener('click', (ev) => this.#onClick(ev), opts);
176
+ document.addEventListener('input', (ev) => this.#onInput(ev), opts);
177
+ document.addEventListener('change', (ev) => this.#onChange(ev), opts);
178
+ document.addEventListener('submit', (ev) => this.#onSubmit(ev), opts);
176
179
  }
177
180
  #ignore(ev) {
178
181
  const target = ev.target;
@@ -0,0 +1,2 @@
1
+ // A script evaluated in the page, read by the daemon as text. Nothing here is importable.
2
+ export {};