@reticlehq/browser 2.13.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/dist/actions/actions-dom.d.ts +8 -20
  2. package/dist/actions/actions-dom.js +70 -69
  3. package/dist/actions/actions-press.d.ts +14 -0
  4. package/dist/actions/actions-press.js +123 -0
  5. package/dist/actions/actions.d.ts +1 -89
  6. package/dist/actions/actions.js +64 -365
  7. package/dist/actions/appeared-text.d.ts +0 -20
  8. package/dist/actions/appeared-text.js +6 -36
  9. package/dist/actions/danger-context.d.ts +3 -0
  10. package/dist/actions/danger-context.js +39 -0
  11. package/dist/actions/scroll.d.ts +8 -10
  12. package/dist/actions/scroll.js +28 -17
  13. package/dist/actions/synthetic/synthetic-input.d.ts +2 -0
  14. package/dist/actions/synthetic/synthetic-input.js +13 -0
  15. package/dist/actions/value-input.d.ts +0 -30
  16. package/dist/actions/value-input.js +0 -33
  17. package/dist/commands/commands.d.ts +0 -2
  18. package/dist/commands/commands.js +10 -70
  19. package/dist/connect-options.d.ts +2 -96
  20. package/dist/dom/a11y.d.ts +0 -29
  21. package/dist/dom/a11y.js +3 -90
  22. package/dist/dom/addressing/auto-anchor.d.ts +25 -0
  23. package/dist/dom/addressing/auto-anchor.js +41 -0
  24. package/dist/dom/addressing/refs.d.ts +14 -0
  25. package/dist/dom/addressing/refs.js +114 -0
  26. package/dist/dom/addressing/source.d.ts +9 -0
  27. package/dist/dom/addressing/source.js +29 -0
  28. package/dist/dom/chart.d.ts +0 -61
  29. package/dist/dom/chart.js +0 -60
  30. package/dist/dom/desktop-capture.d.ts +0 -30
  31. package/dist/dom/desktop-capture.js +1 -73
  32. package/dist/dom/dom-ignore.d.ts +0 -30
  33. package/dist/dom/dom-ignore.js +1 -48
  34. package/dist/dom/occlusion.d.ts +0 -7
  35. package/dist/dom/occlusion.js +31 -12
  36. package/dist/dom/paint-context.d.ts +6 -0
  37. package/dist/dom/paint-context.js +38 -0
  38. package/dist/dom/query.d.ts +0 -14
  39. package/dist/dom/query.js +50 -216
  40. package/dist/dom/realm.d.ts +0 -20
  41. package/dist/dom/realm.js +0 -28
  42. package/dist/dom/shadow-registry.d.ts +0 -33
  43. package/dist/dom/shadow-registry.js +2 -72
  44. package/dist/dom/snapshot.d.ts +2 -43
  45. package/dist/dom/snapshot.js +27 -152
  46. package/dist/dom/theme.d.ts +0 -37
  47. package/dist/dom/theme.js +2 -52
  48. package/dist/dom/virtualized.d.ts +0 -17
  49. package/dist/dom/virtualized.js +0 -27
  50. package/dist/edit-epoch.d.ts +1 -23
  51. package/dist/edit-epoch.js +1 -53
  52. package/dist/index.d.ts +4 -5
  53. package/dist/index.js +4 -28
  54. package/dist/observers/animation.d.ts +0 -1
  55. package/dist/observers/animation.js +1 -4
  56. package/dist/observers/blind-spots.d.ts +0 -26
  57. package/dist/observers/blind-spots.js +3 -62
  58. package/dist/observers/console.d.ts +1 -2
  59. package/dist/observers/console.js +41 -30
  60. package/dist/observers/context-open.d.ts +1 -11
  61. package/dist/observers/context-open.js +10 -14
  62. package/dist/observers/departure.d.ts +3 -0
  63. package/dist/observers/departure.js +52 -0
  64. package/dist/observers/dialogs.d.ts +2 -0
  65. package/dist/observers/dialogs.js +39 -0
  66. package/dist/observers/dom.js +28 -52
  67. package/dist/observers/download.d.ts +1 -2
  68. package/dist/observers/download.js +39 -56
  69. package/dist/observers/focus.d.ts +0 -6
  70. package/dist/observers/focus.js +0 -7
  71. package/dist/observers/frames.d.ts +0 -9
  72. package/dist/observers/frames.js +1 -28
  73. package/dist/observers/health.d.ts +0 -5
  74. package/dist/observers/health.js +2 -34
  75. package/dist/observers/install-all.d.ts +2 -0
  76. package/dist/observers/install-all.js +26 -27
  77. package/dist/observers/ipc.d.ts +0 -25
  78. package/dist/observers/ipc.js +0 -65
  79. package/dist/observers/navigation.d.ts +2 -0
  80. package/dist/observers/navigation.js +33 -0
  81. package/dist/observers/net-detail/network-body.d.ts +12 -0
  82. package/dist/observers/net-detail/network-body.js +71 -0
  83. package/dist/observers/net-detail/network-redact.d.ts +1 -0
  84. package/dist/observers/net-detail/network-redact.js +1 -0
  85. package/dist/observers/network-stream.d.ts +0 -21
  86. package/dist/observers/network-stream.js +3 -46
  87. package/dist/observers/network.d.ts +3 -27
  88. package/dist/observers/network.js +178 -295
  89. package/dist/observers/perf.d.ts +0 -10
  90. package/dist/observers/perf.js +0 -16
  91. package/dist/observers/route.d.ts +1 -2
  92. package/dist/observers/route.js +13 -19
  93. package/dist/observers/scroll.d.ts +0 -1
  94. package/dist/observers/scroll.js +2 -11
  95. package/dist/observers/sdk-failure.d.ts +1 -9
  96. package/dist/observers/sdk-failure.js +1 -30
  97. package/dist/observers/state.d.ts +1 -11
  98. package/dist/observers/state.js +13 -41
  99. package/dist/observers/storage.d.ts +0 -14
  100. package/dist/observers/storage.js +10 -34
  101. package/dist/observers/types.d.ts +0 -15
  102. package/dist/observers/types.js +0 -14
  103. package/dist/patching/capture-method.d.ts +0 -24
  104. package/dist/patching/capture-method.js +0 -26
  105. package/dist/presenter/chrome/layers.d.ts +2 -0
  106. package/dist/presenter/chrome/layers.js +2 -0
  107. package/dist/presenter/chrome/log-kinds.d.ts +12 -0
  108. package/dist/presenter/chrome/log-kinds.js +7 -0
  109. package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -1
  110. package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -1
  111. package/dist/presenter/chrome/presenter-brand.d.ts +3 -0
  112. package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -21
  113. package/dist/presenter/chrome/presenter-effects.d.ts +4 -0
  114. package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -10
  115. package/dist/presenter/chrome/presenter-hud-chrome.d.ts +7 -0
  116. package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -10
  117. package/dist/presenter/chrome/presenter-log.d.ts +17 -0
  118. package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +5 -54
  119. package/dist/presenter/chrome/presenter-report-copy.d.ts +51 -0
  120. package/dist/presenter/chrome/presenter-report-copy.js +95 -0
  121. package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
  122. package/dist/presenter/chrome/presenter-safe-html.js +16 -0
  123. package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -5
  124. package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -5
  125. package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -7
  126. package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -8
  127. package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -8
  128. package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -6
  129. package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -6
  130. package/dist/presenter/presenter-account-styles.d.ts +1 -0
  131. package/dist/presenter/presenter-account-styles.js +99 -0
  132. package/dist/presenter/presenter-account.d.ts +40 -0
  133. package/dist/presenter/presenter-account.js +178 -0
  134. package/dist/presenter/presenter-config.d.ts +10 -107
  135. package/dist/presenter/presenter-config.js +5 -92
  136. package/dist/presenter/presenter-controls.d.ts +1 -35
  137. package/dist/presenter/presenter-controls.js +10 -133
  138. package/dist/presenter/presenter-dock-layout.d.ts +0 -3
  139. package/dist/presenter/presenter-dock-layout.js +1 -5
  140. package/dist/presenter/presenter-drag.d.ts +2 -42
  141. package/dist/presenter/presenter-drag.js +3 -76
  142. package/dist/presenter/presenter-glow.d.ts +0 -21
  143. package/dist/presenter/presenter-glow.js +2 -28
  144. package/dist/presenter/presenter-hud-position.d.ts +18 -0
  145. package/dist/presenter/presenter-hud-position.js +50 -0
  146. package/dist/presenter/presenter-report-styles.d.ts +0 -6
  147. package/dist/presenter/presenter-report-styles.js +22 -7
  148. package/dist/presenter/presenter-report.d.ts +5 -7
  149. package/dist/presenter/presenter-report.js +44 -95
  150. package/dist/presenter/presenter-run-state.d.ts +1 -8
  151. package/dist/presenter/presenter-run-state.js +1 -6
  152. package/dist/presenter/presenter-settings-styles.d.ts +0 -1
  153. package/dist/presenter/presenter-settings-styles.js +2 -2
  154. package/dist/presenter/presenter-settings.d.ts +5 -23
  155. package/dist/presenter/presenter-settings.js +21 -33
  156. package/dist/presenter/presenter-shell-styles.js +23 -20
  157. package/dist/presenter/presenter-shell.d.ts +6 -18
  158. package/dist/presenter/presenter-shell.js +26 -77
  159. package/dist/presenter/presenter-styles.d.ts +2 -10
  160. package/dist/presenter/presenter-styles.js +6 -12
  161. package/dist/presenter/presenter-workspace.d.ts +4 -4
  162. package/dist/presenter/presenter-workspace.js +30 -10
  163. package/dist/presenter/presenter.d.ts +3 -63
  164. package/dist/presenter/presenter.js +29 -132
  165. package/dist/presenter/tour/tour-view.d.ts +33 -0
  166. package/dist/presenter/tour/tour-view.js +188 -0
  167. package/dist/presenter/tour/tour.d.ts +25 -0
  168. package/dist/presenter/tour/tour.js +245 -0
  169. package/dist/recorder/recorder-styles.d.ts +0 -6
  170. package/dist/recorder/recorder-styles.js +0 -6
  171. package/dist/recorder/recorder.d.ts +0 -34
  172. package/dist/recorder/recorder.js +6 -49
  173. package/dist/registry/auto-testids.d.ts +0 -19
  174. package/dist/registry/auto-testids.js +1 -21
  175. package/dist/registry/capabilities.d.ts +0 -31
  176. package/dist/registry/capabilities.js +1 -46
  177. package/dist/registry/commit-and-signal.d.ts +0 -6
  178. package/dist/registry/commit-and-signal.js +1 -7
  179. package/dist/registry/declared-channels.d.ts +2 -0
  180. package/dist/registry/declared-channels.js +14 -0
  181. package/dist/registry/declared-platform.d.ts +4 -0
  182. package/dist/registry/declared-platform.js +34 -0
  183. package/dist/registry/domains.d.ts +0 -8
  184. package/dist/registry/domains.js +0 -7
  185. package/dist/registry/emitter.d.ts +0 -14
  186. package/dist/registry/emitter.js +1 -11
  187. package/dist/registry/store-adapters.d.ts +0 -186
  188. package/dist/registry/store-adapters.js +1 -164
  189. package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -10
  190. package/dist/registry/{adapters.js → stores/adapters.js} +0 -10
  191. package/dist/registry/stores.d.ts +0 -33
  192. package/dist/registry/stores.js +1 -60
  193. package/dist/reticle-presenter-helpers.d.ts +0 -13
  194. package/dist/reticle-presenter-helpers.js +10 -20
  195. package/dist/reticle-singleton.d.ts +2 -0
  196. package/dist/reticle-singleton.js +3 -0
  197. package/dist/reticle.d.ts +1 -52
  198. package/dist/reticle.js +115 -180
  199. package/dist/review/annotator-styles.js +1 -8
  200. package/dist/review/annotator.d.ts +2 -13
  201. package/dist/review/annotator.js +2 -42
  202. package/dist/review/mark-anchor.d.ts +0 -11
  203. package/dist/review/mark-anchor.js +3 -11
  204. package/dist/security/serialization.d.ts +0 -20
  205. package/dist/security/serialization.js +1 -66
  206. package/dist/session-continuity.d.ts +0 -16
  207. package/dist/session-continuity.js +1 -20
  208. package/dist/timers/clock.d.ts +1 -14
  209. package/dist/timers/clock.js +59 -60
  210. package/dist/timers/console/native-console.js +5 -0
  211. package/dist/timers/native/native-timers.d.ts +11 -0
  212. package/dist/timers/native/native-timers.js +48 -0
  213. package/dist/transport/transport.d.ts +0 -51
  214. package/dist/transport/transport.js +5 -131
  215. package/dist/transport/unreachable-message.d.ts +0 -17
  216. package/dist/transport/unreachable-message.js +0 -17
  217. package/dist/util/captured-method.d.ts +0 -35
  218. package/dist/util/captured-method.js +0 -35
  219. package/package.json +5 -4
  220. package/dist/actions/synthetic-input.d.ts +0 -9
  221. package/dist/actions/synthetic-input.js +0 -40
  222. package/dist/dom/auto-anchor.d.ts +0 -65
  223. package/dist/dom/auto-anchor.js +0 -74
  224. package/dist/dom/refs.d.ts +0 -90
  225. package/dist/dom/refs.js +0 -245
  226. package/dist/dom/source.d.ts +0 -53
  227. package/dist/dom/source.js +0 -74
  228. package/dist/observers/network-body.d.ts +0 -35
  229. package/dist/observers/network-body.js +0 -168
  230. package/dist/observers/network-redact.d.ts +0 -20
  231. package/dist/observers/network-redact.js +0 -80
  232. package/dist/presenter/presenter-brand.d.ts +0 -22
  233. package/dist/presenter/presenter-effects.d.ts +0 -13
  234. package/dist/presenter/presenter-hud-chrome.d.ts +0 -17
  235. package/dist/presenter/presenter-log.d.ts +0 -64
  236. package/dist/presenter/presenter-report-copy.d.ts +0 -112
  237. package/dist/presenter/presenter-report-copy.js +0 -156
  238. package/dist/presenter/presenter-test-helpers.d.ts +0 -16
  239. package/dist/presenter/presenter-test-helpers.js +0 -41
  240. package/dist/timers/native-console.js +0 -9
  241. package/dist/timers/native-timers.d.ts +0 -36
  242. package/dist/timers/native-timers.js +0 -78
  243. /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
  244. /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
