@reticlehq/browser 2.2.1 → 2.4.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 (273) hide show
  1. package/dist/actions/actions-dom.d.ts +0 -1
  2. package/dist/actions/actions-dom.js +0 -1
  3. package/dist/actions/actions.d.ts +3 -1
  4. package/dist/actions/actions.js +55 -36
  5. package/dist/actions/scroll.d.ts +0 -1
  6. package/dist/actions/scroll.js +0 -1
  7. package/dist/actions/value-input.d.ts +33 -0
  8. package/dist/actions/value-input.js +63 -0
  9. package/dist/commands/commands.d.ts +0 -1
  10. package/dist/commands/commands.js +27 -7
  11. package/dist/connect-options.d.ts +97 -0
  12. package/dist/connect-options.js +1 -0
  13. package/dist/dom/a11y.d.ts +0 -2
  14. package/dist/dom/a11y.js +54 -24
  15. package/dist/dom/auto-anchor.d.ts +0 -1
  16. package/dist/dom/auto-anchor.js +0 -1
  17. package/dist/dom/blind-spots.d.ts +0 -1
  18. package/dist/dom/blind-spots.js +0 -1
  19. package/dist/dom/chart.d.ts +0 -1
  20. package/dist/dom/chart.js +0 -1
  21. package/dist/dom/desktop-capture.d.ts +36 -0
  22. package/dist/dom/desktop-capture.js +113 -0
  23. package/dist/dom/dom-ignore.d.ts +7 -1
  24. package/dist/dom/dom-ignore.js +1 -2
  25. package/dist/dom/occlusion.d.ts +0 -1
  26. package/dist/dom/occlusion.js +0 -1
  27. package/dist/dom/query.d.ts +0 -1
  28. package/dist/dom/query.js +61 -13
  29. package/dist/dom/realm.d.ts +30 -0
  30. package/dist/dom/realm.js +58 -0
  31. package/dist/dom/refs.d.ts +0 -1
  32. package/dist/dom/refs.js +0 -1
  33. package/dist/dom/shadow-registry.d.ts +40 -0
  34. package/dist/dom/shadow-registry.js +165 -0
  35. package/dist/dom/snapshot.d.ts +2 -2
  36. package/dist/dom/snapshot.js +21 -4
  37. package/dist/dom/source.d.ts +0 -1
  38. package/dist/dom/source.js +0 -1
  39. package/dist/dom/theme.d.ts +0 -1
  40. package/dist/dom/theme.js +0 -1
  41. package/dist/dom/virtualized.d.ts +19 -0
  42. package/dist/dom/virtualized.js +80 -0
  43. package/dist/index.d.ts +3 -4
  44. package/dist/index.js +2 -3
  45. package/dist/observers/animation.d.ts +0 -1
  46. package/dist/observers/animation.js +0 -1
  47. package/dist/observers/blind-spots.d.ts +16 -4
  48. package/dist/observers/blind-spots.js +65 -11
  49. package/dist/observers/console.d.ts +0 -1
  50. package/dist/observers/console.js +0 -1
  51. package/dist/observers/dom.d.ts +0 -2
  52. package/dist/observers/dom.js +67 -9
  53. package/dist/observers/download.d.ts +6 -0
  54. package/dist/observers/download.js +103 -0
  55. package/dist/observers/focus.d.ts +0 -1
  56. package/dist/observers/focus.js +0 -1
  57. package/dist/observers/frames.d.ts +12 -0
  58. package/dist/observers/frames.js +67 -0
  59. package/dist/observers/health.d.ts +0 -1
  60. package/dist/observers/health.js +30 -2
  61. package/dist/observers/install-all.d.ts +5 -0
  62. package/dist/observers/install-all.js +63 -0
  63. package/dist/observers/ipc.d.ts +22 -0
  64. package/dist/observers/ipc.js +121 -0
  65. package/dist/observers/network-body.d.ts +17 -1
  66. package/dist/observers/network-body.js +26 -1
  67. package/dist/observers/network-redact.d.ts +0 -1
  68. package/dist/observers/network-redact.js +6 -3
  69. package/dist/observers/network-stream.d.ts +23 -0
  70. package/dist/observers/network-stream.js +102 -0
  71. package/dist/observers/network.d.ts +11 -1
  72. package/dist/observers/network.js +7 -1
  73. package/dist/observers/perf.d.ts +0 -1
  74. package/dist/observers/perf.js +0 -1
  75. package/dist/observers/route.d.ts +0 -1
  76. package/dist/observers/route.js +0 -1
  77. package/dist/observers/scroll.d.ts +0 -1
  78. package/dist/observers/scroll.js +0 -1
  79. package/dist/observers/sdk-failure.d.ts +28 -0
  80. package/dist/observers/sdk-failure.js +60 -0
  81. package/dist/observers/state.d.ts +0 -1
  82. package/dist/observers/state.js +0 -1
  83. package/dist/observers/storage.d.ts +0 -1
  84. package/dist/observers/storage.js +0 -1
  85. package/dist/observers/types.d.ts +0 -1
  86. package/dist/observers/types.js +0 -1
  87. package/dist/patching/capture-method.d.ts +0 -1
  88. package/dist/patching/capture-method.js +0 -1
  89. package/dist/presenter/overlay.d.ts +0 -1
  90. package/dist/presenter/overlay.js +0 -1
  91. package/dist/presenter/presenter-config.d.ts +0 -1
  92. package/dist/presenter/presenter-config.js +0 -1
  93. package/dist/presenter/presenter-controls.d.ts +0 -1
  94. package/dist/presenter/presenter-controls.js +0 -1
  95. package/dist/presenter/presenter-effects.d.ts +0 -1
  96. package/dist/presenter/presenter-effects.js +0 -1
  97. package/dist/presenter/presenter-glow.d.ts +0 -1
  98. package/dist/presenter/presenter-glow.js +0 -1
  99. package/dist/presenter/presenter-log.d.ts +0 -1
  100. package/dist/presenter/presenter-log.js +0 -1
  101. package/dist/presenter/presenter-run-state.d.ts +0 -1
  102. package/dist/presenter/presenter-run-state.js +0 -1
  103. package/dist/presenter/presenter-styles.d.ts +0 -1
  104. package/dist/presenter/presenter-styles.js +0 -1
  105. package/dist/presenter/presenter-tally.d.ts +0 -1
  106. package/dist/presenter/presenter-tally.js +0 -1
  107. package/dist/presenter/presenter-test-helpers.d.ts +0 -1
  108. package/dist/presenter/presenter-test-helpers.js +0 -1
  109. package/dist/presenter/presenter-verbs.d.ts +0 -1
  110. package/dist/presenter/presenter-verbs.js +0 -1
  111. package/dist/presenter/presenter.d.ts +0 -1
  112. package/dist/presenter/presenter.js +0 -1
  113. package/dist/recorder/recorder-styles.d.ts +0 -1
  114. package/dist/recorder/recorder-styles.js +0 -1
  115. package/dist/recorder/recorder.d.ts +0 -1
  116. package/dist/recorder/recorder.js +0 -1
  117. package/dist/registry/adapters.d.ts +0 -1
  118. package/dist/registry/adapters.js +0 -1
  119. package/dist/registry/capabilities.d.ts +2 -1
  120. package/dist/registry/capabilities.js +16 -1
  121. package/dist/registry/commit-and-signal.d.ts +0 -1
  122. package/dist/registry/commit-and-signal.js +0 -1
  123. package/dist/registry/domains.d.ts +0 -1
  124. package/dist/registry/domains.js +0 -1
  125. package/dist/registry/emitter.d.ts +0 -1
  126. package/dist/registry/emitter.js +0 -1
  127. package/dist/registry/store-adapters.d.ts +141 -7
  128. package/dist/registry/store-adapters.js +178 -1
  129. package/dist/registry/stores.d.ts +0 -1
  130. package/dist/registry/stores.js +8 -1
  131. package/dist/reticle-presenter-helpers.d.ts +0 -1
  132. package/dist/reticle-presenter-helpers.js +0 -1
  133. package/dist/reticle.d.ts +5 -59
  134. package/dist/reticle.js +37 -33
  135. package/dist/review/annotator.d.ts +0 -1
  136. package/dist/review/annotator.js +0 -1
  137. package/dist/review/mark-anchor.d.ts +0 -1
  138. package/dist/review/mark-anchor.js +0 -1
  139. package/dist/security/serialization.d.ts +0 -1
  140. package/dist/security/serialization.js +0 -1
  141. package/dist/timers/clock.d.ts +0 -1
  142. package/dist/timers/clock.js +0 -1
  143. package/dist/timers/native-console.d.ts +0 -1
  144. package/dist/timers/native-console.js +0 -1
  145. package/dist/timers/native-timers.d.ts +0 -1
  146. package/dist/timers/native-timers.js +0 -1
  147. package/dist/transport/transport.d.ts +12 -6
  148. package/dist/transport/transport.js +107 -17
  149. package/package.json +13 -3
  150. package/dist/actions/actions-dom.d.ts.map +0 -1
  151. package/dist/actions/actions-dom.js.map +0 -1
  152. package/dist/actions/actions.d.ts.map +0 -1
  153. package/dist/actions/actions.js.map +0 -1
  154. package/dist/actions/scroll.d.ts.map +0 -1
  155. package/dist/actions/scroll.js.map +0 -1
  156. package/dist/commands/commands.d.ts.map +0 -1
  157. package/dist/commands/commands.js.map +0 -1
  158. package/dist/dom/a11y.d.ts.map +0 -1
  159. package/dist/dom/a11y.js.map +0 -1
  160. package/dist/dom/auto-anchor.d.ts.map +0 -1
  161. package/dist/dom/auto-anchor.js.map +0 -1
  162. package/dist/dom/blind-spots.d.ts.map +0 -1
  163. package/dist/dom/blind-spots.js.map +0 -1
  164. package/dist/dom/chart.d.ts.map +0 -1
  165. package/dist/dom/chart.js.map +0 -1
  166. package/dist/dom/dom-ignore.d.ts.map +0 -1
  167. package/dist/dom/dom-ignore.js.map +0 -1
  168. package/dist/dom/occlusion.d.ts.map +0 -1
  169. package/dist/dom/occlusion.js.map +0 -1
  170. package/dist/dom/query.d.ts.map +0 -1
  171. package/dist/dom/query.js.map +0 -1
  172. package/dist/dom/refs.d.ts.map +0 -1
  173. package/dist/dom/refs.js.map +0 -1
  174. package/dist/dom/snapshot.d.ts.map +0 -1
  175. package/dist/dom/snapshot.js.map +0 -1
  176. package/dist/dom/source.d.ts.map +0 -1
  177. package/dist/dom/source.js.map +0 -1
  178. package/dist/dom/theme.d.ts.map +0 -1
  179. package/dist/dom/theme.js.map +0 -1
  180. package/dist/index.d.ts.map +0 -1
  181. package/dist/index.js.map +0 -1
  182. package/dist/observers/animation.d.ts.map +0 -1
  183. package/dist/observers/animation.js.map +0 -1
  184. package/dist/observers/blind-spots.d.ts.map +0 -1
  185. package/dist/observers/blind-spots.js.map +0 -1
  186. package/dist/observers/console.d.ts.map +0 -1
  187. package/dist/observers/console.js.map +0 -1
  188. package/dist/observers/dom.d.ts.map +0 -1
  189. package/dist/observers/dom.js.map +0 -1
  190. package/dist/observers/focus.d.ts.map +0 -1
  191. package/dist/observers/focus.js.map +0 -1
  192. package/dist/observers/health.d.ts.map +0 -1
  193. package/dist/observers/health.js.map +0 -1
  194. package/dist/observers/network-body.d.ts.map +0 -1
  195. package/dist/observers/network-body.js.map +0 -1
  196. package/dist/observers/network-redact.d.ts.map +0 -1
  197. package/dist/observers/network-redact.js.map +0 -1
  198. package/dist/observers/network.d.ts.map +0 -1
  199. package/dist/observers/network.js.map +0 -1
  200. package/dist/observers/perf.d.ts.map +0 -1
  201. package/dist/observers/perf.js.map +0 -1
  202. package/dist/observers/route.d.ts.map +0 -1
  203. package/dist/observers/route.js.map +0 -1
  204. package/dist/observers/scroll.d.ts.map +0 -1
  205. package/dist/observers/scroll.js.map +0 -1
  206. package/dist/observers/state.d.ts.map +0 -1
  207. package/dist/observers/state.js.map +0 -1
  208. package/dist/observers/storage.d.ts.map +0 -1
  209. package/dist/observers/storage.js.map +0 -1
  210. package/dist/observers/types.d.ts.map +0 -1
  211. package/dist/observers/types.js.map +0 -1
  212. package/dist/patching/capture-method.d.ts.map +0 -1
  213. package/dist/patching/capture-method.js.map +0 -1
  214. package/dist/presenter/overlay.d.ts.map +0 -1
  215. package/dist/presenter/overlay.js.map +0 -1
  216. package/dist/presenter/presenter-config.d.ts.map +0 -1
  217. package/dist/presenter/presenter-config.js.map +0 -1
  218. package/dist/presenter/presenter-controls.d.ts.map +0 -1
  219. package/dist/presenter/presenter-controls.js.map +0 -1
  220. package/dist/presenter/presenter-effects.d.ts.map +0 -1
  221. package/dist/presenter/presenter-effects.js.map +0 -1
  222. package/dist/presenter/presenter-glow.d.ts.map +0 -1
  223. package/dist/presenter/presenter-glow.js.map +0 -1
  224. package/dist/presenter/presenter-log.d.ts.map +0 -1
  225. package/dist/presenter/presenter-log.js.map +0 -1
  226. package/dist/presenter/presenter-run-state.d.ts.map +0 -1
  227. package/dist/presenter/presenter-run-state.js.map +0 -1
  228. package/dist/presenter/presenter-styles.d.ts.map +0 -1
  229. package/dist/presenter/presenter-styles.js.map +0 -1
  230. package/dist/presenter/presenter-tally.d.ts.map +0 -1
  231. package/dist/presenter/presenter-tally.js.map +0 -1
  232. package/dist/presenter/presenter-test-helpers.d.ts.map +0 -1
  233. package/dist/presenter/presenter-test-helpers.js.map +0 -1
  234. package/dist/presenter/presenter-verbs.d.ts.map +0 -1
  235. package/dist/presenter/presenter-verbs.js.map +0 -1
  236. package/dist/presenter/presenter.d.ts.map +0 -1
  237. package/dist/presenter/presenter.js.map +0 -1
  238. package/dist/recorder/recorder-styles.d.ts.map +0 -1
  239. package/dist/recorder/recorder-styles.js.map +0 -1
  240. package/dist/recorder/recorder.d.ts.map +0 -1
  241. package/dist/recorder/recorder.js.map +0 -1
  242. package/dist/registry/adapters.d.ts.map +0 -1
  243. package/dist/registry/adapters.js.map +0 -1
  244. package/dist/registry/capabilities.d.ts.map +0 -1
  245. package/dist/registry/capabilities.js.map +0 -1
  246. package/dist/registry/commit-and-signal.d.ts.map +0 -1
  247. package/dist/registry/commit-and-signal.js.map +0 -1
  248. package/dist/registry/domains.d.ts.map +0 -1
  249. package/dist/registry/domains.js.map +0 -1
  250. package/dist/registry/emitter.d.ts.map +0 -1
  251. package/dist/registry/emitter.js.map +0 -1
  252. package/dist/registry/store-adapters.d.ts.map +0 -1
  253. package/dist/registry/store-adapters.js.map +0 -1
  254. package/dist/registry/stores.d.ts.map +0 -1
  255. package/dist/registry/stores.js.map +0 -1
  256. package/dist/reticle-presenter-helpers.d.ts.map +0 -1
  257. package/dist/reticle-presenter-helpers.js.map +0 -1
  258. package/dist/reticle.d.ts.map +0 -1
  259. package/dist/reticle.js.map +0 -1
  260. package/dist/review/annotator.d.ts.map +0 -1
  261. package/dist/review/annotator.js.map +0 -1
  262. package/dist/review/mark-anchor.d.ts.map +0 -1
  263. package/dist/review/mark-anchor.js.map +0 -1
  264. package/dist/security/serialization.d.ts.map +0 -1
  265. package/dist/security/serialization.js.map +0 -1
  266. package/dist/timers/clock.d.ts.map +0 -1
  267. package/dist/timers/clock.js.map +0 -1
  268. package/dist/timers/native-console.d.ts.map +0 -1
  269. package/dist/timers/native-console.js.map +0 -1
  270. package/dist/timers/native-timers.d.ts.map +0 -1
  271. package/dist/timers/native-timers.js.map +0 -1
  272. package/dist/transport/transport.d.ts.map +0 -1
  273. package/dist/transport/transport.js.map +0 -1
