@reticlehq/browser 3.5.0 → 3.7.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 (116) hide show
  1. package/dist/actions/action-report.d.ts +54 -0
  2. package/dist/actions/action-report.js +60 -0
  3. package/dist/actions/actions-dom.d.ts +1 -0
  4. package/dist/actions/actions-dom.js +56 -43
  5. package/dist/actions/actions-press.d.ts +11 -4
  6. package/dist/actions/actions-press.js +132 -17
  7. package/dist/actions/actions.d.ts +1 -35
  8. package/dist/actions/actions.js +32 -65
  9. package/dist/actions/appeared-text.js +3 -0
  10. package/dist/actions/danger-context.d.ts +2 -0
  11. package/dist/actions/danger-context.js +29 -3
  12. package/dist/actions/value-input.d.ts +1 -0
  13. package/dist/actions/value-input.js +11 -0
  14. package/dist/commands/commands.js +4 -0
  15. package/dist/connect-options.d.ts +1 -0
  16. package/dist/dom/a11y.js +264 -14
  17. package/dist/dom/addressing/coverage-markers.d.ts +6 -0
  18. package/dist/dom/addressing/coverage-markers.js +13 -0
  19. package/dist/dom/addressing/refs.js +2 -1
  20. package/dist/dom/addressing/testid-attr.d.ts +4 -0
  21. package/dist/dom/addressing/testid-attr.js +14 -0
  22. package/dist/dom/dom-ignore.d.ts +2 -0
  23. package/dist/dom/dom-ignore.js +15 -2
  24. package/dist/dom/query.d.ts +2 -1
  25. package/dist/dom/query.js +116 -27
  26. package/dist/dom/render-page.d.ts +12 -0
  27. package/dist/dom/render-page.js +135 -0
  28. package/dist/dom/snapshot.js +56 -7
  29. package/dist/observers/departure.js +51 -9
  30. package/dist/observers/dom.js +35 -18
  31. package/dist/observers/field.js +5 -21
  32. package/dist/observers/health.js +63 -2
  33. package/dist/observers/network-stream.d.ts +1 -1
  34. package/dist/observers/network-stream.js +3 -3
  35. package/dist/observers/network.js +57 -8
  36. package/dist/observers/perf.js +53 -1
  37. package/dist/observers/route.js +6 -5
  38. package/dist/observers/storage.js +6 -1
  39. package/dist/presenter/carousel/carousel.d.ts +5 -2
  40. package/dist/presenter/carousel/carousel.js +30 -8
  41. package/dist/presenter/carousel/panel-slides.d.ts +3 -4
  42. package/dist/presenter/carousel/panel-slides.js +48 -9
  43. package/dist/presenter/carousel/talk-card.d.ts +1 -1
  44. package/dist/presenter/chrome/log-kinds.d.ts +5 -0
  45. package/dist/presenter/chrome/log-kinds.js +1 -0
  46. package/dist/presenter/chrome/log-memory.d.ts +13 -0
  47. package/dist/presenter/chrome/log-memory.js +39 -0
  48. package/dist/presenter/chrome/presenter-hud-chrome.d.ts +1 -0
  49. package/dist/presenter/chrome/presenter-hud-chrome.js +7 -1
  50. package/dist/presenter/chrome/presenter-log.d.ts +12 -4
  51. package/dist/presenter/chrome/presenter-log.js +100 -10
  52. package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
  53. package/dist/presenter/chrome/presenter-morph.js +34 -0
  54. package/dist/presenter/chrome/presenter-report-copy.d.ts +28 -5
  55. package/dist/presenter/chrome/presenter-report-copy.js +40 -5
  56. package/dist/presenter/hud-telemetry.js +83 -3
  57. package/dist/presenter/icons/presenter-heroicons-data.d.ts +1 -0
  58. package/dist/presenter/icons/presenter-heroicons-data.js +4 -3
  59. package/dist/presenter/icons/presenter-icons.d.ts +2 -0
  60. package/dist/presenter/icons/presenter-icons.js +2 -0
  61. package/dist/presenter/presenter-account-styles.js +3 -3
  62. package/dist/presenter/presenter-account.d.ts +31 -8
  63. package/dist/presenter/presenter-account.js +105 -15
  64. package/dist/presenter/presenter-agent-link.d.ts +9 -0
  65. package/dist/presenter/presenter-agent-link.js +62 -0
  66. package/dist/presenter/presenter-chat-views.d.ts +43 -0
  67. package/dist/presenter/presenter-chat-views.js +622 -0
  68. package/dist/presenter/presenter-config.d.ts +5 -2
  69. package/dist/presenter/presenter-config.js +5 -3
  70. package/dist/presenter/presenter-controls.d.ts +3 -2
  71. package/dist/presenter/presenter-controls.js +163 -61
  72. package/dist/presenter/presenter-design-tokens.d.ts +1 -0
  73. package/dist/presenter/presenter-design-tokens.js +34 -0
  74. package/dist/presenter/presenter-dock-layout.js +1 -1
  75. package/dist/presenter/presenter-frame.d.ts +12 -0
  76. package/dist/presenter/presenter-frame.js +187 -0
  77. package/dist/presenter/presenter-harness-row.d.ts +67 -0
  78. package/dist/presenter/presenter-harness-row.js +209 -0
  79. package/dist/presenter/presenter-personas.d.ts +35 -0
  80. package/dist/presenter/presenter-personas.js +95 -0
  81. package/dist/presenter/presenter-plan.d.ts +10 -0
  82. package/dist/presenter/presenter-plan.js +186 -0
  83. package/dist/presenter/presenter-report-styles.js +46 -21
  84. package/dist/presenter/presenter-report.d.ts +2 -1
  85. package/dist/presenter/presenter-report.js +123 -20
  86. package/dist/presenter/presenter-settings-styles.js +18 -4
  87. package/dist/presenter/presenter-settings.d.ts +8 -2
  88. package/dist/presenter/presenter-settings.js +134 -33
  89. package/dist/presenter/presenter-shell-styles.js +48 -42
  90. package/dist/presenter/presenter-shell.d.ts +17 -4
  91. package/dist/presenter/presenter-shell.js +139 -79
  92. package/dist/presenter/presenter-styles.js +9 -3
  93. package/dist/presenter/presenter-workspace.js +10 -3
  94. package/dist/presenter/presenter.d.ts +6 -4
  95. package/dist/presenter/presenter.js +171 -22
  96. package/dist/presenter/tour/tour.d.ts +4 -1
  97. package/dist/presenter/tour/tour.js +6 -4
  98. package/dist/recorder/recorder.js +2 -2
  99. package/dist/registry/auto-testids.js +3 -4
  100. package/dist/registry/declared-platform.js +4 -0
  101. package/dist/reticle-inject.js +938 -494
  102. package/dist/reticle.d.ts +10 -1
  103. package/dist/reticle.js +109 -11
  104. package/dist/review/annotator-styles.d.ts +1 -1
  105. package/dist/review/annotator.d.ts +7 -0
  106. package/dist/review/annotator.js +20 -2
  107. package/dist/review/mark-anchor.js +2 -2
  108. package/dist/session-continuity.js +2 -1
  109. package/dist/storage-keys.d.ts +15 -0
  110. package/dist/storage-keys.js +21 -0
  111. package/dist/transport/transport.js +5 -3
  112. package/dist/transport/unreachable-message.d.ts +1 -1
  113. package/dist/transport/unreachable-message.js +3 -5
  114. package/package.json +3 -2
  115. package/dist/presenter/carousel/offer-card.d.ts +0 -21
  116. package/dist/presenter/carousel/offer-card.js +0 -55