@@ -1,12 +1,2 @@
1
1
  import type { Emit, Teardown } from './types.js';
2
- /**
3
- * Observe the web-perf signals a screenshot tool fundamentally cannot verify — largest-contentful-paint
4
- * (LCP), cumulative layout shift (CLS), and long tasks — and emit them into the ring buffer so an agent
5
- * can assert "no layout shift on load" or "LCP under 2.5s". No-ops when PerformanceObserver or a given
6
- * entry type is unavailable (jsdom / older browsers), so it never throws in an unsupported context.
7
- *
8
- * Each event carries `at` (the entry's own startTime) so a consumer can reason about WHEN the metric
9
- * occurred rather than when it was flushed — buffered:true replays pre-install entries, whose emit time
10
- * would otherwise be install time.
11
- */
12
2
  export declare function installPerf(emit: Emit): Teardown;
@@ -1,14 +1,4 @@
1
1
  import { EventType, PerfMetric } from '@reticlehq/core';
2
- /**
3
- * Observe the web-perf signals a screenshot tool fundamentally cannot verify — largest-contentful-paint
4
- * (LCP), cumulative layout shift (CLS), and long tasks — and emit them into the ring buffer so an agent
5
- * can assert "no layout shift on load" or "LCP under 2.5s". No-ops when PerformanceObserver or a given
6
- * entry type is unavailable (jsdom / older browsers), so it never throws in an unsupported context.
7
- *
8
- * Each event carries `at` (the entry's own startTime) so a consumer can reason about WHEN the metric
9
- * occurred rather than when it was flushed — buffered:true replays pre-install entries, whose emit time
10
- * would otherwise be install time.
11
- */
12
2
  export function installPerf(emit) {
13
3
  if (typeof PerformanceObserver !== 'function')
14
4
  return () => undefined;
@@ -23,14 +13,9 @@ export function installPerf(emit) {
23
13
  observers.push(po);
24
14
  }
25
15
  catch {
26
- /* entry type unsupported in this browser — skip it, leave the others installed */
27
16
  }
28
17
  };
