@reticlehq/browser 2.2.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/dist/actions/actions-dom.d.ts +0 -1
  2. package/dist/actions/actions-dom.js +0 -1
  3. package/dist/actions/actions.d.ts +3 -1
  4. package/dist/actions/actions.js +55 -36
  5. package/dist/actions/scroll.d.ts +0 -1
  6. package/dist/actions/scroll.js +0 -1
  7. package/dist/actions/value-input.d.ts +33 -0
  8. package/dist/actions/value-input.js +63 -0
  9. package/dist/commands/commands.d.ts +0 -1
  10. package/dist/commands/commands.js +27 -7
  11. package/dist/connect-options.d.ts +97 -0
  12. package/dist/connect-options.js +1 -0
  13. package/dist/dom/a11y.d.ts +0 -2
  14. package/dist/dom/a11y.js +54 -24
  15. package/dist/dom/auto-anchor.d.ts +0 -1
  16. package/dist/dom/auto-anchor.js +0 -1
  17. package/dist/dom/blind-spots.d.ts +0 -1
  18. package/dist/dom/blind-spots.js +0 -1
  19. package/dist/dom/chart.d.ts +0 -1
  20. package/dist/dom/chart.js +0 -1
  21. package/dist/dom/desktop-capture.d.ts +36 -0
  22. package/dist/dom/desktop-capture.js +113 -0
  23. package/dist/dom/dom-ignore.d.ts +7 -1
  24. package/dist/dom/dom-ignore.js +1 -2
  25. package/dist/dom/occlusion.d.ts +0 -1
  26. package/dist/dom/occlusion.js +0 -1
  27. package/dist/dom/query.d.ts +0 -1
  28. package/dist/dom/query.js +61 -13
  29. package/dist/dom/realm.d.ts +30 -0
  30. package/dist/dom/realm.js +58 -0
  31. package/dist/dom/refs.d.ts +0 -1
  32. package/dist/dom/refs.js +0 -1
  33. package/dist/dom/shadow-registry.d.ts +40 -0
  34. package/dist/dom/shadow-registry.js +165 -0
  35. package/dist/dom/snapshot.d.ts +2 -2
  36. package/dist/dom/snapshot.js +21 -4
  37. package/dist/dom/source.d.ts +0 -1
  38. package/dist/dom/source.js +0 -1
  39. package/dist/dom/theme.d.ts +0 -1
  40. package/dist/dom/theme.js +0 -1
  41. package/dist/dom/virtualized.d.ts +19 -0
  42. package/dist/dom/virtualized.js +80 -0
  43. package/dist/index.d.ts +3 -4
  44. package/dist/index.js +2 -3
  45. package/dist/observers/animation.d.ts +0 -1
  46. package/dist/observers/animation.js +0 -1
  47. package/dist/observers/blind-spots.d.ts +16 -4
  48. package/dist/observers/blind-spots.js +65 -11
  49. package/dist/observers/console.d.ts +0 -1
  50. package/dist/observers/console.js +0 -1
  51. package/dist/observers/dom.d.ts +0 -2
  52. package/dist/observers/dom.js +67 -9
  53. package/dist/observers/download.d.ts +6 -0
  54. package/dist/observers/download.js +103 -0
  55. package/dist/observers/focus.d.ts +0 -1
  56. package/dist/observers/focus.js +0 -1
  57. package/dist/observers/frames.d.ts +12 -0
  58. package/dist/observers/frames.js +67 -0
  59. package/dist/observers/health.d.ts +0 -1
  60. package/dist/observers/health.js +30 -2
  61. package/dist/observers/install-all.d.ts +5 -0
  62. package/dist/observers/install-all.js +63 -0
  63. package/dist/observers/ipc.d.ts +22 -0
  64. package/dist/observers/ipc.js +121 -0
  65. package/dist/observers/network-body.d.ts +17 -1
  66. package/dist/observers/network-body.js +26 -1
  67. package/dist/observers/network-redact.d.ts +0 -1
  68. package/dist/observers/network-redact.js +6 -3
  69. package/dist/observers/network-stream.d.ts +23 -0
  70. package/dist/observers/network-stream.js +102 -0
  71. package/dist/observers/network.d.ts +11 -1
  72. package/dist/observers/network.js +7 -1
  73. package/dist/observers/perf.d.ts +0 -1
  74. package/dist/observers/perf.js +0 -1
  75. package/dist/observers/route.d.ts +0 -1
  76. package/dist/observers/route.js +0 -1
  77. package/dist/observers/scroll.d.ts +0 -1
  78. package/dist/observers/scroll.js +0 -1
  79. package/dist/observers/sdk-failure.d.ts +28 -0
  80. package/dist/observers/sdk-failure.js +60 -0
  81. package/dist/observers/state.d.ts +0 -1
  82. package/dist/observers/state.js +0 -1
  83. package/dist/observers/storage.d.ts +0 -1
  84. package/dist/observers/storage.js +0 -1
  85. package/dist/observers/types.d.ts +0 -1
  86. package/dist/observers/types.js +0 -1
  87. package/dist/patching/capture-method.d.ts +0 -1
  88. package/dist/patching/capture-method.js +0 -1
  89. package/dist/presenter/overlay.d.ts +0 -1
  90. package/dist/presenter/overlay.js +0 -1
  91. package/dist/presenter/presenter-config.d.ts +0 -1
  92. package/dist/presenter/presenter-config.js +0 -1
  93. package/dist/presenter/presenter-controls.d.ts +0 -1
  94. package/dist/presenter/presenter-controls.js +0 -1
  95. package/dist/presenter/presenter-effects.d.ts +0 -1
  96. package/dist/presenter/presenter-effects.js +0 -1
  97. package/dist/presenter/presenter-glow.d.ts +0 -1
  98. package/dist/presenter/presenter-glow.js +0 -1
  99. package/dist/presenter/presenter-log.d.ts +0 -1
  100. package/dist/presenter/presenter-log.js +0 -1
  101. package/dist/presenter/presenter-run-state.d.ts +0 -1
  102. package/dist/presenter/presenter-run-state.js +0 -1
  103. package/dist/presenter/presenter-styles.d.ts +0 -1
  104. package/dist/presenter/presenter-styles.js +0 -1
  105. package/dist/presenter/presenter-tally.d.ts +0 -1
  106. package/dist/presenter/presenter-tally.js +0 -1
  107. package/dist/presenter/presenter-test-helpers.d.ts +0 -1
  108. package/dist/presenter/presenter-test-helpers.js +0 -1
  109. package/dist/presenter/presenter-verbs.d.ts +0 -1
  110. package/dist/presenter/presenter-verbs.js +0 -1
  111. package/dist/presenter/presenter.d.ts +0 -1
  112. package/dist/presenter/presenter.js +0 -1
  113. package/dist/recorder/recorder-styles.d.ts +0 -1
  114. package/dist/recorder/recorder-styles.js +0 -1
  115. package/dist/recorder/recorder.d.ts +0 -1
  116. package/dist/recorder/recorder.js +0 -1
  117. package/dist/registry/adapters.d.ts +0 -1
  118. package/dist/registry/adapters.js +0 -1
  119. package/dist/registry/capabilities.d.ts +2 -1
  120. package/dist/registry/capabilities.js +16 -1
  121. package/dist/registry/commit-and-signal.d.ts +0 -1
  122. package/dist/registry/commit-and-signal.js +0 -1
  123. package/dist/registry/domains.d.ts +0 -1
  124. package/dist/registry/domains.js +0 -1
  125. package/dist/registry/emitter.d.ts +0 -1
  126. package/dist/registry/emitter.js +0 -1
  127. package/dist/registry/store-adapters.d.ts +141 -7
  128. package/dist/registry/store-adapters.js +178 -1
  129. package/dist/registry/stores.d.ts +0 -1
  130. package/dist/registry/stores.js +8 -1
  131. package/dist/reticle-presenter-helpers.d.ts +0 -1
  132. package/dist/reticle-presenter-helpers.js +0 -1
  133. package/dist/reticle.d.ts +5 -59
  134. package/dist/reticle.js +37 -33
  135. package/dist/review/annotator.d.ts +0 -1
  136. package/dist/review/annotator.js +0 -1
  137. package/dist/review/mark-anchor.d.ts +0 -1
  138. package/dist/review/mark-anchor.js +0 -1
  139. package/dist/security/serialization.d.ts +0 -1
  140. package/dist/security/serialization.js +0 -1
  141. package/dist/timers/clock.d.ts +0 -1
  142. package/dist/timers/clock.js +0 -1
  143. package/dist/timers/native-console.d.ts +0 -1
  144. package/dist/timers/native-console.js +0 -1
  145. package/dist/timers/native-timers.d.ts +0 -1
  146. package/dist/timers/native-timers.js +0 -1
  147. package/dist/transport/transport.d.ts +12 -6
  148. package/dist/transport/transport.js +107 -17
  149. package/package.json +13 -3
  150. package/dist/actions/actions-dom.d.ts.map +0 -1
  151. package/dist/actions/actions-dom.js.map +0 -1
  152. package/dist/actions/actions.d.ts.map +0 -1
  153. package/dist/actions/actions.js.map +0 -1
  154. package/dist/actions/scroll.d.ts.map +0 -1
  155. package/dist/actions/scroll.js.map +0 -1
  156. package/dist/commands/commands.d.ts.map +0 -1
  157. package/dist/commands/commands.js.map +0 -1
  158. package/dist/dom/a11y.d.ts.map +0 -1
  159. package/dist/dom/a11y.js.map +0 -1
  160. package/dist/dom/auto-anchor.d.ts.map +0 -1
  161. package/dist/dom/auto-anchor.js.map +0 -1
  162. package/dist/dom/blind-spots.d.ts.map +0 -1
  163. package/dist/dom/blind-spots.js.map +0 -1
  164. package/dist/dom/chart.d.ts.map +0 -1
  165. package/dist/dom/chart.js.map +0 -1
  166. package/dist/dom/dom-ignore.d.ts.map +0 -1
  167. package/dist/dom/dom-ignore.js.map +0 -1
  168. package/dist/dom/occlusion.d.ts.map +0 -1
  169. package/dist/dom/occlusion.js.map +0 -1
  170. package/dist/dom/query.d.ts.map +0 -1
  171. package/dist/dom/query.js.map +0 -1
  172. package/dist/dom/refs.d.ts.map +0 -1
  173. package/dist/dom/refs.js.map +0 -1
  174. package/dist/dom/snapshot.d.ts.map +0 -1
  175. package/dist/dom/snapshot.js.map +0 -1
  176. package/dist/dom/source.d.ts.map +0 -1
  177. package/dist/dom/source.js.map +0 -1
  178. package/dist/dom/theme.d.ts.map +0 -1
  179. package/dist/dom/theme.js.map +0 -1
  180. package/dist/index.d.ts.map +0 -1
  181. package/dist/index.js.map +0 -1
  182. package/dist/observers/animation.d.ts.map +0 -1
  183. package/dist/observers/animation.js.map +0 -1
  184. package/dist/observers/blind-spots.d.ts.map +0 -1
  185. package/dist/observers/blind-spots.js.map +0 -1
  186. package/dist/observers/console.d.ts.map +0 -1
  187. package/dist/observers/console.js.map +0 -1
  188. package/dist/observers/dom.d.ts.map +0 -1
  189. package/dist/observers/dom.js.map +0 -1
  190. package/dist/observers/focus.d.ts.map +0 -1
  191. package/dist/observers/focus.js.map +0 -1
  192. package/dist/observers/health.d.ts.map +0 -1
  193. package/dist/observers/health.js.map +0 -1
  194. package/dist/observers/network-body.d.ts.map +0 -1
  195. package/dist/observers/network-body.js.map +0 -1
  196. package/dist/observers/network-redact.d.ts.map +0 -1
  197. package/dist/observers/network-redact.js.map +0 -1
  198. package/dist/observers/network.d.ts.map +0 -1
  199. package/dist/observers/network.js.map +0 -1
  200. package/dist/observers/perf.d.ts.map +0 -1
  201. package/dist/observers/perf.js.map +0 -1
  202. package/dist/observers/route.d.ts.map +0 -1
  203. package/dist/observers/route.js.map +0 -1
  204. package/dist/observers/scroll.d.ts.map +0 -1
  205. package/dist/observers/scroll.js.map +0 -1
  206. package/dist/observers/state.d.ts.map +0 -1
  207. package/dist/observers/state.js.map +0 -1
  208. package/dist/observers/storage.d.ts.map +0 -1
  209. package/dist/observers/storage.js.map +0 -1
  210. package/dist/observers/types.d.ts.map +0 -1
  211. package/dist/observers/types.js.map +0 -1
  212. package/dist/patching/capture-method.d.ts.map +0 -1
  213. package/dist/patching/capture-method.js.map +0 -1
  214. package/dist/presenter/overlay.d.ts.map +0 -1
  215. package/dist/presenter/overlay.js.map +0 -1
  216. package/dist/presenter/presenter-config.d.ts.map +0 -1
  217. package/dist/presenter/presenter-config.js.map +0 -1
  218. package/dist/presenter/presenter-controls.d.ts.map +0 -1
  219. package/dist/presenter/presenter-controls.js.map +0 -1
  220. package/dist/presenter/presenter-effects.d.ts.map +0 -1
  221. package/dist/presenter/presenter-effects.js.map +0 -1
  222. package/dist/presenter/presenter-glow.d.ts.map +0 -1
  223. package/dist/presenter/presenter-glow.js.map +0 -1
  224. package/dist/presenter/presenter-log.d.ts.map +0 -1
  225. package/dist/presenter/presenter-log.js.map +0 -1
  226. package/dist/presenter/presenter-run-state.d.ts.map +0 -1
  227. package/dist/presenter/presenter-run-state.js.map +0 -1
  228. package/dist/presenter/presenter-styles.d.ts.map +0 -1
  229. package/dist/presenter/presenter-styles.js.map +0 -1
  230. package/dist/presenter/presenter-tally.d.ts.map +0 -1
  231. package/dist/presenter/presenter-tally.js.map +0 -1
  232. package/dist/presenter/presenter-test-helpers.d.ts.map +0 -1
  233. package/dist/presenter/presenter-test-helpers.js.map +0 -1
  234. package/dist/presenter/presenter-verbs.d.ts.map +0 -1
  235. package/dist/presenter/presenter-verbs.js.map +0 -1
  236. package/dist/presenter/presenter.d.ts.map +0 -1
  237. package/dist/presenter/presenter.js.map +0 -1
  238. package/dist/recorder/recorder-styles.d.ts.map +0 -1
  239. package/dist/recorder/recorder-styles.js.map +0 -1
  240. package/dist/recorder/recorder.d.ts.map +0 -1
  241. package/dist/recorder/recorder.js.map +0 -1
  242. package/dist/registry/adapters.d.ts.map +0 -1
  243. package/dist/registry/adapters.js.map +0 -1
  244. package/dist/registry/capabilities.d.ts.map +0 -1
  245. package/dist/registry/capabilities.js.map +0 -1
  246. package/dist/registry/commit-and-signal.d.ts.map +0 -1
  247. package/dist/registry/commit-and-signal.js.map +0 -1
  248. package/dist/registry/domains.d.ts.map +0 -1
  249. package/dist/registry/domains.js.map +0 -1
  250. package/dist/registry/emitter.d.ts.map +0 -1
  251. package/dist/registry/emitter.js.map +0 -1
  252. package/dist/registry/store-adapters.d.ts.map +0 -1
  253. package/dist/registry/store-adapters.js.map +0 -1
  254. package/dist/registry/stores.d.ts.map +0 -1
  255. package/dist/registry/stores.js.map +0 -1
  256. package/dist/reticle-presenter-helpers.d.ts.map +0 -1
  257. package/dist/reticle-presenter-helpers.js.map +0 -1
  258. package/dist/reticle.d.ts.map +0 -1
  259. package/dist/reticle.js.map +0 -1
  260. package/dist/review/annotator.d.ts.map +0 -1
  261. package/dist/review/annotator.js.map +0 -1
  262. package/dist/review/mark-anchor.d.ts.map +0 -1
  263. package/dist/review/mark-anchor.js.map +0 -1
  264. package/dist/security/serialization.d.ts.map +0 -1
  265. package/dist/security/serialization.js.map +0 -1
  266. package/dist/timers/clock.d.ts.map +0 -1
  267. package/dist/timers/clock.js.map +0 -1
  268. package/dist/timers/native-console.d.ts.map +0 -1
  269. package/dist/timers/native-console.js.map +0 -1
  270. package/dist/timers/native-timers.d.ts.map +0 -1
  271. package/dist/timers/native-timers.js.map +0 -1
  272. package/dist/transport/transport.d.ts.map +0 -1
  273. package/dist/transport/transport.js.map +0 -1
