@buoy-gg/redux 7.0.10 → 7.0.12

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.
@@ -48,7 +48,8 @@ function ReduxModal({
48
48
  onBack,
49
49
  onMinimize,
50
50
  enableSharedModalDimensions = false,
51
- inline = false
51
+ inline = false,
52
+ onActionSelect
52
53
  }) {
53
54
  const isPro = (0, _license.useIsPro)();
54
55
  const [showProModal, setShowProModal] = (0, _react.useState)(false);
@@ -93,6 +94,17 @@ function ReduxModal({
93
94
 
94
95
  // Get selected action from index
95
96
  const selectedAction = selectedActionIndex !== null ? visibleActions[selectedActionIndex] : null;
97
+
98
+ // Notify the host on selection (keyed by action id so re-renders with the
99
+ // same selection don't re-fire; remote mirrors fetch state trees here).
100
+ const lastNotifiedSelectionRef = (0, _react.useRef)(null);
101
+ (0, _react.useEffect)(() => {
102
+ if (!onActionSelect) return;
103
+ const id = selectedAction?.id ?? null;
104
+ if (id === null || id === lastNotifiedSelectionRef.current) return;
105
+ lastNotifiedSelectionRef.current = id;
106
+ onActionSelect(selectedAction);
107
+ }, [selectedAction, onActionSelect]);
96
108
  const [searchText, setSearchText] = (0, _react.useState)("");
97
109
  const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
98
110
  const searchInputRef = (0, _react.useRef)(null);
@@ -6,6 +6,15 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.reduxSyncAdapter = void 0;
7
7
  var _reduxActionStore = require("../utils/reduxActionStore");
8
8
  var _autoInstrument = require("../utils/autoInstrument");
9
+ /**
10
+ * Upper bound for one on-demand action-detail response. Generous — this is a
11
+ * single explicit fetch, not a per-action stream — but still guarded so a
12
+ * pathological tree can't OOM the device through the reply emit.
13
+ */
14
+ const MAX_ACTION_DETAIL_BYTES = 8 * 1024 * 1024;
15
+
16
+ /** Per-action budget for `payload`/`action` on the snapshot wire. */
17
+ const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
9
18
  /** Top-level slice names of the state tree, for the compact (no-values) view. */
10
19
  function topLevelKeys(state) {
11
20
  return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
@@ -43,13 +52,82 @@ function unavailable() {
43
52
  * action-log snapshot.
44
53
  */
45
54
  const reduxSyncAdapter = exports.reduxSyncAdapter = {
46
- version: 1,
47
- getSnapshot: () => _reduxActionStore.reduxActionStore.getActions(),
55
+ version: 2,
56
+ /**
57
+ * WIRE-FORM snapshot: actions WITHOUT raw prevState/nextState trees.
58
+ * Shipping the trees serialized up to 25×2 full state trees through
59
+ * socket.emit per action — no guard on that path — which froze and
60
+ * native-OOM-crashed enterprise-scale apps whenever a dashboard was
61
+ * connected (root cause confirmed by patch-and-retest). Dashboards fetch
62
+ * trees on demand via `getActionDetail`; already-evicted entries keep the
63
+ * eviction sentinel so consumers can distinguish "fetchable" from "gone".
64
+ */
65
+ getSnapshot: () => _reduxActionStore.reduxActionStore.getActions().map(a => {
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
+ }),
48
84
  subscribe: onChange => _reduxActionStore.reduxActionStore.subscribe(onChange),
49
85
  actions: {
50
86
  clearEvents: () => {
51
87
  _reduxActionStore.reduxActionStore.clearActions();
52
88
  },
89
+ /**
90
+ * On-demand detail: one action's real prevState/nextState trees.
91
+ * The per-snapshot stream stays tree-free; this is the explicit,
92
+ * size-guarded channel for the detail pane.
93
+ */
94
+ getActionDetail: params => {
95
+ const {
96
+ id
97
+ } = params ?? {};
98
+ if (!id) return {
99
+ found: false,
100
+ reason: "missing id"
101
+ };
102
+ const action = _reduxActionStore.reduxActionStore.getActionById(id);
103
+ if (!action) return {
104
+ found: false,
105
+ reason: "unknown id"
106
+ };
107
+ if ((0, _reduxActionStore.isEvictedState)(action.prevState)) {
108
+ return {
109
+ found: true,
110
+ id,
111
+ evicted: true,
112
+ reason: `Raw state was evicted (retained for recent actions only).`
113
+ };
114
+ }
115
+ // Per-field cap: oversized fields degrade individually instead of
116
+ // failing the whole detail (a giant payload shouldn't hide the trees).
117
+ const cappedNote = {
118
+ __buoyTruncated: true,
119
+ note: `Exceeds the ${MAX_ACTION_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
120
+ };
121
+ const cap = value => (0, _reduxActionStore.jsonLongerThan)(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
122
+ return {
123
+ found: true,
124
+ id,
125
+ prevState: cap(action.prevState),
126
+ nextState: cap(action.nextState),
127
+ payload: cap(action.payload),
128
+ action: cap(action.action)
129
+ };
130
+ },
53
131
  /**
54
132
  * Compact current-state reader: the instrumented store's slice names, and
55
133
  * the full state tree only when `includeValues` is set (heavy).
@@ -3,8 +3,9 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.STATE_RETENTION_LIMIT = exports.STATE_EVICTED = void 0;
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.jsonLongerThan = jsonLongerThan;
8
9
  exports.reduxActionStore = void 0;
9
10
  /**
10
11
  * Redux action store - captures and stores Redux Toolkit actions
@@ -26,9 +27,39 @@ const STATE_EVICTED = exports.STATE_EVICTED = Object.freeze({
26
27
  note: `Raw state retained for the ${STATE_RETENTION_LIMIT} most recent actions only.`
27
28
  });
28
29
 
29
- /** True when a value is the evicted-state sentinel (guards time travel). */
30
+ /**
31
+ * Sentinel used in WIRE-FORM snapshots (sync to desktop/MCP) in place of raw
32
+ * prevState/nextState trees. Unlike STATE_EVICTED the trees still exist on
33
+ * the device — dashboards fetch them on demand via the adapter's
34
+ * `getActionDetail` action. Shipping them in every snapshot serialized up to
35
+ * 25×2 full state trees through socket.emit per action (no guard), which
36
+ * froze and native-OOM-crashed enterprise-scale apps whenever a dashboard
37
+ * was connected (confirmed by patch-and-retest on the affected app).
38
+ */
39
+ const STATE_ON_DEVICE = exports.STATE_ON_DEVICE = Object.freeze({
40
+ __buoyStateOnDevice: true,
41
+ note: "State trees stay on the device — fetched on demand via getActionDetail."
42
+ });
43
+
44
+ /**
45
+ * Wire-form sentinel for an oversized action `payload`/`action`. A dispatched
46
+ * payload can itself be huge (rehydration actions carry the whole new state),
47
+ * so the per-snapshot stream ships `payloadPreview` + this marker instead;
48
+ * `getActionDetail` serves the real value on demand.
49
+ */
50
+ const PAYLOAD_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = Object.freeze({
51
+ __buoyPayloadOnDevice: true,
52
+ note: "Payload stays on the device — fetched on demand via getActionDetail."
53
+ });
54
+
55
+ /**
56
+ * True when a value is one of the state sentinels (evicted or on-device) —
57
+ * guards time travel and any consumer that needs a REAL tree.
58
+ */
30
59
  function isEvictedState(value) {
31
- return typeof value === "object" && value !== null && value.__buoyStateEvicted === true;
60
+ if (typeof value !== "object" || value === null) return false;
61
+ const v = value;
62
+ return v.__buoyStateEvicted === true || v.__buoyStateOnDevice === true;
32
63
  }
33
64
 
34
65
  // ============================================
@@ -356,7 +387,39 @@ class ReduxActionStore {
356
387
  if (!this.isEnabled) {
357
388
  return;
358
389
  }
359
- this.actions = actions.slice(0, this.maxActions);
390
+ // Preserve any trees a remote mirror already hydrated via
391
+ // replaceActionStates — wire-form snapshots always carry markers, and a
392
+ // full snapshot arriving after a detail fetch must not blank the detail
393
+ // pane back out.
394
+ const hydrated = new Map();
395
+ for (const existing of this.actions) {
396
+ if (existing.prevState !== undefined && !isEvictedState(existing.prevState)) {
397
+ hydrated.set(existing.id, existing);
398
+ }
399
+ }
400
+ this.actions = actions.slice(0, this.maxActions).map(a => {
401
+ const h = hydrated.get(a.id);
402
+ return h && isEvictedState(a.prevState) ? {
403
+ ...a,
404
+ prevState: h.prevState,
405
+ nextState: h.nextState
406
+ } : a;
407
+ });
408
+ this.notifyListeners();
409
+ }
410
+
411
+ /**
412
+ * Hydrate one mirrored action with real values (state trees, payload,
413
+ * action) fetched on demand from the device via the adapter's
414
+ * `getActionDetail` action. Remote-mirror (desktop) use only.
415
+ */
416
+ hydrateAction(id, patch) {
417
+ const index = this.actions.findIndex(a => a.id === id);
418
+ if (index === -1) return;
419
+ this.actions = [...this.actions.slice(0, index), {
420
+ ...this.actions[index],
421
+ ...patch
422
+ }, ...this.actions.slice(index + 1)];
360
423
  this.notifyListeners();
361
424
  }
362
425
  setEnabled(enabled) {
@@ -7,7 +7,7 @@
7
7
  * providing zero-config action capture for the DevTools.
8
8
  */
9
9
 
10
- import { useState, useMemo, useRef, useCallback } from "react";
10
+ import { useState, useMemo, useRef, useCallback, useEffect } from "react";
11
11
  import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity } from "react-native";
12
12
  import { JsModal, ModalHeader, macOSColors, buoyColors, Search, Trash2, Power, X, Zap, CheckCircle, Copy, Lock, AlertTriangle, devToolsStorageKeys, copyToClipboard, ProUpgradeModal, PowerToggleButton, TickProvider } from "@buoy-gg/shared-ui";
13
13
  import { useIsPro } from "@buoy-gg/license";
@@ -42,7 +42,8 @@ export function ReduxModal({
42
42
  onBack,
43
43
  onMinimize,
44
44
  enableSharedModalDimensions = false,
45
- inline = false
45
+ inline = false,
46
+ onActionSelect
46
47
  }) {
47
48
  const isPro = useIsPro();
48
49
  const [showProModal, setShowProModal] = useState(false);
@@ -87,6 +88,17 @@ export function ReduxModal({
87
88
 
88
89
  // Get selected action from index
89
90
  const selectedAction = selectedActionIndex !== null ? visibleActions[selectedActionIndex] : null;
91
+
92
+ // Notify the host on selection (keyed by action id so re-renders with the
93
+ // same selection don't re-fire; remote mirrors fetch state trees here).
94
+ const lastNotifiedSelectionRef = useRef(null);
95
+ useEffect(() => {
96
+ if (!onActionSelect) return;
97
+ const id = selectedAction?.id ?? null;
98
+ if (id === null || id === lastNotifiedSelectionRef.current) return;
99
+ lastNotifiedSelectionRef.current = id;
100
+ onActionSelect(selectedAction);
101
+ }, [selectedAction, onActionSelect]);
90
102
  const [searchText, setSearchText] = useState("");
91
103
  const [isSearchActive, setIsSearchActive] = useState(false);
92
104
  const searchInputRef = useRef(null);
@@ -1,7 +1,17 @@
1
1
  "use strict";
2
2
 
3
- import { reduxActionStore } from "../utils/reduxActionStore";
3
+ import { reduxActionStore, STATE_ON_DEVICE, PAYLOAD_ON_DEVICE, isEvictedState, jsonLongerThan } from "../utils/reduxActionStore";
4
4
  import { getActiveStore, getInstrumentationStatus } from "../utils/autoInstrument";
5
+
6
+ /**
7
+ * Upper bound for one on-demand action-detail response. Generous — this is a
8
+ * single explicit fetch, not a per-action stream — but still guarded so a
9
+ * pathological tree can't OOM the device through the reply emit.
10
+ */
11
+ const MAX_ACTION_DETAIL_BYTES = 8 * 1024 * 1024;
12
+
13
+ /** Per-action budget for `payload`/`action` on the snapshot wire. */
14
+ const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
5
15
  /** Top-level slice names of the state tree, for the compact (no-values) view. */
6
16
  function topLevelKeys(state) {
7
17
  return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
@@ -39,13 +49,82 @@ function unavailable() {
39
49
  * action-log snapshot.
40
50
  */
41
51
  export const reduxSyncAdapter = {
42
- version: 1,
43
- getSnapshot: () => reduxActionStore.getActions(),
52
+ version: 2,
53
+ /**
54
+ * WIRE-FORM snapshot: actions WITHOUT raw prevState/nextState trees.
55
+ * Shipping the trees serialized up to 25×2 full state trees through
56
+ * socket.emit per action — no guard on that path — which froze and
57
+ * native-OOM-crashed enterprise-scale apps whenever a dashboard was
58
+ * connected (root cause confirmed by patch-and-retest). Dashboards fetch
59
+ * trees on demand via `getActionDetail`; already-evicted entries keep the
60
+ * eviction sentinel so consumers can distinguish "fetchable" from "gone".
61
+ */
62
+ getSnapshot: () => reduxActionStore.getActions().map(a => {
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
+ }),
44
81
  subscribe: onChange => reduxActionStore.subscribe(onChange),
45
82
  actions: {
46
83
  clearEvents: () => {
47
84
  reduxActionStore.clearActions();
48
85
  },
86
+ /**
87
+ * On-demand detail: one action's real prevState/nextState trees.
88
+ * The per-snapshot stream stays tree-free; this is the explicit,
89
+ * size-guarded channel for the detail pane.
90
+ */
91
+ getActionDetail: params => {
92
+ const {
93
+ id
94
+ } = params ?? {};
95
+ if (!id) return {
96
+ found: false,
97
+ reason: "missing id"
98
+ };
99
+ const action = reduxActionStore.getActionById(id);
100
+ if (!action) return {
101
+ found: false,
102
+ reason: "unknown id"
103
+ };
104
+ if (isEvictedState(action.prevState)) {
105
+ return {
106
+ found: true,
107
+ id,
108
+ evicted: true,
109
+ reason: `Raw state was evicted (retained for recent actions only).`
110
+ };
111
+ }
112
+ // Per-field cap: oversized fields degrade individually instead of
113
+ // failing the whole detail (a giant payload shouldn't hide the trees).
114
+ const cappedNote = {
115
+ __buoyTruncated: true,
116
+ note: `Exceeds the ${MAX_ACTION_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
117
+ };
118
+ const cap = value => jsonLongerThan(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
119
+ return {
120
+ found: true,
121
+ id,
122
+ prevState: cap(action.prevState),
123
+ nextState: cap(action.nextState),
124
+ payload: cap(action.payload),
125
+ action: cap(action.action)
126
+ };
127
+ },
49
128
  /**
50
129
  * Compact current-state reader: the instrumented store's slice names, and
51
130
  * the full state tree only when `includeValues` is set (heavy).
@@ -20,9 +20,39 @@ export const STATE_EVICTED = Object.freeze({
20
20
  note: `Raw state retained for the ${STATE_RETENTION_LIMIT} most recent actions only.`
21
21
  });
22
22
 
23
- /** True when a value is the evicted-state sentinel (guards time travel). */
23
+ /**
24
+ * Sentinel used in WIRE-FORM snapshots (sync to desktop/MCP) in place of raw
25
+ * prevState/nextState trees. Unlike STATE_EVICTED the trees still exist on
26
+ * the device — dashboards fetch them on demand via the adapter's
27
+ * `getActionDetail` action. Shipping them in every snapshot serialized up to
28
+ * 25×2 full state trees through socket.emit per action (no guard), which
29
+ * froze and native-OOM-crashed enterprise-scale apps whenever a dashboard
30
+ * was connected (confirmed by patch-and-retest on the affected app).
31
+ */
32
+ export const STATE_ON_DEVICE = Object.freeze({
33
+ __buoyStateOnDevice: true,
34
+ note: "State trees stay on the device — fetched on demand via getActionDetail."
35
+ });
36
+
37
+ /**
38
+ * Wire-form sentinel for an oversized action `payload`/`action`. A dispatched
39
+ * payload can itself be huge (rehydration actions carry the whole new state),
40
+ * so the per-snapshot stream ships `payloadPreview` + this marker instead;
41
+ * `getActionDetail` serves the real value on demand.
42
+ */
43
+ export const PAYLOAD_ON_DEVICE = Object.freeze({
44
+ __buoyPayloadOnDevice: true,
45
+ note: "Payload stays on the device — fetched on demand via getActionDetail."
46
+ });
47
+
48
+ /**
49
+ * True when a value is one of the state sentinels (evicted or on-device) —
50
+ * guards time travel and any consumer that needs a REAL tree.
51
+ */
24
52
  export function isEvictedState(value) {
25
- return typeof value === "object" && value !== null && value.__buoyStateEvicted === true;
53
+ if (typeof value !== "object" || value === null) return false;
54
+ const v = value;
55
+ return v.__buoyStateEvicted === true || v.__buoyStateOnDevice === true;
26
56
  }
27
57
 
28
58
  // ============================================
@@ -85,7 +115,7 @@ function parseActionType(type) {
85
115
  * 40-char preview; that full stringify ran on EVERY dispatch and blocked the
86
116
  * JS thread at enterprise state sizes.
87
117
  */
88
- function jsonLongerThan(value, limit) {
118
+ export function jsonLongerThan(value, limit) {
89
119
  let bytes = 0;
90
120
  const stack = [value];
91
121
  const seen = new Set();
@@ -350,7 +380,39 @@ class ReduxActionStore {
350
380
  if (!this.isEnabled) {
351
381
  return;
352
382
  }
353
- this.actions = actions.slice(0, this.maxActions);
383
+ // Preserve any trees a remote mirror already hydrated via
384
+ // replaceActionStates — wire-form snapshots always carry markers, and a
385
+ // full snapshot arriving after a detail fetch must not blank the detail
386
+ // pane back out.
387
+ const hydrated = new Map();
388
+ for (const existing of this.actions) {
389
+ if (existing.prevState !== undefined && !isEvictedState(existing.prevState)) {
390
+ hydrated.set(existing.id, existing);
391
+ }
392
+ }
393
+ this.actions = actions.slice(0, this.maxActions).map(a => {
394
+ const h = hydrated.get(a.id);
395
+ return h && isEvictedState(a.prevState) ? {
396
+ ...a,
397
+ prevState: h.prevState,
398
+ nextState: h.nextState
399
+ } : a;
400
+ });
401
+ this.notifyListeners();
402
+ }
403
+
404
+ /**
405
+ * Hydrate one mirrored action with real values (state trees, payload,
406
+ * action) fetched on demand from the device via the adapter's
407
+ * `getActionDetail` action. Remote-mirror (desktop) use only.
408
+ */
409
+ hydrateAction(id, patch) {
410
+ const index = this.actions.findIndex(a => a.id === id);
411
+ if (index === -1) return;
412
+ this.actions = [...this.actions.slice(0, index), {
413
+ ...this.actions[index],
414
+ ...patch
415
+ }, ...this.actions.slice(index + 1)];
354
416
  this.notifyListeners();
355
417
  }
356
418
  setEnabled(enabled) {
@@ -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 { ReduxAction } from "../types";
7
8
  /** Free tier limit for action history */
8
9
  export declare const FREE_TIER_ACTION_LIMIT = 25;
9
10
  interface ReduxModalProps {
@@ -19,8 +20,14 @@ interface ReduxModalProps {
19
20
  onBack?: () => void;
20
21
  onMinimize?: (modalState: unknown) => void;
21
22
  enableSharedModalDimensions?: boolean;
23
+ /**
24
+ * Fired when the user selects an action (opens its detail view). Remote
25
+ * mirrors (Buoy Desktop) use this to fetch the action's real state trees
26
+ * on demand — wire-form snapshots carry markers instead of trees.
27
+ */
28
+ onActionSelect?: (action: ReduxAction) => void;
22
29
  }
23
- export declare function ReduxModal({ visible, onClose, onBack, onMinimize, enableSharedModalDimensions, inline, }: ReduxModalProps): import("react").JSX.Element | null;
30
+ export declare function ReduxModal({ visible, onClose, onBack, onMinimize, enableSharedModalDimensions, inline, onActionSelect, }: ReduxModalProps): import("react").JSX.Element | null;
24
31
  /**
25
32
  * The same Redux tool as `ReduxModal`, rendered INLINE for hosts that place
26
33
  * 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;AA2CH,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;CACvC;AAgBD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,MAAc,GACf,EAAE,eAAe,sCAwajB;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,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"}
@@ -16,10 +16,71 @@
16
16
  */
17
17
  export declare const reduxSyncAdapter: {
18
18
  version: number;
19
- getSnapshot: () => import("../types").ReduxAction[];
19
+ /**
20
+ * WIRE-FORM snapshot: actions WITHOUT raw prevState/nextState trees.
21
+ * Shipping the trees serialized up to 25×2 full state trees through
22
+ * socket.emit per action — no guard on that path — which froze and
23
+ * native-OOM-crashed enterprise-scale apps whenever a dashboard was
24
+ * connected (root cause confirmed by patch-and-retest). Dashboards fetch
25
+ * trees on demand via `getActionDetail`; already-evicted entries keep the
26
+ * eviction sentinel so consumers can distinguish "fetchable" from "gone".
27
+ */
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
+ }[];
20
48
  subscribe: (onChange: () => void) => () => void;
21
49
  actions: {
22
50
  clearEvents: () => void;
51
+ /**
52
+ * On-demand detail: one action's real prevState/nextState trees.
53
+ * The per-snapshot stream stays tree-free; this is the explicit,
54
+ * size-guarded channel for the detail pane.
55
+ */
56
+ getActionDetail: (params: unknown) => {
57
+ found: false;
58
+ reason: string;
59
+ id?: undefined;
60
+ evicted?: undefined;
61
+ prevState?: undefined;
62
+ nextState?: undefined;
63
+ payload?: undefined;
64
+ action?: undefined;
65
+ } | {
66
+ found: true;
67
+ id: string;
68
+ evicted: true;
69
+ reason: string;
70
+ prevState?: undefined;
71
+ nextState?: undefined;
72
+ payload?: undefined;
73
+ action?: undefined;
74
+ } | {
75
+ found: true;
76
+ id: string;
77
+ prevState: unknown;
78
+ nextState: unknown;
79
+ payload: unknown;
80
+ action: unknown;
81
+ reason?: undefined;
82
+ evicted?: undefined;
83
+ };
23
84
  /**
24
85
  * Compact current-state reader: the instrumented store's slice names, and
25
86
  * the full state tree only when `includeValues` is set (heavy).
@@ -1 +1 @@
1
- {"version":3,"file":"reduxSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/redux/sync/reduxSyncAdapter.ts"],"names":[],"mappings":"AAsCA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB;;;0BAGL,MAAM,IAAI;;;QAK9B;;;WAGG;2BACgB,OAAO;;oBAlCZ,MAAM,EAAE;YACtB,4DAA4D;;;;;;YA6CxD,wEAAwE;;;;QAM5E,gEAAgE;2BAC7C,OAAO;;oBArDZ,MAAM,EAAE;YACtB,4DAA4D;;;;;;;;;CA8D/D,CAAC"}
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"}
@@ -16,8 +16,43 @@ export declare const STATE_EVICTED: Readonly<{
16
16
  __buoyStateEvicted: true;
17
17
  note: "Raw state retained for the 25 most recent actions only.";
18
18
  }>;
19
- /** True when a value is the evicted-state sentinel (guards time travel). */
19
+ /**
20
+ * Sentinel used in WIRE-FORM snapshots (sync to desktop/MCP) in place of raw
21
+ * prevState/nextState trees. Unlike STATE_EVICTED the trees still exist on
22
+ * the device — dashboards fetch them on demand via the adapter's
23
+ * `getActionDetail` action. Shipping them in every snapshot serialized up to
24
+ * 25×2 full state trees through socket.emit per action (no guard), which
25
+ * froze and native-OOM-crashed enterprise-scale apps whenever a dashboard
26
+ * was connected (confirmed by patch-and-retest on the affected app).
27
+ */
28
+ export declare const STATE_ON_DEVICE: Readonly<{
29
+ __buoyStateOnDevice: true;
30
+ note: "State trees stay on the device — fetched on demand via getActionDetail.";
31
+ }>;
32
+ /**
33
+ * Wire-form sentinel for an oversized action `payload`/`action`. A dispatched
34
+ * payload can itself be huge (rehydration actions carry the whole new state),
35
+ * so the per-snapshot stream ships `payloadPreview` + this marker instead;
36
+ * `getActionDetail` serves the real value on demand.
37
+ */
38
+ export declare const PAYLOAD_ON_DEVICE: Readonly<{
39
+ __buoyPayloadOnDevice: true;
40
+ note: "Payload stays on the device — fetched on demand via getActionDetail.";
41
+ }>;
42
+ /**
43
+ * True when a value is one of the state sentinels (evicted or on-device) —
44
+ * guards time travel and any consumer that needs a REAL tree.
45
+ */
20
46
  export declare function isEvictedState(value: unknown): boolean;
47
+ /**
48
+ * Cheap early-abort size probe: is `value`'s JSON form definitely longer than
49
+ * `limit`? Bails the moment the running estimate exceeds the limit, so the
50
+ * cost is O(limit) — never O(payload). Used to avoid JSON.stringify-ing large
51
+ * payloads (a fulfilled thunk's payload can be megabytes) just to produce a
52
+ * 40-char preview; that full stringify ran on EVERY dispatch and blocked the
53
+ * JS thread at enterprise state sizes.
54
+ */
55
+ export declare function jsonLongerThan(value: unknown, limit: number): boolean;
21
56
  declare class ReduxActionStore {
22
57
  private actions;
23
58
  private listeners;
@@ -53,6 +88,12 @@ declare class ReduxActionStore {
53
88
  * the enabled flag so the UI's pause button freezes the mirror.
54
89
  */
55
90
  replaceActions(actions: ReduxAction[]): void;
91
+ /**
92
+ * Hydrate one mirrored action with real values (state trees, payload,
93
+ * action) fetched on demand from the device via the adapter's
94
+ * `getActionDetail` action. Remote-mirror (desktop) use only.
95
+ */
96
+ hydrateAction(id: string, patch: Partial<ReduxAction>): void;
56
97
  setEnabled(enabled: boolean): void;
57
98
  getEnabled(): boolean;
58
99
  subscribe(listener: (actions: ReduxAction[]) => void): () => void;
@@ -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,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMtD;AAyND,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;IAQ5C,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;;;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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/redux",
3
- "version": "7.0.10",
3
+ "version": "7.0.12",
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",
@@ -29,9 +29,9 @@
29
29
  "**/devtoolsHook.ts"
30
30
  ],
31
31
  "dependencies": {
32
- "@buoy-gg/shared-ui": "^7.0.10",
33
- "@buoy-gg/floating-tools-core": "^7.0.10",
34
- "@buoy-gg/license": "^7.0.10"
32
+ "@buoy-gg/shared-ui": "^7.0.12",
33
+ "@buoy-gg/floating-tools-core": "^7.0.12",
34
+ "@buoy-gg/license": "^7.0.12"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@reduxjs/toolkit": ">=1.9.0",