@reticlehq/browser 2.14.0 → 3.1.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 (130) hide show
  1. package/dist/actions/actions-dom.d.ts +8 -0
  2. package/dist/actions/actions-dom.js +70 -3
  3. package/dist/actions/actions-press.d.ts +4 -0
  4. package/dist/actions/actions-press.js +46 -0
  5. package/dist/actions/actions.d.ts +1 -0
  6. package/dist/actions/actions.js +41 -8
  7. package/dist/actions/appeared-text.js +6 -0
  8. package/dist/actions/scroll.d.ts +8 -1
  9. package/dist/actions/scroll.js +28 -10
  10. package/dist/commands/commands.js +10 -3
  11. package/dist/dom/a11y.js +2 -2
  12. package/dist/dom/desktop-capture.js +1 -1
  13. package/dist/dom/dom-ignore.js +1 -1
  14. package/dist/dom/occlusion.js +31 -5
  15. package/dist/dom/paint-context.d.ts +6 -0
  16. package/dist/dom/paint-context.js +38 -0
  17. package/dist/dom/query.js +2 -2
  18. package/dist/dom/snapshot.js +1 -1
  19. package/dist/edit-epoch.d.ts +1 -1
  20. package/dist/edit-epoch.js +1 -1
  21. package/dist/index.d.ts +4 -5
  22. package/dist/index.js +3 -5
  23. package/dist/observers/animation.js +1 -1
  24. package/dist/observers/blind-spots.js +1 -1
  25. package/dist/observers/dialogs.js +1 -1
  26. package/dist/observers/dom.js +28 -8
  27. package/dist/observers/download.js +1 -1
  28. package/dist/observers/health.js +1 -1
  29. package/dist/observers/{network-body.js → net-detail/network-body.js} +2 -2
  30. package/dist/observers/network-stream.js +2 -2
  31. package/dist/observers/network.d.ts +1 -1
  32. package/dist/observers/network.js +2 -2
  33. package/dist/observers/scroll.js +2 -2
  34. package/dist/presenter/chrome/layers.d.ts +2 -0
  35. package/dist/presenter/chrome/layers.js +2 -0
  36. package/dist/presenter/chrome/log-kinds.d.ts +12 -0
  37. package/dist/presenter/chrome/log-kinds.js +7 -0
  38. package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -1
  39. package/dist/presenter/{presenter-log.d.ts → chrome/presenter-log.d.ts} +3 -12
  40. package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +4 -9
  41. package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
  42. package/dist/presenter/chrome/presenter-safe-html.js +16 -0
  43. package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -1
  44. package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -0
  45. package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -0
  46. package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -0
  47. package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -0
  48. package/dist/presenter/presenter-account-styles.d.ts +1 -0
  49. package/dist/presenter/presenter-account-styles.js +99 -0
  50. package/dist/presenter/presenter-account.d.ts +40 -0
  51. package/dist/presenter/presenter-account.js +178 -0
  52. package/dist/presenter/presenter-config.d.ts +9 -2
  53. package/dist/presenter/presenter-config.js +3 -1
  54. package/dist/presenter/presenter-controls.d.ts +1 -6
  55. package/dist/presenter/presenter-controls.js +7 -2
  56. package/dist/presenter/presenter-dock-layout.js +1 -1
  57. package/dist/presenter/presenter-drag.d.ts +1 -19
  58. package/dist/presenter/presenter-drag.js +3 -50
  59. package/dist/presenter/presenter-glow.js +1 -1
  60. package/dist/presenter/presenter-hud-position.d.ts +18 -0
  61. package/dist/presenter/presenter-hud-position.js +50 -0
  62. package/dist/presenter/presenter-report-styles.js +16 -1
  63. package/dist/presenter/presenter-report.d.ts +2 -1
  64. package/dist/presenter/presenter-report.js +29 -22
  65. package/dist/presenter/presenter-run-state.d.ts +1 -1
  66. package/dist/presenter/presenter-run-state.js +1 -1
  67. package/dist/presenter/presenter-settings-styles.js +2 -1
  68. package/dist/presenter/presenter-settings.d.ts +4 -0
  69. package/dist/presenter/presenter-settings.js +20 -2
  70. package/dist/presenter/presenter-shell-styles.js +21 -15
  71. package/dist/presenter/presenter-shell.d.ts +4 -0
  72. package/dist/presenter/presenter-shell.js +25 -4
  73. package/dist/presenter/presenter-styles.d.ts +2 -1
  74. package/dist/presenter/presenter-styles.js +6 -3
  75. package/dist/presenter/presenter-workspace.d.ts +4 -0
  76. package/dist/presenter/presenter-workspace.js +29 -2
  77. package/dist/presenter/presenter.d.ts +3 -3
  78. package/dist/presenter/presenter.js +25 -14
  79. package/dist/presenter/tour/tour-view.d.ts +33 -0
  80. package/dist/presenter/tour/tour-view.js +188 -0
  81. package/dist/presenter/tour/tour.d.ts +25 -0
  82. package/dist/presenter/tour/tour.js +245 -0
  83. package/dist/recorder/recorder.js +6 -0
  84. package/dist/registry/declared-channels.d.ts +2 -0
  85. package/dist/registry/declared-channels.js +14 -0
  86. package/dist/registry/declared-platform.d.ts +4 -0
  87. package/dist/registry/declared-platform.js +34 -0
  88. package/dist/registry/emitter.js +1 -1
  89. package/dist/registry/store-adapters.js +1 -1
  90. package/dist/registry/stores.js +1 -1
  91. package/dist/reticle-presenter-helpers.js +10 -6
  92. package/dist/reticle-singleton.d.ts +2 -0
  93. package/dist/reticle-singleton.js +3 -0
  94. package/dist/reticle.js +95 -35
  95. package/dist/review/annotator-styles.js +1 -1
  96. package/dist/review/annotator.js +2 -2
  97. package/dist/review/mark-anchor.js +3 -3
  98. package/dist/transport/transport.js +2 -2
  99. package/package.json +5 -4
  100. package/dist/presenter/presenter-test-helpers.d.ts +0 -14
  101. package/dist/presenter/presenter-test-helpers.js +0 -39
  102. /package/dist/actions/{synthetic-input.d.ts → synthetic/synthetic-input.d.ts} +0 -0
  103. /package/dist/actions/{synthetic-input.js → synthetic/synthetic-input.js} +0 -0
  104. /package/dist/dom/{auto-anchor.d.ts → addressing/auto-anchor.d.ts} +0 -0
  105. /package/dist/dom/{auto-anchor.js → addressing/auto-anchor.js} +0 -0
  106. /package/dist/dom/{refs.d.ts → addressing/refs.d.ts} +0 -0
  107. /package/dist/dom/{refs.js → addressing/refs.js} +0 -0
  108. /package/dist/dom/{source.d.ts → addressing/source.d.ts} +0 -0
  109. /package/dist/dom/{source.js → addressing/source.js} +0 -0
  110. /package/dist/observers/{network-body.d.ts → net-detail/network-body.d.ts} +0 -0
  111. /package/dist/observers/{network-redact.d.ts → net-detail/network-redact.d.ts} +0 -0
  112. /package/dist/observers/{network-redact.js → net-detail/network-redact.js} +0 -0
  113. /package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -0
  114. /package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -0
  115. /package/dist/presenter/{presenter-brand.d.ts → chrome/presenter-brand.d.ts} +0 -0
  116. /package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -0
  117. /package/dist/presenter/{presenter-effects.d.ts → chrome/presenter-effects.d.ts} +0 -0
  118. /package/dist/presenter/{presenter-hud-chrome.d.ts → chrome/presenter-hud-chrome.d.ts} +0 -0
  119. /package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -0
  120. /package/dist/presenter/{presenter-report-copy.d.ts → chrome/presenter-report-copy.d.ts} +0 -0
  121. /package/dist/presenter/{presenter-report-copy.js → chrome/presenter-report-copy.js} +0 -0
  122. /package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -0
  123. /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
  124. /package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -0
  125. /package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -0
  126. /package/dist/registry/{adapters.js → stores/adapters.js} +0 -0
  127. /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
  128. /package/dist/timers/{native-console.js → console/native-console.js} +0 -0
  129. /package/dist/timers/{native-timers.d.ts → native/native-timers.d.ts} +0 -0
  130. /package/dist/timers/{native-timers.js → native/native-timers.js} +0 -0