@@ -21,6 +21,32 @@ const CAPTURABLE_CONTENT = /application\/json|text\/|application\/xml|x-www-form
21
21
  * observability SDK. Never read these; the frame observer covers SSE properly.
22
22
  */
23
23
  const STREAMING_CONTENT = /event-stream|x-ndjson|application\/stream/i;
24
+ /**
25
+ * Content types that stay OPEN by design — watching them would make every first settle pay the full
26
+ * watch bound for a stream doing exactly its job. Real SSE is covered by the EventSource observer.
27
+ */
28
+ const ENDLESS_BY_DESIGN = /event-stream/i;
29
+ /**
30
+ * Is this response body still arriving after the request itself resolved?
31
+ *
32
+ * STRUCTURAL, not a content-type allowlist. The first version listed `x-component|x-ndjson|
33
+ * application/stream` — which is to say "Next.js RSC, plus two guesses". It closed the exact defect
34
+ * it was written against and left every other streamed response as invisible as before: a chunked
35
+ * `application/json` from a token API, a streamed HTML document, an export assembled server-side.
36
+ * Fixing one app's symptom and calling the class solved is how a false green comes back wearing
37
+ * different clothes.
38
+ *
39
+ * The real signal is on the wire: a response with a BODY and no `content-length` is being sent
40
+ * chunked, which is the definition of "the server is still writing". `content-length` present means
41
+ * the size was known up front and the body cannot grow.
42
+ *
43
+ * Deliberately independent of content type, with one exception above for streams that never end.
44
+ */
45
+ export function isStreamingBody(contentType, contentLength) {
46
+ if (contentType !== null && ENDLESS_BY_DESIGN.test(contentType))
47
+ return false;
48
+ return contentLength === null;
49
+ }
24
50
  /**
25
51
  * Longest the SDK will wait on a response-body clone before emitting without it.
26
52
  *
@@ -140,4 +166,3 @@ export function projectBody(rawText, contentType) {
140
166
  const truncated = oversized || out.length > MAX_BODY_CHARS;
141
167
  return { body: out.length > MAX_BODY_CHARS ? out.slice(0, MAX_BODY_CHARS) : out, truncated };
142
168
  }
143
- //# sourceMappingURL=network-body.js.map
@@ -5,4 +5,3 @@
5
5
  * The hash is preserved and the URL is returned byte-for-byte when nothing matched.
6
6
  */
