@reticlehq/browser 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/actions/actions-dom.d.ts +0 -20
  2. package/dist/actions/actions-dom.js +0 -66
  3. package/dist/actions/actions-press.d.ts +10 -0
  4. package/dist/actions/actions-press.js +77 -0
  5. package/dist/actions/actions.d.ts +0 -89
  6. package/dist/actions/actions.js +24 -358
  7. package/dist/actions/appeared-text.d.ts +0 -20
  8. package/dist/actions/appeared-text.js +0 -36
  9. package/dist/actions/danger-context.d.ts +3 -0
  10. package/dist/actions/danger-context.js +39 -0
  11. package/dist/actions/scroll.d.ts +0 -9
  12. package/dist/actions/scroll.js +0 -7
  13. package/dist/actions/synthetic-input.d.ts +0 -7
  14. package/dist/actions/synthetic-input.js +0 -27
  15. package/dist/actions/value-input.d.ts +0 -30
  16. package/dist/actions/value-input.js +0 -33
  17. package/dist/commands/commands.d.ts +0 -2
  18. package/dist/commands/commands.js +0 -67
  19. package/dist/connect-options.d.ts +2 -96
  20. package/dist/dom/a11y.d.ts +0 -29
  21. package/dist/dom/a11y.js +1 -88
  22. package/dist/dom/auto-anchor.d.ts +0 -40
  23. package/dist/dom/auto-anchor.js +0 -33
  24. package/dist/dom/chart.d.ts +0 -61
  25. package/dist/dom/chart.js +0 -60
  26. package/dist/dom/desktop-capture.d.ts +0 -30
  27. package/dist/dom/desktop-capture.js +0 -72
  28. package/dist/dom/dom-ignore.d.ts +0 -30
  29. package/dist/dom/dom-ignore.js +0 -47
  30. package/dist/dom/occlusion.d.ts +0 -7
  31. package/dist/dom/occlusion.js +0 -7
  32. package/dist/dom/query.d.ts +0 -14
  33. package/dist/dom/query.js +48 -214
  34. package/dist/dom/realm.d.ts +0 -20
  35. package/dist/dom/realm.js +0 -28
  36. package/dist/dom/refs.d.ts +0 -76
  37. package/dist/dom/refs.js +2 -133
  38. package/dist/dom/shadow-registry.d.ts +0 -33
  39. package/dist/dom/shadow-registry.js +2 -72
  40. package/dist/dom/snapshot.d.ts +2 -43
  41. package/dist/dom/snapshot.js +26 -151
  42. package/dist/dom/source.d.ts +0 -44
  43. package/dist/dom/source.js +0 -45
  44. package/dist/dom/theme.d.ts +0 -37
  45. package/dist/dom/theme.js +2 -52
  46. package/dist/dom/virtualized.d.ts +0 -17
  47. package/dist/dom/virtualized.js +0 -27
  48. package/dist/edit-epoch.d.ts +0 -22
  49. package/dist/edit-epoch.js +0 -52
  50. package/dist/index.js +1 -23
  51. package/dist/observers/animation.d.ts +0 -1
  52. package/dist/observers/animation.js +0 -3
  53. package/dist/observers/blind-spots.d.ts +0 -26
  54. package/dist/observers/blind-spots.js +2 -61
  55. package/dist/observers/console.d.ts +1 -2
  56. package/dist/observers/console.js +41 -30
  57. package/dist/observers/context-open.d.ts +1 -11
  58. package/dist/observers/context-open.js +10 -14
  59. package/dist/observers/departure.d.ts +3 -0
  60. package/dist/observers/departure.js +52 -0
  61. package/dist/observers/dialogs.d.ts +2 -0
  62. package/dist/observers/dialogs.js +39 -0
  63. package/dist/observers/dom.js +0 -44
  64. package/dist/observers/download.d.ts +1 -2
  65. package/dist/observers/download.js +38 -55
  66. package/dist/observers/focus.d.ts +0 -6
  67. package/dist/observers/focus.js +0 -7
  68. package/dist/observers/frames.d.ts +0 -9
  69. package/dist/observers/frames.js +1 -28
  70. package/dist/observers/health.d.ts +0 -5
  71. package/dist/observers/health.js +1 -33
  72. package/dist/observers/install-all.d.ts +2 -0
  73. package/dist/observers/install-all.js +26 -27
  74. package/dist/observers/ipc.d.ts +0 -25
  75. package/dist/observers/ipc.js +0 -65
  76. package/dist/observers/navigation.d.ts +2 -0
  77. package/dist/observers/navigation.js +33 -0
  78. package/dist/observers/network-body.d.ts +5 -28
  79. package/dist/observers/network-body.js +11 -108
  80. package/dist/observers/network-redact.d.ts +1 -20
  81. package/dist/observers/network-redact.js +1 -80
  82. package/dist/observers/network-stream.d.ts +0 -21
  83. package/dist/observers/network-stream.js +1 -44
  84. package/dist/observers/network.d.ts +2 -26
  85. package/dist/observers/network.js +177 -294
  86. package/dist/observers/perf.d.ts +0 -10
  87. package/dist/observers/perf.js +0 -16
  88. package/dist/observers/route.d.ts +1 -2
  89. package/dist/observers/route.js +13 -19
  90. package/dist/observers/scroll.d.ts +0 -1
  91. package/dist/observers/scroll.js +0 -9
  92. package/dist/observers/sdk-failure.d.ts +1 -9
  93. package/dist/observers/sdk-failure.js +1 -30
  94. package/dist/observers/state.d.ts +1 -11
  95. package/dist/observers/state.js +13 -41
  96. package/dist/observers/storage.d.ts +0 -14
  97. package/dist/observers/storage.js +10 -34
  98. package/dist/observers/types.d.ts +0 -15
  99. package/dist/observers/types.js +0 -14
  100. package/dist/patching/capture-method.d.ts +0 -24
  101. package/dist/patching/capture-method.js +0 -26
  102. package/dist/presenter/overlay.d.ts +0 -1
  103. package/dist/presenter/overlay.js +0 -1
  104. package/dist/presenter/presenter-brand.d.ts +0 -19
  105. package/dist/presenter/presenter-brand.js +0 -21
  106. package/dist/presenter/presenter-config.d.ts +1 -105
  107. package/dist/presenter/presenter-config.js +2 -91
  108. package/dist/presenter/presenter-controls.d.ts +0 -29
  109. package/dist/presenter/presenter-controls.js +3 -131
  110. package/dist/presenter/presenter-dock-layout.d.ts +0 -3
  111. package/dist/presenter/presenter-dock-layout.js +0 -4
  112. package/dist/presenter/presenter-drag.d.ts +2 -24
  113. package/dist/presenter/presenter-drag.js +0 -26
  114. package/dist/presenter/presenter-effects.d.ts +0 -9
  115. package/dist/presenter/presenter-effects.js +0 -9
  116. package/dist/presenter/presenter-glow.d.ts +0 -21
  117. package/dist/presenter/presenter-glow.js +1 -27
  118. package/dist/presenter/presenter-heroicons-data.d.ts +0 -8
  119. package/dist/presenter/presenter-heroicons-data.js +0 -8
  120. package/dist/presenter/presenter-hud-chrome.d.ts +0 -10
  121. package/dist/presenter/presenter-hud-chrome.js +0 -10
  122. package/dist/presenter/presenter-icons.d.ts +0 -6
  123. package/dist/presenter/presenter-icons.js +0 -6
  124. package/dist/presenter/presenter-log.d.ts +0 -38
  125. package/dist/presenter/presenter-log.js +1 -45
  126. package/dist/presenter/presenter-report-copy.d.ts +5 -66
  127. package/dist/presenter/presenter-report-copy.js +5 -66
  128. package/dist/presenter/presenter-report-styles.d.ts +0 -6
  129. package/dist/presenter/presenter-report-styles.js +6 -6
  130. package/dist/presenter/presenter-report.d.ts +4 -7
  131. package/dist/presenter/presenter-report.js +18 -76
  132. package/dist/presenter/presenter-run-state.d.ts +0 -7
  133. package/dist/presenter/presenter-run-state.js +0 -5
  134. package/dist/presenter/presenter-settings-styles.d.ts +0 -1
  135. package/dist/presenter/presenter-settings-styles.js +0 -1
  136. package/dist/presenter/presenter-settings.d.ts +1 -23
  137. package/dist/presenter/presenter-settings.js +1 -31
  138. package/dist/presenter/presenter-shell-styles.js +2 -5
  139. package/dist/presenter/presenter-shell.d.ts +2 -18
  140. package/dist/presenter/presenter-shell.js +1 -73
  141. package/dist/presenter/presenter-styles.d.ts +0 -9
  142. package/dist/presenter/presenter-styles.js +0 -9
  143. package/dist/presenter/presenter-tally.d.ts +0 -5
  144. package/dist/presenter/presenter-tally.js +0 -4
  145. package/dist/presenter/presenter-test-helpers.d.ts +0 -2
  146. package/dist/presenter/presenter-test-helpers.js +0 -2
  147. package/dist/presenter/presenter-verbs.js +0 -7
  148. package/dist/presenter/presenter-workspace.d.ts +0 -4
  149. package/dist/presenter/presenter-workspace.js +1 -8
  150. package/dist/presenter/presenter.d.ts +0 -60
  151. package/dist/presenter/presenter.js +4 -118
  152. package/dist/recorder/recorder-styles.d.ts +0 -6
  153. package/dist/recorder/recorder-styles.js +0 -6
  154. package/dist/recorder/recorder.d.ts +0 -34
  155. package/dist/recorder/recorder.js +0 -49
  156. package/dist/registry/adapters.d.ts +0 -10
  157. package/dist/registry/adapters.js +0 -10
  158. package/dist/registry/auto-testids.d.ts +0 -19
  159. package/dist/registry/auto-testids.js +1 -21
  160. package/dist/registry/capabilities.d.ts +0 -31
  161. package/dist/registry/capabilities.js +1 -46
  162. package/dist/registry/commit-and-signal.d.ts +0 -6
  163. package/dist/registry/commit-and-signal.js +1 -7
  164. package/dist/registry/domains.d.ts +0 -8
  165. package/dist/registry/domains.js +0 -7
  166. package/dist/registry/emitter.d.ts +0 -14
  167. package/dist/registry/emitter.js +0 -10
  168. package/dist/registry/store-adapters.d.ts +0 -186
  169. package/dist/registry/store-adapters.js +0 -163
  170. package/dist/registry/stores.d.ts +0 -33
  171. package/dist/registry/stores.js +0 -59
  172. package/dist/reticle-presenter-helpers.d.ts +0 -13
  173. package/dist/reticle-presenter-helpers.js +0 -14
  174. package/dist/reticle.d.ts +1 -52
  175. package/dist/reticle.js +20 -145
  176. package/dist/review/annotator-styles.js +0 -7
  177. package/dist/review/annotator.d.ts +2 -13
  178. package/dist/review/annotator.js +0 -40
  179. package/dist/review/mark-anchor.d.ts +0 -11
  180. package/dist/review/mark-anchor.js +0 -8
  181. package/dist/security/serialization.d.ts +0 -20
  182. package/dist/security/serialization.js +1 -66
  183. package/dist/session-continuity.d.ts +0 -16
  184. package/dist/session-continuity.js +1 -20
  185. package/dist/timers/clock.d.ts +1 -14
  186. package/dist/timers/clock.js +59 -60
  187. package/dist/timers/native-console.js +0 -4
  188. package/dist/timers/native-timers.d.ts +0 -25
  189. package/dist/timers/native-timers.js +3 -33
  190. package/dist/transport/transport.d.ts +0 -51
  191. package/dist/transport/transport.js +3 -129
  192. package/dist/transport/unreachable-message.d.ts +0 -17
  193. package/dist/transport/unreachable-message.js +0 -17
  194. package/dist/util/captured-method.d.ts +0 -35
  195. package/dist/util/captured-method.js +0 -35
  196. package/package.json +2 -2
