@reticlehq/browser 2.13.1 → 2.14.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 (196) hide show
  1. package/dist/actions/actions-dom.d.ts +0 -20
  2. package/dist/actions/actions-dom.js +0 -66
  3. package/dist/actions/actions-press.d.ts +10 -0
  4. package/dist/actions/actions-press.js +77 -0
  5. package/dist/actions/actions.d.ts +0 -89
  6. package/dist/actions/actions.js +24 -358
  7. package/dist/actions/appeared-text.d.ts +0 -20
  8. package/dist/actions/appeared-text.js +0 -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 +0 -9
  12. package/dist/actions/scroll.js +0 -7
  13. package/dist/actions/synthetic-input.d.ts +0 -7
  14. package/dist/actions/synthetic-input.js +0 -27
  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 +0 -67
  19. package/dist/connect-options.d.ts +2 -96
  20. package/dist/dom/a11y.d.ts +0 -29
  21. package/dist/dom/a11y.js +1 -88
  22. package/dist/dom/auto-anchor.d.ts +0 -40
  23. package/dist/dom/auto-anchor.js +0 -33
  24. package/dist/dom/chart.d.ts +0 -61
  25. package/dist/dom/chart.js +0 -60
  26. package/dist/dom/desktop-capture.d.ts +0 -30
  27. package/dist/dom/desktop-capture.js +0 -72
  28. package/dist/dom/dom-ignore.d.ts +0 -30
  29. package/dist/dom/dom-ignore.js +0 -47
  30. package/dist/dom/occlusion.d.ts +0 -7
  31. package/dist/dom/occlusion.js +0 -7
  32. package/dist/dom/query.d.ts +0 -14
  33. package/dist/dom/query.js +48 -214
  34. package/dist/dom/realm.d.ts +0 -20
  35. package/dist/dom/realm.js +0 -28
  36. package/dist/dom/refs.d.ts +0 -76
  37. package/dist/dom/refs.js +2 -133
  38. package/dist/dom/shadow-registry.d.ts +0 -33
  39. package/dist/dom/shadow-registry.js +2 -72
  40. package/dist/dom/snapshot.d.ts +2 -43
  41. package/dist/dom/snapshot.js +26 -151
  42. package/dist/dom/source.d.ts +0 -44
  43. package/dist/dom/source.js +0 -45
  44. package/dist/dom/theme.d.ts +0 -37
  45. package/dist/dom/theme.js +2 -52
  46. package/dist/dom/virtualized.d.ts +0 -17
  47. package/dist/dom/virtualized.js +0 -27
  48. package/dist/edit-epoch.d.ts +0 -22
  49. package/dist/edit-epoch.js +0 -52
  50. package/dist/index.js +1 -23
  51. package/dist/observers/animation.d.ts +0 -1
  52. package/dist/observers/animation.js +0 -3
  53. package/dist/observers/blind-spots.d.ts +0 -26
  54. package/dist/observers/blind-spots.js +2 -61
  55. package/dist/observers/console.d.ts +1 -2
  56. package/dist/observers/console.js +41 -30
  57. package/dist/observers/context-open.d.ts +1 -11
  58. package/dist/observers/context-open.js +10 -14
  59. package/dist/observers/departure.d.ts +3 -0
  60. package/dist/observers/departure.js +52 -0
  61. package/dist/observers/dialogs.d.ts +2 -0
  62. package/dist/observers/dialogs.js +39 -0
  63. package/dist/observers/dom.js +0 -44
  64. package/dist/observers/download.d.ts +1 -2
  65. package/dist/observers/download.js +38 -55
  66. package/dist/observers/focus.d.ts +0 -6
  67. package/dist/observers/focus.js +0 -7
  68. package/dist/observers/frames.d.ts +0 -9
  69. package/dist/observers/frames.js +1 -28
  70. package/dist/observers/health.d.ts +0 -5
  71. package/dist/observers/health.js +1 -33
  72. package/dist/observers/install-all.d.ts +2 -0
  73. package/dist/observers/install-all.js +26 -27
  74. package/dist/observers/ipc.d.ts +0 -25
  75. package/dist/observers/ipc.js +0 -65
  76. package/dist/observers/navigation.d.ts +2 -0
  77. package/dist/observers/navigation.js +33 -0
  78. package/dist/observers/network-body.d.ts +5 -28
  79. package/dist/observers/network-body.js +11 -108
  80. package/dist/observers/network-redact.d.ts +1 -20
  81. package/dist/observers/network-redact.js +1 -80
  82. package/dist/observers/network-stream.d.ts +0 -21
  83. package/dist/observers/network-stream.js +1 -44
  84. package/dist/observers/network.d.ts +2 -26
  85. package/dist/observers/network.js +177 -294
  86. package/dist/observers/perf.d.ts +0 -10
  87. package/dist/observers/perf.js +0 -16
  88. package/dist/observers/route.d.ts +1 -2
  89. package/dist/observers/route.js +13 -19
  90. package/dist/observers/scroll.d.ts +0 -1
  91. package/dist/observers/scroll.js +0 -9
  92. package/dist/observers/sdk-failure.d.ts +1 -9
  93. package/dist/observers/sdk-failure.js +1 -30
  94. package/dist/observers/state.d.ts +1 -11
  95. package/dist/observers/state.js +13 -41
  96. package/dist/observers/storage.d.ts +0 -14
  97. package/dist/observers/storage.js +10 -34
  98. package/dist/observers/types.d.ts +0 -15
  99. package/dist/observers/types.js +0 -14
  100. package/dist/patching/capture-method.d.ts +0 -24
  101. package/dist/patching/capture-method.js +0 -26
  102. package/dist/presenter/overlay.d.ts +0 -1
  103. package/dist/presenter/overlay.js +0 -1
  104. package/dist/presenter/presenter-brand.d.ts +0 -19
  105. package/dist/presenter/presenter-brand.js +0 -21
  106. package/dist/presenter/presenter-config.d.ts +1 -105
  107. package/dist/presenter/presenter-config.js +2 -91
  108. package/dist/presenter/presenter-controls.d.ts +0 -29
  109. package/dist/presenter/presenter-controls.js +3 -131
  110. package/dist/presenter/presenter-dock-layout.d.ts +0 -3
  111. package/dist/presenter/presenter-dock-layout.js +0 -4
  112. package/dist/presenter/presenter-drag.d.ts +2 -24
  113. package/dist/presenter/presenter-drag.js +0 -26
  114. package/dist/presenter/presenter-effects.d.ts +0 -9
  115. package/dist/presenter/presenter-effects.js +0 -9
  116. package/dist/presenter/presenter-glow.d.ts +0 -21
  117. package/dist/presenter/presenter-glow.js +1 -27
  118. package/dist/presenter/presenter-heroicons-data.d.ts +0 -8
  119. package/dist/presenter/presenter-heroicons-data.js +0 -8
  120. package/dist/presenter/presenter-hud-chrome.d.ts +0 -10
  121. package/dist/presenter/presenter-hud-chrome.js +0 -10
  122. package/dist/presenter/presenter-icons.d.ts +0 -6
  123. package/dist/presenter/presenter-icons.js +0 -6
  124. package/dist/presenter/presenter-log.d.ts +0 -38
  125. package/dist/presenter/presenter-log.js +1 -45
  126. package/dist/presenter/presenter-report-copy.d.ts +5 -66
  127. package/dist/presenter/presenter-report-copy.js +5 -66
  128. package/dist/presenter/presenter-report-styles.d.ts +0 -6
  129. package/dist/presenter/presenter-report-styles.js +6 -6
  130. package/dist/presenter/presenter-report.d.ts +4 -7
  131. package/dist/presenter/presenter-report.js +18 -76
  132. package/dist/presenter/presenter-run-state.d.ts +0 -7
  133. package/dist/presenter/presenter-run-state.js +0 -5
  134. package/dist/presenter/presenter-settings-styles.d.ts +0 -1
  135. package/dist/presenter/presenter-settings-styles.js +0 -1
  136. package/dist/presenter/presenter-settings.d.ts +1 -23
  137. package/dist/presenter/presenter-settings.js +1 -31
  138. package/dist/presenter/presenter-shell-styles.js +2 -5
  139. package/dist/presenter/presenter-shell.d.ts +2 -18
  140. package/dist/presenter/presenter-shell.js +1 -73
  141. package/dist/presenter/presenter-styles.d.ts +0 -9
  142. package/dist/presenter/presenter-styles.js +0 -9
  143. package/dist/presenter/presenter-tally.d.ts +0 -5
  144. package/dist/presenter/presenter-tally.js +0 -4
  145. package/dist/presenter/presenter-test-helpers.d.ts +0 -2
  146. package/dist/presenter/presenter-test-helpers.js +0 -2
  147. package/dist/presenter/presenter-verbs.js +0 -7
  148. package/dist/presenter/presenter-workspace.d.ts +0 -4
  149. package/dist/presenter/presenter-workspace.js +1 -8
  150. package/dist/presenter/presenter.d.ts +0 -60
  151. package/dist/presenter/presenter.js +4 -118
  152. package/dist/recorder/recorder-styles.d.ts +0 -6
  153. package/dist/recorder/recorder-styles.js +0 -6
  154. package/dist/recorder/recorder.d.ts +0 -34
  155. package/dist/recorder/recorder.js +0 -49
  156. package/dist/registry/adapters.d.ts +0 -10
  157. package/dist/registry/adapters.js +0 -10
  158. package/dist/registry/auto-testids.d.ts +0 -19
  159. package/dist/registry/auto-testids.js +1 -21
  160. package/dist/registry/capabilities.d.ts +0 -31
  161. package/dist/registry/capabilities.js +1 -46
  162. package/dist/registry/commit-and-signal.d.ts +0 -6
  163. package/dist/registry/commit-and-signal.js +1 -7
  164. package/dist/registry/domains.d.ts +0 -8
  165. package/dist/registry/domains.js +0 -7
  166. package/dist/registry/emitter.d.ts +0 -14
  167. package/dist/registry/emitter.js +0 -10
  168. package/dist/registry/store-adapters.d.ts +0 -186
  169. package/dist/registry/store-adapters.js +0 -163
  170. package/dist/registry/stores.d.ts +0 -33
  171. package/dist/registry/stores.js +0 -59
  172. package/dist/reticle-presenter-helpers.d.ts +0 -13
  173. package/dist/reticle-presenter-helpers.js +0 -14
  174. package/dist/reticle.d.ts +1 -52
  175. package/dist/reticle.js +20 -145
  176. package/dist/review/annotator-styles.js +0 -7
  177. package/dist/review/annotator.d.ts +2 -13
  178. package/dist/review/annotator.js +0 -40
  179. package/dist/review/mark-anchor.d.ts +0 -11
  180. package/dist/review/mark-anchor.js +0 -8
  181. package/dist/security/serialization.d.ts +0 -20
  182. package/dist/security/serialization.js +1 -66
  183. package/dist/session-continuity.d.ts +0 -16
  184. package/dist/session-continuity.js +1 -20
  185. package/dist/timers/clock.d.ts +1 -14
  186. package/dist/timers/clock.js +59 -60
  187. package/dist/timers/native-console.js +0 -4
  188. package/dist/timers/native-timers.d.ts +0 -25
  189. package/dist/timers/native-timers.js +3 -33
  190. package/dist/transport/transport.d.ts +0 -51
  191. package/dist/transport/transport.js +3 -129
  192. package/dist/transport/unreachable-message.d.ts +0 -17
  193. package/dist/transport/unreachable-message.js +0 -17
  194. package/dist/util/captured-method.d.ts +0 -35
  195. package/dist/util/captured-method.js +0 -35
  196. package/package.json +2 -2