7
7
  export declare function redactUrl(raw: string): string;
8
- //# sourceMappingURL=network-redact.d.ts.map
@@ -30,13 +30,17 @@ export function redactUrl(raw) {
30
30
  let newQuery = query;
31
31
  if (query !== '') {
32
32
  const params = new URLSearchParams(query);
33
+ let queryChanged = false;
33
34
  for (const key of [...params.keys()]) {
34
35
  if (isSensitiveKey(key)) {
35
36
  params.set(key, REDACTED_VALUE);
36
- changed = true;
37
+ queryChanged = true;
37
38
  }
38
39
  }
39
- newQuery = params.toString();
40
+ if (queryChanged) {
41
+ newQuery = params.toString();
42
+ changed = true;
43
+ }
40
44
  }
41
45
  const segments = authority.split('/');
42
46
  for (let i = 0; i + 1 < segments.length; i++) {
@@ -63,4 +67,3 @@ export function redactUrl(raw) {
63
67
  const queryOut = queryStart === -1 ? '' : `?${newQuery}`;
64
68
  return `${segments.join('/')}${queryOut}${newHash}`;
65
69
  }
66
- //# sourceMappingURL=network-redact.js.map
@@ -0,0 +1,23 @@
1
+ import type { Emit } from './types.js';
2
+ /**
3
+ * Watch a STREAMED response body to its end, so `settled` cannot pass while the server is still
4
+ * writing.
5
+ *
6
+ * `fetch` resolves at HEADERS. On a Next.js App Router page the RSC payload's NET_REQUEST landed
7
+ * 16 ms in, the shell rendered at 3013, and the Suspense boundary's rows arrived at 3902 — 889 ms
8
+ * in which NOTHING was observed. Quiet is exactly what settle tests for, so it passed with the
9
+ * fallback still on screen and zero rows rendered: a green on every streaming boundary.
10
+ *
11
+ * STRUCTURAL, not an allowlist: a response with a body and no `content-length` is being sent
12
+ * chunked, which is the wire's own way of saying the server is still writing. An earlier version
13
+ * gated on a content-type list containing `x-component` — "Next.js RSC, plus two guesses" — which
14
+ * closed the one defect it was written against and left every other streamed response as invisible
15
+ * as before. The clone is DRAINED as it arrives rather than buffered, so watching costs a second
16
+ * read of bytes already in flight, not a second copy held in memory.
17
+ *
18
+ * BOUNDED both ways: an endless stream (SSE over fetch, a live feed) must not hold `settled` open
19
+ * forever, so the watch gives up after STREAM_WATCH_MS and closes the record anyway, marked
20
+ * `gaveUp`. That is the safe direction — a settle that eventually proceeds beats one that never
21
+ * does — and it says which happened rather than pretending the stream ended.
22
+ */
23
+ export declare function watchStreamedBody(emit: Emit, res: Response, id: string, url: string, contentType: string | null, contentLength: string | null): void;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Watching a STREAMED response body to its end, so `settled` cannot pass mid-stream.
3
+ *
4
+ * Split out of network.ts, which was already at its line cap. One question: when did this response
5
+ * actually finish arriving?
6
+ */
7
+ import { EventType, StreamDirection, StreamTransport } from '@reticlehq/core';
8
+ import { isStreamingBody } from './network-body.js';
9
+ import { nativeClearTimeout, nativeSetTimeout } from '../timers/native-timers.js';
10
+ /**
11
+ * Longest a streamed response body is watched before the record is closed anyway.
12
+ *
13
+ * An endless stream must never hold `settled` open forever. Ten seconds is longer than any
14
+ * page-render stream and short enough that a genuinely endless one costs one settle window, not the
15
+ * session.
16
+ */
17
+ const STREAM_WATCH_MS = 10_000;
18
+ /**
19
+ * Watch a STREAMED response body to its end, so `settled` cannot pass while the server is still
20
+ * writing.
21
+ *
22
+ * `fetch` resolves at HEADERS. On a Next.js App Router page the RSC payload's NET_REQUEST landed
23
+ * 16 ms in, the shell rendered at 3013, and the Suspense boundary's rows arrived at 3902 — 889 ms
24
+ * in which NOTHING was observed. Quiet is exactly what settle tests for, so it passed with the
25
+ * fallback still on screen and zero rows rendered: a green on every streaming boundary.
26
+ *
27
+ * STRUCTURAL, not an allowlist: a response with a body and no `content-length` is being sent
28
+ * chunked, which is the wire's own way of saying the server is still writing. An earlier version
29
+ * gated on a content-type list containing `x-component` — "Next.js RSC, plus two guesses" — which
30
+ * closed the one defect it was written against and left every other streamed response as invisible
31
+ * as before. The clone is DRAINED as it arrives rather than buffered, so watching costs a second
32
+ * read of bytes already in flight, not a second copy held in memory.
33
+ *
34
+ * BOUNDED both ways: an endless stream (SSE over fetch, a live feed) must not hold `settled` open
35
+ * forever, so the watch gives up after STREAM_WATCH_MS and closes the record anyway, marked
36
+ * `gaveUp`. That is the safe direction — a settle that eventually proceeds beats one that never
37
+ * does — and it says which happened rather than pretending the stream ended.
38
+ */
39
+ export function watchStreamedBody(emit, res, id, url, contentType, contentLength) {
40
+ // `=== null` is not enough: jsdom's Response has no `body` at all, and older/polyfilled
41
+ // implementations return undefined. Reaching for `.getReader()` on that threw an unhandled
42
+ // rejection out of a detached promise — an observability layer throwing into the host page, from
43
+ // code the host never called. Duck-type the reader instead of trusting the shape.
44
+ if (!isStreamingBody(contentType, contentLength))
45
+ return;
46
+ if (typeof res.body?.getReader !== 'function')
47
+ return;
48
+ let clone;
49
+ try {
50
+ clone = res.clone();
51
+ }
52
+ catch {
53
+ return; // already consumed or locked — the app owns it; observing it would take it away
54
+ }
55
+ const body = clone.body;
56
+ if (body === null)
57
+ return;
58
+ emit(EventType.NET_STREAM, {
59
+ transport: StreamTransport.FETCH,
60
+ direction: StreamDirection.OPEN,
61
+ url,
62
+ id,
63
+ });
64
+ void (async () => {
65
+ const reader = body.getReader();
66
+ let gaveUp = false;
67
+ // The deadline must be a TIMER, not a check after each read. An idle stream parks inside
68
+ // `read()` waiting for a chunk that may never come, so a post-read check never runs and the
69
+ // record never closes — which would hold `settled` open forever on exactly the endless streams
70
+ // the bound exists to protect against.
71
+ let timer;
72
+ const expired = new Promise((resolve) => {
73
+ timer = nativeSetTimeout(() => resolve('expired'), STREAM_WATCH_MS);
74
+ });
75
+ try {
76
+ for (;;) {
77
+ const next = await Promise.race([reader.read(), expired]);
78
+ if (next === 'expired') {
79
+ gaveUp = true;
80
+ void reader.cancel().catch(() => undefined);
81
+ break;
82
+ }
83
+ if (next.done)
84
+ break;
85
+ }
86
+ }
87
+ catch {
88
+ /* aborted or errored — the stream is over either way, which is what we report */
89
+ }
90
+ finally {
91
+ if (timer !== undefined)
92
+ nativeClearTimeout(timer);
93
+ }
94
+ emit(EventType.NET_STREAM, {
95
+ transport: StreamTransport.FETCH,
96
+ direction: StreamDirection.CLOSE,
97
+ url,
98
+ id,
99
+ ...(gaveUp ? { gaveUp: true } : {}),
100
+ });
101
+ })();
102
+ }
@@ -2,9 +2,20 @@ import type { Emit, Teardown } from './types.js';
2
2
  import { redactUrl } from './network-redact.js';
3
3
  export { redactUrl };
4
4
  /** Config for the network observer. Body capture is OFF by default and dev-only opt-in. */
5
+ /**
6
+ * Reinterpret a completed request's fields from its response.
7
+ *
8
+ * The seam exists so this observer stays ignorant of desktop IPC. A Tauri `invoke` arrives here as an
9
+ * ordinary fetch to an `ipc://` protocol, and the header that says whether the Rust command
10
+ * succeeded means nothing to a network observer — so the knowledge lives in the IPC observer and is
11
+ * passed IN, rather than this module importing it. Returns undefined to leave the record untouched.
12
+ */
13
+ export type NetResponseReinterpreter = (url: string, header: (name: string) => string | null) => Record<string, unknown> | undefined;
5
14
  export interface NetworkOptions {
6
15
  /** Capture request/response bodies (text-like content only, redacted, per-body capped). */
7
16
  captureBodies?: boolean;
17
+ /** Optional hook that reinterprets a completed request — see NetResponseReinterpreter. */
18
+ reinterpret?: NetResponseReinterpreter;
8
19
  }
9
20
  /** Pure: the first real app-code frame in a stack string, capped. Exported for unit testing. */
10
21
  export declare function firstAppFrame(stack: string | undefined): string | undefined;
@@ -20,4 +31,3 @@ export interface NetTiming {
20
31
  */
21
32
  export declare function extractTiming(entry: PerformanceResourceTiming | undefined): NetTiming;
22
33
  export declare function installNetwork(emit: Emit, opts?: NetworkOptions): Teardown;
23
- //# sourceMappingURL=network.d.ts.map
@@ -2,6 +2,7 @@ import { BlindSpotKind, EventType, RETICLE_WS_PATH, StreamTransport, StreamDirec
2
2
  import { captureMethod } from '../patching/capture-method.js';
3
3
  import { isCapturableType, projectBody, withBodyDeadline } from './network-body.js';
4
4
  import { redactUrl } from './network-redact.js';
5
+ import { watchStreamedBody } from './network-stream.js';
5
6
  // Redaction moved to its own cohesive module (network.ts is at its line cap); re-exported so callers
6
7
  // and the existing test suite keep importing it from here.
7
8
  export { redactUrl };
@@ -191,6 +192,7 @@ function isNativeFetch(fn) {
191
192
  const OURS = new WeakSet();
192
193
  export function installNetwork(emit, opts = {}) {
193
194
  const captureBodies = opts.captureBodies === true;
195
+ const reinterpret = opts.reinterpret;
194
196
  // Keep the true original for teardown identity, plus a window-bound copy to invoke
195
197
  // (fetch throws "Illegal invocation" if called with the wrong `this`).
196
198
  const origFetch = window.fetch;
@@ -230,6 +232,8 @@ export function installNetwork(emit, opts = {}) {
230
232
  // headers-received, so it stays honest whether or not we read the body.
231
233
  const headersAt = performance.now();
232
234
  const contentType = res.headers.get('content-type');
235
+ // The request is done; the BODY may not be. Watch it so settle cannot pass mid-stream.
236
+ watchStreamedBody(emit, res, id, url, contentType, res.headers.get('content-length'));
233
237
  const emitRequest = (responseBodyFields) => {
234
238
  emit(EventType.NET_REQUEST, {
235
239
  id,
@@ -244,6 +248,9 @@ export function installNetwork(emit, opts = {}) {
244
248
  ...netResponseMeta(res.statusText, contentType, res.headers.get('content-length')),
245
249
  ...projectRequestBody(init?.body, captureBodies),
246
250
  ...responseBodyFields,
251
+ // Applied LAST so a reinterpreted verdict wins over the transport's own fields — a Tauri
252
+ // command that returned Err still travelled down a fetch that answered HTTP 200.
253
+ ...(reinterpret?.(url, (name) => res.headers.get(name)) ?? {}),
247
254
  });
248
255
  };
249
256
  if (captureBodies && isCapturableType(contentType)) {
@@ -511,4 +518,3 @@ export function installNetwork(emit, opts = {}) {
511
518
  }
512
519
  };
513
520
  }
514
- //# sourceMappingURL=network.js.map
@@ -10,4 +10,3 @@ import type { Emit, Teardown } from './types.js';
10
10
  * would otherwise be install time.
11
11
  */
12
12
  export declare function installPerf(emit: Emit): Teardown;
13
- //# sourceMappingURL=perf.d.ts.map
@@ -59,4 +59,3 @@ export function installPerf(emit) {
59
59
  po.disconnect();
60
60
  };
61
61
  }
62
- //# sourceMappingURL=perf.js.map
@@ -1,4 +1,3 @@
1
1
  import type { Emit, Teardown } from './types.js';
2
2
  /** Patch the History API + listen to popstate/hashchange to emit route.change. */
3
3
  export declare function installRoute(emit: Emit): Teardown;
4
- //# sourceMappingURL=route.d.ts.map
@@ -62,4 +62,3 @@ export function installRoute(emit) {
62
62
  window.removeEventListener('hashchange', onNav);
63
63
  };
64
64
  }
65
- //# sourceMappingURL=route.js.map
@@ -1,4 +1,3 @@
1
1
  import type { Emit, Teardown } from './types.js';
2
2
  /** Observe scroll position + reveal-on-scroll for modern scroll-reactive UIs. */
3
3
  export declare function installScroll(emit: Emit): Teardown;
4
- //# sourceMappingURL=scroll.d.ts.map
@@ -66,4 +66,3 @@ export function installScroll(emit) {
66
66
  io?.disconnect();
67
67
  };
68
68
  }
69
- //# sourceMappingURL=scroll.js.map
@@ -0,0 +1,28 @@
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
+ export declare const SdkSite: {
8
+ readonly CONNECT: "connect";
9
+ readonly DOM_OBSERVER: "dom_observer";
10
+ readonly NETWORK_OBSERVER: "network_observer";
11
+ readonly CONSOLE_OBSERVER: "console_observer";
12
+ readonly IPC_OBSERVER: "ipc_observer";
13
+ readonly HEALTH_OBSERVER: "health_observer";
14
+ readonly ROUTER_OBSERVER: "router_observer";
15
+ readonly ANIMATION_OBSERVER: "animation_observer";
16
+ readonly STORE_ADAPTER: "store_adapter";
17
+ readonly SNAPSHOT: "snapshot";
18
+ readonly ACTION: "action";
19
+ readonly COMMAND: "command";
20
+ readonly RECORDER: "recorder";
21
+ readonly TRANSPORT: "transport";
22
+ };
23
+ 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
+ export declare function reportSdkFailure(emit: Emit, site: SdkSite, error: unknown): void;
@@ -0,0 +1,60 @@
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
+ 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
+ export const SdkSite = {
27
+ CONNECT: 'connect',
28
+ DOM_OBSERVER: 'dom_observer',
29
+ NETWORK_OBSERVER: 'network_observer',
30
+ CONSOLE_OBSERVER: 'console_observer',
31
+ IPC_OBSERVER: 'ipc_observer',
32
+ HEALTH_OBSERVER: 'health_observer',
33
+ ROUTER_OBSERVER: 'router_observer',
34
+ ANIMATION_OBSERVER: 'animation_observer',
35
+ STORE_ADAPTER: 'store_adapter',
36
+ SNAPSHOT: 'snapshot',
37
+ ACTION: 'action',
38
+ COMMAND: 'command',
39
+ RECORDER: 'recorder',
40
+ TRANSPORT: 'transport',
41
+ };
42
+ /** Cap the message here too — the server strips variables, but an unbounded string is a bad idea twice. */
43
+ 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
+ export function reportSdkFailure(emit, site, error) {
49
+ try {
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ emit(EventType.SDK_FAILED, {
52
+ site,
53
+ message: message.slice(0, MAX_MESSAGE),
54
+ ...(error instanceof Error ? { errorType: error.constructor.name.slice(0, 64) } : {}),
55
+ });
56
+ }
57
+ catch {
58
+ /* a failure report must never become a second failure */
59
+ }
60
+ }
@@ -17,4 +17,3 @@ export declare function diffState(prev: unknown, next: unknown): StateChange[];
17
17
  */
18
18
  export declare function installStoreState(emit: Emit): Teardown;
19
19
  export {};
20
- //# sourceMappingURL=state.d.ts.map
@@ -84,4 +84,3 @@ export function installStoreState(emit) {
84
84
  active.clear();
85
85
  };
86
86
  }
87
- //# sourceMappingURL=state.js.map
@@ -20,4 +20,3 @@ export declare function readStorage(area?: string): StorageSnapshot | Record<str
20
20
  * method access) and re-dispatched via `.call(this)`. Fully reversible; values redacted like the reads.
21
21
  */
22
22
  export declare function installStorage(emit: Emit): Teardown;
23
- //# sourceMappingURL=storage.d.ts.map
@@ -174,4 +174,3 @@ export function installStorage(emit) {
174
174
  proto.clear = origClear;
175
175
  };
176
176
  }
177
- //# sourceMappingURL=storage.js.map
@@ -18,4 +18,3 @@ export type Teardown = () => void;
18
18
  export declare function observeSafely(observation: () => void): void;
19
19
  /** Like observeSafely, but for reading a value the observation needs; undefined if the read throws. */
20
20
  export declare function observeValue<T>(read: () => T): T | undefined;
21
- //# sourceMappingURL=types.d.ts.map
@@ -27,4 +27,3 @@ export function observeValue(read) {
27
27
  return undefined;
28
28
  }
29
29
  }
