@buoy-gg/zustand 7.0.34 → 7.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/zustand/components/ZustandActionButton.js +1 -1
- package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +5 -5
- package/lib/commonjs/zustand/components/ZustandStateInfoView.js +6 -6
- package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +2 -2
- package/lib/commonjs/zustand/components/ZustandStoreDetailContent.js +4 -4
- package/lib/commonjs/zustand/components/ZustandStoreEditorScreen.js +3 -3
- package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +169 -6
- package/lib/module/zustand/components/ZustandActionButton.js +2 -2
- package/lib/module/zustand/components/ZustandStateDetailContent.js +6 -6
- package/lib/module/zustand/components/ZustandStateInfoView.js +7 -7
- package/lib/module/zustand/components/ZustandStoreBrowser.js +3 -3
- package/lib/module/zustand/components/ZustandStoreDetailContent.js +5 -5
- package/lib/module/zustand/components/ZustandStoreEditorScreen.js +4 -4
- package/lib/module/zustand/sync/zustandSyncAdapter.js +169 -5
- package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts.map +1 -1
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +33 -0
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -301,7 +301,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
301
301
|
valueLabel: {
|
|
302
302
|
fontSize: 10,
|
|
303
303
|
color: _sharedUi.night.color.textSecondary,
|
|
304
|
-
fontFamily:
|
|
304
|
+
fontFamily: _sharedUi.monoFont,
|
|
305
305
|
letterSpacing: 0.5,
|
|
306
306
|
fontWeight: "600"
|
|
307
307
|
},
|
|
@@ -318,7 +318,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
318
318
|
changeText: {
|
|
319
319
|
fontSize: 9,
|
|
320
320
|
fontWeight: "700",
|
|
321
|
-
fontFamily:
|
|
321
|
+
fontFamily: _sharedUi.monoFont,
|
|
322
322
|
letterSpacing: 0.3
|
|
323
323
|
},
|
|
324
324
|
typeBadge: {
|
|
@@ -331,7 +331,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
331
331
|
fontSize: 9,
|
|
332
332
|
fontWeight: "600",
|
|
333
333
|
color: _sharedUi.night.color.textTertiary,
|
|
334
|
-
fontFamily:
|
|
334
|
+
fontFamily: _sharedUi.monoFont
|
|
335
335
|
},
|
|
336
336
|
valueBox: {
|
|
337
337
|
// Inset code box — darker than the card, the night track color.
|
|
@@ -344,7 +344,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
344
344
|
valueText: {
|
|
345
345
|
fontSize: 12,
|
|
346
346
|
color: _sharedUi.night.color.text,
|
|
347
|
-
fontFamily:
|
|
347
|
+
fontFamily: _sharedUi.monoFont,
|
|
348
348
|
lineHeight: 18
|
|
349
349
|
},
|
|
350
350
|
diffContainer: {
|
|
@@ -362,7 +362,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
362
362
|
changeTextSmall: {
|
|
363
363
|
fontSize: 8,
|
|
364
364
|
fontWeight: "700",
|
|
365
|
-
fontFamily:
|
|
365
|
+
fontFamily: _sharedUi.monoFont
|
|
366
366
|
},
|
|
367
367
|
dataViewerContainer: {
|
|
368
368
|
marginTop: -12,
|
|
@@ -337,7 +337,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
337
337
|
fontWeight: "600",
|
|
338
338
|
letterSpacing: 0.5,
|
|
339
339
|
color: _sharedUi.night.color.accent,
|
|
340
|
-
fontFamily:
|
|
340
|
+
fontFamily: _sharedUi.monoFont
|
|
341
341
|
},
|
|
342
342
|
cardContent: {
|
|
343
343
|
padding: 14,
|
|
@@ -375,7 +375,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
375
375
|
fontSize: 9,
|
|
376
376
|
fontWeight: "600",
|
|
377
377
|
color: _sharedUi.night.color.textSecondary,
|
|
378
|
-
fontFamily:
|
|
378
|
+
fontFamily: _sharedUi.monoFont
|
|
379
379
|
},
|
|
380
380
|
durationTextSlow: {
|
|
381
381
|
color: _sharedUi.buoyColors.error
|
|
@@ -437,7 +437,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
437
437
|
fontSize: 11,
|
|
438
438
|
fontWeight: "600",
|
|
439
439
|
color: _sharedUi.night.color.accent,
|
|
440
|
-
fontFamily:
|
|
440
|
+
fontFamily: _sharedUi.monoFont
|
|
441
441
|
},
|
|
442
442
|
countBadge: {
|
|
443
443
|
paddingHorizontal: 8,
|
|
@@ -449,7 +449,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
449
449
|
fontSize: 9,
|
|
450
450
|
fontWeight: "700",
|
|
451
451
|
color: _sharedUi.night.color.accent,
|
|
452
|
-
fontFamily:
|
|
452
|
+
fontFamily: _sharedUi.monoFont
|
|
453
453
|
},
|
|
454
454
|
typeBadge: {
|
|
455
455
|
paddingHorizontal: 6,
|
|
@@ -461,7 +461,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
461
461
|
fontSize: 8,
|
|
462
462
|
fontWeight: "600",
|
|
463
463
|
color: _sharedUi.night.color.textTertiary,
|
|
464
|
-
fontFamily:
|
|
464
|
+
fontFamily: _sharedUi.monoFont
|
|
465
465
|
},
|
|
466
466
|
dataViewerContainer: {
|
|
467
467
|
// Inset display panel — darker than the card, the night track color.
|
|
@@ -471,7 +471,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
471
471
|
updaterText: {
|
|
472
472
|
fontSize: 12,
|
|
473
473
|
color: _sharedUi.night.color.text,
|
|
474
|
-
fontFamily:
|
|
474
|
+
fontFamily: _sharedUi.monoFont
|
|
475
475
|
},
|
|
476
476
|
updaterSubtext: {
|
|
477
477
|
fontSize: 11,
|
|
@@ -170,7 +170,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
170
170
|
absCountText: {
|
|
171
171
|
fontSize: 9,
|
|
172
172
|
fontWeight: "700",
|
|
173
|
-
fontFamily:
|
|
173
|
+
fontFamily: _sharedUi.monoFont
|
|
174
174
|
},
|
|
175
175
|
scrollContent: {
|
|
176
176
|
paddingTop: 8,
|
|
@@ -199,6 +199,6 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
199
199
|
fontSize: 10,
|
|
200
200
|
fontWeight: "700",
|
|
201
201
|
color: _sharedUi.night.color.accent,
|
|
202
|
-
fontFamily:
|
|
202
|
+
fontFamily: _sharedUi.monoFont
|
|
203
203
|
}
|
|
204
204
|
});
|
|
@@ -396,7 +396,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
396
396
|
color: _sharedUi.night.color.accent,
|
|
397
397
|
letterSpacing: 0.5,
|
|
398
398
|
textTransform: "uppercase",
|
|
399
|
-
fontFamily:
|
|
399
|
+
fontFamily: _sharedUi.monoFont
|
|
400
400
|
},
|
|
401
401
|
rawButton: {
|
|
402
402
|
flexDirection: "row",
|
|
@@ -443,7 +443,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
443
443
|
borderBottomColor: _sharedUi.night.color.border,
|
|
444
444
|
letterSpacing: 0.5,
|
|
445
445
|
textTransform: "uppercase",
|
|
446
|
-
fontFamily:
|
|
446
|
+
fontFamily: _sharedUi.monoFont
|
|
447
447
|
},
|
|
448
448
|
infoBody: {
|
|
449
449
|
padding: 12,
|
|
@@ -459,13 +459,13 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
459
459
|
inlineLinkText: {
|
|
460
460
|
fontSize: 10,
|
|
461
461
|
fontWeight: "600",
|
|
462
|
-
fontFamily:
|
|
462
|
+
fontFamily: _sharedUi.monoFont
|
|
463
463
|
},
|
|
464
464
|
actionsText: {
|
|
465
465
|
flex: 1,
|
|
466
466
|
color: _sharedUi.night.color.textSecondary,
|
|
467
467
|
fontSize: 10,
|
|
468
|
-
fontFamily:
|
|
468
|
+
fontFamily: _sharedUi.monoFont,
|
|
469
469
|
lineHeight: 15
|
|
470
470
|
}
|
|
471
471
|
});
|
|
@@ -301,7 +301,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
301
301
|
color: _sharedUi.night.color.text,
|
|
302
302
|
fontSize: 13,
|
|
303
303
|
fontWeight: "600",
|
|
304
|
-
fontFamily:
|
|
304
|
+
fontFamily: _sharedUi.monoFont
|
|
305
305
|
},
|
|
306
306
|
headerState: {
|
|
307
307
|
color: _sharedUi.night.color.textTertiary,
|
|
@@ -320,7 +320,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
320
320
|
metaText: {
|
|
321
321
|
color: _sharedUi.night.color.textTertiary,
|
|
322
322
|
fontSize: 11,
|
|
323
|
-
fontFamily:
|
|
323
|
+
fontFamily: _sharedUi.monoFont
|
|
324
324
|
},
|
|
325
325
|
treeScroll: {
|
|
326
326
|
flex: 1
|
|
@@ -345,7 +345,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
345
345
|
error: {
|
|
346
346
|
color: _sharedUi.night.color.danger,
|
|
347
347
|
fontSize: 11,
|
|
348
|
-
fontFamily:
|
|
348
|
+
fontFamily: _sharedUi.monoFont,
|
|
349
349
|
paddingHorizontal: 12,
|
|
350
350
|
paddingBottom: 6
|
|
351
351
|
},
|
|
@@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.zustandSyncAdapter = exports.STATE_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = void 0;
|
|
7
|
-
var _zustandStateStore = require("../utils/zustandStateStore");
|
|
8
7
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
8
|
+
var _zustandStateStore = require("../utils/zustandStateStore");
|
|
9
9
|
/**
|
|
10
10
|
* Sentinel used in WIRE-FORM snapshots in place of raw prevState/nextState.
|
|
11
11
|
* Same hazard as redux: up to 200 changes × 2 full trees on every snapshot
|
|
@@ -15,6 +15,23 @@ const STATE_ON_DEVICE = exports.STATE_ON_DEVICE = (0, _sharedUi.onDeviceMarker)(
|
|
|
15
15
|
|
|
16
16
|
/** Wire-form sentinel for an oversized `partial` (replace can carry a whole tree). */
|
|
17
17
|
const PAYLOAD_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = (0, _sharedUi.onDeviceMarker)("Payload", "Partial stays on the device — fetched on demand via getChangeDetail.");
|
|
18
|
+
const isPlainObject = v => !!v && typeof v === "object" && !Array.isArray(v);
|
|
19
|
+
/**
|
|
20
|
+
* Does this patch write into the list at `path`? Only then is an unchecked-shape
|
|
21
|
+
* warning about that list worth saying — a patch that changes `promoCode` should
|
|
22
|
+
* not be told about an empty `lines`.
|
|
23
|
+
*/
|
|
24
|
+
function touchesList(patch, path) {
|
|
25
|
+
let cur = patch;
|
|
26
|
+
for (const seg of path.split(".")) {
|
|
27
|
+
if (!cur || typeof cur !== "object" || Array.isArray(cur)) return false;
|
|
28
|
+
if (!(seg in cur)) return false;
|
|
29
|
+
cur = cur[seg];
|
|
30
|
+
}
|
|
31
|
+
// An empty list stays empty; there is no new item to be unsure about.
|
|
32
|
+
return Array.isArray(cur) ? cur.length > 0 : !!cur && typeof cur === "object";
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
/** Top-level keys of an object state, for the compact (no-values) view. */
|
|
19
36
|
function topLevelKeys(state) {
|
|
20
37
|
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : undefined;
|
|
@@ -80,7 +97,8 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = (0, _sharedUi.createSync
|
|
|
80
97
|
*/
|
|
81
98
|
getStoreState: params => {
|
|
82
99
|
const {
|
|
83
|
-
storeName
|
|
100
|
+
storeName,
|
|
101
|
+
path
|
|
84
102
|
} = (0, _sharedUi.readParams)(params);
|
|
85
103
|
if (!storeName) return {
|
|
86
104
|
found: false,
|
|
@@ -97,16 +115,46 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = (0, _sharedUi.createSync
|
|
|
97
115
|
} catch {
|
|
98
116
|
currentState = undefined;
|
|
99
117
|
}
|
|
118
|
+
// One slice instead of the whole store. The reason to want it is the
|
|
119
|
+
// engine's 24,000-character result cap: a caller that never saw the field
|
|
120
|
+
// it means to edit is left inventing a shape, which is the failure this
|
|
121
|
+
// whole surface keeps producing.
|
|
122
|
+
if (path) {
|
|
123
|
+
const at = (0, _sharedUi.readPath)(currentState, path);
|
|
124
|
+
if (!at.ok) return {
|
|
125
|
+
found: false,
|
|
126
|
+
reason: at.error
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
found: true,
|
|
130
|
+
storeName,
|
|
131
|
+
path,
|
|
132
|
+
shape: (0, _sharedUi.shapeSummary)(at.value),
|
|
133
|
+
currentState: at.value
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// The sketch goes BEFORE currentState, deliberately. The engine truncates
|
|
137
|
+
// a result by slicing the encoded string at 24,000 characters, so whatever
|
|
138
|
+
// is serialised first is what survives — and the reads most in need of a
|
|
139
|
+
// shape are exactly the long ones that get cut. It costs ~150 characters
|
|
140
|
+
// and does not grow with the data.
|
|
141
|
+
const shape = (0, _sharedUi.shapeSummary)(currentState);
|
|
100
142
|
if (!(0, _sharedUi.isOverWireBudget)(currentState, _sharedUi.WIRE_DETAIL_LIMIT_BYTES)) {
|
|
101
143
|
return {
|
|
102
144
|
found: true,
|
|
103
145
|
storeName,
|
|
146
|
+
...(shape ? {
|
|
147
|
+
shape
|
|
148
|
+
} : {}),
|
|
104
149
|
currentState
|
|
105
150
|
};
|
|
106
151
|
}
|
|
107
152
|
return {
|
|
108
153
|
found: true,
|
|
109
154
|
storeName,
|
|
155
|
+
...(shape ? {
|
|
156
|
+
shape
|
|
157
|
+
} : {}),
|
|
110
158
|
currentState: (0, _sharedUi.isJsonSerializable)(currentState) ? (0, _sharedUi.truncatedNote)(_sharedUi.WIRE_DETAIL_LIMIT_BYTES) : STATE_ON_DEVICE
|
|
111
159
|
};
|
|
112
160
|
},
|
|
@@ -133,7 +181,13 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = (0, _sharedUi.createSync
|
|
|
133
181
|
currentState: s.currentState
|
|
134
182
|
} : {
|
|
135
183
|
keys: topLevelKeys(s.currentState)
|
|
136
|
-
})
|
|
184
|
+
}),
|
|
185
|
+
// Cheap enough to always include: this is the compact view a caller
|
|
186
|
+
// reaches for precisely because it does not want to pull the data, and
|
|
187
|
+
// the item shape of a list is the one thing it cannot infer without it.
|
|
188
|
+
...((0, _sharedUi.shapeSummary)(s.currentState) ? {
|
|
189
|
+
shape: (0, _sharedUi.shapeSummary)(s.currentState)
|
|
190
|
+
} : {})
|
|
137
191
|
}));
|
|
138
192
|
return {
|
|
139
193
|
stores,
|
|
@@ -146,8 +200,14 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = (0, _sharedUi.createSync
|
|
|
146
200
|
const {
|
|
147
201
|
storeName,
|
|
148
202
|
state,
|
|
149
|
-
replace
|
|
203
|
+
replace,
|
|
204
|
+
force,
|
|
205
|
+
path,
|
|
206
|
+
value
|
|
150
207
|
} = (0, _sharedUi.readParams)(params);
|
|
208
|
+
let effective = state;
|
|
209
|
+
let keyedNotes = [];
|
|
210
|
+
let unchecked = [];
|
|
151
211
|
if (!storeName) return {
|
|
152
212
|
ok: false,
|
|
153
213
|
error: "Missing storeName."
|
|
@@ -157,9 +217,112 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = (0, _sharedUi.createSync
|
|
|
157
217
|
ok: false,
|
|
158
218
|
error: `No store named "${storeName}".`
|
|
159
219
|
};
|
|
160
|
-
|
|
220
|
+
// `path` is sugar over a merge: it can only ever produce a patch, which
|
|
221
|
+
// then goes through the guard below like any other. Its value is that
|
|
222
|
+
// there is no nesting depth for the caller to get wrong.
|
|
223
|
+
// `state` is no longer schema-required, because `path`+`value` is a valid
|
|
224
|
+
// alternative — so the handler has to be the thing that rejects NEITHER.
|
|
225
|
+
// Without this, `state` arrives undefined and falls through to
|
|
226
|
+
// `setState(undefined, true)`, which is zustand's whole-state replace: the
|
|
227
|
+
// store's data AND its action functions, gone, from a call that looks like
|
|
228
|
+
// a typo. This is the most destructive thing in this file.
|
|
229
|
+
if (state === undefined && path === undefined) {
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
error: `Refused: no change was described. Send \`state\` with the fields to merge, or \`path\` and \`value\` to set one value.`
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
let pathed = false;
|
|
236
|
+
if (path !== undefined) {
|
|
237
|
+
if (value === undefined) {
|
|
238
|
+
return {
|
|
239
|
+
ok: false,
|
|
240
|
+
error: `Refused: sent \`path\` without \`value\`. Say what to set "${path}" to; if you mean to clear it, send value:null and the field must already be nullable.`
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
let currentForPath;
|
|
244
|
+
try {
|
|
245
|
+
currentForPath = store.api.getState();
|
|
246
|
+
} catch {
|
|
247
|
+
currentForPath = undefined;
|
|
248
|
+
}
|
|
249
|
+
const built = (0, _sharedUi.patchFromPath)(currentForPath, path, value);
|
|
250
|
+
if (!built.ok) return {
|
|
251
|
+
ok: false,
|
|
252
|
+
error: `Refused: ${built.error}`
|
|
253
|
+
};
|
|
254
|
+
effective = built.patch;
|
|
255
|
+
pathed = true;
|
|
256
|
+
}
|
|
257
|
+
// On a MERGE (replace:false), constrain it to a typed edit so a remote
|
|
258
|
+
// caller can't hand the store a field of the wrong type or a malformed
|
|
259
|
+
// new list item — the same protection the query cache has. A replace
|
|
260
|
+
// (whole-store reset) and an explicit `force` skip this.
|
|
261
|
+
if (!force && (replace === false || pathed)) {
|
|
262
|
+
let current;
|
|
263
|
+
try {
|
|
264
|
+
current = store.api.getState();
|
|
265
|
+
} catch {
|
|
266
|
+
current = undefined;
|
|
267
|
+
}
|
|
268
|
+
// Expand id-addressed list edits BEFORE validating, so the typed-edit
|
|
269
|
+
// guard below sees a real array and every existing protection still
|
|
270
|
+
// applies to the result.
|
|
271
|
+
if (current !== undefined) {
|
|
272
|
+
const expanded = (0, _sharedUi.expandKeyedArrays)(current, effective);
|
|
273
|
+
// See keyedArrays.ts: an added item whose id disagrees with the key
|
|
274
|
+
// it was sent under is ambiguous, and only this layer can say so.
|
|
275
|
+
if (expanded.error) {
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
error: `Refused: ${expanded.error}`
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
effective = expanded.patch;
|
|
282
|
+
keyedNotes = expanded.notes;
|
|
283
|
+
}
|
|
284
|
+
// The guard builds its template from `list[0]`, so a list that is EMPTY
|
|
285
|
+
// has no template and an added item is written with nothing to check it
|
|
286
|
+
// against. That is not a rare corner: it is every "add the first thing"
|
|
287
|
+
// flow, and it is where models invent shapes, because on an empty list
|
|
288
|
+
// the item shape genuinely exists nowhere in the running app. We cannot
|
|
289
|
+
// refuse it — refusing would break adding the first item to anything —
|
|
290
|
+
// so we say so instead of letting the guess through unremarked.
|
|
291
|
+
if (current !== undefined) {
|
|
292
|
+
unchecked = (0, _sharedUi.unshapedLists)(current).filter(path => touchesList(effective, path));
|
|
293
|
+
}
|
|
294
|
+
if (current !== undefined) {
|
|
295
|
+
const violations = (0, _sharedUi.typedEditViolations)(current, effective, {
|
|
296
|
+
noun: "store"
|
|
297
|
+
});
|
|
298
|
+
if (violations.length > 0) {
|
|
299
|
+
return {
|
|
300
|
+
ok: false,
|
|
301
|
+
error: (0, _sharedUi.refusalText)(violations, "store", (0, _sharedUi.suggestedPatch)(effective, violations)),
|
|
302
|
+
...((0, _sharedUi.suggestedPatch)(effective, violations) ? {
|
|
303
|
+
suggestedState: (0, _sharedUi.suggestedPatch)(effective, violations)
|
|
304
|
+
} : {}),
|
|
305
|
+
violations
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// A pathed write is a merge by construction — it names one value, so it
|
|
311
|
+
// must never fall through to zustand's default whole-state replace.
|
|
312
|
+
store.api.setState(effective, pathed ? false : replace ?? true);
|
|
313
|
+
// Say what an addressed edit resolved to. A caller that meant to change one
|
|
314
|
+
// line and sees "17 item(s) remain" has caught its own mistake.
|
|
161
315
|
return {
|
|
162
|
-
ok: true
|
|
316
|
+
ok: true,
|
|
317
|
+
...(path !== undefined ? {
|
|
318
|
+
path
|
|
319
|
+
} : {}),
|
|
320
|
+
...(keyedNotes.length ? {
|
|
321
|
+
keyed: keyedNotes
|
|
322
|
+
} : {}),
|
|
323
|
+
...(unchecked.length ? {
|
|
324
|
+
unchecked: unchecked.map(path => `\`${path}\` was empty, so nothing in the app knows what its items look like and the shape of what you added was NOT checked. If the app renders a field you left out, it will render undefined — read the store back and compare against how the screen uses it.`)
|
|
325
|
+
} : {})
|
|
163
326
|
};
|
|
164
327
|
},
|
|
165
328
|
/**
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { TouchableOpacity, Text, View, StyleSheet } from "react-native";
|
|
11
|
-
import { night } from "@buoy-gg/shared-ui";
|
|
11
|
+
import { night, monoFont } from "@buoy-gg/shared-ui";
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
const buttonConfigs = {
|
|
14
14
|
copy: {
|
|
@@ -108,7 +108,7 @@ const styles = StyleSheet.create({
|
|
|
108
108
|
fontWeight: "600",
|
|
109
109
|
letterSpacing: 0.5,
|
|
110
110
|
textTransform: "uppercase",
|
|
111
|
-
fontFamily:
|
|
111
|
+
fontFamily: monoFont,
|
|
112
112
|
height: 12
|
|
113
113
|
}
|
|
114
114
|
});
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { View, Text, StyleSheet, ScrollView } from "react-native";
|
|
15
15
|
import { useState, useCallback, useMemo } from "react";
|
|
16
|
-
import { night, EventStepperFooter, formatRelativeTime, parseValue } from "@buoy-gg/shared-ui";
|
|
16
|
+
import { night, EventStepperFooter, formatRelativeTime, parseValue, monoFont } from "@buoy-gg/shared-ui";
|
|
17
17
|
import { DataViewer, SplitDiffViewer, TreeDiffViewer, diffThemes } from "@buoy-gg/shared-ui/dataViewer";
|
|
18
18
|
import { ZustandDetailViewToggle } from "./ZustandDetailViewToggle";
|
|
19
19
|
import { ZustandStateInfoView } from "./ZustandStateInfoView";
|
|
@@ -297,7 +297,7 @@ const styles = StyleSheet.create({
|
|
|
297
297
|
valueLabel: {
|
|
298
298
|
fontSize: 10,
|
|
299
299
|
color: night.color.textSecondary,
|
|
300
|
-
fontFamily:
|
|
300
|
+
fontFamily: monoFont,
|
|
301
301
|
letterSpacing: 0.5,
|
|
302
302
|
fontWeight: "600"
|
|
303
303
|
},
|
|
@@ -314,7 +314,7 @@ const styles = StyleSheet.create({
|
|
|
314
314
|
changeText: {
|
|
315
315
|
fontSize: 9,
|
|
316
316
|
fontWeight: "700",
|
|
317
|
-
fontFamily:
|
|
317
|
+
fontFamily: monoFont,
|
|
318
318
|
letterSpacing: 0.3
|
|
319
319
|
},
|
|
320
320
|
typeBadge: {
|
|
@@ -327,7 +327,7 @@ const styles = StyleSheet.create({
|
|
|
327
327
|
fontSize: 9,
|
|
328
328
|
fontWeight: "600",
|
|
329
329
|
color: night.color.textTertiary,
|
|
330
|
-
fontFamily:
|
|
330
|
+
fontFamily: monoFont
|
|
331
331
|
},
|
|
332
332
|
valueBox: {
|
|
333
333
|
// Inset code box — darker than the card, the night track color.
|
|
@@ -340,7 +340,7 @@ const styles = StyleSheet.create({
|
|
|
340
340
|
valueText: {
|
|
341
341
|
fontSize: 12,
|
|
342
342
|
color: night.color.text,
|
|
343
|
-
fontFamily:
|
|
343
|
+
fontFamily: monoFont,
|
|
344
344
|
lineHeight: 18
|
|
345
345
|
},
|
|
346
346
|
diffContainer: {
|
|
@@ -358,7 +358,7 @@ const styles = StyleSheet.create({
|
|
|
358
358
|
changeTextSmall: {
|
|
359
359
|
fontSize: 8,
|
|
360
360
|
fontWeight: "700",
|
|
361
|
-
fontFamily:
|
|
361
|
+
fontFamily: monoFont
|
|
362
362
|
},
|
|
363
363
|
dataViewerContainer: {
|
|
364
364
|
marginTop: -12,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import React, { memo, useCallback, useState, useEffect } from "react";
|
|
15
15
|
import { View, Text, StyleSheet, ScrollView } from "react-native";
|
|
16
|
-
import { buoyColors, night, Clock, AlertTriangle, Zap, Info, Box, Database, parseValue, ProUpgradeModal, copyToClipboard, InfoRow } from "@buoy-gg/shared-ui";
|
|
16
|
+
import { buoyColors, night, Clock, AlertTriangle, Zap, Info, Box, Database, parseValue, ProUpgradeModal, copyToClipboard, InfoRow, monoFont } from "@buoy-gg/shared-ui";
|
|
17
17
|
import { useIsPro } from "@buoy-gg/license";
|
|
18
18
|
import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
|
|
19
19
|
import { ZustandActionButton } from "./ZustandActionButton";
|
|
@@ -332,7 +332,7 @@ const styles = StyleSheet.create({
|
|
|
332
332
|
fontWeight: "600",
|
|
333
333
|
letterSpacing: 0.5,
|
|
334
334
|
color: night.color.accent,
|
|
335
|
-
fontFamily:
|
|
335
|
+
fontFamily: monoFont
|
|
336
336
|
},
|
|
337
337
|
cardContent: {
|
|
338
338
|
padding: 14,
|
|
@@ -370,7 +370,7 @@ const styles = StyleSheet.create({
|
|
|
370
370
|
fontSize: 9,
|
|
371
371
|
fontWeight: "600",
|
|
372
372
|
color: night.color.textSecondary,
|
|
373
|
-
fontFamily:
|
|
373
|
+
fontFamily: monoFont
|
|
374
374
|
},
|
|
375
375
|
durationTextSlow: {
|
|
376
376
|
color: buoyColors.error
|
|
@@ -432,7 +432,7 @@ const styles = StyleSheet.create({
|
|
|
432
432
|
fontSize: 11,
|
|
433
433
|
fontWeight: "600",
|
|
434
434
|
color: night.color.accent,
|
|
435
|
-
fontFamily:
|
|
435
|
+
fontFamily: monoFont
|
|
436
436
|
},
|
|
437
437
|
countBadge: {
|
|
438
438
|
paddingHorizontal: 8,
|
|
@@ -444,7 +444,7 @@ const styles = StyleSheet.create({
|
|
|
444
444
|
fontSize: 9,
|
|
445
445
|
fontWeight: "700",
|
|
446
446
|
color: night.color.accent,
|
|
447
|
-
fontFamily:
|
|
447
|
+
fontFamily: monoFont
|
|
448
448
|
},
|
|
449
449
|
typeBadge: {
|
|
450
450
|
paddingHorizontal: 6,
|
|
@@ -456,7 +456,7 @@ const styles = StyleSheet.create({
|
|
|
456
456
|
fontSize: 8,
|
|
457
457
|
fontWeight: "600",
|
|
458
458
|
color: night.color.textTertiary,
|
|
459
|
-
fontFamily:
|
|
459
|
+
fontFamily: monoFont
|
|
460
460
|
},
|
|
461
461
|
dataViewerContainer: {
|
|
462
462
|
// Inset display panel — darker than the card, the night track color.
|
|
@@ -466,7 +466,7 @@ const styles = StyleSheet.create({
|
|
|
466
466
|
updaterText: {
|
|
467
467
|
fontSize: 12,
|
|
468
468
|
color: night.color.text,
|
|
469
|
-
fontFamily:
|
|
469
|
+
fontFamily: monoFont
|
|
470
470
|
},
|
|
471
471
|
updaterSubtext: {
|
|
472
472
|
fontSize: 11,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { useMemo } from "react";
|
|
13
13
|
import { View, Text, StyleSheet, ScrollView } from "react-native";
|
|
14
|
-
import { CompactRow, night, ToolEmptyState, resolveListEmptyState } from "@buoy-gg/shared-ui";
|
|
14
|
+
import { CompactRow, night, ToolEmptyState, resolveListEmptyState, monoFont } from "@buoy-gg/shared-ui";
|
|
15
15
|
import { zustandStateStore } from "../utils/zustandStateStore";
|
|
16
16
|
import { ZUSTAND_TEST_ID, storeRowTestID } from "../utils/testIDs";
|
|
17
17
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -166,7 +166,7 @@ const styles = StyleSheet.create({
|
|
|
166
166
|
absCountText: {
|
|
167
167
|
fontSize: 9,
|
|
168
168
|
fontWeight: "700",
|
|
169
|
-
fontFamily:
|
|
169
|
+
fontFamily: monoFont
|
|
170
170
|
},
|
|
171
171
|
scrollContent: {
|
|
172
172
|
paddingTop: 8,
|
|
@@ -195,6 +195,6 @@ const styles = StyleSheet.create({
|
|
|
195
195
|
fontSize: 10,
|
|
196
196
|
fontWeight: "700",
|
|
197
197
|
color: night.color.accent,
|
|
198
|
-
fontFamily:
|
|
198
|
+
fontFamily: monoFont
|
|
199
199
|
}
|
|
200
200
|
});
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
36
36
|
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
37
|
-
import { buoyColors, Database, ExpandedInfoRow, FileJson, night, PillBadge, parseValue } from "@buoy-gg/shared-ui";
|
|
37
|
+
import { buoyColors, Database, ExpandedInfoRow, FileJson, night, PillBadge, parseValue, monoFont } from "@buoy-gg/shared-ui";
|
|
38
38
|
import { DataViewer, LiveExplorer, ValueEditModal, updateNestedDataByPath, deleteNestedDataByPath } from "@buoy-gg/shared-ui/dataViewer";
|
|
39
39
|
import { computeMergePartial, editableStateOf, isPlainObjectLike } from "../utils/editableState";
|
|
40
40
|
import { ZUSTAND_TEST_ID, storeRowTestID } from "../utils/testIDs";
|
|
@@ -394,7 +394,7 @@ const styles = StyleSheet.create({
|
|
|
394
394
|
color: night.color.accent,
|
|
395
395
|
letterSpacing: 0.5,
|
|
396
396
|
textTransform: "uppercase",
|
|
397
|
-
fontFamily:
|
|
397
|
+
fontFamily: monoFont
|
|
398
398
|
},
|
|
399
399
|
rawButton: {
|
|
400
400
|
flexDirection: "row",
|
|
@@ -441,7 +441,7 @@ const styles = StyleSheet.create({
|
|
|
441
441
|
borderBottomColor: night.color.border,
|
|
442
442
|
letterSpacing: 0.5,
|
|
443
443
|
textTransform: "uppercase",
|
|
444
|
-
fontFamily:
|
|
444
|
+
fontFamily: monoFont
|
|
445
445
|
},
|
|
446
446
|
infoBody: {
|
|
447
447
|
padding: 12,
|
|
@@ -457,13 +457,13 @@ const styles = StyleSheet.create({
|
|
|
457
457
|
inlineLinkText: {
|
|
458
458
|
fontSize: 10,
|
|
459
459
|
fontWeight: "600",
|
|
460
|
-
fontFamily:
|
|
460
|
+
fontFamily: monoFont
|
|
461
461
|
},
|
|
462
462
|
actionsText: {
|
|
463
463
|
flex: 1,
|
|
464
464
|
color: night.color.textSecondary,
|
|
465
465
|
fontSize: 10,
|
|
466
|
-
fontFamily:
|
|
466
|
+
fontFamily: monoFont,
|
|
467
467
|
lineHeight: 15
|
|
468
468
|
}
|
|
469
469
|
});
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
import { useCallback, useMemo, useState } from "react";
|
|
33
33
|
import { Alert, ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
34
|
-
import { buoyColors, Database, FileJson, night, PillBadge, ToolBackground, useSafeAreaInsets, X } from "@buoy-gg/shared-ui";
|
|
34
|
+
import { buoyColors, Database, FileJson, night, PillBadge, ToolBackground, useSafeAreaInsets, X, monoFont } from "@buoy-gg/shared-ui";
|
|
35
35
|
import { DataTreeActionDock, DataViewer, getAtPath, useTreeEditor, ValueEditModal } from "@buoy-gg/shared-ui/dataViewer";
|
|
36
36
|
import { computeMergePartial, editableStateOf } from "../utils/editableState";
|
|
37
37
|
import { ZUSTAND_TEST_ID } from "../utils/testIDs";
|
|
@@ -297,7 +297,7 @@ const styles = StyleSheet.create({
|
|
|
297
297
|
color: night.color.text,
|
|
298
298
|
fontSize: 13,
|
|
299
299
|
fontWeight: "600",
|
|
300
|
-
fontFamily:
|
|
300
|
+
fontFamily: monoFont
|
|
301
301
|
},
|
|
302
302
|
headerState: {
|
|
303
303
|
color: night.color.textTertiary,
|
|
@@ -316,7 +316,7 @@ const styles = StyleSheet.create({
|
|
|
316
316
|
metaText: {
|
|
317
317
|
color: night.color.textTertiary,
|
|
318
318
|
fontSize: 11,
|
|
319
|
-
fontFamily:
|
|
319
|
+
fontFamily: monoFont
|
|
320
320
|
},
|
|
321
321
|
treeScroll: {
|
|
322
322
|
flex: 1
|
|
@@ -341,7 +341,7 @@ const styles = StyleSheet.create({
|
|
|
341
341
|
error: {
|
|
342
342
|
color: night.color.danger,
|
|
343
343
|
fontSize: 11,
|
|
344
|
-
fontFamily:
|
|
344
|
+
fontFamily: monoFont,
|
|
345
345
|
paddingHorizontal: 12,
|
|
346
346
|
paddingBottom: 6
|
|
347
347
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
import { expandKeyedArrays, patchFromPath, readPath, refusalText, shapeSummary, suggestedPatch, typedEditViolations, unshapedLists } from "@buoy-gg/shared-ui";
|
|
3
4
|
import { zustandStateStore } from "../utils/zustandStateStore";
|
|
4
5
|
import { WIRE_DETAIL_LIMIT_BYTES, createSyncAdapter, detailByIdAction, isJsonSerializable, isOverWireBudget, memoByIdentity, onDeviceMarker, readParams, toWire, truncatedNote } from "@buoy-gg/shared-ui";
|
|
5
6
|
|
|
@@ -12,6 +13,23 @@ export const STATE_ON_DEVICE = onDeviceMarker("State", "State trees stay on the
|
|
|
12
13
|
|
|
13
14
|
/** Wire-form sentinel for an oversized `partial` (replace can carry a whole tree). */
|
|
14
15
|
export const PAYLOAD_ON_DEVICE = onDeviceMarker("Payload", "Partial stays on the device — fetched on demand via getChangeDetail.");
|
|
16
|
+
const isPlainObject = v => !!v && typeof v === "object" && !Array.isArray(v);
|
|
17
|
+
/**
|
|
18
|
+
* Does this patch write into the list at `path`? Only then is an unchecked-shape
|
|
19
|
+
* warning about that list worth saying — a patch that changes `promoCode` should
|
|
20
|
+
* not be told about an empty `lines`.
|
|
21
|
+
*/
|
|
22
|
+
function touchesList(patch, path) {
|
|
23
|
+
let cur = patch;
|
|
24
|
+
for (const seg of path.split(".")) {
|
|
25
|
+
if (!cur || typeof cur !== "object" || Array.isArray(cur)) return false;
|
|
26
|
+
if (!(seg in cur)) return false;
|
|
27
|
+
cur = cur[seg];
|
|
28
|
+
}
|
|
29
|
+
// An empty list stays empty; there is no new item to be unsure about.
|
|
30
|
+
return Array.isArray(cur) ? cur.length > 0 : !!cur && typeof cur === "object";
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
/** Top-level keys of an object state, for the compact (no-values) view. */
|
|
16
34
|
function topLevelKeys(state) {
|
|
17
35
|
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : undefined;
|
|
@@ -77,7 +95,8 @@ export const zustandSyncAdapter = createSyncAdapter({
|
|
|
77
95
|
*/
|
|
78
96
|
getStoreState: params => {
|
|
79
97
|
const {
|
|
80
|
-
storeName
|
|
98
|
+
storeName,
|
|
99
|
+
path
|
|
81
100
|
} = readParams(params);
|
|
82
101
|
if (!storeName) return {
|
|
83
102
|
found: false,
|
|
@@ -94,16 +113,46 @@ export const zustandSyncAdapter = createSyncAdapter({
|
|
|
94
113
|
} catch {
|
|
95
114
|
currentState = undefined;
|
|
96
115
|
}
|
|
116
|
+
// One slice instead of the whole store. The reason to want it is the
|
|
117
|
+
// engine's 24,000-character result cap: a caller that never saw the field
|
|
118
|
+
// it means to edit is left inventing a shape, which is the failure this
|
|
119
|
+
// whole surface keeps producing.
|
|
120
|
+
if (path) {
|
|
121
|
+
const at = readPath(currentState, path);
|
|
122
|
+
if (!at.ok) return {
|
|
123
|
+
found: false,
|
|
124
|
+
reason: at.error
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
found: true,
|
|
128
|
+
storeName,
|
|
129
|
+
path,
|
|
130
|
+
shape: shapeSummary(at.value),
|
|
131
|
+
currentState: at.value
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// The sketch goes BEFORE currentState, deliberately. The engine truncates
|
|
135
|
+
// a result by slicing the encoded string at 24,000 characters, so whatever
|
|
136
|
+
// is serialised first is what survives — and the reads most in need of a
|
|
137
|
+
// shape are exactly the long ones that get cut. It costs ~150 characters
|
|
138
|
+
// and does not grow with the data.
|
|
139
|
+
const shape = shapeSummary(currentState);
|
|
97
140
|
if (!isOverWireBudget(currentState, WIRE_DETAIL_LIMIT_BYTES)) {
|
|
98
141
|
return {
|
|
99
142
|
found: true,
|
|
100
143
|
storeName,
|
|
144
|
+
...(shape ? {
|
|
145
|
+
shape
|
|
146
|
+
} : {}),
|
|
101
147
|
currentState
|
|
102
148
|
};
|
|
103
149
|
}
|
|
104
150
|
return {
|
|
105
151
|
found: true,
|
|
106
152
|
storeName,
|
|
153
|
+
...(shape ? {
|
|
154
|
+
shape
|
|
155
|
+
} : {}),
|
|
107
156
|
currentState: isJsonSerializable(currentState) ? truncatedNote(WIRE_DETAIL_LIMIT_BYTES) : STATE_ON_DEVICE
|
|
108
157
|
};
|
|
109
158
|
},
|
|
@@ -130,7 +179,13 @@ export const zustandSyncAdapter = createSyncAdapter({
|
|
|
130
179
|
currentState: s.currentState
|
|
131
180
|
} : {
|
|
132
181
|
keys: topLevelKeys(s.currentState)
|
|
133
|
-
})
|
|
182
|
+
}),
|
|
183
|
+
// Cheap enough to always include: this is the compact view a caller
|
|
184
|
+
// reaches for precisely because it does not want to pull the data, and
|
|
185
|
+
// the item shape of a list is the one thing it cannot infer without it.
|
|
186
|
+
...(shapeSummary(s.currentState) ? {
|
|
187
|
+
shape: shapeSummary(s.currentState)
|
|
188
|
+
} : {})
|
|
134
189
|
}));
|
|
135
190
|
return {
|
|
136
191
|
stores,
|
|
@@ -143,8 +198,14 @@ export const zustandSyncAdapter = createSyncAdapter({
|
|
|
143
198
|
const {
|
|
144
199
|
storeName,
|
|
145
200
|
state,
|
|
146
|
-
replace
|
|
201
|
+
replace,
|
|
202
|
+
force,
|
|
203
|
+
path,
|
|
204
|
+
value
|
|
147
205
|
} = readParams(params);
|
|
206
|
+
let effective = state;
|
|
207
|
+
let keyedNotes = [];
|
|
208
|
+
let unchecked = [];
|
|
148
209
|
if (!storeName) return {
|
|
149
210
|
ok: false,
|
|
150
211
|
error: "Missing storeName."
|
|
@@ -154,9 +215,112 @@ export const zustandSyncAdapter = createSyncAdapter({
|
|
|
154
215
|
ok: false,
|
|
155
216
|
error: `No store named "${storeName}".`
|
|
156
217
|
};
|
|
157
|
-
|
|
218
|
+
// `path` is sugar over a merge: it can only ever produce a patch, which
|
|
219
|
+
// then goes through the guard below like any other. Its value is that
|
|
220
|
+
// there is no nesting depth for the caller to get wrong.
|
|
221
|
+
// `state` is no longer schema-required, because `path`+`value` is a valid
|
|
222
|
+
// alternative — so the handler has to be the thing that rejects NEITHER.
|
|
223
|
+
// Without this, `state` arrives undefined and falls through to
|
|
224
|
+
// `setState(undefined, true)`, which is zustand's whole-state replace: the
|
|
225
|
+
// store's data AND its action functions, gone, from a call that looks like
|
|
226
|
+
// a typo. This is the most destructive thing in this file.
|
|
227
|
+
if (state === undefined && path === undefined) {
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
error: `Refused: no change was described. Send \`state\` with the fields to merge, or \`path\` and \`value\` to set one value.`
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
let pathed = false;
|
|
234
|
+
if (path !== undefined) {
|
|
235
|
+
if (value === undefined) {
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
error: `Refused: sent \`path\` without \`value\`. Say what to set "${path}" to; if you mean to clear it, send value:null and the field must already be nullable.`
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
let currentForPath;
|
|
242
|
+
try {
|
|
243
|
+
currentForPath = store.api.getState();
|
|
244
|
+
} catch {
|
|
245
|
+
currentForPath = undefined;
|
|
246
|
+
}
|
|
247
|
+
const built = patchFromPath(currentForPath, path, value);
|
|
248
|
+
if (!built.ok) return {
|
|
249
|
+
ok: false,
|
|
250
|
+
error: `Refused: ${built.error}`
|
|
251
|
+
};
|
|
252
|
+
effective = built.patch;
|
|
253
|
+
pathed = true;
|
|
254
|
+
}
|
|
255
|
+
// On a MERGE (replace:false), constrain it to a typed edit so a remote
|
|
256
|
+
// caller can't hand the store a field of the wrong type or a malformed
|
|
257
|
+
// new list item — the same protection the query cache has. A replace
|
|
258
|
+
// (whole-store reset) and an explicit `force` skip this.
|
|
259
|
+
if (!force && (replace === false || pathed)) {
|
|
260
|
+
let current;
|
|
261
|
+
try {
|
|
262
|
+
current = store.api.getState();
|
|
263
|
+
} catch {
|
|
264
|
+
current = undefined;
|
|
265
|
+
}
|
|
266
|
+
// Expand id-addressed list edits BEFORE validating, so the typed-edit
|
|
267
|
+
// guard below sees a real array and every existing protection still
|
|
268
|
+
// applies to the result.
|
|
269
|
+
if (current !== undefined) {
|
|
270
|
+
const expanded = expandKeyedArrays(current, effective);
|
|
271
|
+
// See keyedArrays.ts: an added item whose id disagrees with the key
|
|
272
|
+
// it was sent under is ambiguous, and only this layer can say so.
|
|
273
|
+
if (expanded.error) {
|
|
274
|
+
return {
|
|
275
|
+
ok: false,
|
|
276
|
+
error: `Refused: ${expanded.error}`
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
effective = expanded.patch;
|
|
280
|
+
keyedNotes = expanded.notes;
|
|
281
|
+
}
|
|
282
|
+
// The guard builds its template from `list[0]`, so a list that is EMPTY
|
|
283
|
+
// has no template and an added item is written with nothing to check it
|
|
284
|
+
// against. That is not a rare corner: it is every "add the first thing"
|
|
285
|
+
// flow, and it is where models invent shapes, because on an empty list
|
|
286
|
+
// the item shape genuinely exists nowhere in the running app. We cannot
|
|
287
|
+
// refuse it — refusing would break adding the first item to anything —
|
|
288
|
+
// so we say so instead of letting the guess through unremarked.
|
|
289
|
+
if (current !== undefined) {
|
|
290
|
+
unchecked = unshapedLists(current).filter(path => touchesList(effective, path));
|
|
291
|
+
}
|
|
292
|
+
if (current !== undefined) {
|
|
293
|
+
const violations = typedEditViolations(current, effective, {
|
|
294
|
+
noun: "store"
|
|
295
|
+
});
|
|
296
|
+
if (violations.length > 0) {
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
error: refusalText(violations, "store", suggestedPatch(effective, violations)),
|
|
300
|
+
...(suggestedPatch(effective, violations) ? {
|
|
301
|
+
suggestedState: suggestedPatch(effective, violations)
|
|
302
|
+
} : {}),
|
|
303
|
+
violations
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// A pathed write is a merge by construction — it names one value, so it
|
|
309
|
+
// must never fall through to zustand's default whole-state replace.
|
|
310
|
+
store.api.setState(effective, pathed ? false : replace ?? true);
|
|
311
|
+
// Say what an addressed edit resolved to. A caller that meant to change one
|
|
312
|
+
// line and sees "17 item(s) remain" has caught its own mistake.
|
|
158
313
|
return {
|
|
159
|
-
ok: true
|
|
314
|
+
ok: true,
|
|
315
|
+
...(path !== undefined ? {
|
|
316
|
+
path
|
|
317
|
+
} : {}),
|
|
318
|
+
...(keyedNotes.length ? {
|
|
319
|
+
keyed: keyedNotes
|
|
320
|
+
} : {}),
|
|
321
|
+
...(unchecked.length ? {
|
|
322
|
+
unchecked: unchecked.map(path => `\`${path}\` was empty, so nothing in the app knows what its items look like and the shape of what you added was NOT checked. If the app renders a field you left out, it will render undefined — read the store back and compare against how the screen uses it.`)
|
|
323
|
+
} : {})
|
|
160
324
|
};
|
|
161
325
|
},
|
|
162
326
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZustandStateDetailContent.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStateDetailContent.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"ZustandStateDetailContent.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStateDetailContent.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkBH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,2BAA2B,CAAC;AAMnC,UAAU,8BAA8B;IACtC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,0EAA0E;IAC1E,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAyCD,wBAAgB,yBAAyB,CAAC,EACxC,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,qBAA6B,EAC7B,WAAsB,GACvB,EAAE,8BAA8B,+BAmNhC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,GACd,EAAE;IACD,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,sCAmBA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZustandStateInfoView.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStateInfoView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAiD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ZustandStateInfoView.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStateInfoView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAiD,MAAM,OAAO,CAAC;AAmBtE,OAAO,KAAK,EAAE,kBAAkB,EAAuB,MAAM,UAAU,CAAC;AAKxE,UAAU,yBAAyB;IACjC,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAwED,eAAO,MAAM,oBAAoB,uDAuQ/B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZustandStoreDetailContent.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStoreDetailContent.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;
|
|
1
|
+
{"version":3,"file":"ZustandStoreDetailContent.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStoreDetailContent.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AA4BH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAyBjD,UAAU,8BAA8B;IACtC,KAAK,EAAE,gBAAgB,CAAC;IACxB,mEAAmE;IACnE,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAgB,yBAAyB,CAAC,EACxC,KAAK,EACL,aAAa,GACd,EAAE,8BAA8B,+BAsQhC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZustandStoreEditorScreen.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStoreEditorScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;
|
|
1
|
+
{"version":3,"file":"ZustandStoreEditorScreen.d.ts","sourceRoot":"","sources":["../../../../src/zustand/components/ZustandStoreEditorScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA6BH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAMjD,UAAU,6BAA6B;IACrC,KAAK,EAAE,gBAAgB,CAAC;IACxB,uEAAuE;IACvE,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,wBAAwB,CAAC,EACvC,KAAK,EACL,OAAO,EACP,kBAAkB,GACnB,EAAE,6BAA6B,+BAsO/B"}
|
|
@@ -63,12 +63,29 @@ export declare const zustandSyncAdapter: import("@buoy-gg/shared-ui").SyncAdapte
|
|
|
63
63
|
found: false;
|
|
64
64
|
reason: string;
|
|
65
65
|
storeName?: undefined;
|
|
66
|
+
path?: undefined;
|
|
67
|
+
shape?: undefined;
|
|
66
68
|
currentState?: undefined;
|
|
67
69
|
} | {
|
|
68
70
|
found: true;
|
|
69
71
|
storeName: string;
|
|
72
|
+
path: string;
|
|
73
|
+
shape: {
|
|
74
|
+
shape: string;
|
|
75
|
+
lists?: import("@buoy-gg/shared-ui").ListShape[];
|
|
76
|
+
} | undefined;
|
|
70
77
|
currentState: unknown;
|
|
71
78
|
reason?: undefined;
|
|
79
|
+
} | {
|
|
80
|
+
currentState: unknown;
|
|
81
|
+
shape?: {
|
|
82
|
+
shape: string;
|
|
83
|
+
lists?: import("@buoy-gg/shared-ui").ListShape[];
|
|
84
|
+
} | undefined;
|
|
85
|
+
found: true;
|
|
86
|
+
storeName: string;
|
|
87
|
+
reason?: undefined;
|
|
88
|
+
path?: undefined;
|
|
72
89
|
};
|
|
73
90
|
/**
|
|
74
91
|
* Compact current-state reader for a remote driver (MCP/LLM): the registered
|
|
@@ -78,6 +95,10 @@ export declare const zustandSyncAdapter: import("@buoy-gg/shared-ui").SyncAdapte
|
|
|
78
95
|
*/
|
|
79
96
|
listStores: (params?: unknown) => {
|
|
80
97
|
stores: ({
|
|
98
|
+
shape?: {
|
|
99
|
+
shape: string;
|
|
100
|
+
lists?: import("@buoy-gg/shared-ui").ListShape[];
|
|
101
|
+
} | undefined;
|
|
81
102
|
currentState: unknown;
|
|
82
103
|
name: string;
|
|
83
104
|
changes: number;
|
|
@@ -85,6 +106,10 @@ export declare const zustandSyncAdapter: import("@buoy-gg/shared-ui").SyncAdapte
|
|
|
85
106
|
persistName: string | undefined;
|
|
86
107
|
canRehydrate: boolean | undefined;
|
|
87
108
|
} | {
|
|
109
|
+
shape?: {
|
|
110
|
+
shape: string;
|
|
111
|
+
lists?: import("@buoy-gg/shared-ui").ListShape[];
|
|
112
|
+
} | undefined;
|
|
88
113
|
keys: string[] | undefined;
|
|
89
114
|
name: string;
|
|
90
115
|
changes: number;
|
|
@@ -100,7 +125,15 @@ export declare const zustandSyncAdapter: import("@buoy-gg/shared-ui").SyncAdapte
|
|
|
100
125
|
ok: boolean;
|
|
101
126
|
error: string;
|
|
102
127
|
} | {
|
|
128
|
+
violations: import("@buoy-gg/shared-ui").EditViolation[];
|
|
129
|
+
suggestedState?: Record<string, unknown> | undefined;
|
|
103
130
|
ok: boolean;
|
|
131
|
+
error: string;
|
|
132
|
+
} | {
|
|
133
|
+
unchecked?: string[] | undefined;
|
|
134
|
+
keyed?: string[] | undefined;
|
|
135
|
+
path?: string | undefined;
|
|
136
|
+
ok: true;
|
|
104
137
|
error?: undefined;
|
|
105
138
|
};
|
|
106
139
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zustandSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/zustand/sync/zustandSyncAdapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"zustandSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/zustand/sync/zustandSyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAenD;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;;EAG3B,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,iBAAiB;;;;EAG7B,CAAC;AAuEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;IAiB3B;;;;OAIG;;;;;;IASH;;;OAGG;6BACsB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsChC;;;;;OAKG;0BACmB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA0BT,OAAO;;;;;;;;;;;;;;;IAwG3B;;;;;;;;;;;;;;;;;;;;OAoBG;yBACwB,OAAO;;;;;;;;;;;EAqBV,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/zustand",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.36",
|
|
4
4
|
"description": "Zustand devtools inside your React Native app — state diffs, jump-to-state & one-tap store reset, no middleware. Part of Buoy devtools.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"**/snapshotProvider.ts"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@buoy-gg/
|
|
33
|
-
"@buoy-gg/
|
|
32
|
+
"@buoy-gg/shared-ui": "^7.0.36",
|
|
33
|
+
"@buoy-gg/license": "^7.0.36"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": "*",
|