@buoy-gg/core 7.0.40 → 7.0.41

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 (140) hide show
  1. package/README.md +10 -0
  2. package/bin/buoy.js +25 -31
  3. package/lib/commonjs/browserRegister.js +48 -0
  4. package/lib/commonjs/browserRegister.server.js +10 -0
  5. package/lib/commonjs/floatingMenu/AppHost.web.js +71 -59
  6. package/lib/commonjs/floatingMenu/BrowserToolRuntime.js +150 -0
  7. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +10 -1111
  8. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.shared.js +1114 -0
  9. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +28 -1028
  10. package/lib/commonjs/floatingMenu/FloatingDevTools.js +7 -2
  11. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +54 -40
  12. package/lib/commonjs/floatingMenu/MinimizedToolsStack.web.js +8 -2
  13. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +10 -0
  14. package/lib/commonjs/floatingMenu/autoExternalSync.js +9 -0
  15. package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +86 -12
  16. package/lib/commonjs/floatingMenu/dial/DialIcon.js +4 -2
  17. package/lib/commonjs/floatingMenu/settingsBus.js +6 -17
  18. package/lib/commonjs/floatingMenu/useDesktopSyncStatus.web.js +14 -0
  19. package/lib/commonjs/index.web.js +92 -1
  20. package/lib/commonjs/reactDevtoolsTypes.js +1 -0
  21. package/lib/commonjs/utils/autoDiscoverPresets.web.js +5 -178
  22. package/lib/commonjs/utils/createWebTool.js +20 -0
  23. package/lib/commonjs/utils/createWebTools.js +30 -0
  24. package/lib/commonjs/version.js +1 -1
  25. package/lib/module/browserRegister.js +44 -0
  26. package/lib/module/browserRegister.server.js +6 -0
  27. package/lib/module/floatingMenu/AppHost.web.js +71 -59
  28. package/lib/module/floatingMenu/BrowserToolRuntime.js +146 -0
  29. package/lib/module/floatingMenu/DevToolsSettingsModal.js +1 -1113
  30. package/lib/module/floatingMenu/DevToolsSettingsModal.shared.js +1110 -0
  31. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +32 -1043
  32. package/lib/module/floatingMenu/FloatingDevTools.js +7 -2
  33. package/lib/module/floatingMenu/FloatingDevTools.web.js +55 -42
  34. package/lib/module/floatingMenu/MinimizedToolsStack.web.js +8 -2
  35. package/lib/module/floatingMenu/autoDiscoverPresets.js +10 -0
  36. package/lib/module/floatingMenu/autoExternalSync.js +9 -0
  37. package/lib/module/floatingMenu/dial/DialDevTools.web.js +87 -14
  38. package/lib/module/floatingMenu/dial/DialIcon.js +4 -2
  39. package/lib/module/floatingMenu/settingsBus.js +1 -17
  40. package/lib/module/floatingMenu/useDesktopSyncStatus.web.js +10 -0
  41. package/lib/module/index.web.js +8 -1
  42. package/lib/module/reactDevtoolsTypes.js +1 -0
  43. package/lib/module/utils/autoDiscoverPresets.web.js +5 -180
  44. package/lib/module/utils/createWebTool.js +17 -0
  45. package/lib/module/utils/createWebTools.js +25 -0
  46. package/lib/module/version.js +1 -1
  47. package/lib/typescript/commonjs/browserRegister.d.ts +5 -0
  48. package/lib/typescript/commonjs/browserRegister.d.ts.map +1 -0
  49. package/lib/typescript/commonjs/browserRegister.server.d.ts +5 -0
  50. package/lib/typescript/commonjs/browserRegister.server.d.ts.map +1 -0
  51. package/lib/typescript/commonjs/floatingMenu/AppHost.web.d.ts +3 -3
  52. package/lib/typescript/commonjs/floatingMenu/AppHost.web.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/floatingMenu/BrowserToolRuntime.d.ts +14 -0
  54. package/lib/typescript/commonjs/floatingMenu/BrowserToolRuntime.d.ts.map +1 -0
  55. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts +2 -67
  56. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.shared.d.ts +71 -0
  58. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.shared.d.ts.map +1 -0
  59. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts +3 -16
  60. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts +12 -6
  63. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.web.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +1 -1
  68. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
  70. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts +1 -9
  72. package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/floatingMenu/useDesktopSyncStatus.web.d.ts +3 -0
  74. package/lib/typescript/commonjs/floatingMenu/useDesktopSyncStatus.web.d.ts.map +1 -0
  75. package/lib/typescript/commonjs/index.web.d.ts +6 -0
  76. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/reactDevtoolsTypes.d.ts +8 -0
  78. package/lib/typescript/commonjs/reactDevtoolsTypes.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts +3 -40
  80. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/utils/createWebTool.d.ts +6 -0
  82. package/lib/typescript/commonjs/utils/createWebTool.d.ts.map +1 -0
  83. package/lib/typescript/commonjs/utils/createWebTools.d.ts +4 -0
  84. package/lib/typescript/commonjs/utils/createWebTools.d.ts.map +1 -0
  85. package/lib/typescript/commonjs/version.d.ts +1 -1
  86. package/lib/typescript/module/browserRegister.d.ts +5 -0
  87. package/lib/typescript/module/browserRegister.d.ts.map +1 -0
  88. package/lib/typescript/module/browserRegister.server.d.ts +5 -0
  89. package/lib/typescript/module/browserRegister.server.d.ts.map +1 -0
  90. package/lib/typescript/module/floatingMenu/AppHost.web.d.ts +3 -3
  91. package/lib/typescript/module/floatingMenu/AppHost.web.d.ts.map +1 -1
  92. package/lib/typescript/module/floatingMenu/BrowserToolRuntime.d.ts +14 -0
  93. package/lib/typescript/module/floatingMenu/BrowserToolRuntime.d.ts.map +1 -0
  94. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts +2 -67
  95. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  96. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.shared.d.ts +71 -0
  97. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.shared.d.ts.map +1 -0
  98. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts +3 -16
  99. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  100. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  101. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts +12 -6
  102. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  103. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.web.d.ts.map +1 -1
  104. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  105. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
  106. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +1 -1
  107. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -1
  108. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
  109. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
  110. package/lib/typescript/module/floatingMenu/settingsBus.d.ts +1 -9
  111. package/lib/typescript/module/floatingMenu/settingsBus.d.ts.map +1 -1
  112. package/lib/typescript/module/floatingMenu/useDesktopSyncStatus.web.d.ts +3 -0
  113. package/lib/typescript/module/floatingMenu/useDesktopSyncStatus.web.d.ts.map +1 -0
  114. package/lib/typescript/module/index.web.d.ts +6 -0
  115. package/lib/typescript/module/index.web.d.ts.map +1 -1
  116. package/lib/typescript/module/reactDevtoolsTypes.d.ts +8 -0
  117. package/lib/typescript/module/reactDevtoolsTypes.d.ts.map +1 -0
  118. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts +3 -40
  119. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts.map +1 -1
  120. package/lib/typescript/module/utils/createWebTool.d.ts +6 -0
  121. package/lib/typescript/module/utils/createWebTool.d.ts.map +1 -0
  122. package/lib/typescript/module/utils/createWebTools.d.ts +4 -0
  123. package/lib/typescript/module/utils/createWebTools.d.ts.map +1 -0
  124. package/lib/typescript/module/version.d.ts +1 -1
  125. package/lib/web/chunks/chunk-5NZOQR6Q.mjs +2 -0
  126. package/lib/web/chunks/chunk-CGDSLLZF.mjs +2 -0
  127. package/lib/web/chunks/chunk-DCJDUSNR.mjs +2 -0
  128. package/lib/web/chunks/chunk-IDSHFS6G.mjs +2 -0
  129. package/lib/web/chunks/chunk-JOAMJGCS.mjs +2 -0
  130. package/lib/web/chunks/chunk-KYKZ2N52.mjs +2 -0
  131. package/lib/web/chunks/chunk-NGM7JGGS.mjs +2 -0
  132. package/lib/web/chunks/chunk-QZDVCMCT.mjs +2 -0
  133. package/lib/web/chunks/chunk-R7RGRTY5.mjs +2 -0
  134. package/lib/web/chunks/chunk-Z3XWDDIO.mjs +2 -0
  135. package/lib/web/chunks/chunk-ZTGQ6TTY.mjs +2 -0
  136. package/lib/web/index.mjs +11 -0
  137. package/lib/web/register-server.mjs +2 -0
  138. package/lib/web/register.mjs +2 -0
  139. package/lib/web/toolkit.mjs +2 -0
  140. package/package.json +50 -12