30
- //# sourceMappingURL=types.js.map
@@ -24,4 +24,3 @@ export declare function captureMethod<T, K extends keyof T>(target: T, key: K):
24
24
  * `set` is an unbound function, and naming that intention once beats disabling the rule at each use.
25
25
  */
26
26
  export declare function captureValueSetter(proto: object): ((this: HTMLElement, value: string) => void) | undefined;
27
- //# sourceMappingURL=capture-method.d.ts.map
@@ -34,4 +34,3 @@ export function captureValueSetter(proto) {
34
34
  const setter = captureMethod(descriptor, 'set');
35
35
  return setter;
36
36
  }
37
- //# sourceMappingURL=capture-method.js.map
@@ -9,4 +9,3 @@ export interface OverlayHandle {
9
9
  /** A tiny in-page status chip: connection + event count. Off by default. */
10
10
  export declare function installOverlay(): OverlayHandle;
11
11
  export {};
12
- //# sourceMappingURL=overlay.d.ts.map
@@ -28,4 +28,3 @@ export function installOverlay() {
28
28
  },
29
29
  };
30
30
  }
31
- //# sourceMappingURL=overlay.js.map
@@ -83,4 +83,3 @@ export declare const DATA_ON = "data-on";
83
83
  /** Overlay-root attribute toggled when the panel is minimised to a bar. */
