@buoy-gg/redux 7.0.5 → 7.0.7

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 (45) hide show
  1. package/README.md +21 -2
  2. package/lib/commonjs/index.js +38 -0
  3. package/lib/commonjs/redux/components/ReduxAutoInstrument.js +20 -0
  4. package/lib/commonjs/redux/components/ReduxModal.js +11 -1
  5. package/lib/commonjs/redux/components/index.js +8 -1
  6. package/lib/commonjs/redux/hooks/useAutoInstrumentRedux.js +32 -42
  7. package/lib/commonjs/redux/sync/reduxSyncAdapter.js +27 -8
  8. package/lib/commonjs/redux/utils/activeStoreRegistry.js +73 -0
  9. package/lib/commonjs/redux/utils/autoInstrument.js +88 -114
  10. package/lib/commonjs/redux/utils/buoyReduxMiddleware.js +20 -15
  11. package/lib/commonjs/redux/utils/devtoolsHook.js +137 -0
  12. package/lib/commonjs/redux/utils/index.js +32 -1
  13. package/lib/module/index.js +26 -3
  14. package/lib/module/redux/components/ReduxAutoInstrument.js +17 -0
  15. package/lib/module/redux/components/ReduxModal.js +11 -1
  16. package/lib/module/redux/components/index.js +2 -1
  17. package/lib/module/redux/hooks/useAutoInstrumentRedux.js +33 -43
  18. package/lib/module/redux/sync/reduxSyncAdapter.js +28 -9
  19. package/lib/module/redux/utils/activeStoreRegistry.js +63 -0
  20. package/lib/module/redux/utils/autoInstrument.js +82 -113
  21. package/lib/module/redux/utils/buoyReduxMiddleware.js +20 -15
  22. package/lib/module/redux/utils/devtoolsHook.js +130 -0
  23. package/lib/module/redux/utils/index.js +3 -1
  24. package/lib/typescript/index.d.ts +18 -4
  25. package/lib/typescript/index.d.ts.map +1 -1
  26. package/lib/typescript/redux/components/ReduxAutoInstrument.d.ts +11 -0
  27. package/lib/typescript/redux/components/ReduxAutoInstrument.d.ts.map +1 -0
  28. package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -1
  29. package/lib/typescript/redux/components/index.d.ts +1 -0
  30. package/lib/typescript/redux/components/index.d.ts.map +1 -1
  31. package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts +16 -28
  32. package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts.map +1 -1
  33. package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts +18 -4
  34. package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +1 -1
  35. package/lib/typescript/redux/utils/activeStoreRegistry.d.ts +47 -0
  36. package/lib/typescript/redux/utils/activeStoreRegistry.d.ts.map +1 -0
  37. package/lib/typescript/redux/utils/autoInstrument.d.ts +46 -23
  38. package/lib/typescript/redux/utils/autoInstrument.d.ts.map +1 -1
  39. package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts +5 -1
  40. package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts.map +1 -1
  41. package/lib/typescript/redux/utils/devtoolsHook.d.ts +45 -0
  42. package/lib/typescript/redux/utils/devtoolsHook.d.ts.map +1 -0
  43. package/lib/typescript/redux/utils/index.d.ts +3 -2
  44. package/lib/typescript/redux/utils/index.d.ts.map +1 -1
  45. package/package.json +8 -5
package/README.md CHANGED
@@ -14,7 +14,7 @@ npm install @buoy-gg/core @buoy-gg/redux
14
14
 
15
15
  ## Quick start
16
16
 
17
- Zero config. Your existing store works as-is — no middleware, no wrapper. The tool auto-instruments the store when `FloatingDevTools` mounts.
17
+ Zero config. Your existing store works as-is — no middleware, no wrapper. Buoy hooks the store at creation via the official Redux DevTools integration point (which Redux Toolkit enables by default), so actions are captured from your app's very first dispatch — including everything dispatched inside thunks and RTK Query.
18
18
 
19
19
  ```tsx
20
20
  import { configureStore } from '@reduxjs/toolkit';
@@ -33,9 +33,28 @@ export default function App() {
33
33
  }
34
34
  ```
35
35
 
