@buoy-gg/zustand 7.0.38 → 7.0.39

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.
Files changed (62) hide show
  1. package/lib/commonjs/index.js +105 -1
  2. package/lib/commonjs/preset.js +102 -1
  3. package/lib/commonjs/zustand/components/ZustandActionButton.js +118 -1
  4. package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +48 -1
  5. package/lib/commonjs/zustand/components/ZustandEventFilterView.js +43 -1
  6. package/lib/commonjs/zustand/components/ZustandIcon.js +18 -1
  7. package/lib/commonjs/zustand/components/ZustandModal.js +772 -1
  8. package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +117 -1
  9. package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +372 -1
  10. package/lib/commonjs/zustand/components/ZustandStateInfoView.js +488 -1
  11. package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +204 -1
  12. package/lib/commonjs/zustand/components/ZustandStoreDetailContent.js +471 -1
  13. package/lib/commonjs/zustand/components/ZustandStoreEditorScreen.js +393 -1
  14. package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +74 -1
  15. package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +382 -1
  16. package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +284 -1
  17. package/lib/commonjs/zustand/utils/editableState.js +146 -1
  18. package/lib/commonjs/zustand/utils/snapshotProvider.js +209 -1
  19. package/lib/commonjs/zustand/utils/testIDs.js +82 -1
  20. package/lib/commonjs/zustand/utils/zustandStateStore.js +379 -1
  21. package/lib/module/index.js +90 -1
  22. package/lib/module/preset.js +98 -1
  23. package/lib/module/zustand/components/ZustandActionButton.js +114 -1
  24. package/lib/module/zustand/components/ZustandDetailViewToggle.js +43 -1
  25. package/lib/module/zustand/components/ZustandEventFilterView.js +39 -1
  26. package/lib/module/zustand/components/ZustandIcon.js +11 -1
  27. package/lib/module/zustand/components/ZustandModal.js +767 -1
  28. package/lib/module/zustand/components/ZustandStateChangeItem.js +113 -1
  29. package/lib/module/zustand/components/ZustandStateDetailContent.js +368 -1
  30. package/lib/module/zustand/components/ZustandStateInfoView.js +483 -1
  31. package/lib/module/zustand/components/ZustandStoreBrowser.js +200 -1
  32. package/lib/module/zustand/components/ZustandStoreDetailContent.js +469 -1
  33. package/lib/module/zustand/components/ZustandStoreEditorScreen.js +389 -1
  34. package/lib/module/zustand/hooks/useZustandStateChanges.js +70 -1
  35. package/lib/module/zustand/sync/zustandSyncAdapter.js +380 -1
  36. package/lib/module/zustand/utils/buoyZustandMiddleware.js +278 -1
  37. package/lib/module/zustand/utils/editableState.js +138 -1
  38. package/lib/module/zustand/utils/snapshotProvider.js +209 -1
  39. package/lib/module/zustand/utils/testIDs.js +74 -1
  40. package/lib/module/zustand/utils/zustandStateStore.js +375 -1
  41. package/lib/typescript/index.d.ts.map +1 -0
  42. package/lib/typescript/preset.d.ts.map +1 -0
  43. package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +1 -0
  44. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +1 -0
  45. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +1 -0
  46. package/lib/typescript/zustand/components/ZustandIcon.d.ts.map +1 -0
  47. package/lib/typescript/zustand/components/ZustandModal.d.ts.map +1 -0
  48. package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +1 -0
  49. package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +1 -0
  50. package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -0
  51. package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +1 -0
  52. package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts.map +1 -0
  53. package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts.map +1 -0
  54. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +1 -0
  55. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -0
  56. package/lib/typescript/zustand/types/index.d.ts.map +1 -0
  57. package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +1 -0
  58. package/lib/typescript/zustand/utils/editableState.d.ts.map +1 -0
  59. package/lib/typescript/zustand/utils/snapshotProvider.d.ts.map +1 -0
  60. package/lib/typescript/zustand/utils/testIDs.d.ts.map +1 -0
  61. package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +1 -0
  62. package/package.json +3 -3
