@buoy-gg/redux 7.0.16 → 7.0.18

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.
@@ -213,6 +213,7 @@ Object.defineProperty(exports, "withBuoyDevTools", {
213
213
  return _buoyReduxMiddleware.withBuoyDevTools;
214
214
  }
215
215
  });
216
+ require("./redux/utils/snapshotProvider");
216
217
  var _devtoolsHook = require("./redux/utils/devtoolsHook");
217
218
  var _preset = require("./preset");
218
219
  var _buoyReduxMiddleware = require("./redux/utils/buoyReduxMiddleware");
@@ -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)("action");
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,
@@ -13,8 +13,17 @@ var _autoInstrument = require("../utils/autoInstrument");
13
13
  */
14
14
  const MAX_ACTION_DETAIL_BYTES = 8 * 1024 * 1024;
15
15
 
16
- /** Per-action budget for `payload`/`action` on the snapshot wire. */
16
+ /** Per-action budget for `payload`/`action`/`meta.arg`/`error` on the snapshot wire. */
17
17
  const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
18
+ function toWireValue(value) {
19
+ if (value === undefined) return value;
20
+ try {
21
+ JSON.stringify(value);
22
+ } catch {
23
+ return _reduxActionStore.PAYLOAD_ON_DEVICE;
24
+ }
25
+ return (0, _reduxActionStore.jsonLongerThan)(value, MAX_WIRE_PAYLOAD_BYTES) ? _reduxActionStore.PAYLOAD_ON_DEVICE : value;
26
+ }
18
27
  /** Top-level slice names of the state tree, for the compact (no-values) view. */
19
28
  function topLevelKeys(state) {
20
29
  return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
@@ -72,12 +81,20 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
72
81
  };
73
82
  // A dispatched payload can itself be huge (rehydration actions carry
74
83
  // 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;
84
+ // oversized / unserializable values stay on-device behind markers.
85
+ // `meta.arg` is a leftover: RTK copies the thunk argument there, and
86
+ // capping `action` still left the extracted copy on the wire.
87
+ wire.payload = toWireValue(wire.payload);
88
+ wire.action = toWireValue(wire.action);
89
+ wire.error = toWireValue(wire.error);
90
+ if (wire.meta) {
91
+ const arg = toWireValue(wire.meta.arg);
92
+ if (arg !== wire.meta.arg) {
93
+ wire.meta = {
94
+ ...wire.meta,
95
+ arg
96
+ };
97
+ }
81
98
  }
82
99
  return wire;
83
100
  }),
@@ -118,14 +135,31 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
118
135
  __buoyTruncated: true,