36
+ ### How capture works (and how to guarantee full capture)
37
+
38
+ Buoy binds to your store the earliest way available, in this order:
39
+
40
+ 1. **Store-creation hook (default, full capture)** — importing `@buoy-gg/redux` claims the Redux DevTools global that RTK checks by default. If the package loads before your store module, Buoy is inside the store from creation: every action (thunk-internal, RTK Query) is captured and time travel is real. To **guarantee** this ordering, make it the first import of your app entry:
41
+
42
+ ```tsx
43
+ // index.js — first line
44
+ import '@buoy-gg/redux';
45
+ ```
46
+
47
+ 2. **Middleware (explicit full capture)** — one line if you prefer being explicit:
48
+
49
+ ```tsx
50
+ middleware: (getDefault) => getDefault().concat(buoyReduxMiddleware),
51
+ ```
52
+
53
+ 3. **Startup fallback** — if your store was created before Buoy loaded, `FloatingDevTools` binds it automatically at app mount. Top-level dispatches are captured from startup; actions dispatched *inside* thunks/middleware aren't visible to this mode (the tool tells you when it's in it).
54
+
36
55
  ### Optional: full time travel
37
56
 
38
- To jump back to past states (not just view them), wrap your reducer:
57
+ Time travel works out of the box on path 1. On the middleware path, wrap your reducer:
39
58
 
