@reticlehq/browser 3.2.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 (69) hide show
  1. package/dist/actions/actions-dom.d.ts +1 -1
  2. package/dist/actions/actions-dom.js +5 -4
  3. package/dist/actions/actions.d.ts +0 -1
  4. package/dist/actions/actions.js +4 -2
  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 +6 -4
  11. package/dist/dom/snapshot.d.ts +1 -1
  12. package/dist/observers/console.js +38 -13
  13. package/dist/observers/dom.js +3 -1
  14. package/dist/observers/download.js +3 -2
  15. package/dist/observers/error-repeats.d.ts +18 -0
  16. package/dist/observers/error-repeats.js +41 -0
  17. package/dist/observers/field.d.ts +2 -0
  18. package/dist/observers/field.js +86 -0
  19. package/dist/observers/frames.js +3 -4
  20. package/dist/observers/install-all.js +2 -0
  21. package/dist/observers/net-detail/network-body.d.ts +2 -0
  22. package/dist/observers/net-detail/network-body.js +24 -7
  23. package/dist/observers/route.js +4 -4
  24. package/dist/observers/state.js +18 -8
  25. package/dist/presenter/carousel/carousel.d.ts +13 -0
  26. package/dist/presenter/carousel/carousel.js +126 -0
  27. package/dist/presenter/carousel/offer-card.d.ts +21 -0
  28. package/dist/presenter/carousel/offer-card.js +55 -0
  29. package/dist/presenter/carousel/panel-slides.d.ts +7 -0
  30. package/dist/presenter/carousel/panel-slides.js +10 -0
  31. package/dist/presenter/carousel/talk-card.d.ts +8 -0
  32. package/dist/presenter/carousel/talk-card.js +20 -0
  33. package/dist/presenter/chrome/presenter-log.d.ts +2 -1
  34. package/dist/presenter/chrome/presenter-log.js +14 -3
  35. package/dist/presenter/chrome/presenter-report-copy.d.ts +0 -1
  36. package/dist/presenter/chrome/presenter-report-copy.js +1 -1
  37. package/dist/presenter/hud-telemetry.d.ts +3 -0
  38. package/dist/presenter/hud-telemetry.js +87 -0
  39. package/dist/presenter/presenter-account.js +1 -1
  40. package/dist/presenter/presenter-config.d.ts +5 -1
  41. package/dist/presenter/presenter-config.js +4 -1
  42. package/dist/presenter/presenter-controls.d.ts +1 -0
  43. package/dist/presenter/presenter-controls.js +12 -1
  44. package/dist/presenter/presenter-report.js +6 -6
  45. package/dist/presenter/presenter-settings-styles.js +2 -1
  46. package/dist/presenter/presenter-settings.d.ts +2 -4
  47. package/dist/presenter/presenter-settings.js +13 -4
  48. package/dist/presenter/presenter-shell-styles.js +4 -0
  49. package/dist/presenter/presenter-shell.d.ts +4 -1
  50. package/dist/presenter/presenter-shell.js +49 -11
  51. package/dist/presenter/presenter-styles.js +5 -1
  52. package/dist/presenter/presenter-workspace.d.ts +0 -7
  53. package/dist/presenter/presenter-workspace.js +7 -7
  54. package/dist/presenter/presenter.js +43 -10
  55. package/dist/presenter/tour/tour.d.ts +3 -0
  56. package/dist/presenter/tour/tour.js +5 -0
  57. package/dist/recorder/recorder.js +25 -22
  58. package/dist/reticle-inject.d.ts +2 -0
  59. package/dist/reticle-inject.js +1337 -0
  60. package/dist/reticle.js +6 -0
  61. package/dist/review/annotator.d.ts +3 -0
  62. package/dist/review/annotator.js +37 -37
  63. package/dist/review/marks-for-agent.d.ts +9 -0
  64. package/dist/review/marks-for-agent.js +12 -0
  65. package/dist/util/captured-method.d.ts +0 -1
  66. package/dist/util/captured-method.js +1 -1
  67. package/package.json +8 -4
  68. package/dist/presenter/presenter-offer.d.ts +0 -25
  69. package/dist/presenter/presenter-offer.js +0 -87