@@ -3,1117 +3,16 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.useDevToolsSettings = exports.DevToolsSettingsModal = void 0;
7
- var _react = require("react");
8
- var _reactNative = require("react-native");
9
- var _settingsBus = require("./settingsBus.js");
10
- var _useDesktopSyncStatus = require("./useDesktopSyncStatus.js");
11
- var _sharedUi = require("@buoy-gg/shared-ui");
12
- var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
13
- var _DefaultConfigContext = require("./DefaultConfigContext.js");
14
- var _AppIcon = require("./AppIcon");
15
- var _jsxRuntime = require("react/jsx-runtime");
16
- const STORAGE_KEY = "@react_buoy_dev_tools_settings";
17
- const MAX_DIAL_SLOTS = 6;
18
-
19
- // Lazy load license hooks to avoid circular dependencies
20
- let _useLicense = null;
21
- function getUseLicense() {
22
- if (!_useLicense) {
23
- try {
24
- const mod = require("@buoy-gg/license");
25
- _useLicense = mod.useLicense;
26
- } catch {
27
- // License package not available
28
- }
29
- }
30
- return _useLicense;
31
- }
32
- const enforceDialLimit = dialTools => {
33
- let remaining = MAX_DIAL_SLOTS;
34
- const limited = {};
35
- for (const [id, enabled] of Object.entries(dialTools)) {
36
- if (enabled && remaining > 0) {
37
- limited[id] = true;
38
- remaining -= 1;
39
- } else {
40
- limited[id] = false;
41
- }
42
- }
43
- return limited;
44
- };
45
- const sanitizeFloating = (floating, allowedKeys) => {
46
- const {
47
- userStatus,
48
- environment,
49
- ...rest
50
- } = floating;
51
- const filteredEntries = allowedKeys ? Object.entries(rest).filter(([key]) => allowedKeys.includes(key)) : Object.entries(rest);
52
- return {
53
- ...Object.fromEntries(filteredEntries),
54
- environment: environment ?? false
55
- };
56
- };
57
- const mergeWithDefaults = (defaults, stored, options) => {
58
- if (!stored) return defaults;
59
- const combinedDial = {
60
- ...defaults.dialTools,
61
- ...(stored.dialTools ?? {})
62
- };
63
- const dialEntries = options?.allowedDialKeys ? Object.entries(combinedDial).filter(([key]) => options.allowedDialKeys?.includes(key)) : Object.entries(combinedDial);
64
- const mergedDial = enforceDialLimit(Object.fromEntries(dialEntries));
65
- return {
66
- dialTools: mergedDial,
67
- floatingTools: sanitizeFloating({
68
- ...defaults.floatingTools,
69
- ...(stored.floatingTools ?? {})
70
- }, options?.allowedFloatingKeys),
71
- globalSettings: {
72
- enableSharedModalDimensions: false,
73
- // Default to false
74
- expandableWindowControls: true,
75
- // Default to true
76
- ...(stored.globalSettings ?? {})
77
- }
78
- };
79
- };
80
-
81
- /**
82
- * Global settings that apply to all dev tools.
83
- * These settings override individual tool configurations.
84
- */
85
-
86
- /**
87
- * Serialized preferences that power the floating dev tools UI. Values persist across sessions
88
- * via AsyncStorage so teams can tailor which tools appear in the dial or floating row.
89
- */
90
-
91
- /**
92
- * The subset of InstalledApp the settings modal needs per tool. `icon`/`color`
93
- * let rows fall back to the tool's own glyph when getToolIcon() has no
94
- * night-tuned override for it.
95
- */
96
-
97
- /**
98
- * Generate default settings based on available apps and optional team default configuration.
99
- *
100
- * @param availableApps - List of available apps from auto-discovery
101
- * @param defaultFloatingTools - Optional array of tool IDs to enable by default in floating bubble
102
- * @param defaultDialTools - Optional array of tool IDs to enable by default in dial menu
103
- * If not provided, all dial-compatible tools are enabled by default (up to 6)
104
- */
105
- const generateDefaultSettings = (availableApps = [], defaultFloatingTools, defaultDialTools) => {
106
- const dialDefaults = {};
107
- const floatingDefaults = {};
108
-
109
- // Create sets for quick lookup of default-enabled tools
110
- // Cast to Set<string> to allow comparison with any tool ID (including custom tools)
111
- const enabledFloatingSet = new Set(defaultFloatingTools ?? []);
112
- const enabledDialSet = new Set(defaultDialTools ?? []);
113
-
114
- // If no defaultDialTools specified, auto-enable all dial-compatible tools
115
- const autoEnableDialTools = !defaultDialTools || defaultDialTools.length === 0;
116
- for (const app of availableApps) {
117
- const {
118
- id,
119
- slot = "both"
120
- } = app;
121
- if (slot === "dial" || slot === "both") {
122
- // Auto-enable if no defaults specified, otherwise check the set
123
- dialDefaults[id] = autoEnableDialTools ? true : enabledDialSet.has(id);
124
- }
125
- if (slot === "row" || slot === "both") {
126
- // Enable if in defaultFloatingTools, otherwise false
127
- floatingDefaults[id] = enabledFloatingSet.has(id);
128
- }
6
+ Object.defineProperty(exports, "DevToolsSettingsModal", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _DevToolsSettingsModalShared.DevToolsSettingsModal;
129
10
  }
130
- return {
131
- dialTools: enforceDialLimit(dialDefaults),
132
- floatingTools: {
133
- ...floatingDefaults,
134
- // Special: environment badge - check if 'environment' is in the floating defaults
135
- environment: enabledFloatingSet.has('environment')
136
- },
137
- globalSettings: {
138
- enableSharedModalDimensions: false,
139
- // Default to false - each modal has its own persistence
140
- expandableWindowControls: true // Default to true - expandable on touch devices
141
- }
142
- };
143
- };
144
-
145
- /**
146
- * Configurable modal surface that lets engineers pick which dev tools appear in the dial and
147
- * floating row. Persists preferences and enforces slot limits for a consistent UX.
148
- *
149
- * Styled in the "night" language (Everlights port — see shared-ui's
150
- * ui/night): section labels outside near-black cards, hairline-separated
151
- * rows, real switches, and a transparent header the star field runs through.
152
- */
153
- const DevToolsSettingsModal = ({
154
- visible,
155
- onClose,
156
- onSettingsChange,
157
- initialSettings,
158
- availableApps = []
159
- }) => {
160
- // Get team default configuration from context
161
- const {
162
- defaultFloatingTools,
163
- defaultDialTools
164
- } = (0, _DefaultConfigContext.useDefaultConfig)();
165
- const defaultSettings = (0, _react.useMemo)(() => generateDefaultSettings(availableApps, defaultFloatingTools, defaultDialTools), [availableApps, defaultFloatingTools, defaultDialTools]);
166
- const allowedDialKeys = (0, _react.useMemo)(() => Object.keys(defaultSettings.dialTools), [defaultSettings]);
167
- const allowedFloatingKeys = (0, _react.useMemo)(() => Object.keys(defaultSettings.floatingTools).filter(key => key !== "environment"), [defaultSettings]);
168
- const [settings, setSettings] = (0, _react.useState)(initialSettings || cachedSettings || defaultSettings);
169
- const [activeTab, setActiveTab] = (0, _react.useState)(cachedActiveTab ?? "floating");
170
- const [activeTabLoaded, setActiveTabLoaded] = (0, _react.useState)(cachedActiveTab !== null);
171
- const [expandedSettings, setExpandedSettings] = (0, _react.useState)(new Set());
172
- const [showLicenseModal, setShowLicenseModal] = (0, _react.useState)(false);
173
- /**
174
- * Tool icons are the expensive half of this modal: ~20 of them, each a
175
- * data-driven glyph that mounts one view per shape. Rendering them in the
176
- * first pass pushed the modal's first paint out to ~240ms, so the tap felt
177
- * dead. Holding them back a single frame lets the chrome and the tool rows
178
- * paint at ~100ms and the glyphs land during the entrance fade.
179
- *
180
- * There is no layout shift: `styles.toolIconSlot` is a fixed box, so it
181
- * reads as the icon's placeholder until filled.
182
- */
183
- const [iconsReady, setIconsReady] = (0, _react.useState)(false);
184
- (0, _react.useEffect)(() => {
185
- const frame = requestAnimationFrame(() => setIconsReady(true));
186
- return () => cancelAnimationFrame(frame);
187
- }, []);
188
-
189
- // null when external-sync isn't installed (or predates the status store);
190
- // "idle" until AutoExternalSync initializes — the row hides in both cases.
191
- const desktopSyncStatus = (0, _useDesktopSyncStatus.useDesktopSyncStatus)();
192
-
193
- // Load persisted active tab on mount
194
- (0, _react.useEffect)(() => {
195
- const loadActiveTab = async () => {
196
- try {
197
- const savedTab = await _sharedUi.persistentStorage.getItem(_sharedUi.devToolsStorageKeys.settings.activeTab());
198
- if (savedTab && ["floating", "settings", "pro"].includes(savedTab)) {
199
- const tab = savedTab;
200
- cachedActiveTab = tab;
201
- setActiveTab(prev => prev === tab ? prev : tab);
202
- }
203
- } catch (error) {
204
- // Failed to load active tab - use default
205
- } finally {
206
- setActiveTabLoaded(true);
207
- }
208
- };
209
- loadActiveTab();
210
- }, []);
211
-
212
- // Persist active tab when it changes
213
- (0, _react.useEffect)(() => {
214
- // Only persist after initial state is loaded to avoid overwriting with default
215
- if (!activeTabLoaded) return;
216
- _sharedUi.persistentStorage.setItem(_sharedUi.devToolsStorageKeys.settings.activeTab(), activeTab).catch(error => {
217
- // Failed to save active tab - continue without persistence
218
- console.warn("Failed to save settings active tab:", error);
219
- });
220
- }, [activeTab, activeTabLoaded]);
221
-
222
- // License hooks
223
- const useLicenseHook = getUseLicense();
224
- const license = useLicenseHook?.();
225
- const isPro = license?.isPro ?? false;
226
- // A key is set but validation is failing. Without this the tab renders a bare
227
- // "Free" badge, which reads as "your key was rejected" when what actually
228
- // happened is that we never reached the license server. `errorKind` decides
229
- // which story to tell: only a transport failure gets the VPN/proxy advice — a
230
- // real verdict (suspended, expired) with that advice would send the user off
231
- // to fight their network over a key that was genuinely rejected.
232
- const licenseError = !isPro && license?.licenseKey ? license.error : null;
233
- const licenseErrorIsTransport = license?.errorKind === "transport";
234
- const [storageBackend, setStorageBackend] = (0, _react.useState)(cachedStorageBackend);
235
- const [isClearing, setIsClearing] = (0, _react.useState)(false);
236
- const [clearSuccess, setClearSuccess] = (0, _react.useState)(false);
237
- const [isStorageExpanded, setIsStorageExpanded] = (0, _react.useState)(false);
238
- const [savedKeys, setSavedKeys] = (0, _react.useState)([]);
239
- const [savedKeysLoading, setSavedKeysLoading] = (0, _react.useState)(false);
240
- const insets = (0, _sharedUi.useSafeAreaInsets)();
241
- const screenHeight = _reactNative.Dimensions.get("window").height;
242
- const screenWidth = _reactNative.Dimensions.get("window").width;
243
- const modalHeight = Math.floor(screenHeight * 0.33); // 1/3 of screen height
244
- const modalWidth = Math.min(screenWidth - 32, 400); // Modal width with padding
245
-
246
- const loadSettings = (0, _react.useCallback)(async () => {
247
- try {
248
- const savedSettings = await _sharedUi.persistentStorage.getItem(STORAGE_KEY);
249
- if (savedSettings) {
250
- const parsed = JSON.parse(savedSettings);
251
- const merged = mergeWithDefaults(defaultSettings, parsed, {
252
- allowedDialKeys,
253
- allowedFloatingKeys
254
- });
255
- cachedSettings = merged;
256
- setSettings(prev => sameSettings(prev, merged) ? prev : merged);
257
- return;
258
- }
259
- cachedSettings = defaultSettings;
260
- setSettings(prev => sameSettings(prev, defaultSettings) ? prev : defaultSettings);
261
- } catch (error) {
262
- console.error("Failed to load dev tools settings:", error);
263
- setSettings(defaultSettings);
264
- }
265
- }, [defaultSettings, allowedDialKeys, allowedFloatingKeys]);
266
- (0, _react.useEffect)(() => {
267
- loadSettings();
268
- }, [loadSettings]);
269
-
270
- // Load storage backend type
271
- (0, _react.useEffect)(() => {
272
- (0, _sharedUi.getStorageBackendType)().then(backend => {
273
- cachedStorageBackend = backend;
274
- setStorageBackend(prev => prev === backend ? prev : backend);
275
- });
276
- }, []);
277
-
278
- // Load saved keys when storage card is expanded
279
- (0, _react.useEffect)(() => {
280
- if (isStorageExpanded) {
281
- setSavedKeysLoading(true);
282
- _sharedUi.persistentStorage.getAllKeys().then(keys => {
283
- setSavedKeys(keys.sort());
284
- }).catch(() => {
285
- setSavedKeys([]);
286
- }).finally(() => {
287
- setSavedKeysLoading(false);
288
- });
289
- }
290
- }, [isStorageExpanded]);
291
- const saveSettings = async newSettings => {
292
- try {
293
- const limitedSettings = {
294
- ...newSettings,
295
- dialTools: enforceDialLimit(newSettings.dialTools)
296
- };
297
- await _sharedUi.persistentStorage.setItem(STORAGE_KEY, JSON.stringify(limitedSettings));
298
- cachedSettings = limitedSettings;
299
- setSettings(limitedSettings);
300
- onSettingsChange?.(limitedSettings);
301
- // Notify listeners (e.g., floating bubble) to refresh immediately
302
- _settingsBus.settingsBus.emit(limitedSettings);
303
- } catch (error) {
304
- console.error("Failed to save dev tools settings:", error);
305
- }
306
- };
307
- const toggleFloatingTool = tool => {
308
- const newSettings = {
309
- ...settings,
310
- floatingTools: {
311
- ...settings.floatingTools,
312
- [tool]: !settings.floatingTools[tool]
313
- }
314
- };
315
- saveSettings(newSettings);
316
- };
317
- const toggleGlobalSetting = setting => {
318
- const newSettings = {
319
- ...settings,
320
- globalSettings: {
321
- ...settings.globalSettings,
322
- [setting]: !settings.globalSettings?.[setting]
323
- }
324
- };
325
- saveSettings(newSettings);
326
- };
327
- const handleClearStorage = async () => {
328
- setIsClearing(true);
329
- setClearSuccess(false);
330
- try {
331
- await _sharedUi.persistentStorage.clear();
332
- setClearSuccess(true);
333
- // Reset settings to defaults after clearing
334
- setSettings(defaultSettings);
335
- _settingsBus.settingsBus.emit(defaultSettings);
336
- // Reset success state after 2 seconds
337
- setTimeout(() => setClearSuccess(false), 2000);
338
- } catch (error) {
339
- console.error("Failed to clear storage:", error);
340
- } finally {
341
- setIsClearing(false);
342
- }
343
- };
344
-
345
- // Modal is fixed to bottom sheet mode
346
- const handleModeChange = (0, _react.useCallback)(_mode => {
347
- // Mode changes handled by JsModal
348
- }, []);
349
- const getToolDescription = tool => {
350
- // Get description from availableApps
351
- const app = availableApps.find(a => a.id === tool);
352
- if (app?.description) {
353
- return app.description;
354
- }
355
- if (tool === "environment") {
356
- return "Environment badge.";
357
- }
358
- return "";
359
- };
360
- const getToolLabel = tool => {
361
- if (tool === "environment") {
362
- return "Env Badge";
363
- }
364
- const app = availableApps.find(a => a.id === tool);
365
- return app?.name ?? tool.toUpperCase().replace(/_/g, " ");
366
- };
367
- const getToolIcon = tool => {
368
- if (!iconsReady) return null;
369
- switch (tool) {
370
- case "query":
371
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.QueryIcon, {
372
- size: 18
373
- });
374
- case "env":
375
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.EnvIcon, {
376
- size: 18
377
- });
378
- case "sentry":
379
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SentryIcon, {
380
- size: 18,
381
- color: "#9945FF"
382
- });
383
- case "storage":
384
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.StorageIcon, {
385
- size: 18,
386
- bgColor: _sharedUi.night.color.surface
387
- });
388
- case "wifi":
389
- case "query-wifi-toggle":
390
- // Support both IDs for wifi toggle
391
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.WifiCircuitIcon, {
392
- size: 18,
393
- colorPreset: "green",
394
- strength: 4,
395
- noBackground: true
396
- });
397
- case "route-events":
398
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.RoutesIcon, {
399
- size: 18,
400
- bgColor: _sharedUi.night.color.surface
401
- });
402
- case "network":
403
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.NetworkIcon, {
404
- size: 18
405
- });
406
- case "environment":
407
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.EnvIcon, {
408
- size: 18
409
- });
410
- case "debug-borders":
411
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Layers, {
412
- size: 18,
413
- color: _sharedUi.night.color.accent
414
- });
415
- case "highlight-updates":
416
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RenderCountIcon, {
417
- size: 18,
418
- color: _sharedUi.night.color.accent
419
- });
420
- case "highlight-updates-modal":
421
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.HighlighterIcon, {
422
- size: 18
423
- });
424
- case "redux":
425
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.ReduxIcon, {
426
- size: 18
427
- });
428
- case "image-overlay":
429
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ImageOverlayIcon, {
430
- size: 18
431
- });
432
- case "events":
433
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.EventsIcon, {
434
- size: 18
435
- });
436
- case "zustand":
437
- try {
438
- // @ts-ignore - Optional package
439
- const {
440
- ZustandIcon
441
- } = require("@buoy-gg/zustand");
442
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(ZustandIcon, {
443
- size: 18
444
- });
445
- } catch {
446
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
447
- size: 18,
448
- color: _sharedUi.night.color.textSecondary
449
- });
450
- }
451
- case "jotai":
452
- try {
453
- // @ts-ignore - Optional package
454
- const {
455
- JotaiIcon
456
- } = require("@buoy-gg/jotai");
457
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(JotaiIcon, {
458
- size: 18
459
- });
460
- } catch {
461
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
462
- size: 18,
463
- color: _sharedUi.night.color.textSecondary
464
- });
465
- }
466
- default:
467
- {
468
- // Tools without a night-tuned override (console, images, time
469
- // machine, perf monitor, custom toolkit tools, …) render their own
470
- // app icon — the same artwork the dial shows.
471
- const app = availableApps.find(a => a.id === tool);
472
- if (app?.icon != null) {
473
- return (0, _AppIcon.renderAppIcon)(app.icon, {
474
- slot: "row",
475
- size: 18,
476
- color: app.color
477
- });
478
- }
479
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
480
- size: 18,
481
- color: _sharedUi.night.color.textSecondary
482
- });
483
- }
484
- }
485
- };
486
-
487
- /**
488
- * One tool row: glyph, name + one-line description, switch. Lives inside a
489
- * NightCard's hairline-separated rows.
490
- */
491
- const renderToolRow = (keyName, value, disabled, onToggle) => {
492
- const description = getToolDescription(keyName);
493
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
494
- activeOpacity: disabled ? 1 : 0.7,
495
- onPress: () => !disabled && onToggle(),
496
- accessibilityLabel: getToolLabel(keyName),
497
- style: [styles.toolRow, disabled && styles.rowDisabled],
498
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
499
- style: styles.toolIconSlot,
500
- children: getToolIcon(keyName)
501
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
502
- style: styles.rowInfo,
503
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
504
- style: styles.rowLabel,
505
- children: [getToolLabel(keyName), disabled ? " · max 6" : ""]
506
- }), description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
507
- style: styles.rowCaption,
508
- numberOfLines: 1,
509
- children: description
510
- }) : null]
511
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSwitch, {
512
- value: value,
513
- disabled: disabled,
514
- onValueChange: () => onToggle()
515
- })]
516
- }, keyName);
517
- };
518
-
519
- // Toggle expanded state for a setting card
520
- const toggleSettingExpanded = settingKey => {
521
- setExpandedSettings(prev => {
522
- const newSet = new Set(prev);
523
- if (newSet.has(settingKey)) {
524
- newSet.delete(settingKey);
525
- } else {
526
- newSet.add(settingKey);
527
- }
528
- return newSet;
529
- });
530
- };
531
-
532
- /**
533
- * One global-setting row: name + one-line hint, switch, and a tap-to-expand
534
- * body holding the full description and recommendation.
535
- */
536
- const renderGlobalSettingRow = (settingKey, label, shortDescription, fullDescription, recommendation) => {
537
- const value = settings.globalSettings?.[settingKey] ?? false;
538
- const isExpanded = expandedSettings.has(settingKey);
539
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
540
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
541
- activeOpacity: 0.7,
542
- onPress: () => toggleSettingExpanded(settingKey),
543
- accessibilityLabel: label,
544
- style: styles.settingRow,
545
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
546
- style: styles.rowInfo,
547
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
548
- style: styles.rowLabel,
549
- children: label
550
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
551
- style: styles.rowCaption,
552
- numberOfLines: isExpanded ? 0 : 1,
553
- children: shortDescription
554
- })]
555
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSwitch, {
556
- value: value,
557
- onValueChange: () => toggleGlobalSetting(settingKey)
558
- })]
559
- }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
560
- style: styles.settingRowBody,
561
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
562
- style: styles.settingRowBodyText,
563
- children: fullDescription
564
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
565
- style: styles.settingRowBodyHint,
566
- children: recommendation
567
- })]
568
- })]
569
- }, settingKey);
570
- };
571
-
572
- /** Section = uppercase label, card, optional footnote(s) under it. */
573
- const renderSection = (label, card, footnotes) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
574
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSectionLabel, {
575
- children: label
576
- }), card, footnotes]
577
- });
578
- const storageBadge = storageBackend === "filesystem" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
579
- label: "File System",
580
- tone: "accent"
581
- }) : storageBackend === "asyncstorage" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
582
- label: "Async Storage",
583
- tone: "warning"
584
- }) : storageBackend === "memory" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
585
- label: "Memory",
586
- tone: "danger"
587
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
588
- label: "Loading\u2026",
589
- tone: "neutral"
590
- });
591
- const renderContent = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
592
- style: styles.container,
593
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
594
- style: styles.scrollContent,
595
- showsVerticalScrollIndicator: false,
596
- contentContainerStyle: styles.scrollContainer,
597
- children: [activeTab === "floating" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
598
- children: renderSection("Floating Tools", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
599
- glass: true,
600
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightRows, {
601
- children: Object.entries(settings.floatingTools).map(([key, value]) => renderToolRow(key, value, false, () => toggleFloatingTool(key)))
602
- })
603
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightFootnote, {
604
- children: "Tools switched on here appear in the floating bubble row."
605
- }))
606
- }), activeTab === "settings" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
607
- children: [renderSection("Background", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
608
- glass: true,
609
- padded: true,
610
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.BackgroundSwitcher, {
611
- embedded: true
612
- })
613
- })), renderSection("Storage", /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightCard, {
614
- glass: true,
615
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
616
- activeOpacity: 0.7,
617
- onPress: () => setIsStorageExpanded(!isStorageExpanded),
618
- accessibilityLabel: "Storage Type",
619
- style: styles.settingRow,
620
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
621
- style: styles.rowInfo,
622
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
623
- style: styles.rowLabel,
624
- children: "Storage Type"
625
- })
626
- }), storageBadge, isStorageExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
627
- size: 16,
628
- color: _sharedUi.night.color.textTertiary
629
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRightIcon, {
630
- size: 16,
631
- color: _sharedUi.night.color.textTertiary
632
- })]
633
- }), isStorageExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
634
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
635
- style: styles.settingRowBody,
636
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
637
- style: styles.settingRowBodyText,
638
- children: storageBackend === "filesystem" ? "Settings persist independently and survive AsyncStorage.clear() calls during logout." : storageBackend === "asyncstorage" ? "Settings may be lost if AsyncStorage is cleared during logout." : storageBackend === "memory" ? "Settings are stored in memory only and will be lost on app restart." : "Checking storage backend…"
639
- }), storageBackend === "asyncstorage" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
640
- style: [styles.settingRowBodyText, styles.bodyWarning],
641
- children: "Install expo-file-system to persist settings through logout flows."
642
- }), storageBackend === "memory" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
643
- style: [styles.settingRowBodyText, styles.bodyDanger],
644
- children: "No persistent storage available \u2014 install expo-file-system (best) or @react-native-async-storage/async-storage."
645
- })]
646
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSeparator, {}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
647
- style: styles.storageExpanded,
648
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
649
- style: styles.storageExpandedHeader,
650
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
651
- style: styles.storageExpandedTitle,
652
- children: "SAVED SETTINGS"
653
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
654
- style: styles.storageExpandedCount,
655
- children: savedKeysLoading ? "…" : `${savedKeys.length} keys`
656
- })]
657
- }), savedKeysLoading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
658
- style: styles.storageKeyEmpty,
659
- children: "Loading\u2026"
660
- }) : savedKeys.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
661
- style: styles.storageKeyEmpty,
662
- children: "No settings saved yet"
663
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
664
- style: styles.storageKeysList,
665
- nestedScrollEnabled: true,
666
- showsVerticalScrollIndicator: true,
667
- children: savedKeys.map(key => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
668
- style: styles.storageKeyItem,
669
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
670
- style: styles.storageKeyText,
671
- numberOfLines: 1,
672
- children: key.replace("@react_buoy_", "")
673
- })
674
- }, key))
675
- })]
676
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
677
- style: styles.cardButtonWrap,
678
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightButton, {
679
- label: clearSuccess ? "Cleared" : isClearing ? "Clearing…" : "Clear All Settings",
680
- variant: clearSuccess ? "secondary" : "destructive",
681
- size: "md",
682
- disabled: isClearing,
683
- onPress: handleClearStorage,
684
- icon: clearSuccess ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
685
- size: 15,
686
- color: _sharedUi.night.color.accent
687
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
688
- size: 15,
689
- color: isClearing ? _sharedUi.night.color.textTertiary : _sharedUi.night.color.danger
690
- })
691
- })
692
- })]
693
- })]
694
- })), desktopSyncStatus && desktopSyncStatus.state !== "idle" && renderSection("Desktop Sync", /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightCard, {
695
- glass: true,
696
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
697
- activeOpacity: 0.7,
698
- onPress: () => toggleSettingExpanded("desktopSync"),
699
- accessibilityLabel: "Buoy Desktop",
700
- style: styles.settingRow,
701
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
702
- style: styles.rowInfo,
703
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
704
- style: styles.rowLabel,
705
- children: "Buoy Desktop"
706
- })
707
- }), desktopSyncStatus.state === "connected" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
708
- label: "Connected",
709
- tone: "accent"
710
- }) : desktopSyncStatus.state === "connecting" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
711
- label: "Connecting\u2026",
712
- tone: "neutral"
713
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
714
- label: "Retrying",
715
- tone: "warning"
716
- }), expandedSettings.has("desktopSync") ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
717
- size: 16,
718
- color: _sharedUi.night.color.textTertiary
719
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRightIcon, {
720
- size: 16,
721
- color: _sharedUi.night.color.textTertiary
722
- })]
723
- }), expandedSettings.has("desktopSync") && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
724
- style: styles.settingRowBody,
725
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
726
- style: styles.settingRowBodyText,
727
- children: [desktopSyncStatus.state === "connected" ? "Streaming tool data to Buoy Desktop at " : "Trying to reach Buoy Desktop at ", /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
728
- style: styles.footnoteMono,
729
- children: desktopSyncStatus.targetURL ?? "(unknown URL)"
730
- }), desktopSyncStatus.state !== "connected" && " — is the desktop app running? Override with the externalSync socketURL option if it runs elsewhere."]
731
- }), desktopSyncStatus.state === "retrying" && desktopSyncStatus.lastError && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
732
- style: [styles.settingRowBodyText, styles.bodyWarning],
733
- children: ["Last error: ", desktopSyncStatus.lastError]
734
- })]
735
- })]
736
- })), renderSection("Modal Behavior", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
737
- glass: true,
738
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightRows, {
739
- children: [renderGlobalSettingRow("enableSharedModalDimensions", "Shared Modal Size", "Sync dimensions across all tools", "When enabled, all tool modals will share the same size and position. Resizing one modal will affect all others. When disabled, each tool remembers its own size and position independently.", "Keep OFF for the best experience. This allows you to customize each tool's modal size separately."), _reactNative.Platform.OS !== "web" ? renderGlobalSettingRow("expandableWindowControls", "Expand Controls", "iPad-style expandable window buttons", "When enabled, the window control buttons (minimize, toggle mode, close) start as small dots and expand into larger, easy-to-tap buttons when pressed. When disabled, buttons are directly tappable at their small size.", "Keep ON for touch devices where the small buttons are hard to press. Turn OFF if you prefer direct single-tap access.") : null]
740
- })
741
- }))]
742
- }), activeTab === "pro" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
743
- children: [renderSection("License", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
744
- glass: true,
745
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
746
- style: styles.settingRow,
747
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
748
- style: styles.rowInfo,
749
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
750
- style: styles.rowLabel,
751
- children: "License Status"
752
- })
753
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
754
- testID: "buoy-settings-license-status",
755
- accessibilityLabel: isPro ? "License: Active" : "License: Free",
756
- children: isPro ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
757
- label: "Active",
758
- tone: "accent"
759
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
760
- label: "Free",
761
- tone: "neutral"
762
- })
763
- })]
764
- })
765
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightFootnote, {
766
- children: isPro ? "You have full access to all Buoy DevTools features." : licenseError ? licenseErrorIsTransport ? `Couldn't reach the license server (${licenseError}). A VPN or corporate proxy can block buoy.gg — turn it off and reload. Your key hasn't been rejected.` : `License check failed: ${licenseError}.` : "Upgrade to Pro to unlock all features and support development."
767
- })), !isPro && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
768
- children: [renderSection("Pro Features", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
769
- glass: true,
770
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightRows, {
771
- children: ["Advanced Settings", "Priority Support"].map(feature => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
772
- style: styles.featureRow,
773
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
774
- size: 16,
775
- color: _sharedUi.night.color.accent
776
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
777
- style: styles.rowLabel,
778
- children: feature
779
- })]
780
- }, feature))
781
- })
782
- })), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightButton, {
783
- label: "Upgrade to Pro",
784
- variant: "primary",
785
- size: "lg",
786
- onPress: () => setShowLicenseModal(true)
787
- })]
788
- })]
789
- })]
790
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.LicenseEntryModal, {
791
- visible: showLicenseModal,
792
- onClose: () => setShowLicenseModal(false),
793
- onSuccess: () => setShowLicenseModal(false)
794
- })]
795
- });
796
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.JsModal, {
797
- visible: visible,
798
- onClose: onClose,
799
- variant: "night",
800
- header: {
801
- showToggleButton: false,
802
- customContent: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
803
- style: styles.headerRow,
804
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSegmented, {
805
- glass: true,
806
- tabs: [{
807
- key: "floating",
808
- label: "Floating"
809
- }, {
810
- key: "settings",
811
- label: "Settings"
812
- }, {
813
- key: "pro",
814
- label: "Pro"
815
- }],
816
- activeKey: activeTab,
817
- onChange: tab => setActiveTab(tab)
818
- })
819
- })
820
- },
821
- initialMode: "bottomSheet",
822
- onModeChange: handleModeChange,
823
- persistenceKey: _sharedUi.devToolsStorageKeys.settings.root(),
824
- enablePersistence: true,
825
- maxHeight: screenHeight - insets.top,
826
- initialHeight: modalHeight,
827
- initialFloatingPosition: {
828
- x: (screenWidth - modalWidth) / 2,
829
- y: insets.top + 20
830
- },
831
- enableGlitchEffects: true,
832
- children: renderContent()
833
- });
834
- };
835
-
836
- // Basic default settings for the hook (when apps are not available and no defaults configured)
837
- exports.DevToolsSettingsModal = DevToolsSettingsModal;
838
- const basicDefaultSettings = {
839
- dialTools: {},
840
- floatingTools: {
841
- environment: false
842
- },
843
- globalSettings: {
844
- enableSharedModalDimensions: false
845
- }
846
- };
847
-
848
- /**
849
- * Creates default settings from the team configuration arrays.
850
- * Used by the hook when no stored settings exist.
851
- *
852
- * Note: This function doesn't have access to availableApps, so it can only
853
- * enable tools that are explicitly listed in the config arrays.
854
- * For auto-enabling all dial tools, the generateDefaultSettings function
855
- * (which has access to availableApps) handles that case.
856
- */
857
- const createDefaultsFromConfig = (defaultFloatingTools, defaultDialTools) => {
858
- const floatingSet = new Set(defaultFloatingTools ?? []);
859
- const dialSet = new Set(defaultDialTools ?? []);
860
-
861
- // Build dial tools record from defaults
862
- // Note: If no defaults specified, dialTools will be empty here
863
- // but generateDefaultSettings will auto-enable all tools when it has access to availableApps
864
- const dialTools = {};
865
- for (const id of dialSet) {
866
- dialTools[id] = true;
867
- }
868
-
869
- // Build floating tools record from defaults
870
- const floatingTools = {
871
- environment: floatingSet.has('environment')
872
- };
873
- for (const id of floatingSet) {
874
- if (id !== 'environment') {
875
- floatingTools[id] = true;
876
- }
877
- }
878
- return {
879
- dialTools: enforceDialLimit(dialTools),
880
- floatingTools,
881
- globalSettings: {
882
- enableSharedModalDimensions: false
883
- }
884
- };
885
- };
886
-
887
- /**
888
- * Convenience hook for accessing persisted dev tools settings. Subscribes to the internal
889
- * event bus so all surfaces stay in sync when the modal saves new preferences.
890
- *
891
- * When no saved settings exist, the hook uses the default configuration from
892
- * the DefaultConfigProvider (if available) to determine initial tool states.
893
- */
894
- /**
895
- * Session cache for the values the settings modal loads asynchronously on mount.
896
- *
897
- * Without it every open renders three times: once with defaults, then again as
898
- * each of the persisted reads (settings, active tab, storage backend) resolves —
899
- * and each of those re-renders rebuilds the whole tool list. The values cannot
900
- * change behind our back (writes go through this module, and settingsBus
901
- * broadcasts them), so after the first open we can render the real values
902
- * immediately and only re-render if a read actually came back different.
903
- */
904
- let cachedSettings = null;
905
- let cachedActiveTab = null;
906
- let cachedStorageBackend = null;
907
-
908
- /** Cheap structural equality — these objects are small and JSON-safe. */
909
- const sameSettings = (a, b) => JSON.stringify(a) === JSON.stringify(b);
910
-
911
- // Every surface that changes settings broadcasts on the bus, so listening here
912
- // keeps the cache authoritative rather than merely last-seen — otherwise a
913
- // change made while the modal was unmounted would show stale for one frame.
914
- // Registered for the app's lifetime on purpose; there is nothing to unsubscribe.
915
- _settingsBus.settingsBus.addListener(payload => {
916
- if (payload) cachedSettings = payload;
917
11
  });