40
59
  ```tsx
41
60
  import { withBuoyDevTools } from '@buoy-gg/redux';
@@ -45,6 +45,12 @@ Object.defineProperty(exports, "ReduxActionItem", {
45
45
  return _ReduxActionItem.ReduxActionItem;
46
46
  }
47
47
  });
48
+ Object.defineProperty(exports, "ReduxAutoInstrument", {
49
+ enumerable: true,
50
+ get: function () {
51
+ return _ReduxAutoInstrument.ReduxAutoInstrument;
52
+ }
53
+ });
48
54
  Object.defineProperty(exports, "ReduxIcon", {
49
55
  enumerable: true,
50
56
  get: function () {
@@ -63,12 +69,24 @@ Object.defineProperty(exports, "ReduxToolView", {
63
69
  return _ReduxModal.ReduxToolView;
64
70
  }
65
71
  });
72
+ Object.defineProperty(exports, "buoyDevtoolsEnhancer", {
73
+ enumerable: true,
74
+ get: function () {
75
+ return _devtoolsHook.buoyDevtoolsEnhancer;
76
+ }
77
+ });
66
78
  Object.defineProperty(exports, "buoyReduxMiddleware", {
67
79
  enumerable: true,
68
80
  get: function () {
69
81
  return _buoyReduxMiddleware.buoyReduxMiddleware;
70
82
  }
71
83
  });
84
+ Object.defineProperty(exports, "claimDevtoolsHook", {
85
+ enumerable: true,
86
+ get: function () {
87
+ return _devtoolsHook.claimDevtoolsHook;
88
+ }
89
+ });
72
90
  Object.defineProperty(exports, "createBuoyReduxMiddleware", {
73
91
  enumerable: true,
74
92
  get: function () {
@@ -93,6 +111,12 @@ Object.defineProperty(exports, "getActiveStore", {
93
111
  return _autoInstrument.getActiveStore;
94
112
  }
95
113
  });
114
+ Object.defineProperty(exports, "getInstrumentationStatus", {
115
+ enumerable: true,
116
+ get: function () {
117
+ return _autoInstrument.getInstrumentationStatus;
118
+ }
119
+ });
96
120
  Object.defineProperty(exports, "instrumentStore", {
97
121
  enumerable: true,
98
122
  get: function () {
@@ -105,6 +129,12 @@ Object.defineProperty(exports, "isAutoInstrumentActive", {
105
129
  return _autoInstrument.isAutoInstrumentActive;
106
130
  }
107
131
  });
132
+ Object.defineProperty(exports, "isDevtoolsHookClaimed", {
133
+ enumerable: true,
134
+ get: function () {
135
+ return _devtoolsHook.isDevtoolsHookClaimed;
136
+ }
137
+ });
108
138
  Object.defineProperty(exports, "isStoreInstrumented", {
109
139
  enumerable: true,
110
140
  get: function () {
@@ -141,6 +171,12 @@ Object.defineProperty(exports, "reduxToolPreset", {
141
171
  return _preset.reduxToolPreset;
142
172
  }
143
173
  });
174
+ Object.defineProperty(exports, "registerReduxStore", {
175
+ enumerable: true,
176
+ get: function () {
177
+ return _autoInstrument.registerReduxStore;
178
+ }
179
+ });
144
180
  Object.defineProperty(exports, "replayAction", {
145
181
  enumerable: true,
146
182
  get: function () {
@@ -177,6 +213,7 @@ Object.defineProperty(exports, "withBuoyDevTools", {
177
213
  return _buoyReduxMiddleware.withBuoyDevTools;
178
214
  }
179
215
  });
216
+ var _devtoolsHook = require("./redux/utils/devtoolsHook");
180
217
  var _preset = require("./preset");
181
218
  var _buoyReduxMiddleware = require("./redux/utils/buoyReduxMiddleware");
182
219
  var _autoInstrument = require("./redux/utils/autoInstrument");
@@ -189,4 +226,5 @@ var _ReduxActionItem = require("./redux/components/ReduxActionItem");
189
226
  var _ReduxActionDetailView = require("./redux/components/ReduxActionDetailView");
190
227
  var _ReduxActionDetailContent = require("./redux/components/ReduxActionDetailContent");
191
228
  var _ReduxIcon = require("./redux/components/ReduxIcon");
229
+ var _ReduxAutoInstrument = require("./redux/components/ReduxAutoInstrument");
192
230
  var _reduxActionStore = require("./redux/utils/reduxActionStore");
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ReduxAutoInstrument = ReduxAutoInstrument;
7
+ var _useAutoInstrumentRedux = require("../hooks/useAutoInstrumentRedux");
8
+ /**
9
+ * Headless startup instrumentation. Auto-rendered by FloatingDevTools (same
10
+ * pattern as RouteTracker & the overlay tools) so the Redux store is bound
11
+ * from app mount — action log, MCP getState/dispatch, and desktop sync all
12
+ * work WITHOUT the user ever opening the Redux tool. Renders nothing.
13
+ *
14
+ * Safe anywhere: if react-redux is missing or there's no Provider above, the
15
+ * hook records the diagnostic and fails gracefully.
16
+ */
17
+ function ReduxAutoInstrument() {
18
+ (0, _useAutoInstrumentRedux.useAutoInstrumentRedux)();
19
+ return null;
20
+ }
@@ -58,7 +58,8 @@ function ReduxModal({
58
58
  isInstrumented,
59
59
  isLoading: isInstrumentLoading,
60
60
  error: instrumentError,
61
- isReactReduxAvailable
61
+ isReactReduxAvailable,
62
+ captureMechanism
62
63
  } = (0, _useAutoInstrumentRedux.useAutoInstrumentRedux)();
63
64
  const {
64
65
  filteredActions,
@@ -314,6 +315,15 @@ function ReduxModal({
314
315
  style: styles.disabledText,
315
316
  children: "Action capture is disabled"
316
317
  })]
318
+ }), captureMechanism === "patch" && !_reduxActionStore.reduxActionStore.isCaptureSuppressed() && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
319
+ style: styles.disabledBanner,
320
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
321
+ size: 14,
322
+ color: _sharedUi.macOSColors.semantic.warning
323
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
324
+ style: styles.disabledText,
325
+ children: "Top-level capture only \u2014 thunk/RTK Query internals aren't visible. Put import '@buoy-gg/redux' first in your app entry (or add buoyReduxMiddleware) for full capture."
326
+ })]
317
327
  }), hasLockedActions && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
318
328
  style: styles.lockedBanner,
319
329
  onPress: handleUpgradePress,
@@ -33,6 +33,12 @@ Object.defineProperty(exports, "ReduxActionItem", {
33
33
  return _ReduxActionItem.ReduxActionItem;
34
34
  }
35
35
  });
36
+ Object.defineProperty(exports, "ReduxAutoInstrument", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _ReduxAutoInstrument.ReduxAutoInstrument;
40
+ }
41
+ });
36
42
  Object.defineProperty(exports, "ReduxIcon", {
37
43
  enumerable: true,
38
44
  get: function () {
@@ -49,4 +55,5 @@ var _ReduxModal = require("./ReduxModal");
49
55
  var _ReduxActionItem = require("./ReduxActionItem");
50
56
  var _ReduxActionDetailView = require("./ReduxActionDetailView");
51
57
  var _ReduxActionDetailContent = require("./ReduxActionDetailContent");
52
- var _ReduxIcon = require("./ReduxIcon");
58
+ var _ReduxIcon = require("./ReduxIcon");
59
+ var _ReduxAutoInstrument = require("./ReduxAutoInstrument");
@@ -7,29 +7,21 @@ exports.useAutoInstrumentRedux = useAutoInstrumentRedux;
7
7
  exports.useReduxAvailability = useReduxAvailability;
8
8
  var _react = require("react");
9
9
  var _autoInstrument = require("../utils/autoInstrument");
10
+ var _activeStoreRegistry = require("../utils/activeStoreRegistry");
10
11
  /**
11
12
  * React hook for auto-instrumenting Redux stores
12
13
  *
13
- * This hook automatically patches the Redux store when the component mounts,
14
- * providing zero-config action capture for the Redux DevTools.
14
+ * Mounted for the app's lifetime by FloatingDevTools (via ReduxAutoInstrument)
15
+ * and also called by the Redux modal. Binds Buoy to the store from react-redux
16
+ * context:
17
+ * - If the store was created through the devtools-hook enhancer, capture is
18
+ * already live — this just keeps the registry pointed at it.
19
+ * - Otherwise it applies the dispatch-patch fallback (top-level-only capture).
20
+ * - If react-redux is missing or there's no Provider above, it fails
21
+ * gracefully and records WHY (surfaced by the sync adapter / MCP).
15
22
  *
16
- * Usage:
17
- * - The hook attempts to use react-redux's useStore() to get the store
18
- * - If successful, it instruments the store automatically
19
- * - If react-redux is not available or not in a Provider, it fails gracefully
20
- *
21
- * @example
22
- * ```tsx
23
- * function ReduxDevTools() {
24
- * const { isInstrumented, error } = useAutoInstrumentRedux();
25
- *
26
- * if (error) {
27
- * return <Text>Redux not available: {error}</Text>;
28
- * }
29
- *
30
- * return <ReduxActionList />;
31
- * }
32
- * ```
23
+ * Re-instruments when the store INSTANCE changes (hosts that rebuild their
24
+ * store at runtime), so the registry never pins a dead store.
33
25
  */
34
26
 
35
27
  // ============================================
@@ -59,19 +51,16 @@ function getReactReduxUseStore() {
59
51
  // Main Hook
60
52
  // ============================================
61
53
 
62
- /**
63
- * Hook to automatically instrument Redux store for DevTools
64
- *
65
- * This provides zero-config Redux action capture by:
66
- * 1. Using react-redux's useStore() to get the Redux store
67
- * 2. Automatically patching store.dispatch to capture actions
68
- * 3. Cleaning up when the component unmounts
69
- */
70
54
  function useAutoInstrumentRedux(options = {}) {
71
55
  const {
72
- autoStart = true,
73
- ...instrumentOptions
56
+ autoStart = true
74
57
  } = options;
58
+
59
+ // Options live in a ref: this hook is app-lifetime-mounted, and a fresh
60
+ // `{...rest}` object every render would churn startInstrumentation's
61
+ // identity and re-run the effect on every render.
62
+ const optionsRef = (0, _react.useRef)(options);
63
+ optionsRef.current = options;
75
64
  const [isInstrumented, setIsInstrumented] = (0, _react.useState)(false);
76
65
  const [isLoading, setIsLoading] = (0, _react.useState)(true);
77
66
  const [error, setError] = (0, _react.useState)(null);
@@ -86,37 +75,35 @@ function useAutoInstrumentRedux(options = {}) {
86
75
  // This must be called unconditionally due to hooks rules
87
76
  let reduxStore = null;
88
77
  let storeError = null;
78
+ let storeDiagnostic = null;
89
79
  if (useStoreHook) {
90
80
  try {
91
81
  // eslint-disable-next-line react-hooks/rules-of-hooks
92
82
  reduxStore = useStoreHook();
93
83
  } catch (e) {
94
84
  // Not inside a Provider or other error
85
+ storeDiagnostic = "no-provider";
95
86
  storeError = e instanceof Error ? e.message : "Failed to access Redux store. Make sure your app is wrapped in a Redux Provider.";
96
87
  }
97
88
  } else {
89
+ storeDiagnostic = "no-react-redux";
98
90
  storeError = "react-redux is not installed or not available";
99
91
  }
100
92
 
101
93
  // Function to start instrumentation
102
94
  const startInstrumentation = (0, _react.useCallback)(() => {
103
95
  if (!reduxStore) {
96
+ // Record the failure reason for non-React surfaces (sync adapter/MCP).
97
+ (0, _activeStoreRegistry.setReduxDiagnostic)(storeDiagnostic);
104
98
  setError(storeError || "No Redux store available");
105
99
  setIsLoading(false);
106
100
  return;
107
101
  }
108
-
109
- // Check if already instrumented
110
- if ((0, _autoInstrument.isStoreInstrumented)(reduxStore)) {
111
- setIsInstrumented(true);
112
- setStore(reduxStore);
113
- setIsLoading(false);
114
- setError(null);
115
- return;
116
- }
117
102
  try {
118
- // Instrument the store
119
- const cleanup = (0, _autoInstrument.instrumentStore)(reduxStore, instrumentOptions);
103
+ // Instrument the store. Idempotent per store: enhancer-created and
104
+ // already-patched stores just get (re)registered, so a store SWAP at
105
+ // runtime re-binds the registry to the new instance.
106
+ const cleanup = (0, _autoInstrument.instrumentStore)(reduxStore, optionsRef.current);
120
107
  cleanupRef.current = cleanup;
121
108
  setIsInstrumented(true);
122
109
  setStore(reduxStore);
@@ -127,7 +114,8 @@ function useAutoInstrumentRedux(options = {}) {
127
114
  } finally {
128
115
  setIsLoading(false);
129
116
  }
130
- }, [reduxStore, storeError, instrumentOptions]);
117
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- storeError/storeDiagnostic derive from reduxStore
118
+ }, [reduxStore]);
131
119
 
132
120
  // Function to stop instrumentation
133
121
  const stopInstrumentation = (0, _react.useCallback)(() => {
@@ -142,7 +130,8 @@ function useAutoInstrumentRedux(options = {}) {
142
130
  setStore(null);
143
131
  }, [store]);
144
132
 
145
- // Auto-start instrumentation on mount
133
+ // Auto-start instrumentation on mount, and again whenever the store
134
+ // INSTANCE changes (hosts that rebuild their store re-point the registry).
146
135
  (0, _react.useEffect)(() => {
147
136
  if (autoStart) {
148
137
  startInstrumentation();
@@ -163,6 +152,7 @@ function useAutoInstrumentRedux(options = {}) {
163
152
  isLoading,
164
153
  error,
165
154
  isReactReduxAvailable,
155
+ captureMechanism: (0, _autoInstrument.getInstrumentationStatus)().mechanism,
166
156
  startInstrumentation,
167
157
  stopInstrumentation,
168
158
  store
@@ -11,14 +11,31 @@ function topLevelKeys(state) {
11
11
  return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
12
12
  }
13
13
 
14
+ /**
15
+ * Why there's no bound store, in a shape remote drivers (MCP/desktop) can turn
16
+ * into a SPECIFIC remedy instead of a generic "check your setup".
17
+ */
18
+ function unavailable() {
19
+ const status = (0, _autoInstrument.getInstrumentationStatus)();
20
+ const reason = status.diagnostic ?? "not-instrumented";
21
+ return {
22
+ available: false,
23
+ slices: [],
24
+ /** 'no-react-redux' | 'no-provider' | 'not-instrumented' */
25
+ reason
26
+ };
27
+ }
28
+
14
29
  /**
15
30
  * Sync adapter for the redux tool, consumed by @buoy-gg/external-sync's
16
31
  * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
17
32
  * this package doesn't need a dependency on it).
18
33
  *
19
- * Actions are captured by the auto-instrumentation middleware (installed when
20
- * FloatingDevTools mounts), independent of whether a dashboard is watching —
21
- * subscribing here only streams what the store already records.
34
+ * Actions are captured from app startup — by the store-creation enhancer or
35
+ * the explicit middleware (full capture), or by the dispatch-patch fallback
36
+ * mounted with FloatingDevTools (top-level-only) — independent of whether a
37
+ * dashboard is watching. Subscribing here only streams what's already
38
+ * recorded.
22
39
  *
23
40
  * `getState`/`dispatch` read & drive the live instrumented store (via
24
41
  * getActiveStore()) so a remote driver (MCP/LLM) can inspect current state and
@@ -43,15 +60,16 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
43
60
  } = params ?? {};
44
61
  const store = (0, _autoInstrument.getActiveStore)();
45
62
  if (!store) {
46
- return {
47
- available: false,
48
- slices: []
49
- };
63
+ return unavailable();
50
64
  }
51
65
  const state = store.getState();
66
+ const status = (0, _autoInstrument.getInstrumentationStatus)();
52
67
  return {
53
68
  available: true,
54
69
  slices: topLevelKeys(state),
70
+ /** 'full' | 'top-level-only' — what the live capture mechanism sees. */
71
+ capture: status.capture,
72
+ mechanism: status.mechanism,
55
73
  ...(includeValues ? {
56
74
  state
57
75
  } : {})
@@ -65,7 +83,8 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
65
83
  const store = (0, _autoInstrument.getActiveStore)();
66
84
  if (!store) {
67
85
  return {
68
- dispatched: false
86
+ dispatched: false,
87
+ ...unavailable()
69
88
  };
70
89
  }
71
90
  store.dispatch(action);
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ENHANCER_SYMBOL = void 0;
7
+ exports.getActiveStore = getActiveStore;
8
+ exports.getCaptureMechanism = getCaptureMechanism;
9
+ exports.getInstrumentationStatus = getInstrumentationStatus;
10
+ exports.registerActiveStore = registerActiveStore;
11
+ exports.setReduxDiagnostic = setReduxDiagnostic;
12
+ exports.unregisterActiveStore = unregisterActiveStore;
13
+ /**
14
+ * Shared registry for the one live Redux store Buoy is bound to, plus how it
15
+ * got bound (the capture mechanism) and why binding failed when it did.
16
+ *
17
+ * Lives in its own module so the three capture paths (devtools-hook enhancer,
18
+ * explicit middleware, dispatch-patch fallback) and the sync adapter can all
19
+ * import it without cycles.
20
+ */
21
+
22
+ /** The minimum store surface Buoy needs (middleware storeApi satisfies this). */
23
+
24
+ /**
25
+ * How the store got instrumented:
26
+ * - `enhancer` — store-creation-time enhancer via the Redux DevTools global.
27
+ * FULL capture (thunk-internal & RTK Query actions included).
28
+ * - `middleware` — user-added buoyReduxMiddleware. FULL capture.
29
+ * - `patch` — store.dispatch patched after creation (auto-instrumentation
30
+ * fallback). TOP-LEVEL-ONLY capture: actions dispatched from
31
+ * inside middleware/thunks bypass the patch.
32
+ */
33
+
34
+ /** Why there is no bound store, when there isn't one. */
35
+
36
+ /** Marks stores created through the Buoy devtools-hook enhancer. */
37
+ const ENHANCER_SYMBOL = exports.ENHANCER_SYMBOL = Symbol.for("@@buoy/enhancerInstrumented");
38
+ let activeStore = null;
39
+ let mechanism = null;
40
+ let diagnostic = null;
41
+ function registerActiveStore(store, mech) {
42
+ activeStore = store;
43
+ mechanism = mech;
44
+ diagnostic = null;
45
+ }
46
+ function unregisterActiveStore(store) {
47
+ if (activeStore === store) {
48
+ activeStore = null;
49
+ mechanism = null;
50
+ }
51
+ }
52
+ function getActiveStore() {
53
+ return activeStore;
54
+ }
55
+ function getCaptureMechanism() {
56
+ return mechanism;
57
+ }
58
+
59
+ /**
60
+ * Recorded by the auto-instrument hook so non-React surfaces (sync adapter,
61
+ * MCP) can report WHY there's no store instead of a generic failure.
62
+ */
63
+ function setReduxDiagnostic(d) {
64
+ diagnostic = d;
65
+ }
66
+ function getInstrumentationStatus() {
67
+ return {
68
+ mechanism,
69
+ capture: mechanism === "enhancer" || mechanism === "middleware" ? "full" : mechanism === "patch" ? "top-level-only" : "none",
70
+ hasStore: activeStore !== null,
71
+ diagnostic
72
+ };
73
+ }