119
136
  note: `Exceeds the ${MAX_ACTION_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
120
137
  };
121
- const cap = value => (0, _reduxActionStore.jsonLongerThan)(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
138
+ const cap = value => {
139
+ try {
140
+ JSON.stringify(value);
141
+ } catch {
142
+ return cappedNote;
143
+ }
144
+ return (0, _reduxActionStore.jsonLongerThan)(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
145
+ };
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,6 +5,7 @@ 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;
10
11
  /**
@@ -52,6 +53,11 @@ const PAYLOAD_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = Object.freeze({
52
53
  note: "Payload stays on the device — fetched on demand via getActionDetail."
53
54
  });
54
55
 
56
+ /** True when a wire-form value is the payload-on-device sentinel. */
57
+ function isPayloadOnDevice(value) {
58
+ return typeof value === "object" && value !== null && value.__buoyPayloadOnDevice === true;
59
+ }
60
+
55
61
  /**
56
62
  * True when a value is one of the state sentinels (evicted or on-device) —
57
63
  * guards time travel and any consumer that needs a REAL tree.
@@ -399,11 +405,16 @@ class ReduxActionStore {
399
405
  }
400
406
  this.actions = actions.slice(0, this.maxActions).map(a => {
401
407
  const h = hydrated.get(a.id);
402
- return h && isEvictedState(a.prevState) ? {
408
+ if (!h || !isEvictedState(a.prevState)) return a;
409
+ return {
403
410
  ...a,
404
411
  prevState: h.prevState,
405
- nextState: h.nextState
406
- } : a;
412
+ nextState: h.nextState,
413
+ payload: isPayloadOnDevice(a.payload) ? h.payload : a.payload,
414
+ action: isPayloadOnDevice(a.action) ? h.action : a.action,
415
+ meta: isPayloadOnDevice(a.meta?.arg) ? h.meta : a.meta,
416
+ error: isPayloadOnDevice(a.error) ? h.error : a.error
417
+ };
407
418
  });
408
419
  this.notifyListeners();
409
420
  }
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+
3
+ var _sharedUi = require("@buoy-gg/shared-ui");
4
+ var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
5
+ var _activeStoreRegistry = require("./activeStoreRegistry");
6
+ var _buoyReduxMiddleware = require("./buoyReduxMiddleware");
7
+ /**
8
+ * Time Machine snapshot provider for Redux.
9
+ *
10
+ * Registered into @buoy-gg/shared-ui's stateSnapshotRegistry at module init;
11
+ * consumed by @buoy-gg/time-machine.
12
+ *
13
+ * Restore rides the existing `@@buoy/JUMP_TO_STATE` full-state-replace action:
14
+ * - enhancer path: always handled (wrapReducer).
15
+ * - middleware path: handled ONLY when the app wrapped its root reducer in
16
+ * `withBuoyDevTools()` — unverifiable statically, so restore verifies by
17
+ * reference identity after dispatch (the wrapped reducer returns the payload
18
+ * object itself).
19
+ * - patch path: never handled; restore is reported as unavailable up front.
20
+ */
21
+
22
+ const ENABLE_HINT = "enable restore by using the Buoy enhancer or wrapping your root reducer in withBuoyDevTools()";
23
+ function status() {
24
+ const {
25
+ mechanism,
26
+ hasStore,
27
+ diagnostic
28
+ } = (0, _activeStoreRegistry.getInstrumentationStatus)();
29
+ if (!hasStore) {
30
+ return {
31
+ canCapture: false,
32
+ canRestore: false,
33
+ detail: diagnostic === "no-react-redux" ? "react-redux not installed" : diagnostic === "no-provider" ? "No Redux <Provider> found" : "No Redux store instrumented"
34
+ };
35
+ }
36
+ if (mechanism === "patch") {
37
+ return {
38
+ canCapture: true,
39
+ canRestore: false,
40
+ detail: `Auto-instrumented store cannot be restored — ${ENABLE_HINT}`
41
+ };
42
+ }
43
+ return {
44
+ canCapture: true,
45
+ canRestore: true,
46
+ detail: mechanism === "middleware" ? "Restore requires withBuoyDevTools(rootReducer) — verified at restore time" : undefined
47
+ };
48
+ }
49
+ async function capture() {
50
+ const store = (0, _activeStoreRegistry.getActiveStore)();
51
+ if (!store) {
52
+ return {
53
+ data: null,
54
+ warnings: ["no Redux store instrumented"]
55
+ };
56
+ }
57
+ return {
58
+ data: {
59
+ state: store.getState()
60
+ },
61
+ warnings: []
62
+ };
63
+ }
64
+ async function restore(data, options) {
65
+ const snapshot = data;
66
+ const store = (0, _activeStoreRegistry.getActiveStore)();
67
+ 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
+ };
77
+ }
78
+ const {
79
+ mechanism
80
+ } = (0, _activeStoreRegistry.getInstrumentationStatus)();
81
+ 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
+ };
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
+ }
107
+ store.dispatch({
108
+ type: _buoyReduxMiddleware.BUOY_JUMP_TO_STATE,
109
+ payload
110
+ });
111
+ // The wrapped reducer returns the payload object itself, so reference
112
+ // identity proves the jump was actually handled (and not silently ignored
113
+ // by an unwrapped middleware-path reducer).
114
+ if (store.getState() === payload) {
115
+ const warnings = excluded.length > 0 ? [`${excluded.length} slice(s) excluded by you`] : [];
116
+ return {
117
+ ok: true,
118
+ applied: 1,
119
+ skipped: [],
120
+ warnings
121
+ };
122
+ }
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
+ };
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
+ }
214
+ (0, _sharedUi.registerStateSnapshotProvider)({
215
+ id: "redux",
216
+ label: "Redux",
217
+ status,
218
+ capture,
219
+ restore,
220
+ previewItems
221
+ });
@@ -53,6 +53,9 @@
53
53
  * ```
