@buoy-gg/zustand 7.0.35 → 7.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +27 -5
  2. package/lib/commonjs/index.js +1 -105
  3. package/lib/commonjs/preset.js +1 -102
  4. package/lib/commonjs/zustand/components/ZustandActionButton.js +1 -118
  5. package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +1 -48
  6. package/lib/commonjs/zustand/components/ZustandEventFilterView.js +1 -43
  7. package/lib/commonjs/zustand/components/ZustandIcon.js +1 -18
  8. package/lib/commonjs/zustand/components/ZustandModal.js +1 -772
  9. package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +1 -117
  10. package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +1 -372
  11. package/lib/commonjs/zustand/components/ZustandStateInfoView.js +1 -488
  12. package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +1 -204
  13. package/lib/commonjs/zustand/components/ZustandStoreDetailContent.js +1 -471
  14. package/lib/commonjs/zustand/components/ZustandStoreEditorScreen.js +1 -393
  15. package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +1 -72
  16. package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +1 -219
  17. package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +1 -284
  18. package/lib/commonjs/zustand/utils/editableState.js +1 -139
  19. package/lib/commonjs/zustand/utils/snapshotProvider.js +1 -209
  20. package/lib/commonjs/zustand/utils/testIDs.js +1 -82
  21. package/lib/commonjs/zustand/utils/zustandStateStore.js +1 -375
  22. package/lib/module/index.js +1 -90
  23. package/lib/module/preset.js +1 -98
  24. package/lib/module/zustand/components/ZustandActionButton.js +1 -114
  25. package/lib/module/zustand/components/ZustandDetailViewToggle.js +1 -43
  26. package/lib/module/zustand/components/ZustandEventFilterView.js +1 -39
  27. package/lib/module/zustand/components/ZustandIcon.js +1 -11
  28. package/lib/module/zustand/components/ZustandModal.js +1 -767
  29. package/lib/module/zustand/components/ZustandStateChangeItem.js +1 -113
  30. package/lib/module/zustand/components/ZustandStateDetailContent.js +1 -368
  31. package/lib/module/zustand/components/ZustandStateInfoView.js +1 -483
  32. package/lib/module/zustand/components/ZustandStoreBrowser.js +1 -200
  33. package/lib/module/zustand/components/ZustandStoreDetailContent.js +1 -469
  34. package/lib/module/zustand/components/ZustandStoreEditorScreen.js +1 -389
  35. package/lib/module/zustand/hooks/useZustandStateChanges.js +1 -68
  36. package/lib/module/zustand/sync/zustandSyncAdapter.js +1 -216
  37. package/lib/module/zustand/utils/buoyZustandMiddleware.js +1 -278
  38. package/lib/module/zustand/utils/editableState.js +1 -131
  39. package/lib/module/zustand/utils/snapshotProvider.js +1 -209
  40. package/lib/module/zustand/utils/testIDs.js +1 -74
  41. package/lib/module/zustand/utils/zustandStateStore.js +1 -371
  42. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +33 -0
  43. package/package.json +4 -4
  44. package/lib/typescript/index.d.ts.map +0 -1
  45. package/lib/typescript/preset.d.ts.map +0 -1
  46. package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +0 -1
  47. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +0 -1
  48. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +0 -1
  49. package/lib/typescript/zustand/components/ZustandIcon.d.ts.map +0 -1
  50. package/lib/typescript/zustand/components/ZustandModal.d.ts.map +0 -1
  51. package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +0 -1
  52. package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +0 -1
  53. package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +0 -1
  54. package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +0 -1
  55. package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts.map +0 -1
  56. package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts.map +0 -1
  57. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +0 -1
  58. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +0 -1
  59. package/lib/typescript/zustand/types/index.d.ts.map +0 -1
  60. package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +0 -1
  61. package/lib/typescript/zustand/utils/editableState.d.ts.map +0 -1
  62. package/lib/typescript/zustand/utils/snapshotProvider.d.ts.map +0 -1
  63. package/lib/typescript/zustand/utils/testIDs.d.ts.map +0 -1
  64. package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +0 -1