@@ -0,0 +1,245 @@
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
+ import { TourAnchor } from '@reticlehq/core/tour';
3
+ import { RETICLE_URL_PARAM } from '@reticlehq/core';
4
+ export const TOUR_SEEN_KEY_PREFIX = 'reticle.tour.seen.';
5
+ const APP_SELECTORS = ['main', '#root', '#app'];
6
+ const HUD_PARTS = ['[data-reticle-hud]', '[data-reticle-chat-panel]'];
7
+ function unionOfVisible(doc, selectors) {
8
+ let left = Number.POSITIVE_INFINITY;
9
+ let top = Number.POSITIVE_INFINITY;
10
+ let right = Number.NEGATIVE_INFINITY;
11
+ let bottom = Number.NEGATIVE_INFINITY;
12
+ for (const selector of selectors) {
13
+ const box = doc.querySelector(selector)?.getBoundingClientRect();
14
+ if (undefined === box || box.width <= 0 || box.height <= 0)
15
+ continue;
16
+ left = Math.min(left, box.left);
17
+ top = Math.min(top, box.top);
18
+ right = Math.max(right, box.left + box.width);
19
+ bottom = Math.max(bottom, box.top + box.height);
20
+ }
21
+ if (right === Number.NEGATIVE_INFINITY)
22
+ return undefined;
23
+ return { left, top, width: right - left, height: bottom - top };
24
+ }
25
+ const HUD_CONTROL_SELECTORS = {
26
+ [TourAnchor.HUD_CHAT]: '[data-reticle-chat-toggle]',
27
+ [TourAnchor.HUD_ANNOTATE]: '[data-reticle-annotate-btn]',
28
+ [TourAnchor.HUD_IMPACT]: '[data-reticle-report-btn]',
29
+ [TourAnchor.HUD_SETTINGS]: '[data-reticle-settings-btn]',
30
+ };
31
+ function anchorBox(doc, anchor) {
32
+ if (TourAnchor.HUD === anchor)
33
+ return unionOfVisible(doc, HUD_PARTS);
34
+ const control = HUD_CONTROL_SELECTORS[anchor];
35
+ if (undefined !== control) {
36
+ const box = doc.querySelector(control)?.getBoundingClientRect();
37
+ return undefined === box ? undefined : box;
38
+ }
39
+ if (TourAnchor.APP !== anchor)
40
+ return undefined;
41
+ for (const selector of APP_SELECTORS) {
42
+ const found = doc.querySelector(selector);
43
+ if (null === found)
44
+ continue;
45
+ const box = found.getBoundingClientRect();
46
+ if (box.width > 0)
47
+ return box;
48
+ }
49
+ return undefined;
50
+ }
51
+ export const tourSeenKey = (projectId) => `${TOUR_SEEN_KEY_PREFIX}${projectId}`;
52
+ export function openedByReticle(search) {
53
+ if (search === undefined || '' === search)
54
+ return false;
55
+ const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
56
+ return (params.has(RETICLE_URL_PARAM.OPENED) ||
57
+ params.has(RETICLE_URL_PARAM.SESSION) ||
58
+ params.has(RETICLE_URL_PARAM.PROJECT));
59
+ }
60
+ export function tourAlreadySeen(storage, projectId) {
61
+ if (undefined === storage)
62
+ return true;
63
+ try {
64
+ return null !== storage.getItem(tourSeenKey(projectId));
65
+ }
66
+ catch {
67
+ return true;
68
+ }
69
+ }
70
+ function markSeen(storage, projectId) {
71
+ try {
72
+ storage?.setItem(tourSeenKey(projectId), '1');
73
+ }
74
+ catch {
75
+ }
76
+ }
77
+ export function mountTour(deps) {
78
+ if (deps.isDriving())
79
+ return undefined;
80
+ if (openedByReticle(deps.search))
81
+ return undefined;
82
+ if (tourAlreadySeen(deps.storage, deps.projectId))
83
+ return undefined;
84
+ const doc = deps.document;
85
+ const style = doc.createElement('style');
86
+ style.setAttribute(TOUR_ATTR, '');
87
+ style.textContent = TOUR_CSS;
88
+ doc.head.appendChild(style);
89
+ const root = doc.createElement('div');
90
+ root.setAttribute(TOUR_ATTR, '');
91
+ const slides = tourSlides();
92
+ let index = 0;
93
+ let open = true;
94
+ let detachKeys = () => undefined;
95
+ const close = () => {
96
+ if (!open)
97
+ return;
98
+ open = false;
99
+ detachKeys();
100
+ markSeen(deps.storage, deps.projectId);
101
+ root.remove();
102
+ style.remove();
103
+ };
104
+ const draw = () => {
105
+ const slide = slides[index];
106
+ if (undefined === slide)
107
+ return;
108
+ root.innerHTML = `<div class="reticle-tour-scrim"></div>${slideHtml(slide)}`;
109
+ if (TourAnchor.NONE !== slide.anchor) {
110
+ const box = anchorBox(doc, slide.anchor);
111
+ if (undefined !== box && box.width > 0) {
112
+ const region = TourAnchor.APP === slide.anchor;
113
+ const ring = doc.createElement('div');
114
+ ring.className = region ? 'reticle-tour-ring is-region' : 'reticle-tour-ring';
115
+ const pad = region ? -2 : 6;
116
+ ring.style.left = `${String(Math.round(box.left - pad))}px`;
117
+ ring.style.top = `${String(Math.round(box.top - pad))}px`;
118
+ ring.style.width = `${String(Math.round(box.width + pad * 2))}px`;
119
+ ring.style.height = `${String(Math.round(box.height + pad * 2))}px`;
120
+ root.appendChild(ring);
121
+ if (!region)
122
+ root.querySelector('.reticle-tour-scrim')?.classList.add('is-clear');
123
+ if (isInteractive(slide)) {
124
+ ring.classList.add('is-live');
125
+ root.querySelector('.reticle-tour-scrim')?.remove();
126
+ const view = doc.defaultView;
127
+ for (const rect of holeRects({
128
+ left: box.left - pad,
129
+ top: box.top - pad,
130
+ width: box.width + pad * 2,
131
+ height: box.height + pad * 2,
132
+ }, view?.innerWidth ?? doc.documentElement.clientWidth, view?.innerHeight ?? doc.documentElement.clientHeight)) {
133
+ const blocker = doc.createElement('div');
134
+ blocker.className = 'reticle-tour-blocker';
135
+ blocker.style.left = `${String(Math.round(rect.left))}px`;
136
+ blocker.style.top = `${String(Math.round(rect.top))}px`;
137
+ blocker.style.width = `${String(Math.round(rect.width))}px`;
138
+ blocker.style.height = `${String(Math.round(rect.height))}px`;
139
+ root.appendChild(blocker);
140
+ }
141
+ }
142
+ }
143
+ }
144
+ };
145
+ const copyPrompt = (button) => {
146
+ const slide = slides[index];
147
+ const which = Number(button.getAttribute(TOUR_PROMPT_ATTR));
148
+ const prompt = slide?.prompts?.[which];
149
+ if (undefined === prompt)
150
+ return;
151
+ const flash = (label, mark) => {
152
+ button.textContent = label;
153
+ button.classList.add(mark);
154
+ doc.defaultView?.setTimeout(() => {
155
+ button.textContent = COPY_LABEL;
156
+ button.classList.remove(mark);
157
+ }, COPY_FLASH_MS);
158
+ };
159
+ const settle = (copied) => {
160
+ if (copied) {
161
+ flash(COPIED_LABEL, 'is-done');
162
+ return;
163
+ }
164
+ const text = root.querySelector(`.reticle-tour-prompt[${TOUR_PROMPT_ATTR}="${String(which)}"] .reticle-tour-prompt-text`);
165
+ if (null !== text)
166
+ deps.select?.(text);
167
+ flash(COPY_MANUAL_LABEL, 'is-manual');
168
+ };
169
+ const result = deps.copy?.(prompt.text);
170
+ if (undefined === result) {
171
+ settle(false);
172
+ return;
173
+ }
174
+ if ('boolean' === typeof result) {
175
+ settle(result);
176
+ return;
177
+ }
178
+ void result.then(settle).catch(() => {
179
+ settle(false);
180
+ });
181
+ };
182
+ root.addEventListener('click', (event) => {
183
+ const target = event.target;
184
+ if (!(target instanceof Element))
185
+ return;
186
+ const hit = target.closest(`[${TOUR_TARGET_ATTR}]`);
187
+ if (null === hit)
188
+ return;
189
+ const what = hit.getAttribute(TOUR_TARGET_ATTR);
190
+ if (null === what)
191
+ return;
192
+ event.preventDefault();
193
+ event.stopPropagation();
194
+ if ('skip' === what || 'done' === what) {
195
+ close();
196
+ return;
197
+ }
198
+ if ('copy' === what) {
199
+ copyPrompt(hit);
200
+ return;
201
+ }
202
+ const moved = nextIndex(index, 'back' === what ? -1 : 1);
203
+ if (moved === index)
204
+ return;
205
+ index = moved;
206
+ draw();
207
+ });
208
+ const onKey = (event) => {
209
+ if (!open)
210
+ return;
211
+ const step = 'ArrowRight' === event.key ? 1 : 'ArrowLeft' === event.key ? -1 : 0;
212
+ if ('Escape' === event.key) {
213
+ event.preventDefault();
214
+ close();
215
+ return;
216
+ }
217
+ if (0 === step)
218
+ return;
219
+ const moved = nextIndex(index, step);
220
+ if (moved === index)
221
+ return;
222
+ event.preventDefault();
223
+ index = moved;
224
+ draw();
225
+ };
226
+ doc.addEventListener('keydown', onKey);
227
+ detachKeys = () => {
228
+ doc.removeEventListener('keydown', onKey);
229
+ };
230
+ draw();
231
+ doc.body.appendChild(root);
232
+ return {
233
+ destroy: close,
234
+ isOpen: () => open,
235
+ };
236
+ }
237
+ export { isLastSlide };
238
+ export function safeLocalStorage() {
239
+ try {
240
+ return window.localStorage;
241
+ }
242
+ catch {
243
+ return undefined;
244
+ }
245
+ }
@@ -1,5 +1,6 @@
1
1
  import { ActionType, AnchorKind, AnnotationKind, DEGRADED_ANCHOR_ROLE, EventType, FLOW_FILE_VERSION, FlowStepTool, RecorderPhase, } from '@reticlehq/core';