54
54
  */
55
55
 
56
+ // Side-effect: registers the Time Machine snapshot provider
57
+ import "./redux/utils/snapshotProvider";
58
+
56
59
  // SIDE EFFECT (must stay first): claim the Redux DevTools global so stores
57
60
  // created after this module loads get the full-capture enhancer. Dev-only,
58
61
  // never overrides an existing claimant. See ./redux/utils/devtoolsHook.ts.
@@ -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("action");
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,
@@ -10,8 +10,17 @@ import { getActiveStore, getInstrumentationStatus } from "../utils/autoInstrumen
10
10
  */
11
11
  const MAX_ACTION_DETAIL_BYTES = 8 * 1024 * 1024;
12
12
 
13
- /** Per-action budget for `payload`/`action` on the snapshot wire. */
13
+ /** Per-action budget for `payload`/`action`/`meta.arg`/`error` on the snapshot wire. */
14
14
  const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
15
+ function toWireValue(value) {
16
+ if (value === undefined) return value;
17
+ try {
18
+ JSON.stringify(value);
19
+ } catch {
20
+ return PAYLOAD_ON_DEVICE;
21
+ }
22
+ return jsonLongerThan(value, MAX_WIRE_PAYLOAD_BYTES) ? PAYLOAD_ON_DEVICE : value;
23
+ }
15
24
  /** Top-level slice names of the state tree, for the compact (no-values) view. */
16
25
  function topLevelKeys(state) {
17
26
  return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
@@ -69,12 +78,20 @@ export const reduxSyncAdapter = {
69
78
  };
70
79
  // A dispatched payload can itself be huge (rehydration actions carry
71
80
  // 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;
81
+ // oversized / unserializable values stay on-device behind markers.
82
+ // `meta.arg` is a leftover: RTK copies the thunk argument there, and
83
+ // capping `action` still left the extracted copy on the wire.
84
+ wire.payload = toWireValue(wire.payload);
85
+ wire.action = toWireValue(wire.action);
86
+ wire.error = toWireValue(wire.error);
87
+ if (wire.meta) {
88
+ const arg = toWireValue(wire.meta.arg);
89
+ if (arg !== wire.meta.arg) {
90
+ wire.meta = {
91
+ ...wire.meta,
92
+ arg
93
+ };
94
+ }
78
95
  }
79
96
  return wire;
80
97
  }),
