@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
@@ -1,14 +1,9 @@
1
- /** Framework adapter registry — enriches elements with component identity + source. */
2
- // Persist on a global so the registry survives HMR module re-evaluation (otherwise the
3
- // adapter silently drops after a hot reload and source mapping degrades). See feedback #7.
4
1
  const globalStore = globalThis;
5
2
  const adapters = (globalStore.__reticleAdapters ??= []);
6
- /** Called by @reticlehq/react (and future adapters) to register themselves. */
7
3
  export function registerAdapter(adapter) {
8
4
  if (!adapters.some((a) => a.name === adapter.name))
9
5
  adapters.push(adapter);
10
6
  }
11
- /** First adapter that can identify the element wins. */
12
7
  export function identifyComponent(el) {
13
8
  for (const adapter of adapters) {
14
9
  const info = adapter.identify(el);
@@ -17,7 +12,6 @@ export function identifyComponent(el) {
17
12
  }
18
13
  return null;
19
14
  }
20
- /** First adapter that returns non-undefined component state for the element wins. */
21
15
  export function readComponentState(el) {
22
16
  for (const adapter of adapters) {
23
17
  if (adapter.readState === undefined)
@@ -28,10 +22,6 @@ export function readComponentState(el) {
28
22
  }
29
23
  return undefined;
30
24
  }
31
- /**
32
- * True if any installed adapter reports framework enter/leave hover handlers on the element.
33
- * No-op-safe: returns false when no adapter is installed or none implements the probe.
34
- */
35
25
  export function elementHasHoverHandlers(el) {
36
26
  for (const adapter of adapters) {
37
27
  if (adapter.hasHoverHandlers === undefined)
@@ -1,21 +1,2 @@
1
- /**
2
- * The testids the page is actually showing, read from the live DOM.
3
- *
4
- * `registerCapabilities({ testids: [...] })` used to be the only source, and `init` filled it by
5
- * grepping the source at install time. That list is a snapshot of the codebase on the day `init` ran:
6
- * it goes stale on the next commit that renames an element, it is empty for every app whose testids
7
- * arrive with a lazy route, and finishing it was homework handed to whoever ran the install.
8
- *
9
- * The attribute in the DOM is the same declaration, read fresh, so nothing has to be copied into a
10
- * config file to be true. Reticle's own presenter markup is excluded — advertising our own controls
11
- * as the app's testable surface would be the tool describing itself.
12
- */
13
- /**
14
- * Ceiling on the reported list.
15
- *
16
- * A virtualized 500-row grid stamps a testid per cell, and the capabilities payload rides in every
17
- * announce — an uncapped read turns one long table into a permanently expensive message. The cap is
18
- * on the REPORT, not on what can be driven: a testid past it is still findable by query.
19
- */
20
1
  export declare const MAX_AUTO_TESTIDS = 200;
21
2
  export declare function domTestids(doc: Document): string[];
@@ -1,26 +1,6 @@
1
- /**
2
- * The testids the page is actually showing, read from the live DOM.
3
- *
4
- * `registerCapabilities({ testids: [...] })` used to be the only source, and `init` filled it by
5
- * grepping the source at install time. That list is a snapshot of the codebase on the day `init` ran:
6
- * it goes stale on the next commit that renames an element, it is empty for every app whose testids
7
- * arrive with a lazy route, and finishing it was homework handed to whoever ran the install.
8
- *
9
- * The attribute in the DOM is the same declaration, read fresh, so nothing has to be copied into a
10
- * config file to be true. Reticle's own presenter markup is excluded — advertising our own controls
11
- * as the app's testable surface would be the tool describing itself.
12
- */
13
1
  const TESTID_ATTR = 'data-testid';
14
2
  const TESTID_SELECTOR = `[${TESTID_ATTR}]`;
15
- /**
16
- * Ceiling on the reported list.
17
- *
18
- * A virtualized 500-row grid stamps a testid per cell, and the capabilities payload rides in every
19
- * announce — an uncapped read turns one long table into a permanently expensive message. The cap is
20
- * on the REPORT, not on what can be driven: a testid past it is still findable by query.
21
- */
22
3
  export const MAX_AUTO_TESTIDS = 200;
23
- /** Reticle's own UI carries testids; they are not the host app's surface. */
24
4
  const RETICLE_OWN_PREFIX = 'reticle-';
25
5
  export function domTestids(doc) {
26
6
  const out = [];
@@ -30,7 +10,7 @@ export function domTestids(doc) {
30
10
  nodes = doc.querySelectorAll(TESTID_SELECTOR);
31
11
  }
32
12
  catch {
33
- return out; // a document that cannot be queried (detached, hostile) simply advertises nothing
13
+ return out;
34
14
  }
35
15
  for (let i = 0; i < nodes.length && out.length < MAX_AUTO_TESTIDS; i += 1) {
36
16
  const value = nodes[i]?.getAttribute(TESTID_ATTR);
@@ -1,4 +1,3 @@
1
- /** Self-describing capability registry — the testable surface the app advertises. */
2
1
  export interface CapabilityFlow {
3
2
  name: string;
4
3
  steps: string[];
@@ -8,46 +7,16 @@ export interface Capabilities {
8
7
  signals: string[];
9
8
  stores: string[];
10
9
  flows: CapabilityFlow[];
11
- /**
12
- * True when Reticle's OWN presenter is visible to snapshots and queries.
13
- *
14
- * Present only when the hatch is open, and reported here rather than left implicit, because a
15
- * verdict drawn against Reticle's own interface is not an ordinary verdict: an agent that can see
16
- * the impact panel can also assert against it. Anybody reading a result should be able to tell
17
- * which kind they are holding without going to look at a build config.
18
- */
19
10
  presenterExposed?: boolean;
20
11
  }
21
- /** What the host app passes to reticle.describe; all fields optional. */
22
12
  export interface CapabilitiesInput {
23
13
  testids?: string[];
24
14
  signals?: string[];
25
15
  stores?: string[];
26
16
  flows?: CapabilityFlow[];
27
17
  }
28
- /** Set by the SDK at connect. Idempotent; the last connect wins. */
29
18
  export declare function setCapabilitiesListener(cb: (() => void) | undefined): void;
30
- /** Called by the host app via reticle.describe. Merges (idempotent), never replaces wholesale. */
31
19
  export declare function registerCapabilities(input: CapabilitiesInput): void;
32
- /** Snapshot copy of the registered capabilities (defensive — never hand out the live arrays). */
33
20
  export declare function getCapabilities(): Capabilities;
34
- /**
35
- * Testids the app DECLARED, without the ones merely observed in the DOM.
36
- *
37
- * The difference carries meaning the merged list cannot. `knownEmptyState` asks "did the app NAME
38
- * this element as part of its testable surface?", which is how a zero-match query against
39
- * `cart-empty-region` is told apart from a broken selector. Every testid on the page is observed, so
40
- * answering that over the merged list makes it true whenever the page has any testid at all — the
41
- * same as not asking.
42
- */
43
21
  export declare function declaredTestids(): string[];
44
- /**
45
- * Whether the app has a testable surface at all (used in the HELLO flag).
46
- *
47
- * Answered over the OBSERVED surface as well as the declared one. It used to be answerable only by
48
- * declaration, so an app with two hundred testids in its DOM and a live Redux store reported
49
- * `hasCapabilities: false` until somebody typed those facts into a config file — and the agent
50
- * reading that flag concluded, correctly by the flag and wrongly in fact, that there was nothing to
51
- * drive.
52
- */
53
22
  export declare function hasCapabilities(): boolean;
@@ -1,23 +1,12 @@
1
- /** Self-describing capability registry — the testable surface the app advertises. */
2
1
  import { RETICLE_RENDERS_STORE } from '@reticlehq/core';
3
2
  import { isPresenterVisible } from '../dom/dom-ignore.js';
4
3
  import { domTestids } from './auto-testids.js';
5
4
  import { storeNames } from './stores.js';
6
- /**
7
- * The two halves of the capability report the app no longer has to write down.
8
- *
9
- * `testids` and `stores` used to be *declarations* — strings copied into a generated file by `init`
10
- * and then expected to stay true. Both are now read from the thing they describe: the testids from
11
- * the DOM, the store names from the registry (which the React adapter fills by discovery). What the
12
- * app declares is still merged in and still wins nothing away: `signals` and `flows` cannot be
13
- * observed and remain the app's to state.
14
- */
15
5
  function liveTestids() {
16
6
  if ('undefined' === typeof document)
17
7
  return [];
18
8
  return domTestids(document);
19
9
  }
20
- /** Registered stores, minus the ones Reticle registered on the app's behalf. */
21
10
  function liveStores() {
22
11
  return storeNames().filter((name) => RETICLE_RENDERS_STORE !== name);
23
12
  }
@@ -28,7 +17,6 @@ function union(declared, observed) {
28
17
  out.push(value);
29
18
  return out;
30
19
  }
31
- // Persist on a global so the registry survives HMR module re-evaluation (matches __reticleAdapters).
32
20
  const globalStore = globalThis;
33
21
  function empty() {
34
22
  return { testids: [], signals: [], stores: [], flows: [] };
@@ -41,22 +29,10 @@ function mergeUnique(into, add) {
41
29
  if (!into.includes(v))
42
30
  into.push(v);
43
31
  }
44
- /**
45
- * Notified whenever capabilities change, so the SDK can re-announce them to the bridge.
46
- *
47
- * `hasCapabilities` rides in the HELLO, which goes out at connect() — and registering deliberately
48
- * happens AFTER connect, because `registerStore` needs a live SDK to subscribe through. Without a
49
- * notification, an app that declared its whole testable surface still appeared to the agent as
50
- * having none, permanently. The hook lives HERE rather than on `reticle.describe` because the
51
- * documented entry point is this bare function; only wiring `describe` would have fixed the path
52
- * almost nobody uses.
53
- */
54
32
  let onChanged;
55
- /** Set by the SDK at connect. Idempotent; the last connect wins. */
56
33
  export function setCapabilitiesListener(cb) {
57
34
  onChanged = cb;
58
35
  }
59
- /** Called by the host app via reticle.describe. Merges (idempotent), never replaces wholesale. */
60
36
  export function registerCapabilities(input) {
61
37
  mergeUnique(capabilities.testids, input.testids);
62
38
  mergeUnique(capabilities.signals, input.signals);
@@ -68,45 +44,24 @@ export function registerCapabilities(input) {
68
44
  capabilities.flows.push({ name: flow.name, steps: [...flow.steps] });
69
45
  }
70
46
  else {
71
- existing.steps = [...flow.steps]; // last writer wins for a named flow
47
+ existing.steps = [...flow.steps];
72
48
  }
73
49
  }
74
50
  }
75
51
  onChanged?.();
76
52
  }
77
- /** Snapshot copy of the registered capabilities (defensive — never hand out the live arrays). */
78
53
  export function getCapabilities() {
79
54
  return {
80
55
  testids: union(capabilities.testids, liveTestids()),
81
56
  signals: [...capabilities.signals],
82
57
  stores: union(capabilities.stores, liveStores()),
83
58
  flows: capabilities.flows.map((f) => ({ name: f.name, steps: [...f.steps] })),
84
- // Only when open — an absent field is the ordinary case and should not cost a line in every
85
- // capabilities payload ever sent.
86
59
  ...(isPresenterVisible() ? { presenterExposed: true } : {}),
87
60
  };
88
61
  }
89
- /**
90
- * Testids the app DECLARED, without the ones merely observed in the DOM.
91
- *
92
- * The difference carries meaning the merged list cannot. `knownEmptyState` asks "did the app NAME
93
- * this element as part of its testable surface?", which is how a zero-match query against
94
- * `cart-empty-region` is told apart from a broken selector. Every testid on the page is observed, so
95
- * answering that over the merged list makes it true whenever the page has any testid at all — the
96
- * same as not asking.
97
- */
98
62
  export function declaredTestids() {
99
63
  return [...capabilities.testids];
100
64
  }
101
- /**
102
- * Whether the app has a testable surface at all (used in the HELLO flag).
103
- *
104
- * Answered over the OBSERVED surface as well as the declared one. It used to be answerable only by
105
- * declaration, so an app with two hundred testids in its DOM and a live Redux store reported
106
- * `hasCapabilities: false` until somebody typed those facts into a config file — and the agent
107
- * reading that flag concluded, correctly by the flag and wrongly in fact, that there was nothing to
108
- * drive.
109
- */
110
65
  export function hasCapabilities() {
111
66
  return (capabilities.signals.length > 0 ||
112
67
  capabilities.flows.length > 0 ||
@@ -1,8 +1,2 @@
1
- /**
2
- * P5b — pair a mutation with its signal so the two can't drift. The lighter alternative to a
3
- * Zustand middleware: one call runs the mutation, then emits the signal exactly once, and returns
4
- * the mutation's value. If `mutate` throws, the mutation never happened — so the signal is NOT
5
- * emitted and the error propagates unchanged (no try/finally, which would wrongly emit on throw).
6
- */
7
1
  import type { ReticleEmitter } from './emitter.js';
8
2
  export declare function commitAndSignal<T>(emitter: ReticleEmitter, mutate: () => T, name: string, data?: Record<string, unknown>): T;
@@ -1,11 +1,5 @@
1
- /**
2
- * P5b — pair a mutation with its signal so the two can't drift. The lighter alternative to a
3
- * Zustand middleware: one call runs the mutation, then emits the signal exactly once, and returns
4
- * the mutation's value. If `mutate` throws, the mutation never happened — so the signal is NOT
5
- * emitted and the error propagates unchanged (no try/finally, which would wrongly emit on throw).
6
- */
7
1
  export function commitAndSignal(emitter, mutate, name, data) {
8
- const result = mutate(); // throws here? then signal below is never reached — by design.
2
+ const result = mutate();
9
3
  emitter.signal(name, data);
10
4
  return result;
11
5
  }
@@ -1,11 +1,3 @@
1
- /**
2
- * P5c — self-registering domains. Instead of one hand-maintained flat-map of the whole testable
3
- * surface, each app domain co-locates its `{ testids, signals, stores }` and calls
4
- * `registerReticleDomain...)`; the capability registry assembles itself from every domain. Thin
5
- * adapter over `registerCapabilities`, so merge/dedupe (and HMR persistence) are reused, not
6
- * reinvented — later calls accumulate as a union, empty domains are a no-op.
7
- */
8
- /** A co-located domain bundle (one `reticle.ts` per app domain). */
9
1
  export interface ReticleDomain {
10
2
  testids?: readonly string[];
11
3
  signals?: readonly string[];
@@ -1,10 +1,3 @@
1
- /**
2
- * P5c — self-registering domains. Instead of one hand-maintained flat-map of the whole testable
3
- * surface, each app domain co-locates its `{ testids, signals, stores }` and calls
4
- * `registerReticleDomain...)`; the capability registry assembles itself from every domain. Thin
5
- * adapter over `registerCapabilities`, so merge/dedupe (and HMR persistence) are reused, not
6
- * reinvented — later calls accumulate as a union, empty domains are a no-op.
7
- */
8
1
  import { registerCapabilities } from './capabilities.js';
9
2
  export function registerReticleDomain(domain) {
10
3
  const input = {};
@@ -1,27 +1,13 @@
1
- /**
2
- * The injected-emitter pattern (P5a): host-app components depend on a tiny structural interface
3
- * instead of importing the SDK directly. The real emitter proxies to the connected `reticle`
4
- * singleton; when Reticle isn't connected (or isn't loaded) every call is a SAFE NO-OP — so nothing
5
- * breaks in production or before `reticle.connect`, and `@reticlehq/browser` stays out of the prod bundle.
6
- */
7
- /** Structural emitter the host app's components depend on. */
8
1
  export interface ReticleEmitter {
9
2
  signal: (name: string, data?: Record<string, unknown>) => void;
10
3
  state: (name: string, value: unknown) => void;
11
4
  }
12
- /** The subset of the reticle singleton an emitter proxies to (lets tests inject a fake). */
13
5
  export interface EmitterTarget {
14
6
  signal(name: string, data?: Record<string, unknown>): void;
15
7
  state(name: string, value: unknown): void;
16
- /** Discriminator the emitter reads per call to decide forward-vs-no-op. */
17
8
  readonly connected: boolean;
18
9
  }
19
10
  export interface CreateReticleEmitterOptions {
20
- /** Override the proxy target (tests / advanced embedding). Defaults to the reticle singleton. */
21
11
  target?: EmitterTarget;
22
12
  }
23
- /**
24
- * Create an emitter. Reads `target.connected` on EVERY call (not at creation) so an emitter
25
- * made at module load — before `reticle.connect` — starts working the moment Reticle connects.
26
- */
27
13
  export declare function createReticleEmitter(options?: CreateReticleEmitterOptions): ReticleEmitter;
@@ -1,14 +1,4 @@
1
- /**
2
- * The injected-emitter pattern (P5a): host-app components depend on a tiny structural interface
3
- * instead of importing the SDK directly. The real emitter proxies to the connected `reticle`
4
- * singleton; when Reticle isn't connected (or isn't loaded) every call is a SAFE NO-OP — so nothing
5
- * breaks in production or before `reticle.connect`, and `@reticlehq/browser` stays out of the prod bundle.
6
- */
7
1
  import { reticle } from '../index.js';
8
- /**
9
- * Create an emitter. Reads `target.connected` on EVERY call (not at creation) so an emitter
10
- * made at module load — before `reticle.connect` — starts working the moment Reticle connects.
11
- */
12
2
  export function createReticleEmitter(options = {}) {
13
3
  const target = options.target ?? reticle;
14
4
  return {
@@ -1,23 +1,4 @@
1
1
  import { type StoreLike, type StoreSubscribe } from './stores.js';
2
- /**
3
- * Adapters that give non-`{getState, subscribe}` state libraries the shape `registerStore` wants.
4
- *
5
- * `registerStore` duck-types on `{getState, subscribe}`, which zustand and Redux satisfy natively — so
6
- * those two needed nothing and got support for free. Everything else was unserved, and the gap was not
7
- * evenly distributed: by weekly npm downloads TanStack Query (~64M) is larger than zustand (~46M) and
8
- * redux (~39M), and it holds the state most likely to be WRONG in a way nothing else can see.
9
- *
10
- * That last point is the reason this file exists rather than a docs page. A stale-cache bug — the UI
11
- * rendering data the server has since changed, a mutation that never invalidated its query, an
12
- * optimistic update that was never rolled back — fires NO network request. An outside-in tool watching
13
- * the network sees silence and calls it healthy; the DOM shows a plausible number. The only witness is
14
- * the cache itself, and until now Reticle could not read it either.
15
- *
16
- * Every adapter here is a pure function returning `{getState, subscribe}`. None of them import their
17
- * library — they take an already-constructed client/store/proxy and use structural types, so this file
18
- * adds no dependency and no bundle weight for an app that uses none of them.
19
- */
20
- /** The minimum of TanStack Query's `QueryClient` that this adapter touches. */
21
2
  interface QueryLike {
22
3
  queryKey: readonly unknown[];
23
4
  state: {
@@ -38,7 +19,6 @@ interface QueryCacheLike {
38
19
  interface QueryClientLike {
39
20
  getQueryCache: () => QueryCacheLike;
40
21
  }
41
- /** How a single cached query is projected into readable state. */
42
22
  export interface QuerySnapshot {
43
23
  status: string;
44
24
  fetchStatus: string | undefined;
@@ -47,174 +27,40 @@ export interface QuerySnapshot {
47
27
  error: string | null;
48
28
  data: unknown;
49
29
  }
50
- /**
51
- * Expose a TanStack Query cache as a Reticle store, keyed by query key.
52
- *
53
- * `isStale` / `fetchStatus` / `dataUpdatedAt` are carried deliberately: they are what let an agent
54
- * assert the stronger property — not merely "the value rendered is X" but "the cache the UI rendered
55
- * from was actually fresh". A screenshot cannot distinguish a correct number from a correct-looking
56
- * stale one, and neither can a network log when the request never fired.
57
- *
58
- * ```ts
59
- * registerStore('queries', tanstackQueryStore(queryClient));
60
- * ```
61
- */
62
30
  export declare function tanstackQueryStore(client: QueryClientLike): StoreLike;
63
- /**
64
- * The minimum of a Jotai vanilla store this adapter touches, generic in the ATOM type.
65
- *
66
- * `atom: object` was rejected by TypeScript for a real store and the reason is contravariance, not
67
- * pedantry: Jotai's `get` is `<Value>(atom: Atom<Value>) => Value`, and a function that requires an
68
- * `Atom` cannot stand in for one that accepts any `object`. So `jotaiStore(createStore(), …)` did
69
- * not compile — the adapter was advertised, shipped, and unusable from TypeScript without a cast,
70
- * which the shape-matched fake in the sibling test could never reveal because the fake was written
71
- * to the same belief the adapter was.
72
- */
73
31
  interface JotaiStoreLike<A> {
74
32
  get: (atom: A) => unknown;
75
33
  sub: (atom: A, listener: () => void) => () => void;
76
34
  }
77
- /**
78
- * Expose a chosen set of Jotai atoms as one Reticle store.
79
- *
80
- * The atom map is not an ergonomic shortcut, it is forced by the design: Jotai has no registry of live
81
- * atoms to enumerate, so "the whole store" is not a thing that exists. Naming the atoms you care about
82
- * is the only way to snapshot them, and it doubles as a declaration of what matters.
83
- *
84
- * ```ts
85
- * registerStore('app', jotaiStore(getDefaultStore(), { cart: cartAtom, user: userAtom }));
86
- * ```
87
- */
88
35
  export declare function jotaiStore<A>(store: JotaiStoreLike<A>, atoms: Record<string, A>): StoreLike;
89
- /** The minimum of an XState actor this adapter touches. */
90
36
  interface ActorLike {
91
37
  getSnapshot: () => unknown;
92
38
  subscribe: (listener: () => void) => {
93
39
  unsubscribe: () => void;
94
40
  };
95
41
  }
96
- /**
97
- * Expose an XState actor as a Reticle store. Its `subscribe` returns a subscription OBJECT rather than
98
- * an unsubscribe function, which is the single reason it does not satisfy `StoreLike` already.
99
- */
100
42
  export declare function xstateStore(actor: ActorLike): StoreLike;
101
- /**
102
- * Expose a Valtio proxy as a Reticle store. Valtio ships `snapshot`/`subscribe` as free functions
103
- * rather than methods, so the caller passes them in — importing them here would make every consumer
104
- * of this file depend on valtio.
105
- *
106
- * ```ts
107
- * import { snapshot, subscribe } from 'valtio/vanilla';
108
- * registerStore('app', valtioStore(state, snapshot, subscribe));
109
- * ```
110
- */
111
43
  export declare function valtioStore<T extends object>(proxy: T, snapshot: (p: T) => unknown, subscribe: (p: T, listener: () => void) => () => void): StoreLike;
112
- /**
113
- * Expose a MobX observable as a Reticle store. `toJS` and `reaction` are passed in for the same
114
- * dependency reason as valtio above.
115
- *
116
- * ```ts
117
- * import { reaction, toJS } from 'mobx';
118
- * registerStore('app', mobxStore(store, toJS, reaction));
119
- * ```
120
- */
121
44
  export declare function mobxStore<T>(observable: T, toJS: (value: T) => unknown, reaction: (track: () => unknown, effect: () => void) => () => void): StoreLike;
122
- /** The minimum of a Recoil `Loadable` this adapter touches. */
123
45
  interface RecoilLoadableLike {
124
- /** 'hasValue' | 'loading' | 'hasError'. Widened to string: it arrives from outside this package. */
125
46
  state: string;
126
- /** The value, the pending promise, or the thrown error — which one depends on `state`. */
127
47
  contents: unknown;
128
48
  }
129
- /** The minimum of a Recoil `Snapshot` this adapter touches, generic in the ATOM type. */
130
49
  interface RecoilSnapshotLike<A> {
131
50
  getLoadable: (atom: A) => RecoilLoadableLike;
132
51
  }
133
- /** How one Recoil atom is projected: the value never travels without the load state beside it. */
134
52
  export interface RecoilAtomSnapshot {
135
53
  status: string;
136
54
  value: unknown;
137
55
  error: string | null;
138
56
  }
139
- /**
140
- * Expose a chosen set of Recoil atoms as one Reticle store.
141
- *
142
- * Three things about Recoil's API shape this adapter is built around, none of them optional:
143
- *
144
- * - **The atom map is forced, not an ergonomic choice.** Recoil has no enumerable registry of live
145
- * atoms, so "the whole store" is not a thing that exists. Naming the atoms is the only way to
146
- * snapshot them, and it doubles as a declaration of what matters — the same reason `jotaiStore`
147
- * takes one.
148
- * - **`.state`/`.contents`, never `.getValue()`.** On a pending async selector `getValue()` THROWS
149
- * the pending promise, and on a failed one it throws the error. Either would take down the whole
150
- * state read over one slow atom — so a loading atom reports `status: 'loading'` with a null value
151
- * rather than silently becoming an empty object or an exception.
152
- * - **One subscription, not one per atom.** Recoil exposes no per-atom subscription outside React
153
- * (`jotaiStore` gets `store.sub(atom, …)`; there is no equivalent here). The transaction stream is
154
- * the only public change signal, so the caller passes it in — the same "the library ships free
155
- * functions, so hand them over" shape as `valtioStore` and `mobxStore`.
156
- *
157
- * Wire it from a bridge component, which is where `useRecoilTransactionObserver_UNSTABLE` lives:
158
- *
159
- * ```tsx
160
- * const latest = useRef(snapshot_UNSTABLE());
161
- * const listeners = useRef(new Set<() => void>()).current;
162
- * useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
163
- * latest.current = snapshot;
164
- * for (const l of listeners) l();
165
- * });
166
- * registerStore('recoil', recoilStore({ cart: cartAtom }, () => latest.current, (l) => {
167
- * listeners.add(l);
168
- * return () => listeners.delete(l);
169
- * }));
170
- * ```
171
- */
172
57
  export declare function recoilStore<A>(atoms: Record<string, A>, snapshot: () => RecoilSnapshotLike<A>, subscribe: StoreSubscribe): StoreLike;
173
- /**
174
- * The minimum of a Svelte store this adapter touches.
175
- *
176
- * `subscribe` may return either the unsubscribe function or an object carrying one — the store
177
- * contract permits both, and an RxJS-shaped source returns the object form.
178
- */
179
58
  interface SvelteReadable {
180
59
  subscribe: (run: (value: unknown) => void) => (() => void) | {
181
60
  unsubscribe: () => void;
182
61
  };
183
62
  }
184
- /**
185
- * Expose a Svelte store as a Reticle store.
186
- *
187
- * Structurally unlike every other adapter in this file: they all wrap something that can be PULLED
188
- * (`getState`, `getSnapshot`, `snapshot(proxy)`, `toJS`). A Svelte store has no pull side at all —
189
- * `{ subscribe }` is the entire contract. What makes a pull possible anyway is the part of that
190
- * contract which says `subscribe` must call back **immediately and synchronously** with the current
191
- * value: subscribe, catch the value, unsubscribe, return it. That is precisely how `svelte/store`'s
192
- * own `get()` is implemented, so it is a blessed read rather than a trick.
193
- *
194
- * Doing it that way is what keeps this adapter free of the `dispose` the obvious implementation
195
- * needs. Caching the latest value would mean holding a permanent subscription, which means a
196
- * teardown the other five adapters don't have, which means a leak the moment a caller forgets it.
197
- * A transient subscription per read owns nothing and leaks nothing.
198
- *
199
- * ```ts
200
- * registerStore('cart', svelteStore(cartStore));
201
- * ```
202
- */
203
63
  export declare function svelteStore(readable: SvelteReadable, warn?: (message: string) => void): StoreLike;
204
- /**
205
- * The minimum of a Pinia store this adapter touches.
206
- *
207
- * Two things here look over-specified and are not, both found by compiling against the real library
208
- * rather than against a fake written to the same belief as the adapter:
209
- *
210
- * - **`$state: object`, not `Record<string, unknown>`.** A Pinia store's `$state` is a concrete
211
- * object type with named keys and no index signature, and TypeScript rejects those for a
212
- * `Record<string, unknown>` parameter. `piniaStore(useCartStore())` did not compile — the same
213
- * shape of failure as the Jotai contravariance note above, and equally invisible to a fake.
214
- * - **`flush` as the literal union**, not `string`. The option object is checked contravariantly, so
215
- * a widened `string` here makes the real `$subscribe` unassignable to this shape and the whole
216
- * store stops matching.
217
- */
218
64
  interface PiniaStoreLike {
219
65
  $state: object;
220
66
  $subscribe: (callback: (mutation: unknown, state: unknown) => void, options?: {
@@ -222,39 +68,7 @@ interface PiniaStoreLike {
222
68
  flush?: 'pre' | 'post' | 'sync';
223
69
  }) => () => void;
224
70
  }
225
- /**
226
- * Expose a Pinia store as a Reticle store. `$state` / `$subscribe` are close to `{getState,
227
- * subscribe}` but not close enough for `registerStore`'s duck-type, which is the whole reason a Vue
228
- * app currently connects and then reports no state at all.
229
- *
230
- * Both `$subscribe` options are load-bearing:
231
- *
232
- * - **`detached`** keeps the subscription alive past the component that happened to register the
233
- * store. Without it Pinia tears the listener down on unmount, so a store registered from inside a
234
- * component goes permanently silent after the first route change — readable, but never again
235
- * emitting a STATE_CHANGE, which reads exactly like an app that stopped changing.
236
- * - **`flush: 'sync'`** puts the notification inside the action's attribution window. The default
237
- * (`'post'`) defers it past the Vue update tick, and a state change that lands after the window
238
- * closed is no longer linked to the click that caused it in the causal summary.
239
- *
240
- * `$state` carries state, not getters — a Pinia getter is derived, so asserting on the state it
241
- * derives from is the stronger assertion anyway.
242
- *
243
- * ```ts
244
- * registerStore('cart', piniaStore(useCartStore()));
245
- * ```
246
- */
247
71
  export declare function piniaStore(store: PiniaStoreLike): StoreLike;
248
- /**
249
- * Build a store whose value is PUSHED in rather than pulled — for state that has no object to read.
250
- *
251
- * React Context and `useState`/`useReducer` keep their value inside the fiber tree; there is no store
252
- * instance and no subscription point outside React, so no adapter over a public API is possible. The
253
- * only way in is to invert the direction: the component holding the value tells Reticle when it
254
- * changes. `@reticlehq/react`'s `useReticleStore` hook is the ergonomic wrapper over this.
255
- *
256
- * Returns the store plus the `push` that updates it, so the caller owns the write side.
257
- */
258
72
  export declare function pushStore(initial: unknown): {
259
73
  store: StoreLike;
260
74
  push: (value: unknown) => void;