2
2
  import { REDACTED_VALUE } from '@reticlehq/core';
3
+ import { sourceFromDom } from '../dom/addressing/source.js';
3
4
  import { getAccessibleName, getRole, isSensitiveField } from '../dom/a11y.js';
4
5
  import { isReticleOverlay } from '../dom/dom-ignore.js';
5
6
  import { getCapabilities } from '../registry/capabilities.js';
@@ -64,6 +65,11 @@ function buildStep(el, action, args) {
64
65
  step.args = args;
65
66
  if (degraded)
66
67
  step.degraded = true;
68
+ const source = sourceFromDom(el);
69
+ if (source !== undefined) {
70
+ step.source =
71
+ source.line === undefined ? { file: source.file } : { file: source.file, line: source.line };
72
+ }
67
73
  return step;
68
74
  }
69
75
  export function compileRecording(name, steps, annotations, createdAt, startPath) {
@@ -0,0 +1,2 @@
1
+ import { ChannelId } from '@reticlehq/core';
2
+ export declare function declaredChannels(): ChannelId[];
@@ -0,0 +1,14 @@
1
+ import { ChannelId } from '@reticlehq/core';
2
+ import { storeNames } from './stores.js';
3
+ export function declaredChannels() {
4
+ const fixed = [
5
+ ChannelId.UI,
6
+ ChannelId.NET,
7
+ ChannelId.LOG,
8
+ ChannelId.ROUTE,
9
+ ChannelId.STORAGE,
10
+ ChannelId.TIME,
11
+ ChannelId.SIGNAL,
12
+ ];
13
+ return storeNames().length > 0 ? [...fixed, ChannelId.STATE] : fixed;
14
+ }
@@ -0,0 +1,4 @@
1
+ import { PlatformProfile } from '@reticlehq/core';
2
+ export declare function declaredPlatform(): PlatformProfile;
3
+ export declare const SERVED_COMMANDS: readonly string[];
4
+ export declare function declaredCommands(): string[];
@@ -0,0 +1,34 @@
1
+ import { PlatformProfile, RETICLE_IPC_GLOBAL, ReticleCommand } from '@reticlehq/core';
2
+ const TAURI_INTERNALS_GLOBAL = '__TAURI_INTERNALS__';
3
+ export function declaredPlatform() {
4
+ if ('undefined' === typeof window)
5
+ return PlatformProfile.SERVICE;
6
+ const global = window;
7
+ const isDesktop = global[RETICLE_IPC_GLOBAL] !== undefined || global[TAURI_INTERNALS_GLOBAL] !== undefined;
8
+ return isDesktop ? PlatformProfile.WEBVIEW : PlatformProfile.WEB;
9
+ }
10
+ export const SERVED_COMMANDS = [
11
+ ReticleCommand.SNAPSHOT,
12
+ ReticleCommand.QUERY,
13
+ ReticleCommand.MATCH,
14
+ ReticleCommand.INSPECT,
15
+ ReticleCommand.ACT,
16
+ ReticleCommand.ACT_SEQUENCE,
17
+ ReticleCommand.ANIMATIONS,
18
+ ReticleCommand.NARRATE,
19
+ ReticleCommand.CLOCK,
20
+ ReticleCommand.CAPABILITIES,
21
+ ReticleCommand.STATE_READ,
22
+ ReticleCommand.STORAGE_READ,
23
+ ReticleCommand.SCROLL,
24
+ ReticleCommand.SESSION_CONFIG,
25
+ ReticleCommand.PRESENTER,
26
+ ReticleCommand.IMPACT,
27
+ ReticleCommand.CAPTURE,
28
+ ReticleCommand.NAVIGATE,
29
+ ReticleCommand.REFRESH,
30
+ ReticleCommand.FLOWS,
31
+ ];
32
+ export function declaredCommands() {
33
+ return [...SERVED_COMMANDS];
34
+ }
@@ -1,4 +1,4 @@
1
- import { reticle } from '../index.js';
1
+ import { reticle } from '../reticle-singleton.js';
2
2
  export function createReticleEmitter(options = {}) {
3
3
  const target = options.target ?? reticle;
4
4
  return {
@@ -1,4 +1,4 @@
1
- import { nativeWarn } from '../timers/native-console.js';
1
+ import { nativeWarn } from '../timers/console/native-console.js';
2
2
  import { markAdapterSource } from './stores.js';
3
3
  export function tanstackQueryStore(client) {
4
4
  const store = {
@@ -1,5 +1,5 @@
1
1
  import { RETICLE_RENDERS_STORE } from '@reticlehq/core';
2
- import { nativeWarn } from '../timers/native-console.js';
2
+ import { nativeWarn } from '../timers/console/native-console.js';
3
3
  import { sanitizeWithReport } from '../security/serialization.js';
4
4
  const globalStore = globalThis;
5
5
  const stores = (globalStore.__reticleStores ??= new Map());
@@ -1,9 +1,13 @@
1
1
  import { ReticleCommand, PresenterMode, QueryBy } from '@reticlehq/core';
2
- import { refs } from './dom/refs.js';
2
+ import { refs } from './dom/addressing/refs.js';
3
3
  import { describe } from './dom/a11y.js';
4
4
  export function str(value, fallback = '') {
5
5
  return 'string' === typeof value ? value : fallback;
6
6
  }
7
+ function text(value) {
8
+ const asString = str(value);
9
+ return '' === asString ? undefined : asString;
10
+ }
7
11
  export function refLabel(refId) {
8
12
  const el = refs.resolve(refId);
9
13
  if (!(el instanceof Element))
@@ -39,13 +43,13 @@ export function presentStatus(commandName, args = {}) {
39
43
  return target !== undefined ? `Finding ${target}` : 'Finding an element';
40
44
  }
41
45
  case ReticleCommand.INSPECT: {
42
- const ref = str(args['ref']);
46
+ const ref = text(args['ref']);
43
47
  return ref !== undefined ? `Inspecting ${refLabel(ref)}` : 'Inspecting an element';
44
48
  }
45
49
  case ReticleCommand.ANIMATIONS:
46
50
  return 'Reading animations';
47
51
  case ReticleCommand.STATE_READ: {
48
- const store = str(args['store']);
52
+ const store = text(args['store']);
49
53
  return store !== undefined ? `Reading state: ${store}` : 'Reading state';
50
54
  }
51
55
  case ReticleCommand.CAPABILITIES:
@@ -55,11 +59,11 @@ export function presentStatus(commandName, args = {}) {
55
59
  }
56
60
  }
57
61
  function queryTarget(q) {
58
- const testid = str(q['testid']) ?? (QueryBy.TESTID === str(q['by']) ? str(q['value']) : undefined);
62
+ const testid = text(q['testid']) ?? (QueryBy.TESTID === str(q['by']) ? text(q['value']) : undefined);
59
63
  if (testid !== undefined)
60
64
  return `[testid=${testid}]`;
61
- const name = str(q['name']);
62
- const value = str(q['value']) ?? str(q['text']) ?? str(q['label']) ?? str(q['role']);
65
+ const name = text(q['name']);
66
+ const value = text(q['value']) ?? text(q['text']) ?? text(q['label']) ?? text(q['role']);
63
67
  if (value !== undefined)
64
68
  return name !== undefined ? `"${value}" (${name})` : `"${value}"`;
65
69
  return name !== undefined ? `"${name}"` : undefined;
@@ -0,0 +1,2 @@
1
+ import { Reticle } from './reticle.js';
2
+ export declare const reticle: Reticle;
@@ -0,0 +1,3 @@
1
+ import { Reticle } from './reticle.js';
2
+ const globalStore = globalThis;
3
+ export const reticle = (globalStore.__reticleInstance ??= new Reticle());
package/dist/reticle.js CHANGED
@@ -1,20 +1,21 @@
1
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, newDocumentId, NO_EDITS_OBSERVED, } from '@reticlehq/core';
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';
3
3
  import { rememberSessionLabel } from './session-continuity.js';
4
4
  import { editEpoch } from './edit-epoch.js';
5
5
  import { createCommandRegistry, RELOAD_CACHE_BUST_PARAM, } from './commands/commands.js';
6
6
  import { Transport } from './transport/transport.js';
7
7
  import { unreachableMessage } from './transport/unreachable-message.js';
8
- import { adapterNames } from './registry/adapters.js';
8
+ import { adapterNames } from './registry/stores/adapters.js';
9
+ import { declaredChannels } from './registry/declared-channels.js';
10
+ import { declaredCommands, declaredPlatform } from './registry/declared-platform.js';
9
11
  import { registerCapabilities, setCapabilitiesListener, hasCapabilities, } from './registry/capabilities.js';
10
12
  import { installAllObservers, runTeardowns } from './observers/install-all.js';
11
- import { installOverlay } from './presenter/overlay.js';
12
- import { Presenter, LOG_KIND, LOG_RESULT, } from './presenter/presenter.js';
13
- import { getPresenterSettings } from './presenter/presenter-settings.js';
14
- import { actionVerb } from './presenter/presenter-verbs.js';
13
+ import { installOverlay } from './presenter/chrome/overlay.js';
14
+ import { LOG_KIND, LOG_RESULT } from './presenter/chrome/log-kinds.js';
15
+ import { actionVerb } from './presenter/chrome/presenter-verbs.js';
15
16
  import { str, refLabel, modeForCommand, presentStatus } from './reticle-presenter-helpers.js';
16
17
  import { resetClock } from './timers/clock.js';
17
- import { nativeWarn } from './timers/native-console.js';
18
+ import { nativeWarn } from './timers/console/native-console.js';
18
19
  import { installRecorder } from './recorder/recorder.js';
19
20
  import { Annotator } from './review/annotator.js';
20
21
  export function shouldBlockProduction(nodeEnv, allowInProduction) {
@@ -113,6 +114,10 @@ export class Reticle {
113
114
  #start = 0;
114
115
  #overlay;
115
116
  #presenter;
117
+ #pendingPushes = new Map();
118
+ #readPanelSettings;
119
+ #panelRequested = false;
120
+ #bridgeConnected = false;
116
121
  #recorder;
117
122
  #annotator;
118
123
  #eventCount = 0;
@@ -165,7 +170,10 @@ export class Reticle {
165
170
  url,
166
171
  hello: () => this.#hello(),
167
172
  handleCommand: (command) => this.#handleCommand(command),
168
- onConnected: () => this.#presenter?.sessionStart(),
173
+ onConnected: () => {
174
+ this.#bridgeConnected = true;
175
+ this.#presenter?.sessionStart();
176
+ },
169
177
  onConnectionLost: () => {
170
178
  resetClock();
171
179
  if (true === this.#presenter?.sessionActive) {
@@ -193,39 +201,18 @@ export class Reticle {
193
201
  this.#overlay = installOverlay();
194
202
  this.#overlay.update({ connected: true, events: 0 });
195
203
  }
196
- if (options.present !== false) {
197
- const presenterOptions = {};
198
- if (options.pace !== undefined)
199
- presenterOptions.paceMs = options.pace;
200
- if (options.narrationDwellMs !== undefined) {
201
- presenterOptions.narrationDwellMs = options.narrationDwellMs;
202
- }
203
- if (options.border !== undefined)
204
- presenterOptions.border = options.border;
205
- if (options.logMax !== undefined)
206
- presenterOptions.logMax = options.logMax;
207
- if (options.endedFadeMs !== undefined)
208
- presenterOptions.endedFadeMs = options.endedFadeMs;
209
- if (options.idleEndMs !== undefined)
210
- presenterOptions.idleEndMs = options.idleEndMs;
211
- presenterOptions.sessionId = this.#session;
212
- presenterOptions.onControl = (intent) => this.#emit(EventType.HUMAN_CONTROL, intent.text !== undefined
213
- ? { kind: intent.kind, text: intent.text }
214
- : { kind: intent.kind });
215
- this.#presenter = new Presenter(presenterOptions);
216
- this.#presenter.mount();
217
- }
204
+ if (options.present !== false)
205
+ this.#fetchAndShowPanel(options);
218
206
  if (true === options.recorder) {
219
207
  this.#recorder = installRecorder({ emit, now: () => Date.now() });
220
208
  this.#recorder.mount();
221
209
  }
222
210
  if (options.annotate ?? options.present !== false) {
223
- const presenter = this.#presenter;
224
211
  this.#annotator = new Annotator({
225
212
  emit,
226
213
  now: () => Date.now(),
227
- onMark: (mark) => presenter?.log(LOG_KIND.HUMAN, `🚩 #${String(mark.index)} ${mark.anchor}${mark.source !== undefined ? ` · ${mark.source}` : ''} — ${mark.note}`),
228
- shouldBlock: () => getPresenterSettings().blockPageInteractions,
214
+ onMark: (mark) => this.#presenter?.log(LOG_KIND.HUMAN, `🚩 #${String(mark.index)} ${mark.anchor}${mark.source !== undefined ? ` · ${mark.source}` : ''} — ${mark.note}`),
215
+ shouldBlock: () => this.#readPanelSettings?.().blockPageInteractions ?? true,
229
216
  });
230
217
  this.#annotator.mount();
231
218
  this.#presenter?.bindAnnotator(this.#annotator);
@@ -233,6 +220,67 @@ export class Reticle {
233
220
  this.#transport.connect();
234
221
  this.#connected = true;
235
222
  }
223
+ #fetchAndShowPanel(options) {
224
+ if (this.#panelRequested)
225
+ return;
226
+ this.#panelRequested = true;
227
+ void import('./presenter/presenter.js')
228
+ .then(({ Presenter }) => import('./presenter/presenter-settings.js').then((settings) => ({ Presenter, settings })))
229
+ .then(({ Presenter, settings }) => {
230
+ if (!this.#connected)
231
+ return;
232
+ this.#readPanelSettings = settings.getPresenterSettings;
233
+ const panelOptions = {};
234
+ if (options.pace !== undefined)
235
+ panelOptions.paceMs = options.pace;
236
+ if (options.narrationDwellMs !== undefined) {
237
+ panelOptions.narrationDwellMs = options.narrationDwellMs;
238
+ }
239
+ if (options.border !== undefined)
240
+ panelOptions.border = options.border;
241
+ if (options.logMax !== undefined)
242
+ panelOptions.logMax = options.logMax;
243
+ if (options.endedFadeMs !== undefined)
244
+ panelOptions.endedFadeMs = options.endedFadeMs;
245
+ if (options.idleEndMs !== undefined)
246
+ panelOptions.idleEndMs = options.idleEndMs;
247
+ panelOptions.sessionId = this.#session;
248
+ panelOptions.onControl = (intent) => this.#emit(EventType.HUMAN_CONTROL, intent.text !== undefined
249
+ ? { kind: intent.kind, text: intent.text }
250
+ : { kind: intent.kind });
251
+ const panel = new Presenter(panelOptions);
252
+ this.#presenter = panel;
253
+ panel.mount();
254
+ for (const buffered of this.#pendingPushes.values())
255
+ panel.handlePush(buffered);
256
+ this.#pendingPushes.clear();
257
+ void import('./presenter/tour/tour.js').then((tour) => {
258
+ tour.mountTour({
259
+ document,
260
+ storage: tour.safeLocalStorage(),
261
+ projectId: reticleParamsFromSearch(window.location.search).projectId ?? window.location.origin,
262
+ isDriving: () => PresenterMode.IDLE !== panel.mode,
263
+ search: window.location.search,
264
+ copy: (text) => navigator.clipboard?.writeText(text).then(() => true, () => false) ?? false,
265
+ select: (element) => {
266
+ const selection = window.getSelection();
267
+ if (null === selection)
268
+ return;
269
+ const range = document.createRange();
270
+ range.selectNodeContents(element);
271
+ selection.removeAllRanges();
272
+ selection.addRange(range);
273
+ },
274
+ });
275
+ });
276
+ if (this.#bridgeConnected)
277
+ panel.sessionStart();
278
+ if (this.#annotator !== undefined)
279
+ panel.bindAnnotator(this.#annotator);
280
+ })
281
+ .catch(() => {
282
+ });
283
+ }
236
284
  get connected() {
237
285
  return this.#connected;
238
286
  }
@@ -310,10 +358,18 @@ export class Reticle {
310
358
  adapters: adapterNames(),
311
359
  ...(this.#token === undefined ? {} : { token: this.#token }),
312
360
  hasCapabilities: hasCapabilities(),
361
+ channels: declaredChannels(),
362
+ platform: declaredPlatform(),
363
+ commands: declaredCommands(),
313
364
  captureBodies: this.#captureBodies,
314
365
  ...(this.#sourceMapping === undefined ? {} : { sourceMapping: this.#sourceMapping }),
315
366
  ...(this.#sdkVersion === undefined ? {} : { sdkVersion: this.#sdkVersion }),
316
367
  contract: CONTRACT_FINGERPRINT,
368
+ contractParts: {
369
+ commands: [...CONTRACT_PARTS.commands],
370
+ events: [...CONTRACT_PARTS.events],
371
+ actions: [...CONTRACT_PARTS.actions],
372
+ },
317
373
  ...(0 === this.#redactKeys.length ? {} : { redactKeys: this.#redactKeys }),
318
374
  };
319
375
  }
@@ -332,8 +388,12 @@ export class Reticle {
332
388
  if (command.name === ReticleCommand.PRESENTER ||
333
389
  command.name === ReticleCommand.FLOWS ||
334
390
  command.name === ReticleCommand.IMPACT) {
335
- this.#presenter?.handlePush(command);
336
- return { ok: true, result: { applied: this.#presenter !== undefined } };
391
+ if (this.#presenter === undefined) {
392
+ this.#pendingPushes.set(command.name, command);
393
+ return { ok: true, result: { applied: false, buffered: true } };
394
+ }
395
+ this.#presenter.handlePush(command);
396
+ return { ok: true, result: { applied: true } };
337
397
  }
338
398
  const handler = this.#registry.get(command.name);
339
399
  if (handler === undefined) {
@@ -1,4 +1,4 @@
1
- import { Z_OVERLAY } from '../presenter/presenter-styles.js';
1
+ import { Z_OVERLAY } from '../presenter/chrome/layers.js';
2
2
  const MARK_ATTR = 'data-reticle-mark';
3
3
  const sel = (role) => `[${MARK_ATTR}="${role}"]`;
4
4
  export const MARK_PLACEHOLDER = 'What should change?';
@@ -1,8 +1,8 @@
1
- import { isSyntheticInput } from '../actions/synthetic-input.js';
1
+ import { isSyntheticInput } from '../actions/synthetic/synthetic-input.js';
2
2
  import { EventType } from '@reticlehq/core';
3
3
  import { isReticleUi, isReticleOverlay } from '../dom/dom-ignore.js';
4
4
  import { resolveMarkAnchor } from './mark-anchor.js';
5
- import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
5
+ import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
6
6
  import { ANNOTATOR_CSS, ANNOTATOR_ROOT_HTML, MARK_CANCEL, MARK_PENDING_GLYPH, MARK_PLACEHOLDER, MARK_SUBMIT, } from './annotator-styles.js';
7
7
  const HIGHLIGHT_REST_MS = 130;
8
8
  const SHAKE_MS = 250;
@@ -1,8 +1,8 @@
1
1
  import { MarkAnchorStrategy } from '@reticlehq/core';
2
- import { AnchorStrategy, synthesizeAnchor } from '../dom/auto-anchor.js';
2
+ import { AnchorStrategy, synthesizeAnchor, } from '../dom/addressing/auto-anchor.js';
3
3
  import { getAccessibleName, getRole } from '../dom/a11y.js';
4
- import { identifyComponent } from '../registry/adapters.js';
5
- import { sourceFor } from '../dom/source.js';
4
+ import { identifyComponent } from '../registry/stores/adapters.js';
5
+ import { sourceFor } from '../dom/addressing/source.js';
6
6
  const TESTID_ATTR = 'data-testid';
7
7
  const STRATEGY = {
8
8
  [AnchorStrategy.TESTID]: MarkAnchorStrategy.TESTID,
@@ -1,6 +1,6 @@
1
1
  import { CHURN_TYPES, CommandMessageSchema, EventType, MessageKind, SESSION_LIFECYCLE, } from '@reticlehq/core';
2
- import { nativeSetTimeout, nativeNow } from '../timers/native-timers.js';
3
- import { nativeWarn } from '../timers/native-console.js';
2
+ import { nativeSetTimeout, nativeNow } from '../timers/native/native-timers.js';
3
+ import { nativeWarn } from '../timers/console/native-console.js';
4
4
  import { safeStringify } from '../security/serialization.js';
5
5
  function subscribeDocumentVisible(handler) {
6
6
  if ('undefined' === typeof document)