84
84
  export declare const MIN_ATTR = "data-reticle-min";
85
85
  export declare const THROTTLED_ATTR = "data-reticle-throttled";
86
- //# sourceMappingURL=presenter-config.d.ts.map
@@ -48,4 +48,3 @@ export const DATA_ON = 'data-on';
48
48
  /** Overlay-root attribute toggled when the panel is minimised to a bar. */
49
49
  export const MIN_ATTR = 'data-reticle-min';
50
50
  export const THROTTLED_ATTR = 'data-reticle-throttled';
51
- //# sourceMappingURL=presenter-config.js.map
@@ -71,4 +71,3 @@ export declare class ControlPanel {
71
71
  setState(state: SessionState, text?: string, tone?: PresenterTone): void;
72
72
  }
73
73
  export { DATA_RETICLE_STATE };
74
- //# sourceMappingURL=presenter-controls.d.ts.map
@@ -365,4 +365,3 @@ export class ControlPanel {
365
365
  }
366
366
  }
367
367
  export { DATA_RETICLE_STATE };
368
- //# sourceMappingURL=presenter-controls.js.map
@@ -11,4 +11,3 @@ export declare function ringAround(ring: HTMLElement | undefined, rect: DOMRect)
11
11
  export declare function spawnRipple(root: HTMLElement | undefined, x: number, y: number): void;
12
12
  /** Pace for the human so an action is watchable, not instant. */
13
13
  export declare function pace(ms: number): Promise<void>;
14
- //# sourceMappingURL=presenter-effects.d.ts.map
@@ -37,4 +37,3 @@ export function spawnRipple(root, x, y) {
37
37
  export function pace(ms) {
38
38
  return new Promise((res) => nativeSetTimeout(res, ms));
39
39
  }
40
- //# sourceMappingURL=presenter-effects.js.map
@@ -40,4 +40,3 @@ export declare class GlowController {
40
40
  teardown(): void;
41
41
  }
42
42
  export {};
43
- //# sourceMappingURL=presenter-glow.d.ts.map
@@ -123,4 +123,3 @@ export class GlowController {
123
123
  }, this.#glowFadeMs);
124
124
  }