package/dist/dom/a11y.js CHANGED
@@ -4,19 +4,6 @@ import { refs } from './refs.js';
4
4
  import { inspectChart } from './chart.js';
5
5
  import { isSensitiveKey } from '../security/serialization.js';
6
6
  import { formatSource, sourceFromDom } from './source.js';
7
- /**
8
- * Roles whose accessible name comes from their text content (ARIA's `nameFrom: author content`).
9
- *
10
- * `radio`, `checkbox`, `row` and `tooltip` were missing, and the gap is not cosmetic: a segmented
11
- * filter written as `<button role="radio">held</button>` — an extremely ordinary design-system
12
- * control — reported as a nameless `radio`, so `by: role` + name could not address it at all and an
13
- * agent had to fall back to a testid the app has no reason to carry. Measured on a shipments console:
14
- * six filters, six nameless radios, none reachable by name.
15
- *
16
- * `listitem`, `status` and `alert` are NOT in the spec's list. They are kept as a deliberate
17
- * over-approximation — a name computed from content is more useful than no name — and removing them
18
- * has no measured symptom to justify the risk.
19
- */
20
7
  const NAME_FROM_CONTENT = new Set([
21
8
  'button',
22
9
  'link',
@@ -57,16 +44,6 @@ function inputRole(input) {
57
44
  return 'button';
58
45
  return 'textbox';
59
46
  }
60
- /**
61
- * Cheap author-supplied-naming probe: an explicit `aria-label`, any `aria-labelledby`, or a
62
- * `title`. Attribute reads only - no name computation - so `getRole` can consult it without
63
- * recursion into `getAccessibleName`.
64
- *
65
- * This decides `section` -> `region`, which the implicit-role table makes CONDITIONAL: an unnamed
66
- * `<section>` is a plain `generic` container, while one carrying an accessible name is exposed as
67
- * `region`. Getting that backwards either floods every page with phantom regions or hides real
68
- * ones, so both halves are pinned by tests.
69
- */
70
47
  function hasAuthorNaming(el) {
71
48
  const label = el.getAttribute('aria-label');
72
49
  if (label !== null && label.trim().length > 0)
@@ -77,7 +54,6 @@ function hasAuthorNaming(el) {
77
54
  const title = el.getAttribute('title');
78
55
  return title !== null && title.trim().length > 0;
79
56
  }
80
- /** Compute the ARIA role (explicit wins, else implicit from the tag). */
81
57
  export function getRole(el) {
82
58
  const explicit = el.getAttribute('role');
83
59
  if (explicit !== null && explicit.trim().length > 0)
@@ -124,10 +100,6 @@ export function getRole(el) {
124
100
  case 'thead':
125
101
  case 'tfoot':
126
102
  return 'rowgroup';
127
- // A cell's role follows its grid context: plain tables expose `cell`/`columnheader`,
128
- // while inside an explicit `role="grid"`/`role="treegrid"` the same markup is exposed as
129
- // `gridcell` - the pair data-grid queries actually reach for (`{ role: "cell" }` against a
130
- // CSS grid pretending to be a table would otherwise answer zero).
131
103
  case 'td':
132
104
  return el.closest('[role~="grid"], [role~="treegrid"]') !== null ? 'gridcell' : 'cell';
133
105
  case 'th': {
@@ -186,22 +158,6 @@ function labelledByText(el) {
186
158
  const joined = parts.join(' ').trim();
187
159
  return joined.length > 0 ? joined : null;
188
160
  }
189
- /** Accessible name via a practical subset of the accname algorithm. */
190
- /** Accessible name via a practical subset of the accname algorithm. */
191
- /**
192
- * Text content with `aria-hidden` subtrees removed.
193
- *
194
- * The accessible-name spec excludes them, and so does the matcher, because THIS function is what
195
- * `by: role` + name matches through. Reading raw `textContent` here made the name we REPORT differ
196
- * from the name that can be SELECTED: Material UI renders its required-field marker as
197
- * `<span aria-hidden="true"> *</span>`, so a login field was reported as `"Username *"` and
198
- * addressable only as `"Username"`. Reporting a name the agent cannot use defeats the purpose of
199
- * reporting it at all, so both come from the same rule.
200
- *
201
- * An `<img alt>` contributes its alt text the way the spec's subtree step treats embedded
202
- * alternatives: `<button><img alt="Close"></button>` is named "Close", not nameless. Pieces are
203
- * joined with spaces so an icon followed by a word never fuses into one unmatchable token.
204
- */
205
161
  function textWithoutHidden(node) {
206
162
  if (node.nodeType === Node.TEXT_NODE)
207
163
  return node.textContent ?? '';
@@ -244,10 +200,6 @@ export function getAccessibleName(el) {
244
200
  if (text.length > 0)
245
201
  return text;
246
202
  }
247
- // Submit-like inputs carry their name on `value`, exactly where the visible caption comes
248
- // from: `<input type="submit" value="Send">` renders a button reading Send. Without this the
249
- // descriptor printed `button ""` while `by: text` found the very same input by "Send", so the
250
- // two locators disagreed about one element - the disagreement this engine exists to prevent.
251
203
  if (isInput(el)) {
252
204
  const type = el.type.toLowerCase();
253
205
  if ('submit' === type || 'button' === type || 'reset' === type) {
@@ -278,10 +230,6 @@ function ariaBool(el, attr) {
278
230
  return undefined;
279
231
  return 'true' === value;
280
232
  }
281
- /**
282
- * The set of states relevant to assertions. `visible` is an O(depth) forced-style walk; callers that
283
- * already computed it (describe) pass it in so it isn't resolved twice per element.
284
- */
285
233
  export function getStates(el, visible = isVisible(el)) {
286
234
  const states = [ElementState.PRESENT];
287
235
  states.push(visible ? ElementState.VISIBLE : ElementState.HIDDEN);
@@ -297,12 +245,6 @@ export function getStates(el, visible = isVisible(el)) {
297
245
  states.push(ElementState.FOCUSED);
298
246
  return states;
299
247
  }
300
- /**
301
- * True when a form field holds a secret the SDK must never capture verbatim — a password input, a
302
- * sensitive `autocomplete` (cc-number, one-time-code, …), or a name/id/testid/aria-label that trips
303
- * `isSensitiveKey`. The single source of truth for both live-snapshot redaction (`getValue`) and the
304
- * flow recorder's fill-value redaction, so recorded flows never persist typed passwords/OTPs/keys.
305
- */
306
248
  export function isSensitiveField(el) {
307
249
  if (!isInput(el) && !isTextArea(el) && !isSelect(el)) {
308
250
  return false;
@@ -328,7 +270,6 @@ export function getValue(el) {
328
270
  const valueNow = el.getAttribute('aria-valuenow');
329
271
  return valueNow ?? undefined;
330
272
  }
331
- /** Whether the element's OWN box hides it — one forced-style resolution, no ancestor walk. */
332
273
  function selfHidden(el) {
333
274
  if ('true' === el.getAttribute('aria-hidden'))
334
275
  return true;
@@ -347,22 +288,6 @@ function selfHidden(el) {
347
288
  }
348
289
  return false;
349
290
  }
350
- /**
351
- * Whether the element is actually visible (not display:none/hidden/aria-hidden/opacity:0), walking to
352
- * root. This is an O(depth) forced-style walk PER node; `memo` (optional, scoped to ONE synchronous
353
- * query pass) caches the full inherited result per element so a broad state-filtered query stops
354
- * re-resolving getComputedStyle up the same ancestor chain for every sibling. Sound because the DOM is
355
- * static for the pass's duration — the cache MUST be a per-call Map, never module-level (that would go
356
- * stale the instant the app mutates, the same trap the shadow-root note in query.ts documents).
357
- */
358
- /**
359
- * True when the element is inside the viewport right now: visible AND its bounding box intersects
360
- * the window. This is what makes a scroll assertable (#398) — content below the fold of a scrolling
361
- * container is `visible`/`present` before any scroll, so only a viewport-intersection check can tell
362
- * "scrolled into view" from "was always in the DOM". Uses getBoundingClientRect (viewport-relative,
363
- * already accounts for scroll position), not an IntersectionObserver, so it stays synchronous inside
364
- * the predicate pass.
365
- */
366
291
  export function isInViewport(el, memo) {
367
292
  if (!isVisible(el, memo))
368
293
  return false;
@@ -381,9 +306,6 @@ export function isVisible(el, memo) {
381
306
  if (cached !== undefined)
382
307
  return cached;
383
308
  const parent = el.parentElement;
384
- // Each cached boolean already folds in that node's own aria-hidden/[hidden]/display/visibility/opacity,
385
- // so inherited visibility composes by AND up the chain and a sibling short-circuits at the first
386
- // cached ancestor.
387
309
  const result = !selfHidden(el) && (null === parent || isVisible(parent, memo));
388
310
  if (memo !== undefined)
389
311
  memo.set(el, result);
@@ -394,13 +316,11 @@ function getVisibleText(el) {
394
316
  const text = collapse(el.textContent ?? '');
395
317
  return text.length > MAX_TEXT ? `${text.slice(0, MAX_TEXT)}…` : text;
396
318
  }
397
- /** Build the compact descriptor surfaced to the agent. `memo` (optional) shares the per-call
398
- * visibility cache with the query's state filter so ancestors aren't re-walked per element. */
399
319
  export function describe(el, memo) {
400
320
  const value = getValue(el);
401
321
  const text = getVisibleText(el);
402
322
  const name = getAccessibleName(el);
403
- const visible = isVisible(el, memo); // O(depth) style walk — computed ONCE and reused by getStates
323
+ const visible = isVisible(el, memo);
404
324
  const base = {
405
325
  ref: refs.refFor(el),
406
326
  role: getRole(el),
@@ -412,16 +332,9 @@ export function describe(el, memo) {
412
332
  base.value = value;
413
333
  if (text.length > 0 && text !== name)
414
334
  base.text = text;
415
- // DOM-only lookup on purpose: describe() runs once per matched element, so the adapter's fiber walk
416
- // would turn a broad query into thousands of tree traversals. The stamped attribute answers the
417
- // same question for a fraction of the cost, and single-element paths that can afford the better
418
- // answer (inspect, act, review) use sourceFor() instead.
419
335
  const source = formatSource(sourceFromDom(el));
420
336
  if (source !== undefined)
421
337
  base.source = source;
422
- // Chart faults, only when there are any. Gated on the element actually containing plot geometry so
423
- // the common case — every non-chart element on the page — pays one querySelector miss and nothing
424
- // else, and a HEALTHY chart adds no bytes to the wire either.
425
338
  if (el.querySelector('path, polyline, polygon') !== null) {
426
339
  const faults = inspectChart(el).findings;
427
340
  if (faults.length > 0)
@@ -1,30 +1,3 @@
1
- /**
2
- * Auto-anchor synthesis — derive a STABLE way to address any element WITHOUT a hand-added
3
- * data-testid. This is the brain of zero-config integration: given what the framework already knows
4
- * about an element (its component name + source location from the fiber, its role + accessible name,
5
- * its position), pick the most durable anchor so a flow can re-find the element across refactors.
6
- *
7
- * Pure and framework-agnostic: it takes a plain descriptor (the React adapter, or a future Vue/Svelte
8
- * adapter, fills it in) and returns the chosen anchor + whether it is stable. No DOM, no fiber here.
9
- *
10
- * Priority (most → least durable). Each tier is only used when the one above is unavailable:
11
- * 1. testid — explicit, author-chosen, the gold standard. Stable.
12
- * 2. component@source — component display name + source file:line. Survives text/markup/style
13
- * changes; uniquely identifies WHERE in the source the element comes from.
14
- * Stable (a move that changes the source line is a real change worth re-pinning).
15
- * 3. component+role/name — component name plus role/accessible-name when no source is available.
16
- * Stable enough: the component identity anchors it, role/name disambiguate.
17
- * 4. role+name — semantic but content-dependent: an accessible name can change with copy.
18
- * NOT marked stable (a healed locator could drift to a renamed element).
19
- * 5. role / position — last resort. NOT stable; surfaced so the element is still addressable, but
20
- * a flow built on it should be flagged (the recorder marks the step degraded).
21
- *
22
- * The point: tiers 1–3 are "stable" (a flow anchored on them can be trusted to re-resolve to the
23
- * same element); tiers 4–5 are best-effort. Today most elements without a testid fall to role/text
24
- * (degraded). With component+source, the SAME elements become tier-2 stable anchors — that is how
25
- * integration becomes universal without the app author adding a single testid.
26
- */
27
- /** Which signal produced the anchor — also its durability tier. */
28
1
  export declare const AnchorStrategy: {
29
2
  readonly TESTID: "testid";
30
3
  readonly COMPONENT: "component";
@@ -32,34 +5,21 @@ export declare const AnchorStrategy: {
32
5
  readonly POSITION: "position";
33
6
  };
34
7
  export type AnchorStrategy = (typeof AnchorStrategy)[keyof typeof AnchorStrategy];
35
- /** What an adapter knows about an element — any subset; richer input yields a more durable anchor. */
36
8
  export interface AnchorInput {
37
- /** Explicit data-testid, if the author added one. */
38
9
  testid?: string;
39
- /** Nearest component display name (React fiber / Vue instance / …). */
40
10
  component?: string;
41
- /** Source location stamped by the framework's compiler/plugin. */
42
11
  source?: {
43
12
  file: string;
44
13
  line: number;
45
14
  };
46
- /** ARIA role. */
47
15
  role?: string;
48
- /** Accessible name (label / text). */
49
16
  name?: string;
50
- /** Index among same-component-or-role siblings — a positional tiebreaker. */
51
17
  nth?: number;
52
18
  }
53
19
  interface SynthesizedAnchor {
54
20
  strategy: AnchorStrategy;
55
- /** A compact, human-legible, re-resolvable anchor string. */
56
21
  value: string;
57
- /** True when a flow can trust this anchor to re-resolve to the same element (tiers 1–3). */
58
22
  stable: boolean;
59
23
  }
60
- /**
61
- * Choose the most durable anchor for an element from whatever the adapter could observe. Always
62
- * returns something addressable (falls through to position), with `stable` marking tiers 1–3.
63
- */
64
24
  export declare function synthesizeAnchor(input: AnchorInput): SynthesizedAnchor;
65
25
  export {};
@@ -1,30 +1,3 @@
1
- /**
2
- * Auto-anchor synthesis — derive a STABLE way to address any element WITHOUT a hand-added
3
- * data-testid. This is the brain of zero-config integration: given what the framework already knows
4
- * about an element (its component name + source location from the fiber, its role + accessible name,
5
- * its position), pick the most durable anchor so a flow can re-find the element across refactors.
6
- *
7
- * Pure and framework-agnostic: it takes a plain descriptor (the React adapter, or a future Vue/Svelte
8
- * adapter, fills it in) and returns the chosen anchor + whether it is stable. No DOM, no fiber here.
9
- *
10
- * Priority (most → least durable). Each tier is only used when the one above is unavailable:
11
- * 1. testid — explicit, author-chosen, the gold standard. Stable.
12
- * 2. component@source — component display name + source file:line. Survives text/markup/style
13
- * changes; uniquely identifies WHERE in the source the element comes from.
14
- * Stable (a move that changes the source line is a real change worth re-pinning).
15
- * 3. component+role/name — component name plus role/accessible-name when no source is available.
16
- * Stable enough: the component identity anchors it, role/name disambiguate.
17
- * 4. role+name — semantic but content-dependent: an accessible name can change with copy.
18
- * NOT marked stable (a healed locator could drift to a renamed element).
19
- * 5. role / position — last resort. NOT stable; surfaced so the element is still addressable, but
20
- * a flow built on it should be flagged (the recorder marks the step degraded).
21
- *
22
- * The point: tiers 1–3 are "stable" (a flow anchored on them can be trusted to re-resolve to the
23
- * same element); tiers 4–5 are best-effort. Today most elements without a testid fall to role/text
24
- * (degraded). With component+source, the SAME elements become tier-2 stable anchors — that is how
25
- * integration becomes universal without the app author adding a single testid.
26
- */
27
- /** Which signal produced the anchor — also its durability tier. */
28
1
  export const AnchorStrategy = {
29
2
  TESTID: 'testid',
30
3
  COMPONENT: 'component',
@@ -34,15 +7,10 @@ export const AnchorStrategy = {
34
7
  function nonEmpty(s) {
35
8
  return 'string' === typeof s && s.length > 0;
36
9
  }
37
- /** Source as a compact `file:line` suffix; basename only to stay terse and path-independent. */
38
10
  function sourceTag(source) {
39
11
  const base = source.file.split('/').pop() ?? source.file;
40
12
  return `${base}:${source.line}`;
41
13
  }
42
- /**
43
- * Choose the most durable anchor for an element from whatever the adapter could observe. Always
44
- * returns something addressable (falls through to position), with `stable` marking tiers 1–3.
45
- */
46
14
  export function synthesizeAnchor(input) {
47
15
  if (nonEmpty(input.testid)) {
48
16
  return { strategy: AnchorStrategy.TESTID, value: input.testid, stable: true };
@@ -69,6 +37,5 @@ export function synthesizeAnchor(input) {
69
37
  const suffix = input.nth !== undefined ? `#${input.nth}` : '';
70
38
  return { strategy: AnchorStrategy.ROLE, value: `${input.role}${suffix}`, stable: false };
71
39
  }
72
- // Last resort: position only — addressable but never trusted.
73
40
  return { strategy: AnchorStrategy.POSITION, value: `el#${input.nth ?? 0}`, stable: false };
74
41
  }
@@ -1,83 +1,22 @@
1
- /**
2
- * Chart inspection — the one dashboard surface Reticle could not previously see.
3
- *
4
- * A chart is where the store and the screen diverge most easily and most silently. Everything else on
5
- * a dashboard is text: a KPI card renders a number you can read, a table renders rows you can count,
6
- * and if the store disagrees with either, a state-vs-DOM comparison catches it. A chart renders
7
- * GEOMETRY — the data has been through a scale function into `points`/`d` coordinates — and geometry
8
- * is not text. `series` can be perfectly correct in the store while the polyline is blank, flat, or
9
- * full of NaN, and no reader of either side would notice.
10
- *
11
- * Two failure classes are worth detecting, and they are chosen because they are DECIDABLE from the
12
- * element alone — no baseline image, no per-library scale inversion, no false positives from a chart
13
- * that is merely ugly:
14
- *
15
- * 1. **Non-finite coordinates.** `NaN`/`Infinity` in a path reaches the DOM whenever the scale math
16
- * divides by a zero range — an empty series, a single-point series, or min === max. The browser
17
- * silently renders nothing. This is a genuine bug every time; there is no chart for which NaN
18
- * coordinates are correct.
19
- * 2. **Degenerate geometry.** A chart element that exists but encloses no area: no points at all, or
20
- * every point identical. Distinguishes "the chart is missing" (already catchable — the element is
21
- * absent) from "the chart is present and empty", which looks healthy to a presence check and to a
22
- * screenshot diff against a baseline that was also empty.
23
- *
24
- * Deliberately NOT attempted: comparing plotted geometry against the source data. That needs the
25
- * chart's scale functions inverted per library, is a large surface to get wrong, and `chartInstance`
26
- * below obsoletes it for the libraries people actually use — Chart.js and ECharts both hang their
27
- * instance off the canvas element, so their data can be read directly rather than reverse-engineered
28
- * from pixels.
29
- */
30
1
  export interface ChartFinding {
31
- /** What is wrong, as a stable machine-readable kind. */
32
2
  kind: 'non-finite-coordinates' | 'empty-geometry' | 'degenerate-geometry';
33
- /** The offending element's tag, so the agent can find it. */
34
3
  tag: string;
35
- /** Which attribute carried the fault. */
36
4
  attr: string;
37
- /** A short excerpt of the offending value — bounded; the full path can be thousands of chars. */
38
5
  sample: string;
39
6
  }
40
7
  export interface ChartReport {
41
- /** How many data-bearing geometry elements were examined. */
42
8
  examined: number;
43
9
  findings: ChartFinding[];
44
- /** True when a canvas is present, i.e. the geometry is NOT in the DOM and this report cannot see it. */
45
10
  canvas: boolean;
46
11
  }
47
- /**
48
- * Every number embedded in a geometry attribute. Path data interleaves single-letter drawing
49
- * commands with coordinate pairs, so this extracts numeric-looking tokens rather than parsing the
50
- * path grammar — the question here is only "is any coordinate non-finite", which does not need a
51
- * full parser.
52
- */
53
12
  export declare function geometryNumbers(value: string): number[];
54
- /** True when the attribute contains a coordinate the browser cannot render. */
55
13
  export declare function hasNonFiniteCoordinate(value: string): boolean;
56
- /** True when the geometry encloses no area — no coordinates, or every coordinate pair identical. */
57
14
  export declare function isDegenerate(value: string): boolean;
58
- /**
59
- * Inspect a subtree for chart geometry faults. Pure over the element — no events, no globals — so it
60
- * can back a tool call, an assertion, or a snapshot annotation without any of them owning it.
61
- */
62
15
  export declare function inspectChart(root: Element): ChartReport;
63
- /** The shape a canvas charting library hangs off its canvas element, once located. */
64
16
  export interface CanvasChartData {
65
17
  library: 'chartjs' | 'echarts';
66
- /** The library's own data structure — series/datasets — read directly, not inferred from pixels. */
67
18
  data: unknown;
68
19
  }
69
- /**
70
- * Read the data behind a CANVAS chart.
71
- *
72
- * Canvas is the one place where "instrument from inside the app" is not merely cheaper than pixels but
73
- * is the only option at all: the chart is a bitmap, so the DOM shows an empty `<canvas>` and a
74
- * screenshot shows colours no assertion can interpret. Both major canvas libraries keep their live
75
- * instance reachable from the element — Chart.js via its registry, ECharts via a per-DOM lookup — so
76
- * the series data is READABLE rather than reverse-engineerable.
77
- *
78
- * Takes the globals as arguments rather than reaching for `window`, so this stays pure and testable
79
- * and the SDK never depends on either library being present.
80
- */
81
20
  export declare function canvasChartData(canvas: Element, globals: {
82
21
  Chart?: {
83
22
  getChart?: (el: Element) => {
package/dist/dom/chart.js CHANGED
@@ -1,72 +1,24 @@
1
- /**
2
- * Chart inspection — the one dashboard surface Reticle could not previously see.
3
- *
4
- * A chart is where the store and the screen diverge most easily and most silently. Everything else on
5
- * a dashboard is text: a KPI card renders a number you can read, a table renders rows you can count,
6
- * and if the store disagrees with either, a state-vs-DOM comparison catches it. A chart renders
7
- * GEOMETRY — the data has been through a scale function into `points`/`d` coordinates — and geometry
8
- * is not text. `series` can be perfectly correct in the store while the polyline is blank, flat, or
9
- * full of NaN, and no reader of either side would notice.
10
- *
11
- * Two failure classes are worth detecting, and they are chosen because they are DECIDABLE from the
12
- * element alone — no baseline image, no per-library scale inversion, no false positives from a chart
13
- * that is merely ugly:
14
- *
15
- * 1. **Non-finite coordinates.** `NaN`/`Infinity` in a path reaches the DOM whenever the scale math
16
- * divides by a zero range — an empty series, a single-point series, or min === max. The browser
17
- * silently renders nothing. This is a genuine bug every time; there is no chart for which NaN
18
- * coordinates are correct.
19
- * 2. **Degenerate geometry.** A chart element that exists but encloses no area: no points at all, or
20
- * every point identical. Distinguishes "the chart is missing" (already catchable — the element is
21
- * absent) from "the chart is present and empty", which looks healthy to a presence check and to a
22
- * screenshot diff against a baseline that was also empty.
23
- *
24
- * Deliberately NOT attempted: comparing plotted geometry against the source data. That needs the
25
- * chart's scale functions inverted per library, is a large surface to get wrong, and `chartInstance`
26
- * below obsoletes it for the libraries people actually use — Chart.js and ECharts both hang their
27
- * instance off the canvas element, so their data can be read directly rather than reverse-engineered
28
- * from pixels.
29
- */
30
- /** Elements whose geometry encodes data. `line`/`circle`/`rect` are excluded: they are used for axes,
31
- * gridlines and backgrounds, where a zero-extent element is normal rather than a fault. */
32
1
  const GEOMETRY_TAGS = ['path', 'polyline', 'polygon'];
33
- /** Attributes carrying that geometry, per tag. */
34
2
  const GEOMETRY_ATTRS = ['d', 'points'];
35
3
  const SAMPLE_MAX = 120;
36
- /**
37
- * Every number embedded in a geometry attribute. Path data interleaves single-letter drawing
38
- * commands with coordinate pairs, so this extracts numeric-looking tokens rather than parsing the
39
- * path grammar — the question here is only "is any coordinate non-finite", which does not need a
40
- * full parser.
41
- */
42
4
  export function geometryNumbers(value) {
43
5
  const tokens = value.match(/-?\d*\.?\d+(?:e[-+]?\d+)?|NaN|Infinity|-Infinity/gi) ?? [];
44
6
  return tokens.map((t) => Number(t));
45
7
  }
46
- /** True when the attribute contains a coordinate the browser cannot render. */
47
8
  export function hasNonFiniteCoordinate(value) {
48
- // Checked textually first: `NaN` and `Infinity` survive into the attribute as literal words, and a
49
- // string scan catches them without depending on how the number extractor tokenises.
50
9
  if (/NaN|Infinity/i.test(value))
51
10
  return true;
52
11
  return geometryNumbers(value).some((n) => !Number.isFinite(n));
53
12
  }
54
- /** True when the geometry encloses no area — no coordinates, or every coordinate pair identical. */
55
13
  export function isDegenerate(value) {
56
14
  const nums = geometryNumbers(value).filter((n) => Number.isFinite(n));
57
15
  if (0 === nums.length)
58
16
  return true;
59
- // A polyline of one repeated point draws nothing. Compare pairs, not raw numbers, so a legitimately
60
- // flat line (varying x, constant y) is NOT flagged — that is a real chart of constant data.
61
17
  const xs = nums.filter((_, i) => 0 === i % 2);
62
18
  const ys = nums.filter((_, i) => 1 === i % 2);
63
19
  const allSame = (arr) => arr.every((v) => v === arr[0]);
64
20
  return xs.length > 1 && allSame(xs) && allSame(ys);
65
21
  }
66
- /**
67
- * Inspect a subtree for chart geometry faults. Pure over the element — no events, no globals — so it
68
- * can back a tool call, an assertion, or a snapshot annotation without any of them owning it.
69
- */
70
22
  export function inspectChart(root) {
71
23
  const findings = [];
72
24
  let examined = 0;
@@ -93,18 +45,6 @@ export function inspectChart(root) {
93
45
  const canvas = root.querySelectorAll('canvas').length > 0;
94
46
  return { examined, findings, canvas };
95
47
  }
96
- /**
97
- * Read the data behind a CANVAS chart.
98
- *
99
- * Canvas is the one place where "instrument from inside the app" is not merely cheaper than pixels but
100
- * is the only option at all: the chart is a bitmap, so the DOM shows an empty `<canvas>` and a
101
- * screenshot shows colours no assertion can interpret. Both major canvas libraries keep their live
102
- * instance reachable from the element — Chart.js via its registry, ECharts via a per-DOM lookup — so
103
- * the series data is READABLE rather than reverse-engineerable.
104
- *
105
- * Takes the globals as arguments rather than reaching for `window`, so this stays pure and testable
106
- * and the SDK never depends on either library being present.
107
- */
108
48
  export function canvasChartData(canvas, globals) {
109
49
  const chartjs = globals.Chart?.getChart?.(canvas);
110
50
  if (chartjs !== undefined && chartjs !== null) {
@@ -1,35 +1,5 @@
1
- /**
2
- * Desktop screenshots, asked of the runtime that owns the window.
3
- *
4
- * A browser tab is captured through CDP. A desktop webview has no CDP endpoint, so the pixels have
5
- * to come from the shell — and the shell is the only party that can produce them CORRECTLY.
6
- *
7
- * Capturing a screen *region* instead was tried and rejected: it photographs the glass, so an app
8
- * window sitting behind the editor yields a picture of the editor, saved as a visual baseline that a
9
- * later diff would trust. A screenshot tool that can silently return another window's pixels is
10
- * worse than one that returns nothing — it manufactures exactly the false green Reticle exists to
11
- * eliminate. Electron's `webContents.capturePage()` reads the window's own backing store instead:
12
- * correct while occluded, correct while backgrounded, and needing no screen-recording permission.
13
- *
14
- * Both desktop runtimes can do this, each through its own shell API — `webContents.capturePage()` on
15
- * Electron, and on Tauri the platform's own webview snapshot (`WKWebView.takeSnapshot`, WebView2
16
- * `CapturePreview`, or WebKitGTK's snapshot). None of them reads the screen, so all stay correct with
17
- * the window occluded, hidden, or running headless with nothing on screen at all.
18
- *
19
- * The app opts in with one line in its main process (`@reticlehq/electron/main`) or one Rust command
20
- * (`reticle-tauri`). Absent that, this returns `{ ok: false }` and the tool reports no-provider
21
- * rather than guessing.
22
- */
23
1
  interface CaptureResult {
24
2
  ok: boolean;
25
- /**
26
- * Filesystem path of the captured PNG, written by the desktop shell.
27
- *
28
- * A path, not the image bytes: the SDK's transport sanitizer caps every string at 64KB, so a
29
- * base64 PNG came back SILENTLY TRUNCATED and was saved as a "successful" screenshot that no
30
- * decoder could read. The daemon and the app always share a machine here (the bridge is loopback),
31
- * so handing over a path keeps the image off the event wire entirely.
32
- */
33
3
  path?: string;
34
4
  reason?: string;
35
5
  }
@@ -1,59 +1,12 @@
1
- /**
2
- * Desktop screenshots, asked of the runtime that owns the window.
3
- *
4
- * A browser tab is captured through CDP. A desktop webview has no CDP endpoint, so the pixels have
5
- * to come from the shell — and the shell is the only party that can produce them CORRECTLY.
6
- *
7
- * Capturing a screen *region* instead was tried and rejected: it photographs the glass, so an app
8
- * window sitting behind the editor yields a picture of the editor, saved as a visual baseline that a
9
- * later diff would trust. A screenshot tool that can silently return another window's pixels is
10
- * worse than one that returns nothing — it manufactures exactly the false green Reticle exists to
11
- * eliminate. Electron's `webContents.capturePage()` reads the window's own backing store instead:
12
- * correct while occluded, correct while backgrounded, and needing no screen-recording permission.
13
- *
14
- * Both desktop runtimes can do this, each through its own shell API — `webContents.capturePage()` on
15
- * Electron, and on Tauri the platform's own webview snapshot (`WKWebView.takeSnapshot`, WebView2
16
- * `CapturePreview`, or WebKitGTK's snapshot). None of them reads the screen, so all stay correct with
17
- * the window occluded, hidden, or running headless with nothing on screen at all.
18
- *
19
- * The app opts in with one line in its main process (`@reticlehq/electron/main`) or one Rust command
20
- * (`reticle-tauri`). Absent that, this returns `{ ok: false }` and the tool reports no-provider
21
- * rather than guessing.
22
- */
23
1
  import { RETICLE_IPC_GLOBAL, RETICLE_TAURI_CAPTURE_COMMAND, VisualReason } from '@reticlehq/core';
24
2
  import { nativeFrame } from '../timers/native-timers.js';
25
3
  import { RETICLE_OVERLAY } from './dom-ignore.js';
26
- /**
27
- * What a shell returns when it can photograph the viewport but not the whole scroll height.
28
- *
29
- * Only WebKitGTK can render a full document offscreen; `capturePage()` and `takeSnapshot` both give
30
- * what is composited. Downgrading silently would be the worst option available: the caller asked for
31
- * the content below the fold, would get an image without it, and would bank it as a baseline that a
32
- * later diff reports as matching. So the shells say so, and the tool reports the reason.
33
- */
34
4
  const FULL_PAGE_UNSUPPORTED_MARKER = VisualReason.FULL_PAGE_UNSUPPORTED;
35
- /**
36
- * Reasons a shell can name, in the order they are matched.
37
- *
38
- * The shell throws one of these rather than answering a bare null, because "nothing came back" was
39
- * covering three different problems at once: a window with no composited frame yet, a webContents
40
- * that had gone away, and any error at all. They need different responses from the reader, and the
41
- * tool reported them identically.
42
- */
43
5
  const RECOGNISED_MARKERS = [
44
6
  FULL_PAGE_UNSUPPORTED_MARKER,
45
7
  VisualReason.NOT_COMPOSITED,
46
8
  ];
47
9
  const TAURI_INTERNALS_GLOBAL = '__TAURI_INTERNALS__';
48
- /**
49
- * Tauri's capture path, used when no preload-installed channel exists.
50
- *
51
- * Electron can install `RETICLE_IPC_GLOBAL` from its preload, which runs before app code. Tauri has
52
- * no preload stage at all, so requiring the same global would mean every Tauri app hand-writing a
53
- * frontend shim — a setup step that silently yields "no screenshots" when forgotten. Invoking the
54
- * command directly removes the JavaScript side entirely: the app registers one Rust command and the
55
- * SDK finds it. Absent that command the invoke rejects, and this reports no-provider like any other.
56
- */
57
10
  function tauriCapture() {
58
11
  const internals = window[TAURI_INTERNALS_GLOBAL];
59
12
  if (typeof internals?.invoke !== 'function')
@@ -64,31 +17,9 @@ function tauriCapture() {
64
17
  return 'string' === typeof path ? path : null;
65
18
  };
66
19
  }
67
- /**
68
- * Take Reticle's own UI out of the shot, then put it back.
69
- *
70
- * The shell photographs the composited window, so anything Reticle mounted is IN the picture — and
71
- * the presenter panel is the worst possible thing to bank into a visual baseline, because it renders
72
- * live session state: an event tally, a log tail, a run badge. A baseline captured with it is a
73
- * baseline of Reticle's own UI as much as the app's, and the next `reticle_visual_diff` reports a
74
- * change that belongs entirely to the observer. That is measuring the instrument.
75
- *
76
- * Two frames, not one: the first lets the style change apply, the second lets the compositor present
77
- * it — capture before that and the shell photographs the pre-hide window anyway. Both are BOUNDED
78
- * frames, which is load-bearing rather than defensive: a headless Tauri window is hidden, and a
79
- * hidden webview never fires `requestAnimationFrame` at all, so a raw double-rAF here deadlocked
80
- * every capture until the command timed out at 8s. Restoration runs in a `finally`, so a capture
81
- * that throws still leaves the panel on screen for the human watching.
82
- */
83
20
  async function withReticleUiHidden(capture) {
84
- // The SAME selector the snapshot layer already uses to keep Reticle's own DOM out of what the
85
- // agent reads — the presenter panel, the cursor, the glow, and the annotator's "flag a bug"
86
- // button. One definition: a root that leaked into snapshots once would otherwise leak into
87
- // baselines the second time someone added one.
88
21
  const roots = [...document.querySelectorAll(RETICLE_OVERLAY)];
89
22
  const previous = roots.map((el) => el.style.visibility);
90
- // `visibility`, not `display`: it keeps the element's box, so hiding cannot reflow the app
91
- // underneath and change the very layout the screenshot is meant to record.
92
23
  for (const el of roots)
93
24
  el.style.visibility = 'hidden';
94
25
  try {
@@ -118,9 +49,6 @@ export async function captureDesktopWindow(fullPage = false) {
118
49
  }
119
50
  catch (error) {
120
51
  const reason = error instanceof Error ? error.message : String(error);
121
- // Recognised markers are reported as the reason ITSELF, so the tool can branch on a closed
122
- // vocabulary rather than on prose. Everything else keeps its own message: dressing an unknown
123
- // failure up as a known one would send the reader to the wrong place entirely.
124
52
  for (const marker of RECOGNISED_MARKERS) {
125
53
  if (reason.includes(marker))
126
54
  return { ok: false, reason: marker };
@@ -1,37 +1,7 @@
1
- /**
2
- * Selectors for Reticle's own presenter overlay (cursor, HUD, glow) + the annotator's
3
- * UI (`data-reticle-mark`) - never observed/snapshotted. The annotator mounts by DEFAULT with the
4
- * presenter, so omitting its selector here leaked annotation chrome into every snapshot.
5
- */
6
1
  export declare const RETICLE_OVERLAY: string;
7
- /**
8
- * Make Reticle's own presenter visible to snapshots and queries. Contributors only.
9
- *
10
- * Deliberately a function rather than a field on the ignore set, so the one place that decides
11
- * "is this ours" also decides "may it be seen", and no caller can half-apply it.
12
- */
13
2
  export declare function setPresenterVisible(visible: boolean): void;
14
- /** Whether the hatch is open — reported in the app's capabilities so a verdict is never mistaken. */
15
3
  export declare function isPresenterVisible(): boolean;
16
- /** Let the host app add selectors to exclude from snapshots (e.g. its own dev widgets). */
17
4
  export declare function setIgnoreSelectors(selectors: string[]): void;
18
- /** True if the element is part of Reticle's own presenter overlay. */
19
5
  export declare function isReticleOverlay(el: Element): boolean;
20
- /**
21
- * True iff the element is part of Reticle's OWN UI - the presenter overlay, the HUD, the synthetic
22
- * cursor, the glow, or the annotator's marks - or lives inside one of them.
23
- *
24
- * The rule used to be "any ancestor carries a data-reticle* attribute", which is wrong twice over.
25
- * `data-reticle-mark-active` sits on <html> while annotate mode is live, so the whole document
26
- * answered yes; and `data-reticle-source` is stamped by the Vite/Babel plugins on every element the
27
- * APP renders, so in an instrumented app - which is the only kind there is - most of the page
28
- * answered yes too. Those attributes describe page content; they do not make it ours.
29
- *
30
- * Two things read this and both failed silently. `pageElementAt` (annotator) skipped every stamped
31
- * element and anchored the note to the outermost unstamped ancestor, i.e. the app shell instead of
32
- * the control under the cursor. `occlusion.ts` reads a yes as "nothing to report", so occlusion
33
- * detection - a bug class Reticle advertises catching - came back clean wherever the stamp reached.
34
- */
35
6
  export declare function isReticleUi(node: Element | null): boolean;
36
- /** True if the element should be excluded from snapshots/queries (Reticle overlay or dev overlay). */
37
7
  export declare function isIgnored(el: Element): boolean;