@@ -1,767 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Main Zustand DevTools modal
5
- *
6
- * Two tabs mirroring the Storage DevTools pattern:
7
- * - Stores tab: Browse all registered stores and their current state
8
- * - Events tab: Live state change monitoring with diffs
9
- */
10
-
11
- import { useState, useMemo, useRef, useCallback, useEffect } from "react";
12
- import { View, Text, StyleSheet, FlatList, TouchableOpacity } from "react-native";
13
- import { JsModal, ModalHeader, TabSelector, macOSColors, night, ToolEmptyState, resolveListEmptyState, Search, Filter, Power, Lock, devToolsStorageKeys, ProUpgradeModal, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider, useLimit, windowDragProps, EVENT_LIST_PRESET } from "@buoy-gg/shared-ui";
14
- import { useIsPro } from "@buoy-gg/license";
15
- import { useZustandStateChanges } from "../hooks/useZustandStateChanges";
16
- import { ZustandStateChangeItem } from "./ZustandStateChangeItem";
17
- import { ZustandStateDetailContent, ZustandStateDetailFooter } from "./ZustandStateDetailContent";
18
- import { ZustandStoreBrowser } from "./ZustandStoreBrowser";
19
- import { ZustandStoreDetailContent } from "./ZustandStoreDetailContent";
20
- import { ZustandEventFilterView } from "./ZustandEventFilterView";
21
- import { ZUSTAND_TEST_ID, buildChangeRowTestIDs } from "../utils/testIDs";
22
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
23
- /**
24
- * The change log's "nothing here". Everything but the nouns is the shared
25
- * precedence — see `ToolEmptyState` in shared-ui for why it lives there.
26
- */
27
- function EventsEmptyState({
28
- isEnabled,
29
- searchText,
30
- totalChanges
31
- }) {
32
- const content = resolveListEmptyState({
33
- subject: "changes",
34
- search: searchText,
35
- total: totalChanges,
36
- capturing: isEnabled,
37
- waiting: {
38
- otto: "waiting",
39
- title: "No state changes",
40
- message: "State changes will appear here as stores update."
41
- },
42
- paused: {
43
- title: "No state changes",
44
- message: "Enable capture to start recording state changes"
45
- }
46
- });
47
- return /*#__PURE__*/_jsx(ToolEmptyState, {
48
- ...content,
49
- testID: `${ZUSTAND_TEST_ID}-empty`
50
- });
51
- }
52
- export function ZustandModal({
53
- visible,
54
- onClose,
55
- onBack,
56
- onMinimize,
57
- enableSharedModalDimensions = false,
58
- inline = false,
59
- storesLoading = false,
60
- initialTab = "stores",
61
- initialSelectedChangeId,
62
- initialDetailView,
63
- onChangeSelect,
64
- onStoreExpand
65
- }) {
66
- const isPro = useIsPro();
67
- const changeLimit = useLimit("zustand.changes");
68
- const [showProModal, setShowProModal] = useState(false);
69
- const [activeTab, setActiveTab] = useState(initialTab);
70
- const [selectedHistoryStore, setSelectedHistoryStore] = useState(null);
71
- /**
72
- * Which store's DETAIL screen is open (live editor + details + tree —
73
- * the React Query interaction). Null means the browser list.
74
- */
75
- const [selectedStoreName, setSelectedStoreName] = useState(null);
76
- const [showFilters, setShowFilters] = useState(false);
77
- const [ignoredPatterns, setIgnoredPatterns] = useState(new Set());
78
- const {
79
- stateChanges,
80
- filteredChanges,
81
- filter,
82
- setFilter,
83
- stats,
84
- stores,
85
- clearChanges,
86
- isEnabled,
87
- toggleCapture
88
- } = useZustandStateChanges();
89
-
90
- // Apply ignored patterns on top of hook's filteredChanges
91
- const displayedChanges = useMemo(() => {
92
- if (ignoredPatterns.size === 0) return filteredChanges;
93
- return filteredChanges.filter(c => !Array.from(ignoredPatterns).some(p => c.storeName.toLowerCase().includes(p.toLowerCase())));
94
- }, [filteredChanges, ignoredPatterns]);
95
-
96
- // Apply ignored patterns to stores list too
97
- const displayedStores = useMemo(() => {
98
- if (ignoredPatterns.size === 0) return stores;
99
- return stores.filter(s => !Array.from(ignoredPatterns).some(p => s.name.toLowerCase().includes(p.toLowerCase())));
100
- }, [stores, ignoredPatterns]);
101
-
102
- /**
103
- * How many changes this store has captured, before the search — the
104
- * denominator the scoped empty state needs so "clear the search" is only
105
- * offered when clearing it would actually show something.
106
- */
107
- const scopedTotalForStore = useMemo(() => {
108
- if (!selectedHistoryStore) return 0;
109
- return stateChanges.filter(c => c.storeName === selectedHistoryStore).length;
110
- }, [stateChanges, selectedHistoryStore]);
111
-
112
- // Scope to a store BEFORE the tier cap, not after.
113
- //
114
- // The other way round, the cap was applied to the whole log and a store's
115
- // history was whatever survived it — so a store with one change, made a
116
- // while ago, opened an EMPTY history from a card that said "Changes: 1" and
117
- // offered a "view history" link. The cap is a limit on how much of a list
118
- // you can see; which list you are looking at is decided first.
119
- const scopedChanges = useMemo(() => {
120
- if (!selectedHistoryStore) return displayedChanges;
121
- return displayedChanges.filter(c => c.storeName === selectedHistoryStore);
122
- }, [displayedChanges, selectedHistoryStore]);
123
-
124
- // Pro is Infinity here, so slice() returns everything and the locked count
125
- // floors at 0 — no tier branching needed at the call site.
126
- const visibleChanges = useMemo(() => scopedChanges.slice(0, changeLimit), [scopedChanges, changeLimit]);
127
- const lockedChangeCount = useMemo(() => Math.max(0, scopedChanges.length - changeLimit), [scopedChanges.length, changeLimit]);
128
- const hasLockedChanges = lockedChangeCount > 0;
129
-
130
- /** The list the detail stepper walks: already scoped, already capped. */
131
- const activeChanges = visibleChanges;
132
-
133
- // Selection state — track by ID so new events don't shift position
134
- const [selectedChangeId, setSelectedChangeId] = useState(null);
135
- const selectedChangeIndex = useMemo(() => {
136
- if (selectedChangeId === null) return null;
137
- const idx = activeChanges.findIndex(c => c.id === selectedChangeId);
138
- return idx >= 0 ? idx : null;
139
- }, [selectedChangeId, activeChanges]);
140
- const selectedChange = selectedChangeIndex !== null ? activeChanges[selectedChangeIndex] : null;
141
-
142
- // Notify the host on selection (keyed by change id so re-renders with the
143
- // same selection don't re-fire; remote mirrors fetch state trees here).
144
- const lastNotifiedSelectionRef = useRef(null);
145
- useEffect(() => {
146
- if (!onChangeSelect) return;
147
- const id = selectedChange?.id ?? null;
148
- if (id === null || id === lastNotifiedSelectionRef.current) return;
149
- lastNotifiedSelectionRef.current = id;
150
- onChangeSelect(selectedChange);
151
- }, [selectedChange, onChangeSelect]);
152
-
153
- // Deep link into a change's detail (see initialSelectedChangeId). Applied
154
- // at most once per id, so the list re-rendering — or the user pressing Back
155
- // — never re-opens it.
156
- const appliedInitialSelectionRef = useRef(null);
157
- useEffect(() => {
158
- if (!initialSelectedChangeId) return;
159
- if (appliedInitialSelectionRef.current === initialSelectedChangeId) return;
160
- const exists = activeChanges.some(c => c.id === initialSelectedChangeId);
161
- if (!exists) return;
162
- appliedInitialSelectionRef.current = initialSelectedChangeId;
163
- setSelectedChangeId(initialSelectedChangeId);
164
- }, [initialSelectedChangeId, activeChanges]);
165
-
166
- // Search state — shared across tabs
167
- const [searchText, setSearchText] = useState("");
168
- const [isSearchActive, setIsSearchActive] = useState(false);
169
- const searchInputRef = useRef(null);
170
- const flatListRef = useRef(null);
171
- const handleModeChange = useCallback(_mode => {}, []);
172
- const handleSearch = text => {
173
- setSearchText(text);
174
- if (activeTab === "events") {
175
- setFilter(prev => ({
176
- ...prev,
177
- searchText: text
178
- }));
179
- }
180
- };
181
-
182
- /** Drop the query AND put the header back. The X, and only the X. */
183
- const closeSearch = useCallback(() => {
184
- setSearchText("");
185
- setFilter(prev => ({
186
- ...prev,
187
- searchText: ""
188
- }));
189
- setIsSearchActive(false);
190
- }, [setFilter]);
191
- const handleChangePress = useCallback(change => {
192
- setSelectedChangeId(change.id);
193
- }, []);
194
- const handleUpgradePress = useCallback(() => {
195
- setShowProModal(true);
196
- }, []);
197
- const handleBack = useCallback(() => {
198
- setSelectedChangeId(null);
199
- }, []);
200
- const handleIndexChange = useCallback(newIndex => {
201
- const change = activeChanges[newIndex];
202
- if (change) {
203
- setSelectedChangeId(change.id);
204
- }
205
- }, [activeChanges]);
206
- const handleViewHistory = useCallback(storeName => {
207
- setSelectedHistoryStore(storeName);
208
- setSelectedChangeId(null);
209
- }, []);
210
- const handleBackFromHistory = useCallback(() => {
211
- setSelectedHistoryStore(null);
212
- setSelectedChangeId(null);
213
- }, []);
214
- const handleOpenStore = useCallback(storeName => {
215
- setSelectedStoreName(storeName);
216
- // The desktop mirror hydrates a store's full state on demand — this is
217
- // the moment it becomes needed (was fired on card-expand before the
218
- // detail screen replaced expansion).
219
- onStoreExpand?.(storeName);
220
- }, [onStoreExpand]);
221
- const handleBackFromStore = useCallback(() => {
222
- setSelectedStoreName(null);
223
- }, []);
224
- const getStoresSnapshot = useCallback(() => {
225
- return displayedStores.reduce((acc, store) => {
226
- try {
227
- const state = store.api.getState();
228
- if (state && typeof state === "object") {
229
- const filtered = {};
230
- for (const [key, value] of Object.entries(state)) {
231
- if (typeof value !== "function") filtered[key] = truncatePayload(value);
232
- }
233
- acc[store.name] = filtered;
234
- } else {
235
- acc[store.name] = truncatePayload(state);
236
- }
237
- } catch {
238
- acc[store.name] = null;
239
- }
240
- return acc;
241
- }, {});
242
- }, [displayedStores]);
243
- const getEventsSnapshot = useCallback(() => {
244
- return displayedChanges.map(change => ({
245
- id: change.id,
246
- storeName: change.storeName,
247
- timestamp: change.timestamp,
248
- prevState: truncatePayload(change.prevState),
249
- nextState: truncatePayload(change.nextState),
250
- hasStateChange: change.hasStateChange,
251
- changedKeys: change.changedKeys
252
- }));
253
- }, [displayedChanges]);
254
- const getHistorySnapshot = useCallback(() => {
255
- return activeChanges.map(change => ({
256
- id: change.id,
257
- storeName: change.storeName,
258
- timestamp: change.timestamp,
259
- prevState: truncatePayload(change.prevState),
260
- nextState: truncatePayload(change.nextState),
261
- hasStateChange: change.hasStateChange,
262
- changedKeys: change.changedKeys
263
- }));
264
- }, [activeChanges]);
265
- const handleTogglePattern = useCallback(pattern => {
266
- setIgnoredPatterns(prev => {
267
- const next = new Set(prev);
268
- if (next.has(pattern)) next.delete(pattern);else next.add(pattern);
269
- return next;
270
- });
271
- }, []);
272
- const handleAddPattern = useCallback(pattern => {
273
- setIgnoredPatterns(prev => new Set([...prev, pattern]));
274
- }, []);
275
- const handleTabChange = useCallback(tab => {
276
- setActiveTab(tab);
277
- setSelectedHistoryStore(null);
278
- setSelectedStoreName(null);
279
- setSelectedChangeId(null);
280
- setShowFilters(false);
281
- if (searchText) {
282
- setSearchText("");
283
- setFilter(prev => ({
284
- ...prev,
285
- searchText: ""
286
- }));
287
- }
288
- setIsSearchActive(false);
289
- }, [searchText, setFilter]);
290
- const keyExtractor = useCallback(item => item.id, []);
291
-
292
- // Built for the whole list rather than per row: the ordinal a row carries is
293
- // a fact about the list around it. See ../utils/testIDs.ts for why a store
294
- // name alone cannot address a change row.
295
- const rowTestIDs = useMemo(() => buildChangeRowTestIDs(activeChanges), [activeChanges]);
296
- const renderItem = useCallback(({
297
- item
298
- }) => {
299
- return /*#__PURE__*/_jsx(ZustandStateChangeItem, {
300
- change: item,
301
- onPress: handleChangePress,
302
- testID: rowTestIDs.get(item.id)
303
- });
304
- }, [handleChangePress, rowTestIDs]);
305
-
306
- // ---- Header rendering ----
307
-
308
- const hasActiveFilters = ignoredPatterns.size > 0;
309
- const renderHeaderContent = () => {
310
- // Filter view header
311
- if (showFilters) {
312
- return /*#__PURE__*/_jsxs(ModalHeader, {
313
- children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
314
- onBack: () => setShowFilters(false)
315
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
316
- title: "Filters",
317
- centered: true,
318
- testID: `${ZUSTAND_TEST_ID}-filters-title`
319
- })]
320
- });
321
- }
322
-
323
- // Change detail header
324
- if (selectedChange) {
325
- return /*#__PURE__*/_jsxs(ModalHeader, {
326
- children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
327
- onBack: handleBack
328
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
329
- title: `${selectedChange.storeName}/setState`,
330
- centered: true,
331
- testID: `${ZUSTAND_TEST_ID}-detail-title`
332
- })]
333
- });
334
- }
335
-
336
- // Store history header
337
- if (selectedHistoryStore) {
338
- return /*#__PURE__*/_jsxs(ModalHeader, {
339
- children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
340
- onBack: handleBackFromHistory
341
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
342
- title: `${selectedHistoryStore} History`,
343
- centered: true,
344
- testID: `${ZUSTAND_TEST_ID}-history-title`
345
- }), /*#__PURE__*/_jsx(ModalHeader.Actions, {
346
- children: /*#__PURE__*/_jsx(ToolbarCopyButton, {
347
- value: getHistorySnapshot,
348
- disabled: activeChanges.length === 0,
349
- buttonStyle: styles.headerActionButton,
350
- testID: `${ZUSTAND_TEST_ID}-copy`
351
- })
352
- })]
353
- });
354
- }
355
-
356
- // Store detail header — the live editor screen. AFTER history: history is
357
- // reached FROM the detail screen and stacks over it, so when both are set
358
- // history owns the header, and its back lands you back on the detail.
359
- if (selectedStoreName) {
360
- return /*#__PURE__*/_jsxs(ModalHeader, {
361
- children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
362
- onBack: handleBackFromStore
363
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
364
- title: selectedStoreName,
365
- centered: true,
366
- testID: `${ZUSTAND_TEST_ID}-store-detail-title`
367
- })]
368
- });
369
- }
370
-
371
- // Main view with tabs
372
- return /*#__PURE__*/_jsxs(ModalHeader, {
373
- children: [onBack && /*#__PURE__*/_jsx(ModalHeader.Navigation, {
374
- onBack: onBack
375
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
376
- title: "",
377
- children: isSearchActive ? /*#__PURE__*/_jsx(ModalHeader.Search, {
378
- value: searchText,
379
- onChangeText: handleSearch,
380
- onCancel: closeSearch,
381
- placeholder: activeTab === "stores" ? "Search stores..." : "Search events...",
382
- accessibilityLabel: "Search Zustand stores and events",
383
- iconSize: 14,
384
- testID: `${ZUSTAND_TEST_ID}-search-field`,
385
- containerTestID: `${ZUSTAND_TEST_ID}-search`,
386
- clearTestID: `${ZUSTAND_TEST_ID}-search-clear`
387
- // Stay open while the query is still narrowing the list: the
388
- // header would otherwise go back to its TABS — whose counts are
389
- // of the WHOLE capture — above a filtered list, with nothing to
390
- // explain the gap.
391
- ,
392
- onBlur: () => {
393
- if (searchText.length === 0) setIsSearchActive(false);
394
- }
395
- }) : /*#__PURE__*/_jsx(TabSelector, {
396
- tabs: [{
397
- key: "stores",
398
- label: `Stores${displayedStores.length > 0 ? ` (${displayedStores.length})` : ""}`
399
- }, {
400
- key: "events",
401
- label: `Events${displayedChanges.length > 0 ? ` (${displayedChanges.length})` : ""}`
402
- }],
403
- activeTab: activeTab,
404
- onTabChange: handleTabChange,
405
- testIDPrefix: `${ZUSTAND_TEST_ID}-tab`
406
- })
407
- }), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
408
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
409
- onPress: () => setIsSearchActive(true),
410
- style: styles.headerActionButton,
411
- testID: `${ZUSTAND_TEST_ID}-search-open`,
412
- accessibilityRole: "button",
413
- accessibilityLabel: "Search",
414
- children: /*#__PURE__*/_jsx(Search, {
415
- size: 14,
416
- color: night.color.textSecondary
417
- })
418
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
419
- onPress: () => setShowFilters(true),
420
- style: [styles.headerActionButton, hasActiveFilters && styles.headerActionButtonActive],
421
- testID: `${ZUSTAND_TEST_ID}-filters-open`,
422
- accessibilityRole: "button",
423
- accessibilityLabel: "Filters",
424
- children: /*#__PURE__*/_jsx(Filter, {
425
- size: 14,
426
- color: hasActiveFilters ? night.color.accent : night.color.textSecondary
427
- })
428
- }), activeTab === "stores" && /*#__PURE__*/_jsx(ToolbarCopyButton, {
429
- value: getStoresSnapshot,
430
- disabled: displayedStores.length === 0,
431
- buttonStyle: styles.headerActionButton,
432
- testID: `${ZUSTAND_TEST_ID}-copy`
433
- }), activeTab === "events" && /*#__PURE__*/_jsxs(_Fragment, {
434
- children: [/*#__PURE__*/_jsx(ToolbarCopyButton, {
435
- value: getEventsSnapshot,
436
- disabled: displayedChanges.length === 0,
437
- buttonStyle: styles.headerActionButton,
438
- testID: `${ZUSTAND_TEST_ID}-copy`
439
- }), /*#__PURE__*/_jsx(PowerToggleButton, {
440
- isEnabled: isEnabled,
441
- onToggle: toggleCapture,
442
- accessibilityLabel: "Toggle Zustand state capture",
443
- testID: `${ZUSTAND_TEST_ID}-power`
444
- }), /*#__PURE__*/_jsx(ToolbarClearButton, {
445
- onPress: clearChanges,
446
- disabled: displayedChanges.length === 0,
447
- buttonStyle: styles.headerActionButton,
448
- testID: `${ZUSTAND_TEST_ID}-clear`
449
- })]
450
- })]
451
- })]
452
- });
453
- };
454
-
455
- // ---- Content rendering ----
456
-
457
- const renderContent = () => {
458
- // Filter view (applies to both tabs)
459
- if (showFilters) {
460
- return /*#__PURE__*/_jsx(ZustandEventFilterView, {
461
- ignoredPatterns: ignoredPatterns,
462
- onTogglePattern: handleTogglePattern,
463
- onAddPattern: handleAddPattern,
464
- stores: stores
465
- });
466
- }
467
-
468
- // Change detail view (works for both events tab and store history)
469
- if (selectedChange && selectedChangeIndex !== null) {
470
- return /*#__PURE__*/_jsx(ZustandStateDetailContent, {
471
- change: selectedChange,
472
- changes: activeChanges,
473
- selectedIndex: selectedChangeIndex,
474
- onIndexChange: handleIndexChange,
475
- disableInternalFooter: true,
476
- initialView: initialDetailView
477
- });
478
- }
479
-
480
- // Store history view — filtered to one store
481
- if (selectedHistoryStore) {
482
- return activeChanges.length > 0 ? /*#__PURE__*/_jsx(FlatList, {
483
- testID: `${ZUSTAND_TEST_ID}-history-list`,
484
- data: activeChanges,
485
- renderItem: renderItem,
486
- keyExtractor: keyExtractor,
487
- contentContainerStyle: styles.listContent,
488
- showsVerticalScrollIndicator: true,
489
- ...EVENT_LIST_PRESET
490
- }) : /*#__PURE__*/_jsx(ToolEmptyState, {
491
- testID: `${ZUSTAND_TEST_ID}-history-empty`,
492
- ...resolveListEmptyState({
493
- subject: "changes",
494
- search: searchText,
495
- // Scoped to one store, so the count is this store's, not the log's.
496
- total: searchText.trim() ? scopedTotalForStore : 0,
497
- capturing: isEnabled,
498
- waiting: {
499
- otto: "waiting",
500
- title: "No history yet",
501
- message: `State changes for ${selectedHistoryStore} will appear here.`
502
- },
503
- paused: {
504
- title: "No history yet",
505
- message: `Capture is off, so nothing is being recorded for ${selectedHistoryStore}.`
506
- }
507
- })
508
- });
509
- }
510
-
511
- // Store detail — live editor + details + read-only tree. After history
512
- // in this chain for the same reason as the header: history stacks OVER
513
- // the detail screen it was opened from.
514
- if (selectedStoreName) {
515
- const selectedStore = stores.find(s => s.name === selectedStoreName) ?? null;
516
- return selectedStore ? /*#__PURE__*/_jsx(ZustandStoreDetailContent, {
517
- store: selectedStore,
518
- onViewHistory: handleViewHistory
519
- }) :
520
- /*#__PURE__*/
521
- // The store fell out of the registry mid-look (a remote mirror
522
- // rebuilding, a dev reload). Say so rather than rendering nothing.
523
- _jsx(ToolEmptyState, {
524
- testID: `${ZUSTAND_TEST_ID}-store-detail-gone`
525
- // ghost = "stale or detached: an unmounted component, an old
526
- // snapshot" — exactly what the store you are looking at has become.
527
- // No other tool has this cause, so it skips the resolver.
528
- ,
529
- otto: "ghost",
530
- title: "Store no longer registered",
531
- message: `${selectedStoreName} is not in the registry any more.`
532
- });
533
- }
534
-
535
- // Stores browse tab
536
- if (activeTab === "stores") {
537
- return /*#__PURE__*/_jsx(ZustandStoreBrowser, {
538
- stores: displayedStores,
539
- searchQuery: searchText,
540
- isLoading: storesLoading,
541
- onOpenStore: handleOpenStore
542
- });
543
- }
544
-
545
- // Events tab
546
- return /*#__PURE__*/_jsxs(_Fragment, {
547
- children: [!isEnabled && /*#__PURE__*/_jsxs(View, {
548
- style: styles.disabledBanner,
549
- testID: `${ZUSTAND_TEST_ID}-banner-disabled`,
550
- children: [/*#__PURE__*/_jsx(Power, {
551
- size: 14,
552
- color: night.color.warning
553
- }), /*#__PURE__*/_jsx(Text, {
554
- style: styles.disabledText,
555
- children: "State capture is disabled"
556
- })]
557
- }), hasLockedChanges && /*#__PURE__*/_jsxs(TouchableOpacity, {
558
- style: styles.lockedBanner,
559
- onPress: handleUpgradePress,
560
- activeOpacity: 0.8,
561
- testID: `${ZUSTAND_TEST_ID}-locked`,
562
- accessibilityRole: "button",
563
- children: [/*#__PURE__*/_jsx(Lock, {
564
- size: 14,
565
- color: night.color.accent
566
- }), /*#__PURE__*/_jsxs(Text, {
567
- style: styles.lockedText,
568
- testID: `${ZUSTAND_TEST_ID}-locked-text`,
569
- children: [lockedChangeCount, " older", " ", lockedChangeCount === 1 ? "change" : "changes", " locked"]
570
- }), /*#__PURE__*/_jsx(View, {
571
- style: styles.upgradeBadge,
572
- children: /*#__PURE__*/_jsx(Text, {
573
- style: styles.upgradeBadgeText,
574
- children: "UPGRADE"
575
- })
576
- })]
577
- }), visibleChanges.length > 0 ? /*#__PURE__*/_jsx(FlatList, {
578
- ref: flatListRef,
579
- testID: `${ZUSTAND_TEST_ID}-list`,
580
- data: visibleChanges,
581
- renderItem: renderItem,
582
- keyExtractor: keyExtractor,
583
- contentContainerStyle: styles.listContent,
584
- showsVerticalScrollIndicator: true,
585
- ...EVENT_LIST_PRESET
586
- }) : /*#__PURE__*/_jsx(EventsEmptyState, {
587
- isEnabled: isEnabled,
588
- searchText: searchText,
589
- totalChanges: stats.totalChanges
590
- })]
591
- });
592
- };
593
- const persistenceKey = enableSharedModalDimensions ? devToolsStorageKeys.modal.root() : "buoy-zustand-modal";
594
- if (!visible) return null;
595
- const footerNode = selectedChange && selectedChangeIndex !== null ? /*#__PURE__*/_jsx(ZustandStateDetailFooter, {
596
- change: selectedChange,
597
- changes: activeChanges,
598
- selectedIndex: selectedChangeIndex,
599
- onIndexChange: handleIndexChange
600
- }) : null;
601
- const footerHeight = selectedChange && activeChanges.length > 1 ? 68 : 0;
602
-
603
- // The change lists own their scrolling so FlatList virtualization works
604
- // (inside JsModal's ScrollView wrapper every row mounts — the Network
605
- // tool measured 17 JS FPS / 133% CPU at max load from this exact
606
- // pattern; see tool-bench batches 2026-07-06).
607
- //
608
- // The STORES tab owns its scrolling too, for a different reason: the
609
- // browser hosts the store editor as an absolute-fill overlay over a
610
- // flex:1 root, and inside JsModal's ScrollView wrapper that root
611
- // collapses to zero height — the list still paints (iOS overflow), but
612
- // the editor opens into a 0pt box and never appears. The nested-flex:1-
613
- // in-JsModal trap; storage's browser disables the wrapper for the same
614
- // reason.
615
- const listOwnsScroll = !showFilters && !(selectedChange && selectedChangeIndex !== null) && (selectedHistoryStore != null || activeTab === "events" || activeTab === "stores");
616
- const body = /*#__PURE__*/_jsx(View, {
617
- style: styles.container,
618
- children: renderContent()
619
- });
620
- const upgradeModal = /*#__PURE__*/_jsx(ProUpgradeModal, {
621
- visible: showProModal,
622
- onClose: () => setShowProModal(false),
623
- featureName: "Full State History"
624
- });
625
-
626
- // The ONLY difference between the phone and the desktop dashboard is which
627
- // shell the header, body and footer go in. Everything above this is shared.
628
- if (inline) {
629
- return /*#__PURE__*/_jsxs(TickProvider, {
630
- children: [/*#__PURE__*/_jsxs(View, {
631
- style: styles.inlineRoot,
632
- children: [/*#__PURE__*/_jsx(View, {
633
- style: styles.inlineHeader,
634
- ...windowDragProps,
635
- children: renderHeaderContent()
636
- }), body, footerNode]
637
- }), upgradeModal]
638
- });
639
- }
640
- return /*#__PURE__*/_jsxs(TickProvider, {
641
- children: [/*#__PURE__*/_jsx(JsModal, {
642
- visible: visible,
643
- onClose: onClose,
644
- onMinimize: onMinimize,
645
- persistenceKey: persistenceKey,
646
- header: {
647
- showToggleButton: true,
648
- customContent: renderHeaderContent()
649
- },
650
- onModeChange: handleModeChange,
651
- enablePersistence: true,
652
- initialMode: "bottomSheet",
653
- enableGlitchEffects: true,
654
- disableScrollWrapper: listOwnsScroll,
655
- styles: {},
656
- footer: footerNode,
657
- footerHeight: footerHeight,
658
- children: body
659
- }), upgradeModal]
660
- });
661
- }
662
-
663
- /**
664
- * The same Zustand tool as `ZustandModal`, rendered INLINE for hosts that
665
- * place tools in a panel rather than a modal. Exists so there is exactly one
666
- * implementation — the desktop dashboard used to ship its own copy of this
667
- * component's header, list and detail router.
668
- */
669
- export function ZustandToolView(props = {}) {
670
- return /*#__PURE__*/_jsx(ZustandModal, {
671
- ...props,
672
- inline: true,
673
- visible: true,
674
- onClose: () => {}
675
- });
676
- }
677
- const styles = StyleSheet.create({
678
- inlineRoot: {
679
- flex: 1,
680
- minHeight: 0,
681
- // Transparent so the desktop workbench's hoisted sky (one field spanning
682
- // sidebar + panel) shows through — the shell paints its base beneath it.
683
- backgroundColor: "transparent"
684
- },
685
- inlineHeader: {
686
- paddingHorizontal: 12,
687
- paddingVertical: 8,
688
- borderBottomWidth: 1,
689
- borderBottomColor: macOSColors.border.default,
690
- // Clear, like Bench — the host's hoisted sky shows through the header.
691
- backgroundColor: "transparent"
692
- },
693
- container: {
694
- flex: 1,
695
- // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
696
- backgroundColor: "transparent"
697
- },
698
- headerActionButton: {
699
- width: 32,
700
- height: 32,
701
- borderRadius: 8,
702
- backgroundColor: night.color.buttonSurface,
703
- borderWidth: 1,
704
- borderColor: night.color.border,
705
- alignItems: "center",
706
- justifyContent: "center"
707
- },
708
- headerActionButtonDisabled: {
709
- opacity: 0.55
710
- },
711
- headerActionButtonActive: {
712
- backgroundColor: night.color.accentSoft,
713
- borderColor: night.color.accentBorderStrong
714
- },
715
- disabledBanner: {
716
- flexDirection: "row",
717
- alignItems: "center",
718
- gap: 8,
719
- padding: 10,
720
- marginHorizontal: 12,
721
- marginTop: 8,
722
- backgroundColor: night.color.warningSoft,
723
- borderRadius: 8,
724
- borderWidth: 1,
725
- borderColor: night.color.warning + "20"
726
- },
727
- disabledText: {
728
- color: night.color.warning,
729
- fontSize: 11,
730
- flex: 1
731
- },
732
- listContent: {
733
- paddingTop: 8,
734
- paddingBottom: 20
735
- },
736
- lockedBanner: {
737
- flexDirection: "row",
738
- alignItems: "center",
739
- gap: 8,
740
- padding: 10,
741
- marginHorizontal: 12,
742
- marginTop: 8,
743
- backgroundColor: night.color.accentSoft,
744
- borderRadius: 8,
745
- borderWidth: 1,
746
- borderColor: night.color.accentBorder
747
- },
748
- lockedText: {
749
- color: night.color.accent,
750
- fontSize: 11,
751
- fontWeight: "500",
752
- flex: 1
753
- },
754
- upgradeBadge: {
755
- backgroundColor: night.color.accent,
756
- paddingHorizontal: 8,
757
- paddingVertical: 3,
758
- borderRadius: 4
759
- },
760
- upgradeBadgeText: {
761
- // Ink on a filled accent surface — the night accent takes dark ink.
762
- color: night.color.onAccent,
763
- fontSize: 9,
764
- fontWeight: "700",
765
- letterSpacing: 0.5
766
- }
767
- });
1
+ "use strict";import{useState,useMemo,useRef,useCallback,useEffect}from"react";import{View,Text,StyleSheet,FlatList,TouchableOpacity}from"react-native";import{JsModal,ModalHeader,TabSelector,macOSColors,night,ToolEmptyState,resolveListEmptyState,Search,Filter,Power,Lock,devToolsStorageKeys,ProUpgradeModal,PowerToggleButton,ToolbarCopyButton,ToolbarClearButton,truncatePayload,TickProvider,useLimit,windowDragProps,EVENT_LIST_PRESET}from"@buoy-gg/shared-ui";import{useIsPro}from"@buoy-gg/license";import{useZustandStateChanges}from"../hooks/useZustandStateChanges";import{ZustandStateChangeItem}from"./ZustandStateChangeItem";import{ZustandStateDetailContent,ZustandStateDetailFooter}from"./ZustandStateDetailContent";import{ZustandStoreBrowser}from"./ZustandStoreBrowser";import{ZustandStoreDetailContent}from"./ZustandStoreDetailContent";import{ZustandEventFilterView}from"./ZustandEventFilterView";import{ZUSTAND_TEST_ID,buildChangeRowTestIDs}from"../utils/testIDs";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";function EventsEmptyState({isEnabled:e,searchText:t,totalChanges:a}){const o=resolveListEmptyState({subject:"changes",search:t,total:a,capturing:e,waiting:{otto:"waiting",title:"No state changes",message:"State changes will appear here as stores update."},paused:{title:"No state changes",message:"Enable capture to start recording state changes"}});return _jsx(ToolEmptyState,{...o,testID:`${ZUSTAND_TEST_ID}-empty`})}export function ZustandModal({visible:e,onClose:t,onBack:a,onMinimize:o,enableSharedModalDimensions:s=!1,inline:n=!1,storesLoading:r=!1,initialTab:l="stores",initialSelectedChangeId:i,initialDetailView:c,onChangeSelect:d,onStoreExpand:u}){useIsPro();const g=useLimit("zustand.changes"),[h,S]=useState(!1),[T,b]=useState(l),[m,y]=useState(null),[_,p]=useState(null),[x,C]=useState(!1),[D,f]=useState(new Set),{stateChanges:I,filteredChanges:E,filter:j,setFilter:w,stats:k,stores:Z,clearChanges:N,isEnabled:A,toggleCapture:B}=useZustandStateChanges(),v=useMemo(()=>0===D.size?E:E.filter(e=>!Array.from(D).some(t=>e.storeName.toLowerCase().includes(t.toLowerCase()))),[E,D]),M=useMemo(()=>0===D.size?Z:Z.filter(e=>!Array.from(D).some(t=>e.name.toLowerCase().includes(t.toLowerCase()))),[Z,D]),P=useMemo(()=>m?I.filter(e=>e.storeName===m).length:0,[I,m]),$=useMemo(()=>m?v.filter(e=>e.storeName===m):v,[v,m]),U=useMemo(()=>$.slice(0,g),[$,g]),H=useMemo(()=>Math.max(0,$.length-g),[$.length,g]),L=H>0,R=U,[V,z]=useState(null),F=useMemo(()=>{if(null===V)return null;const e=R.findIndex(e=>e.id===V);return e>=0?e:null},[V,R]),O=null!==F?R[F]:null,K=useRef(null);useEffect(()=>{if(!d)return;const e=O?.id??null;null!==e&&e!==K.current&&(K.current=e,d(O))},[O,d]);const W=useRef(null);useEffect(()=>{i&&W.current!==i&&R.some(e=>e.id===i)&&(W.current=i,z(i))},[i,R]);const[G,J]=useState(""),[Q,q]=useState(!1),X=(useRef(null),useRef(null)),Y=useCallback(e=>{},[]),ee=e=>{J(e),"events"===T&&w(t=>({...t,searchText:e}))},te=useCallback(()=>{J(""),w(e=>({...e,searchText:""})),q(!1)},[w]),ae=useCallback(e=>{z(e.id)},[]),oe=useCallback(()=>{S(!0)},[]),se=useCallback(()=>{z(null)},[]),ne=useCallback(e=>{const t=R[e];t&&z(t.id)},[R]),re=useCallback(e=>{y(e),z(null)},[]),le=useCallback(()=>{y(null),z(null)},[]),ie=useCallback(e=>{p(e),u?.(e)},[u]),ce=useCallback(()=>{p(null)},[]),de=useCallback(()=>M.reduce((e,t)=>{try{const a=t.api.getState();if(a&&"object"==typeof a){const o={};for(const[e,t]of Object.entries(a))"function"!=typeof t&&(o[e]=truncatePayload(t));e[t.name]=o}else e[t.name]=truncatePayload(a)}catch{e[t.name]=null}return e},{}),[M]),ue=useCallback(()=>v.map(e=>({id:e.id,storeName:e.storeName,timestamp:e.timestamp,prevState:truncatePayload(e.prevState),nextState:truncatePayload(e.nextState),hasStateChange:e.hasStateChange,changedKeys:e.changedKeys})),[v]),ge=useCallback(()=>R.map(e=>({id:e.id,storeName:e.storeName,timestamp:e.timestamp,prevState:truncatePayload(e.prevState),nextState:truncatePayload(e.nextState),hasStateChange:e.hasStateChange,changedKeys:e.changedKeys})),[R]),he=useCallback(e=>{f(t=>{const a=new Set(t);return a.has(e)?a.delete(e):a.add(e),a})},[]),Se=useCallback(e=>{f(t=>new Set([...t,e]))},[]),Te=useCallback(e=>{b(e),y(null),p(null),z(null),C(!1),G&&(J(""),w(e=>({...e,searchText:""}))),q(!1)},[G,w]),be=useCallback(e=>e.id,[]),me=useMemo(()=>buildChangeRowTestIDs(R),[R]),ye=useCallback(({item:e})=>_jsx(ZustandStateChangeItem,{change:e,onPress:ae,testID:me.get(e.id)}),[ae,me]),_e=D.size>0,pe=()=>_jsxs(ModalHeader,x?{children:[_jsx(ModalHeader.Navigation,{onBack:()=>C(!1)}),_jsx(ModalHeader.Content,{title:"Filters",centered:!0,testID:`${ZUSTAND_TEST_ID}-filters-title`})]}:O?{children:[_jsx(ModalHeader.Navigation,{onBack:se}),_jsx(ModalHeader.Content,{title:`${O.storeName}/setState`,centered:!0,testID:`${ZUSTAND_TEST_ID}-detail-title`})]}:m?{children:[_jsx(ModalHeader.Navigation,{onBack:le}),_jsx(ModalHeader.Content,{title:`${m} History`,centered:!0,testID:`${ZUSTAND_TEST_ID}-history-title`}),_jsx(ModalHeader.Actions,{children:_jsx(ToolbarCopyButton,{value:ge,disabled:0===R.length,buttonStyle:styles.headerActionButton,testID:`${ZUSTAND_TEST_ID}-copy`})})]}:_?{children:[_jsx(ModalHeader.Navigation,{onBack:ce}),_jsx(ModalHeader.Content,{title:_,centered:!0,testID:`${ZUSTAND_TEST_ID}-store-detail-title`})]}:{children:[a&&_jsx(ModalHeader.Navigation,{onBack:a}),_jsx(ModalHeader.Content,{title:"",children:Q?_jsx(ModalHeader.Search,{value:G,onChangeText:ee,onCancel:te,placeholder:"stores"===T?"Search stores...":"Search events...",accessibilityLabel:"Search Zustand stores and events",iconSize:14,testID:`${ZUSTAND_TEST_ID}-search-field`,containerTestID:`${ZUSTAND_TEST_ID}-search`,clearTestID:`${ZUSTAND_TEST_ID}-search-clear`,onBlur:()=>{0===G.length&&q(!1)}}):_jsx(TabSelector,{tabs:[{key:"stores",label:"Stores"+(M.length>0?` (${M.length})`:"")},{key:"events",label:"Events"+(v.length>0?` (${v.length})`:"")}],activeTab:T,onTabChange:Te,testIDPrefix:`${ZUSTAND_TEST_ID}-tab`})}),_jsxs(ModalHeader.Actions,{children:[_jsx(TouchableOpacity,{onPress:()=>q(!0),style:styles.headerActionButton,testID:`${ZUSTAND_TEST_ID}-search-open`,accessibilityRole:"button",accessibilityLabel:"Search",children:_jsx(Search,{size:14,color:night.color.textSecondary})}),_jsx(TouchableOpacity,{onPress:()=>C(!0),style:[styles.headerActionButton,_e&&styles.headerActionButtonActive],testID:`${ZUSTAND_TEST_ID}-filters-open`,accessibilityRole:"button",accessibilityLabel:"Filters",children:_jsx(Filter,{size:14,color:_e?night.color.accent:night.color.textSecondary})}),"stores"===T&&_jsx(ToolbarCopyButton,{value:de,disabled:0===M.length,buttonStyle:styles.headerActionButton,testID:`${ZUSTAND_TEST_ID}-copy`}),"events"===T&&_jsxs(_Fragment,{children:[_jsx(ToolbarCopyButton,{value:ue,disabled:0===v.length,buttonStyle:styles.headerActionButton,testID:`${ZUSTAND_TEST_ID}-copy`}),_jsx(PowerToggleButton,{isEnabled:A,onToggle:B,accessibilityLabel:"Toggle Zustand state capture",testID:`${ZUSTAND_TEST_ID}-power`}),_jsx(ToolbarClearButton,{onPress:N,disabled:0===v.length,buttonStyle:styles.headerActionButton,testID:`${ZUSTAND_TEST_ID}-clear`})]})]})]}),xe=s?devToolsStorageKeys.modal.root():"buoy-zustand-modal";if(!e)return null;const Ce=O&&null!==F?_jsx(ZustandStateDetailFooter,{change:O,changes:R,selectedIndex:F,onIndexChange:ne}):null,De=O&&R.length>1?68:0,fe=!(x||O&&null!==F||null==m&&"events"!==T&&"stores"!==T),Ie=_jsx(View,{style:styles.container,children:(()=>{if(x)return _jsx(ZustandEventFilterView,{ignoredPatterns:D,onTogglePattern:he,onAddPattern:Se,stores:Z});if(O&&null!==F)return _jsx(ZustandStateDetailContent,{change:O,changes:R,selectedIndex:F,onIndexChange:ne,disableInternalFooter:!0,initialView:c});if(m)return R.length>0?_jsx(FlatList,{testID:`${ZUSTAND_TEST_ID}-history-list`,data:R,renderItem:ye,keyExtractor:be,contentContainerStyle:styles.listContent,showsVerticalScrollIndicator:!0,...EVENT_LIST_PRESET}):_jsx(ToolEmptyState,{testID:`${ZUSTAND_TEST_ID}-history-empty`,...resolveListEmptyState({subject:"changes",search:G,total:G.trim()?P:0,capturing:A,waiting:{otto:"waiting",title:"No history yet",message:`State changes for ${m} will appear here.`},paused:{title:"No history yet",message:`Capture is off, so nothing is being recorded for ${m}.`}})});if(_){const e=Z.find(e=>e.name===_)??null;return e?_jsx(ZustandStoreDetailContent,{store:e,onViewHistory:re}):_jsx(ToolEmptyState,{testID:`${ZUSTAND_TEST_ID}-store-detail-gone`,otto:"ghost",title:"Store no longer registered",message:`${_} is not in the registry any more.`})}return"stores"===T?_jsx(ZustandStoreBrowser,{stores:M,searchQuery:G,isLoading:r,onOpenStore:ie}):_jsxs(_Fragment,{children:[!A&&_jsxs(View,{style:styles.disabledBanner,testID:`${ZUSTAND_TEST_ID}-banner-disabled`,children:[_jsx(Power,{size:14,color:night.color.warning}),_jsx(Text,{style:styles.disabledText,children:"State capture is disabled"})]}),L&&_jsxs(TouchableOpacity,{style:styles.lockedBanner,onPress:oe,activeOpacity:.8,testID:`${ZUSTAND_TEST_ID}-locked`,accessibilityRole:"button",children:[_jsx(Lock,{size:14,color:night.color.accent}),_jsxs(Text,{style:styles.lockedText,testID:`${ZUSTAND_TEST_ID}-locked-text`,children:[H," older"," ",1===H?"change":"changes"," locked"]}),_jsx(View,{style:styles.upgradeBadge,children:_jsx(Text,{style:styles.upgradeBadgeText,children:"UPGRADE"})})]}),U.length>0?_jsx(FlatList,{ref:X,testID:`${ZUSTAND_TEST_ID}-list`,data:U,renderItem:ye,keyExtractor:be,contentContainerStyle:styles.listContent,showsVerticalScrollIndicator:!0,...EVENT_LIST_PRESET}):_jsx(EventsEmptyState,{isEnabled:A,searchText:G,totalChanges:k.totalChanges})]})})()}),Ee=_jsx(ProUpgradeModal,{visible:h,onClose:()=>S(!1),featureName:"Full State History"});return _jsxs(TickProvider,n?{children:[_jsxs(View,{style:styles.inlineRoot,children:[_jsx(View,{style:styles.inlineHeader,...windowDragProps,children:pe()}),Ie,Ce]}),Ee]}:{children:[_jsx(JsModal,{visible:e,onClose:t,onMinimize:o,persistenceKey:xe,header:{showToggleButton:!0,customContent:pe()},onModeChange:Y,enablePersistence:!0,initialMode:"bottomSheet",enableGlitchEffects:!0,disableScrollWrapper:fe,styles:{},footer:Ce,footerHeight:De,children:Ie}),Ee]})}export function ZustandToolView(e={}){return _jsx(ZustandModal,{...e,inline:!0,visible:!0,onClose:()=>{}})}const styles=StyleSheet.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:"transparent"},inlineHeader:{paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:macOSColors.border.default,backgroundColor:"transparent"},container:{flex:1,backgroundColor:"transparent"},headerActionButton:{width:32,height:32,borderRadius:8,backgroundColor:night.color.buttonSurface,borderWidth:1,borderColor:night.color.border,alignItems:"center",justifyContent:"center"},headerActionButtonDisabled:{opacity:.55},headerActionButtonActive:{backgroundColor:night.color.accentSoft,borderColor:night.color.accentBorderStrong},disabledBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:night.color.warningSoft,borderRadius:8,borderWidth:1,borderColor:night.color.warning+"20"},disabledText:{color:night.color.warning,fontSize:11,flex:1},listContent:{paddingTop:8,paddingBottom:20},lockedBanner:{flexDirection:"row",alignItems:"center",gap:8,padding:10,marginHorizontal:12,marginTop:8,backgroundColor:night.color.accentSoft,borderRadius:8,borderWidth:1,borderColor:night.color.accentBorder},lockedText:{color:night.color.accent,fontSize:11,fontWeight:"500",flex:1},upgradeBadge:{backgroundColor:night.color.accent,paddingHorizontal:8,paddingVertical:3,borderRadius:4},upgradeBadgeText:{color:night.color.onAccent,fontSize:9,fontWeight:"700",letterSpacing:.5}});