@@ -4,6 +4,7 @@ import { HUD_SURFACE_CLASS } from './chrome/presenter-hud-chrome.js';
4
4
  import { resetHudDockPosition } from './presenter-drag.js';
5
5
  import { findDock, scheduleSyncDockLayout } from './presenter-dock-layout.js';
6
6
  import { SETTINGS_CSS } from './presenter-settings-styles.js';
7
+ import { DISCOVERY_CALL_URL, FOUNDER_EMAIL, FOUNDER_MAILTO } from '@reticlehq/core';
7
8
  import { accountControlHtml } from './presenter-account.js';
8
9
  export { SETTINGS_CSS };
9
10
  export const OutputDetail = {
@@ -11,14 +12,14 @@ export const OutputDetail = {
11
12
  STANDARD: 'standard',
12
13
  VERBOSE: 'verbose',
13
14
  };
14
- export const StatusThemeId = {
15
+ const StatusThemeId = {
15
16
  SIGNAL: 'signal',
16
17
  TRAFFIC: 'traffic',
17
18
  MONO: 'mono',
18
19
  NEON: 'neon',
19
20
  EMBER: 'ember',
20
21
  };
21
- export const STATUS_THEMES = [
22
+ const STATUS_THEMES = [
22
23
  {
23
24
  id: StatusThemeId.SIGNAL,
24
25
  label: 'Signal',
@@ -132,7 +133,7 @@ function settingsHelpIcon() {
132
133
  }
133
134
  function settingsLabel(text, helpTitle) {
134
135
  const help = settingsHelpIcon();
135
- return `<span class="reticle-settings-label">${text}<button type="button" class="reticle-settings-help" title="${helpTitle}" aria-label="${helpTitle}">${help}</button></span>`;
136
+ return `<span class="reticle-settings-label">${text}<button type="button" class="reticle-settings-help" data-reticle-settings-help title="${helpTitle}" aria-label="${helpTitle}">${help}</button></span>`;
136
137
  }
137
138
  function settingsToggleRow(key, label, helpTitle, extra = '') {
138
139
  return `<div class="reticle-settings-row" ${extra}>
@@ -181,6 +182,12 @@ export function paintSettingsAccount(root, account, dashboardUrl, details = {})
181
182
  row.hidden = 0 === html.length;
182
183
  slot.innerHTML = html;
183
184
  }
185
+ const FEEDBACK_TEXT = {
186
+ EMAIL: 'Email the founder',
187
+ EMAIL_TITLE: `Write to ${FOUNDER_EMAIL}: a problem, a wish, anything`,
188
+ CALL: 'Book a call',
189
+ CALL_TITLE: 'Pick a time to talk it through',
190
+ };
184
191
  export function settingsPanelHtml() {
185
192
  const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
186
193
  const caret = hiIconHtml(PresenterIcon.CARET_RIGHT, PRESENTER_ICON_SIZE.HELP);
@@ -228,11 +235,13 @@ export function settingsPanelHtml() {
228
235
  <div class="reticle-settings-foot">
229
236
  <button type="button" class="reticle-settings-reset" data-reticle-settings-reset>Reset HUD position</button>
230
237
  <button type="button" class="reticle-settings-link" data-reticle-settings-mcp>Manage MCP &amp; Webhooks<span class="reticle-settings-link-caret" aria-hidden="true">${caret}</span></button>
238
+ <a class="reticle-settings-link" data-reticle-feedback-email href="${FOUNDER_MAILTO}" target="_blank" rel="noopener noreferrer" title="${FEEDBACK_TEXT.EMAIL_TITLE}">${FEEDBACK_TEXT.EMAIL}<span class="reticle-settings-link-caret" aria-hidden="true">${caret}</span></a>
239
+ <a class="reticle-settings-link" data-reticle-feedback-call href="${DISCOVERY_CALL_URL}" target="_blank" rel="noopener noreferrer" title="${FEEDBACK_TEXT.CALL_TITLE}">${FEEDBACK_TEXT.CALL}<span class="reticle-settings-link-caret" aria-hidden="true">${caret}</span></a>
231
240
  </div>
232
241
  </div>
233
242
  </div>`;
234
243
  }
235
- export function applyPresenterSettings(root, settings) {
244
+ function applyPresenterSettings(root, settings) {
236
245
  const theme = statusTheme(settings.statusThemeId);
237
246
  root.style.setProperty('--reticle-mark-accent', theme.active);
238
247
  root.style.setProperty('--reticle-accent', theme.active);
@@ -246,6 +246,10 @@ export const SHELL_CSS = `
246
246
  transition:opacity .2s var(--reticle-shell-ease),transform .18s var(--reticle-shell-ease);}
247
247
  [${OVERLAY}][${MIN_ATTR}="0"] [${HUD}] .reticle-toolbar{
248
248
  display:flex;opacity:1;transform:scale(1);pointer-events:auto;}
249
+ /* The HUD block is transformed, which makes it a stacking context at level 0 inside the dock, so a
250
+ tooltip's z-index only ranked it within the block and it drew behind the chat and settings panels,
251
+ its siblings. Lift the block while a tooltip can show. */
252
+ [${HUD}]:hover,[${HUD}]:focus-within{z-index:${String(Z_HUD_TOOLTIP)};}
249
253
  [${HUD}] .reticle-toolbar-drag{cursor:grab;touch-action:none;user-select:none;}
250
254
  [${HUD}] .reticle-toolbar-drag.reticle-drag-handle--dragging{cursor:grabbing;}
251
255
  [${HUD}] .reticle-toolbar-actions{
@@ -2,7 +2,7 @@ import type { HarnessConfig } from '@reticlehq/core';
2
2
  import { PresenterReport } from './presenter-report.js';
3
3
  import type { AccountState } from '@reticlehq/core';
4
4
  import { type AccountDetails } from './presenter-account.js';
5
- import { type OfferState } from './presenter-offer.js';
5
+ import { type OfferState } from './carousel/offer-card.js';
6
6
  import { type SettingsHost } from './presenter-settings.js';
7
7
  interface HudShellCallbacks {
8
8
  onChatOpen?: () => void;
@@ -35,4 +35,7 @@ export declare class HudShell {
35
35
  showChat(): void;
36
36
  pulseFab(active: boolean): void;
37
37
  }
38
+ export declare function wasMinimised(): boolean;
39
+ export declare function rememberMinimised(minimised: boolean): void;
40
+ export declare function shouldAutoOpenChat(autoOpenChat: boolean): boolean;
38
41
  export {};
@@ -1,11 +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 { OFFER_SLOT_ATTR, paintOffer } from './presenter-offer.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';
7
10
  import { BRAND_NAME, FAB_TOGGLE_HTML, MARK_SVG } from './chrome/presenter-brand.js';
8
- import { settleLogAtLatest } from './chrome/presenter-log.js';
11
+ import { DATA_RETICLE_LOG, settleLogAtLatest } from './chrome/presenter-log.js';
9
12
  import { installHudDragHandles, installHudPositionGuards } from './presenter-drag.js';
10
13
  import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
11
14
  import { hiIconHtml, hiToggleIconHtml, PRESENTER_ICON_SIZE, PresenterIcon, } from './icons/presenter-icons.js';
@@ -59,9 +62,22 @@ export class HudShell {
59
62
  #pushedOffer;
60
63
  paintOffer(offer) {
61
64
  this.#pushedOffer = offer;
62
- if (this.#root === undefined)
65
+ this.#paintCarousel();
66
+ }
67
+ #paintCarousel() {
68
+ const log = this.#root?.querySelector(`[${DATA_RETICLE_LOG}]`);
69
+ if (!(log instanceof HTMLElement))
63
70
  return;
64
- paintOffer(this.#root, offer, this.#storage());
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
+ }
65
81
  }
66
82
  #storage() {
67
83
  try {
@@ -97,7 +113,6 @@ export class HudShell {
97
113
  ${actStripHtml}
98
114
  <span class="reticle-tally" data-reticle-tally hidden></span>
99
115
  ${bannerHtml}
100
- <div ${OFFER_SLOT_ATTR}></div>
101
116
  <div class="${HUD_LOG_WELL_CLASS}"><div ${logAttr}></div></div>
102
117
  ${flowsHtml}
103
118
  ${footHtml}
@@ -248,8 +263,7 @@ export class HudShell {
248
263
  const pushed = this.#pushedAccount;
249
264
  this.paintAccount(pushed.account, pushed.dashboardUrl, pushed.details);
250
265
  }
251
- if (this.#pushedOffer !== undefined)
252
- this.paintOffer(this.#pushedOffer);
266
+ this.#paintCarousel();
253
267
  }
254
268
  teardown() {
255
269
  this.#accountTeardown?.();
@@ -283,6 +297,7 @@ export class HudShell {
283
297
  return;
284
298
  this.#lockTransition();
285
299
  this.#root.setAttribute(MIN_ATTR, '0');
300
+ rememberMinimised(false);
286
301
  if (this.#fab !== undefined)
287
302
  this.#fab.setAttribute('aria-expanded', 'true');
288
303
  this.#callbacks.onExpand?.();
@@ -297,6 +312,7 @@ export class HudShell {
297
312
  this.closeChat();
298
313
  this.#settings.close();
299
314
  this.#root.setAttribute(MIN_ATTR, '1');
315
+ rememberMinimised(true);
300
316
  if (this.#fab !== undefined)
301
317
  this.#fab.setAttribute('aria-expanded', 'false');
302
318
  this.#callbacks.onCollapse?.();
@@ -367,6 +383,10 @@ export class HudShell {
367
383
  #onKeyDown = (e) => {
368
384
  if ('Escape' !== e.key || this.#root === undefined)
369
385
  return;
386
+ if (e.defaultPrevented)
387
+ return;
388
+ if (appModalOpen(document))
389
+ return;
370
390
  const target = e.target;
371
391
  if (target instanceof HTMLElement &&
372
392
  ('INPUT' === target.tagName ||
@@ -375,18 +395,36 @@ export class HudShell {
375
395
  return;
376
396
  }
377
397
  if (this.isChatOpen()) {
378
- e.preventDefault();
379
398
  this.closeChat();
380
399
  return;
381
400
  }
382
401
  if (this.#settings.isOpen()) {
383
- e.preventDefault();
384
402
  this.#settings.close();
385
403
  return;
386
404
  }
387
405
  if (!this.isCollapsed()) {
388
- e.preventDefault();
389
406
  this.collapse();
390
407
  }
391
408
  };
392
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,7 +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 './presenter-offer.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';
10
12
  export { Z_OVERLAY };
11
13
  export const PRESENTER_CSS = `
12
14
  [data-reticle-overlay]{position:fixed;inset:0;pointer-events:none;z-index:${String(Z_OVERLAY)};}
@@ -65,5 +67,7 @@ ${SETTINGS_CSS}
65
67
  ${REPORT_CSS}
66
68
  ${ACCOUNT_CSS}
67
69
  ${OFFER_CSS}
70
+ ${TALK_CSS}
71
+ ${CAROUSEL_CSS}
68
72
  ${LOG_CSS}
69
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) => {
@@ -159,7 +165,7 @@ export class Presenter {
159
165
  <div data-reticle-glow></div>
160
166
  <div data-reticle-cursor></div>
161
167
  <div data-reticle-ring></div>
162
- ${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)}`;
163
169
  document.body.appendChild(root);
164
170
  this.#root = root;
165
171
  this.#glow = root.querySelector('[data-reticle-glow]') ?? undefined;
@@ -179,6 +185,9 @@ export class Presenter {
179
185
  syncPageBlocker(root, getPresenterSettings(), false);
180
186
  this.#glowCtl.setElements(this.#glow, this.#cursor);
181
187
  this.#panel.mount(root, this.#glow);
188
+ if (this.#onHudUse !== undefined) {
189
+ this.#hudTelemetryTeardown = installHudTelemetry(document, root, this.#onHudUse);
190
+ }
182
191
  this.setMode(this.#mode);
183
192
  this.#renderTally();
184
193
  }
@@ -190,7 +199,23 @@ export class Presenter {
190
199
  const markers = root.querySelector(`[${MARKERS_BTN_ATTR}]`);
191
200
  const clear = root.querySelector(`[${CLEAR_MARKS_ATTR}]`);
192
201
  const count = root.querySelector(`[${MARK_COUNT_ATTR}]`);
202
+ const copy = root.querySelector(`[${COPY_MARKS_ATTR}]`);
203
+ const row = root.querySelector(`[${MARKS_ROW_ATTR}]`);
193
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
+ }
194
219
  if (markers instanceof HTMLElement)
195
220
  chrome.markersBtn = markers;
196
221
  if (clear instanceof HTMLElement)
@@ -201,6 +226,14 @@ export class Presenter {
201
226
  annotator.setAccent(statusTheme(getPresenterSettings().statusThemeId).active);
202
227
  this.#syncAnnotator();
203
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
+ }
204
237
  #syncAnnotator() {
205
238
  const live = !this.#shell.isCollapsed() && this.#shell.isAnnotateOn();
206
239
  this.#annotator?.toggle(live);
@@ -213,6 +246,8 @@ export class Presenter {
213
246
  if (this.#heartbeatTimer !== undefined)
214
247
  nativeClearTimeout(this.#heartbeatTimer);
215
248
  this.#heartbeatTimer = undefined;
249
+ this.#hudTelemetryTeardown?.();
250
+ this.#hudTelemetryTeardown = undefined;
216
251
  this.#shell.teardown();
217
252
  this.#panel.teardown();
218
253
  this.#sessionActive = false;
@@ -237,7 +272,7 @@ export class Presenter {
237
272
  this.#showSession();
238
273
  this.#glowCtl.resetActivity(this.#now());
239
274
  this.#startHeartbeat();
240
- if (getPresenterSettings().autoOpenChat) {
275
+ if (shouldAutoOpenChat(getPresenterSettings().autoOpenChat)) {
241
276
  this.#shell.openChat();
242
277
  }
243
278
  }
@@ -250,7 +285,7 @@ export class Presenter {
250
285
  this.#hud?.setAttribute(DATA_ON, GLOW_ON);
251
286
  this.#lastActionText = unreachableStripText(url, attempts);
252
287
  this.#paintActStrip(this.#lastActionText, true);
253
- if (getPresenterSettings().autoOpenChat)
288
+ if (shouldAutoOpenChat(getPresenterSettings().autoOpenChat))
254
289
  this.#shell.openChat();
255
290
  }
256
291
  #showSession() {
@@ -413,7 +448,7 @@ export class Presenter {
413
448
  this.#runLog = [];
414
449
  this.#tallied = { passes: 0, fails: 0 };
415
450
  if (this.#log !== undefined)
416
- this.#log.replaceChildren();
451
+ clearLogRows(this.#log);
417
452
  this.#renderTally();
418
453
  }
419
454
  #applyHideUntilRestart() {
@@ -477,9 +512,7 @@ export class Presenter {
477
512
  #pruneLog() {
478
513
  if (this.#log === undefined)
479
514
  return;
480
- while (this.#log.childElementCount > this.#logMax) {
481
- this.#log.firstElementChild?.remove();
482
- }
515
+ trimLogRows(this.#log, this.#logMax);
483
516
  }
484
517
  setThrottled(throttled) {
485
518
  this.#root?.setAttribute(THROTTLED_ATTR, throttled ? '1' : '0');
@@ -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;
@@ -220,6 +223,8 @@ export function mountTour(deps) {
220
223
  const onKey = (event) => {
221
224
  if (!open)
222
225
  return;
226
+ if (appModalOpen(doc))
227
+ return;
223
228
  const step = 'ArrowRight' === event.key ? 1 : 'ArrowLeft' === event.key ? -1 : 0;
224
229
  if ('Escape' === event.key) {
225
230
  event.preventDefault();
@@ -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 {};