@@ -26,4 +26,3 @@ export declare function firePointerNonBubbling(el: Element, type: string, relate
26
26
  */
27
27
  export declare function dragElement(source: HTMLElement, target: HTMLElement | null, data: unknown): Promise<boolean>;
28
28
  export {};
29
- //# sourceMappingURL=actions-dom.d.ts.map
@@ -133,4 +133,3 @@ export async function dragElement(source, target, data) {
133
133
  }
134
134
  return dropPrevented;
135
135
  }
136
- //# sourceMappingURL=actions-dom.js.map
@@ -51,6 +51,9 @@ interface ActionResult {
51
51
  * degraded role one — addressing the element across replays with zero hand-added testids.
52
52
  */
53
53
  component?: string;
54
+ /** Role + accessible name — the anchor that identifies an INSTANCE, not a JSX site. */
55
+ role?: string;
56
+ name?: string;
54
57
  source?: {
55
58
  file: string;
56
59
  line: number;
@@ -89,4 +92,3 @@ export declare function executeSequence(steps: ActionStep[]): Promise<{
89
92
  steps: SequenceStepResult[];
90
93
  }>;
91
94
  export {};
92
- //# sourceMappingURL=actions.d.ts.map
@@ -1,26 +1,10 @@
1
1
  import { ActionType, ActionWarning, DANGEROUS_ACTION_CONFIRM_ARG, ElementState, isDangerousActionText, SettleReason, } from '@reticlehq/core';
2
2
  import { refs } from '../dom/refs.js';
3
- import { getAccessibleName, isVisible, getStates } from '../dom/a11y.js';
3
+ import { assertEditable, assertNotRichText, setNativeValue } from './value-input.js';
4
+ import { getAccessibleName, getRole, isVisible, getStates } from '../dom/a11y.js';
4
5
  import { elementHasHoverHandlers, identifyComponent } from '../registry/adapters.js';
5
- import { captureValueSetter } from '../patching/capture-method.js';
6
+ import { isForm, isHtmlElement, isInput, isSelect, isTextArea } from '../dom/realm.js';
6
7
  import { nativeSetTimeout, settle } from '../timers/native-timers.js';
7
- /**
8
- * Set a value on a controlled input the way React expects (native setter + input event).
9
- * Returns the `input` event's dispatchEvent result (defaultPrevented of the primary event).
10
- */
11
- function setNativeValue(el, value) {
12
- const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
13
- const setter = captureValueSetter(proto);
14
- if (setter !== undefined) {
15
- setter.call(el, value);
16
- }
17
- else {
18
- el.value = value;
19
- }
20
- const notPrevented = el.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
21
- el.dispatchEvent(new Event('change', { bubbles: true }));
22
- return !notPrevented;
23
- }
24
8
  function asString(value, fallback = '') {
25
9
  return typeof value === 'string' ? value : fallback;
26
10
  }
@@ -28,7 +12,7 @@ function requireElement(ref) {
28
12
  const el = refs.resolve(ref);
29
13
  if (el === null)
30
14
  throw new Error(`ref '${ref}' no longer resolves to an element`);
31
- if (!(el instanceof HTMLElement))
15
+ if (!isHtmlElement(el))
32
16
  throw new Error(`ref '${ref}' is not an HTMLElement`);
33
17
  return el;
34
18
  }
@@ -55,6 +39,12 @@ function anchorOf(el) {
55
39
  out.component = component;
56
40
  if (info?.source !== undefined)
57
41
  out.source = info.source;
42
+ const role = getRole(el);
43
+ const name = getAccessibleName(el);
44
+ if (role.length > 0 && name.length > 0) {
45
+ out.role = role;
46
+ out.name = name;
47
+ }
58
48
  return out;
59
49
  }
60
50
  const result = (ref, action, effect, settled, settleReason, anchor, warning) => {
@@ -77,6 +67,10 @@ const result = (ref, action, effect, settled, settleReason, anchor, warning) =>
77
67
  base.component = anchor.component;
78
68
  if (anchor.source !== undefined)
79
69
  base.source = anchor.source;
70
+ if (anchor.role !== undefined)
71
+ base.role = anchor.role;
72
+ if (anchor.name !== undefined)
73
+ base.name = anchor.name;
80
74
  if (warning !== undefined)
81
75
  base.warning = warning;
82
76
  return base;
@@ -116,7 +110,7 @@ function assertActionAllowed(el, action, args) {
116
110
  action === ActionType.SUBMIT ||
117
111
  (action === ActionType.PRESS && asString(args['key'], 'Enter') === 'Enter');
118
112
  const dragTarget = action === ActionType.DRAG ? refs.resolve(asString(args['toRef'])) : null;
119
- const context = dragTarget instanceof HTMLElement
113
+ const context = isHtmlElement(dragTarget)
120
114
  ? `${dangerousActionContext(el)} ${dangerousActionContext(dragTarget)}`
121
115
  : dangerousActionContext(el);
122
116
  if (canTrigger &&
@@ -133,9 +127,7 @@ function enabledOf(el) {
133
127
  }
134
128
  /** Current value for inputs/textareas/selects, else undefined. */
135
129
  function valueOf(el) {
136
- if (el instanceof HTMLInputElement ||
137
- el instanceof HTMLTextAreaElement ||
138
- el instanceof HTMLSelectElement) {
130
+ if (isInput(el) || isTextArea(el) || isSelect(el)) {
139
131
  return el.value;
140
132
  }
141
133
  return undefined;
@@ -190,41 +182,69 @@ async function dispatchFor(el, action, args) {
190
182
  el.dispatchEvent(new FocusEvent('focusout', { bubbles: true }));
191
183
  return false;
192
184
  case ActionType.FILL:
193
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
185
+ if (isInput(el) || isTextArea(el)) {
186
+ // A MISSING value is a malformed call, not a request to empty the field. `asString` defaults
187
+ // to '', which made such a fill indistinguishable from `clear`: it wiped what was there,
188
+ // dispatched a real input event so React committed the empty string to state, and reported
189
+ // ok:true with nothing flagged. The value is nested (`args:{value}`) at the tool boundary, so
190
+ // passing it at the top level lands here — measured on bench-app's login form, where
191
+ // "admin@reticle.dev" was destroyed by a fill that reported success. An explicit '' still
192
+ // clears, because that is someone asking for it.
193
+ if (typeof args['value'] !== 'string') {
194
+ throw new Error("fill requires a string `value` — pass it nested, as args: { value: '…' }. " +
195
+ 'To empty a field on purpose use the `clear` action, or fill with an explicit "".');
196
+ }
197
+ assertEditable(el, 'fill');
194
198
  el.focus(); // focus first so a later blur commits (onBlur editors)
195
- return setNativeValue(el, asString(args['value']));
199
+ return setNativeValue(el, args['value']);
196
200
  }
201
+ assertNotRichText(el, 'fill');
197
202
  throw new Error(`cannot fill a <${el.tagName.toLowerCase()}>`);
198
203
  case ActionType.TYPE:
199
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
204
+ if (isInput(el) || isTextArea(el)) {
205
+ // Same family as the valueless FILL: a missing `text` appended '' and reported ok:true, so an
206
+ // agent believed it had typed into a field nothing was written to.
207
+ if (typeof args['text'] !== 'string') {
208
+ throw new Error("type requires a string `text` — pass it nested, as args: { text: '…' }.");
209
+ }
210
+ assertEditable(el, 'type');
200
211
  el.focus();
201
- return setNativeValue(el, el.value + asString(args['text']));
212
+ return setNativeValue(el, el.value + args['text']);
202
213
  }
214
+ assertNotRichText(el, 'type into');
203
215
  throw new Error(`cannot type into a <${el.tagName.toLowerCase()}>`);
204
216
  case ActionType.CLEAR:
205
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
217
+ if (isInput(el) || isTextArea(el)) {
206
218
  return setNativeValue(el, '');
207
219
  }
208
220
  // Consistent with TYPE/SELECT/CHECK: clearing a non-field is not a silent success. Throwing
209
221
  // surfaces the mismatch instead of reporting ok:true for an action that did nothing.
210
222
  throw new Error(`cannot clear a <${el.tagName.toLowerCase()}>`);
211
223
  case ActionType.SELECT:
212
- if (el instanceof HTMLSelectElement) {
213
- el.value = asString(args['value']);
224
+ if (isSelect(el)) {
225
+ // A MISSING value assigned '' to the <select>. No option carries that, so the browser sets
226
+ // selectedIndex to -1 — deselecting everything — from a call that was simply malformed.
227
+ // (Selecting a value that no option carries is a different case and is deliberately NOT
228
+ // refused: `actions.effect.test.ts` pins it as a detectable no-op, where the reported
229
+ // valueChanged delta is what tells the agent the option never took.)
230
+ if (typeof args['value'] !== 'string') {
231
+ throw new Error("select requires a string `value` — pass it nested, as args: { value: '…' }.");
232
+ }
233
+ el.value = args['value'];
214
234
  el.dispatchEvent(new Event('change', { bubbles: true }));
215
235
  return false; // change is not cancelable.
216
236
  }
217
237
  throw new Error(`cannot select on a <${el.tagName.toLowerCase()}>`);
218
238
  case ActionType.CHECK:
219
239
  case ActionType.UNCHECK:
220
- if (el instanceof HTMLInputElement) {
240
+ if (isInput(el)) {
221
241
  el.checked = action === ActionType.CHECK;
222
242
  el.dispatchEvent(new Event('change', { bubbles: true }));
223
243
  return false;
224
244
  }
225
245
  throw new Error(`cannot (un)check a <${el.tagName.toLowerCase()}>`);
226
246
  case ActionType.SUBMIT: {
227
- const form = el instanceof HTMLFormElement ? el : el.closest('form');
247
+ const form = isForm(el) ? el : el.closest('form');
228
248
  if (form === null)
229
249
  throw new Error('no form to submit');
230
250
  form.requestSubmit();
@@ -240,7 +260,7 @@ async function dispatchFor(el, action, args) {
240
260
  el.scrollIntoView();
241
261
  return false;
242
262
  case ActionType.UPLOAD: {
243
- if (!(el instanceof HTMLInputElement) || el.type !== 'file') {
263
+ if (!isInput(el) || el.type !== 'file') {
244
264
  throw new Error('upload target must be a <input type="file">');
245
265
  }
246
266
  const file = new File([asString(args['content'], 'reticle test file')], asString(args['name'], 'file.txt'), {
@@ -258,7 +278,7 @@ async function dispatchFor(el, action, args) {
258
278
  // asString falls back to '' (never undefined), so the old `!== undefined` was always true and
259
279
  // handed '' to refs.resolve. A drag with no target ref is a free drag — resolve nothing.
260
280
  const resolved = toRef !== '' ? refs.resolve(toRef) : null;
261
- return await dragElement(el, resolved instanceof HTMLElement ? resolved : null, args['data']);
281
+ return await dragElement(el, isHtmlElement(resolved) ? resolved : null, args['data']);
262
282
  }
263
283
  default:
264
284
  throw new Error(`unknown action '${action}'`);
@@ -363,4 +383,3 @@ export async function executeSequence(steps) {
363
383
  }
364
384
  return { ok: true, count: steps.length, effects, steps: stepResults };
365
385
  }
366
- //# sourceMappingURL=actions.js.map
@@ -16,4 +16,3 @@ interface ScrollResult {
16
16
  */
17
17
  export declare function scrollContainer(ref: string | undefined, dy: number | undefined, fraction?: number): ScrollResult;
18
18
  export {};
19
- //# sourceMappingURL=scroll.d.ts.map
@@ -41,4 +41,3 @@ export function scrollContainer(ref, dy, fraction) {
41
41
  atEnd: after + target.clientHeight >= target.scrollHeight - 1,
42
42
  };
43
43
  }
44
- //# sourceMappingURL=scroll.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Writing a value into a field, and the two preconditions that make it honest.
3
+ *
4
+ * Lifted out of actions.ts when that file hit the 600-line cap. It is a cohesive unit: every export
5
+ * here exists so that a value write either lands as a real user's edit would, or is refused with a
6
+ * reason — never forced through while reporting success.
7
+ */
8
+ /**
9
+ * Set a value on a controlled input the way React expects (native setter + input event).
10
+ * Returns the `input` event's dispatchEvent result (defaultPrevented of the primary event).
11
+ */
12
+ export declare function setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string): boolean;
13
+ /**
14
+ * Refuse to write to a field a person could not edit.
15
+ *
16
+ * `readonly` and `disabled` block USER input, not scripted assignment, so the prototype value setter
17
+ * writes straight through both. Measured: filling a readonly input reported `ok:true` and
18
+ * `valueChanged:true` with nothing in the effect block marking it read-only. That leaves the app in a
19
+ * state no user could reach, and every conclusion drawn from what follows is about software nobody
20
+ * can operate — the same reason the click path reports `occluded`, and the same choice Playwright
21
+ * makes when it refuses to fill a non-editable element.
22
+ */
23
+ /**
24
+ * A rich-text target is UNSUPPORTED, not a mistake by the caller.
25
+ *
26
+ * Every rich-text editor (TipTap, Quill, ProseMirror, Slate, Lexical) is `[contenteditable]`, and
27
+ * these actions handle only input/textarea. `cannot fill a <div>` reads as "you picked the wrong
28
+ * element" and sends the reader hunting for a better selector that does not exist. Support is not
29
+ * faked: assigning textContent would update the DOM while the editor's own model kept the old value,
30
+ * so the tool would report success for content the app will never submit.
31
+ */
32
+ export declare function assertNotRichText(el: HTMLElement, action: string): void;
33
+ export declare function assertEditable(el: HTMLInputElement | HTMLTextAreaElement, action: string): void;
@@ -0,0 +1,63 @@
1
+ import { captureValueSetter } from '../patching/capture-method.js';
2
+ import { valuePrototypeOf } from '../dom/realm.js';
3
+ /**
4
+ * Writing a value into a field, and the two preconditions that make it honest.
5
+ *
6
+ * Lifted out of actions.ts when that file hit the 600-line cap. It is a cohesive unit: every export
7
+ * here exists so that a value write either lands as a real user's edit would, or is refused with a
8
+ * reason — never forced through while reporting success.
9
+ */
10
+ /**
11
+ * Set a value on a controlled input the way React expects (native setter + input event).
12
+ * Returns the `input` event's dispatchEvent result (defaultPrevented of the primary event).
13
+ */
14
+ export function setNativeValue(el, value) {
15
+ // Realm-aware: a controlled input inside a same-origin frame has its own prototype.
16
+ const proto = valuePrototypeOf(el);
17
+ const setter = proto === undefined ? undefined : captureValueSetter(proto);
18
+ if (setter !== undefined) {
19
+ setter.call(el, value);
20
+ }
21
+ else {
22
+ el.value = value;
23
+ }
24
+ const notPrevented = el.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
25
+ el.dispatchEvent(new Event('change', { bubbles: true }));
26
+ return !notPrevented;
27
+ }
28
+ /**
29
+ * Refuse to write to a field a person could not edit.
30
+ *
31
+ * `readonly` and `disabled` block USER input, not scripted assignment, so the prototype value setter
32
+ * writes straight through both. Measured: filling a readonly input reported `ok:true` and
33
+ * `valueChanged:true` with nothing in the effect block marking it read-only. That leaves the app in a
34
+ * state no user could reach, and every conclusion drawn from what follows is about software nobody
35
+ * can operate — the same reason the click path reports `occluded`, and the same choice Playwright
36
+ * makes when it refuses to fill a non-editable element.
37
+ */
38
+ /**
39
+ * A rich-text target is UNSUPPORTED, not a mistake by the caller.
40
+ *
41
+ * Every rich-text editor (TipTap, Quill, ProseMirror, Slate, Lexical) is `[contenteditable]`, and
42
+ * these actions handle only input/textarea. `cannot fill a <div>` reads as "you picked the wrong
43
+ * element" and sends the reader hunting for a better selector that does not exist. Support is not
44
+ * faked: assigning textContent would update the DOM while the editor's own model kept the old value,
45
+ * so the tool would report success for content the app will never submit.
46
+ */
47
+ export function assertNotRichText(el, action) {
48
+ // The ATTRIBUTE, not `isContentEditable`: jsdom does not implement the property, so a test would
49
+ // pass against a guard that never fires in the environment the unit suite runs in.
50
+ const flag = el.getAttribute('contenteditable');
51
+ if (el.isContentEditable || (flag !== null && flag !== 'false')) {
52
+ throw new Error(`cannot ${action} a contenteditable element — rich-text editors keep their own document model, ` +
53
+ 'so writing to the DOM here would look right and submit the old content. This surface is not supported yet.');
54
+ }
55
+ }
56
+ export function assertEditable(el, action) {
57
+ if (el.disabled) {
58
+ throw new Error(`cannot ${action} a disabled <${el.tagName.toLowerCase()}> — a user could not edit it, so forcing the value would put the app in a state nobody can reach`);
59
+ }
60
+ if (el.readOnly) {
61
+ throw new Error(`cannot ${action} a readonly <${el.tagName.toLowerCase()}> — a user could not edit it, so forcing the value would put the app in a state nobody can reach`);
62
+ }
63
+ }
@@ -4,4 +4,3 @@ export declare const RELOAD_CACHE_BUST_PARAM = "_reticle_reload";
4
4
  export declare function resolveNavigationUrl(rawUrl: string, baseUrl: string): string | null;
5
5
  /** Map browser command names to handlers. Used by the transport on each COMMAND. */
6
6
  export declare function createCommandRegistry(): Map<string, CommandHandler>;
7
- //# sourceMappingURL=commands.d.ts.map
@@ -3,10 +3,13 @@ import { buildSnapshot } from '../dom/snapshot.js';
3
3
  import { matchQuery, runQuery } from '../dom/query.js';
4
4
  import { executeAction, executeSequence, dispatchWebMcp, } from '../actions/actions.js';
5
5
  import { describe } from '../dom/a11y.js';
6
+ import { sourceFor, formatSource } from '../dom/source.js';
6
7
  import { themeReport } from '../dom/theme.js';
7
8
  import { refs } from '../dom/refs.js';
9
+ import { isButton, isInput } from '../dom/realm.js';
8
10
  import { hitTestOccluder } from '../dom/occlusion.js';
9
11
  import { readStorage } from '../observers/storage.js';
12
+ import { captureDesktopWindow } from '../dom/desktop-capture.js';
10
13
  import { identifyComponent, readComponentState } from '../registry/adapters.js';
11
14
  import { readStoresWithTruncation, readStoresRaw, storeNames } from '../registry/stores.js';
12
15
  import { sanitizeWithReport } from '../security/serialization.js';
@@ -24,6 +27,14 @@ function num(value) {
24
27
  function record(value) {
25
28
  return typeof value === 'object' && value !== null ? value : {};
26
29
  }
30
+ function sourceLocation(value) {
31
+ if (typeof value !== 'object' || value === null)
32
+ return undefined;
33
+ const obj = value;
34
+ if (typeof obj['file'] !== 'string' || typeof obj['line'] !== 'number')
35
+ return undefined;
36
+ return { file: obj['file'], line: obj['line'], column: num(obj['column']) };
37
+ }
27
38
  function queryFromArgs(args) {
28
39
  return {
29
40
  by: str(args['by']),
@@ -35,10 +46,12 @@ function queryFromArgs(args) {
35
46
  placeholder: str(args['placeholder']),
36
47
  testid: str(args['testid']),
37
48
  alt: str(args['alt']),
49
+ component: str(args['component']),
38
50
  scope: str(args['scope']),
39
51
  attrs: Array.isArray(args['attrs'])
40
52
  ? args['attrs'].filter((a) => typeof a === 'string')
41
53
  : undefined,
54
+ source: sourceLocation(args['source']),
42
55
  };
43
56
  }
44
57
  function inspect(ref) {
@@ -62,16 +75,19 @@ function inspect(ref) {
62
75
  visibility: cs.visibility,
63
76
  }
64
77
  : null;
78
+ // `describe()` carries the CHEAP source (the nearest babel-stamped ancestor), because it runs per
79
+ // element on paths that describe hundreds at once. inspect is a single-element path, so it takes
80
+ // the adapter's answer first — the component that actually RENDERED this element, not the nearest
81
+ // stamped host. `act` already did this; inspect did not, so the two tools disagreed about the same
82
+ // ref and inspect — the tool you reach for to ask where something lives — was the one saying null.
83
+ const source = formatSource(sourceFor(el, component?.source));
65
84
  return {
66
85
  ...describe(el),
86
+ ...(source !== undefined ? { source } : {}),
67
87
  tag: el.tagName.toLowerCase(),
68
88
  href: el.getAttribute('href') ?? undefined,
69
- formAction: el instanceof HTMLButtonElement || el instanceof HTMLInputElement
70
- ? (el.form?.getAttribute('action') ?? undefined)
71
- : undefined,
72
- formText: el instanceof HTMLButtonElement || el instanceof HTMLInputElement
73
- ? (el.form?.textContent ?? undefined)
74
- : undefined,
89
+ formAction: isButton(el) || isInput(el) ? (el.form?.getAttribute('action') ?? undefined) : undefined,
90
+ formText: isButton(el) || isInput(el) ? (el.form?.textContent ?? undefined) : undefined,
75
91
  box: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
76
92
  // True when another element sits over this one's center point — the click would hit the overlay,
77
93
  // not this control (a z-index/overlay bug the DOM tree cannot show).
@@ -124,6 +140,10 @@ function readState(ref, store, path, depth) {
124
140
  // what keeps "the list is short" distinguishable from "I shortened the list".
125
141
  ...(projected.truncation === undefined ? {} : { truncation: projected.truncation }),
126
142
  ...('availableKeys' in selection ? { availableKeys: selection.availableKeys } : {}),
143
+ // How many keys there REALLY were, when the near-miss list is a sample. Without it, 50 names
144
+ // and no marker reads as "the key you asked for does not exist" — the strongest negative
145
+ // signal there is, and a false one when the key is simply number 51.
146
+ ...('totalKeys' in selection ? { totalKeys: selection.totalKeys } : {}),
127
147
  storeNames: names,
128
148
  };
129
149
  }
@@ -213,6 +233,7 @@ export function createCommandRegistry() {
213
233
  reg.set(ReticleCommand.STATE_READ, (args) => readState(str(args['ref']), str(args['store']), str(args['path']), num(args['depth'])));
214
234
  reg.set(ReticleCommand.STORAGE_READ, (args) => readStorage(str(args['area'])));
215
235
  reg.set(ReticleCommand.CAPABILITIES, () => getCapabilities());
236
+ reg.set(ReticleCommand.CAPTURE, (args) => captureDesktopWindow(args['fullPage'] === true));
216
237
  reg.set(ReticleCommand.SCROLL, (args) => {
217
238
  const dy = args['dy'];
218
239
  const fraction = args['fraction'];
@@ -242,4 +263,3 @@ export function createCommandRegistry() {
242
263
  });
243
264
  return reg;
244
265
  }
245
- //# sourceMappingURL=commands.js.map
@@ -0,0 +1,97 @@
1
+ import type { RedactionConfig } from '@reticlehq/core';
2
+ /**
3
+ * What `reticle.connect()` accepts.
4
+ *
5
+ * Split out of reticle.ts when that file crossed the 600-line cap — the same move `session-info.ts`
6
+ * made for the server's Session. A pure description of the SDK's public entry contract has no reason
7
+ * to live inside the class that happens to consume it, and separating them keeps the surface every
8
+ * integrating app actually reads reviewable on its own.
9
+ */
10
+ export interface ReticleConnectOptions {
11
+ /** WS endpoint of the local bridge. Defaults to ws://localhost:<port><path>. */
12
+ url?: string;
13
+ /** Human-friendly session label so the agent can target the right tab. */
14
+ session?: string;
15
+ /**
16
+ * Stable project identity, normally stamped by the build plugin (e.g. "acme-web-9f3c1d"). Lets the
17
+ * agent scope to the right app even when its dev server boots on an unexpected port. Optional.
18
+ */
19
+ projectId?: string;
20
+ /** Browser/bridge pairing token. Required when either endpoint is non-localhost. */
21
+ token?: string;
22
+ /** Explicitly allow Reticle on a non-localhost page or bridge. Requires token. */
23
+ allowNonLocalhost?: boolean;
24
+ /**
25
+ * Escape hatch for the production backstop. Reticle is dev-only and refuses to connect when the
26
+ * build reports NODE_ENV=production (an SSR healthcheck or a prod bundle opened locally would
27
+ * otherwise activate). The real fix is to gate the import behind `import.meta.env.DEV` so it's
28
+ * tree-shaken out; this flag only exists for the rare intentional prod diagnostic.
29
+ */
30
+ allowInProduction?: boolean;
31
+ /** Show a small in-page status chip (connection + event count). */
32
+ overlay?: boolean;
33
+ /**
34
+ * Capture request/response bodies on net.request events (dev-only; text-like content only,
35
+ * sensitive keys redacted, per-body capped). Off by default — bodies cost tokens and can carry PII.
36
+ */
37
+ captureNetworkBodies?: boolean;
38
+ /**
39
+ * The project root, so source paths report repo-relative instead of absolute.
40
+ *
41
+ * React's `_debugSource.fileName` is absolute; the babel stamp is repo-relative. Build plugins that
42
+ * can `define` a global set it themselves; frameworks that cannot (Next passes it through env,
43
+ * Astro through its own config) hand it here instead. Taking it as a connect OPTION is what keeps
44
+ * the generated app-side code plain JavaScript — the previous shape made the caller assign a global,
45
+ * and the TypeScript cast that needed shipped into a `.jsx` file and broke the build.
46
+ */
47
+ root?: string;
48
+ /** Presenter mode: glow border, animated cursor, click/hover effects, narration HUD. */
49
+ present?: boolean;
50
+ /** Per-action pacing (ms) in presenter mode so a human can follow. Default 450. */
51
+ pace?: number;
52
+ /** Min ms each narration line stays visible before the next replaces it (presenter). Default 3000. */
53
+ narrationDwellMs?: number;
54
+ /**
55
+ * Border behavior in presenter mode: 'session' (default) persists the border for the whole
56
+ * session; 'busy' restores the fade-after-idle behavior.
57
+ */
58
+ border?: 'session' | 'busy';
59
+ /** Max accumulated activity-log rows before the oldest are pruned (presenter). Default 50. */
60
+ logMax?: number;
61
+ /**
62
+ * Mount the floating human-recorder toolbar (Record/Stop/Annotate).
63
+ * Default off — purely additive, dev-only.
64
+ */
65
+ recorder?: boolean;
66
+ /**
67
+ * Mount the "Flag a bug" annotator: the human clicks an element that looks wrong, types what's
68
+ * wrong, and Reticle emits a HUMAN_MARK the agent drains via reticle_review. Defaults to ON with the
69
+ * presenter (it's the human's side of the look→act→assert loop); pass `annotate: false` to suppress.
70
+ */
71
+ annotate?: boolean;
72
+ /** Live-control: overridable ended-border fade delay (native timer). Default 4000. */
73
+ endedFadeMs?: number;
74
+ /** Session auto-end after this much agent idle (presenter). Default 5min; agent-tunable via reticle_session. */
75
+ idleEndMs?: number;
76
+ /**
77
+ * Extend the redaction rules with your app's own vocabulary.
78
+ *
79
+ * ```ts
80
+ * reticle.connect({
81
+ * redact: {
82
+ * keys: ['deviceSecretRef', /^partner[-_]?code$/i], // also redact these
83
+ * allow: ['designToken'], // stop redacting this false positive
84
+ * },
85
+ * });
86
+ * ```
87
+ *
88
+ * Additive only — there is no way to replace the built-in rule, because a config that could would
89
+ * eventually ship an app that leaks. `keys` strings match a key name exactly (case-insensitively);
90
+ * a RegExp is tested against it. `allow` exempts a key from the DEFAULT rule and loses to `keys`.
91
+ *
92
+ * Literal `keys` strings also cross the bridge, so the daemon redacts them on the driven path,
93
+ * where request bodies are captured raw from the network stack and never pass through this SDK.
94
+ * RegExp entries and `allow` do NOT cross — see docs/usage.md ("Extending the redaction rules") for why, and what that means.
95
+ */
96
+ redact?: RedactionConfig;
97
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,6 @@
1
1
  import { ElementState, type ElementDescriptor } from '@reticlehq/core';
2
2
  /** Compute the ARIA role (explicit wins, else implicit from the tag). */
3
3
  export declare function getRole(el: Element): string;
4
- /** Accessible name via a practical subset of the accname algorithm. */
5
4
  export declare function getAccessibleName(el: Element): string;
6
5
  /**
7
6
  * The set of states relevant to assertions. `visible` is an O(depth) forced-style walk; callers that
@@ -28,4 +27,3 @@ export declare function isVisible(el: Element, memo?: Map<Element, boolean>): bo
28
27
  /** Build the compact descriptor surfaced to the agent. `memo` (optional) shares the per-call
29
28
  * visibility cache with the query's state filter so ancestors aren't re-walked per element. */
30
29
  export declare function describe(el: Element, memo?: Map<Element, boolean>): ElementDescriptor;
31
- //# sourceMappingURL=a11y.d.ts.map