125
125
  }
126
- //# sourceMappingURL=presenter-glow.js.map
@@ -40,4 +40,3 @@ export declare function formatElapsed(ms: number): string;
40
40
  * Uses createElement/textContent (never innerHTML) so arbitrary narration text can't inject markup.
41
41
  */
42
42
  export declare function appendLogRow(container: HTMLElement, kind: LogKind, text: string, ts: string, logMax: number): LogHandle;
43
- //# sourceMappingURL=presenter-log.d.ts.map
@@ -115,4 +115,3 @@ export function appendLogRow(container, kind, text, ts, logMax) {
115
115
  },
116
116
  };
117
117
  }
118
- //# sourceMappingURL=presenter-log.js.map
@@ -45,4 +45,3 @@ interface RunStateInput {
45
45
  */
46
46
  export declare function buildRunState(input: RunStateInput): PresenterRunState;
47
47
  export {};
48
- //# sourceMappingURL=presenter-run-state.d.ts.map
@@ -34,4 +34,3 @@ export function buildRunState(input) {
34
34
  log: input.runLog.map((e) => ({ ...e })),
35
35
  };
36
36
  }
37
- //# sourceMappingURL=presenter-run-state.js.map
@@ -4,4 +4,3 @@
4
4
  * pure style data (no behavior). LOG_CSS + CONTROLS_CSS are composed in at the end as before.
5
5
  */
