@buoy-gg/redux 7.0.33 → 7.0.34
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/preset.js +14 -22
- package/lib/commonjs/redux/components/ReduxActionInfoView.js +8 -60
- package/lib/commonjs/redux/components/ReduxActionItem.js +64 -118
- package/lib/commonjs/redux/components/ReduxDetailViewToggle.js +15 -109
- package/lib/commonjs/redux/components/ReduxModal.js +24 -69
- package/lib/commonjs/redux/hooks/useReduxActions.js +16 -33
- package/lib/commonjs/redux/sync/reduxSyncAdapter.js +11 -29
- package/lib/commonjs/redux/utils/reduxActionStore.js +24 -94
- package/lib/commonjs/redux/utils/snapshotProvider.js +17 -80
- package/lib/module/preset.js +13 -20
- package/lib/module/redux/components/ReduxActionInfoView.js +1 -53
- package/lib/module/redux/components/ReduxActionItem.js +65 -119
- package/lib/module/redux/components/ReduxDetailViewToggle.js +17 -111
- package/lib/module/redux/components/ReduxModal.js +26 -71
- package/lib/module/redux/hooks/useReduxActions.js +17 -34
- package/lib/module/redux/sync/reduxSyncAdapter.js +12 -30
- package/lib/module/redux/utils/reduxActionStore.js +25 -95
- package/lib/module/redux/utils/snapshotProvider.js +18 -81
- package/lib/typescript/preset.d.ts +3 -9
- package/lib/typescript/preset.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxActionInfoView.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxActionItem.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxDetailViewToggle.d.ts +2 -1
- package/lib/typescript/redux/components/ReduxDetailViewToggle.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -1
- package/lib/typescript/redux/hooks/useReduxActions.d.ts +3 -0
- package/lib/typescript/redux/hooks/useReduxActions.d.ts.map +1 -1
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts +68 -82
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/redux/utils/reduxActionStore.d.ts +2 -29
- package/lib/typescript/redux/utils/reduxActionStore.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -5,41 +5,31 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.useReduxActions = useReduxActions;
|
|
7
7
|
var _react = require("react");
|
|
8
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
8
9
|
var _reduxActionStore = require("../utils/reduxActionStore");
|
|
9
10
|
/**
|
|
10
11
|
* Hook for consuming Redux actions from the store
|
|
12
|
+
*
|
|
13
|
+
* The subscribe / filter / pause / clear plumbing is shared `useChangeLog`;
|
|
14
|
+
* this file owns the redux-specific stats, action types and selection.
|
|
11
15
|
*/
|
|
12
16
|
|
|
17
|
+
const filterActions = (_actions, filter) => _reduxActionStore.reduxActionStore.filterActions(filter);
|
|
13
18
|
function useReduxActions() {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
const log = (0, _sharedUi.useChangeLog)(_reduxActionStore.reduxActionStore, {
|
|
20
|
+
filterItems: filterActions
|
|
21
|
+
});
|
|
17
22
|
const [selectedAction, setSelectedAction] = (0, _react.useState)(null);
|
|
18
23
|
|
|
19
|
-
// Subscribe to store changes
|
|
20
|
-
(0, _react.useEffect)(() => {
|
|
21
|
-
const unsubscribe = _reduxActionStore.reduxActionStore.subscribe(newActions => {
|
|
22
|
-
setActions(newActions);
|
|
23
|
-
});
|
|
24
|
-
return () => {
|
|
25
|
-
unsubscribe();
|
|
26
|
-
};
|
|
27
|
-
}, []);
|
|
28
|
-
|
|
29
|
-
// Filter actions based on current filter
|
|
30
|
-
const filteredActions = (0, _react.useMemo)(() => {
|
|
31
|
-
return _reduxActionStore.reduxActionStore.filterActions(filter);
|
|
32
|
-
}, [actions, filter]);
|
|
33
|
-
|
|
34
24
|
// Get stats
|
|
35
25
|
const stats = (0, _react.useMemo)(() => {
|
|
36
26
|
return _reduxActionStore.reduxActionStore.getStats();
|
|
37
|
-
}, [
|
|
27
|
+
}, [log.items]);
|
|
38
28
|
|
|
39
29
|
// Get unique action types
|
|
40
30
|
const actionTypes = (0, _react.useMemo)(() => {
|
|
41
31
|
return _reduxActionStore.reduxActionStore.getUniqueActionTypes();
|
|
42
|
-
}, [
|
|
32
|
+
}, [log.items]);
|
|
43
33
|
|
|
44
34
|
// Clear actions
|
|
45
35
|
const clearActions = (0, _react.useCallback)(() => {
|
|
@@ -47,26 +37,19 @@ function useReduxActions() {
|
|
|
47
37
|
setSelectedAction(null);
|
|
48
38
|
}, []);
|
|
49
39
|
|
|
50
|
-
// Toggle capture
|
|
51
|
-
const toggleCapture = (0, _react.useCallback)(() => {
|
|
52
|
-
const newEnabled = !isEnabled;
|
|
53
|
-
_reduxActionStore.reduxActionStore.setEnabled(newEnabled);
|
|
54
|
-
setIsEnabled(newEnabled);
|
|
55
|
-
}, [isEnabled]);
|
|
56
|
-
|
|
57
40
|
// Get action by ID
|
|
58
41
|
const getActionById = (0, _react.useCallback)(id => {
|
|
59
42
|
return _reduxActionStore.reduxActionStore.getActionById(id);
|
|
60
43
|
}, []);
|
|
61
44
|
return {
|
|
62
|
-
actions,
|
|
63
|
-
filteredActions,
|
|
64
|
-
filter,
|
|
65
|
-
setFilter,
|
|
45
|
+
actions: log.items,
|
|
46
|
+
filteredActions: log.filteredItems,
|
|
47
|
+
filter: log.filter,
|
|
48
|
+
setFilter: log.setFilter,
|
|
66
49
|
stats,
|
|
67
50
|
clearActions,
|
|
68
|
-
isEnabled,
|
|
69
|
-
toggleCapture,
|
|
51
|
+
isEnabled: log.isEnabled,
|
|
52
|
+
toggleCapture: log.toggleCapture,
|
|
70
53
|
actionTypes,
|
|
71
54
|
getActionById,
|
|
72
55
|
selectedAction,
|
|
@@ -7,19 +7,8 @@ exports.reduxSyncAdapter = void 0;
|
|
|
7
7
|
var _reduxActionStore = require("../utils/reduxActionStore");
|
|
8
8
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
9
|
var _autoInstrument = require("../utils/autoInstrument");
|
|
10
|
-
/**
|
|
11
|
-
* Upper bound for one on-demand action-detail response. Generous — this is a
|
|
12
|
-
* single explicit fetch, not a per-action stream — but still guarded so a
|
|
13
|
-
* pathological tree can't OOM the device through the reply emit.
|
|
14
|
-
*/
|
|
15
|
-
const MAX_ACTION_DETAIL_BYTES = 8 * 1024 * 1024;
|
|
16
|
-
|
|
17
10
|
/** Per-action budget for `payload`/`action`/`meta.arg`/`error` on the snapshot wire. */
|
|
18
|
-
const
|
|
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
|
-
}
|
|
11
|
+
const toWireValue = value => (0, _sharedUi.toWire)(value, _reduxActionStore.PAYLOAD_ON_DEVICE);
|
|
23
12
|
|
|
24
13
|
/**
|
|
25
14
|
* Per-action wire cache. `reduxActionStore` never mutates an action in place —
|
|
@@ -28,10 +17,7 @@ function toWireValue(value) {
|
|
|
28
17
|
* `action`, `meta.arg` and `error` in the list is re-measured on all ~5
|
|
29
18
|
* snapshots per second for as long as it stays in the list.
|
|
30
19
|
*/
|
|
31
|
-
const
|
|
32
|
-
function toWireAction(a) {
|
|
33
|
-
const cached = wireCache.get(a);
|
|
34
|
-
if (cached) return cached;
|
|
20
|
+
const toWireAction = (0, _sharedUi.memoByIdentity)(a => {
|
|
35
21
|
const wire = (0, _reduxActionStore.isEvictedState)(a.prevState) ? {
|
|
36
22
|
...a
|
|
37
23
|
} : {
|
|
@@ -54,9 +40,8 @@ function toWireAction(a) {
|
|
|
54
40
|
arg
|
|
55
41
|
};
|
|
56
42
|
}
|
|
57
|
-
wireCache.set(a, wire);
|
|
58
43
|
return wire;
|
|
59
|
-
}
|
|
44
|
+
});
|
|
60
45
|
/** Top-level slice names of the state tree, for the compact (no-values) view. */
|
|
61
46
|
function topLevelKeys(state) {
|
|
62
47
|
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
|
|
@@ -93,7 +78,7 @@ function unavailable() {
|
|
|
93
78
|
* dispatch actions — redux is action-based, so the *current state* isn't in the
|
|
94
79
|
* action-log snapshot.
|
|
95
80
|
*/
|
|
96
|
-
const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
81
|
+
const reduxSyncAdapter = exports.reduxSyncAdapter = (0, _sharedUi.createSyncAdapter)({
|
|
97
82
|
version: 2,
|
|
98
83
|
/**
|
|
99
84
|
* WIRE-FORM snapshot: actions WITHOUT raw prevState/nextState trees.
|
|
@@ -118,7 +103,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
118
103
|
getActionDetail: params => {
|
|
119
104
|
const {
|
|
120
105
|
id
|
|
121
|
-
} = params
|
|
106
|
+
} = (0, _sharedUi.readParams)(params);
|
|
122
107
|
if (!id) return {
|
|
123
108
|
found: false,
|
|
124
109
|
reason: "missing id"
|
|
@@ -138,11 +123,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
138
123
|
}
|
|
139
124
|
// Per-field cap: oversized fields degrade individually instead of
|
|
140
125
|
// failing the whole detail (a giant payload shouldn't hide the trees).
|
|
141
|
-
const
|
|
142
|
-
__buoyTruncated: true,
|
|
143
|
-
note: `Exceeds the ${MAX_ACTION_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
|
|
144
|
-
};
|
|
145
|
-
const cap = value => (0, _sharedUi.isOverWireBudget)(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
|
|
126
|
+
const cap = value => (0, _sharedUi.capDetail)(value, _sharedUi.WIRE_DETAIL_LIMIT_BYTES);
|
|
146
127
|
const capMeta = meta => {
|
|
147
128
|
if (!meta) return meta;
|
|
148
129
|
const arg = cap(meta.arg);
|
|
@@ -169,7 +150,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
169
150
|
getState: params => {
|
|
170
151
|
const {
|
|
171
152
|
includeValues = false
|
|
172
|
-
} = params
|
|
153
|
+
} = (0, _sharedUi.readParams)(params);
|
|
173
154
|
const store = (0, _autoInstrument.getActiveStore)();
|
|
174
155
|
if (!store) {
|
|
175
156
|
return unavailable();
|
|
@@ -191,7 +172,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
191
172
|
dispatch: params => {
|
|
192
173
|
const {
|
|
193
174
|
action
|
|
194
|
-
} = params;
|
|
175
|
+
} = (0, _sharedUi.readParams)(params);
|
|
195
176
|
const store = (0, _autoInstrument.getActiveStore)();
|
|
196
177
|
if (!store) {
|
|
197
178
|
return {
|
|
@@ -199,11 +180,12 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
199
180
|
...unavailable()
|
|
200
181
|
};
|
|
201
182
|
}
|
|
183
|
+
if (!action) throw new Error("dispatch requires an `action`");
|
|
202
184
|
store.dispatch(action);
|
|
203
185
|
return {
|
|
204
186
|
dispatched: true,
|
|
205
|
-
type: action
|
|
187
|
+
type: action.type
|
|
206
188
|
};
|
|
207
189
|
}
|
|
208
190
|
}
|
|
209
|
-
};
|
|
191
|
+
});
|
|
@@ -238,16 +238,9 @@ function extractMeta(action) {
|
|
|
238
238
|
// Redux Action Store
|
|
239
239
|
// ============================================
|
|
240
240
|
|
|
241
|
-
class ReduxActionStore {
|
|
242
|
-
actions = [];
|
|
243
|
-
listeners = new Set();
|
|
244
|
-
clearListeners = new Set();
|
|
245
|
-
maxActions = 200;
|
|
246
|
-
idCounter = 0;
|
|
247
|
-
isEnabled = true;
|
|
248
|
-
captureSuppressed = false;
|
|
241
|
+
class ReduxActionStore extends _sharedUi.ChangeLog {
|
|
249
242
|
addAction(action, prevState, nextState, duration) {
|
|
250
|
-
if (!this.
|
|
243
|
+
if (!this.canRecord()) {
|
|
251
244
|
return;
|
|
252
245
|
}
|
|
253
246
|
const actionType = action.type || "";
|
|
@@ -273,7 +266,7 @@ class ReduxActionStore {
|
|
|
273
266
|
const isSlowAction = (duration ?? 0) > 16; // Frame budget
|
|
274
267
|
|
|
275
268
|
const reduxAction = {
|
|
276
|
-
id:
|
|
269
|
+
id: this.nextId(),
|
|
277
270
|
type: actionType,
|
|
278
271
|
payload: action.payload,
|
|
279
272
|
action,
|
|
@@ -293,7 +286,7 @@ class ReduxActionStore {
|
|
|
293
286
|
changedKeysCount,
|
|
294
287
|
isSlowAction
|
|
295
288
|
};
|
|
296
|
-
this.
|
|
289
|
+
this.push(reduxAction);
|
|
297
290
|
|
|
298
291
|
// Bound raw-state retention. Every action holds prevState/nextState by
|
|
299
292
|
// reference; with structural sharing that's normally cheap, but when a
|
|
@@ -302,8 +295,8 @@ class ReduxActionStore {
|
|
|
302
295
|
// pinned by 50 slice-replacing actions, enough to OOM-kill enterprise
|
|
303
296
|
// apps. Detail view + time travel realistically use recent actions, so
|
|
304
297
|
// older ones keep their diff metadata but drop the raw trees.
|
|
305
|
-
for (let i = STATE_RETENTION_LIMIT; i < this.
|
|
306
|
-
const a = this.
|
|
298
|
+
for (let i = STATE_RETENTION_LIMIT; i < this.items.length; i++) {
|
|
299
|
+
const a = this.items[i];
|
|
307
300
|
if (a.prevState !== STATE_EVICTED) {
|
|
308
301
|
a.prevState = STATE_EVICTED;
|
|
309
302
|
a.nextState = STATE_EVICTED;
|
|
@@ -314,63 +307,26 @@ class ReduxActionStore {
|
|
|
314
307
|
this.notifyListeners();
|
|
315
308
|
}
|
|
316
309
|
getActions() {
|
|
317
|
-
return
|
|
310
|
+
return this.getAll();
|
|
318
311
|
}
|
|
319
312
|
getActionById(id) {
|
|
320
|
-
return this.
|
|
313
|
+
return this.getById(id);
|
|
321
314
|
}
|
|
322
315
|
clearActions() {
|
|
323
|
-
this.
|
|
324
|
-
this.notifyListeners();
|
|
325
|
-
this.clearListeners.forEach(listener => {
|
|
326
|
-
try {
|
|
327
|
-
listener();
|
|
328
|
-
} catch {
|
|
329
|
-
// Ignore listener errors
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Listen for clearActions() calls. Used in remote mirror mode to forward a
|
|
336
|
-
* clear performed in the dashboard UI to the synced device.
|
|
337
|
-
*/
|
|
338
|
-
onClear(listener) {
|
|
339
|
-
this.clearListeners.add(listener);
|
|
340
|
-
return () => {
|
|
341
|
-
this.clearListeners.delete(listener);
|
|
342
|
-
};
|
|
316
|
+
this.clear();
|
|
343
317
|
}
|
|
344
318
|
|
|
345
319
|
// ============================================
|
|
346
320
|
// Remote Mirror Mode
|
|
347
321
|
// ============================================
|
|
348
322
|
|
|
349
|
-
/**
|
|
350
|
-
* Permanently suppress local capture. Use when this store acts as a mirror
|
|
351
|
-
* of a remote device's actions (e.g. the desktop dashboard): actions arrive
|
|
352
|
-
* via replaceActions() and local instrumentation must never record.
|
|
353
|
-
*/
|
|
354
|
-
disableCapture() {
|
|
355
|
-
this.captureSuppressed = true;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Whether the store is in remote mirror mode (capture suppressed). The UI
|
|
360
|
-
* uses this to skip local-instrumentation affordances like the
|
|
361
|
-
* "react-redux not installed" banner.
|
|
362
|
-
*/
|
|
363
|
-
isCaptureSuppressed() {
|
|
364
|
-
return this.captureSuppressed;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
323
|
/**
|
|
368
324
|
* Replace the entire action list and notify listeners. Used in remote
|
|
369
325
|
* mirror mode where full snapshots arrive from a synced device. Respects
|
|
370
326
|
* the enabled flag so the UI's pause button freezes the mirror.
|
|
371
327
|
*/
|
|
372
328
|
replaceActions(actions) {
|
|
373
|
-
if (!this.
|
|
329
|
+
if (!this.getEnabled()) {
|
|
374
330
|
return;
|
|
375
331
|
}
|
|
376
332
|
// Preserve any trees a remote mirror already hydrated via
|
|
@@ -378,12 +334,12 @@ class ReduxActionStore {
|
|
|
378
334
|
// full snapshot arriving after a detail fetch must not blank the detail
|
|
379
335
|
// pane back out.
|
|
380
336
|
const hydrated = new Map();
|
|
381
|
-
for (const existing of this.
|
|
337
|
+
for (const existing of this.items) {
|
|
382
338
|
if (existing.prevState !== undefined && !isEvictedState(existing.prevState)) {
|
|
383
339
|
hydrated.set(existing.id, existing);
|
|
384
340
|
}
|
|
385
341
|
}
|
|
386
|
-
|
|
342
|
+
const merged = actions.slice(0, this.max).map(a => {
|
|
387
343
|
const h = hydrated.get(a.id);
|
|
388
344
|
if (!h || !isEvictedState(a.prevState)) return a;
|
|
389
345
|
return {
|
|
@@ -396,7 +352,7 @@ class ReduxActionStore {
|
|
|
396
352
|
error: isPayloadOnDevice(a.error) ? h.error : a.error
|
|
397
353
|
};
|
|
398
354
|
});
|
|
399
|
-
this.
|
|
355
|
+
this.setItems(merged);
|
|
400
356
|
}
|
|
401
357
|
|
|
402
358
|
/**
|
|
@@ -405,44 +361,18 @@ class ReduxActionStore {
|
|
|
405
361
|
* `getActionDetail` action. Remote-mirror (desktop) use only.
|
|
406
362
|
*/
|
|
407
363
|
hydrateAction(id, patch) {
|
|
408
|
-
|
|
409
|
-
if (index === -1) return;
|
|
410
|
-
this.actions = [...this.actions.slice(0, index), {
|
|
411
|
-
...this.actions[index],
|
|
412
|
-
...patch
|
|
413
|
-
}, ...this.actions.slice(index + 1)];
|
|
414
|
-
this.notifyListeners();
|
|
415
|
-
}
|
|
416
|
-
setEnabled(enabled) {
|
|
417
|
-
this.isEnabled = enabled;
|
|
418
|
-
}
|
|
419
|
-
getEnabled() {
|
|
420
|
-
return this.isEnabled;
|
|
421
|
-
}
|
|
422
|
-
subscribe(listener) {
|
|
423
|
-
this.listeners.add(listener);
|
|
424
|
-
return () => {
|
|
425
|
-
this.listeners.delete(listener);
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
notifyListeners() {
|
|
429
|
-
const actions = this.getActions();
|
|
430
|
-
this.listeners.forEach(listener => listener(actions));
|
|
364
|
+
this.hydrateById(id, patch);
|
|
431
365
|
}
|
|
432
366
|
setMaxActions(max) {
|
|
433
|
-
this.
|
|
434
|
-
if (this.actions.length > max) {
|
|
435
|
-
this.actions = this.actions.slice(0, max);
|
|
436
|
-
this.notifyListeners();
|
|
437
|
-
}
|
|
367
|
+
this.setMax(max);
|
|
438
368
|
}
|
|
439
369
|
getStats() {
|
|
440
|
-
const total = this.
|
|
441
|
-
const withChanges = this.
|
|
442
|
-
const actionTypes = new Set(this.
|
|
370
|
+
const total = this.items.length;
|
|
371
|
+
const withChanges = this.items.filter(a => a.hasStateChange).length;
|
|
372
|
+
const actionTypes = new Set(this.items.map(a => a.type));
|
|
443
373
|
|
|
444
374
|
// Calculate average duration
|
|
445
|
-
const durations = this.
|
|
375
|
+
const durations = this.items.filter(a => a.duration !== undefined).map(a => a.duration);
|
|
446
376
|
const avgDuration = durations.length > 0 ? durations.reduce((sum, d) => sum + d, 0) / durations.length : 0;
|
|
447
377
|
return {
|
|
448
378
|
totalActions: total,
|
|
@@ -453,7 +383,7 @@ class ReduxActionStore {
|
|
|
453
383
|
};
|
|
454
384
|
}
|
|
455
385
|
filterActions(filter) {
|
|
456
|
-
let filtered = [...this.
|
|
386
|
+
let filtered = [...this.items];
|
|
457
387
|
if (filter.searchText) {
|
|
458
388
|
const search = filter.searchText.toLowerCase();
|
|
459
389
|
filtered = filtered.filter(a => a.type.toLowerCase().includes(search));
|
|
@@ -467,14 +397,14 @@ class ReduxActionStore {
|
|
|
467
397
|
return filtered;
|
|
468
398
|
}
|
|
469
399
|
getUniqueActionTypes() {
|
|
470
|
-
return Array.from(new Set(this.
|
|
400
|
+
return Array.from(new Set(this.items.map(a => a.type))).sort();
|
|
471
401
|
}
|
|
472
402
|
|
|
473
403
|
/**
|
|
474
404
|
* Find related async actions by requestId
|
|
475
405
|
*/
|
|
476
406
|
getRelatedActions(requestId) {
|
|
477
|
-
return this.
|
|
407
|
+
return this.items.filter(a => a.meta?.requestId === requestId).sort((a, b) => a.timestamp - b.timestamp); // Sort chronologically
|
|
478
408
|
}
|
|
479
409
|
|
|
480
410
|
/**
|
|
@@ -490,7 +420,7 @@ class ReduxActionStore {
|
|
|
490
420
|
*/
|
|
491
421
|
getLinkedActionsCount(action) {
|
|
492
422
|
if (!action.meta?.requestId) return 0;
|
|
493
|
-
return this.
|
|
423
|
+
return this.items.filter(a => a.meta?.requestId === action.meta?.requestId).length;
|
|
494
424
|
}
|
|
495
425
|
|
|
496
426
|
/**
|
|
@@ -528,7 +458,7 @@ class ReduxActionStore {
|
|
|
528
458
|
getAsyncOperationIndex(requestId) {
|
|
529
459
|
// Get all unique requestIds with their first timestamp
|
|
530
460
|
const requestIdTimestamps = new Map();
|
|
531
|
-
for (const action of this.
|
|
461
|
+
for (const action of this.items) {
|
|
532
462
|
if (action.meta?.requestId && !requestIdTimestamps.has(action.meta.requestId)) {
|
|
533
463
|
requestIdTimestamps.set(action.meta.requestId, action.timestamp);
|
|
534
464
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
4
|
-
var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
|
|
5
4
|
var _activeStoreRegistry = require("./activeStoreRegistry");
|
|
6
5
|
var _buoyReduxMiddleware = require("./buoyReduxMiddleware");
|
|
7
6
|
/**
|
|
@@ -65,36 +64,19 @@ async function restore(data, options) {
|
|
|
65
64
|
const snapshot = data;
|
|
66
65
|
const store = (0, _activeStoreRegistry.getActiveStore)();
|
|
67
66
|
if (!store || snapshot === undefined || snapshot === null) {
|
|
68
|
-
return
|
|
69
|
-
ok: false,
|
|
70
|
-
applied: 0,
|
|
71
|
-
skipped: [{
|
|
72
|
-
id: "redux",
|
|
73
|
-
reason: store ? "empty redux snapshot" : "no Redux store instrumented"
|
|
74
|
-
}],
|
|
75
|
-
warnings: []
|
|
76
|
-
};
|
|
67
|
+
return (0, _sharedUi.skippedRestore)("redux", store ? "empty redux snapshot" : "no Redux store instrumented");
|
|
77
68
|
}
|
|
78
69
|
const {
|
|
79
70
|
mechanism
|
|
80
71
|
} = (0, _activeStoreRegistry.getInstrumentationStatus)();
|
|
81
72
|
if (mechanism === "patch") {
|
|
82
|
-
return {
|
|
83
|
-
ok: false,
|
|
84
|
-
applied: 0,
|
|
85
|
-
skipped: [{
|
|
86
|
-
id: "redux",
|
|
87
|
-
reason: `restore unavailable on auto-instrumented stores — ${ENABLE_HINT}`
|
|
88
|
-
}],
|
|
89
|
-
warnings: []
|
|
90
|
-
};
|
|
73
|
+
return (0, _sharedUi.skippedRestore)("redux", `restore unavailable on auto-instrumented stores — ${ENABLE_HINT}`);
|
|
91
74
|
}
|
|
92
75
|
// Untouched slices keep their LIVE values inside the jump payload, so the
|
|
93
76
|
// full-state replace leaves them alone. A slice is untouched when it's
|
|
94
77
|
// explicitly excluded OR outside the includeKeys scope.
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const excluded = Object.keys(snapshot.state ?? {}).filter(k => excludedSet.has(k) || includedSet && !includedSet.has(k));
|
|
78
|
+
const scope = (0, _sharedUi.createRestoreScope)(options);
|
|
79
|
+
const excluded = Object.keys(snapshot.state ?? {}).filter(k => !scope.touch(k));
|
|
98
80
|
let payload = snapshot.state;
|
|
99
81
|
if (excluded.length > 0) {
|
|
100
82
|
const live = store.getState();
|
|
@@ -120,17 +102,8 @@ async function restore(data, options) {
|
|
|
120
102
|
warnings
|
|
121
103
|
};
|
|
122
104
|
}
|
|
123
|
-
return {
|
|
124
|
-
ok: false,
|
|
125
|
-
applied: 0,
|
|
126
|
-
skipped: [{
|
|
127
|
-
id: "redux",
|
|
128
|
-
reason: `store ignored the state jump — ${ENABLE_HINT}`
|
|
129
|
-
}],
|
|
130
|
-
warnings: []
|
|
131
|
-
};
|
|
105
|
+
return (0, _sharedUi.skippedRestore)("redux", `store ignored the state jump — ${ENABLE_HINT}`);
|
|
132
106
|
}
|
|
133
|
-
const ITEM_RENDER_BYTE_LIMIT = 256 * 1024;
|
|
134
107
|
|
|
135
108
|
/** Per-slice restore preview. `base` omitted = compare against the live store. */
|
|
136
109
|
async function previewItems(target, base) {
|
|
@@ -155,54 +128,18 @@ async function previewItems(target, base) {
|
|
|
155
128
|
}
|
|
156
129
|
const oldObj = baseState ?? {};
|
|
157
130
|
const newObj = targetState ?? {};
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
}
|
|
131
|
+
for (const key of (0, _sharedUi.unionKeys)(oldObj, newObj)) {
|
|
132
|
+
let item = (0, _sharedUi.classifyPreviewItem)({
|
|
133
|
+
key,
|
|
134
|
+
label: key,
|
|
135
|
+
oldValue: oldObj[key],
|
|
136
|
+
newValue: newObj[key],
|
|
137
|
+
inOld: Object.prototype.hasOwnProperty.call(oldObj, key),
|
|
138
|
+
inNew: Object.prototype.hasOwnProperty.call(newObj, key)
|
|
139
|
+
}, {
|
|
140
|
+
tooLargeReason: "slice too large to render a diff — restore still applies it"
|
|
141
|
+
});
|
|
142
|
+
if (wontApplyReason) item = (0, _sharedUi.wontApply)(item, wontApplyReason);
|
|
206
143
|
items.push(item);
|
|
207
144
|
}
|
|
208
145
|
if (wontApplyReason) notes.push(`Redux restore unavailable: ${wontApplyReason}`);
|
package/lib/module/preset.js
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
import { ReduxModal } from "./redux/components/ReduxModal";
|
|
36
36
|
import { ReduxIcon } from "./redux/components/ReduxIcon";
|
|
37
|
+
import { createToolFactory, defineToolPreset } from "@buoy-gg/shared-ui";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Pre-configured Redux DevTools preset for FloatingDevTools.
|
|
@@ -44,7 +45,7 @@ import { ReduxIcon } from "./redux/components/ReduxIcon";
|
|
|
44
45
|
* - Filter by action type
|
|
45
46
|
*/
|
|
46
47
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
47
|
-
export const reduxToolPreset = {
|
|
48
|
+
export const reduxToolPreset = defineToolPreset({
|
|
48
49
|
id: "redux",
|
|
49
50
|
name: "REDUX",
|
|
50
51
|
description: "Redux action & state inspector",
|
|
@@ -58,7 +59,7 @@ export const reduxToolPreset = {
|
|
|
58
59
|
props: {
|
|
59
60
|
enableSharedModalDimensions: false
|
|
60
61
|
}
|
|
61
|
-
};
|
|
62
|
+
});
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
65
|
* Create a custom Redux DevTools configuration.
|
|
@@ -74,21 +75,13 @@ export const reduxToolPreset = {
|
|
|
74
75
|
* });
|
|
75
76
|
* ```
|
|
76
77
|
*/
|
|
77
|
-
export
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
color: options?.iconColor
|
|
88
|
-
}),
|
|
89
|
-
component: ReduxModal,
|
|
90
|
-
props: {
|
|
91
|
-
enableSharedModalDimensions: options?.enableSharedModalDimensions !== undefined ? options.enableSharedModalDimensions : false
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
}
|
|
78
|
+
export const createReduxTool = createToolFactory(reduxToolPreset, options => ({
|
|
79
|
+
enableSharedModalDimensions: options.enableSharedModalDimensions ?? false
|
|
80
|
+
}), options => ({
|
|
81
|
+
icon: ({
|
|
82
|
+
size
|
|
83
|
+
}) => /*#__PURE__*/_jsx(ReduxIcon, {
|
|
84
|
+
size: size,
|
|
85
|
+
color: options.iconColor
|
|
86
|
+
})
|
|
87
|
+
}));
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import React, { memo, useMemo, useCallback, useState, useEffect } from "react";
|
|
14
14
|
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from "react-native";
|
|
15
|
-
import { buoyColors, night, Clock, AlertTriangle, Zap, Hash, Info, Box, AlertCircle, FileText, parseValue, ProUpgradeModal, copyToClipboard } from "@buoy-gg/shared-ui";
|
|
15
|
+
import { buoyColors, night, Clock, AlertTriangle, Zap, Hash, Info, Box, AlertCircle, FileText, parseValue, ProUpgradeModal, copyToClipboard, InfoRow } from "@buoy-gg/shared-ui";
|
|
16
16
|
import { useIsPro } from "@buoy-gg/license";
|
|
17
17
|
import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
|
|
18
18
|
import { reduxActionStore, isEvictedState } from "../utils/reduxActionStore";
|
|
@@ -104,37 +104,6 @@ function SectionHeader({
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/**
|
|
108
|
-
* Info row component for key-value display
|
|
109
|
-
*/
|
|
110
|
-
function InfoRow({
|
|
111
|
-
label,
|
|
112
|
-
value,
|
|
113
|
-
valueColor,
|
|
114
|
-
mono = false,
|
|
115
|
-
testID
|
|
116
|
-
}) {
|
|
117
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
118
|
-
style: styles.infoRow,
|
|
119
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
120
|
-
style: styles.infoLabel,
|
|
121
|
-
children: label
|
|
122
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
123
|
-
style: [styles.infoValue, mono && styles.infoValueMono, valueColor && {
|
|
124
|
-
color: valueColor
|
|
125
|
-
}]
|
|
126
|
-
// `ellipsizeMode="middle"` truncates the RENDERED text and iOS reports
|
|
127
|
-
// the truncated string back, so anything a test reads off one of these
|
|
128
|
-
// rows has to be short enough to fit on the line.
|
|
129
|
-
,
|
|
130
|
-
numberOfLines: 1,
|
|
131
|
-
ellipsizeMode: "middle",
|
|
132
|
-
testID: testID,
|
|
133
|
-
children: value
|
|
134
|
-
})]
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
107
|
/**
|
|
139
108
|
* Format timestamp for display
|
|
140
109
|
*/
|
|
@@ -604,27 +573,6 @@ const styles = StyleSheet.create({
|
|
|
604
573
|
padding: 14,
|
|
605
574
|
gap: 10
|
|
606
575
|
},
|
|
607
|
-
infoRow: {
|
|
608
|
-
flexDirection: "row",
|
|
609
|
-
alignItems: "center",
|
|
610
|
-
justifyContent: "space-between"
|
|
611
|
-
},
|
|
612
|
-
infoLabel: {
|
|
613
|
-
fontSize: 11,
|
|
614
|
-
color: night.color.textTertiary,
|
|
615
|
-
fontWeight: "500"
|
|
616
|
-
},
|
|
617
|
-
infoValue: {
|
|
618
|
-
fontSize: 11,
|
|
619
|
-
color: night.color.text,
|
|
620
|
-
fontWeight: "500",
|
|
621
|
-
flex: 1,
|
|
622
|
-
textAlign: "right",
|
|
623
|
-
marginLeft: 12
|
|
624
|
-
},
|
|
625
|
-
infoValueMono: {
|
|
626
|
-
fontFamily: "monospace"
|
|
627
|
-
},
|
|
628
576
|
badgeRow: {
|
|
629
577
|
flexDirection: "row",
|
|
630
578
|
flexWrap: "wrap",
|