@@ -115,14 +132,31 @@ export const reduxSyncAdapter = {
115
132
  __buoyTruncated: true,
116
133
  note: `Exceeds the ${MAX_ACTION_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
117
134
  };
118
- const cap = value => jsonLongerThan(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
135
+ const cap = value => {
136
+ try {
137
+ JSON.stringify(value);
138
+ } catch {
139
+ return cappedNote;
140
+ }
141
+ return jsonLongerThan(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
142
+ };
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
  /**
@@ -45,6 +45,11 @@ export const PAYLOAD_ON_DEVICE = Object.freeze({
45
45
  note: "Payload stays on the device — fetched on demand via getActionDetail."
46
46
  });
47
47
 
48
+ /** True when a wire-form value is the payload-on-device sentinel. */
49
+ export function isPayloadOnDevice(value) {
50
+ return typeof value === "object" && value !== null && value.__buoyPayloadOnDevice === true;
51
+ }
52
+
48
53
  /**
49
54
  * True when a value is one of the state sentinels (evicted or on-device) —
50
55
  * guards time travel and any consumer that needs a REAL tree.
@@ -392,11 +397,16 @@ class ReduxActionStore {
392
397
  }
393
398
  this.actions = actions.slice(0, this.maxActions).map(a => {
394
399
  const h = hydrated.get(a.id);
395
- return h && isEvictedState(a.prevState) ? {
400
+ if (!h || !isEvictedState(a.prevState)) return a;
401
+ return {
396
402
  ...a,
397
403
  prevState: h.prevState,
398
- nextState: h.nextState
399
- } : a;
404
+ nextState: h.nextState,
405
+ payload: isPayloadOnDevice(a.payload) ? h.payload : a.payload,
406
+ action: isPayloadOnDevice(a.action) ? h.action : a.action,
407
+ meta: isPayloadOnDevice(a.meta?.arg) ? h.meta : a.meta,
408
+ error: isPayloadOnDevice(a.error) ? h.error : a.error
409
+ };
400
410
  });
401
411
  this.notifyListeners();
402
412
  }
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Time Machine snapshot provider for Redux.
5
+ *
6
+ * Registered into @buoy-gg/shared-ui's stateSnapshotRegistry at module init;
7
+ * consumed by @buoy-gg/time-machine.
8
+ *
9
+ * Restore rides the existing `@@buoy/JUMP_TO_STATE` full-state-replace action:
10
+ * - enhancer path: always handled (wrapReducer).
11
+ * - middleware path: handled ONLY when the app wrapped its root reducer in
12
+ * `withBuoyDevTools()` — unverifiable statically, so restore verifies by
13
+ * reference identity after dispatch (the wrapped reducer returns the payload
14
+ * object itself).
15
+ * - patch path: never handled; restore is reported as unavailable up front.
16
+ */
17
+ import { registerStateSnapshotProvider } from "@buoy-gg/shared-ui";
18
+ import { boundedDeepEqual, jsonLikelyLargerThan } from "@buoy-gg/shared-ui/dataViewer";
19
+ import { getActiveStore, getInstrumentationStatus } from "./activeStoreRegistry";
20
+ import { BUOY_JUMP_TO_STATE } from "./buoyReduxMiddleware";
21
+ const ENABLE_HINT = "enable restore by using the Buoy enhancer or wrapping your root reducer in withBuoyDevTools()";
22
+ function status() {
23
+ const {
24
+ mechanism,
25
+ hasStore,
26
+ diagnostic
27
+ } = getInstrumentationStatus();
28
+ if (!hasStore) {
29
+ return {
30
+ canCapture: false,
31
+ canRestore: false,
32
+ detail: diagnostic === "no-react-redux" ? "react-redux not installed" : diagnostic === "no-provider" ? "No Redux <Provider> found" : "No Redux store instrumented"
33
+ };
34
+ }
35
+ if (mechanism === "patch") {
36
+ return {
37
+ canCapture: true,
38
+ canRestore: false,
39
+ detail: `Auto-instrumented store cannot be restored — ${ENABLE_HINT}`
40
+ };
41
+ }
42
+ return {
43
+ canCapture: true,
44
+ canRestore: true,
45
+ detail: mechanism === "middleware" ? "Restore requires withBuoyDevTools(rootReducer) — verified at restore time" : undefined
46
+ };
47
+ }
48
+ async function capture() {
49
+ const store = getActiveStore();
50
+ if (!store) {
51
+ return {
52
+ data: null,
53
+ warnings: ["no Redux store instrumented"]
54
+ };
55
+ }
56
+ return {
57
+ data: {
58
+ state: store.getState()
59
+ },
60
+ warnings: []
61
+ };
62
+ }
63
+ async function restore(data, options) {
64
+ const snapshot = data;
65
+ const store = getActiveStore();
66
+ if (!store || snapshot === undefined || snapshot === null) {
67
+ return {
68
+ ok: false,
69
+ applied: 0,
70
+ skipped: [{
71
+ id: "redux",
72
+ reason: store ? "empty redux snapshot" : "no Redux store instrumented"
73
+ }],
74
+ warnings: []
75
+ };
76
+ }
77
+ const {
78
+ mechanism
79
+ } = getInstrumentationStatus();
80
+ if (mechanism === "patch") {
81
+ return {
82
+ ok: false,
83
+ applied: 0,
84
+ skipped: [{
85
+ id: "redux",
86
+ reason: `restore unavailable on auto-instrumented stores — ${ENABLE_HINT}`
87
+ }],
88
+ warnings: []
89
+ };
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
+ }
106
+ store.dispatch({
107
+ type: BUOY_JUMP_TO_STATE,
108
+ payload
109
+ });
110
+ // The wrapped reducer returns the payload object itself, so reference
111
+ // identity proves the jump was actually handled (and not silently ignored
112
+ // by an unwrapped middleware-path reducer).
113
+ if (store.getState() === payload) {
114
+ const warnings = excluded.length > 0 ? [`${excluded.length} slice(s) excluded by you`] : [];
115
+ return {
116
+ ok: true,
117
+ applied: 1,
118
+ skipped: [],
119
+ warnings
120
+ };
121
+ }
122
+ return {
123
+ ok: false,
124
+ applied: 0,
125
+ skipped: [{
126
+ id: "redux",
127
+ reason: `store ignored the state jump — ${ENABLE_HINT}`
128
+ }],
129
+ warnings: []
130
+ };
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
+ }
213
+ registerStateSnapshotProvider({
214
+ id: "redux",
215
+ label: "Redux",
216
+ status,
217
+ capture,
218
+ restore,
219
+ previewItems
220
+ });
@@ -50,6 +50,7 @@
50
50
  * });
51
51
  * ```
52
52
  */
53
+ import "./redux/utils/snapshotProvider";
53
54
  import "./redux/utils/devtoolsHook";
54
55
  export { reduxToolPreset, createReduxTool } from "./preset";
55
56
  export { buoyReduxMiddleware, createBuoyReduxMiddleware, withBuoyDevTools, jumpToState, replayAction, BUOY_JUMP_TO_STATE, BUOY_REPLAY_ACTION, } from "./redux/utils/buoyReduxMiddleware";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAKH,OAAO,4BAA4B,CAAC;AAKpC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAK5D,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAK3C,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACd,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,yCAAyC,CAAC;AAKjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAG3E,OAAO,EACL,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,sCAAsC,CAAC;AAO9C,OAAO,EACL,UAAU,EACV,aAAa,EACb,sBAAsB,GACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAGzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAK7E,YAAY,EACV,cAAc,EACd,UAAU,EACV,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAKvB,gBAAgB;AAChB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAGH,OAAO,gCAAgC,CAAC;AAKxC,OAAO,4BAA4B,CAAC;AAKpC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAK5D,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAK3C,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACd,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,yCAAyC,CAAC;AAKjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAG3E,OAAO,EACL,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,sCAAsC,CAAC;AAO9C,OAAO,EACL,UAAU,EACV,aAAa,EACb,sBAAsB,GACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAGzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAK7E,YAAY,EACV,cAAc,EACd,UAAU,EACV,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAKvB,gBAAgB;AAChB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -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;AAQ5C,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;CACjC;AAwDD,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,qBAA6B,GAC9B,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"}
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;CAChD;AAgBD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,MAAc,EACd,cAAc,GACf,EAAE,eAAe,sCAmbjB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,GAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAM,+BAGpE"}
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 } 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
@@ -38,7 +39,7 @@ export declare const reduxSyncAdapter: {
38
39
  category: import("../types").ActionCategory;
39
40
  sliceName: string | null;
40
41
  actionName: string;
41
- meta?: import("../types").ActionMeta;
42
+ meta?: ActionMeta;
42
43
  error?: unknown;
43
44
  payloadPreview: string;
44
45
  diffSummary: string;
@@ -62,6 +63,8 @@ export declare const reduxSyncAdapter: {
62
63
  nextState?: undefined;
63
64
  payload?: undefined;
64
65
  action?: undefined;
66
+ meta?: undefined;
67
+ error?: undefined;
65
68
  } | {
66
69
  found: true;
67
70
  id: string;
@@ -71,6 +74,8 @@ export declare const reduxSyncAdapter: {
71
74
  nextState?: undefined;
72
75
  payload?: undefined;
73
76
  action?: undefined;
77
+ meta?: undefined;
78
+ error?: undefined;
74
79
  } | {
75
80
  found: true;
76
81
  id: string;
@@ -78,6 +83,8 @@ export declare const reduxSyncAdapter: {
78
83
  nextState: unknown;
79
84
  payload: unknown;
80
85
  action: unknown;
86
+ meta: ActionMeta | undefined;
87
+ error: unknown;
81
88
  reason?: undefined;
82
89
  evicted?: undefined;
83
90
  };
@@ -1 +1 @@
1
- {"version":3,"file":"reduxSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/redux/sync/reduxSyncAdapter.ts"],"names":[],"mappings":"AAsDA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB;;IAE3B;;;;;;;;OAQG;;;;;;;;;;;;;;;;;;;;;0BAiBmB,MAAM,IAAI;;;QAK9B;;;;WAIG;kCACuB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8BjC;;;WAGG;2BACgB,OAAO;;oBA7FZ,MAAM,EAAE;YACtB,4DAA4D;;;;;;YAwGxD,wEAAwE;;;;QAM5E,gEAAgE;2BAC7C,OAAO;;oBAhHZ,MAAM,EAAE;YACtB,4DAA4D;;;;;;;;;CAyH/D,CAAC"}
1
+ {"version":3,"file":"reduxSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/redux/sync/reduxSyncAdapter.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AA4D3C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB;;IAE3B;;;;;;;;OAQG;;;;;;;;;;;;;;;;;;;;;0BAsBmB,MAAM,IAAI;;;QAK9B;;;;WAIG;kCACuB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA6CjC;;;WAGG;2BACgB,OAAO;;oBAjHZ,MAAM,EAAE;YACtB,4DAA4D;;;;;;YA4HxD,wEAAwE;;;;QAM5E,gEAAgE;2BAC7C,OAAO;;oBApIZ,MAAM,EAAE;YACtB,4DAA4D;;;;;;;;;CA6I/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;AAEH,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;;;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,CA2BrE;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;IAuB5C;;;;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"}
1
+ {"version":3,"file":"reduxActionStore.d.ts","sourceRoot":"","sources":["../../../../src/redux/utils/reduxActionStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,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,CA2BrE;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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=snapshotProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshotProvider.d.ts","sourceRoot":"","sources":["../../../../src/redux/utils/snapshotProvider.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/redux",
3
- "version": "7.0.16",
3
+ "version": "7.0.18",
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",
@@ -26,12 +26,14 @@
26
26
  ],
27
27
  "sideEffects": [
28
28
  "**/devtoolsHook.js",
29
- "**/devtoolsHook.ts"
29
+ "**/devtoolsHook.ts",
30
+ "**/snapshotProvider.js",
31
+ "**/snapshotProvider.ts"
30
32
  ],
31
33
  "dependencies": {
32
- "@buoy-gg/shared-ui": "^7.0.16",
33
- "@buoy-gg/floating-tools-core": "^7.0.16",
34
- "@buoy-gg/license": "^7.0.16"
34
+ "@buoy-gg/shared-ui": "^7.0.18",
35
+ "@buoy-gg/license": "^7.0.18",
36
+ "@buoy-gg/floating-tools-core": "^7.0.18"
35
37
  },
36
38
  "peerDependencies": {
37
39
  "@reduxjs/toolkit": ">=1.9.0",