6
6
  export declare const PRESENTER_CSS = "\n[data-reticle-glow]{position:fixed;inset:0;pointer-events:none;z-index:2147483600;opacity:0;\n transition:opacity .25s ease;box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 28px 6px rgba(99,102,241,.45);}\n[data-reticle-glow][data-on=\"1\"]{opacity:1;animation:reticle-pulse 1.6s ease-in-out infinite;}\n[data-reticle-glow][data-on=\"1\"][data-busy=\"1\"]{animation:reticle-shimmer 1.1s ease-in-out infinite;}\n@keyframes reticle-pulse{0%,100%{box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 22px 4px rgba(99,102,241,.35)}\n 50%{box-shadow:inset 0 0 0 3px rgba(124,127,242,1),inset 0 0 40px 10px rgba(99,102,241,.6)}}\n@keyframes reticle-shimmer{0%,100%{box-shadow:inset 0 0 0 3px rgba(124,127,242,1),inset 0 0 34px 8px rgba(99,102,241,.55)}\n 50%{box-shadow:inset 0 0 0 3px rgba(140,142,255,1),inset 0 0 48px 12px rgba(99,102,241,.7)}}\n[data-reticle-cursor]{position:fixed;top:0;left:0;width:22px;height:22px;margin:-11px 0 0 -11px;\n border:2px solid #6366f1;border-radius:50%;background:rgba(99,102,241,.25);pointer-events:none;\n z-index:2147483646;opacity:0;transition:transform .32s cubic-bezier(.22,1,.36,1),opacity .2s ease;}\n[data-reticle-cursor][data-on=\"1\"]{opacity:1;}\n[data-reticle-cursor]::after{content:\"\";position:absolute;inset:7px;border-radius:50%;background:#6366f1;}\n[data-reticle-ripple]{position:fixed;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;\n background:rgba(99,102,241,.5);pointer-events:none;z-index:2147483645;animation:reticle-ripple .5s ease-out forwards;}\n@keyframes reticle-ripple{from{transform:scale(.4);opacity:.8}to{transform:scale(5);opacity:0}}\n[data-reticle-ring]{position:fixed;pointer-events:none;z-index:2147483644;border:2px solid #22c55e;border-radius:8px;\n box-shadow:0 0 0 3px rgba(34,197,94,.25);opacity:0;transition:opacity .15s ease;}\n[data-reticle-ring][data-on=\"1\"]{opacity:1;}\n[data-reticle-hud]{\n --reticle-accent:#7c83ff;--reticle-accent-soft:rgba(124,131,255,.16);\n --reticle-bg:rgba(13,15,22,.92);--reticle-bg2:rgba(19,22,32,.88);\n --reticle-fg:#e9ebf2;--reticle-muted:#9aa0b2;--reticle-faint:#6a7186;\n --reticle-line:rgba(255,255,255,.09);--reticle-line2:rgba(255,255,255,.05);\n --reticle-read:#54d2e6;--reticle-ok:#3dd7a6;--reticle-bad:#ff7a7a;\n --reticle-font:\"Inter\",system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;\n --reticle-serif:\"IBM Plex Serif\",Georgia,\"Times New Roman\",serif;\n position:fixed;left:50%;right:auto;bottom:20px;box-sizing:border-box;\n width:384px;height:468px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);\n display:flex;flex-direction:column;overflow:hidden;text-align:left;z-index:2147483647;pointer-events:none;\n font-family:var(--reticle-font);font-size:13px;line-height:1.5;color:var(--reticle-fg);-webkit-font-smoothing:antialiased;\n background:linear-gradient(180deg,var(--reticle-bg),var(--reticle-bg2));\n /* No backdrop-filter here, deliberately. blur(24px) on this panel was measured as the single most\n expensive thing the whole SDK does: a backdrop blur must re-sample everything behind it whenever\n that content changes, and on a page repainting at 60fps that is continuous full-panel work. On\n the hostile fixture it cost +4.0pp of main thread \u2014 five times the rest of the presenter, and\n more than every observer combined. Removing it took total main-thread time from 2.15s to 1.67s\n over an 8s window. The panel background is ~85% opaque, so the glass effect it bought was\n marginal. If you want it back, add it here and expect to pay that. */\n border:1px solid var(--reticle-line);border-radius:20px;\n box-shadow:0 28px 70px -18px rgba(0,0,0,.66),0 0 0 1px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.07),0 0 54px -22px var(--reticle-accent);\n opacity:0;transform:translateX(-50%) translateY(14px) scale(.985);\n transition:opacity .3s ease,transform .42s cubic-bezier(.16,1,.3,1),height .42s cubic-bezier(.16,1,.3,1),border-radius .42s ease,box-shadow .35s ease;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-hud]{--reticle-accent:#f6b44c;--reticle-accent-soft:rgba(246,180,76,.16);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-hud]{--reticle-accent:#3dd7a6;--reticle-accent-soft:rgba(61,215,166,.14);}\n[data-reticle-hud]::before{content:\"\";position:absolute;inset:0;border-radius:inherit;pointer-events:none;\n background:radial-gradient(130% 90% at 50% 0%,var(--reticle-accent-soft),transparent 60%);}\n[data-reticle-hud]>*{position:relative;}\n[data-reticle-hud][data-on=\"1\"]{opacity:1;transform:translateX(-50%) translateY(0) scale(1);}\n/* Click-through: the glassy panel itself never blocks the app \u2014 only its interactive controls\n capture clicks (buttons / inputs), so a human can click straight through the HUD to the page.\n The log auto-scrolls, so it stays click-through too (drag-scroll is traded for click-through). */\n[data-reticle-hud] button,[data-reticle-hud] input,[data-reticle-hud] textarea,\n[data-reticle-hud] select,[data-reticle-hud] [contenteditable]{pointer-events:auto;}\n/* When minimised to a pill, the whole bar is the (single) click target to restore. */\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud][data-on=\"1\"]{pointer-events:auto;}\n[data-reticle-hud] .reticle-hud-head{display:flex;align-items:center;gap:8px;flex:none;\n padding:12px 12px 12px 15px;border-bottom:1px solid var(--reticle-line2);}\n[data-reticle-hud] .reticle-dot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--reticle-accent);\n animation:reticle-breathe 2.6s ease-in-out infinite;}\n@keyframes reticle-breathe{0%,100%{box-shadow:0 0 0 0 var(--reticle-accent),0 0 7px 1px var(--reticle-accent);opacity:.85}\n 50%{box-shadow:0 0 0 4px var(--reticle-accent-soft),0 0 15px 3px var(--reticle-accent);opacity:1}}\n[data-reticle-hud] .reticle-brand{font-family:var(--reticle-serif);font-weight:500;font-size:15px;letter-spacing:.01em;color:var(--reticle-fg);}\n[data-reticle-hud] .reticle-head-sp{flex:1;}\n[data-reticle-hud] .reticle-live{display:none;flex:1;min-width:0;color:var(--reticle-muted);font-size:12.5px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n[data-reticle-hud] .reticle-maxhint{display:none;flex:none;color:var(--reticle-faint);font-size:13px;line-height:1;}\n[data-reticle-hud] .reticle-act-strip{flex:none;padding:7px 15px;border-bottom:1px solid var(--reticle-line2);background:rgba(0,0,0,.14);}\n[data-reticle-hud] .reticle-act{display:block;color:var(--reticle-muted);font-size:11.5px;\n white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n[data-reticle-hud] [data-reticle-min-btn]{flex:none;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;\n width:26px;height:26px;border-radius:8px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);\n color:var(--reticle-muted);font-size:15px;line-height:1;transition:background .15s,color .15s,transform .1s;}\n[data-reticle-hud] [data-reticle-min-btn]:hover{color:var(--reticle-fg);background:rgba(255,255,255,.08);}\n[data-reticle-hud] [data-reticle-min-btn]:active{transform:scale(.94);}\n[data-reticle-hud] .reticle-pass{color:var(--reticle-ok);}[data-reticle-hud] .reticle-fail{color:var(--reticle-bad);}\n[data-reticle-hud] .reticle-tally{flex:none;display:inline-flex;align-items:center;gap:8px;font-size:11.5px;\n font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:.01em;}\n[data-reticle-hud] .reticle-tally[hidden]{display:none;}\n[data-reticle-hud] .reticle-tally .reticle-t-pass{color:var(--reticle-ok);}\n[data-reticle-hud] .reticle-tally .reticle-t-fail{color:var(--reticle-bad);}\n[data-reticle-hud] .reticle-tally [data-z=\"1\"]{opacity:.4;}\n@keyframes reticle-tally-pop{0%{transform:scale(1)}38%{transform:scale(1.3)}100%{transform:scale(1)}}\n[data-reticle-hud] .reticle-tally [data-bump=\"1\"]{display:inline-block;animation:reticle-tally-pop .36s cubic-bezier(.16,1,.3,1);}\n[data-reticle-hud] .reticle-chip{display:none;flex:none;font-size:9px;font-weight:600;letter-spacing:.08em;\n padding:2px 7px;border-radius:6px;vertical-align:middle;}\n[data-reticle-hud] .reticle-chip[data-mode=\"reading\"]{display:inline-block;color:var(--reticle-read);\n background:rgba(84,210,230,.12);border:1px solid rgba(84,210,230,.32);}\n[data-reticle-hud] .reticle-chip[data-mode=\"acting\"]{display:inline-block;color:var(--reticle-accent);\n background:var(--reticle-accent-soft);border:1px solid var(--reticle-accent);}\n[data-reticle-hud] .reticle-chip[data-mode=\"idle\"]{display:none;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud]{height:50px;border-radius:25px;cursor:pointer;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-hud-head{border-bottom:none;height:50px;padding:0 12px 0 16px;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-brand,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-chip,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-head-sp,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-min-btn],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-ctl,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-badge,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-act-strip,\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-log],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] [data-reticle-foot],\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-banner{display:none;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-live{display:block;}\n[data-reticle-overlay][data-reticle-min=\"1\"] [data-reticle-hud] .reticle-maxhint{display:inline-flex;}\n[data-reticle-mode=\"reading\"] [data-reticle-glow][data-on=\"1\"]{\n box-shadow:inset 0 0 0 3px rgba(34,211,238,.9),inset 0 0 28px 6px rgba(34,211,238,.4);}\n[data-reticle-mode=\"reading\"] [data-reticle-ring]{border-color:#22d3ee;\n box-shadow:0 0 0 3px rgba(34,211,238,.25);}\n[data-reticle-overlay][data-reticle-throttled=\"1\"] [data-reticle-glow][data-on=\"1\"]{\n box-shadow:inset 0 0 0 3px rgba(251,191,36,.9),inset 0 0 28px 6px rgba(251,191,36,.45);}\n[data-reticle-overlay][data-reticle-throttled=\"1\"] [data-reticle-hud]{--reticle-accent:#fbbf24;--reticle-accent-soft:rgba(251,191,36,.16);}\n\n[data-reticle-log]{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;\n gap:7px;padding:12px 14px;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.16) transparent;}\n[data-reticle-log]::-webkit-scrollbar{width:9px;}\n[data-reticle-log]::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-log]::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.26);background-clip:content-box;}\n[data-reticle-log-row]{display:flex;align-items:baseline;gap:8px;font-size:12px;line-height:1.45;\n animation:reticle-row-in .26s cubic-bezier(.16,1,.3,1);}\n@keyframes reticle-row-in{from{opacity:0;transform:translateY(5px);}to{opacity:1;transform:none;}}\n[data-reticle-log-ts]{flex:none;color:var(--reticle-faint);font-size:10px;font-variant-numeric:tabular-nums;padding-top:1px;}\n[data-reticle-log] .reticle-log-text{flex:1;min-width:0;color:#d6dae4;overflow-wrap:anywhere;word-break:break-word;}\n[data-reticle-log] .reticle-res{flex:none;font-weight:700;}\n[data-reticle-log-row][data-kind=\"human\"]{align-self:flex-end;max-width:88%;\n background:var(--reticle-accent-soft);border:1px solid var(--reticle-accent);border-radius:13px 13px 4px 13px;padding:6px 11px;}\n[data-reticle-log-row][data-kind=\"human\"] [data-reticle-log-ts]{display:none;}\n[data-reticle-log-row][data-kind=\"human\"] .reticle-log-text{color:var(--reticle-fg);}\n\n\n[data-reticle-hud] .reticle-ctl{pointer-events:auto;cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;\n height:26px;padding:0 11px;border-radius:8px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);\n color:var(--reticle-muted);font-family:var(--reticle-font);font-size:11px;font-weight:500;letter-spacing:.01em;line-height:1;\n transition:background .15s,color .15s,border-color .15s,transform .1s;}\n[data-reticle-hud] .reticle-ctl:hover{color:var(--reticle-fg);background:rgba(255,255,255,.09);}\n[data-reticle-hud] .reticle-ctl:active{transform:scale(.95);}\n[data-reticle-hud] .reticle-ctl:disabled{opacity:.35;cursor:default;}\n[data-reticle-hud] [data-reticle-end]{color:#ff9aa2;border-color:rgba(255,107,107,.22);}\n[data-reticle-hud] [data-reticle-end]:hover{color:#ff7a7a;border-color:rgba(255,107,107,.5);background:rgba(255,107,107,.1);}\n[data-reticle-hud] .reticle-badge{display:none;align-items:center;flex:none;font-weight:600;letter-spacing:.1em;font-size:9px;\n color:var(--reticle-accent);border:1px solid var(--reticle-accent);background:var(--reticle-accent-soft);padding:2px 8px;border-radius:999px;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-badge]{display:inline-flex;}\n[data-reticle-hud] [data-reticle-foot]{flex:none;padding:10px 12px 12px;border-top:1px solid var(--reticle-line2);background:rgba(0,0,0,.16);}\n[data-reticle-hud] .reticle-composer{display:flex;align-items:flex-end;gap:6px;background:rgba(255,255,255,.05);\n border:1px solid var(--reticle-line);border-radius:14px;padding:5px 6px 5px 14px;transition:border-color .15s,box-shadow .15s;}\n[data-reticle-hud] .reticle-composer:focus-within{border-color:var(--reticle-accent);box-shadow:0 0 0 3px var(--reticle-accent-soft);}\n[data-reticle-hud] .reticle-msg{flex:1;min-width:0;pointer-events:auto;background:transparent;border:none;outline:none;resize:none;\n box-sizing:border-box;color:var(--reticle-fg);font-family:var(--reticle-font);font-size:13px;line-height:18px;\n height:28px;min-height:28px;max-height:96px;padding:5px 0;overflow-y:auto;\n scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.14) transparent;}\n[data-reticle-hud] .reticle-msg::-webkit-scrollbar{width:9px;}\n[data-reticle-hud] .reticle-msg::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-hud] .reticle-msg::placeholder{color:var(--reticle-faint);}\n[data-reticle-hud] .reticle-msg:disabled{opacity:.5;}\n[data-reticle-hud] .reticle-send{flex:none;width:30px;height:30px;padding:0;border-radius:10px;border:none;cursor:pointer;pointer-events:auto;\n background:var(--reticle-accent);color:#0b0d14;display:inline-flex;align-items:center;justify-content:center;transition:filter .15s,transform .1s;}\n[data-reticle-hud] .reticle-send svg{display:block;}\n[data-reticle-hud] .reticle-send:hover{filter:brightness(1.12);}\n[data-reticle-hud] .reticle-send:active{transform:scale(.9);}\n[data-reticle-hud] .reticle-send:disabled{opacity:.4;cursor:default;}\n[data-reticle-hud] .reticle-banner{display:none;flex:none;padding:8px 15px;color:var(--reticle-accent);\n font-size:11.5px;font-weight:500;border-bottom:1px solid var(--reticle-line2);background:var(--reticle-accent-soft);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-banner]{display:block;}\n/* Export row: hidden during a live session; revealed when ended so the run can be copied/saved. */\n[data-reticle-hud] .reticle-export{display:none;align-items:center;gap:8px;margin-top:9px;}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-hud] .reticle-export{display:flex;}\n[data-reticle-hud] .reticle-export-msg{color:var(--reticle-ok);font-size:11px;opacity:0;transition:opacity .15s;}\n[data-reticle-hud] .reticle-export-msg[data-show=\"1\"]{opacity:1;}\n[data-reticle-overlay][data-reticle-state=\"paused\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 3px rgba(246,180,76,.9),inset 0 0 30px 6px rgba(246,180,76,.4);}\n[data-reticle-overlay][data-reticle-state=\"ended\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 2px rgba(61,215,166,.55);}\n/* Handoff tones tell the human the agent's mode at a glance. waiting = calm teal \"your turn\" (no\n alarm); ask = amber \"answer me\" with a pulse; warn = amber \"agent crashed\" with a pulse. Each leads\n the banner with an icon and overrides the calm ended-green accent. */\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-hud]{--reticle-accent:#38bdf8;--reticle-accent-soft:rgba(56,189,248,.16);}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-banner]{font-weight:600;color:#7dd3fc;}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-banner]::before{content:\"\\270B \";}\n[data-reticle-overlay][data-reticle-tone=\"waiting\"] [data-reticle-glow][data-on=\"1\"]{animation:none;\n box-shadow:inset 0 0 0 2px rgba(56,189,248,.5);}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-hud],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-hud]{--reticle-accent:#fb923c;--reticle-accent-soft:rgba(251,146,60,.18);}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-banner],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-banner]{font-weight:600;color:#fdba74;}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-banner]::before{content:\"\\2753 \";}\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-banner]::before{content:\"\\26A0\\FE0F \";}\n[data-reticle-overlay][data-reticle-tone=\"ask\"] [data-reticle-glow][data-on=\"1\"],\n[data-reticle-overlay][data-reticle-tone=\"warn\"] [data-reticle-glow][data-on=\"1\"]{animation:reticle-warn-pulse 1.5s ease-in-out infinite;\n box-shadow:inset 0 0 0 2px rgba(251,146,60,.7);}\n@keyframes reticle-warn-pulse{0%,100%{box-shadow:inset 0 0 0 2px rgba(251,146,60,.32);}\n 50%{box-shadow:inset 0 0 0 3px rgba(251,146,60,.85),inset 0 0 26px 5px rgba(251,146,60,.34);}}\n/* Replay-a-flow row: the human re-runs a saved flow with no agent. Hidden until flows are pushed.\n Bounded + self-scrolling: it sits between the flex:1 log and the flex:none composer, so without a\n height cap a long flow list would squeeze the log to nothing and push the message input past the\n panel's overflow:hidden clip. flex:none + max-height + overflow-y keep the log and input always\n visible; extra flow chips scroll inside this section instead of growing the panel. */\n[data-reticle-hud] .reticle-flows{display:none;flex:none;flex-wrap:wrap;align-content:flex-start;gap:6px;\n padding:9px 12px;border-top:1px solid var(--reticle-line2);max-height:88px;overflow-y:auto;overscroll-behavior:contain;}\n[data-reticle-hud] .reticle-flows[data-has=\"1\"]{display:flex;}\n[data-reticle-hud] .reticle-flows::-webkit-scrollbar{width:9px;}\n[data-reticle-hud] .reticle-flows::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:9px;border:2px solid transparent;background-clip:content-box;}\n[data-reticle-hud] .reticle-flows-cap{flex:0 0 100%;margin-bottom:1px;color:var(--reticle-faint);font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;}\n[data-reticle-hud] .reticle-flow{pointer-events:auto;cursor:pointer;display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 10px;\n border-radius:7px;border:1px solid var(--reticle-line);background:rgba(255,255,255,.04);color:var(--reticle-muted);\n font-family:var(--reticle-font);font-size:11px;font-weight:500;transition:background .15s,color .15s,border-color .15s,transform .1s;}\n[data-reticle-hud] .reticle-flow:hover{color:var(--reticle-fg);background:var(--reticle-accent-soft);border-color:var(--reticle-accent);}\n[data-reticle-hud] .reticle-flow:active{transform:scale(.95);}\n";
