@reticlehq/browser 2.13.1 → 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 (244) hide show
  1. package/dist/actions/actions-dom.d.ts +8 -20
  2. package/dist/actions/actions-dom.js +70 -69
  3. package/dist/actions/actions-press.d.ts +14 -0
  4. package/dist/actions/actions-press.js +123 -0
  5. package/dist/actions/actions.d.ts +1 -89
  6. package/dist/actions/actions.js +64 -365
  7. package/dist/actions/appeared-text.d.ts +0 -20
  8. package/dist/actions/appeared-text.js +6 -36
  9. package/dist/actions/danger-context.d.ts +3 -0
  10. package/dist/actions/danger-context.js +39 -0
  11. package/dist/actions/scroll.d.ts +8 -10
  12. package/dist/actions/scroll.js +28 -17
  13. package/dist/actions/synthetic/synthetic-input.d.ts +2 -0
  14. package/dist/actions/synthetic/synthetic-input.js +13 -0
  15. package/dist/actions/value-input.d.ts +0 -30
  16. package/dist/actions/value-input.js +0 -33
  17. package/dist/commands/commands.d.ts +0 -2
  18. package/dist/commands/commands.js +10 -70
  19. package/dist/connect-options.d.ts +2 -96
  20. package/dist/dom/a11y.d.ts +0 -29
  21. package/dist/dom/a11y.js +3 -90
  22. package/dist/dom/addressing/auto-anchor.d.ts +25 -0
  23. package/dist/dom/addressing/auto-anchor.js +41 -0
  24. package/dist/dom/addressing/refs.d.ts +14 -0
  25. package/dist/dom/addressing/refs.js +114 -0
  26. package/dist/dom/addressing/source.d.ts +9 -0
  27. package/dist/dom/addressing/source.js +29 -0
  28. package/dist/dom/chart.d.ts +0 -61
  29. package/dist/dom/chart.js +0 -60
  30. package/dist/dom/desktop-capture.d.ts +0 -30
  31. package/dist/dom/desktop-capture.js +1 -73
  32. package/dist/dom/dom-ignore.d.ts +0 -30
  33. package/dist/dom/dom-ignore.js +1 -48
  34. package/dist/dom/occlusion.d.ts +0 -7
  35. package/dist/dom/occlusion.js +31 -12
  36. package/dist/dom/paint-context.d.ts +6 -0
  37. package/dist/dom/paint-context.js +38 -0
  38. package/dist/dom/query.d.ts +0 -14
  39. package/dist/dom/query.js +50 -216
  40. package/dist/dom/realm.d.ts +0 -20
  41. package/dist/dom/realm.js +0 -28
  42. package/dist/dom/shadow-registry.d.ts +0 -33
  43. package/dist/dom/shadow-registry.js +2 -72
  44. package/dist/dom/snapshot.d.ts +2 -43
  45. package/dist/dom/snapshot.js +27 -152
  46. package/dist/dom/theme.d.ts +0 -37
  47. package/dist/dom/theme.js +2 -52
  48. package/dist/dom/virtualized.d.ts +0 -17
  49. package/dist/dom/virtualized.js +0 -27
  50. package/dist/edit-epoch.d.ts +1 -23
  51. package/dist/edit-epoch.js +1 -53
  52. package/dist/index.d.ts +4 -5
  53. package/dist/index.js +4 -28
  54. package/dist/observers/animation.d.ts +0 -1
  55. package/dist/observers/animation.js +1 -4
  56. package/dist/observers/blind-spots.d.ts +0 -26
  57. package/dist/observers/blind-spots.js +3 -62
  58. package/dist/observers/console.d.ts +1 -2
  59. package/dist/observers/console.js +41 -30
  60. package/dist/observers/context-open.d.ts +1 -11
  61. package/dist/observers/context-open.js +10 -14
  62. package/dist/observers/departure.d.ts +3 -0
  63. package/dist/observers/departure.js +52 -0
  64. package/dist/observers/dialogs.d.ts +2 -0
  65. package/dist/observers/dialogs.js +39 -0
  66. package/dist/observers/dom.js +28 -52
  67. package/dist/observers/download.d.ts +1 -2
  68. package/dist/observers/download.js +39 -56
  69. package/dist/observers/focus.d.ts +0 -6
  70. package/dist/observers/focus.js +0 -7
  71. package/dist/observers/frames.d.ts +0 -9
  72. package/dist/observers/frames.js +1 -28
  73. package/dist/observers/health.d.ts +0 -5
  74. package/dist/observers/health.js +2 -34
  75. package/dist/observers/install-all.d.ts +2 -0
  76. package/dist/observers/install-all.js +26 -27
  77. package/dist/observers/ipc.d.ts +0 -25
  78. package/dist/observers/ipc.js +0 -65
  79. package/dist/observers/navigation.d.ts +2 -0
  80. package/dist/observers/navigation.js +33 -0
  81. package/dist/observers/net-detail/network-body.d.ts +12 -0
  82. package/dist/observers/net-detail/network-body.js +71 -0
  83. package/dist/observers/net-detail/network-redact.d.ts +1 -0
  84. package/dist/observers/net-detail/network-redact.js +1 -0
  85. package/dist/observers/network-stream.d.ts +0 -21
  86. package/dist/observers/network-stream.js +3 -46
  87. package/dist/observers/network.d.ts +3 -27
  88. package/dist/observers/network.js +178 -295
  89. package/dist/observers/perf.d.ts +0 -10
  90. package/dist/observers/perf.js +0 -16
  91. package/dist/observers/route.d.ts +1 -2
  92. package/dist/observers/route.js +13 -19
  93. package/dist/observers/scroll.d.ts +0 -1
  94. package/dist/observers/scroll.js +2 -11
  95. package/dist/observers/sdk-failure.d.ts +1 -9
  96. package/dist/observers/sdk-failure.js +1 -30
  97. package/dist/observers/state.d.ts +1 -11
  98. package/dist/observers/state.js +13 -41
  99. package/dist/observers/storage.d.ts +0 -14
  100. package/dist/observers/storage.js +10 -34
  101. package/dist/observers/types.d.ts +0 -15
  102. package/dist/observers/types.js +0 -14
  103. package/dist/patching/capture-method.d.ts +0 -24
  104. package/dist/patching/capture-method.js +0 -26
  105. package/dist/presenter/chrome/layers.d.ts +2 -0
  106. package/dist/presenter/chrome/layers.js +2 -0
  107. package/dist/presenter/chrome/log-kinds.d.ts +12 -0
  108. package/dist/presenter/chrome/log-kinds.js +7 -0
  109. package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -1
  110. package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -1
  111. package/dist/presenter/chrome/presenter-brand.d.ts +3 -0
  112. package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -21
  113. package/dist/presenter/chrome/presenter-effects.d.ts +4 -0
  114. package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -10
  115. package/dist/presenter/chrome/presenter-hud-chrome.d.ts +7 -0
  116. package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -10
  117. package/dist/presenter/chrome/presenter-log.d.ts +17 -0
  118. package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +5 -54
  119. package/dist/presenter/chrome/presenter-report-copy.d.ts +51 -0
  120. package/dist/presenter/chrome/presenter-report-copy.js +95 -0
  121. package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
  122. package/dist/presenter/chrome/presenter-safe-html.js +16 -0
  123. package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -5
  124. package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -5
  125. package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -7
  126. package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -8
  127. package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -8
  128. package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -6
  129. package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -6
  130. package/dist/presenter/presenter-account-styles.d.ts +1 -0
  131. package/dist/presenter/presenter-account-styles.js +99 -0
  132. package/dist/presenter/presenter-account.d.ts +40 -0
  133. package/dist/presenter/presenter-account.js +178 -0
  134. package/dist/presenter/presenter-config.d.ts +10 -107
  135. package/dist/presenter/presenter-config.js +5 -92
  136. package/dist/presenter/presenter-controls.d.ts +1 -35
  137. package/dist/presenter/presenter-controls.js +10 -133
  138. package/dist/presenter/presenter-dock-layout.d.ts +0 -3
  139. package/dist/presenter/presenter-dock-layout.js +1 -5
  140. package/dist/presenter/presenter-drag.d.ts +2 -42
  141. package/dist/presenter/presenter-drag.js +3 -76
  142. package/dist/presenter/presenter-glow.d.ts +0 -21
  143. package/dist/presenter/presenter-glow.js +2 -28
  144. package/dist/presenter/presenter-hud-position.d.ts +18 -0
  145. package/dist/presenter/presenter-hud-position.js +50 -0
  146. package/dist/presenter/presenter-report-styles.d.ts +0 -6
  147. package/dist/presenter/presenter-report-styles.js +22 -7
  148. package/dist/presenter/presenter-report.d.ts +5 -7
  149. package/dist/presenter/presenter-report.js +44 -95
  150. package/dist/presenter/presenter-run-state.d.ts +1 -8
  151. package/dist/presenter/presenter-run-state.js +1 -6
  152. package/dist/presenter/presenter-settings-styles.d.ts +0 -1
  153. package/dist/presenter/presenter-settings-styles.js +2 -2
  154. package/dist/presenter/presenter-settings.d.ts +5 -23
  155. package/dist/presenter/presenter-settings.js +21 -33
  156. package/dist/presenter/presenter-shell-styles.js +23 -20
  157. package/dist/presenter/presenter-shell.d.ts +6 -18
  158. package/dist/presenter/presenter-shell.js +26 -77
  159. package/dist/presenter/presenter-styles.d.ts +2 -10
  160. package/dist/presenter/presenter-styles.js +6 -12
  161. package/dist/presenter/presenter-workspace.d.ts +4 -4
  162. package/dist/presenter/presenter-workspace.js +30 -10
  163. package/dist/presenter/presenter.d.ts +3 -63
  164. package/dist/presenter/presenter.js +29 -132
  165. package/dist/presenter/tour/tour-view.d.ts +33 -0
  166. package/dist/presenter/tour/tour-view.js +188 -0
  167. package/dist/presenter/tour/tour.d.ts +25 -0
  168. package/dist/presenter/tour/tour.js +245 -0
  169. package/dist/recorder/recorder-styles.d.ts +0 -6
  170. package/dist/recorder/recorder-styles.js +0 -6
  171. package/dist/recorder/recorder.d.ts +0 -34
  172. package/dist/recorder/recorder.js +6 -49
  173. package/dist/registry/auto-testids.d.ts +0 -19
  174. package/dist/registry/auto-testids.js +1 -21
  175. package/dist/registry/capabilities.d.ts +0 -31
  176. package/dist/registry/capabilities.js +1 -46
  177. package/dist/registry/commit-and-signal.d.ts +0 -6
  178. package/dist/registry/commit-and-signal.js +1 -7
  179. package/dist/registry/declared-channels.d.ts +2 -0
  180. package/dist/registry/declared-channels.js +14 -0
  181. package/dist/registry/declared-platform.d.ts +4 -0
  182. package/dist/registry/declared-platform.js +34 -0
  183. package/dist/registry/domains.d.ts +0 -8
  184. package/dist/registry/domains.js +0 -7
  185. package/dist/registry/emitter.d.ts +0 -14
  186. package/dist/registry/emitter.js +1 -11
  187. package/dist/registry/store-adapters.d.ts +0 -186
  188. package/dist/registry/store-adapters.js +1 -164
  189. package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -10
  190. package/dist/registry/{adapters.js → stores/adapters.js} +0 -10
  191. package/dist/registry/stores.d.ts +0 -33
  192. package/dist/registry/stores.js +1 -60
  193. package/dist/reticle-presenter-helpers.d.ts +0 -13
  194. package/dist/reticle-presenter-helpers.js +10 -20
  195. package/dist/reticle-singleton.d.ts +2 -0
  196. package/dist/reticle-singleton.js +3 -0
  197. package/dist/reticle.d.ts +1 -52
  198. package/dist/reticle.js +115 -180
  199. package/dist/review/annotator-styles.js +1 -8
  200. package/dist/review/annotator.d.ts +2 -13
  201. package/dist/review/annotator.js +2 -42
  202. package/dist/review/mark-anchor.d.ts +0 -11
  203. package/dist/review/mark-anchor.js +3 -11
  204. package/dist/security/serialization.d.ts +0 -20
  205. package/dist/security/serialization.js +1 -66
  206. package/dist/session-continuity.d.ts +0 -16
  207. package/dist/session-continuity.js +1 -20
  208. package/dist/timers/clock.d.ts +1 -14
  209. package/dist/timers/clock.js +59 -60
  210. package/dist/timers/console/native-console.js +5 -0
  211. package/dist/timers/native/native-timers.d.ts +11 -0
  212. package/dist/timers/native/native-timers.js +48 -0
  213. package/dist/transport/transport.d.ts +0 -51
  214. package/dist/transport/transport.js +5 -131
  215. package/dist/transport/unreachable-message.d.ts +0 -17
  216. package/dist/transport/unreachable-message.js +0 -17
  217. package/dist/util/captured-method.d.ts +0 -35
  218. package/dist/util/captured-method.js +0 -35
  219. package/package.json +5 -4
  220. package/dist/actions/synthetic-input.d.ts +0 -9
  221. package/dist/actions/synthetic-input.js +0 -40
  222. package/dist/dom/auto-anchor.d.ts +0 -65
  223. package/dist/dom/auto-anchor.js +0 -74
  224. package/dist/dom/refs.d.ts +0 -90
  225. package/dist/dom/refs.js +0 -245
  226. package/dist/dom/source.d.ts +0 -53
  227. package/dist/dom/source.js +0 -74
  228. package/dist/observers/network-body.d.ts +0 -35
  229. package/dist/observers/network-body.js +0 -168
  230. package/dist/observers/network-redact.d.ts +0 -20
  231. package/dist/observers/network-redact.js +0 -80
  232. package/dist/presenter/presenter-brand.d.ts +0 -22
  233. package/dist/presenter/presenter-effects.d.ts +0 -13
  234. package/dist/presenter/presenter-hud-chrome.d.ts +0 -17
  235. package/dist/presenter/presenter-log.d.ts +0 -64
  236. package/dist/presenter/presenter-report-copy.d.ts +0 -112
  237. package/dist/presenter/presenter-report-copy.js +0 -156
  238. package/dist/presenter/presenter-test-helpers.d.ts +0 -16
  239. package/dist/presenter/presenter-test-helpers.js +0 -41
  240. package/dist/timers/native-console.js +0 -9
  241. package/dist/timers/native-timers.d.ts +0 -36
  242. package/dist/timers/native-timers.js +0 -78
  243. /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
  244. /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
