@buoy-gg/redux 7.0.4 → 7.0.6
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.
- package/README.md +21 -2
- package/lib/commonjs/index.js +44 -0
- package/lib/commonjs/redux/components/ReduxAutoInstrument.js +20 -0
- package/lib/commonjs/redux/components/ReduxModal.js +123 -68
- package/lib/commonjs/redux/components/index.js +8 -1
- package/lib/commonjs/redux/hooks/useAutoInstrumentRedux.js +32 -42
- package/lib/commonjs/redux/sync/reduxSyncAdapter.js +27 -8
- package/lib/commonjs/redux/utils/activeStoreRegistry.js +73 -0
- package/lib/commonjs/redux/utils/autoInstrument.js +88 -114
- package/lib/commonjs/redux/utils/buoyReduxMiddleware.js +20 -15
- package/lib/commonjs/redux/utils/devtoolsHook.js +137 -0
- package/lib/commonjs/redux/utils/index.js +32 -1
- package/lib/module/index.js +29 -4
- package/lib/module/redux/components/ReduxAutoInstrument.js +17 -0
- package/lib/module/redux/components/ReduxModal.js +122 -68
- package/lib/module/redux/components/index.js +2 -1
- package/lib/module/redux/hooks/useAutoInstrumentRedux.js +33 -43
- package/lib/module/redux/sync/reduxSyncAdapter.js +28 -9
- package/lib/module/redux/utils/activeStoreRegistry.js +63 -0
- package/lib/module/redux/utils/autoInstrument.js +82 -113
- package/lib/module/redux/utils/buoyReduxMiddleware.js +20 -15
- package/lib/module/redux/utils/devtoolsHook.js +130 -0
- package/lib/module/redux/utils/index.js +3 -1
- package/lib/typescript/index.d.ts +19 -5
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxAutoInstrument.d.ts +11 -0
- package/lib/typescript/redux/components/ReduxAutoInstrument.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxModal.d.ts +15 -1
- package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -1
- package/lib/typescript/redux/components/index.d.ts +1 -0
- package/lib/typescript/redux/components/index.d.ts.map +1 -1
- package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts +16 -28
- package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts.map +1 -1
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts +18 -4
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/redux/utils/activeStoreRegistry.d.ts +47 -0
- package/lib/typescript/redux/utils/activeStoreRegistry.d.ts.map +1 -0
- package/lib/typescript/redux/utils/autoInstrument.d.ts +46 -23
- package/lib/typescript/redux/utils/autoInstrument.d.ts.map +1 -1
- package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts +5 -1
- package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts.map +1 -1
- package/lib/typescript/redux/utils/devtoolsHook.d.ts +45 -0
- package/lib/typescript/redux/utils/devtoolsHook.d.ts.map +1 -0
- package/lib/typescript/redux/utils/index.d.ts +3 -2
- package/lib/typescript/redux/utils/index.d.ts.map +1 -1
- 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.
|
|
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
|
-
|
|
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';
|
package/lib/commonjs/index.js
CHANGED
|
@@ -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 () {
|
|
@@ -57,12 +63,30 @@ Object.defineProperty(exports, "ReduxModal", {
|
|
|
57
63
|
return _ReduxModal.ReduxModal;
|
|
58
64
|
}
|
|
59
65
|
});
|
|
66
|
+
Object.defineProperty(exports, "ReduxToolView", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
get: function () {
|
|
69
|
+
return _ReduxModal.ReduxToolView;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
Object.defineProperty(exports, "buoyDevtoolsEnhancer", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
get: function () {
|
|
75
|
+
return _devtoolsHook.buoyDevtoolsEnhancer;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
60
78
|
Object.defineProperty(exports, "buoyReduxMiddleware", {
|
|
61
79
|
enumerable: true,
|
|
62
80
|
get: function () {
|
|
63
81
|
return _buoyReduxMiddleware.buoyReduxMiddleware;
|
|
64
82
|
}
|
|
65
83
|
});
|
|
84
|
+
Object.defineProperty(exports, "claimDevtoolsHook", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
get: function () {
|
|
87
|
+
return _devtoolsHook.claimDevtoolsHook;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
66
90
|
Object.defineProperty(exports, "createBuoyReduxMiddleware", {
|
|
67
91
|
enumerable: true,
|
|
68
92
|
get: function () {
|
|
@@ -87,6 +111,12 @@ Object.defineProperty(exports, "getActiveStore", {
|
|
|
87
111
|
return _autoInstrument.getActiveStore;
|
|
88
112
|
}
|
|
89
113
|
});
|
|
114
|
+
Object.defineProperty(exports, "getInstrumentationStatus", {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: function () {
|
|
117
|
+
return _autoInstrument.getInstrumentationStatus;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
90
120
|
Object.defineProperty(exports, "instrumentStore", {
|
|
91
121
|
enumerable: true,
|
|
92
122
|
get: function () {
|
|
@@ -99,6 +129,12 @@ Object.defineProperty(exports, "isAutoInstrumentActive", {
|
|
|
99
129
|
return _autoInstrument.isAutoInstrumentActive;
|
|
100
130
|
}
|
|
101
131
|
});
|
|
132
|
+
Object.defineProperty(exports, "isDevtoolsHookClaimed", {
|
|
133
|
+
enumerable: true,
|
|
134
|
+
get: function () {
|
|
135
|
+
return _devtoolsHook.isDevtoolsHookClaimed;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
102
138
|
Object.defineProperty(exports, "isStoreInstrumented", {
|
|
103
139
|
enumerable: true,
|
|
104
140
|
get: function () {
|
|
@@ -135,6 +171,12 @@ Object.defineProperty(exports, "reduxToolPreset", {
|
|
|
135
171
|
return _preset.reduxToolPreset;
|
|
136
172
|
}
|
|
137
173
|
});
|
|
174
|
+
Object.defineProperty(exports, "registerReduxStore", {
|
|
175
|
+
enumerable: true,
|
|
176
|
+
get: function () {
|
|
177
|
+
return _autoInstrument.registerReduxStore;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
138
180
|
Object.defineProperty(exports, "replayAction", {
|
|
139
181
|
enumerable: true,
|
|
140
182
|
get: function () {
|
|
@@ -171,6 +213,7 @@ Object.defineProperty(exports, "withBuoyDevTools", {
|
|
|
171
213
|
return _buoyReduxMiddleware.withBuoyDevTools;
|
|
172
214
|
}
|
|
173
215
|
});
|
|
216
|
+
var _devtoolsHook = require("./redux/utils/devtoolsHook");
|
|
174
217
|
var _preset = require("./preset");
|
|
175
218
|
var _buoyReduxMiddleware = require("./redux/utils/buoyReduxMiddleware");
|
|
176
219
|
var _autoInstrument = require("./redux/utils/autoInstrument");
|
|
@@ -183,4 +226,5 @@ var _ReduxActionItem = require("./redux/components/ReduxActionItem");
|
|
|
183
226
|
var _ReduxActionDetailView = require("./redux/components/ReduxActionDetailView");
|
|
184
227
|
var _ReduxActionDetailContent = require("./redux/components/ReduxActionDetailContent");
|
|
185
228
|
var _ReduxIcon = require("./redux/components/ReduxIcon");
|
|
229
|
+
var _ReduxAutoInstrument = require("./redux/components/ReduxAutoInstrument");
|
|
186
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
|
+
}
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.FREE_TIER_ACTION_LIMIT = void 0;
|
|
7
7
|
exports.ReduxModal = ReduxModal;
|
|
8
|
+
exports.ReduxToolView = ReduxToolView;
|
|
8
9
|
var _react = require("react");
|
|
9
10
|
var _reactNative = require("react-native");
|
|
10
11
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
@@ -46,7 +47,8 @@ function ReduxModal({
|
|
|
46
47
|
onClose,
|
|
47
48
|
onBack,
|
|
48
49
|
onMinimize,
|
|
49
|
-
enableSharedModalDimensions = false
|
|
50
|
+
enableSharedModalDimensions = false,
|
|
51
|
+
inline = false
|
|
50
52
|
}) {
|
|
51
53
|
const isPro = (0, _license.useIsPro)();
|
|
52
54
|
const [showProModal, setShowProModal] = (0, _react.useState)(false);
|
|
@@ -56,7 +58,8 @@ function ReduxModal({
|
|
|
56
58
|
isInstrumented,
|
|
57
59
|
isLoading: isInstrumentLoading,
|
|
58
60
|
error: instrumentError,
|
|
59
|
-
isReactReduxAvailable
|
|
61
|
+
isReactReduxAvailable,
|
|
62
|
+
captureMechanism
|
|
60
63
|
} = (0, _useAutoInstrumentRedux.useAutoInstrumentRedux)();
|
|
61
64
|
const {
|
|
62
65
|
filteredActions,
|
|
@@ -285,6 +288,95 @@ function ReduxModal({
|
|
|
285
288
|
// The action list owns its scrolling so FlatList virtualization works
|
|
286
289
|
// (see ZustandModal — same pattern, same tool-bench evidence).
|
|
287
290
|
const listOwnsScroll = selectedAction == null;
|
|
291
|
+
const body = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
292
|
+
style: styles.container,
|
|
293
|
+
children: selectedAction && selectedActionIndex !== null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ReduxActionDetailContent.ReduxActionDetailContent, {
|
|
294
|
+
action: selectedAction,
|
|
295
|
+
actions: visibleActions,
|
|
296
|
+
selectedIndex: selectedActionIndex,
|
|
297
|
+
onIndexChange: handleIndexChange,
|
|
298
|
+
disableInternalFooter: true
|
|
299
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
300
|
+
children: [instrumentError && !isInstrumented && !_reduxActionStore.reduxActionStore.isCaptureSuppressed() && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
301
|
+
style: styles.errorBanner,
|
|
302
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
|
|
303
|
+
size: 14,
|
|
304
|
+
color: _sharedUi.macOSColors.semantic.error
|
|
305
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
306
|
+
style: styles.errorText,
|
|
307
|
+
children: isReactReduxAvailable ? "Not inside a Redux Provider. Wrap your app with <Provider store={store}>." : "react-redux not installed. Run: npm install react-redux"
|
|
308
|
+
})]
|
|
309
|
+
}), !isEnabled && isInstrumented && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
310
|
+
style: styles.disabledBanner,
|
|
311
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Power, {
|
|
312
|
+
size: 14,
|
|
313
|
+
color: _sharedUi.macOSColors.semantic.warning
|
|
314
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
315
|
+
style: styles.disabledText,
|
|
316
|
+
children: "Action capture is disabled"
|
|
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
|
+
})]
|
|
327
|
+
}), hasLockedActions && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
328
|
+
style: styles.lockedBanner,
|
|
329
|
+
onPress: handleUpgradePress,
|
|
330
|
+
activeOpacity: 0.8,
|
|
331
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
|
|
332
|
+
size: 14,
|
|
333
|
+
color: _sharedUi.buoyColors.primary
|
|
334
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
335
|
+
style: styles.lockedText,
|
|
336
|
+
children: [lockedActionCount, " older ", lockedActionCount === 1 ? "action" : "actions", " locked"]
|
|
337
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
338
|
+
style: styles.upgradeBadge,
|
|
339
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
340
|
+
style: styles.upgradeBadgeText,
|
|
341
|
+
children: "UPGRADE"
|
|
342
|
+
})
|
|
343
|
+
})]
|
|
344
|
+
}), visibleActions.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
|
|
345
|
+
ref: flatListRef,
|
|
346
|
+
data: visibleActions,
|
|
347
|
+
renderItem: renderItem,
|
|
348
|
+
keyExtractor: keyExtractor,
|
|
349
|
+
contentContainerStyle: styles.listContent,
|
|
350
|
+
showsVerticalScrollIndicator: true,
|
|
351
|
+
removeClippedSubviews: true,
|
|
352
|
+
initialNumToRender: 12,
|
|
353
|
+
maxToRenderPerBatch: 8,
|
|
354
|
+
windowSize: 5,
|
|
355
|
+
updateCellsBatchingPeriod: 50
|
|
356
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {
|
|
357
|
+
isEnabled: isEnabled
|
|
358
|
+
})]
|
|
359
|
+
})
|
|
360
|
+
});
|
|
361
|
+
const upgradeModal = /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
|
|
362
|
+
visible: showProModal,
|
|
363
|
+
onClose: () => setShowProModal(false),
|
|
364
|
+
featureName: "Full Action History"
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// The ONLY difference between the phone and the desktop dashboard is which
|
|
368
|
+
// shell the header, body and footer go in. Everything above this is shared.
|
|
369
|
+
if (inline) {
|
|
370
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.TickProvider, {
|
|
371
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
372
|
+
style: styles.inlineRoot,
|
|
373
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
374
|
+
style: styles.inlineHeader,
|
|
375
|
+
children: renderHeaderContent()
|
|
376
|
+
}), body, footerNode]
|
|
377
|
+
}), upgradeModal]
|
|
378
|
+
});
|
|
379
|
+
}
|
|
288
380
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.TickProvider, {
|
|
289
381
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.JsModal, {
|
|
290
382
|
visible: visible,
|
|
@@ -303,75 +395,38 @@ function ReduxModal({
|
|
|
303
395
|
styles: {},
|
|
304
396
|
footer: footerNode,
|
|
305
397
|
footerHeight: footerHeight,
|
|
306
|
-
children:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
})]
|
|
324
|
-
}), !isEnabled && isInstrumented && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
325
|
-
style: styles.disabledBanner,
|
|
326
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Power, {
|
|
327
|
-
size: 14,
|
|
328
|
-
color: _sharedUi.macOSColors.semantic.warning
|
|
329
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
330
|
-
style: styles.disabledText,
|
|
331
|
-
children: "Action capture is disabled"
|
|
332
|
-
})]
|
|
333
|
-
}), hasLockedActions && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
334
|
-
style: styles.lockedBanner,
|
|
335
|
-
onPress: handleUpgradePress,
|
|
336
|
-
activeOpacity: 0.8,
|
|
337
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
|
|
338
|
-
size: 14,
|
|
339
|
-
color: _sharedUi.buoyColors.primary
|
|
340
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
341
|
-
style: styles.lockedText,
|
|
342
|
-
children: [lockedActionCount, " older ", lockedActionCount === 1 ? "action" : "actions", " locked"]
|
|
343
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
344
|
-
style: styles.upgradeBadge,
|
|
345
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
346
|
-
style: styles.upgradeBadgeText,
|
|
347
|
-
children: "UPGRADE"
|
|
348
|
-
})
|
|
349
|
-
})]
|
|
350
|
-
}), visibleActions.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
|
|
351
|
-
ref: flatListRef,
|
|
352
|
-
data: visibleActions,
|
|
353
|
-
renderItem: renderItem,
|
|
354
|
-
keyExtractor: keyExtractor,
|
|
355
|
-
contentContainerStyle: styles.listContent,
|
|
356
|
-
showsVerticalScrollIndicator: true,
|
|
357
|
-
removeClippedSubviews: true,
|
|
358
|
-
initialNumToRender: 12,
|
|
359
|
-
maxToRenderPerBatch: 8,
|
|
360
|
-
windowSize: 5,
|
|
361
|
-
updateCellsBatchingPeriod: 50
|
|
362
|
-
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {
|
|
363
|
-
isEnabled: isEnabled
|
|
364
|
-
})]
|
|
365
|
-
})
|
|
366
|
-
})
|
|
367
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
|
|
368
|
-
visible: showProModal,
|
|
369
|
-
onClose: () => setShowProModal(false),
|
|
370
|
-
featureName: "Full Action History"
|
|
371
|
-
})]
|
|
398
|
+
children: body
|
|
399
|
+
}), upgradeModal]
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The same Redux tool as `ReduxModal`, rendered INLINE for hosts that place
|
|
405
|
+
* tools in a panel rather than a modal. Exists so there is exactly one
|
|
406
|
+
* implementation — the desktop dashboard used to ship its own copy of this
|
|
407
|
+
* component's header, action list and detail router.
|
|
408
|
+
*/
|
|
409
|
+
function ReduxToolView(props = {}) {
|
|
410
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(ReduxModal, {
|
|
411
|
+
...props,
|
|
412
|
+
inline: true,
|
|
413
|
+
visible: true,
|
|
414
|
+
onClose: () => {}
|
|
372
415
|
});
|
|
373
416
|
}
|
|
374
417
|
const styles = _reactNative.StyleSheet.create({
|
|
418
|
+
inlineRoot: {
|
|
419
|
+
flex: 1,
|
|
420
|
+
minHeight: 0,
|
|
421
|
+
backgroundColor: _sharedUi.macOSColors.background.base
|
|
422
|
+
},
|
|
423
|
+
inlineHeader: {
|
|
424
|
+
paddingHorizontal: 12,
|
|
425
|
+
paddingVertical: 8,
|
|
426
|
+
borderBottomWidth: 1,
|
|
427
|
+
borderBottomColor: _sharedUi.macOSColors.border.default,
|
|
428
|
+
backgroundColor: _sharedUi.macOSColors.background.card
|
|
429
|
+
},
|
|
375
430
|
container: {
|
|
376
431
|
flex: 1,
|
|
377
432
|
backgroundColor: _sharedUi.macOSColors.background.base
|
|
@@ -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
|
-
*
|
|
14
|
-
*
|
|
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
|
-
*
|
|
17
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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);
|