7
- //# sourceMappingURL=presenter-styles.d.ts.map
@@ -124,4 +124,3 @@ export const PRESENTER_CSS = `
124
124
  [data-reticle-overlay][data-reticle-throttled="1"] [data-reticle-hud]{--reticle-accent:#fbbf24;--reticle-accent-soft:rgba(251,191,36,.16);}
125
125
  ${LOG_CSS}
126
126
  ${CONTROLS_CSS}`;
127
- //# sourceMappingURL=presenter-styles.js.map
@@ -17,4 +17,3 @@ export declare function countVerdicts(runLog: {
17
17
  export declare function renderTally(el: HTMLElement | undefined, runLog: {
18
18
  result?: LogResult;
19
19
  }[], prev: TallyCounts): TallyCounts;
20
- //# sourceMappingURL=presenter-tally.d.ts.map
@@ -35,4 +35,3 @@ export function renderTally(el, runLog, prev) {
35
35
  `<span class="reticle-t-fail"${dimF}${bumpFail}>✗ ${String(next.fails)}</span>`;
36
36
  return next;
37
37
  }
38
- //# sourceMappingURL=presenter-tally.js.map
@@ -13,4 +13,3 @@ export interface GlowFlips {
13
13
  stop: () => void;
14
14
  }
15
15
  export declare function trackGlowFlips(glow: HTMLElement): GlowFlips;
16
- //# sourceMappingURL=presenter-test-helpers.d.ts.map
@@ -39,4 +39,3 @@ export function trackGlowFlips(glow) {
39
39
  stop: () => obs.disconnect(),
40
40
  };
41
41
  }
42
- //# sourceMappingURL=presenter-test-helpers.js.map
@@ -1,2 +1 @@
1
1
  export declare function actionVerb(action: string): string;
2
- //# sourceMappingURL=presenter-verbs.d.ts.map
@@ -26,4 +26,3 @@ export function actionVerb(action) {
26
26
  return action;
27
27
  }
28
28
  }
29
- //# sourceMappingURL=presenter-verbs.js.map
@@ -84,4 +84,3 @@ export declare class Presenter {
84
84
  /** Fly the cursor to an element, play the action's effect, then pace for the human. */
85
85
  beforeAct(refId: string, action: string, label: string): Promise<void>;
86
86
  }
87
- //# sourceMappingURL=presenter.d.ts.map