@@ -0,0 +1,25 @@
1
+ import { isLastSlide } from './tour-view.js';
2
+ export declare const TOUR_SEEN_KEY_PREFIX = "reticle.tour.seen.";
3
+ export declare const tourSeenKey: (projectId: string) => string;
4
+ export interface TourStorage {
5
+ getItem: (key: string) => string | null;
6
+ setItem: (key: string, value: string) => void;
7
+ }
8
+ export interface TourDeps {
9
+ readonly document: Document;
10
+ readonly storage: TourStorage | undefined;
11
+ readonly projectId: string;
12
+ readonly isDriving: () => boolean;
13
+ readonly search?: string;
14
+ readonly copy?: (text: string) => Promise<boolean> | boolean;
15
+ readonly select?: (element: Element) => void;
16
+ }
17
+ export interface TourHandle {
18
+ readonly destroy: () => void;
19
+ readonly isOpen: () => boolean;
20
+ }
21
+ export declare function openedByReticle(search: string | undefined): boolean;
22
+ export declare function tourAlreadySeen(storage: TourStorage | undefined, projectId: string): boolean;
23
+ export declare function mountTour(deps: TourDeps): TourHandle | undefined;
24
+ export { isLastSlide };
25
+ export declare function safeLocalStorage(): TourStorage | undefined;
@@ -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,9 +1,3 @@
1
- /**
2
- * Inline style strings for the floating recorder toolbar. Browser-local UI
3
- * text (never crosses the wire), split out of recorder.ts to keep that file under the 500-line cap
4
- * (mirrors how presenter.ts extracts presenter-log.ts). All toolbar nodes also carry
5
- * data-reticle-overlay, so they are snapshot-excluded via dom-ignore.ts regardless of these styles.
6
- */
7
1
  export declare const TOOLBAR_CSS: string;
