@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.
@@ -112,7 +112,7 @@ const styles = _reactNative.StyleSheet.create({
112
112
  fontWeight: "600",
113
113
  letterSpacing: 0.5,
114
114
  textTransform: "uppercase",
115
- fontFamily: "monospace",
115
+ fontFamily: _sharedUi.monoFont,
116
116
  height: 12
117
117
  }
118
118
  });
@@ -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: "monospace",
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: "monospace",
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: "monospace"
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: "monospace",
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace",
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: "monospace"
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: "monospace"
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: "monospace",
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
- store.api.setState(state, replace ?? true);
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: "monospace",
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: "monospace",
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: "monospace",
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: "monospace"
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: "monospace",
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
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: "monospace"
460
+ fontFamily: monoFont
461
461
  },
462
462
  actionsText: {
463
463
  flex: 1,
464
464
  color: night.color.textSecondary,
465
465
  fontSize: 10,
466
- fontFamily: "monospace",
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: "monospace"
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: "monospace"
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: "monospace",
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
- store.api.setState(state, replace ?? true);
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;AAiBH,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
+ {"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;AAkBtE,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
+ {"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;AA2BH,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
+ {"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;AA4BH,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"}
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":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAenD;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;;EAG3B,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,iBAAiB;;;;EAG7B,CAAC;AAsCF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;IAiB3B;;;;OAIG;;;;;;IASH;;;OAGG;6BACsB,OAAO;;;;;;;;;;;IAsBhC;;;;;OAKG;0BACmB,OAAO;;;;;;;;;;;;;;;;;;;;wBAsBT,OAAO;;;;;;;IAQ3B;;;;;;;;;;;;;;;;;;;;OAoBG;yBACwB,OAAO;;;;;;;;;;;EAqBV,CAAC"}
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.34",
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/license": "^7.0.34",
33
- "@buoy-gg/shared-ui": "^7.0.34"
32
+ "@buoy-gg/shared-ui": "^7.0.36",
33
+ "@buoy-gg/license": "^7.0.36"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": "*",