@@ -1 +1,471 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ZustandStoreDetailContent=ZustandStoreDetailContent;var _react=require("react"),_reactNative=require("react-native"),_sharedUi=require("@buoy-gg/shared-ui"),_dataViewer=require("@buoy-gg/shared-ui/dataViewer"),_editableState=require("../utils/editableState"),_testIDs=require("../utils/testIDs"),_jsxRuntime=require("react/jsx-runtime");const TYPING_COMMIT_MS=300,DETAIL_ID=`${_testIDs.ZUSTAND_TEST_ID}-detail`;function detailExplorerTestID(e){return 0===e.length?`${DETAIL_ID}-explorer-root`:`${DETAIL_ID}-explorer-${e.map(e=>e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")).filter(Boolean).join("-")}`}function ZustandStoreDetailContent({store:e,onViewHistory:t}){const i=(0,_testIDs.storeRowTestID)(e.name),[a,s]=(0,_react.useState)(0);(0,_react.useEffect)(()=>e.api.subscribe(()=>s(e=>e+1)),[e.api]);const r=e.api.getState(),o=(0,_react.useMemo)(()=>(0,_editableState.editableStateOf)(r),[r,a]),[n,l]=(0,_react.useState)(!1),d=(0,_react.useMemo)(()=>{if(null!==o.data)return{update:(t,i)=>{const a=e.api.getState();if(!(0,_editableState.isPlainObjectLike)(a)||0===t.length)return;const s=t[0],r=1===t.length?i:(0,_dataViewer.updateNestedDataByPath)(a[s],t.slice(1),i);e.api.setState({[s]:r},!1)},remove:t=>{const i=e.api.getState();if(!(0,_editableState.isPlainObjectLike)(i)||t.length<=1)return;const a=t[0],s=(0,_dataViewer.deleteNestedDataByPath)(i[a],t.slice(1));e.api.setState({[a]:s},!1)},canRemove:e=>e.length>1}},[e.api,null===o.data]),c=(0,_react.useCallback)(t=>{const i=(0,_editableState.editableStateOf)(e.api.getState());if(null===i.data)return;const a=(0,_editableState.computeMergePartial)(i.data,t);Object.keys(a).length>0&&e.api.setState(a,!1),l(!1)},[e.api]),h=(0,_react.useMemo)(()=>{if(r&&"object"==typeof r){const e={};for(const[t,i]of Object.entries(r))"function"!=typeof i&&(e[t]=i);return(0,_sharedUi.parseValue)(e)}return(0,_sharedUi.parseValue)(r)},[r,a]);return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.root,children:[(0,_jsxRuntime.jsxs)(_reactNative.ScrollView,{testID:`${DETAIL_ID}-scroll`,style:styles.scroll,contentContainerStyle:styles.scrollContent,keyboardShouldPersistTaps:"handled",children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.section,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.editorContainer,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.sectionHeaderRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{testID:`${DETAIL_ID}-editor-header`,style:styles.sectionHeaderText,children:"State Editor"}),null!==o.data?(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{onPress:()=>l(!0),testID:`${DETAIL_ID}-raw`,accessibilityRole:"button",accessibilityLabel:"Edit raw JSON",style:styles.rawButton,hitSlop:{top:6,bottom:6,left:6,right:6},children:[(0,_jsxRuntime.jsx)(_sharedUi.FileJson,{size:13,color:_sharedUi.night.color.accent}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.rawButtonText,children:"Raw"})]}):null]}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.sectionBody,children:null!==o.data?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_dataViewer.LiveExplorer,{editable:!0,label:"State",value:o.data,defaultExpanded:["State"],writer:d,dataVersion:a,testIDForPath:detailExplorerTestID,debounceMs:TYPING_COMMIT_MS}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.editorHint,children:"Edits write into the live store as you make them — merged, so the store's actions and untouched keys survive."})]}):(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.blockedText,testID:`${DETAIL_ID}-edit-blocked`,children:o.blockedReason})})]})}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.section,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoContainer,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.sectionHeaderTextPlain,children:"Store Details"}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoBody,children:[(0,_jsxRuntime.jsx)(_sharedUi.ExpandedInfoRow,{label:"Type",children:(0,_jsxRuntime.jsx)(_sharedUi.PillBadge,{color:e.color,testID:`${i}-type`,children:"ZUSTAND"})}),(0,_jsxRuntime.jsxs)(_sharedUi.ExpandedInfoRow,{label:"Backing",children:[(0,_jsxRuntime.jsx)(_sharedUi.PillBadge,{color:_sharedUi.buoyColors.info,icon:e.isPersisted?(0,_jsxRuntime.jsx)(_sharedUi.Database,{size:9,color:_sharedUi.buoyColors.info}):void 0,testID:`${i}-persist`,children:e.isPersisted?e.persistName||"persisted":"in-memory"}),e.isPersisted?(0,_jsxRuntime.jsx)(RereadSavedValue,{store:e,rowID:i}):null]}),e.stateChangeCount>0&&(0,_jsxRuntime.jsxs)(_sharedUi.ExpandedInfoRow,{label:"Changes",children:[(0,_jsxRuntime.jsx)(_sharedUi.PillBadge,{color:_sharedUi.buoyColors.warning,testID:`${i}-changes`,children:String(e.stateChangeCount)}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:()=>t(e.name),style:styles.inlineLinkButton,hitSlop:{top:6,bottom:6,left:6,right:6},testID:`${i}-history`,accessibilityRole:"button",accessibilityLabel:`View ${e.name} history`,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.inlineLinkText,{color:e.color}],children:"view history →"})})]}),o.fnPaths.length>0&&(0,_jsxRuntime.jsx)(_sharedUi.ExpandedInfoRow,{label:"Actions",children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.actionsText,testID:`${DETAIL_ID}-actions`,numberOfLines:2,children:o.fnPaths.join(" · ")})})]})]})}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.section,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoContainer,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.sectionHeaderTextPlain,children:"State Explorer"}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.viewerBody,testID:`${i}-state`,children:h&&"object"==typeof h?(0,_jsxRuntime.jsx)(_dataViewer.DataViewer,{title:"",data:h,showTypeFilter:!0,rawMode:!0,initialExpanded:!1}):(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.blockedText,children:"Empty state"})})]})})]}),n&&null!==o.data?(0,_jsxRuntime.jsx)(_dataViewer.ValueEditModal,{path:[],value:o.data,onSave:e=>{try{c(e)}catch{}},onCancel:()=>l(!1),saveLabel:"Apply",testIDPrefix:`${_testIDs.ZUSTAND_TEST_ID}-value`}):null]})}function RereadSavedValue({store:e,rowID:t}){const[i,a]=(0,_react.useState)(!1),[s,r]=(0,_react.useState)(null);return e.rehydrate?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:()=>{a(!0),r(null),(async()=>{try{await(e.rehydrate?.())}catch(e){r(e instanceof Error?e.message:String(e))}finally{a(!1)}})()},disabled:i,style:styles.inlineLinkButton,hitSlop:{top:6,bottom:6,left:6,right:6},testID:`${t}-reread`,accessibilityRole:"button",accessibilityLabel:`Re-read ${e.name} from storage`,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.inlineLinkText,{color:e.color}],children:i?"re-reading…":"re-read saved value →"})}),s?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.inlineLinkText,{color:_sharedUi.night.color.danger}],children:s}):null]}):null}const styles=_reactNative.StyleSheet.create({root:{flex:1,minHeight:0},scroll:{flex:1},scrollContent:{paddingHorizontal:8,paddingTop:8,paddingBottom:24},section:{marginBottom:16},editorContainer:{minWidth:200,backgroundColor:_sharedUi.night.color.surface,borderRadius:6,borderWidth:1,borderColor:_sharedUi.night.color.accent+"4D",overflow:"hidden",shadowColor:_sharedUi.night.color.accent,shadowOffset:{width:0,height:0},shadowOpacity:.1,shadowRadius:6},sectionHeaderRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",backgroundColor:_sharedUi.night.color.accent+"1A",paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:_sharedUi.night.color.accent+"33"},sectionHeaderText:{fontWeight:"600",fontSize:12,color:_sharedUi.night.color.accent,letterSpacing:.5,textTransform:"uppercase",fontFamily:_sharedUi.monoFont},rawButton:{flexDirection:"row",alignItems:"center",gap:4},rawButtonText:{color:_sharedUi.night.color.accent,fontSize:11,fontWeight:"700"},sectionBody:{padding:8},editorHint:{color:_sharedUi.night.color.textTertiary,fontSize:10,paddingTop:8,paddingHorizontal:4,lineHeight:14},blockedText:{color:_sharedUi.night.color.textSecondary,fontSize:12,padding:12,lineHeight:18},infoContainer:{minWidth:200,backgroundColor:_sharedUi.night.color.surface,borderRadius:6,borderWidth:1,borderColor:_sharedUi.night.color.border,overflow:"hidden"},sectionHeaderTextPlain:{backgroundColor:_sharedUi.night.color.surfaceElevated,paddingHorizontal:12,paddingVertical:8,fontWeight:"600",fontSize:12,color:_sharedUi.night.color.textSecondary,borderBottomWidth:1,borderBottomColor:_sharedUi.night.color.border,letterSpacing:.5,textTransform:"uppercase",fontFamily:_sharedUi.monoFont},infoBody:{padding:12,gap:10},viewerBody:{backgroundColor:_sharedUi.night.color.bg,minHeight:60},inlineLinkButton:{marginLeft:4},inlineLinkText:{fontSize:10,fontWeight:"600",fontFamily:_sharedUi.monoFont},actionsText:{flex:1,color:_sharedUi.night.color.textSecondary,fontSize:10,fontFamily:_sharedUi.monoFont,lineHeight:15}});
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ZustandStoreDetailContent = ZustandStoreDetailContent;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
11
+ var _editableState = require("../utils/editableState");
12
+ var _testIDs = require("../utils/testIDs");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ /**
15
+ * One store, on a screen of its own — the React Query detail-screen shape
16
+ * pointed at a live zustand store.
17
+ *
18
+ * Tapping a store card opens this instead of expanding in place. Top to
19
+ * bottom, mirroring RQ's `DataEditorMode`:
20
+ *
21
+ * 1. STATE EDITOR — the shared LiveExplorer. No draft, no Save: an edit
22
+ * writes into the live store as you make it and the app reacts
23
+ * immediately. Typing commits on a short debounce (~300ms, flushed on
24
+ * blur) so a word is one recorded change, not one per keystroke —
25
+ * zustand writes land in the change log and count against the free
26
+ * tier's cap, which is the one way this tool differs from RQ's
27
+ * unlogged cache writes.
28
+ * 2. STORE DETAILS — what the expanded card used to say: backing,
29
+ * change count (with the road to history), the store's actions.
30
+ * 3. STATE EXPLORER — the read-only tree of the same state.
31
+ *
32
+ * THE WRITE RULE (unchanged from the previous editor — see editableState.ts):
33
+ * merge-only, always. However deep the edit, it is written as
34
+ * `setState({ [topKey]: rewritten }, false)`, which cannot delete a top-level
35
+ * key and cannot touch the action functions stores keep in state. The
36
+ * explorer's delete affordance is therefore vetoed at depth 1 via
37
+ * `canRemove`, and the Raw escape hatch (whole-state JSON, for the add-key /
38
+ * restructure cases live editing can't express) refuses drops through
39
+ * `computeMergePartial`, naming the key.
40
+ *
41
+ * On a desktop mirror the same writes go over the wire: the mirrored store's
42
+ * `api.setState` forwards to the device's sync adapter (the time-travel
43
+ * path), fire-and-forget, with the next snapshot as confirmation.
44
+ */
45
+
46
+ /** How long typed edits coalesce before the single merge write fires. */const TYPING_COMMIT_MS = 300;
47
+
48
+ /** `buoy-zustand-detail-…` — this screen's own handle root. */
49
+ const DETAIL_ID = `${_testIDs.ZUSTAND_TEST_ID}-detail`;
50
+ function detailExplorerTestID(dataPath) {
51
+ const slug = part => part.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
52
+ return dataPath.length === 0 ? `${DETAIL_ID}-explorer-root` : `${DETAIL_ID}-explorer-${dataPath.map(slug).filter(Boolean).join("-")}`;
53
+ }
54
+ function ZustandStoreDetailContent({
55
+ store,
56
+ onViewHistory
57
+ }) {
58
+ const rowID = (0, _testIDs.storeRowTestID)(store.name);
59
+
60
+ // Re-render on every store write — our own edits included — so the tree,
61
+ // the read-only explorer, and the change count all track the live store.
62
+ // On a desktop mirror the stub subscribe is a no-op and the re-render
63
+ // arrives via the stores snapshot refreshing this component's props.
64
+ const [version, setVersion] = (0, _react.useState)(0);
65
+ (0, _react.useEffect)(() => store.api.subscribe(() => setVersion(v => v + 1)), [store.api]);
66
+ const state = store.api.getState();
67
+ // Recomputed per write on purpose — this screen edits LIVE state, so every
68
+ // view of it derives from the store's current answer, never a seed.
69
+ const editable = (0, _react.useMemo)(() => (0, _editableState.editableStateOf)(state),
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ [state, version]);
72
+ const [rawOpen, setRawOpen] = (0, _react.useState)(false);
73
+ const writer = (0, _react.useMemo)(() => {
74
+ if (editable.data === null) return undefined;
75
+ return {
76
+ update: (path, value) => {
77
+ const current = store.api.getState();
78
+ if (!(0, _editableState.isPlainObjectLike)(current) || path.length === 0) return;
79
+ const topKey = path[0];
80
+ const nextTop = path.length === 1 ? value : (0, _dataViewer.updateNestedDataByPath)(
81
+ // The live value, not the stripped view — although below the
82
+ // top level they are the same thing (a store with functions
83
+ // nested in data keys is refused editing entirely).
84
+ current[topKey], path.slice(1), value);
85
+ store.api.setState({
86
+ [topKey]: nextTop
87
+ }, false);
88
+ },
89
+ remove: path => {
90
+ // Depth 1 is vetoed by canRemove; this guard is the belt to that
91
+ // suspender — a merge write cannot delete a top-level key.
92
+ const current = store.api.getState();
93
+ if (!(0, _editableState.isPlainObjectLike)(current) || path.length <= 1) return;
94
+ const topKey = path[0];
95
+ const nextTop = (0, _dataViewer.deleteNestedDataByPath)(current[topKey], path.slice(1));
96
+ store.api.setState({
97
+ [topKey]: nextTop
98
+ }, false);
99
+ },
100
+ canRemove: path => path.length > 1
101
+ };
102
+ }, [store.api, editable.data === null]);
103
+
104
+ /** Raw-hatch save: whole-document edit diffed into one merge write. */
105
+ const applyRaw = (0, _react.useCallback)(next => {
106
+ const current = (0, _editableState.editableStateOf)(store.api.getState());
107
+ if (current.data === null) return;
108
+ const partial = (0, _editableState.computeMergePartial)(current.data, next);
109
+ if (Object.keys(partial).length > 0) {
110
+ store.api.setState(partial, false);
111
+ }
112
+ setRawOpen(false);
113
+ }, [store.api]);
114
+ const displayState = (0, _react.useMemo)(() => {
115
+ if (state && typeof state === "object") {
116
+ const filtered = {};
117
+ for (const [key, value] of Object.entries(state)) {
118
+ if (typeof value !== "function") filtered[key] = value;
119
+ }
120
+ return (0, _sharedUi.parseValue)(filtered);
121
+ }
122
+ return (0, _sharedUi.parseValue)(state);
123
+ // eslint-disable-next-line react-hooks/exhaustive-deps
124
+ }, [state, version]);
125
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
126
+ style: styles.root,
127
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
128
+ testID: `${DETAIL_ID}-scroll`,
129
+ style: styles.scroll,
130
+ contentContainerStyle: styles.scrollContent,
131
+ keyboardShouldPersistTaps: "handled",
132
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
133
+ style: styles.section,
134
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
135
+ style: styles.editorContainer,
136
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
137
+ style: styles.sectionHeaderRow,
138
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
139
+ testID: `${DETAIL_ID}-editor-header`,
140
+ style: styles.sectionHeaderText,
141
+ children: "State Editor"
142
+ }), editable.data !== null ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
143
+ onPress: () => setRawOpen(true),
144
+ testID: `${DETAIL_ID}-raw`,
145
+ accessibilityRole: "button",
146
+ accessibilityLabel: "Edit raw JSON",
147
+ style: styles.rawButton,
148
+ hitSlop: {
149
+ top: 6,
150
+ bottom: 6,
151
+ left: 6,
152
+ right: 6
153
+ },
154
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
155
+ size: 13,
156
+ color: _sharedUi.night.color.accent
157
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
158
+ style: styles.rawButtonText,
159
+ children: "Raw"
160
+ })]
161
+ }) : null]
162
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
163
+ style: styles.sectionBody,
164
+ children: editable.data !== null ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
165
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.LiveExplorer, {
166
+ editable: true,
167
+ label: "State",
168
+ value: editable.data,
169
+ defaultExpanded: ["State"],
170
+ writer: writer,
171
+ dataVersion: version,
172
+ testIDForPath: detailExplorerTestID,
173
+ debounceMs: TYPING_COMMIT_MS
174
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
175
+ style: styles.editorHint,
176
+ children: "Edits write into the live store as you make them \u2014 merged, so the store's actions and untouched keys survive."
177
+ })]
178
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
179
+ style: styles.blockedText,
180
+ testID: `${DETAIL_ID}-edit-blocked`,
181
+ children: editable.blockedReason
182
+ })
183
+ })]
184
+ })
185
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
186
+ style: styles.section,
187
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
188
+ style: styles.infoContainer,
189
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
190
+ style: styles.sectionHeaderTextPlain,
191
+ children: "Store Details"
192
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
193
+ style: styles.infoBody,
194
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ExpandedInfoRow, {
195
+ label: "Type",
196
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
197
+ color: store.color,
198
+ testID: `${rowID}-type`,
199
+ children: "ZUSTAND"
200
+ })
201
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ExpandedInfoRow, {
202
+ label: "Backing",
203
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
204
+ color: _sharedUi.buoyColors.info,
205
+ icon: store.isPersisted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Database, {
206
+ size: 9,
207
+ color: _sharedUi.buoyColors.info
208
+ }) : undefined,
209
+ testID: `${rowID}-persist`,
210
+ children: store.isPersisted ? store.persistName || "persisted" : "in-memory"
211
+ }), store.isPersisted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(RereadSavedValue, {
212
+ store: store,
213
+ rowID: rowID
214
+ }) : null]
215
+ }), store.stateChangeCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ExpandedInfoRow, {
216
+ label: "Changes",
217
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
218
+ color: _sharedUi.buoyColors.warning,
219
+ testID: `${rowID}-changes`,
220
+ children: String(store.stateChangeCount)
221
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
222
+ onPress: () => onViewHistory(store.name),
223
+ style: styles.inlineLinkButton,
224
+ hitSlop: {
225
+ top: 6,
226
+ bottom: 6,
227
+ left: 6,
228
+ right: 6
229
+ },
230
+ testID: `${rowID}-history`,
231
+ accessibilityRole: "button",
232
+ accessibilityLabel: `View ${store.name} history`,
233
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
234
+ style: [styles.inlineLinkText, {
235
+ color: store.color
236
+ }],
237
+ children: "view history \u2192"
238
+ })
239
+ })]
240
+ }), editable.fnPaths.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ExpandedInfoRow, {
241
+ label: "Actions",
242
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
243
+ style: styles.actionsText,
244
+ testID: `${DETAIL_ID}-actions`,
245
+ numberOfLines: 2,
246
+ children: editable.fnPaths.join(" · ")
247
+ })
248
+ })]
249
+ })]
250
+ })
251
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
252
+ style: styles.section,
253
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
254
+ style: styles.infoContainer,
255
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
256
+ style: styles.sectionHeaderTextPlain,
257
+ children: "State Explorer"
258
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
259
+ style: styles.viewerBody,
260
+ testID: `${rowID}-state`,
261
+ children: displayState && typeof displayState === "object" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
262
+ title: "",
263
+ data: displayState,
264
+ showTypeFilter: true,
265
+ rawMode: true,
266
+ initialExpanded: false
267
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
268
+ style: styles.blockedText,
269
+ children: "Empty state"
270
+ })
271
+ })]
272
+ })
273
+ })]
274
+ }), rawOpen && editable.data !== null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.ValueEditModal, {
275
+ path: [],
276
+ value: editable.data,
277
+ onSave: next => {
278
+ try {
279
+ applyRaw(next);
280
+ } catch {
281
+ // computeMergePartial refuses dropped top-level keys; the modal
282
+ // stays open with its own validation, so just don't close it.
283
+ }
284
+ },
285
+ onCancel: () => setRawOpen(false),
286
+ saveLabel: "Apply",
287
+ testIDPrefix: `${_testIDs.ZUSTAND_TEST_ID}-value`
288
+ }) : null]
289
+ });
290
+ }
291
+
292
+ /**
293
+ * Pull this store's saved value back into memory.
294
+ *
295
+ * Lives on the store rather than in the Storage tool because here there is
296
+ * nothing to infer: `store.rehydrate` IS the store's own `persist.rehydrate`.
297
+ * The merge keeps the store's action functions, and it does not write back
298
+ * unless a version migration ran. A store registered before its persist
299
+ * middleware attached has no handle — then there is simply no button.
300
+ */
301
+ function RereadSavedValue({
302
+ store,
303
+ rowID
304
+ }) {
305
+ const [busy, setBusy] = (0, _react.useState)(false);
306
+ const [error, setError] = (0, _react.useState)(null);
307
+ if (!store.rehydrate) return null;
308
+ const reread = () => {
309
+ setBusy(true);
310
+ setError(null);
311
+ void (async () => {
312
+ try {
313
+ await store.rehydrate?.();
314
+ } catch (e) {
315
+ setError(e instanceof Error ? e.message : String(e));
316
+ } finally {
317
+ setBusy(false);
318
+ }
319
+ })();
320
+ };
321
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
322
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
323
+ onPress: reread,
324
+ disabled: busy,
325
+ style: styles.inlineLinkButton,
326
+ hitSlop: {
327
+ top: 6,
328
+ bottom: 6,
329
+ left: 6,
330
+ right: 6
331
+ },
332
+ testID: `${rowID}-reread`,
333
+ accessibilityRole: "button",
334
+ accessibilityLabel: `Re-read ${store.name} from storage`,
335
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
336
+ style: [styles.inlineLinkText, {
337
+ color: store.color
338
+ }],
339
+ children: busy ? "re-reading…" : "re-read saved value →"
340
+ })
341
+ }), error ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
342
+ style: [styles.inlineLinkText, {
343
+ color: _sharedUi.night.color.danger
344
+ }],
345
+ children: error
346
+ }) : null]
347
+ });
348
+ }
349
+ const styles = _reactNative.StyleSheet.create({
350
+ root: {
351
+ flex: 1,
352
+ minHeight: 0
353
+ },
354
+ scroll: {
355
+ flex: 1
356
+ },
357
+ scrollContent: {
358
+ paddingHorizontal: 8,
359
+ paddingTop: 8,
360
+ paddingBottom: 24
361
+ },
362
+ section: {
363
+ marginBottom: 16
364
+ },
365
+ // The editable section wears the accent, like RQ's Data Editor — the one
366
+ // box on this screen that changes things should read differently from the
367
+ // two that don't.
368
+ editorContainer: {
369
+ minWidth: 200,
370
+ backgroundColor: _sharedUi.night.color.surface,
371
+ borderRadius: 6,
372
+ borderWidth: 1,
373
+ borderColor: _sharedUi.night.color.accent + "4D",
374
+ overflow: "hidden",
375
+ shadowColor: _sharedUi.night.color.accent,
376
+ shadowOffset: {
377
+ width: 0,
378
+ height: 0
379
+ },
380
+ shadowOpacity: 0.1,
381
+ shadowRadius: 6
382
+ },
383
+ sectionHeaderRow: {
384
+ flexDirection: "row",
385
+ alignItems: "center",
386
+ justifyContent: "space-between",
387
+ backgroundColor: _sharedUi.night.color.accent + "1A",
388
+ paddingHorizontal: 12,
389
+ paddingVertical: 8,
390
+ borderBottomWidth: 1,
391
+ borderBottomColor: _sharedUi.night.color.accent + "33"
392
+ },
393
+ sectionHeaderText: {
394
+ fontWeight: "600",
395
+ fontSize: 12,
396
+ color: _sharedUi.night.color.accent,
397
+ letterSpacing: 0.5,
398
+ textTransform: "uppercase",
399
+ fontFamily: _sharedUi.monoFont
400
+ },
401
+ rawButton: {
402
+ flexDirection: "row",
403
+ alignItems: "center",
404
+ gap: 4
405
+ },
406
+ rawButtonText: {
407
+ color: _sharedUi.night.color.accent,
408
+ fontSize: 11,
409
+ fontWeight: "700"
410
+ },
411
+ sectionBody: {
412
+ padding: 8
413
+ },
414
+ editorHint: {
415
+ color: _sharedUi.night.color.textTertiary,
416
+ fontSize: 10,
417
+ paddingTop: 8,
418
+ paddingHorizontal: 4,
419
+ lineHeight: 14
420
+ },
421
+ blockedText: {
422
+ color: _sharedUi.night.color.textSecondary,
423
+ fontSize: 12,
424
+ padding: 12,
425
+ lineHeight: 18
426
+ },
427
+ infoContainer: {
428
+ minWidth: 200,
429
+ backgroundColor: _sharedUi.night.color.surface,
430
+ borderRadius: 6,
431
+ borderWidth: 1,
432
+ borderColor: _sharedUi.night.color.border,
433
+ overflow: "hidden"
434
+ },
435
+ sectionHeaderTextPlain: {
436
+ backgroundColor: _sharedUi.night.color.surfaceElevated,
437
+ paddingHorizontal: 12,
438
+ paddingVertical: 8,
439
+ fontWeight: "600",
440
+ fontSize: 12,
441
+ color: _sharedUi.night.color.textSecondary,
442
+ borderBottomWidth: 1,
443
+ borderBottomColor: _sharedUi.night.color.border,
444
+ letterSpacing: 0.5,
445
+ textTransform: "uppercase",
446
+ fontFamily: _sharedUi.monoFont
447
+ },
448
+ infoBody: {
449
+ padding: 12,
450
+ gap: 10
451
+ },
452
+ viewerBody: {
453
+ backgroundColor: _sharedUi.night.color.bg,
454
+ minHeight: 60
455
+ },
456
+ inlineLinkButton: {
457
+ marginLeft: 4
458
+ },
459
+ inlineLinkText: {
460
+ fontSize: 10,
461
+ fontWeight: "600",
462
+ fontFamily: _sharedUi.monoFont
463
+ },
464
+ actionsText: {
465
+ flex: 1,
466
+ color: _sharedUi.night.color.textSecondary,
467
+ fontSize: 10,
468
+ fontFamily: _sharedUi.monoFont,
469
+ lineHeight: 15
470
+ }
471
+ });