8
2
  export declare const BTN_CSS: string;
9
3
  export declare const NAME_CSS: string;
@@ -1,9 +1,3 @@
1
- /**
2
- * Inline style strings for the floating recorder toolbar. Browser-local UI
3
- * text (never crosses the wire), split out of recorder.ts to keep that file under the 500-line cap
4
- * (mirrors how presenter.ts extracts presenter-log.ts). All toolbar nodes also carry
5
- * data-reticle-overlay, so they are snapshot-excluded via dom-ignore.ts regardless of these styles.
6
- */
7
1
  export const TOOLBAR_CSS = [
8
2
  'position:fixed',
9
3
  'top:8px',
@@ -1,59 +1,25 @@
1
1
  import { AnnotationKind, EventType, RecorderPhase, type FlowAnchor, type FlowFile, type FlowStep } from '@reticlehq/core';
2
- /**
3
- * The human recorder. A floating toolbar (Record / Stop / Annotate) lets a
4
- * human click the golden path; capture-phase listeners map each real interaction to a SEMANTIC
5
- * anchor (testid → role+name → degraded placeholder, NEVER a volatile ref — invariant #1) and
6
- * append a FlowStep. Stop compiles a FlowFile in-page and emits ONE EventType.FLOW_RECORDED event
7
- * the server persists. Four structured annotation kinds compile into expect/dynamic/success.
8
- *
9
- * FIRST CUT scope: structured annotations only (the 4 AnnotationKind values via a menu + a signal
10
- * <select> drawn from registered capabilities). Explicitly FUTURE, not faked here:
11
- * - NL annotation → predicate compiler (CO-OWNED-FLOWS invariant #2 best-UX path).
12
- * - fixtures/preconditions (FlowFile.fixture stays a schema slot, never written).
13
- * - wait-for / ignore-region annotation kinds (only the 4 requested ship).
14
- */
15
- /** Browser-local UI text — never crosses the wire, mirrors presenter.ts CHIP_LABEL precedent. */
16
2
  export declare const RECORDER_EMPTY_MSG = "recorded 0 steps";
17
3
  interface RecorderDeps {
18
- /** Emit a wire event (Reticle.#emit bound). */
19
4
  emit: (type: EventType, data: Record<string, unknown>) => void;
20
- /** Injected clock for FlowFile.createdAt — never Date.now in pure logic (rule 7). */
21
5
  now: () => number;
22
- /** Default flow name when the toolbar field is blank. */
23
6
  defaultName?: string;
24
7
  }
25
8
  export interface RecorderHandle {
26
9
  mount(): void;
27
10
  destroy(): void;
28
- /** Test seam: current phase. */
29
11
  phase(): RecorderPhase;
30
- /** Test seam: steps captured so far in the active span. */
31
12
  steps(): FlowStep[];
32
13
  }
33
- /** A structured annotation captured while recording (compiled into flow fields at Stop). */
34
14
  export interface Annotation {
35
15
  kind: AnnotationKind;
36
16
  anchor: FlowAnchor;
37
- /** Required for ASSERT_SIGNAL / SUCCESS_STATE; the chosen capability signal name. */
38
17
  signal?: string;
39
18
  }
40
- /**
41
- * Pure: map a clicked/typed element → semantic FlowAnchor. testid wins; else role+accessible-name;
42
- * else the DEGRADED_ANCHOR_ROLE placeholder + degraded:true (never a ref — invariant #1).
43
- */
44
19
  export declare function anchorFor(el: Element): {
45
20
  anchor: FlowAnchor;
46
21
  degraded: boolean;
47
22
  };
48
- /**
49
- * Pure: assemble the in-page FlowFile from captured steps + annotations.
50
- * - ASSERT_SIGNAL → the most-recent step's expect.signal.
51
- * - ASSERT_VISIBLE → the most-recent step's expect.element (from the annotation anchor).
52
- * - MARK_DYNAMIC → push the anchor into flow.dynamic[].
53
- * - SUCCESS_STATE → flow.success = { signal }.
54
- * (FUTURE: per-step annotation targeting beyond the most-recent step.)
55
- */
56
23
  export declare function compileRecording(name: string, steps: FlowStep[], annotations: Annotation[], createdAt: number, startPath?: string): FlowFile;
57
- /** Install the floating recorder toolbar. Default off; gated by the host (reticle.ts recorder flag). */
58
24
  export declare function installRecorder(deps: RecorderDeps): RecorderHandle;
59
25
  export {};
@@ -1,23 +1,10 @@
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';
6
7
  import { TOOLBAR_CSS, BTN_CSS, NAME_CSS, STATUS_CSS, MENU_CSS } from './recorder-styles.js';
7
- /**
8
- * The human recorder. A floating toolbar (Record / Stop / Annotate) lets a
9
- * human click the golden path; capture-phase listeners map each real interaction to a SEMANTIC
10
- * anchor (testid → role+name → degraded placeholder, NEVER a volatile ref — invariant #1) and
11
- * append a FlowStep. Stop compiles a FlowFile in-page and emits ONE EventType.FLOW_RECORDED event
12
- * the server persists. Four structured annotation kinds compile into expect/dynamic/success.
13
- *
14
- * FIRST CUT scope: structured annotations only (the 4 AnnotationKind values via a menu + a signal
15
- * <select> drawn from registered capabilities). Explicitly FUTURE, not faked here:
16
- * - NL annotation → predicate compiler (CO-OWNED-FLOWS invariant #2 best-UX path).
17
- * - fixtures/preconditions (FlowFile.fixture stays a schema slot, never written).
18
- * - wait-for / ignore-region annotation kinds (only the 4 requested ship).
19
- */
20
- /** Browser-local UI text — never crosses the wire, mirrors presenter.ts CHIP_LABEL precedent. */
21
8
  export const RECORDER_EMPTY_MSG = 'recorded 0 steps';
22
9
  const STATUS_RECORDING = 'recording…';
23
10
  const STATUS_IDLE = 'ready';
@@ -38,16 +25,11 @@ const ANNOTATION_LABEL = {
38
25
  [AnnotationKind.SUCCESS_STATE]: 'success state',
39
26
  [AnnotationKind.INTENT]: 'intent',
40
27
  };
41
- /** Annotation kinds whose compilation needs a signal name (from the capabilities <select>). */
42
28
  const NEEDS_SIGNAL = new Set([
43
29
  AnnotationKind.ASSERT_SIGNAL,
44
30
  AnnotationKind.SUCCESS_STATE,
45
31
  ]);
46
32
  const DEFAULT_NAME = 'recorded-flow';
47
- /**
48
- * Pure: map a clicked/typed element → semantic FlowAnchor. testid wins; else role+accessible-name;
49
- * else the DEGRADED_ANCHOR_ROLE placeholder + degraded:true (never a ref — invariant #1).
50
- */
51
33
  export function anchorFor(el) {
52
34
  const testid = el.getAttribute(TESTID_ATTR);
53
35
  if (testid !== null && testid.length > 0) {
@@ -59,7 +41,6 @@ export function anchorFor(el) {
59
41
  const anchor = name.length > 0 ? { kind: AnchorKind.ROLE, role, name } : { kind: AnchorKind.ROLE, role };
60
42
  return { anchor, degraded: false };
61
43
  }
62
- // No resolvable testid/role/name — keep the step with a legible placeholder, never a ref.
63
44
  return { anchor: { kind: AnchorKind.ROLE, role: DEGRADED_ANCHOR_ROLE }, degraded: true };
64
45
  }
65
46
  function isTextbox(el) {
@@ -84,16 +65,13 @@ function buildStep(el, action, args) {
84
65
  step.args = args;
85
66
  if (degraded)
86
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
+ }
87
73
  return step;
88
74
  }
89
- /**
90
- * Pure: assemble the in-page FlowFile from captured steps + annotations.
91
- * - ASSERT_SIGNAL → the most-recent step's expect.signal.
92
- * - ASSERT_VISIBLE → the most-recent step's expect.element (from the annotation anchor).
93
- * - MARK_DYNAMIC → push the anchor into flow.dynamic[].
94
- * - SUCCESS_STATE → flow.success = { signal }.
95
- * (FUTURE: per-step annotation targeting beyond the most-recent step.)
96
- */
97
75
  export function compileRecording(name, steps, annotations, createdAt, startPath) {
98
76
  const out = steps.map((s) => ({ ...s }));
99
77
  const dynamic = [];
@@ -137,12 +115,10 @@ function anchorToElement(anchor) {
137
115
  }
138
116
  return {};
139
117
  }
140
- /** Live recorder instance: holds the capture buffer, toolbar DOM, and the phase machine. */
141
118
  class Recorder {
142
119
  #deps;
143
120
  #phase = RecorderPhase.IDLE;
144
121
  #steps = [];
145
- /** The pathname the recording began on, so replay can navigate here before step 1. */
146
122
  #startPath;
147
123
  #annotations = [];
148
124
  #pendingFill;
@@ -150,7 +126,6 @@ class Recorder {
150
126
  #root;
151
127
  #statusEl;
152
128
  #menuEl;
153
- /** The annotation being assembled in ANNOTATING phase. */
154
129
  #draft;
155
130
  constructor(deps) {
156
131
  this.#deps = deps;
@@ -183,7 +158,6 @@ class Recorder {
183
158
  this.#draft = undefined;
184
159
  this.#pendingFill = undefined;
185
160
  }
186
- // ---- capture ----
187
161
  #installCapture() {
188
162
  const onClick = (ev) => this.#onClick(ev);
189
163
  const onInput = (ev) => this.#onInput(ev);
@@ -200,7 +174,6 @@ class Recorder {
200
174
  document.removeEventListener('submit', onSubmit, true);
201
175
  });
202
176
  }
203
- /** True if the event should be ignored (toolbar self-click or non-element target). */
204
177
  #ignore(ev) {
205
178
  const target = ev.target;
206
179
  if (!(target instanceof Element))
@@ -219,9 +192,6 @@ class Recorder {
219
192
  }
220
193
  if (this.#phase !== RecorderPhase.RECORDING)
221
194
  return;
222
- // Controls that emit `change` (checkbox, radio, select) are recorded by #onChange as their
223
- // semantic step (CHECK/UNCHECK/SELECT). Recording a CLICK here too would replay any onChange
224
- // side effect twice (analytics, dependent-field resets), so skip the duplicate.
225
195
  if ((target instanceof HTMLInputElement &&
226
196
  ('checkbox' === inputRole(target) || 'radio' === inputRole(target))) ||
227
197
  target instanceof HTMLSelectElement) {
@@ -236,7 +206,6 @@ class Recorder {
236
206
  const target = this.#ignore(ev);
237
207
  if (target === undefined || !isTextbox(target))
238
208
  return;
239
- // Debounce per-keystroke input: keep the latest value, flush on change/blur/next-act.
240
209
  this.#pendingFill = { el: target, value: target.value };
241
210
  }
242
211
  #onChange(ev) {
@@ -279,12 +248,9 @@ class Recorder {
279
248
  if (pending === undefined)
280
249
  return;
281
250
  this.#pendingFill = undefined;
282
- // Never persist a typed secret into a git-checked flow file: password/OTP/cc/sensitive-keyed
283
- // fields record REDACTED_VALUE, the same redaction live snapshots apply (dom/a11y.ts getValue).
284
251
  const value = isSensitiveField(pending.el) ? REDACTED_VALUE : pending.value;
285
252
  this.#steps.push(buildStep(pending.el, ActionType.FILL, { value }));
286
253
  }
287
- // ---- annotation ----
288
254
  #captureAnnotationTarget(target) {
289
255
  const draft = this.#draft;
290
256
  if (draft === undefined)
@@ -299,7 +265,6 @@ class Recorder {
299
265
  this.#setPhase(RecorderPhase.RECORDING);
300
266
  this.#closeMenu();
301
267
  }
302
- /** Annotate-on-prior: confirm the most-recent step as the target (no extra click needed). */
303
268
  #confirmAnnotationOnPrior() {
304
269
  const draft = this.#draft;
305
270
  if (draft === undefined)
@@ -325,9 +290,7 @@ class Recorder {
325
290
  const value = select?.value ?? '';
326
291
  return value.length > 0 ? value : undefined;
327
292
  }
328
- // ---- toolbar lifecycle actions ----
329
293
  #start() {
330
- // A fresh span — no leakage from a previous Record→Stop cycle.
331
294
  this.#steps = [];
332
295
  this.#startPath = 'undefined' === typeof location ? undefined : location.pathname;
333
296
  this.#annotations = [];
@@ -355,7 +318,6 @@ class Recorder {
355
318
  #setPhase(phase) {
356
319
  this.#phase = phase;
357
320
  }
358
- // ---- toolbar DOM (all nodes data-reticle-overlay → snapshot-excluded via dom-ignore.ts) ----
359
321
  #buildToolbar() {
360
322
  const root = document.createElement('div');
361
323
  root.setAttribute('data-reticle-overlay', '');
@@ -396,9 +358,6 @@ class Recorder {
396
358
  return;
397
359
  menu.textContent = '';
398
360
  for (const kind of Object.values(AnnotationKind)) {
399
- // INTENT is free-text (the business goal), not a signal/testid pick — authored via
400
- // reticle_annotate { kind:'intent', text }. The human toolbar's name field is signal/testid only,
401
- // so intent is omitted from this menu rather than shipped as a control that can't capture text.
402
361
  if (kind === AnnotationKind.INTENT)
403
362
  continue;
404
363
  const item = document.createElement('button');
@@ -426,7 +385,6 @@ class Recorder {
426
385
  }
427
386
  menu.appendChild(select);
428
387
  }
429
- // "Confirm on prior step" path (annotate-on-prior); or the next click selects the target.
430
388
  const confirm = document.createElement('button');
431
389
  confirm.setAttribute('data-reticle-action', 'annotate-confirm');
432
390
  confirm.textContent = 'on prior step';
@@ -448,7 +406,6 @@ class Recorder {
448
406
  return value.length > 0 ? value : undefined;
449
407
  }
450
408
  }
451
- /** Install the floating recorder toolbar. Default off; gated by the host (reticle.ts recorder flag). */
452
409
  export function installRecorder(deps) {
453
410
  return new Recorder(deps);
454
411
  }
@@ -1,21 +1,2 @@
1
- /**
2
- * The testids the page is actually showing, read from the live DOM.
3
- *
4
- * `registerCapabilities({ testids: [...] })` used to be the only source, and `init` filled it by
5
- * grepping the source at install time. That list is a snapshot of the codebase on the day `init` ran:
6
- * it goes stale on the next commit that renames an element, it is empty for every app whose testids
7
- * arrive with a lazy route, and finishing it was homework handed to whoever ran the install.
8
- *
9
- * The attribute in the DOM is the same declaration, read fresh, so nothing has to be copied into a
10
- * config file to be true. Reticle's own presenter markup is excluded — advertising our own controls
11
- * as the app's testable surface would be the tool describing itself.
12
- */
13
- /**
14
- * Ceiling on the reported list.
15
- *
16
- * A virtualized 500-row grid stamps a testid per cell, and the capabilities payload rides in every
17
- * announce — an uncapped read turns one long table into a permanently expensive message. The cap is
18
- * on the REPORT, not on what can be driven: a testid past it is still findable by query.
19
- */
20
1
  export declare const MAX_AUTO_TESTIDS = 200;
21
2
  export declare function domTestids(doc: Document): string[];
@@ -1,26 +1,6 @@
1
- /**
2
- * The testids the page is actually showing, read from the live DOM.
3
- *
4
- * `registerCapabilities({ testids: [...] })` used to be the only source, and `init` filled it by
5
- * grepping the source at install time. That list is a snapshot of the codebase on the day `init` ran:
6
- * it goes stale on the next commit that renames an element, it is empty for every app whose testids
7
- * arrive with a lazy route, and finishing it was homework handed to whoever ran the install.
8
- *
9
- * The attribute in the DOM is the same declaration, read fresh, so nothing has to be copied into a
10
- * config file to be true. Reticle's own presenter markup is excluded — advertising our own controls
11
- * as the app's testable surface would be the tool describing itself.
12
- */
13
1
  const TESTID_ATTR = 'data-testid';
14
2
  const TESTID_SELECTOR = `[${TESTID_ATTR}]`;
15
- /**
16
- * Ceiling on the reported list.
17
- *
18
- * A virtualized 500-row grid stamps a testid per cell, and the capabilities payload rides in every
19
- * announce — an uncapped read turns one long table into a permanently expensive message. The cap is
20
- * on the REPORT, not on what can be driven: a testid past it is still findable by query.
21
- */
22
3
  export const MAX_AUTO_TESTIDS = 200;
23
- /** Reticle's own UI carries testids; they are not the host app's surface. */
24
4
  const RETICLE_OWN_PREFIX = 'reticle-';
25
5
  export function domTestids(doc) {
26
6
  const out = [];
@@ -30,7 +10,7 @@ export function domTestids(doc) {
30
10
  nodes = doc.querySelectorAll(TESTID_SELECTOR);
31
11
  }
32
12
  catch {
33
- return out; // a document that cannot be queried (detached, hostile) simply advertises nothing
13
+ return out;
34
14
  }
35
15
  for (let i = 0; i < nodes.length && out.length < MAX_AUTO_TESTIDS; i += 1) {
36
16
  const value = nodes[i]?.getAttribute(TESTID_ATTR);
@@ -1,4 +1,3 @@
1
- /** Self-describing capability registry — the testable surface the app advertises. */
2
1
  export interface CapabilityFlow {
3
2
  name: string;
4
3
  steps: string[];
@@ -8,46 +7,16 @@ export interface Capabilities {
8
7
  signals: string[];
9
8
  stores: string[];
10
9
  flows: CapabilityFlow[];
11
- /**
12
- * True when Reticle's OWN presenter is visible to snapshots and queries.
13
- *
14
- * Present only when the hatch is open, and reported here rather than left implicit, because a
15
- * verdict drawn against Reticle's own interface is not an ordinary verdict: an agent that can see
16
- * the impact panel can also assert against it. Anybody reading a result should be able to tell
17
- * which kind they are holding without going to look at a build config.
18
- */
19
10
  presenterExposed?: boolean;
20
11
  }
21
- /** What the host app passes to reticle.describe; all fields optional. */
22
12
  export interface CapabilitiesInput {
23
13
  testids?: string[];
24
14
  signals?: string[];
25
15
  stores?: string[];
26
16
  flows?: CapabilityFlow[];
27
17
  }
28
- /** Set by the SDK at connect. Idempotent; the last connect wins. */
29
18
  export declare function setCapabilitiesListener(cb: (() => void) | undefined): void;
30
- /** Called by the host app via reticle.describe. Merges (idempotent), never replaces wholesale. */
31
19
  export declare function registerCapabilities(input: CapabilitiesInput): void;
32
- /** Snapshot copy of the registered capabilities (defensive — never hand out the live arrays). */
33
20
  export declare function getCapabilities(): Capabilities;
34
- /**
35
- * Testids the app DECLARED, without the ones merely observed in the DOM.
36
- *
37
- * The difference carries meaning the merged list cannot. `knownEmptyState` asks "did the app NAME
38
- * this element as part of its testable surface?", which is how a zero-match query against
39
- * `cart-empty-region` is told apart from a broken selector. Every testid on the page is observed, so
40
- * answering that over the merged list makes it true whenever the page has any testid at all — the
41
- * same as not asking.
42
- */
43
21
  export declare function declaredTestids(): string[];
44
- /**
45
- * Whether the app has a testable surface at all (used in the HELLO flag).
46
- *
47
- * Answered over the OBSERVED surface as well as the declared one. It used to be answerable only by
48
- * declaration, so an app with two hundred testids in its DOM and a live Redux store reported
49
- * `hasCapabilities: false` until somebody typed those facts into a config file — and the agent
50
- * reading that flag concluded, correctly by the flag and wrongly in fact, that there was nothing to
51
- * drive.
52
- */
53
22
  export declare function hasCapabilities(): boolean;