@buoy-gg/redux 7.0.17 → 7.0.19
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/lib/commonjs/redux/components/ReduxActionDetailContent.js +4 -3
- package/lib/commonjs/redux/components/ReduxModal.js +18 -2
- package/lib/commonjs/redux/sync/reduxSyncAdapter.js +56 -22
- package/lib/commonjs/redux/utils/reduxActionStore.js +16 -25
- package/lib/commonjs/redux/utils/snapshotProvider.js +102 -4
- package/lib/module/redux/components/ReduxActionDetailContent.js +4 -3
- package/lib/module/redux/components/ReduxModal.js +18 -2
- package/lib/module/redux/sync/reduxSyncAdapter.js +57 -23
- package/lib/module/redux/utils/reduxActionStore.js +15 -25
- package/lib/module/redux/utils/snapshotProvider.js +102 -4
- package/lib/typescript/redux/components/ReduxActionDetailContent.d.ts +9 -1
- package/lib/typescript/redux/components/ReduxActionDetailContent.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxModal.d.ts +13 -1
- package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -1
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts +11 -23
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/redux/utils/reduxActionStore.d.ts +2 -0
- package/lib/typescript/redux/utils/reduxActionStore.d.ts.map +1 -1
- package/package.json +10 -5
|
@@ -82,10 +82,11 @@ function ReduxActionDetailContent({
|
|
|
82
82
|
actions,
|
|
83
83
|
selectedIndex,
|
|
84
84
|
onIndexChange,
|
|
85
|
-
disableInternalFooter = false
|
|
85
|
+
disableInternalFooter = false,
|
|
86
|
+
initialView = "action"
|
|
86
87
|
}) {
|
|
87
|
-
// View state
|
|
88
|
-
const [activeView, setActiveView] = (0, _react.useState)(
|
|
88
|
+
// View state (initialView seeds it once; taps own it afterwards)
|
|
89
|
+
const [activeView, setActiveView] = (0, _react.useState)(initialView);
|
|
89
90
|
const [diffMode, setDiffMode] = (0, _react.useState)("tree");
|
|
90
91
|
const totalActions = actions.length;
|
|
91
92
|
const actionColor = getActionColor(action.type);
|
|
@@ -49,7 +49,9 @@ function ReduxModal({
|
|
|
49
49
|
onMinimize,
|
|
50
50
|
enableSharedModalDimensions = false,
|
|
51
51
|
inline = false,
|
|
52
|
-
onActionSelect
|
|
52
|
+
onActionSelect,
|
|
53
|
+
initialSelectedActionId,
|
|
54
|
+
initialDetailView
|
|
53
55
|
}) {
|
|
54
56
|
const isPro = (0, _license.useIsPro)();
|
|
55
57
|
const [showProModal, setShowProModal] = (0, _react.useState)(false);
|
|
@@ -105,6 +107,19 @@ function ReduxModal({
|
|
|
105
107
|
lastNotifiedSelectionRef.current = id;
|
|
106
108
|
onActionSelect(selectedAction);
|
|
107
109
|
}, [selectedAction, onActionSelect]);
|
|
110
|
+
|
|
111
|
+
// Deep link into an action's detail (see initialSelectedActionId). Applied
|
|
112
|
+
// at most once per id, so the list re-rendering — or the user pressing Back
|
|
113
|
+
// — never re-opens it.
|
|
114
|
+
const appliedInitialSelectionRef = (0, _react.useRef)(null);
|
|
115
|
+
(0, _react.useEffect)(() => {
|
|
116
|
+
if (!initialSelectedActionId) return;
|
|
117
|
+
if (appliedInitialSelectionRef.current === initialSelectedActionId) return;
|
|
118
|
+
const index = visibleActions.findIndex(a => a.id === initialSelectedActionId);
|
|
119
|
+
if (index < 0) return;
|
|
120
|
+
appliedInitialSelectionRef.current = initialSelectedActionId;
|
|
121
|
+
setSelectedActionIndex(index);
|
|
122
|
+
}, [initialSelectedActionId, visibleActions]);
|
|
108
123
|
const [searchText, setSearchText] = (0, _react.useState)("");
|
|
109
124
|
const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
|
|
110
125
|
const searchInputRef = (0, _react.useRef)(null);
|
|
@@ -307,7 +322,8 @@ function ReduxModal({
|
|
|
307
322
|
actions: visibleActions,
|
|
308
323
|
selectedIndex: selectedActionIndex,
|
|
309
324
|
onIndexChange: handleIndexChange,
|
|
310
|
-
disableInternalFooter: true
|
|
325
|
+
disableInternalFooter: true,
|
|
326
|
+
initialView: initialDetailView
|
|
311
327
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
312
328
|
children: [instrumentError && !isInstrumented && !_reduxActionStore.reduxActionStore.isCaptureSuppressed() && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
313
329
|
style: styles.errorBanner,
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.reduxSyncAdapter = void 0;
|
|
7
7
|
var _reduxActionStore = require("../utils/reduxActionStore");
|
|
8
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
8
9
|
var _autoInstrument = require("../utils/autoInstrument");
|
|
9
10
|
/**
|
|
10
11
|
* Upper bound for one on-demand action-detail response. Generous — this is a
|
|
@@ -13,8 +14,49 @@ var _autoInstrument = require("../utils/autoInstrument");
|
|
|
13
14
|
*/
|
|
14
15
|
const MAX_ACTION_DETAIL_BYTES = 8 * 1024 * 1024;
|
|
15
16
|
|
|
16
|
-
/** Per-action budget for `payload`/`action` on the snapshot wire. */
|
|
17
|
+
/** Per-action budget for `payload`/`action`/`meta.arg`/`error` on the snapshot wire. */
|
|
17
18
|
const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
|
|
19
|
+
function toWireValue(value) {
|
|
20
|
+
if (value === undefined) return value;
|
|
21
|
+
return (0, _sharedUi.isOverWireBudget)(value, MAX_WIRE_PAYLOAD_BYTES) ? _reduxActionStore.PAYLOAD_ON_DEVICE : value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Per-action wire cache. `reduxActionStore` never mutates an action in place —
|
|
26
|
+
* it prepends, and `replaceActions` maps to fresh objects — so keying on
|
|
27
|
+
* identity is correct and self-invalidating. Without it, every payload,
|
|
28
|
+
* `action`, `meta.arg` and `error` in the list is re-measured on all ~5
|
|
29
|
+
* snapshots per second for as long as it stays in the list.
|
|
30
|
+
*/
|
|
31
|
+
const wireCache = new WeakMap();
|
|
32
|
+
function toWireAction(a) {
|
|
33
|
+
const cached = wireCache.get(a);
|
|
34
|
+
if (cached) return cached;
|
|
35
|
+
const wire = (0, _reduxActionStore.isEvictedState)(a.prevState) ? {
|
|
36
|
+
...a
|
|
37
|
+
} : {
|
|
38
|
+
...a,
|
|
39
|
+
prevState: _reduxActionStore.STATE_ON_DEVICE,
|
|
40
|
+
nextState: _reduxActionStore.STATE_ON_DEVICE
|
|
41
|
+
};
|
|
42
|
+
// A dispatched payload can itself be huge (rehydration actions carry whole
|
|
43
|
+
// state trees). The list view renders payloadPreview anyway — oversized /
|
|
44
|
+
// unserializable values stay on-device behind markers. `meta.arg` is a
|
|
45
|
+
// leftover: RTK copies the thunk argument there, and capping `action` still
|
|
46
|
+
// left the extracted copy on the wire.
|
|
47
|
+
wire.payload = toWireValue(wire.payload);
|
|
48
|
+
wire.action = toWireValue(wire.action);
|
|
49
|
+
wire.error = toWireValue(wire.error);
|
|
50
|
+
if (wire.meta) {
|
|
51
|
+
const arg = toWireValue(wire.meta.arg);
|
|
52
|
+
if (arg !== wire.meta.arg) wire.meta = {
|
|
53
|
+
...wire.meta,
|
|
54
|
+
arg
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
wireCache.set(a, wire);
|
|
58
|
+
return wire;
|
|
59
|
+
}
|
|
18
60
|
/** Top-level slice names of the state tree, for the compact (no-values) view. */
|
|
19
61
|
function topLevelKeys(state) {
|
|
20
62
|
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
|
|
@@ -62,25 +104,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
62
104
|
* trees on demand via `getActionDetail`; already-evicted entries keep the
|
|
63
105
|
* eviction sentinel so consumers can distinguish "fetchable" from "gone".
|
|
64
106
|
*/
|
|
65
|
-
getSnapshot: () => _reduxActionStore.reduxActionStore.getActions().map(
|
|
66
|
-
const wire = (0, _reduxActionStore.isEvictedState)(a.prevState) ? {
|
|
67
|
-
...a
|
|
68
|
-
} : {
|
|
69
|
-
...a,
|
|
70
|
-
prevState: _reduxActionStore.STATE_ON_DEVICE,
|
|
71
|
-
nextState: _reduxActionStore.STATE_ON_DEVICE
|
|
72
|
-
};
|
|
73
|
-
// A dispatched payload can itself be huge (rehydration actions carry
|
|
74
|
-
// whole state trees). The list view renders payloadPreview anyway —
|
|
75
|
-
// oversized payload/action values stay on-device behind markers.
|
|
76
|
-
if ((0, _reduxActionStore.jsonLongerThan)(wire.payload, MAX_WIRE_PAYLOAD_BYTES)) {
|
|
77
|
-
wire.payload = _reduxActionStore.PAYLOAD_ON_DEVICE;
|
|
78
|
-
}
|
|
79
|
-
if ((0, _reduxActionStore.jsonLongerThan)(wire.action, MAX_WIRE_PAYLOAD_BYTES)) {
|
|
80
|
-
wire.action = _reduxActionStore.PAYLOAD_ON_DEVICE;
|
|
81
|
-
}
|
|
82
|
-
return wire;
|
|
83
|
-
}),
|
|
107
|
+
getSnapshot: () => _reduxActionStore.reduxActionStore.getActions().map(toWireAction),
|
|
84
108
|
subscribe: onChange => _reduxActionStore.reduxActionStore.subscribe(onChange),
|
|
85
109
|
actions: {
|
|
86
110
|
clearEvents: () => {
|
|
@@ -118,14 +142,24 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
118
142
|
__buoyTruncated: true,
|
|
119
143
|
note: `Exceeds the ${MAX_ACTION_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
|
|
120
144
|
};
|
|
121
|
-
const cap = value => (0,
|
|
145
|
+
const cap = value => (0, _sharedUi.isOverWireBudget)(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
|
|
146
|
+
const capMeta = meta => {
|
|
147
|
+
if (!meta) return meta;
|
|
148
|
+
const arg = cap(meta.arg);
|
|
149
|
+
return arg === meta.arg ? meta : {
|
|
150
|
+
...meta,
|
|
151
|
+
arg
|
|
152
|
+
};
|
|
153
|
+
};
|
|
122
154
|
return {
|
|
123
155
|
found: true,
|
|
124
156
|
id,
|
|
125
157
|
prevState: cap(action.prevState),
|
|
126
158
|
nextState: cap(action.nextState),
|
|
127
159
|
payload: cap(action.payload),
|
|
128
|
-
action: cap(action.action)
|
|
160
|
+
action: cap(action.action),
|
|
161
|
+
meta: capMeta(action.meta),
|
|
162
|
+
error: cap(action.error)
|
|
129
163
|
};
|
|
130
164
|
},
|
|
131
165
|
/**
|
|
@@ -5,8 +5,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.STATE_RETENTION_LIMIT = exports.STATE_ON_DEVICE = exports.STATE_EVICTED = exports.PAYLOAD_ON_DEVICE = void 0;
|
|
7
7
|
exports.isEvictedState = isEvictedState;
|
|
8
|
+
exports.isPayloadOnDevice = isPayloadOnDevice;
|
|
8
9
|
exports.jsonLongerThan = jsonLongerThan;
|
|
9
10
|
exports.reduxActionStore = void 0;
|
|
11
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
12
|
/**
|
|
11
13
|
* Redux action store - captures and stores Redux Toolkit actions
|
|
12
14
|
*/
|
|
@@ -52,6 +54,11 @@ const PAYLOAD_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = Object.freeze({
|
|
|
52
54
|
note: "Payload stays on the device — fetched on demand via getActionDetail."
|
|
53
55
|
});
|
|
54
56
|
|
|
57
|
+
/** True when a wire-form value is the payload-on-device sentinel. */
|
|
58
|
+
function isPayloadOnDevice(value) {
|
|
59
|
+
return typeof value === "object" && value !== null && value.__buoyPayloadOnDevice === true;
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
/**
|
|
56
63
|
* True when a value is one of the state sentinels (evicted or on-device) —
|
|
57
64
|
* guards time travel and any consumer that needs a REAL tree.
|
|
@@ -123,28 +130,7 @@ function parseActionType(type) {
|
|
|
123
130
|
* JS thread at enterprise state sizes.
|
|
124
131
|
*/
|
|
125
132
|
function jsonLongerThan(value, limit) {
|
|
126
|
-
|
|
127
|
-
const stack = [value];
|
|
128
|
-
const seen = new Set();
|
|
129
|
-
while (stack.length > 0) {
|
|
130
|
-
if (bytes > limit) return true;
|
|
131
|
-
const v = stack.pop();
|
|
132
|
-
if (v === null || v === undefined) bytes += 4;else if (typeof v === "string") bytes += v.length + 2;else if (typeof v === "number") bytes += 8;else if (typeof v === "boolean") bytes += 5;else if (typeof v === "object") {
|
|
133
|
-
if (seen.has(v)) continue;
|
|
134
|
-
seen.add(v);
|
|
135
|
-
if (Array.isArray(v)) {
|
|
136
|
-
bytes += 2 + v.length;
|
|
137
|
-
for (let i = 0; i < v.length; i++) stack.push(v[i]);
|
|
138
|
-
} else {
|
|
139
|
-
bytes += 2;
|
|
140
|
-
for (const k in v) {
|
|
141
|
-
bytes += k.length + 4;
|
|
142
|
-
stack.push(v[k]);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
} else bytes += 4;
|
|
146
|
-
}
|
|
147
|
-
return bytes > limit;
|
|
133
|
+
return (0, _sharedUi.approxJsonSize)(value, limit).bytes > limit;
|
|
148
134
|
}
|
|
149
135
|
|
|
150
136
|
/**
|
|
@@ -399,11 +385,16 @@ class ReduxActionStore {
|
|
|
399
385
|
}
|
|
400
386
|
this.actions = actions.slice(0, this.maxActions).map(a => {
|
|
401
387
|
const h = hydrated.get(a.id);
|
|
402
|
-
|
|
388
|
+
if (!h || !isEvictedState(a.prevState)) return a;
|
|
389
|
+
return {
|
|
403
390
|
...a,
|
|
404
391
|
prevState: h.prevState,
|
|
405
|
-
nextState: h.nextState
|
|
406
|
-
|
|
392
|
+
nextState: h.nextState,
|
|
393
|
+
payload: isPayloadOnDevice(a.payload) ? h.payload : a.payload,
|
|
394
|
+
action: isPayloadOnDevice(a.action) ? h.action : a.action,
|
|
395
|
+
meta: isPayloadOnDevice(a.meta?.arg) ? h.meta : a.meta,
|
|
396
|
+
error: isPayloadOnDevice(a.error) ? h.error : a.error
|
|
397
|
+
};
|
|
407
398
|
});
|
|
408
399
|
this.notifyListeners();
|
|
409
400
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
4
|
+
var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
|
|
4
5
|
var _activeStoreRegistry = require("./activeStoreRegistry");
|
|
5
6
|
var _buoyReduxMiddleware = require("./buoyReduxMiddleware");
|
|
6
7
|
/**
|
|
@@ -60,7 +61,7 @@ async function capture() {
|
|
|
60
61
|
warnings: []
|
|
61
62
|
};
|
|
62
63
|
}
|
|
63
|
-
async function restore(data) {
|
|
64
|
+
async function restore(data, options) {
|
|
64
65
|
const snapshot = data;
|
|
65
66
|
const store = (0, _activeStoreRegistry.getActiveStore)();
|
|
66
67
|
if (!store || snapshot === undefined || snapshot === null) {
|
|
@@ -88,7 +89,21 @@ async function restore(data) {
|
|
|
88
89
|
warnings: []
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
+
// Untouched slices keep their LIVE values inside the jump payload, so the
|
|
93
|
+
// full-state replace leaves them alone. A slice is untouched when it's
|
|
94
|
+
// explicitly excluded OR outside the includeKeys scope.
|
|
95
|
+
const excludedSet = new Set(options?.excludeKeys ?? []);
|
|
96
|
+
const includedSet = options?.includeKeys ? new Set(options.includeKeys) : null;
|
|
97
|
+
const excluded = Object.keys(snapshot.state ?? {}).filter(k => excludedSet.has(k) || includedSet && !includedSet.has(k));
|
|
98
|
+
let payload = snapshot.state;
|
|
99
|
+
if (excluded.length > 0) {
|
|
100
|
+
const live = store.getState();
|
|
101
|
+
const merged = {
|
|
102
|
+
...snapshot.state
|
|
103
|
+
};
|
|
104
|
+
for (const slice of excluded) merged[slice] = live?.[slice];
|
|
105
|
+
payload = merged;
|
|
106
|
+
}
|
|
92
107
|
store.dispatch({
|
|
93
108
|
type: _buoyReduxMiddleware.BUOY_JUMP_TO_STATE,
|
|
94
109
|
payload
|
|
@@ -97,11 +112,12 @@ async function restore(data) {
|
|
|
97
112
|
// identity proves the jump was actually handled (and not silently ignored
|
|
98
113
|
// by an unwrapped middleware-path reducer).
|
|
99
114
|
if (store.getState() === payload) {
|
|
115
|
+
const warnings = excluded.length > 0 ? [`${excluded.length} slice(s) excluded by you`] : [];
|
|
100
116
|
return {
|
|
101
117
|
ok: true,
|
|
102
118
|
applied: 1,
|
|
103
119
|
skipped: [],
|
|
104
|
-
warnings
|
|
120
|
+
warnings
|
|
105
121
|
};
|
|
106
122
|
}
|
|
107
123
|
return {
|
|
@@ -114,10 +130,92 @@ async function restore(data) {
|
|
|
114
130
|
warnings: []
|
|
115
131
|
};
|
|
116
132
|
}
|
|
133
|
+
const ITEM_RENDER_BYTE_LIMIT = 256 * 1024;
|
|
134
|
+
|
|
135
|
+
/** Per-slice restore preview. `base` omitted = compare against the live store. */
|
|
136
|
+
async function previewItems(target, base) {
|
|
137
|
+
const targetState = target?.state;
|
|
138
|
+
const liveMode = base === undefined;
|
|
139
|
+
const store = (0, _activeStoreRegistry.getActiveStore)();
|
|
140
|
+
const baseState = liveMode ? store?.getState() : base?.state;
|
|
141
|
+
const items = [];
|
|
142
|
+
const notes = [];
|
|
143
|
+
|
|
144
|
+
// Restore-capability gate — a diff restore can't apply must say so, not
|
|
145
|
+
// render as a green change.
|
|
146
|
+
let wontApplyReason;
|
|
147
|
+
if (liveMode) {
|
|
148
|
+
const {
|
|
149
|
+
mechanism,
|
|
150
|
+
hasStore
|
|
151
|
+
} = (0, _activeStoreRegistry.getInstrumentationStatus)();
|
|
152
|
+
if (!hasStore) wontApplyReason = "no Redux store instrumented";else if (mechanism === "patch") {
|
|
153
|
+
wontApplyReason = `auto-instrumented store cannot be restored — ${ENABLE_HINT}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const oldObj = baseState ?? {};
|
|
157
|
+
const newObj = targetState ?? {};
|
|
158
|
+
const keys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
|
|
159
|
+
for (const key of keys) {
|
|
160
|
+
const inOld = Object.prototype.hasOwnProperty.call(oldObj, key);
|
|
161
|
+
const inNew = Object.prototype.hasOwnProperty.call(newObj, key);
|
|
162
|
+
let item;
|
|
163
|
+
if (!inNew) {
|
|
164
|
+
item = {
|
|
165
|
+
key,
|
|
166
|
+
label: key,
|
|
167
|
+
verdict: "removed",
|
|
168
|
+
oldValue: oldObj[key]
|
|
169
|
+
};
|
|
170
|
+
} else if (!inOld) {
|
|
171
|
+
item = {
|
|
172
|
+
key,
|
|
173
|
+
label: key,
|
|
174
|
+
verdict: "added",
|
|
175
|
+
newValue: newObj[key]
|
|
176
|
+
};
|
|
177
|
+
} else if ((0, _dataViewer.boundedDeepEqual)(oldObj[key], newObj[key])) {
|
|
178
|
+
item = {
|
|
179
|
+
key,
|
|
180
|
+
label: key,
|
|
181
|
+
verdict: "unchanged"
|
|
182
|
+
};
|
|
183
|
+
} else if ((0, _dataViewer.jsonLikelyLargerThan)(oldObj[key], ITEM_RENDER_BYTE_LIMIT) || (0, _dataViewer.jsonLikelyLargerThan)(newObj[key], ITEM_RENDER_BYTE_LIMIT)) {
|
|
184
|
+
item = {
|
|
185
|
+
key,
|
|
186
|
+
label: key,
|
|
187
|
+
verdict: "too-large",
|
|
188
|
+
reason: "slice too large to render a diff — restore still applies it"
|
|
189
|
+
};
|
|
190
|
+
} else {
|
|
191
|
+
item = {
|
|
192
|
+
key,
|
|
193
|
+
label: key,
|
|
194
|
+
verdict: "changed",
|
|
195
|
+
oldValue: oldObj[key],
|
|
196
|
+
newValue: newObj[key]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (wontApplyReason && item.verdict !== "unchanged") {
|
|
200
|
+
item = {
|
|
201
|
+
...item,
|
|
202
|
+
verdict: "wont-apply",
|
|
203
|
+
reason: wontApplyReason
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
items.push(item);
|
|
207
|
+
}
|
|
208
|
+
if (wontApplyReason) notes.push(`Redux restore unavailable: ${wontApplyReason}`);
|
|
209
|
+
return {
|
|
210
|
+
items,
|
|
211
|
+
notes
|
|
212
|
+
};
|
|
213
|
+
}
|
|
117
214
|
(0, _sharedUi.registerStateSnapshotProvider)({
|
|
118
215
|
id: "redux",
|
|
119
216
|
label: "Redux",
|
|
120
217
|
status,
|
|
121
218
|
capture,
|
|
122
|
-
restore
|
|
219
|
+
restore,
|
|
220
|
+
previewItems
|
|
123
221
|
});
|
|
@@ -78,10 +78,11 @@ export function ReduxActionDetailContent({
|
|
|
78
78
|
actions,
|
|
79
79
|
selectedIndex,
|
|
80
80
|
onIndexChange,
|
|
81
|
-
disableInternalFooter = false
|
|
81
|
+
disableInternalFooter = false,
|
|
82
|
+
initialView = "action"
|
|
82
83
|
}) {
|
|
83
|
-
// View state
|
|
84
|
-
const [activeView, setActiveView] = useState(
|
|
84
|
+
// View state (initialView seeds it once; taps own it afterwards)
|
|
85
|
+
const [activeView, setActiveView] = useState(initialView);
|
|
85
86
|
const [diffMode, setDiffMode] = useState("tree");
|
|
86
87
|
const totalActions = actions.length;
|
|
87
88
|
const actionColor = getActionColor(action.type);
|
|
@@ -43,7 +43,9 @@ export function ReduxModal({
|
|
|
43
43
|
onMinimize,
|
|
44
44
|
enableSharedModalDimensions = false,
|
|
45
45
|
inline = false,
|
|
46
|
-
onActionSelect
|
|
46
|
+
onActionSelect,
|
|
47
|
+
initialSelectedActionId,
|
|
48
|
+
initialDetailView
|
|
47
49
|
}) {
|
|
48
50
|
const isPro = useIsPro();
|
|
49
51
|
const [showProModal, setShowProModal] = useState(false);
|
|
@@ -99,6 +101,19 @@ export function ReduxModal({
|
|
|
99
101
|
lastNotifiedSelectionRef.current = id;
|
|
100
102
|
onActionSelect(selectedAction);
|
|
101
103
|
}, [selectedAction, onActionSelect]);
|
|
104
|
+
|
|
105
|
+
// Deep link into an action's detail (see initialSelectedActionId). Applied
|
|
106
|
+
// at most once per id, so the list re-rendering — or the user pressing Back
|
|
107
|
+
// — never re-opens it.
|
|
108
|
+
const appliedInitialSelectionRef = useRef(null);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!initialSelectedActionId) return;
|
|
111
|
+
if (appliedInitialSelectionRef.current === initialSelectedActionId) return;
|
|
112
|
+
const index = visibleActions.findIndex(a => a.id === initialSelectedActionId);
|
|
113
|
+
if (index < 0) return;
|
|
114
|
+
appliedInitialSelectionRef.current = initialSelectedActionId;
|
|
115
|
+
setSelectedActionIndex(index);
|
|
116
|
+
}, [initialSelectedActionId, visibleActions]);
|
|
102
117
|
const [searchText, setSearchText] = useState("");
|
|
103
118
|
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
104
119
|
const searchInputRef = useRef(null);
|
|
@@ -301,7 +316,8 @@ export function ReduxModal({
|
|
|
301
316
|
actions: visibleActions,
|
|
302
317
|
selectedIndex: selectedActionIndex,
|
|
303
318
|
onIndexChange: handleIndexChange,
|
|
304
|
-
disableInternalFooter: true
|
|
319
|
+
disableInternalFooter: true,
|
|
320
|
+
initialView: initialDetailView
|
|
305
321
|
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
306
322
|
children: [instrumentError && !isInstrumented && !reduxActionStore.isCaptureSuppressed() && /*#__PURE__*/_jsxs(View, {
|
|
307
323
|
style: styles.errorBanner,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { reduxActionStore, STATE_ON_DEVICE, PAYLOAD_ON_DEVICE, isEvictedState
|
|
3
|
+
import { reduxActionStore, STATE_ON_DEVICE, PAYLOAD_ON_DEVICE, isEvictedState } from "../utils/reduxActionStore";
|
|
4
|
+
import { isOverWireBudget } from "@buoy-gg/shared-ui";
|
|
4
5
|
import { getActiveStore, getInstrumentationStatus } from "../utils/autoInstrument";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -10,8 +11,49 @@ import { getActiveStore, getInstrumentationStatus } from "../utils/autoInstrumen
|
|
|
10
11
|
*/
|
|
11
12
|
const MAX_ACTION_DETAIL_BYTES = 8 * 1024 * 1024;
|
|
12
13
|
|
|
13
|
-
/** Per-action budget for `payload`/`action` on the snapshot wire. */
|
|
14
|
+
/** Per-action budget for `payload`/`action`/`meta.arg`/`error` on the snapshot wire. */
|
|
14
15
|
const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
|
|
16
|
+
function toWireValue(value) {
|
|
17
|
+
if (value === undefined) return value;
|
|
18
|
+
return isOverWireBudget(value, MAX_WIRE_PAYLOAD_BYTES) ? PAYLOAD_ON_DEVICE : value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Per-action wire cache. `reduxActionStore` never mutates an action in place —
|
|
23
|
+
* it prepends, and `replaceActions` maps to fresh objects — so keying on
|
|
24
|
+
* identity is correct and self-invalidating. Without it, every payload,
|
|
25
|
+
* `action`, `meta.arg` and `error` in the list is re-measured on all ~5
|
|
26
|
+
* snapshots per second for as long as it stays in the list.
|
|
27
|
+
*/
|
|
28
|
+
const wireCache = new WeakMap();
|
|
29
|
+
function toWireAction(a) {
|
|
30
|
+
const cached = wireCache.get(a);
|
|
31
|
+
if (cached) return cached;
|
|
32
|
+
const wire = isEvictedState(a.prevState) ? {
|
|
33
|
+
...a
|
|
34
|
+
} : {
|
|
35
|
+
...a,
|
|
36
|
+
prevState: STATE_ON_DEVICE,
|
|
37
|
+
nextState: STATE_ON_DEVICE
|
|
38
|
+
};
|
|
39
|
+
// A dispatched payload can itself be huge (rehydration actions carry whole
|
|
40
|
+
// state trees). The list view renders payloadPreview anyway — oversized /
|
|
41
|
+
// unserializable values stay on-device behind markers. `meta.arg` is a
|
|
42
|
+
// leftover: RTK copies the thunk argument there, and capping `action` still
|
|
43
|
+
// left the extracted copy on the wire.
|
|
44
|
+
wire.payload = toWireValue(wire.payload);
|
|
45
|
+
wire.action = toWireValue(wire.action);
|
|
46
|
+
wire.error = toWireValue(wire.error);
|
|
47
|
+
if (wire.meta) {
|
|
48
|
+
const arg = toWireValue(wire.meta.arg);
|
|
49
|
+
if (arg !== wire.meta.arg) wire.meta = {
|
|
50
|
+
...wire.meta,
|
|
51
|
+
arg
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
wireCache.set(a, wire);
|
|
55
|
+
return wire;
|
|
56
|
+
}
|
|
15
57
|
/** Top-level slice names of the state tree, for the compact (no-values) view. */
|
|
16
58
|
function topLevelKeys(state) {
|
|
17
59
|
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
|
|
@@ -59,25 +101,7 @@ export const reduxSyncAdapter = {
|
|
|
59
101
|
* trees on demand via `getActionDetail`; already-evicted entries keep the
|
|
60
102
|
* eviction sentinel so consumers can distinguish "fetchable" from "gone".
|
|
61
103
|
*/
|
|
62
|
-
getSnapshot: () => reduxActionStore.getActions().map(
|
|
63
|
-
const wire = isEvictedState(a.prevState) ? {
|
|
64
|
-
...a
|
|
65
|
-
} : {
|
|
66
|
-
...a,
|
|
67
|
-
prevState: STATE_ON_DEVICE,
|
|
68
|
-
nextState: STATE_ON_DEVICE
|
|
69
|
-
};
|
|
70
|
-
// A dispatched payload can itself be huge (rehydration actions carry
|
|
71
|
-
// whole state trees). The list view renders payloadPreview anyway —
|
|
72
|
-
// oversized payload/action values stay on-device behind markers.
|
|
73
|
-
if (jsonLongerThan(wire.payload, MAX_WIRE_PAYLOAD_BYTES)) {
|
|
74
|
-
wire.payload = PAYLOAD_ON_DEVICE;
|
|
75
|
-
}
|
|
76
|
-
if (jsonLongerThan(wire.action, MAX_WIRE_PAYLOAD_BYTES)) {
|
|
77
|
-
wire.action = PAYLOAD_ON_DEVICE;
|
|
78
|
-
}
|
|
79
|
-
return wire;
|
|
80
|
-
}),
|
|
104
|
+
getSnapshot: () => reduxActionStore.getActions().map(toWireAction),
|
|
81
105
|
subscribe: onChange => reduxActionStore.subscribe(onChange),
|
|
82
106
|
actions: {
|
|
83
107
|
clearEvents: () => {
|
|
@@ -115,14 +139,24 @@ export const reduxSyncAdapter = {
|
|
|
115
139
|
__buoyTruncated: true,
|
|
116
140
|
note: `Exceeds the ${MAX_ACTION_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
|
|
117
141
|
};
|
|
118
|
-
const cap = value =>
|
|
142
|
+
const cap = value => isOverWireBudget(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
|
|
143
|
+
const capMeta = meta => {
|
|
144
|
+
if (!meta) return meta;
|
|
145
|
+
const arg = cap(meta.arg);
|
|
146
|
+
return arg === meta.arg ? meta : {
|
|
147
|
+
...meta,
|
|
148
|
+
arg
|
|
149
|
+
};
|
|
150
|
+
};
|
|
119
151
|
return {
|
|
120
152
|
found: true,
|
|
121
153
|
id,
|
|
122
154
|
prevState: cap(action.prevState),
|
|
123
155
|
nextState: cap(action.nextState),
|
|
124
156
|
payload: cap(action.payload),
|
|
125
|
-
action: cap(action.action)
|
|
157
|
+
action: cap(action.action),
|
|
158
|
+
meta: capMeta(action.meta),
|
|
159
|
+
error: cap(action.error)
|
|
126
160
|
};
|
|
127
161
|
},
|
|
128
162
|
/**
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Redux action store - captures and stores Redux Toolkit actions
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { approxJsonSize } from "@buoy-gg/shared-ui";
|
|
7
8
|
/**
|
|
8
9
|
* How many of the most-recent actions keep their raw prevState/nextState
|
|
9
10
|
* trees. Older actions keep all diff metadata but their trees are replaced
|
|
@@ -45,6 +46,11 @@ export const PAYLOAD_ON_DEVICE = Object.freeze({
|
|
|
45
46
|
note: "Payload stays on the device — fetched on demand via getActionDetail."
|
|
46
47
|
});
|
|
47
48
|
|
|
49
|
+
/** True when a wire-form value is the payload-on-device sentinel. */
|
|
50
|
+
export function isPayloadOnDevice(value) {
|
|
51
|
+
return typeof value === "object" && value !== null && value.__buoyPayloadOnDevice === true;
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
/**
|
|
49
55
|
* True when a value is one of the state sentinels (evicted or on-device) —
|
|
50
56
|
* guards time travel and any consumer that needs a REAL tree.
|
|
@@ -116,28 +122,7 @@ function parseActionType(type) {
|
|
|
116
122
|
* JS thread at enterprise state sizes.
|
|
117
123
|
*/
|
|
118
124
|
export function jsonLongerThan(value, limit) {
|
|
119
|
-
|
|
120
|
-
const stack = [value];
|
|
121
|
-
const seen = new Set();
|
|
122
|
-
while (stack.length > 0) {
|
|
123
|
-
if (bytes > limit) return true;
|
|
124
|
-
const v = stack.pop();
|
|
125
|
-
if (v === null || v === undefined) bytes += 4;else if (typeof v === "string") bytes += v.length + 2;else if (typeof v === "number") bytes += 8;else if (typeof v === "boolean") bytes += 5;else if (typeof v === "object") {
|
|
126
|
-
if (seen.has(v)) continue;
|
|
127
|
-
seen.add(v);
|
|
128
|
-
if (Array.isArray(v)) {
|
|
129
|
-
bytes += 2 + v.length;
|
|
130
|
-
for (let i = 0; i < v.length; i++) stack.push(v[i]);
|
|
131
|
-
} else {
|
|
132
|
-
bytes += 2;
|
|
133
|
-
for (const k in v) {
|
|
134
|
-
bytes += k.length + 4;
|
|
135
|
-
stack.push(v[k]);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} else bytes += 4;
|
|
139
|
-
}
|
|
140
|
-
return bytes > limit;
|
|
125
|
+
return approxJsonSize(value, limit).bytes > limit;
|
|
141
126
|
}
|
|
142
127
|
|
|
143
128
|
/**
|
|
@@ -392,11 +377,16 @@ class ReduxActionStore {
|
|
|
392
377
|
}
|
|
393
378
|
this.actions = actions.slice(0, this.maxActions).map(a => {
|
|
394
379
|
const h = hydrated.get(a.id);
|
|
395
|
-
|
|
380
|
+
if (!h || !isEvictedState(a.prevState)) return a;
|
|
381
|
+
return {
|
|
396
382
|
...a,
|
|
397
383
|
prevState: h.prevState,
|
|
398
|
-
nextState: h.nextState
|
|
399
|
-
|
|
384
|
+
nextState: h.nextState,
|
|
385
|
+
payload: isPayloadOnDevice(a.payload) ? h.payload : a.payload,
|
|
386
|
+
action: isPayloadOnDevice(a.action) ? h.action : a.action,
|
|
387
|
+
meta: isPayloadOnDevice(a.meta?.arg) ? h.meta : a.meta,
|
|
388
|
+
error: isPayloadOnDevice(a.error) ? h.error : a.error
|
|
389
|
+
};
|
|
400
390
|
});
|
|
401
391
|
this.notifyListeners();
|
|
402
392
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* - patch path: never handled; restore is reported as unavailable up front.
|
|
16
16
|
*/
|
|
17
17
|
import { registerStateSnapshotProvider } from "@buoy-gg/shared-ui";
|
|
18
|
+
import { boundedDeepEqual, jsonLikelyLargerThan } from "@buoy-gg/shared-ui/dataViewer";
|
|
18
19
|
import { getActiveStore, getInstrumentationStatus } from "./activeStoreRegistry";
|
|
19
20
|
import { BUOY_JUMP_TO_STATE } from "./buoyReduxMiddleware";
|
|
20
21
|
const ENABLE_HINT = "enable restore by using the Buoy enhancer or wrapping your root reducer in withBuoyDevTools()";
|
|
@@ -59,7 +60,7 @@ async function capture() {
|
|
|
59
60
|
warnings: []
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
|
-
async function restore(data) {
|
|
63
|
+
async function restore(data, options) {
|
|
63
64
|
const snapshot = data;
|
|
64
65
|
const store = getActiveStore();
|
|
65
66
|
if (!store || snapshot === undefined || snapshot === null) {
|
|
@@ -87,7 +88,21 @@ async function restore(data) {
|
|
|
87
88
|
warnings: []
|
|
88
89
|
};
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
// Untouched slices keep their LIVE values inside the jump payload, so the
|
|
92
|
+
// full-state replace leaves them alone. A slice is untouched when it's
|
|
93
|
+
// explicitly excluded OR outside the includeKeys scope.
|
|
94
|
+
const excludedSet = new Set(options?.excludeKeys ?? []);
|
|
95
|
+
const includedSet = options?.includeKeys ? new Set(options.includeKeys) : null;
|
|
96
|
+
const excluded = Object.keys(snapshot.state ?? {}).filter(k => excludedSet.has(k) || includedSet && !includedSet.has(k));
|
|
97
|
+
let payload = snapshot.state;
|
|
98
|
+
if (excluded.length > 0) {
|
|
99
|
+
const live = store.getState();
|
|
100
|
+
const merged = {
|
|
101
|
+
...snapshot.state
|
|
102
|
+
};
|
|
103
|
+
for (const slice of excluded) merged[slice] = live?.[slice];
|
|
104
|
+
payload = merged;
|
|
105
|
+
}
|
|
91
106
|
store.dispatch({
|
|
92
107
|
type: BUOY_JUMP_TO_STATE,
|
|
93
108
|
payload
|
|
@@ -96,11 +111,12 @@ async function restore(data) {
|
|
|
96
111
|
// identity proves the jump was actually handled (and not silently ignored
|
|
97
112
|
// by an unwrapped middleware-path reducer).
|
|
98
113
|
if (store.getState() === payload) {
|
|
114
|
+
const warnings = excluded.length > 0 ? [`${excluded.length} slice(s) excluded by you`] : [];
|
|
99
115
|
return {
|
|
100
116
|
ok: true,
|
|
101
117
|
applied: 1,
|
|
102
118
|
skipped: [],
|
|
103
|
-
warnings
|
|
119
|
+
warnings
|
|
104
120
|
};
|
|
105
121
|
}
|
|
106
122
|
return {
|
|
@@ -113,10 +129,92 @@ async function restore(data) {
|
|
|
113
129
|
warnings: []
|
|
114
130
|
};
|
|
115
131
|
}
|
|
132
|
+
const ITEM_RENDER_BYTE_LIMIT = 256 * 1024;
|
|
133
|
+
|
|
134
|
+
/** Per-slice restore preview. `base` omitted = compare against the live store. */
|
|
135
|
+
async function previewItems(target, base) {
|
|
136
|
+
const targetState = target?.state;
|
|
137
|
+
const liveMode = base === undefined;
|
|
138
|
+
const store = getActiveStore();
|
|
139
|
+
const baseState = liveMode ? store?.getState() : base?.state;
|
|
140
|
+
const items = [];
|
|
141
|
+
const notes = [];
|
|
142
|
+
|
|
143
|
+
// Restore-capability gate — a diff restore can't apply must say so, not
|
|
144
|
+
// render as a green change.
|
|
145
|
+
let wontApplyReason;
|
|
146
|
+
if (liveMode) {
|
|
147
|
+
const {
|
|
148
|
+
mechanism,
|
|
149
|
+
hasStore
|
|
150
|
+
} = getInstrumentationStatus();
|
|
151
|
+
if (!hasStore) wontApplyReason = "no Redux store instrumented";else if (mechanism === "patch") {
|
|
152
|
+
wontApplyReason = `auto-instrumented store cannot be restored — ${ENABLE_HINT}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const oldObj = baseState ?? {};
|
|
156
|
+
const newObj = targetState ?? {};
|
|
157
|
+
const keys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
|
|
158
|
+
for (const key of keys) {
|
|
159
|
+
const inOld = Object.prototype.hasOwnProperty.call(oldObj, key);
|
|
160
|
+
const inNew = Object.prototype.hasOwnProperty.call(newObj, key);
|
|
161
|
+
let item;
|
|
162
|
+
if (!inNew) {
|
|
163
|
+
item = {
|
|
164
|
+
key,
|
|
165
|
+
label: key,
|
|
166
|
+
verdict: "removed",
|
|
167
|
+
oldValue: oldObj[key]
|
|
168
|
+
};
|
|
169
|
+
} else if (!inOld) {
|
|
170
|
+
item = {
|
|
171
|
+
key,
|
|
172
|
+
label: key,
|
|
173
|
+
verdict: "added",
|
|
174
|
+
newValue: newObj[key]
|
|
175
|
+
};
|
|
176
|
+
} else if (boundedDeepEqual(oldObj[key], newObj[key])) {
|
|
177
|
+
item = {
|
|
178
|
+
key,
|
|
179
|
+
label: key,
|
|
180
|
+
verdict: "unchanged"
|
|
181
|
+
};
|
|
182
|
+
} else if (jsonLikelyLargerThan(oldObj[key], ITEM_RENDER_BYTE_LIMIT) || jsonLikelyLargerThan(newObj[key], ITEM_RENDER_BYTE_LIMIT)) {
|
|
183
|
+
item = {
|
|
184
|
+
key,
|
|
185
|
+
label: key,
|
|
186
|
+
verdict: "too-large",
|
|
187
|
+
reason: "slice too large to render a diff — restore still applies it"
|
|
188
|
+
};
|
|
189
|
+
} else {
|
|
190
|
+
item = {
|
|
191
|
+
key,
|
|
192
|
+
label: key,
|
|
193
|
+
verdict: "changed",
|
|
194
|
+
oldValue: oldObj[key],
|
|
195
|
+
newValue: newObj[key]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (wontApplyReason && item.verdict !== "unchanged") {
|
|
199
|
+
item = {
|
|
200
|
+
...item,
|
|
201
|
+
verdict: "wont-apply",
|
|
202
|
+
reason: wontApplyReason
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
items.push(item);
|
|
206
|
+
}
|
|
207
|
+
if (wontApplyReason) notes.push(`Redux restore unavailable: ${wontApplyReason}`);
|
|
208
|
+
return {
|
|
209
|
+
items,
|
|
210
|
+
notes
|
|
211
|
+
};
|
|
212
|
+
}
|
|
116
213
|
registerStateSnapshotProvider({
|
|
117
214
|
id: "redux",
|
|
118
215
|
label: "Redux",
|
|
119
216
|
status,
|
|
120
217
|
capture,
|
|
121
|
-
restore
|
|
218
|
+
restore,
|
|
219
|
+
previewItems
|
|
122
220
|
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - DIFF: Compare prev vs next state
|
|
8
8
|
*/
|
|
9
9
|
import type { ReduxAction } from "../types";
|
|
10
|
+
import { type ReduxDetailViewMode } from "./ReduxDetailViewToggle";
|
|
10
11
|
interface ReduxActionDetailContentProps {
|
|
11
12
|
/** The selected action to display */
|
|
12
13
|
action: ReduxAction;
|
|
@@ -18,8 +19,15 @@ interface ReduxActionDetailContentProps {
|
|
|
18
19
|
onIndexChange: (index: number) => void;
|
|
19
20
|
/** If true, do not render the internal footer */
|
|
20
21
|
disableInternalFooter?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Which of the three views the detail opens on (default "action"). Hosts
|
|
24
|
+
* that deep-link into a specific view — the docs live tour, and any future
|
|
25
|
+
* "show me the diff for this action" entry point — set it at mount; the
|
|
26
|
+
* user's own toggle taps take over from there.
|
|
27
|
+
*/
|
|
28
|
+
initialView?: ReduxDetailViewMode;
|
|
21
29
|
}
|
|
22
|
-
export declare function ReduxActionDetailContent({ action, actions, selectedIndex, onIndexChange, disableInternalFooter, }: ReduxActionDetailContentProps): import("react").JSX.Element;
|
|
30
|
+
export declare function ReduxActionDetailContent({ action, actions, selectedIndex, onIndexChange, disableInternalFooter, initialView, }: ReduxActionDetailContentProps): import("react").JSX.Element;
|
|
23
31
|
/**
|
|
24
32
|
* External footer component for modal use
|
|
25
33
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReduxActionDetailContent.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/ReduxActionDetailContent.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiBH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"ReduxActionDetailContent.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/ReduxActionDetailContent.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiBH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,yBAAyB,CAAC;AAIjC,UAAU,6BAA6B;IACrC,qCAAqC;IACrC,MAAM,EAAE,WAAW,CAAC;IACpB,iCAAiC;IACjC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,qCAAqC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iDAAiD;IACjD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC;AAwDD,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,qBAA6B,EAC7B,WAAsB,GACvB,EAAE,6BAA6B,+BA0M/B;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,GACd,EAAE;IACD,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,sCAiBA"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This component auto-instruments the Redux store when it mounts,
|
|
5
5
|
* providing zero-config action capture for the DevTools.
|
|
6
6
|
*/
|
|
7
|
+
import type { ReduxDetailViewMode } from "./ReduxDetailViewToggle";
|
|
7
8
|
import type { ReduxAction } from "../types";
|
|
8
9
|
/** Free tier limit for action history */
|
|
9
10
|
export declare const FREE_TIER_ACTION_LIMIT = 25;
|
|
@@ -26,8 +27,19 @@ interface ReduxModalProps {
|
|
|
26
27
|
* on demand — wire-form snapshots carry markers instead of trees.
|
|
27
28
|
*/
|
|
28
29
|
onActionSelect?: (action: ReduxAction) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Open straight into this action's detail view, by action id. Applied once
|
|
32
|
+
* the id appears in the visible list (the list can arrive after mount when
|
|
33
|
+
* actions are mirrored in), then left alone — Back closes the detail and it
|
|
34
|
+
* stays closed. The counterpart to `onActionSelect`, for hosts that deep-link
|
|
35
|
+
* INTO an action (the docs live tour; a future MCP/desktop "show me this
|
|
36
|
+
* action" jump). Null/undefined means "don't touch the selection".
|
|
37
|
+
*/
|
|
38
|
+
initialSelectedActionId?: string | null;
|
|
39
|
+
/** Which detail view that deep-link opens on. See ReduxActionDetailContent. */
|
|
40
|
+
initialDetailView?: ReduxDetailViewMode;
|
|
29
41
|
}
|
|
30
|
-
export declare function ReduxModal({ visible, onClose, onBack, onMinimize, enableSharedModalDimensions, inline, onActionSelect, }: ReduxModalProps): import("react").JSX.Element | null;
|
|
42
|
+
export declare function ReduxModal({ visible, onClose, onBack, onMinimize, enableSharedModalDimensions, inline, onActionSelect, initialSelectedActionId, initialDetailView, }: ReduxModalProps): import("react").JSX.Element | null;
|
|
31
43
|
/**
|
|
32
44
|
* The same Redux tool as `ReduxModal`, rendered INLINE for hosts that place
|
|
33
45
|
* tools in a panel rather than a modal. Exists so there is exactly one
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReduxModal.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/ReduxModal.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyCH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,yCAAyC;AACzC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,UAAU,eAAe;IACvB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ReduxModal.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/ReduxModal.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyCH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,yCAAyC;AACzC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,UAAU,eAAe;IACvB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IAC/C;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;CACzC;AAgBD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,MAAc,EACd,cAAc,EACd,uBAAuB,EACvB,iBAAiB,GAClB,EAAE,eAAe,sCAmcjB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,GAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAM,+BAGpE"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ActionMeta, ReduxAction } from "../types";
|
|
1
2
|
/**
|
|
2
3
|
* Sync adapter for the redux tool, consumed by @buoy-gg/external-sync's
|
|
3
4
|
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
@@ -25,26 +26,7 @@ export declare const reduxSyncAdapter: {
|
|
|
25
26
|
* trees on demand via `getActionDetail`; already-evicted entries keep the
|
|
26
27
|
* eviction sentinel so consumers can distinguish "fetchable" from "gone".
|
|
27
28
|
*/
|
|
28
|
-
getSnapshot: () =>
|
|
29
|
-
id: string;
|
|
30
|
-
type: string;
|
|
31
|
-
payload?: unknown;
|
|
32
|
-
action: Record<string, unknown>;
|
|
33
|
-
timestamp: number;
|
|
34
|
-
prevState: unknown;
|
|
35
|
-
nextState: unknown;
|
|
36
|
-
duration?: number;
|
|
37
|
-
hasStateChange: boolean;
|
|
38
|
-
category: import("../types").ActionCategory;
|
|
39
|
-
sliceName: string | null;
|
|
40
|
-
actionName: string;
|
|
41
|
-
meta?: import("../types").ActionMeta;
|
|
42
|
-
error?: unknown;
|
|
43
|
-
payloadPreview: string;
|
|
44
|
-
diffSummary: string;
|
|
45
|
-
changedKeysCount: number;
|
|
46
|
-
isSlowAction: boolean;
|
|
47
|
-
}[];
|
|
29
|
+
getSnapshot: () => ReduxAction[];
|
|
48
30
|
subscribe: (onChange: () => void) => () => void;
|
|
49
31
|
actions: {
|
|
50
32
|
clearEvents: () => void;
|
|
@@ -53,7 +35,7 @@ export declare const reduxSyncAdapter: {
|
|
|
53
35
|
* The per-snapshot stream stays tree-free; this is the explicit,
|
|
54
36
|
* size-guarded channel for the detail pane.
|
|
55
37
|
*/
|
|
56
|
-
getActionDetail: (params
|
|
38
|
+
getActionDetail: (params?: unknown) => {
|
|
57
39
|
found: false;
|
|
58
40
|
reason: string;
|
|
59
41
|
id?: undefined;
|
|
@@ -62,6 +44,8 @@ export declare const reduxSyncAdapter: {
|
|
|
62
44
|
nextState?: undefined;
|
|
63
45
|
payload?: undefined;
|
|
64
46
|
action?: undefined;
|
|
47
|
+
meta?: undefined;
|
|
48
|
+
error?: undefined;
|
|
65
49
|
} | {
|
|
66
50
|
found: true;
|
|
67
51
|
id: string;
|
|
@@ -71,6 +55,8 @@ export declare const reduxSyncAdapter: {
|
|
|
71
55
|
nextState?: undefined;
|
|
72
56
|
payload?: undefined;
|
|
73
57
|
action?: undefined;
|
|
58
|
+
meta?: undefined;
|
|
59
|
+
error?: undefined;
|
|
74
60
|
} | {
|
|
75
61
|
found: true;
|
|
76
62
|
id: string;
|
|
@@ -78,6 +64,8 @@ export declare const reduxSyncAdapter: {
|
|
|
78
64
|
nextState: unknown;
|
|
79
65
|
payload: unknown;
|
|
80
66
|
action: unknown;
|
|
67
|
+
meta: ActionMeta | undefined;
|
|
68
|
+
error: unknown;
|
|
81
69
|
reason?: undefined;
|
|
82
70
|
evicted?: undefined;
|
|
83
71
|
};
|
|
@@ -85,7 +73,7 @@ export declare const reduxSyncAdapter: {
|
|
|
85
73
|
* Compact current-state reader: the instrumented store's slice names, and
|
|
86
74
|
* the full state tree only when `includeValues` is set (heavy).
|
|
87
75
|
*/
|
|
88
|
-
getState: (params
|
|
76
|
+
getState: (params?: unknown) => {
|
|
89
77
|
available: false;
|
|
90
78
|
slices: string[];
|
|
91
79
|
/** 'no-react-redux' | 'no-provider' | 'not-instrumented' */
|
|
@@ -99,7 +87,7 @@ export declare const reduxSyncAdapter: {
|
|
|
99
87
|
mechanism: import("../..").CaptureMechanism | null;
|
|
100
88
|
};
|
|
101
89
|
/** Dispatch a plain action object to the instrumented store. */
|
|
102
|
-
dispatch: (params
|
|
90
|
+
dispatch: (params?: unknown) => {
|
|
103
91
|
available: false;
|
|
104
92
|
slices: string[];
|
|
105
93
|
/** 'no-react-redux' | 'no-provider' | 'not-instrumented' */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reduxSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/redux/sync/reduxSyncAdapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reduxSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/redux/sync/reduxSyncAdapter.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAwFxD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB;;IAE3B;;;;;;;;OAQG;;0BAEmB,MAAM,IAAI;;;QAK9B;;;;WAIG;mCACwB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAqClC;;;WAGG;4BACiB,OAAO;;oBArFb,MAAM,EAAE;YACtB,4DAA4D;;;;;;YAgGxD,wEAAwE;;;;QAM5E,gEAAgE;4BAC5C,OAAO;;oBAxGb,MAAM,EAAE;YACtB,4DAA4D;;;;;;;;;CAiH/D,CAAC"}
|
|
@@ -39,6 +39,8 @@ export declare const PAYLOAD_ON_DEVICE: Readonly<{
|
|
|
39
39
|
__buoyPayloadOnDevice: true;
|
|
40
40
|
note: "Payload stays on the device — fetched on demand via getActionDetail.";
|
|
41
41
|
}>;
|
|
42
|
+
/** True when a wire-form value is the payload-on-device sentinel. */
|
|
43
|
+
export declare function isPayloadOnDevice(value: unknown): boolean;
|
|
42
44
|
/**
|
|
43
45
|
* True when a value is one of the state sentinels (evicted or on-device) —
|
|
44
46
|
* guards time travel and any consumer that needs a REAL tree.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reduxActionStore.d.ts","sourceRoot":"","sources":["../../../../src/redux/utils/reduxActionStore.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"reduxActionStore.d.ts","sourceRoot":"","sources":["../../../../src/redux/utils/reduxActionStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAA8B,MAAM,UAAU,CAAC;AAErF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,aAAa;;;EAGxB,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;EAG1B,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB;;;EAG5B,CAAC;AAEH,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMzD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAItD;AAwDD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAErE;AA8HD,cAAM,gBAAgB;IACpB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAoD;IACrE,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,iBAAiB,CAAS;IAElC,SAAS,CACP,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,OAAO,EAClB,SAAS,EAAE,OAAO,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI;IAyEP,UAAU,IAAI,WAAW,EAAE;IAI3B,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIlD,YAAY,IAAI,IAAI;IAYpB;;;OAGG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAWzC;;;;OAIG;IACH,cAAc,IAAI,IAAI;IAItB;;;;OAIG;IACH,mBAAmB,IAAI,OAAO;IAI9B;;;;OAIG;IACH,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI;IA8B5C;;;;OAIG;IACH,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI;IAW5D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIlC,UAAU,IAAI,OAAO;IAIrB,SAAS,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI;IAOjE,OAAO,CAAC,eAAe;IAKvB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQhC,QAAQ;;;;;;;IAuBR,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,EAAE;IAmBjD,oBAAoB,IAAI,MAAM,EAAE;IAIhC;;OAEG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE;IAMnD;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,EAAE;IAKpD;;OAEG;IACH,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM;IAOlD;;OAEG;IACH,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAe3D;;OAEG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAWvC;;;OAGG;IACH,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAkBlD;AAED,eAAO,MAAM,gBAAgB,kBAAyB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/redux",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.19",
|
|
4
4
|
"description": "Redux DevTools inside your React Native app — action log, state diffs & time travel with zero store changes. Part of Buoy devtools.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"**/snapshotProvider.ts"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@buoy-gg/shared-ui": "^7.0.
|
|
35
|
-
"@buoy-gg/license": "^7.0.
|
|
36
|
-
"@buoy-gg/floating-tools-core": "^7.0.
|
|
34
|
+
"@buoy-gg/shared-ui": "^7.0.19",
|
|
35
|
+
"@buoy-gg/license": "^7.0.19",
|
|
36
|
+
"@buoy-gg/floating-tools-core": "^7.0.19"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@reduxjs/toolkit": ">=1.9.0",
|
|
@@ -64,7 +64,12 @@
|
|
|
64
64
|
"sourceMaps": false
|
|
65
65
|
}
|
|
66
66
|
],
|
|
67
|
-
|
|
67
|
+
[
|
|
68
|
+
"typescript",
|
|
69
|
+
{
|
|
70
|
+
"project": "tsconfig.build.json"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
68
73
|
]
|
|
69
74
|
},
|
|
70
75
|
"repository": {
|