29
- // CLS is CUMULATIVE: sum qualifying shifts and report the running total, not each isolated shift.
30
- // ponytail: naive running sum — no session-windowing (5s window / 1s gap); enough for
31
- // "no layout shift on load" and trend, upgrade to windowed sessions if a CWV-exact number is needed.
32
18
  let cls = 0;
33
- // LCP only grows across candidates — surface a value only when it exceeds the last, not every candidate.
34
19
  let lcp = 0;
35
20
  observe('largest-contentful-paint', (e) => {
36
21
  const value = Math.round(e.startTime);
@@ -41,7 +26,6 @@ export function installPerf(emit) {
41
26
  });
42
27
  observe('layout-shift', (e) => {
43
28
  const ls = e;
44
- // Shifts within 500ms of a user input are expected (not CLS) — the spec's hadRecentInput flag.
45
29
  if (true === ls.hadRecentInput)
46
30
  return;
47
31
  cls += ls.value ?? 0;
@@ -1,3 +1,2 @@
1
- import type { Emit, Teardown } from './types.js';
2
- /** Patch the History API + listen to popstate/hashchange to emit route.change. */
1
+ import { type Emit, type Teardown } from './types.js';
3
2
  export declare function installRoute(emit: Emit): Teardown;
@@ -1,4 +1,5 @@
1
1
  import { EventType } from '@reticlehq/core';
2
+ import { observeSafely } from './types.js';
2
3
  import { captureMethod } from '../patching/capture-method.js';
3
4
  function snapshotLocation() {
4
5
  return {
@@ -8,29 +9,25 @@ function snapshotLocation() {
8
9
  href: location.href,
9
10
  };
10
11
  }
11
- /** Patch the History API + listen to popstate/hashchange to emit route.change. */
12
12
  export function installRoute(emit) {
13
- // Keep the true originals for teardown identity; bound copies are used for invocation
14
- // (History methods throw "Illegal invocation" if called with the wrong `this`).
15
13
  const origPush = captureMethod(history, 'pushState');
16
14
  const origReplace = captureMethod(history, 'replaceState');
17
15
  const callPush = origPush.bind(history);
18
16
  const callReplace = origReplace.bind(history);
19
- // The `from` baseline for the NEXT popstate/hashchange. Updated in fire on every emitted change
20
- // — including pushState/replaceState — so a later Back nav isn't compared against a stale href
21
- // (which made `to === from` trip and silently drop the back navigation).
22
17
  let lastHref = location.href;
23
18
  const fire = (from) => {
24
- const to = snapshotLocation();
25
- if (to.href === from)
26
- return;
27
- lastHref = to.href;
28
- emit(EventType.ROUTE_CHANGE, {
29
- from,
30
- to: to.href,
31
- pathname: to.pathname,
32
- search: to.search,
33
- hash: to.hash,
19
+ observeSafely(() => {
20
+ const to = snapshotLocation();
21
+ if (to.href === from)
22
+ return;
23
+ lastHref = to.href;
24
+ emit(EventType.ROUTE_CHANGE, {
25
+ from,
26
+ to: to.href,
27
+ pathname: to.pathname,
28
+ search: to.search,
29
+ hash: to.hash,
30
+ });
34
31
  });
35
32
  };
36
33
  const patchedPush = (data, unused, url) => {
@@ -51,9 +48,6 @@ export function installRoute(emit) {
51
48
  window.addEventListener('popstate', onNav);
52
49
  window.addEventListener('hashchange', onNav);
53
50
  return () => {
54
- // Restore ONLY if the slot still holds our wrapper. If a router/analytics SDK wrapped
55
- // history.pushState AFTER connect(), unconditionally writing origPush back would silently
56
- // uninstall THEIR instrumentation too — the SDK harming the app it only meant to observe.
57
51
  if (history.pushState === patchedPush)
58
52
  history.pushState = origPush;
59
53
  if (history.replaceState === patchedReplace)
@@ -1,3 +1,2 @@
1
1
  import type { Emit, Teardown } from './types.js';
2
- /** Observe scroll position + reveal-on-scroll for modern scroll-reactive UIs. */
3
2
  export declare function installScroll(emit: Emit): Teardown;
@@ -1,9 +1,8 @@
1
1
  import { EventType, ScrollDirection } from '@reticlehq/core';
2
- import { refs } from '../dom/refs.js';
3
- import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
2
+ import { refs } from '../dom/addressing/refs.js';
3
+ import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
4
4
  const THROTTLE_MS = 100;
5
5
  const REVEAL_SELECTOR = '[data-reticle-reveal], [data-reveal], section';
6
- /** Observe scroll position + reveal-on-scroll for modern scroll-reactive UIs. */
7
6
  export function installScroll(emit) {
8
7
  const ac = new AbortController();
9
8
  const { signal } = ac;
@@ -32,8 +31,6 @@ export function installScroll(emit) {
32
31
  emitPosition();
33
32
  return;
34
33
  }
35
- // Leading-edge only dropped the FINAL, resting position — an agent asserting "scrolled to the
36
- // footer" saw the last mid-scroll sample, not where the page settled. Schedule a trailing emit.
37
34
  if (trailingTimer === undefined) {
38
35
  trailingTimer = nativeSetTimeout(() => {
39
36
  trailingTimer = undefined;
@@ -52,12 +49,6 @@ export function installScroll(emit) {
52
49
  }
53
50
  }, { threshold: 0.25 });
54
51
  const observer = io;
55
- // Observe reveal targets present at install. Reveal-on-scroll sections are in the initial DOM by
56
- // design (they exist hidden and animate in on scroll), so this covers the real case. Watching the
57
- // whole body subtree for LATE-mounted reveal targets was tried and reverted: a second body-wide
58
- // MutationObserver on EVERY app — running querySelectorAll(section,...) per mutation batch — is
59
- // disproportionate overhead for a niche signal. Dynamically-mounted reveal targets are simply not
60
- // tracked (an honest blind spot, consistent with the rest of the SDK).
61
52
  for (const el of document.querySelectorAll(REVEAL_SELECTOR))
62
53
  observer.observe(el);
63
54
  }
@@ -1,9 +1,4 @@
1
1
  import type { Emit } from './types.js';
2
- /**
3
- * Where in the SDK a failure happened. A fixed vocabulary of OUR module names — never a file path,
4
- * never anything derived from the user's app — so the field stays low-cardinality and non-identifying
5
- * while still telling a maintainer which subsystem to open.
6
- */
7
2
  export declare const SdkSite: {
8
3
  readonly CONNECT: "connect";
9
4
  readonly DOM_OBSERVER: "dom_observer";
@@ -19,10 +14,7 @@ export declare const SdkSite: {
19
14
  readonly COMMAND: "command";
20
15
  readonly RECORDER: "recorder";
21
16
  readonly TRANSPORT: "transport";
17
+ readonly TEARDOWN: "teardown";
22
18
  };
23
19
  export type SdkSite = (typeof SdkSite)[keyof typeof SdkSite];
24
- /**
25
- * Report that a piece of the SDK failed. Never throws, and never prevents the caller from doing what
26
- * it was going to do anyway (which is almost always: carry on degraded).
27
- */
28
20
  export declare function reportSdkFailure(emit: Emit, site: SdkSite, error: unknown): void;
@@ -1,28 +1,4 @@
1
- /**
2
- * The SDK reporting its OWN failures.
3
- *
4
- * Half of Reticle runs inside the page, and that half was completely silent. Every `catch` in the SDK
5
- * swallows by design — correctly, because an instrumentation layer must never break the app it
6
- * observes — but the consequence was that an observer throwing on page load just produced a quieter
7
- * product. No error, no signal, no way to know. A user would report "Reticle doesn't see my network
8
- * calls" and there would be nothing on our side to look at.
9
- *
10
- * Two properties make this safe to add:
11
- *
12
- * 1. IT MAKES NO NETWORK REQUEST. The report rides the bridge WebSocket the SDK is already connected
13
- * to — localhost, the same channel every other event uses. The published promise that the SDK
14
- * talks only to a local bridge stays exactly true, and the daemon decides what (if anything) goes
15
- * anywhere else. Adding an outbound call from inside someone's app would have been a different
16
- * product with a different privacy posture.
17
- * 2. IT CANNOT BREAK THE APP. Reporting is wrapped in its own try/catch and is strictly additive to
18
- * an existing `catch` block. A failure to report a failure is discarded.
19
- */
20
1
  import { EventType } from '@reticlehq/core';
21
- /**
22
- * Where in the SDK a failure happened. A fixed vocabulary of OUR module names — never a file path,
23
- * never anything derived from the user's app — so the field stays low-cardinality and non-identifying
24
- * while still telling a maintainer which subsystem to open.
25
- */
26
2
  export const SdkSite = {
27
3
  CONNECT: 'connect',
28
4
  DOM_OBSERVER: 'dom_observer',
@@ -38,13 +14,9 @@ export const SdkSite = {
38
14
  COMMAND: 'command',
39
15
  RECORDER: 'recorder',
40
16
  TRANSPORT: 'transport',
17
+ TEARDOWN: 'teardown',
41
18
  };
42
- /** Cap the message here too — the server strips variables, but an unbounded string is a bad idea twice. */
43
19
  const MAX_MESSAGE = 500;
44
- /**
45
- * Report that a piece of the SDK failed. Never throws, and never prevents the caller from doing what
46
- * it was going to do anyway (which is almost always: carry on degraded).
47
- */
48
20
  export function reportSdkFailure(emit, site, error) {
49
21
  try {
50
22
  const message = error instanceof Error ? error.message : String(error);
@@ -55,6 +27,5 @@ export function reportSdkFailure(emit, site, error) {
55
27
  });
56
28
  }
57
29
  catch {
58
- /* a failure report must never become a second failure */
59
30
  }
60
31
  }
@@ -1,19 +1,9 @@
1
- import type { Emit, Teardown } from './types.js';
1
+ import { type Emit, type Teardown } from './types.js';
2
2
  interface StateChange {
3
3
  path: string;
4
4
  old: unknown;
5
5
  new: unknown;
6
6
  }
7
- /**
8
- * Shallow top-level diff of two store states — the keys whose value changed. Immutable updates
9
- * (Zustand/Redux) replace the changed key's reference, so `Object.is` catches them. Pure; deep-nested
10
- * diffing behind an unchanged top-level reference is a v2 concern.
11
- */
12
7
  export declare function diffState(prev: unknown, next: unknown): StateChange[];
13
- /**
14
- * Subscribe to every registered store that exposed a subscribe method, emitting a STATE_CHANGE per
15
- * changed top-level path with {name, path, old, new} — automatic diffs, not pull-only readings. Stores
16
- * without a subscribe fall back to the pull path (reticle_state). Fully reversible.
17
- */
18
8
  export declare function installStoreState(emit: Emit): Teardown;
19
9
  export {};
@@ -1,14 +1,10 @@
1
1
  import { BlindSpotKind, EventType, REDACTED_VALUE } from '@reticlehq/core';
2
2
  import { subscribableStores, onStoreRegistered, } from '../registry/stores.js';
3
3
  import { isSensitiveKey, sanitizeForTransport } from '../security/serialization.js';
4
+ import { observeSafely } from './types.js';
4
5
  function isRecord(value) {
5
6
  return 'object' === typeof value && value !== null && !Array.isArray(value);
6
7
  }
7
- /**
8
- * Shallow top-level diff of two store states — the keys whose value changed. Immutable updates
9
- * (Zustand/Redux) replace the changed key's reference, so `Object.is` catches them. Pure; deep-nested
10
- * diffing behind an unchanged top-level reference is a v2 concern.
11
- */
12
8
  export function diffState(prev, next) {
13
9
  if (!isRecord(prev) || !isRecord(next)) {
14
10
  return Object.is(prev, next) ? [] : [{ path: '', old: prev, new: next }];
@@ -20,7 +16,6 @@ export function diffState(prev, next) {
20
16
  }
21
17
  return changes;
22
18
  }
23
- /** Redact a changed value when its path is credential-bearing, else cap it for transport. */
24
19
  function project(path, value) {
25
20
  return isSensitiveKey(path) ? REDACTED_VALUE : sanitizeForTransport(value);
26
21
  }
@@ -32,60 +27,38 @@ function safeRead(getter) {
32
27
  return undefined;
33
28
  }
34
29
  }
35
- /**
36
- * Subscribe to every registered store that exposed a subscribe method, emitting a STATE_CHANGE per
37
- * changed top-level path with {name, path, old, new} — automatic diffs, not pull-only readings. Stores
38
- * without a subscribe fall back to the pull path (reticle_state). Fully reversible.
39
- */
40
30
  export function installStoreState(emit) {
41
31
  const active = new Map();
42
32
  const watch = ([name, getter, subscribe]) => {
43
- // A re-registration under the same name is the HMR cycle: the store INSTANCE is replaced (new
44
- // getter + new subscribe). Skipping it (the old `seen` guard) left the subscription bound to the
45
- // DEAD getter forever and the live store invisible until a full reload. Rebind: drop the previous
46
- // subscription for this name, then subscribe the new tuple. Guard the old unsubscribe — if it throws
47
- // (a misbehaving store), the rebind must still proceed, or the live store stays invisible.
48
33
  try {
49
34
  active.get(name)?.();
50
35
  }
51
36
  catch {
52
- /* a faulty unsubscribe must not block re-binding to the new instance */
53
37
  }
54
38
  let last = safeRead(getter);
55
39
  active.set(name, subscribe(() => {
56
- const next = safeRead(getter);
57
- for (const change of diffState(last, next)) {
58
- emit(EventType.STATE_CHANGE, {
59
- name,
60
- path: change.path,
61
- value: project(change.path, change.new),
62
- old: project(change.path, change.old),
63
- });
64
- }
65
- last = next;
40
+ observeSafely(() => {
41
+ const next = safeRead(getter);
42
+ for (const change of diffState(last, next)) {
43
+ emit(EventType.STATE_CHANGE, {
44
+ name,
45
+ path: change.path,
46
+ value: project(change.path, change.new),
47
+ old: project(change.path, change.old),
48
+ });
49
+ }
50
+ last = next;
51
+ });
66
52
  }));
67
53
  };
68
- // Stores already registered...
69
54
  for (const entry of subscribableStores())
70
55
  watch(entry);
71
- /**
72
- * Declare an unwatched state channel — and clear it the moment one is watched.
73
- *
74
- * With nothing subscribed, every act reports `stateDiffs: []`, which reads as "the app changed no
75
- * state" when the truth is "nobody was looking". Emitted as a BLIND_SPOT so it travels the path
76
- * that already exists for everything the layer cannot see: bounding, never impeaching, so no
77
- * verdict turns UNKNOWN over it.
78
- */
79
56
  if (0 === active.size) {
80
57
  emit(EventType.BLIND_SPOT, { kind: BlindSpotKind.UNWATCHED_STATE, count: 1 });
81
58
  }
82
- //...and any registered LATER. The SDK installs observers during connect, but apps call
83
- // registerStore after that, so without this the common case subscribes to nothing.
84
59
  const offRegistered = onStoreRegistered((entry) => {
85
60
  const wasDark = 0 === active.size;
86
61
  watch(entry);
87
- // The channel just lit up. A blind spot that is never withdrawn is a permanent lie, and this one
88
- // is withdrawn in the ordinary case: apps register their stores AFTER connect() installs us.
89
62
  if (wasDark && active.size > 0) {
90
63
  emit(EventType.BLIND_SPOT, { kind: BlindSpotKind.UNWATCHED_STATE, count: 0 });
91
64
  }
@@ -97,7 +70,6 @@ export function installStoreState(emit) {
97
70
  unsubscribe();
98
71
  }
99
72
  catch {
100
- /* one faulty store unsubscribe must not abort tearing the rest down */
101
73
  }
102
74
  }
103
75
  active.clear();
@@ -1,22 +1,8 @@
1
1
  import { type Emit, type Teardown } from './types.js';
2
- /** The three readable client-side storage areas. httpOnly cookies are invisible to JS by design. */
3
2
  export interface StorageSnapshot {
4
3
  local: Record<string, string>;
5
4
  session: Record<string, string>;
6
5
  cookies: Record<string, string>;
7
6
  }
8
- /**
9
- * Read client-side storage on demand (the STORAGE_READ command). Powers `reticle_storage` — the
10
- * agent verifies "token persisted after login", "cart survived reload", "logout cleared the session"
11
- * from the app's real storage rather than inferring it from the DOM. `area` scopes to one area; omit
12
- * for all three. Sensitive keys are redacted; httpOnly cookies are unreadable by design (documented).
13
- */
14
7
  export declare function readStorage(area?: string): StorageSnapshot | Record<string, string>;
15
- /**
16
- * Observe storage WRITES (not just reads): patch Storage.setItem/removeItem so a token persisted, a
17
- * cart updated, or a session cleared emits a STORAGE_CHANGE {area, key, old?, new?} diff — the "before"
18
- * the pull path can never see. localStorage and sessionStorage share one prototype, so `this` inside the
19
- * patch identifies the area. The native originals are read off the prototype descriptor (not a bare
20
- * method access) and re-dispatched via `.call(this)`. Fully reversible; values redacted like the reads.
21
- */
22
8
  export declare function installStorage(emit: Emit): Teardown;
@@ -1,7 +1,6 @@
1
1
  import { EventType, REDACTED_VALUE, StorageArea } from '@reticlehq/core';
2
- import { isSensitiveKey } from '../security/serialization.js';
2
+ import { isSensitiveKey, scrubKnownSecrets } from '../security/serialization.js';
3
3
  import { observeSafely, observeValue } from './types.js';
4
- /** Accessing localStorage/sessionStorage throws in a sandboxed iframe / disabled-storage context. */
5
4
  function safeArea(get) {
6
5
  try {
7
6
  return get();
@@ -18,15 +17,13 @@ function readArea(storage) {
18
17
  const key = storage.key(i);
19
18
  if (null === key)
20
19
  continue;
21
- // Redact credential-bearing keys (token/session/password/…) so auth state never leaks verbatim.
22
- out[key] = isSensitiveKey(key) ? REDACTED_VALUE : (storage.getItem(key) ?? '');
20
+ const val = storage.getItem(key) ?? '';
21
+ out[key] = isSensitiveKey(key) ? REDACTED_VALUE : scrubKnownSecrets(val);
23
22
  }
24
23
  return out;
25
24
  }
26
25
  function readCookies() {
27
26
  const out = {};
28
- // Reading document.cookie can throw (SecurityError) in a sandboxed / cookie-disabled context — guard
29
- // it the same way safeArea guards localStorage, so one bad area never crashes the whole STORAGE read.
30
27
  let raw = '';
31
28
  try {
32
29
  raw = typeof document !== 'undefined' ? document.cookie : '';
@@ -45,21 +42,17 @@ function readCookies() {
45
42
  out[key] = REDACTED_VALUE;
46
43
  continue;
47
44
  }
45
+ let val = '';
48
46
  try {
49
- out[key] = decodeURIComponent(part.slice(eq + 1).trim());
47
+ val = decodeURIComponent(part.slice(eq + 1).trim());
50
48
  }
51
49
  catch {
52
- out[key] = part.slice(eq + 1).trim();
50
+ val = part.slice(eq + 1).trim();
53
51
  }
52
+ out[key] = scrubKnownSecrets(val);
54
53
  }
55
54
  return out;
56
55
  }
57
- /**
58
- * Read client-side storage on demand (the STORAGE_READ command). Powers `reticle_storage` — the
59
- * agent verifies "token persisted after login", "cart survived reload", "logout cleared the session"
60
- * from the app's real storage rather than inferring it from the DOM. `area` scopes to one area; omit
61
- * for all three. Sensitive keys are redacted; httpOnly cookies are unreadable by design (documented).
62
- */
63
56
  export function readStorage(area) {
64
57
  if (area === StorageArea.LOCAL)
65
58
  return readArea(safeArea(() => window.localStorage));
@@ -73,19 +66,11 @@ export function readStorage(area) {
73
66
  cookies: readCookies(),
74
67
  };
75
68
  }
76
- /** Redact a value when its key is credential-bearing — the same rule the pull path applies. */
77
69
  function redactFor(key, value) {
78
70
  if (null === value)
79
71
  return undefined;
80
- return isSensitiveKey(key) ? REDACTED_VALUE : value;
72
+ return isSensitiveKey(key) ? REDACTED_VALUE : scrubKnownSecrets(value);
81
73
  }
82
- /**
83
- * Observe storage WRITES (not just reads): patch Storage.setItem/removeItem so a token persisted, a
84
- * cart updated, or a session cleared emits a STORAGE_CHANGE {area, key, old?, new?} diff — the "before"
85
- * the pull path can never see. localStorage and sessionStorage share one prototype, so `this` inside the
86
- * patch identifies the area. The native originals are read off the prototype descriptor (not a bare
87
- * method access) and re-dispatched via `.call(this)`. Fully reversible; values redacted like the reads.
88
- */
89
74
  export function installStorage(emit) {
90
75
  if ('undefined' === typeof Storage)
91
76
  return () => undefined;
@@ -96,9 +81,6 @@ export function installStorage(emit) {
96
81
  if (origSet === undefined || origRemove === undefined || origClear === undefined) {
97
82
  return () => undefined;
98
83
  }
99
- // Resolve sessionStorage at CALL time, not once at install. If the property threw during install
100
- // (a transient sandbox state) the captured reference would be null and every session write would be
101
- // misreported as `local` for the life of the page. Reads are cheap and safeArea swallows throws.
102
84
  const areaOf = (storage) => storage === safeArea(() => window.sessionStorage) ? StorageArea.SESSION : StorageArea.LOCAL;
103
85
  const readOld = (storage, key) => {
104
86
  try {
@@ -109,9 +91,10 @@ export function installStorage(emit) {
109
91
  }
110
92
  };
111
93
  const patchedSetItem = function (key, value) {
112
- // The app's write happens FIRST and outside the guard — it must succeed or fail on its own terms.
113
94
  const old = observeValue(() => readOld(this, key)) ?? null;
114
95
  origSet.call(this, key, value);
96
+ if (old === value)
97
+ return;
115
98
  observeSafely(() => {
116
99
  emit(EventType.STORAGE_CHANGE, {
117
100
  area: areaOf(this),
@@ -126,7 +109,6 @@ export function installStorage(emit) {
126
109
  const old = observeValue(() => readOld(this, key)) ?? null;
127
110
  origRemove.call(this, key);
128
111
  observeSafely(() => {
129
- // No `new` field ⇒ the key was removed.
130
112
  emit(EventType.STORAGE_CHANGE, {
131
113
  area: areaOf(this),
132
114
  key,
@@ -136,9 +118,6 @@ export function installStorage(emit) {
136
118
  };
137
119
  proto.removeItem = patchedRemoveItem;
138
120
  const patchedClear = function () {
139
- // localStorage.clear() is the most common logout — but it emitted NOTHING, so "logout cleared the
140
- // session" was unverifiable from the write path. Snapshot the keys BEFORE clearing (the values are
141
- // gone after), then report each as a removal, reusing the {area, key, old} shape removeItem emits.
142
121
  const area = areaOf(this);
143
122
  const removed = [];
144
123
  try {
@@ -149,7 +128,6 @@ export function installStorage(emit) {
149
128
  }
150
129
  }
151
130
  catch {
152
- // Enumerating keys can throw in a locked-down context; clear anyway, just without the diff.
153
131
  }
154
132
  origClear.call(this);
155
133
  for (const { key, old } of removed) {
@@ -164,8 +142,6 @@ export function installStorage(emit) {
164
142
  };
165
143
  proto.clear = patchedClear;
166
144
  return () => {
167
- // Restore only if the slot still holds our wrapper — never uninstall a wrapper the app layered
168
- // on top of ours after connect().
169
145
  if (proto.setItem === patchedSetItem)
170
146
  proto.setItem = origSet;
171
147
  if (proto.removeItem === patchedRemoveItem)
@@ -1,20 +1,5 @@
1
1
  import type { EventType } from '@reticlehq/core';
2
- /** An observer emits normalized events; the orchestrator stamps time + forwards them. */
3
2
  export type Emit = (type: EventType, data: Record<string, unknown>, ref?: string) => void;
4
- /** Every observer returns a teardown that fully restores any patched globals. */
5
3
  export type Teardown = () => void;
6
- /**
7
- * Run an observation so it can never escape into the app's call stack.
8
- *
9
- * Every monkey-patch calls its observation INSIDE the function it replaced, so a throw does not surface
10
- * as an SDK error — it propagates out of `localStorage.setItem` after the write already succeeded, out
11
- * of `history.pushState` (crashing a router's navigate), or out of `console.log` before the message
12
- * reaches the console. Guarding the transport hop alone is not enough: the payload is BUILT here too
13
- * (reading the old value, resolving the area, redacting), and any of that can throw on a hostile or
14
- * exotic object.
15
- *
16
- * A dev-only observability SDK must never be able to break the app it is observing.
17
- */
18
4
  export declare function observeSafely(observation: () => void): void;
19
- /** Like observeSafely, but for reading a value the observation needs; undefined if the read throws. */
20
5
  export declare function observeValue<T>(read: () => T): T | undefined;
@@ -1,24 +1,10 @@
1
- /**
2
- * Run an observation so it can never escape into the app's call stack.
3
- *
4
- * Every monkey-patch calls its observation INSIDE the function it replaced, so a throw does not surface
5
- * as an SDK error — it propagates out of `localStorage.setItem` after the write already succeeded, out
6
- * of `history.pushState` (crashing a router's navigate), or out of `console.log` before the message
7
- * reaches the console. Guarding the transport hop alone is not enough: the payload is BUILT here too
8
- * (reading the old value, resolving the area, redacting), and any of that can throw on a hostile or
9
- * exotic object.
10
- *
11
- * A dev-only observability SDK must never be able to break the app it is observing.
12
- */
13
1
  export function observeSafely(observation) {
14
2
  try {
15
3
  observation();
16
4
  }
17
5
  catch {
18
- /* observation is best-effort; the host app's control flow is not */
19
6
  }
20
7
  }
21
- /** Like observeSafely, but for reading a value the observation needs; undefined if the read throws. */
22
8
  export function observeValue(read) {
23
9
  try {
24
10
  return read();
@@ -1,26 +1,2 @@
1
- /**
2
- * Capture a method off an object so it can be restored — or re-invoked via `.call` — later.
3
- *
4
- * Monkeypatching is unavoidable in this SDK: observing history, console, fetch and XHR means
5
- * replacing a method and putting the original back on teardown. Putting it back requires holding the
6
- * UNBOUND original, because identity is what teardown compares — a bound copy is a different function
7
- * and would leave the patch installed forever.
8
- *
9
- * `@typescript-eslint/unbound-method` exists to catch the common mistake this pattern deliberately
10
- * makes, so every such site used to carry an eslint-disable. Disabling a rule seven times to express
11
- * one intention is worse than naming the intention once: those disables also suppressed the rule for
12
- * any OTHER unbound access on the same lines, which is exactly what the rule is for.
13
- *
14
- * The generic index access here is not something the rule flags, so callers stay clean and the
15
- * "I meant to do this" is stated in one place instead of seven.
16
- */
17
1
  export declare function captureMethod<T, K extends keyof T>(target: T, key: K): T[K];
18
- /**
19
- * The `value` setter defined on a prototype, for inputs whose value must be set the way the browser
20
- * does it — React installs its own descriptor, and assigning `el.value` directly skips the tracker it
21
- * relies on to notice a change.
22
- *
23
- * Same reason as `captureMethod` for existing separately from its call site: a property descriptor's
24
- * `set` is an unbound function, and naming that intention once beats disabling the rule at each use.
25
- */
26
2
  export declare function captureValueSetter(proto: object): ((this: HTMLElement, value: string) => void) | undefined;
@@ -1,36 +1,10 @@
1
- /**
2
- * Capture a method off an object so it can be restored — or re-invoked via `.call` — later.
3
- *
4
- * Monkeypatching is unavoidable in this SDK: observing history, console, fetch and XHR means
5
- * replacing a method and putting the original back on teardown. Putting it back requires holding the
6
- * UNBOUND original, because identity is what teardown compares — a bound copy is a different function
7
- * and would leave the patch installed forever.
8
- *
9
- * `@typescript-eslint/unbound-method` exists to catch the common mistake this pattern deliberately
10
- * makes, so every such site used to carry an eslint-disable. Disabling a rule seven times to express
11
- * one intention is worse than naming the intention once: those disables also suppressed the rule for
12
- * any OTHER unbound access on the same lines, which is exactly what the rule is for.
13
- *
14
- * The generic index access here is not something the rule flags, so callers stay clean and the
15
- * "I meant to do this" is stated in one place instead of seven.
16
- */
17
1
  export function captureMethod(target, key) {
18
2
  return target[key];
19
3
  }
20
- /**
21
- * The `value` setter defined on a prototype, for inputs whose value must be set the way the browser
22
- * does it — React installs its own descriptor, and assigning `el.value` directly skips the tracker it
23
- * relies on to notice a change.
24
- *
25
- * Same reason as `captureMethod` for existing separately from its call site: a property descriptor's
26
- * `set` is an unbound function, and naming that intention once beats disabling the rule at each use.
27
- */
28
4
  export function captureValueSetter(proto) {
29
5
  const descriptor = Object.getOwnPropertyDescriptor(proto, 'value');
30
6
  if (descriptor === undefined)
31
7
  return undefined;
32
- // Read through the same generic index access `captureMethod` uses: reaching for `.set` directly is
33
- // what the rule flags, and it is right to in general — this is the one place that means it.
34
8
  const setter = captureMethod(descriptor, 'set');
35
9
  return setter;
36
10
  }
@@ -0,0 +1,2 @@
1
+ export declare const Z_OVERLAY = 2147483600;
2
+ export declare const Z_TOUR: number;
@@ -0,0 +1,2 @@
1
+ export const Z_OVERLAY = 2147483600;
2
+ export const Z_TOUR = Z_OVERLAY + 30;
@@ -0,0 +1,12 @@
1
+ export declare const LOG_KIND: {
2
+ readonly READ: "read";
3
+ readonly ACT: "act";
4
+ readonly NARRATION: "narration";
5
+ readonly HUMAN: "human";
6
+ };
7
+ export type LogKind = (typeof LOG_KIND)[keyof typeof LOG_KIND];
8
+ export declare const LOG_RESULT: {
9
+ readonly PASS: "pass";
10
+ readonly FAIL: "fail";
11
+ };
12
+ export type LogResult = (typeof LOG_RESULT)[keyof typeof LOG_RESULT];
@@ -0,0 +1,7 @@
1
+ export const LOG_KIND = {
2
+ READ: 'read',
3
+ ACT: 'act',
4
+ NARRATION: 'narration',
5
+ HUMAN: 'human',
6
+ };
7
+ export const LOG_RESULT = { PASS: 'pass', FAIL: 'fail' };