918
- const useDevToolsSettings = () => {
919
- // Get team default configuration from context
920
- const {
921
- defaultFloatingTools,
922
- defaultDialTools
923
- } = (0, _DefaultConfigContext.useDefaultConfig)();
924
-
925
- // Compute the effective defaults based on team configuration
926
- const effectiveDefaults = (0, _react.useMemo)(() => {
927
- if (!defaultFloatingTools && !defaultDialTools) {
928
- return basicDefaultSettings;
929
- }
930
- return createDefaultsFromConfig(defaultFloatingTools, defaultDialTools);
931
- }, [defaultFloatingTools, defaultDialTools]);
932
- const [settings, setSettings] = (0, _react.useState)(effectiveDefaults);
933
- const loadSettings = (0, _react.useCallback)(async () => {
934
- try {
935
- const savedSettings = await _sharedUi.persistentStorage.getItem(STORAGE_KEY);
936
- if (savedSettings) {
937
- const parsed = JSON.parse(savedSettings);
938
- const merged = mergeWithDefaults(effectiveDefaults, parsed);
939
- setSettings(merged);
940
- return;
941
- }
942
- // No saved settings - use the effective defaults (including team config)
943
- setSettings(effectiveDefaults);
944
- } catch (error) {
945
- console.error("Failed to load dev tools settings:", error);
946
- setSettings(effectiveDefaults);
947
- }
948
- }, [effectiveDefaults]);
949
- (0, _react.useEffect)(() => {
950
- loadSettings();
951
- // Subscribe to settings changes
952
- const unsub = _settingsBus.settingsBus.addListener(payload => {
953
- try {
954
- if (payload) {
955
- setSettings(payload);
956
- }
957
- } catch (err) {
958
- // Listener errors are intentionally swallowed to avoid breaking subscribers
959
- }
960
- });
961
- return () => {
962
- unsub();
963
- };
964
- }, [loadSettings]);
965
-
966
- // Refresh settings when component using this hook becomes visible
967
- const refreshSettings = (0, _react.useCallback)(() => {
968
- loadSettings();
969
- }, [loadSettings]);
970
- return {
971
- settings,
972
- refreshSettings
973
- };
974
- };
975
- exports.useDevToolsSettings = useDevToolsSettings;
976
- const styles = _reactNative.StyleSheet.create({
977
- container: {
978
- flex: 1,
979
- // Transparent so JsModal's night star field shows through everything.
980
- backgroundColor: "transparent"
981
- },
982
- // Header — the segmented control. The window controls are cleared
983
- // vertically by JsModal's drag-indicator strip, so this runs full width.
984
- headerRow: {
985
- flexDirection: "row",
986
- alignItems: "center",
987
- paddingLeft: _sharedUi.night.gutter,
988
- paddingRight: _sharedUi.night.gutter,
989
- paddingBottom: 8
990
- },
991
- // Scroll content — labeled groups on a 24pt rhythm (Everlights create screen)
992
- scrollContent: {
993
- flex: 1
994
- },
995
- scrollContainer: {
996
- paddingHorizontal: _sharedUi.night.gutter,
997
- paddingTop: 8,
998
- paddingBottom: 24,
999
- gap: _sharedUi.night.groupGap
1000
- },
1001
- // Rows
1002
- toolRow: {
1003
- flexDirection: "row",
1004
- alignItems: "center",
1005
- gap: 12,
1006
- paddingHorizontal: _sharedUi.night.rowPadH,
1007
- paddingVertical: 12
1008
- },
1009
- settingRow: {
1010
- flexDirection: "row",
1011
- alignItems: "center",
1012
- gap: 10,
1013
- paddingHorizontal: _sharedUi.night.rowPadH,
1014
- paddingVertical: _sharedUi.night.rowPadV,
1015
- minHeight: 52
1016
- },
1017
- featureRow: {
1018
- flexDirection: "row",
1019
- alignItems: "center",
1020
- gap: 10,
1021
- paddingHorizontal: _sharedUi.night.rowPadH,
1022
- paddingVertical: 12
1023
- },
1024
- rowDisabled: {
1025
- opacity: 0.4
1026
- },
1027
- toolIconSlot: {
1028
- width: 24,
1029
- alignItems: "center"
1030
- },
1031
- rowInfo: {
1032
- flex: 1,
1033
- gap: 2
1034
- },
1035
- rowLabel: {
1036
- color: _sharedUi.night.color.text,
1037
- fontSize: _sharedUi.night.font.row,
1038
- fontWeight: "500"
1039
- },
1040
- rowCaption: {
1041
- color: _sharedUi.night.color.textTertiary,
1042
- fontSize: _sharedUi.night.font.label
1043
- },
1044
- // Expanded body under a global-setting row
1045
- settingRowBody: {
1046
- paddingHorizontal: _sharedUi.night.rowPadH,
1047
- paddingBottom: _sharedUi.night.rowPadV,
1048
- gap: 6
1049
- },
1050
- settingRowBodyText: {
1051
- color: _sharedUi.night.color.textSecondary,
1052
- fontSize: _sharedUi.night.font.caption,
1053
- lineHeight: _sharedUi.night.font.caption * 1.45
1054
- },
1055
- settingRowBodyHint: {
1056
- color: _sharedUi.night.color.textTertiary,
1057
- fontSize: _sharedUi.night.font.caption,
1058
- lineHeight: _sharedUi.night.font.caption * 1.45
1059
- },
1060
- bodyWarning: {
1061
- color: _sharedUi.night.color.warning
1062
- },
1063
- bodyDanger: {
1064
- color: _sharedUi.night.color.danger
1065
- },
1066
- // Buttons that live inside a card
1067
- cardButtonWrap: {
1068
- paddingHorizontal: _sharedUi.night.cardPad,
1069
- paddingTop: 10,
1070
- paddingBottom: _sharedUi.night.cardPad
1071
- },
1072
- // Storage expanded key list
1073
- storageExpanded: {
1074
- paddingHorizontal: _sharedUi.night.rowPadH,
1075
- paddingVertical: 12,
1076
- gap: 8
1077
- },
1078
- storageExpandedHeader: {
1079
- flexDirection: "row",
1080
- alignItems: "center"
1081
- },
1082
- storageExpandedTitle: {
1083
- flex: 1,
1084
- color: _sharedUi.night.color.textSecondary,
1085
- fontSize: _sharedUi.night.font.micro,
1086
- fontWeight: "600",
1087
- letterSpacing: 0.5
1088
- },
1089
- storageExpandedCount: {
1090
- color: _sharedUi.night.color.textTertiary,
1091
- fontSize: _sharedUi.night.font.micro
1092
- },
1093
- storageKeysList: {
1094
- maxHeight: 150
1095
- },
1096
- storageKeyItem: {
1097
- backgroundColor: _sharedUi.night.color.glassElevated,
1098
- borderRadius: _sharedUi.night.radius.segment,
1099
- paddingHorizontal: 10,
1100
- paddingVertical: 7,
1101
- marginBottom: 4
1102
- },
1103
- storageKeyText: {
1104
- color: _sharedUi.night.color.textSecondary,
1105
- fontSize: _sharedUi.night.font.micro,
1106
- fontFamily: _sharedUi.monoFont
1107
- },
1108
- storageKeyEmpty: {
1109
- color: _sharedUi.night.color.textTertiary,
1110
- fontSize: _sharedUi.night.font.caption,
1111
- fontStyle: "italic"
1112
- },
1113
- // Footnote inline code
1114
- footnoteMono: {
1115
- fontFamily: _sharedUi.monoFont,
1116
- fontSize: _sharedUi.night.font.label,
1117
- color: _sharedUi.night.color.textSecondary
12
+ Object.defineProperty(exports, "useDevToolsSettings", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _DevToolsSettingsModalShared.useDevToolsSettings;
1118
16
  }
1119
- });
17
+ });
18
+ var _DevToolsSettingsModalShared = require("./DevToolsSettingsModal.shared.js");