@@ -2,7 +2,6 @@ import { CHURN_TYPES, CommandMessageSchema, EventType, MessageKind, SESSION_LIFE
2
2
  import { nativeSetTimeout, nativeNow } from '../timers/native-timers.js';
3
3
  import { nativeWarn } from '../timers/native-console.js';
4
4
  import { safeStringify } from '../security/serialization.js';
5
- /** Default visibility source: fire `handler` whenever the document returns to the foreground. */
6
5
  function subscribeDocumentVisible(handler) {
7
6
  if ('undefined' === typeof document)
8
7
  return () => undefined;
@@ -13,69 +12,29 @@ function subscribeDocumentVisible(handler) {
13
12
  document.addEventListener('visibilitychange', listener);
14
13
  return () => document.removeEventListener('visibilitychange', listener);
15
14
  }
16
- /** The first retry after a drop. Kept short: the common outage is a daemon restarting. */
17
15
  const RECONNECT_DELAY_MS = 1000;
18
- /**
19
- * The ceiling on reconnect backoff.
20
- *
21
- * A flat 1s retry with no growth and no cap meant a tab left open against a stopped daemon made
22
- * ~600 failed WebSocket opens in ten minutes — CPU, and a devtools console full of
23
- * ERR_CONNECTION_REFUSED. Backing off is only safe BECAUSE this transport also reconnects on
24
- * focus/visibility, so a human returning to the tab retries immediately rather than waiting out
25
- * the delay; the backoff resets on that path for the same reason.
26
- */
27
16
  export const RECONNECT_MAX_DELAY_MS = 30_000;
28
- /**
29
- * The next reconnect delay: double, then clamp. Pure, and exported so the POLICY is testable —
30
- * the scheduling itself runs on `nativeSetTimeout`, which is bound to the real timer at module load
31
- * (deliberately, so a frozen `reticle_clock` cannot deadlock the SDK) and therefore cannot be driven
32
- * by a fake clock.
33
- */
34
17
  export function nextReconnectDelay(previous) {
35
18
  return Math.min(previous * 2, RECONNECT_MAX_DELAY_MS);
36
19
  }
37
- /** Offline queue cap. Exported so tests overflow the real bound instead of a mirrored copy of it. */
38
20
  export const MAX_QUEUE = 500;
39
- /**
40
- * Standard WebSocket close code 1008 ("policy violation") — the bridge sends it for TERMINAL refusals:
41
- * a protocol-version mismatch (the browser package is older than the bridge and must be upgraded),
42
- * auth failure, or a duplicate/invalid handshake. Reconnecting cannot fix any of those, so a blind
43
- * retry loop just hammers the bridge every second forever and buries the actionable reason. 1013
44
- * ("try again later" — rate/session limits) and transport-level closes stay retryable.
45
- */
46
21
  const WS_POLICY_VIOLATION = 1008;
47
- /** Warn that the bridge is unreachable after this many consecutive failed INITIAL connects (~3s). */
48
22
  const UNREACHABLE_WARN_AFTER = 3;
49
- /** WebSocket client to the bridge. Reconnects across reloads; buffers events while down. */
50
23
  export class Transport {
51
24
  #ws;
52
25
  #churnQueue = [];
53
26
  #signalQueue = [];
54
27
  #insertOrder = 0;
55
28
  #closed = false;
56
- /** When the current continuous outage began (nativeNow), or undefined while connected. */
57
29
  #disconnectedSince;
58
- /** Current reconnect delay; grows on each failure, resets on a successful open or on focus. */
59
30
  #reconnectDelay = RECONNECT_DELAY_MS;
60
- /** Whether onConnectionLost has already fired for the current outage (fire-once). */
61
31
  #lost = false;
62
- /** True once the socket has opened at least once — gates the "unreachable" first-connect warning. */
63
32
  #everConnected = false;
64
- /** Consecutive failed INITIAL connects (before any success). */
65
33
  #initialFailures = 0;
66
- /** Whether the unreachable warning has fired (fire-once). */
67
34
  #warnedUnreachable = false;
68
- /** Events the offline queue has evicted since the last gap marker reached the bridge. */
69
35
  #dropped = 0;
70
- /**
71
- * Envelope coordinates of the most recent evicted event — the pending marker's tombstone. Set while
72
- * a gap is undeclared, cleared once the bridge has been told about it.
73
- */
74
36
  #gap;
75
- /** Session id, cached from the first hello(). Stable for a Transport's life — no need to rebuild the
76
- * whole HelloMessage (url/title/adapters) on every inbound command just to read it. */
77
37
  #sessionId;
78
- /** Teardown for the visibility subscription (foreground-triggered reconnect), while connected. */
79
38
  #unsubscribeVisible;
80
39
  #deps;
81
40
  #now;
@@ -90,18 +49,9 @@ export class Transport {
90
49
  this.#unsubscribeVisible ??= (this.#deps.onVisible ?? subscribeDocumentVisible)(() => this.#onVisible());
91
50
  this.#open();
92
51
  }
93
- /**
94
- * The tab returned to the foreground. If we're disconnected (and not deliberately closed), reconnect
95
- * NOW — a hidden tab's throttled timer may be minutes from firing. A no-op when a socket already
96
- * exists (connected or mid-connect), so we never open a duplicate racing the scheduled retry.
97
- */
98
52
  #onVisible() {
99
53
  if (this.#closed)
100
54
  return;
101
- // A human just came back to the tab, which is the strongest signal the outage may be over — and
102
- // it is what makes a 30s ceiling safe. Reset the backoff BEFORE the mid-connect early return: if
103
- // a socket is already in flight and then fails, that retry should be prompt too, rather than
104
- // inheriting a delay earned while nobody was watching.
105
55
  this.#reconnectDelay = RECONNECT_DELAY_MS;
106
56
  if (this.#ws !== undefined)
107
57
  return;
@@ -113,12 +63,6 @@ export class Transport {
113
63
  ws = new WebSocket(this.#deps.url);
114
64
  }
115
65
  catch {
116
- // `new WebSocket()` throws SYNCHRONOUSLY on a mixed-content SecurityError — a ws:// URL from an
117
- // https page, which Safari does not exempt for localhost the way Chrome does. Unguarded, that
118
- // exception propagates out of connect() into the host app's bootstrap (the SDK's cardinal sin:
119
- // it must never break the app it observes) AND leaves connect() half-run, so #connected never
120
- // flips true and disconnect() can't remove the monkeypatches. Treat it like any failed open:
121
- // note the outage and schedule the normal retry.
122
66
  this.#noteOutage();
123
67
  this.#noteInitialFailure();
124
68
  this.#scheduleReopen();
@@ -126,18 +70,13 @@ export class Transport {
126
70
  }
127
71
  this.#ws = ws;
128
72
  ws.onopen = () => {
129
- this.#disconnectedSince = undefined; // healthy again — reset the loss timer
73
+ this.#disconnectedSince = undefined;
130
74
  this.#lost = false;
131
- this.#everConnected = true; // a real connection happened ⇒ never warn "unreachable"
132
- this.#reconnectDelay = RECONNECT_DELAY_MS; // recovered — the next drop starts prompt again
133
- // HELLO is SDK-owned schema data. Preserve its pairing token; the generic sanitizer
134
- // intentionally redacts fields named "token" from app-controlled payloads.
75
+ this.#everConnected = true;
76
+ this.#reconnectDelay = RECONNECT_DELAY_MS;
135
77
  const greeting = this.#deps.hello();
136
78
  this.#sessionId ??= greeting.sessionId;
137
79
  ws.send(JSON.stringify(greeting));
138
- // Declare any gap BEFORE replaying the backlog, stamped with the OLDEST event the queue
139
- // dropped — see #sendRaw. The two queues are drained back into one stream in insertion order,
140
- // so what the bridge receives is the order the page produced, minus the holes.
141
80
  this.#sendGapMarker(ws, greeting.sessionId);
142
81
  const replay = [...this.#churnQueue, ...this.#signalQueue].sort((a, b) => a.order - b.order);
143
82
  for (const msg of replay)
@@ -152,10 +91,6 @@ export class Transport {
152
91
  };
153
92
  ws.onclose = (event) => {
154
93
  this.#ws = undefined;
155
- // A 1008 is the bridge saying "don't come back as you are" — a version mismatch, a rejected
156
- // handshake. Retrying every second cannot change the outcome; stop and surface the reason (the
157
- // bridge puts an actionable message there, e.g. "upgrade @reticlehq/browser") instead of a
158
- // silent reconnect storm. End the session cleanly so the HUD doesn't hang on "running".
159
94
  if (event.code === WS_POLICY_VIOLATION) {
160
95
  this.#closed = true;
161
96
  const reason = event.reason.length > 0 ? event.reason : 'policy violation';
@@ -171,16 +106,6 @@ export class Transport {
171
106
  ws.close();
172
107
  };
173
108
  }
174
- /**
175
- * A scheduled reconnect — skipped if the SDK was torn down, or if a foreground-triggered reconnect
176
- * already opened a socket (guard against the throttled timer racing #onVisible into a duplicate).
177
- */
178
- /**
179
- * Schedule the next retry and grow the delay. Nothing is scheduled once the SDK is torn down.
180
- *
181
- * The delay grows AFTER scheduling, so the first retry of any outage is prompt (a daemon
182
- * restarting must not cost 30 seconds) and only a persistent outage backs off.
183
- */
184
109
  #scheduleReopen() {
185
110
  if (this.#closed)
186
111
  return;
@@ -194,11 +119,6 @@ export class Transport {
194
119
  return;
195
120
  this.#open();
196
121
  }
197
- /**
198
- * The first connection has never opened: count failures and, once they cross the threshold, fire
199
- * onUnreachable ONCE so the app surfaces an actionable hint (wrong port / container network) rather
200
- * than retrying silently forever. Suppressed entirely once any connection has succeeded.
201
- */
202
122
  #noteInitialFailure() {
203
123
  if (this.#everConnected || this.#warnedUnreachable)
204
124
  return;
@@ -208,11 +128,6 @@ export class Transport {
208
128
  this.#deps.onUnreachable?.({ url: this.#deps.url, attempts: this.#initialFailures });
209
129
  }
210
130
  }
211
- /**
212
- * Track how long the bridge has been unreachable. Once the outage exceeds BRIDGE_LOST_MS, fire
213
- * onConnectionLost exactly once so the SDK can end the session (the server/agent is gone and can
214
- * no longer push an end itself).
215
- */
216
131
  #noteOutage() {
217
132
  const at = this.#now();
218
133
  this.#disconnectedSince ??= at;
@@ -231,10 +146,6 @@ export class Transport {
231
146
  }
232
147
  const result = CommandMessageSchema.safeParse(parsed);
233
148
  if (!result.success) {
234
- // Also once silent. A command the page cannot parse is a version skew between the SDK and the
235
- // daemon, and dropping it produces the same unfalsifiable timeout every other hang produces — the one
236
- // failure mode where the cause is knowable and was being thrown away. Reply if we can recover
237
- // an id; a message without one is not a command we can answer at all.
238
149
  const id = parsed?.id;
239
150
  if ('string' === typeof id && id.length > 0) {
240
151
  this.#sendRaw(safeStringify({
@@ -248,13 +159,6 @@ export class Transport {
248
159
  return;
249
160
  }
250
161
  const command = result.data;
251
- // #sessionId is set in onopen, which always precedes any inbound command on the same socket.
252
- //
253
- // A mismatch used to `return` — silently. The command then had no reply of any kind, so the agent
254
- // waited out its full timeout and got "command timed out", which reads as a hung or suspended
255
- // page. Measured on a Tauri shell: the session was connected and streaming events the whole time
256
- // while every command vanished here, and three rounds of debugging went looking at the webview.
257
- // A dropped command is a real fault, so it now says so and names both ids.
258
162
  if (command.sessionId !== undefined && command.sessionId !== this.#sessionId) {
259
163
  this.#sendRaw(safeStringify({
260
164
  kind: MessageKind.COMMAND_RESULT,
@@ -291,15 +195,6 @@ export class Transport {
291
195
  type: event.type,
292
196
  });
293
197
  }
294
- /**
295
- * Re-send the HELLO on a live socket.
296
- *
297
- * The hello carries `hasCapabilities`, and it goes out at connect() — before the app has had a
298
- * chance to call `registerCapabilities`, which by design happens AFTER connect so `registerStore`
299
- * has a live SDK to subscribe through. So an app that declared its whole testable surface still
300
- * appeared to the agent as having none, and nothing ever corrected it. Cheap and idempotent: the
301
- * bridge treats a hello as the session's current identity, not as a new session.
302
- */
303
198
  reannounce() {
304
199
  if (this.#ws === undefined || this.#ws.readyState !== WebSocket.OPEN)
305
200
  return;
@@ -311,17 +206,9 @@ export class Transport {
311
206
  return;
312
207
  }
313
208
  if (this.#churnQueue.length + this.#signalQueue.length >= MAX_QUEUE) {
314
- // Prefer sacrificing churn (high-volume, low-signal) over signal events. Falls back to signal
315
- // FIFO only when the churn queue is empty — the buffer is bounded either way.
316
209
  const evicted = this.#churnQueue.length > 0 ? this.#churnQueue.shift() : this.#signalQueue.shift();
317
210
  if (evicted?.event !== undefined) {
318
211
  this.#dropped += 1;
319
- // Keep the EARLIEST drop, not the latest. Under plain FIFO the two were the same thing, so
320
- // "latest evicted" was a precise boundary: everything before it was gone. Churn-first
321
- // eviction breaks that — a churn event newer than a surviving signal event can be the one
322
- // sacrificed, and stamping the marker with it would claim a hole that swallows events the
323
- // agent is about to receive. The oldest drop is the honest floor: the hole starts no later
324
- // than here. A truncation marker may over-state its reach; it must never under-state it.
325
212
  const previous = this.#gap;
326
213
  if (previous === undefined || evicted.event.t < previous.t)
327
214
  this.#gap = evicted.event;
@@ -336,19 +223,6 @@ export class Transport {
336
223
  this.#signalQueue.push({ text, event, order });
337
224
  }
338
225
  }
339
- /**
340
- * Tell the bridge the offline queue swallowed events, then consider the gap declared.
341
- *
342
- * Written straight to the open socket instead of going back through `sendEvent`, for two reasons
343
- * that both bite at exactly the moment the marker matters. The queue is full when a drop happens,
344
- * so a queued marker would evict another real event — every declared gap widening the gap it
345
- * declares. And it would re-enter `#sendRaw` from inside the eviction branch, between the `shift`
346
- * and the `push`, growing the queue by one on every later send. Writing here makes both impossible.
347
- *
348
- * The marker inherits the seq/t of the last event it displaced. That event never reaches the
349
- * bridge, so the coordinates are free, and the server orders a session by `seq` — a marker minted
350
- * at reconnect would carry the highest seq and blame a segment that never had a hole in it.
351
- */
352
226
  #sendGapMarker(ws, sessionId) {
353
227
  const gap = this.#gap;
354
228
  if (gap === undefined)
@@ -1,19 +1,2 @@
1
- /**
2
- * What the page says when its websocket never opened.
3
- *
4
- * Pulled out of the connect path so it is a pure string function with a test on its CLAIMS rather
5
- * than a template inlined in a warn call. The rule it exists to hold: report what this page observed
6
- * and what it could not determine, never a cause. See unreachable-message.test.ts.
7
- */
8
- /**
9
- * The page tried, failed, and cannot see why. Names the address and the attempt count, refuses to
10
- * diagnose the daemon, and lists the checks in the order that actually resolves the ambiguity.
11
- */
12
1
  export declare function unreachableMessage(url: string, attempts: number): string;
13
- /**
14
- * The same fact, sized for the HUD's one-line status row.
15
- *
16
- * The console warning explains; this one only has room to name the URL, which is the answer in
17
- * most cases anyway — a bridge on a port the page is not dialling.
18
- */
19
2
  export declare function unreachableStripText(url: string, attempts: number): string;
@@ -1,15 +1,4 @@
1
- /**
2
- * What the page says when its websocket never opened.
3
- *
4
- * Pulled out of the connect path so it is a pure string function with a test on its CLAIMS rather
5
- * than a template inlined in a warn call. The rule it exists to hold: report what this page observed
6
- * and what it could not determine, never a cause. See unreachable-message.test.ts.
7
- */
8
1
  import { UNREACHABLE_NOTICE_PREFIX } from '@reticlehq/core';
9
- /**
10
- * The page tried, failed, and cannot see why. Names the address and the attempt count, refuses to
11
- * diagnose the daemon, and lists the checks in the order that actually resolves the ambiguity.
12
- */
13
2
  export function unreachableMessage(url, attempts) {
14
3
  return (`${UNREACHABLE_NOTICE_PREFIX}${url}. ${String(attempts)} attempts, all ` +
15
4
  `failed. That is everything the page can see: from inside the browser it cannot tell a daemon ` +
@@ -19,12 +8,6 @@ export function unreachableMessage(url, attempts) {
19
8
  `different host, so set the URL explicitly (Vite: VITE_RETICLE_WS_URL, or ` +
20
9
  `reticle.connect({ url })). An https page cannot open a ws:// socket at all. Still retrying…`);
21
10
  }
22
- /**
23
- * The same fact, sized for the HUD's one-line status row.
24
- *
25
- * The console warning explains; this one only has room to name the URL, which is the answer in
26
- * most cases anyway — a bridge on a port the page is not dialling.
27
- */
28
11
  export function unreachableStripText(url, attempts) {
29
12
  return `no bridge at ${url} — ${String(attempts)} attempt${1 === attempts ? '' : 's'}`;
30
13
  }
@@ -1,37 +1,2 @@
1
- /**
2
- * Read a host object's method as a STORED VALUE, for code that has to put it back.
3
- *
4
- * Every observer here patches a global and must be able to undo it: `console.error`, `window.fetch`,
5
- * `window.setTimeout`, `Date.now`. Undoing means holding the true original — the same function
6
- * object, so teardown restores exactly what was there and a later install can recognise it by
7
- * identity — and calling through a separately bound copy, because a host method invoked with the
8
- * wrong receiver throws (`fetch` gives "Illegal invocation").
9
- *
10
- * Written as `const original = window.fetch`, that is a bare unbound method reference, and
11
- * `@typescript-eslint/unbound-method` is right to flag the SHAPE even though this use is deliberate:
12
- * the rule cannot see that the next line binds it, nor that the raw value only ever goes back into
13
- * the slot it came from.
14
- *
15
- * The two obvious ways out are both wrong here. An `eslint-disable` is against this repo's standing
16
- * rule and hides the shape from every future reader. Annotating `this: void` asserts the function
17
- * does not use its receiver — which for a console method or `fetch` is simply false, and is the very
18
- * reason the binding exists.
19
- *
20
- * So the intent is expressed instead of suppressed. A property descriptor's `value` is a stored
21
- * value, not a method being detached from its object, which is exactly the distinction the rule is
22
- * drawing and exactly what this code wants. It is also strictly more accurate for the restore case:
23
- * it reads what the slot HOLDS rather than what calling the property would do, so an accessor
24
- * defined on the object cannot be silently flattened into its result.
25
- *
26
- * Returns undefined when the property is absent or inherited rather than own — a caller that needs a
27
- * fallback to the prototype chain should say so at its own call site rather than have it hidden here.
28
- */
29
1
  export declare function capturedMethod<T>(target: object, key: PropertyKey): T | undefined;
30
- /**
31
- * The same, for a property this code knows is present — the globals every observer patches.
32
- *
33
- * Throws rather than returning a broken teardown. A missing `window.fetch` is not a condition any
34
- * caller here can recover from, and silently installing a patch that cannot be undone is worse than
35
- * refusing: the page keeps the wrapper for the rest of its life with nothing able to remove it.
36
- */
37
2
  export declare function requireCapturedMethod<T>(target: object, key: PropertyKey): T;
@@ -1,41 +1,6 @@
1
- /**
2
- * Read a host object's method as a STORED VALUE, for code that has to put it back.
3
- *
4
- * Every observer here patches a global and must be able to undo it: `console.error`, `window.fetch`,
5
- * `window.setTimeout`, `Date.now`. Undoing means holding the true original — the same function
6
- * object, so teardown restores exactly what was there and a later install can recognise it by
7
- * identity — and calling through a separately bound copy, because a host method invoked with the
8
- * wrong receiver throws (`fetch` gives "Illegal invocation").
9
- *
10
- * Written as `const original = window.fetch`, that is a bare unbound method reference, and
11
- * `@typescript-eslint/unbound-method` is right to flag the SHAPE even though this use is deliberate:
12
- * the rule cannot see that the next line binds it, nor that the raw value only ever goes back into
13
- * the slot it came from.
14
- *
15
- * The two obvious ways out are both wrong here. An `eslint-disable` is against this repo's standing
16
- * rule and hides the shape from every future reader. Annotating `this: void` asserts the function
17
- * does not use its receiver — which for a console method or `fetch` is simply false, and is the very
18
- * reason the binding exists.
19
- *
20
- * So the intent is expressed instead of suppressed. A property descriptor's `value` is a stored
21
- * value, not a method being detached from its object, which is exactly the distinction the rule is
22
- * drawing and exactly what this code wants. It is also strictly more accurate for the restore case:
23
- * it reads what the slot HOLDS rather than what calling the property would do, so an accessor
24
- * defined on the object cannot be silently flattened into its result.
25
- *
26
- * Returns undefined when the property is absent or inherited rather than own — a caller that needs a
27
- * fallback to the prototype chain should say so at its own call site rather than have it hidden here.
28
- */
29
1
  export function capturedMethod(target, key) {
30
2
  return Object.getOwnPropertyDescriptor(target, key)?.value;
31
3
  }
32
- /**
33
- * The same, for a property this code knows is present — the globals every observer patches.
34
- *
35
- * Throws rather than returning a broken teardown. A missing `window.fetch` is not a condition any
36
- * caller here can recover from, and silently installing a patch that cannot be undone is worse than
37
- * refusing: the page keeps the wrapper for the rest of its life with nothing able to remove it.
38
- */
39
4
  export function requireCapturedMethod(target, key) {
40
5
  const value = capturedMethod(target, key);
41
6
  if (value === undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reticlehq/browser",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "Reticle browser SDK — installs observers, builds semantic snapshots, executes actions, talks to the bridge.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,7 +13,7 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "dependencies": {
16
- "@reticlehq/core": "2.13.0"
16
+ "@reticlehq/core": "2.14.0"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@iconify-json/heroicons-outline": "^1.2.1",