package/dist/reticle.d.ts CHANGED
@@ -1,8 +1,12 @@
1
- import { EventType, RETICLE_URL_PARAM, type ReticleEvent } from '@reticlehq/core';
1
+ import { EventType, RETICLE_URL_PARAM, HudVisibility, type ReticleEvent } from '@reticlehq/core';
2
2
  import { type CapabilitiesInput } from './registry/capabilities.js';
3
3
  import type { ReticleConnectOptions } from './connect-options.js';
4
4
  export type { ReticleConnectOptions };
5
5
  export declare function shouldBlockProduction(nodeEnv: string | undefined, allowInProduction: boolean): boolean;
6
+ export declare function withBridgePath(bridgeUrl: string): {
7
+ url: string;
8
+ adjusted: boolean;
9
+ };
6
10
  export declare function connectionPolicy(pageHostname: string, bridgeUrl: string, allowNonLocalhost: boolean, token: string | undefined, pageProtocol?: string): {
7
11
  allowed: boolean;
8
12
  reason?: string;
@@ -12,6 +16,11 @@ export { RETICLE_URL_PARAM };
12
16
  export declare function reticleParamsFromSearch(search: string): {
13
17
  session?: string;
14
18
  projectId?: string;
19
+ hud?: HudVisibility;
20
+ };
21
+ export declare function hudWithoutPanel(hud: HudVisibility | undefined): {
22
+ remember?: HudVisibility;
23
+ placed: HudVisibility[];
15
24
  };
16
25
  export declare function resolveConnectIdentity(options: {
17
26
  session?: string;
package/dist/reticle.js CHANGED
@@ -1,5 +1,7 @@
1
- import { setPresenterVisible } from './dom/dom-ignore.js';
2
- import { EventType, RETICLE_DEFAULT_PORT, RETICLE_PROTOCOL_VERSION, RETICLE_URL_PARAM, bridgeWsUrl, ReticleCommand, MessageKind, SESSION_AUTO, SessionState, TRANSPORT_LIMITS, buildRedactionPolicy, isLoopbackHostname, isLocalPage, setActiveRedactionPolicy, wireRedactionKeys, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, CONTRACT_FINGERPRINT, CONTRACT_PARTS, newDocumentId, NO_EDITS_OBSERVED, PresenterMode, } from '@reticlehq/core';
1
+ import { setPresenterViewer, setPresenterVisible } from './dom/dom-ignore.js';
2
+ import { setOutwardPolicy } from './actions/danger-context.js';
3
+ import { coverageMarkers, notApplicableFor } from './dom/addressing/coverage-markers.js';
4
+ import { EventType, RETICLE_DEFAULT_PORT, RETICLE_PROTOCOL_VERSION, RETICLE_URL_PARAM, RETICLE_WS_PATH, bridgeWsUrl, ReticleCommand, MessageKind, SESSION_AUTO, SessionState, TRANSPORT_LIMITS, buildRedactionPolicy, isLoopbackHostname, isLocalPage, setActiveRedactionPolicy, wireRedactionKeys, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, CONTRACT_FINGERPRINT, CONTRACT_PARTS, newDocumentId, NO_EDITS_OBSERVED, PresenterMode, CommandActor, HudCorner, HudVisibility, } from '@reticlehq/core';
3
5
  import { rememberSessionLabel } from './session-continuity.js';
4
6
  import { editEpoch } from './edit-epoch.js';
5
7
  import { createCommandRegistry, RELOAD_CACHE_BUST_PARAM, } from './commands/commands.js';
@@ -10,9 +12,10 @@ import { declaredChannels } from './registry/declared-channels.js';
10
12
  import { declaredCommands, declaredPlatform } from './registry/declared-platform.js';
11
13
  import { registerCapabilities, setCapabilitiesListener, hasCapabilities, } from './registry/capabilities.js';
12
14
  import { installAllObservers, runTeardowns } from './observers/install-all.js';
15
+ import { setTestIdAttr } from './dom/addressing/testid-attr.js';
13
16
  import { setNetworkBodyMaxChars } from './observers/net-detail/network-body.js';
14
17
  import { installOverlay } from './presenter/chrome/overlay.js';
15
- import { LOG_KIND, LOG_RESULT } from './presenter/chrome/log-kinds.js';
18
+ import { LOG_ACTOR, LOG_KIND, LOG_RESULT } from './presenter/chrome/log-kinds.js';
16
19
  import { actionVerb } from './presenter/chrome/presenter-verbs.js';
17
20
  import { str, refLabel, modeForCommand, presentStatus } from './reticle-presenter-helpers.js';
18
21
  import { resetClock } from './timers/clock.js';
@@ -22,6 +25,21 @@ import { Annotator } from './review/annotator.js';
22
25
  export function shouldBlockProduction(nodeEnv, allowInProduction) {
23
26
  return 'production' === nodeEnv && !allowInProduction;
24
27
  }
28
+ export function withBridgePath(bridgeUrl) {
29
+ let parsed;
30
+ try {
31
+ parsed = new URL(bridgeUrl);
32
+ }
33
+ catch {
34
+ return { url: bridgeUrl, adjusted: false };
35
+ }
36
+ if ((parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') ||
37
+ (parsed.pathname !== '' && parsed.pathname !== '/')) {
38
+ return { url: bridgeUrl, adjusted: false };
39
+ }
40
+ parsed.pathname = RETICLE_WS_PATH;
41
+ return { url: parsed.toString(), adjusted: true };
42
+ }
25
43
  export function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, token, pageProtocol = 'http:') {
26
44
  let bridge;
27
45
  try {
@@ -57,6 +75,8 @@ export function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, tok
57
75
  }
58
76
  return { allowed: true };
59
77
  }
78
+ const MAX_PENDING_NARRATIONS = 20;
79
+ const KILLED_MESSAGE = '[Reticle] killed for this page. Restart your dev server or reload the page to bring it back.';
60
80
  const BRIDGE_LOST_SUMMARY = 'Session ended - lost connection to Reticle (the agent is no longer running).';
61
81
  export function resolveSessionLabel(option, gen) {
62
82
  return option === undefined || option === SESSION_AUTO ? gen() : option;
@@ -78,12 +98,20 @@ export function reticleParamsFromSearch(search) {
78
98
  const out = {};
79
99
  const session = params.get(RETICLE_URL_PARAM.SESSION);
80
100
  const projectId = params.get(RETICLE_URL_PARAM.PROJECT);
101
+ const hud = oneOf(HudVisibility, params.get(RETICLE_URL_PARAM.HUD));
81
102
  if (session !== null && session.length > 0)
82
103
  out.session = session;
83
104
  if (projectId !== null && projectId.length > 0)
84
105
  out.projectId = projectId;
106
+ if (hud !== undefined)
107
+ out.hud = hud;
85
108
  return out;
86
109
  }
110
+ export function hudWithoutPanel(hud) {
111
+ if (hud === undefined)
112
+ return { placed: [] };
113
+ return { remember: hud, placed: HudVisibility.SHOWN === hud ? [] : [hud] };
114
+ }
87
115
  export function resolveConnectIdentity(options, search) {
88
116
  const url = reticleParamsFromSearch(search);
89
117
  const explicitSession = options.session !== undefined && options.session !== SESSION_AUTO ? options.session : undefined;
@@ -116,6 +144,8 @@ export class Reticle {
116
144
  #overlay;
117
145
  #presenter;
118
146
  #pendingPushes = new Map();
147
+ #pendingNarrations = [];
148
+ #pendingHud;
119
149
  #readPanelSettings;
120
150
  #panelRequested = false;
121
151
  #bridgeConnected = false;
@@ -142,7 +172,11 @@ export class Reticle {
142
172
  return;
143
173
  }
144
174
  stripReloadCacheBustParam();
145
- const url = options.url ?? bridgeWsUrl(RETICLE_DEFAULT_PORT);
175
+ const bridge = withBridgePath(options.url ?? bridgeWsUrl(RETICLE_DEFAULT_PORT));
176
+ const url = bridge.url;
177
+ if (bridge.adjusted) {
178
+ globalThis.console.warn(`[Reticle] bridge URL has no path; using ${url} (the bridge only accepts WebSocket upgrades on ${RETICLE_WS_PATH}).`);
179
+ }
146
180
  const policy = connectionPolicy(window.location.hostname, url, true === options.allowNonLocalhost, options.token, window.location.protocol);
147
181
  if (!policy.allowed) {
148
182
  globalThis.console.warn(`[Reticle] ${policy.reason ?? 'connection blocked'}`);
@@ -188,6 +222,7 @@ export class Reticle {
188
222
  });
189
223
  setCapabilitiesListener(() => this.#transport?.reannounce());
190
224
  setPresenterVisible(true === options.exposePresenter);
225
+ setTestIdAttr(options.testIdAttribute);
191
226
  if (options.networkBodyMaxChars !== undefined) {
192
227
  setNetworkBodyMaxChars(options.networkBodyMaxChars);
193
228
  }
@@ -205,7 +240,8 @@ export class Reticle {
205
240
  this.#overlay = installOverlay();
206
241
  this.#overlay.update({ connected: true, events: 0 });
207
242
  }
208
- if (options.present !== false)
243
+ this.#pendingHud = reticleParamsFromSearch(window.location.search).hud;
244
+ if (options.present !== false && HudVisibility.REMOVED !== this.#pendingHud)
209
245
  this.#fetchAndShowPanel(options);
210
246
  if (true === options.recorder) {
211
247
  this.#recorder = installRecorder({ emit, now: () => Date.now() });
@@ -215,7 +251,7 @@ export class Reticle {
215
251
  this.#annotator = new Annotator({
216
252
  emit,
217
253
  now: () => Date.now(),
218
- onMark: (mark) => this.#presenter?.log(LOG_KIND.HUMAN, `🚩 #${String(mark.index)} ${mark.anchor}${mark.source !== undefined ? ` · ${mark.source}` : ''} — ${mark.note}`),
254
+ onMark: (mark) => this.#presenter?.log(LOG_KIND.HUMAN, `🚩 #${String(mark.index)} ${mark.label}${mark.source !== undefined ? ` · ${mark.source}` : ''} — ${mark.note}`),
219
255
  shouldBlock: () => this.#readPanelSettings?.().blockPageInteractions ?? true,
220
256
  });
221
257
  this.#annotator.mount();
@@ -249,16 +285,30 @@ export class Reticle {
249
285
  if (options.idleEndMs !== undefined)
250
286
  panelOptions.idleEndMs = options.idleEndMs;
251
287
  panelOptions.sessionId = this.#session;
288
+ if (this.#projectId !== undefined)
289
+ panelOptions.projectId = this.#projectId;
252
290
  panelOptions.onControl = (intent) => this.#emit(EventType.HUMAN_CONTROL, intent.text !== undefined
253
291
  ? { kind: intent.kind, text: intent.text }
254
292
  : { kind: intent.kind });
255
293
  panelOptions.onHudUse = (use) => this.#emit(EventType.HUD_USED, { ...use });
294
+ panelOptions.onKill = () => {
295
+ this.disconnect();
296
+ nativeWarn(KILLED_MESSAGE);
297
+ };
256
298
  const panel = new Presenter(panelOptions);
257
299
  this.#presenter = panel;
258
300
  panel.mount();
301
+ panel.showSdkVersion(this.#sdkVersion);
259
302
  for (const buffered of this.#pendingPushes.values())
260
303
  panel.handlePush(buffered);
261
304
  this.#pendingPushes.clear();
305
+ if (0 < this.#pendingNarrations.length)
306
+ panel.sessionStart();
307
+ for (const early of this.#pendingNarrations.splice(0))
308
+ panel.narrate(early.text, early.level, early.actor, early.summary);
309
+ if (this.#pendingHud !== undefined)
310
+ panel.placeHud(this.#pendingHud, undefined);
311
+ this.#pendingHud = undefined;
262
312
  void import('./presenter/tour/tour.js').then((tour) => {
263
313
  if (!this.#connected)
264
314
  return;
@@ -384,19 +434,56 @@ export class Reticle {
384
434
  };
385
435
  }
386
436
  async #handleCommand(command) {
437
+ if (command.name === ReticleCommand.MARKERS) {
438
+ const markers = coverageMarkers(document);
439
+ const buildPlugin = this.#sdkVersion !== undefined || this.#sourceMapping !== undefined;
440
+ const na = notApplicableFor({ buildPlugin, adapters: adapterNames(), markers });
441
+ return { ok: true, result: { markers, ...(0 === na.length ? {} : { notApplicable: na }) } };
442
+ }
387
443
  if (command.name === ReticleCommand.NARRATE) {
444
+ if (this.#presenter === undefined && this.#panelRequested) {
445
+ if (this.#pendingNarrations.length < MAX_PENDING_NARRATIONS)
446
+ this.#pendingNarrations.push({
447
+ text: str(command.args['text']),
448
+ level: str(command.args['level'], 'info'),
449
+ actor: narrationActor(command.args),
450
+ summary: true === command.args['summary'],
451
+ });
452
+ return { ok: true, result: { shown: false, buffered: true } };
453
+ }
388
454
  this.#presenter?.sessionStart();
389
- this.#presenter?.narrate(str(command.args['text']), str(command.args['level'], 'info'));
455
+ this.#presenter?.narrate(str(command.args['text']), str(command.args['level'], 'info'), narrationActor(command.args), true === command.args['summary']);
390
456
  return { ok: true, result: { shown: this.#presenter !== undefined } };
391
457
  }
392
458
  if (command.name === ReticleCommand.SESSION_CONFIG) {
393
459
  const idleEndMs = command.args['idleEndMs'];
394
460
  if ('number' === typeof idleEndMs)
395
461
  this.#presenter?.setIdleEndMs(idleEndMs);
396
- return { ok: true, result: { applied: this.#presenter !== undefined, idleEndMs } };
462
+ const hud = oneOf(HudVisibility, command.args['hud']);
463
+ const corner = oneOf(HudCorner, command.args['corner']);
464
+ const presenter = this.#presenter;
465
+ let placed;
466
+ if (presenter !== undefined)
467
+ placed = presenter.placeHud(hud, corner);
468
+ else {
469
+ const early = hudWithoutPanel(hud);
470
+ if (early.remember !== undefined)
471
+ this.#pendingHud = early.remember;
472
+ placed = early.placed;
473
+ }
474
+ return {
475
+ ok: true,
476
+ result: {
477
+ applied: this.#presenter !== undefined,
478
+ idleEndMs,
479
+ ...(0 < placed.length ? { hud: placed } : {}),
480
+ },
481
+ };
397
482
  }
398
483
  if (command.name === ReticleCommand.PRESENTER ||
399
484
  command.name === ReticleCommand.FLOWS ||
485
+ command.name === ReticleCommand.FLOW_PROGRESS ||
486
+ command.name === ReticleCommand.PLAN ||
400
487
  command.name === ReticleCommand.IMPACT) {
401
488
  if (this.#presenter === undefined) {
402
489
  this.#pendingPushes.set(command.name, command);
@@ -411,6 +498,8 @@ export class Reticle {
411
498
  }
412
499
  this.#presenter?.sessionStart();
413
500
  await this.#presentBefore(command);
501
+ setPresenterViewer(CommandActor.HARNESS === command.by);
502
+ setOutwardPolicy(command.outward?.allowed);
414
503
  try {
415
504
  const result = await handler(command.args);
416
505
  this.#actHandle?.result(LOG_RESULT.PASS);
@@ -421,6 +510,8 @@ export class Reticle {
421
510
  return { ok: false, error: error instanceof Error ? error.message : String(error) };
422
511
  }
423
512
  finally {
513
+ setPresenterViewer(false);
514
+ setOutwardPolicy(undefined);
424
515
  this.#actHandle = undefined;
425
516
  this.#presenter?.scheduleIdle();
426
517
  }
@@ -431,10 +522,11 @@ export class Reticle {
431
522
  return;
432
523
  p.setMode(modeForCommand(command.name));
433
524
  this.#actHandle = undefined;
525
+ const actor = CommandActor.HARNESS === command.by ? LOG_ACTOR.HARNESS : LOG_ACTOR.AGENT;
434
526
  if (command.name === ReticleCommand.ACT) {
435
527
  const ref = str(command.args['ref']);
436
528
  const label = refLabel(ref);
437
- this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(command.args['action']))} ${label}`);
529
+ this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(command.args['action']))} ${label}`, undefined, actor);
438
530
  await p.beforeAct(ref, str(command.args['action']), label);
439
531
  }
440
532
  else if (command.name === ReticleCommand.ACT_SEQUENCE) {
@@ -443,14 +535,20 @@ export class Reticle {
443
535
  const s = step;
444
536
  const ref = str(s.ref);
445
537
  const label = refLabel(ref);
446
- this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(s.action))} ${label}`);
538
+ this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(s.action))} ${label}`, undefined, actor);
447
539
  await p.beforeAct(ref, str(s.action), label);
448
540
  }
449
541
  }
450
542
  else {
451
543
  const label = presentStatus(command.name, command.args);
452
544
  p.status(label);
453
- p.log(LOG_KIND.READ, label);
545
+ p.log(LOG_KIND.READ, label, undefined, actor);
454
546
  }
455
547
  }
456
548
  }
549
+ function narrationActor(args) {
550
+ return CommandActor.HARNESS === args['actor'] ? LOG_ACTOR.HARNESS : LOG_ACTOR.AGENT;
551
+ }
552
+ function oneOf(values, raw) {
553
+ return Object.values(values).includes(raw) ? raw : undefined;
554
+ }
@@ -3,4 +3,4 @@ export declare const MARK_SUBMIT = "Add";
3
3
  export declare const MARK_CANCEL = "Cancel";
4
4
  export declare const MARK_PENDING_GLYPH = "+";
5
5
  export declare const ANNOTATOR_CSS: string;
6
- export declare const ANNOTATOR_ROOT_HTML = "<div data-reticle-mark=\"hi\"><span data-reticle-mark=\"hilabel\"></span></div><div data-reticle-mark=\"sel\" hidden></div>";
6
+ export declare const ANNOTATOR_ROOT_HTML: string;
@@ -1,4 +1,5 @@
1
1
  import { EventType } from '@reticlehq/core';
2
+ import type { MarkForAgent } from './marks-for-agent.js';
2
3
  interface MarkReport {
3
4
  note: string;
4
5
  anchor: string;
@@ -19,12 +20,18 @@ export interface AnnotatorChrome {
19
20
  copyRow?: HTMLElement;
20
21
  countEl?: HTMLElement;
21
22
  onCount?: (count: number) => void;
23
+ onMarksChange?: (marks: readonly AnnotationItem[]) => void;
24
+ }
25
+ export interface AnnotationItem extends MarkForAgent {
26
+ id: string;
27
+ createdAt: number;
22
28
  }
23
29
  export declare class Annotator {
24
30
  #private;
25
31
  constructor(deps: AnnotatorDeps);
26
32
  get active(): boolean;
27
33
  get markCount(): number;
34
+ annotations(): AnnotationItem[];
28
35
  mount(): void;
29
36
  setAccent(hex: string): void;
30
37
  attachChrome(chrome: AnnotatorChrome): void;
@@ -1,3 +1,4 @@
1
+ import { readTestId } from '../dom/addressing/testid-attr.js';
1
2
  import { isSyntheticInput } from '../actions/synthetic/synthetic-input.js';
2
3
  import { EventType } from '@reticlehq/core';
3
4
  import { isReticleUi, isReticleOverlay } from '../dom/dom-ignore.js';
@@ -36,6 +37,7 @@ export class Annotator {
36
37
  #clearBtn;
37
38
  #copyRow;
38
39
  #onChromeCount;
40
+ #onMarksChange;
39
41
  #countEl;
40
42
  #accent;
41
43
  #ac;
@@ -53,6 +55,17 @@ export class Annotator {
53
55
  get markCount() {
54
56
  return this.#marks.length;
55
57
  }
58
+ annotations() {
59
+ return this.#marks.map(({ id, createdAt, note, label, anchor, route, source }) => ({
60
+ id,
61
+ createdAt,
62
+ note,
63
+ label,
64
+ anchor,
65
+ route,
66
+ ...(source === undefined ? {} : { source }),
67
+ }));
68
+ }
56
69
  mount() {
57
70
  if (this.#root !== undefined || 'undefined' === typeof document)
58
71
  return;
@@ -99,6 +112,7 @@ export class Annotator {
99
112
  this.#clearBtn = chrome.clearBtn;
100
113
  this.#copyRow = chrome.copyRow;
101
114
  this.#onChromeCount = chrome.onCount;
115
+ this.#onMarksChange = chrome.onMarksChange;
102
116
  this.#countEl = chrome.countEl;
103
117
  this.#markersBtn?.addEventListener('click', (e) => {
104
118
  e.stopPropagation();
@@ -184,6 +198,7 @@ export class Annotator {
184
198
  }
185
199
  this.#clearBtn?.toggleAttribute('disabled', 0 === n);
186
200
  this.#onChromeCount?.(n);
201
+ this.#onMarksChange?.(this.annotations());
187
202
  if (this.#copyRow !== undefined) {
188
203
  this.#copyRow.hidden = 0 === n;
189
204
  const text = this.#copyRow.querySelector('[data-reticle-marks-text]');
@@ -406,6 +421,7 @@ export class Annotator {
406
421
  route: editing.route,
407
422
  });
408
423
  this.#report(note, editing.anchor, editing.label, this.#marks.indexOf(editing) + 1, editing.source);
424
+ this.#syncChrome();
409
425
  this.#closePopover();
410
426
  return;
411
427
  }
@@ -480,8 +496,10 @@ export class Annotator {
480
496
  pin.style.left = `${String((x / window.innerWidth) * 100)}%`;
481
497
  pin.style.top = `${String(isFixed ? y : yDoc - window.scrollY)}px`;
482
498
  this.#root.appendChild(pin);
499
+ const createdAt = this.#now();
483
500
  const mark = {
484
- id: `${resolved.anchor}:${String(this.#now())}:${String(n)}`,
501
+ id: `${resolved.anchor}:${String(createdAt)}:${String(n)}`,
502
+ createdAt,
485
503
  note,
486
504
  label: resolved.label,
487
505
  anchor: resolved.anchor,
@@ -593,7 +611,7 @@ function clampPop(x, y) {
593
611
  return { left, top };
594
612
  }
595
613
  function describeEl(el) {
596
- const testid = el.getAttribute('data-testid');
614
+ const testid = readTestId(el);
597
615
  if (testid !== null && testid.length > 0)
598
616
  return testid;
599
617
  const tag = el.tagName.toLowerCase();
@@ -1,9 +1,9 @@
1
+ import { readTestId } from '../dom/addressing/testid-attr.js';
1
2
  import { MarkAnchorStrategy } from '@reticlehq/core';
2
3
  import { AnchorStrategy, synthesizeAnchor, } from '../dom/addressing/auto-anchor.js';
3
4
  import { getAccessibleName, getRole } from '../dom/a11y.js';
4
5
  import { identifyComponent } from '../registry/stores/adapters.js';
5
6
  import { sourceFor } from '../dom/addressing/source.js';
6
- const TESTID_ATTR = 'data-testid';
7
7
  const STRATEGY = {
8
8
  [AnchorStrategy.TESTID]: MarkAnchorStrategy.TESTID,
9
9
  [AnchorStrategy.COMPONENT]: MarkAnchorStrategy.COMPONENT,
@@ -16,7 +16,7 @@ function labelFor(el, role, name) {
16
16
  return role.length > 0 ? role : el.tagName.toLowerCase();
17
17
  }
18
18
  export function resolveMarkAnchor(el) {
19
- const testid = el.getAttribute(TESTID_ATTR) ?? undefined;
19
+ const testid = readTestId(el) ?? undefined;
20
20
  const info = identifyComponent(el);
21
21
  const component = info?.componentStack[0];
22
22
  const source = sourceFor(el, info?.source);
@@ -1,4 +1,5 @@
1
- const STORAGE_KEY = '__reticle_session';
1
+ import { ReticleStorageKey } from './storage-keys.js';
2
+ const STORAGE_KEY = ReticleStorageKey.SESSION;
2
3
  export function rememberSessionLabel(explicit, store, generate) {
3
4
  const read = () => {
4
5
  try {
@@ -0,0 +1,15 @@
1
+ export declare const ReticleStorageKey: {
2
+ readonly SESSION: "__reticle_session";
3
+ readonly REF_BASE: "__reticle_ref_base";
4
+ readonly PRESENTER_LOG: "reticle-presenter-log";
5
+ readonly PRESENTER_LOG_OWNER: "reticle-presenter-log-owner";
6
+ readonly PRESENTER_SETTINGS: "reticle-presenter-settings";
7
+ readonly PRESENTER_MINIMISED: "reticle-presenter-minimised";
8
+ readonly PRESENTER_VIEW: "reticle-presenter-view";
9
+ readonly CAROUSEL_DISMISSED: "reticle.carousel.dismissed";
10
+ readonly ANNOTATION_HISTORY: "reticle.annotations.history.v1";
11
+ };
12
+ export declare const TOUR_SEEN_KEY_PREFIX = "reticle.tour.seen.";
13
+ export declare const PERSONA_KEY_PREFIX = "reticle.harness.persona.";
14
+ export declare const IMPACT_SEEN_KEY_PREFIX = "reticle.impact.seen.";
15
+ export declare function isReticleStorageKey(key: string): boolean;
@@ -0,0 +1,21 @@
1
+ export const ReticleStorageKey = {
2
+ SESSION: '__reticle_session',
3
+ REF_BASE: '__reticle_ref_base',
4
+ PRESENTER_LOG: 'reticle-presenter-log',
5
+ PRESENTER_LOG_OWNER: 'reticle-presenter-log-owner',
6
+ PRESENTER_SETTINGS: 'reticle-presenter-settings',
7
+ PRESENTER_MINIMISED: 'reticle-presenter-minimised',
8
+ PRESENTER_VIEW: 'reticle-presenter-view',
9
+ CAROUSEL_DISMISSED: 'reticle.carousel.dismissed',
10
+ ANNOTATION_HISTORY: 'reticle.annotations.history.v1',
11
+ };
12
+ export const TOUR_SEEN_KEY_PREFIX = 'reticle.tour.seen.';
13
+ export const PERSONA_KEY_PREFIX = 'reticle.harness.persona.';
14
+ export const IMPACT_SEEN_KEY_PREFIX = 'reticle.impact.seen.';
15
+ const OWN_KEYS = new Set(Object.values(ReticleStorageKey));
16
+ export function isReticleStorageKey(key) {
17
+ return (OWN_KEYS.has(key) ||
18
+ key.startsWith(TOUR_SEEN_KEY_PREFIX) ||
19
+ key.startsWith(PERSONA_KEY_PREFIX) ||
20
+ key.startsWith(IMPACT_SEEN_KEY_PREFIX));
21
+ }
@@ -26,6 +26,7 @@ export class Transport {
26
26
  #signalQueue = [];
27
27
  #insertOrder = 0;
28
28
  #closed = false;
29
+ #refused = false;
29
30
  #disconnectedSince;
30
31
  #reconnectDelay = RECONNECT_DELAY_MS;
31
32
  #lost = false;
@@ -55,6 +56,7 @@ export class Transport {
55
56
  this.#reconnectDelay = RECONNECT_DELAY_MS;
56
57
  if (this.#ws !== undefined)
57
58
  return;
59
+ this.#refused = false;
58
60
  this.#open();
59
61
  }
60
62
  #open() {
@@ -92,9 +94,9 @@ export class Transport {
92
94
  ws.onclose = (event) => {
93
95
  this.#ws = undefined;
94
96
  if (event.code === WS_POLICY_VIOLATION) {
95
- this.#closed = true;
97
+ this.#refused = true;
96
98
  const reason = event.reason.length > 0 ? event.reason : 'policy violation';
97
- nativeWarn(`[reticle] bridge refused the connection: ${reason} — not retrying.`);
99
+ nativeWarn(`[reticle] bridge refused the connection: ${reason} — not retrying until you come back to this tab.`);
98
100
  this.#deps.onConnectionLost?.();
99
101
  return;
100
102
  }
@@ -107,7 +109,7 @@ export class Transport {
107
109
  };
108
110
  }
109
111
  #scheduleReopen() {
110
- if (this.#closed)
112
+ if (this.#closed || this.#refused)
111
113
  return;
112
114
  const delay = this.#reconnectDelay;
113
115
  this.#reconnectDelay = nextReconnectDelay(delay);
@@ -1,2 +1,2 @@
1
1
  export declare function unreachableMessage(url: string, attempts: number): string;
2
- export declare function unreachableStripText(url: string, attempts: number): string;
2
+ export declare const UNREACHABLE_HEADLINE = "Can't reach Reticle from this page. Run reticle status to check, or reticle serve to start it.";
@@ -5,9 +5,7 @@ export function unreachableMessage(url, attempts) {
5
5
  `that is not there from one it is not allowed to reach, so this is not evidence about the ` +
6
6
  `daemon. What answers it: run \`npx @reticlehq/server status\` to see whether one is listening ` +
7
7
  `and on which port. If your app runs in a container, devcontainer or WSL, the daemon is on a ` +
8
- `different host, so set the URL explicitly (Vite: VITE_RETICLE_WS_URL, or ` +
9
- `reticle.connect({ url })). An https page cannot open a ws:// socket at all. Still retrying…`);
10
- }
11
- export function unreachableStripText(url, attempts) {
12
- return `no bridge at ${url} — ${String(attempts)} attempt${1 === attempts ? '' : 's'}`;
8
+ `different host, so point the page at it: RETICLE_PORT or reticle({ port }) with the Vite ` +
9
+ `plugin, reticle.connect({ url }) by hand. An https page cannot open a ws:// socket. Still retrying…`);
13
10
  }
11
+ export const UNREACHABLE_HEADLINE = "Can't reach Reticle from this page. Run reticle status to check, or reticle serve to start it.";
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@reticlehq/browser",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
4
4
  "description": "Reticle browser SDK — installs observers, builds semantic snapshots, executes actions, talks to the bridge.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
+ "module": "./dist/reticle-inject.js",
7
8
  "types": "./dist/index.d.ts",
8
9
  "exports": {
9
10
  ".": {
@@ -17,7 +18,7 @@
17
18
  }
18
19
  },
19
20
  "dependencies": {
20
- "@reticlehq/core": "3.5.0"
21
+ "@reticlehq/core": "3.7.0"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@iconify-json/heroicons-outline": "^1.2.1",
@@ -1,21 +0,0 @@
1
- export declare const OFFER_ATTR = "data-reticle-offer";
2
- export declare const OFFER_CLAIM_ATTR = "data-reticle-offer-claim";
3
- export declare const OFFER_DISMISSED_KEY = "reticle.harnessOffer.dismissed";
4
- export declare const OFFER_TEXT: {
5
- readonly HEADLINE: "Get Harness free for 3 months";
6
- readonly BODY: "Explores your app and saves every journey as a free replay.";
7
- readonly CLAIM: "Claim now";
8
- readonly DISMISS: "Not now";
9
- readonly claimed: (days: number) => string;
10
- readonly ending: (days: number) => string;
11
- };
12
- export declare function offerDismissed(storage: Pick<Storage, 'getItem'> | undefined): boolean;
13
- export interface OfferState {
14
- claimed: boolean;
15
- daysRemaining?: number | undefined;
16
- claimUrl?: string | undefined;
17
- eligible?: boolean | undefined;
18
- drivable?: boolean | undefined;
19
- }
20
- export declare function offerHtml(offer: OfferState | undefined, dismissed: boolean): string;
21
- export declare const OFFER_CSS = "\n.reticle-offer{margin:8px 10px;padding:10px 12px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}\n.reticle-offer-title{display:block;font-weight:600;font-size:12px;padding-right:18px;}\n.reticle-offer-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}\n.reticle-offer-claim{display:inline-block;font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}\n.reticle-offer-claimed{margin:6px 10px;font-size:10px;opacity:.6;}\n";
@@ -1,55 +0,0 @@
1
- export const OFFER_ATTR = 'data-reticle-offer';
2
- export const OFFER_CLAIM_ATTR = 'data-reticle-offer-claim';
3
- export const OFFER_DISMISSED_KEY = 'reticle.harnessOffer.dismissed';
4
- export const OFFER_TEXT = {
5
- HEADLINE: 'Get Harness free for 3 months',
6
- BODY: 'Explores your app and saves every journey as a free replay.',
7
- CLAIM: 'Claim now',
8
- DISMISS: 'Not now',
9
- claimed: (days) => `Harness active — ${String(days)} days left`,
10
- ending: (days) => 1 >= days
11
- ? 'Harness free period ends today'
12
- : `Harness free period ends in ${String(days)} days`,
13
- };
14
- const ENDING_SOON_DAYS = 14;
15
- export function offerDismissed(storage) {
16
- try {
17
- return '1' === storage?.getItem(OFFER_DISMISSED_KEY);
18
- }
19
- catch {
20
- return false;
21
- }
22
- }
23
- const escape = (text) => text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
24
- export function offerHtml(offer, dismissed) {
25
- if (offer === undefined)
26
- return '';
27
- if (offer.claimed) {
28
- const days = offer.daysRemaining;
29
- if (days === undefined || 0 > days)
30
- return '';
31
- const text = ENDING_SOON_DAYS >= days ? OFFER_TEXT.ending(days) : OFFER_TEXT.claimed(days);
32
- return `<div ${OFFER_ATTR} class="reticle-offer reticle-offer-claimed">${escape(text)}</div>`;
33
- }
34
- if (false === offer.eligible)
35
- return '';
36
- if (false === offer.drivable)
37
- return '';
38
- if (dismissed)
39
- return '';
40
- const url = offer.claimUrl;
41
- if (url === undefined || 0 === url.length)
42
- return '';
43
- return `<div ${OFFER_ATTR} class="reticle-offer" role="note">
44
- <span class="reticle-offer-title">${escape(OFFER_TEXT.HEADLINE)}</span>
45
- <p class="reticle-offer-body">${escape(OFFER_TEXT.BODY)}</p>
46
- <a ${OFFER_CLAIM_ATTR} class="reticle-offer-claim" href="${escape(url)}" target="_blank" rel="noopener noreferrer">${escape(OFFER_TEXT.CLAIM)}</a>
47
- </div>`;
48
- }
49
- export const OFFER_CSS = `
50
- .reticle-offer{margin:8px 10px;padding:10px 12px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}
51
- .reticle-offer-title{display:block;font-weight:600;font-size:12px;padding-right:18px;}
52
- .reticle-offer-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}
53
- .reticle-offer-claim{display:inline-block;font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}
54
- .reticle-offer-claimed{margin:6px 10px;font-size:10px;opacity:.6;}
55
- `;