@buoy-gg/core 7.0.26 → 7.0.28

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 (48) hide show
  1. package/bin/buoy.js +6 -0
  2. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +532 -1520
  3. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +541 -566
  4. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +5 -1
  5. package/lib/commonjs/floatingMenu/ToolHost.js +2 -1
  6. package/lib/commonjs/floatingMenu/autoExternalSync.js +11 -8
  7. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +50 -21
  8. package/lib/commonjs/floatingMenu/dial/DialHyperspace.js +177 -0
  9. package/lib/commonjs/floatingMenu/dial/DialIcon.js +31 -0
  10. package/lib/commonjs/floatingMenu/dial/DialSnow.js +92 -0
  11. package/lib/commonjs/version.js +1 -1
  12. package/lib/module/floatingMenu/DevToolsSettingsModal.js +533 -1524
  13. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +542 -569
  14. package/lib/module/floatingMenu/FloatingDevTools.web.js +5 -1
  15. package/lib/module/floatingMenu/ToolHost.js +2 -1
  16. package/lib/module/floatingMenu/autoExternalSync.js +11 -8
  17. package/lib/module/floatingMenu/dial/DialDevTools.js +52 -23
  18. package/lib/module/floatingMenu/dial/DialHyperspace.js +173 -0
  19. package/lib/module/floatingMenu/dial/DialIcon.js +33 -1
  20. package/lib/module/floatingMenu/dial/DialSnow.js +90 -0
  21. package/lib/module/version.js +1 -1
  22. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts +19 -13
  23. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts +3 -3
  25. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/floatingMenu/dial/DialHyperspace.d.ts +24 -0
  30. package/lib/typescript/commonjs/floatingMenu/dial/DialHyperspace.d.ts.map +1 -0
  31. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/floatingMenu/dial/DialSnow.d.ts +23 -0
  33. package/lib/typescript/commonjs/floatingMenu/dial/DialSnow.d.ts.map +1 -0
  34. package/lib/typescript/commonjs/version.d.ts +1 -1
  35. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts +19 -13
  36. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  37. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts +3 -3
  38. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  39. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  40. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
  41. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  42. package/lib/typescript/module/floatingMenu/dial/DialHyperspace.d.ts +24 -0
  43. package/lib/typescript/module/floatingMenu/dial/DialHyperspace.d.ts.map +1 -0
  44. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
  45. package/lib/typescript/module/floatingMenu/dial/DialSnow.d.ts +23 -0
  46. package/lib/typescript/module/floatingMenu/dial/DialSnow.d.ts.map +1 -0
  47. package/lib/typescript/module/version.d.ts +1 -1
  48. package/package.json +5 -5
@@ -11,6 +11,7 @@ var _useDesktopSyncStatus = require("./useDesktopSyncStatus.js");
11
11
  var _sharedUi = require("@buoy-gg/shared-ui");
12
12
  var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
13
13
  var _DefaultConfigContext = require("./DefaultConfigContext.js");
14
+ var _AppIcon = require("./AppIcon.js");
14
15
  var _jsxRuntime = require("react/jsx-runtime");
15
16
  const STORAGE_KEY = "@react_buoy_dev_tools_settings";
16
17
  const MAX_DIAL_SLOTS = 6;
@@ -87,6 +88,12 @@ const mergeWithDefaults = (defaults, stored, options) => {
87
88
  * via AsyncStorage so teams can tailor which tools appear in the dial or floating row.
88
89
  */
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
+
90
97
  /**
91
98
  * Generate default settings based on available apps and optional team default configuration.
92
99
  *
@@ -138,6 +145,10 @@ const generateDefaultSettings = (availableApps = [], defaultFloatingTools, defau
138
145
  /**
139
146
  * Configurable modal surface that lets engineers pick which dev tools appear in the dial and
140
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.
141
152
  */
142
153
  const DevToolsSettingsModal = ({
143
154
  visible,
@@ -154,11 +165,27 @@ const DevToolsSettingsModal = ({
154
165
  const defaultSettings = (0, _react.useMemo)(() => generateDefaultSettings(availableApps, defaultFloatingTools, defaultDialTools), [availableApps, defaultFloatingTools, defaultDialTools]);
155
166
  const allowedDialKeys = (0, _react.useMemo)(() => Object.keys(defaultSettings.dialTools), [defaultSettings]);
156
167
  const allowedFloatingKeys = (0, _react.useMemo)(() => Object.keys(defaultSettings.floatingTools).filter(key => key !== "environment"), [defaultSettings]);
157
- const [settings, setSettings] = (0, _react.useState)(initialSettings || defaultSettings);
158
- const [activeTab, setActiveTab] = (0, _react.useState)("floating");
159
- const [activeTabLoaded, setActiveTabLoaded] = (0, _react.useState)(false);
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);
160
171
  const [expandedSettings, setExpandedSettings] = (0, _react.useState)(new Set());
161
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
+
162
189
  // null when external-sync isn't installed (or predates the status store);
163
190
  // "idle" until AutoExternalSync initializes — the row hides in both cases.
164
191
  const desktopSyncStatus = (0, _useDesktopSyncStatus.useDesktopSyncStatus)();
@@ -169,7 +196,9 @@ const DevToolsSettingsModal = ({
169
196
  try {
170
197
  const savedTab = await _sharedUi.persistentStorage.getItem(_sharedUi.devToolsStorageKeys.settings.activeTab());
171
198
  if (savedTab && ["floating", "settings", "pro"].includes(savedTab)) {
172
- setActiveTab(savedTab);
199
+ const tab = savedTab;
200
+ cachedActiveTab = tab;
201
+ setActiveTab(prev => prev === tab ? prev : tab);
173
202
  }
174
203
  } catch (error) {
175
204
  // Failed to load active tab - use default
@@ -194,18 +223,21 @@ const DevToolsSettingsModal = ({
194
223
  const useLicenseHook = getUseLicense();
195
224
  const license = useLicenseHook?.();
196
225
  const isPro = license?.isPro ?? false;
197
- const [storageBackend, setStorageBackend] = (0, _react.useState)(null);
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);
198
235
  const [isClearing, setIsClearing] = (0, _react.useState)(false);
199
236
  const [clearSuccess, setClearSuccess] = (0, _react.useState)(false);
200
237
  const [isStorageExpanded, setIsStorageExpanded] = (0, _react.useState)(false);
201
238
  const [savedKeys, setSavedKeys] = (0, _react.useState)([]);
202
239
  const [savedKeysLoading, setSavedKeysLoading] = (0, _react.useState)(false);
203
- const [copySuccess, setCopySuccess] = (0, _react.useState)(false);
204
- const [isExportExpanded, setIsExportExpanded] = (0, _react.useState)(false);
205
240
  const insets = (0, _sharedUi.useSafeAreaInsets)();
206
-
207
- // Check if in development mode - used to show dev-only features like Export Config
208
- const isDevelopmentMode = typeof __DEV__ !== "undefined" && __DEV__;
209
241
  const screenHeight = _reactNative.Dimensions.get("window").height;
210
242
  const screenWidth = _reactNative.Dimensions.get("window").width;
211
243
  const modalHeight = Math.floor(screenHeight * 0.33); // 1/3 of screen height
@@ -220,10 +252,12 @@ const DevToolsSettingsModal = ({
220
252
  allowedDialKeys,
221
253
  allowedFloatingKeys
222
254
  });
223
- setSettings(merged);
255
+ cachedSettings = merged;
256
+ setSettings(prev => sameSettings(prev, merged) ? prev : merged);
224
257
  return;
225
258
  }
226
- setSettings(defaultSettings);
259
+ cachedSettings = defaultSettings;
260
+ setSettings(prev => sameSettings(prev, defaultSettings) ? prev : defaultSettings);
227
261
  } catch (error) {
228
262
  console.error("Failed to load dev tools settings:", error);
229
263
  setSettings(defaultSettings);
@@ -235,7 +269,10 @@ const DevToolsSettingsModal = ({
235
269
 
236
270
  // Load storage backend type
237
271
  (0, _react.useEffect)(() => {
238
- (0, _sharedUi.getStorageBackendType)().then(setStorageBackend);
272
+ (0, _sharedUi.getStorageBackendType)().then(backend => {
273
+ cachedStorageBackend = backend;
274
+ setStorageBackend(prev => prev === backend ? prev : backend);
275
+ });
239
276
  }, []);
240
277
 
241
278
  // Load saved keys when storage card is expanded
@@ -258,6 +295,7 @@ const DevToolsSettingsModal = ({
258
295
  dialTools: enforceDialLimit(newSettings.dialTools)
259
296
  };
260
297
  await _sharedUi.persistentStorage.setItem(STORAGE_KEY, JSON.stringify(limitedSettings));
298
+ cachedSettings = limitedSettings;
261
299
  setSettings(limitedSettings);
262
300
  onSettingsChange?.(limitedSettings);
263
301
  // Notify listeners (e.g., floating bubble) to refresh immediately
@@ -304,49 +342,6 @@ const DevToolsSettingsModal = ({
304
342
  }
305
343
  };
306
344
 
307
- // Get the enabled tools as arrays for display and copy
308
- const enabledConfig = (0, _react.useMemo)(() => {
309
- const enabledFloating = Object.entries(settings.floatingTools).filter(([_, enabled]) => enabled).map(([id]) => id);
310
- const enabledDial = Object.entries(settings.dialTools).filter(([_, enabled]) => enabled).map(([id]) => id);
311
- return {
312
- floating: enabledFloating,
313
- dial: enabledDial
314
- };
315
- }, [settings]);
316
-
317
- // Generate exportable code snippet from current settings
318
- // Only outputs the defaultFloatingTools and defaultDialTools props
319
- // so users can add them to their existing FloatingDevTools config
320
- const generateConfigCode = (0, _react.useCallback)(() => {
321
- const {
322
- floating,
323
- dial
324
- } = enabledConfig;
325
-
326
- // Build only the default config props (not the full component)
327
- const props = [];
328
- if (floating.length > 0) {
329
- const floatingStr = floating.map(id => `'${id}'`).join(', ');
330
- props.push(`defaultFloatingTools={[${floatingStr}]}`);
331
- }
332
- if (dial.length > 0) {
333
- const dialStr = dial.map(id => `'${id}'`).join(', ');
334
- props.push(`defaultDialTools={[${dialStr}]}`);
335
- }
336
- if (props.length === 0) {
337
- return `// No tools enabled`;
338
- }
339
- return props.join('\n');
340
- }, [enabledConfig]);
341
- const handleCopyConfig = async () => {
342
- const code = generateConfigCode();
343
- const success = await (0, _sharedUi.copyToClipboard)(code);
344
- if (success) {
345
- setCopySuccess(true);
346
- setTimeout(() => setCopySuccess(false), 2000);
347
- }
348
- };
349
-
350
345
  // Modal is fixed to bottom sheet mode
351
346
  const handleModeChange = (0, _react.useCallback)(_mode => {
352
347
  // Mode changes handled by JsModal
@@ -364,153 +359,161 @@ const DevToolsSettingsModal = ({
364
359
  };
365
360
  const getToolLabel = tool => {
366
361
  if (tool === "environment") {
367
- return "ENV BADGE";
362
+ return "Env Badge";
368
363
  }
369
364
  const app = availableApps.find(a => a.id === tool);
370
365
  return app?.name ?? tool.toUpperCase().replace(/_/g, " ");
371
366
  };
372
-
373
- // Clean tool card renderer - unified Buoy theme
374
- const renderToolCard = (keyName, value, disabled, onToggle) => {
375
- const getToolIcon = tool => {
376
- switch (tool) {
377
- case "query":
378
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.QueryIcon, {
379
- size: 16
380
- });
381
- case "env":
382
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.EnvIcon, {
383
- size: 16
384
- });
385
- case "sentry":
386
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SentryBugIcon, {
387
- size: 16,
388
- colorPreset: "red",
389
- noBackground: true
390
- });
391
- case "storage":
392
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.StorageIcon, {
393
- size: 16
394
- });
395
- case "wifi":
396
- case "query-wifi-toggle":
397
- // Support both IDs for wifi toggle
398
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.WifiCircuitIcon, {
399
- size: 16,
400
- colorPreset: "green",
401
- strength: 4,
402
- noBackground: true
403
- });
404
- case "route-events":
405
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.RoutesIcon, {
406
- size: 16
407
- });
408
- case "network":
409
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.NetworkIcon, {
410
- size: 16
411
- });
412
- case "environment":
413
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.EnvIcon, {
414
- size: 16
415
- });
416
- case "debug-borders":
417
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Layers, {
418
- size: 16,
419
- color: _sharedUi.buoyColors.primary
420
- });
421
- case "highlight-updates":
422
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RenderCountIcon, {
423
- size: 16,
424
- color: _sharedUi.buoyColors.primary
425
- });
426
- case "highlight-updates-modal":
427
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.HighlighterIcon, {
428
- size: 16
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.SentryBugIcon, {
380
+ size: 18,
381
+ colorPreset: "red",
382
+ noBackground: true
383
+ });
384
+ case "storage":
385
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.StorageIcon, {
386
+ size: 18,
387
+ bgColor: _sharedUi.night.color.surface
388
+ });
389
+ case "wifi":
390
+ case "query-wifi-toggle":
391
+ // Support both IDs for wifi toggle
392
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.WifiCircuitIcon, {
393
+ size: 18,
394
+ colorPreset: "green",
395
+ strength: 4,
396
+ noBackground: true
397
+ });
398
+ case "route-events":
399
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.RoutesIcon, {
400
+ size: 18,
401
+ bgColor: _sharedUi.night.color.surface
402
+ });
403
+ case "network":
404
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.NetworkIcon, {
405
+ size: 18
406
+ });
407
+ case "environment":
408
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.EnvIcon, {
409
+ size: 18
410
+ });
411
+ case "debug-borders":
412
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Layers, {
413
+ size: 18,
414
+ color: _sharedUi.night.color.accent
415
+ });
416
+ case "highlight-updates":
417
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RenderCountIcon, {
418
+ size: 18,
419
+ color: _sharedUi.night.color.accent
420
+ });
421
+ case "highlight-updates-modal":
422
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.HighlighterIcon, {
423
+ size: 18
424
+ });
425
+ case "redux":
426
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.ReduxIcon, {
427
+ size: 18
428
+ });
429
+ case "image-overlay":
430
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ImageOverlayIcon, {
431
+ size: 18
432
+ });
433
+ case "events":
434
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.EventsIcon, {
435
+ size: 18
436
+ });
437
+ case "zustand":
438
+ try {
439
+ // @ts-ignore - Optional package
440
+ const {
441
+ ZustandIcon
442
+ } = require("@buoy-gg/zustand");
443
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(ZustandIcon, {
444
+ size: 18
429
445
  });
430
- case "redux":
431
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.ReduxIcon, {
432
- size: 16
446
+ } catch {
447
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
448
+ size: 18,
449
+ color: _sharedUi.night.color.textSecondary
433
450
  });
434
- case "image-overlay":
435
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ImageOverlayIcon, {
436
- size: 16
451
+ }
452
+ case "jotai":
453
+ try {
454
+ // @ts-ignore - Optional package
455
+ const {
456
+ JotaiIcon
457
+ } = require("@buoy-gg/jotai");
458
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(JotaiIcon, {
459
+ size: 18
437
460
  });
438
- case "events":
439
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.EventsIcon, {
440
- size: 16
461
+ } catch {
462
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
463
+ size: 18,
464
+ color: _sharedUi.night.color.textSecondary
441
465
  });
442
- case "zustand":
443
- try {
444
- // @ts-ignore - Optional package
445
- const {
446
- ZustandIcon
447
- } = require("@buoy-gg/zustand");
448
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(ZustandIcon, {
449
- size: 16
450
- });
451
- } catch {
452
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
453
- size: 16,
454
- color: _sharedUi.buoyColors.textSecondary
455
- });
456
- }
457
- case "jotai":
458
- try {
459
- // @ts-ignore - Optional package
460
- const {
461
- JotaiIcon
462
- } = require("@buoy-gg/jotai");
463
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(JotaiIcon, {
464
- size: 16
465
- });
466
- } catch {
467
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
468
- size: 16,
469
- color: _sharedUi.buoyColors.textSecondary
466
+ }
467
+ default:
468
+ {
469
+ // Tools without a night-tuned override (console, images, time
470
+ // machine, perf monitor, custom toolkit tools, …) render their own
471
+ // app icon — the same artwork the dial shows.
472
+ const app = availableApps.find(a => a.id === tool);
473
+ if (app?.icon != null) {
474
+ return (0, _AppIcon.renderAppIcon)(app.icon, {
475
+ slot: "row",
476
+ size: 18,
477
+ color: app.color
470
478
  });
471
479
  }
472
- default:
473
480
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
474
- size: 16,
475
- color: _sharedUi.buoyColors.textSecondary
481
+ size: 18,
482
+ color: _sharedUi.night.color.textSecondary
476
483
  });
477
- }
478
- };
479
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
480
- activeOpacity: disabled ? 1 : 0.85,
484
+ }
485
+ }
486
+ };
487
+
488
+ /**
489
+ * One tool row: glyph, name + one-line description, switch. Lives inside a
490
+ * NightCard's hairline-separated rows.
491
+ */
492
+ const renderToolRow = (keyName, value, disabled, onToggle) => {
493
+ const description = getToolDescription(keyName);
494
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
495
+ activeOpacity: disabled ? 1 : 0.7,
481
496
  onPress: () => !disabled && onToggle(),
482
- style: {
483
- marginBottom: 8,
484
- opacity: disabled ? 0.5 : 1
485
- },
486
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
487
- style: [styles.glassCard, value && styles.glassCardActive],
488
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
489
- style: styles.glassCardInner,
490
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
491
- style: [styles.iconContainer, value && styles.iconContainerActive],
492
- children: getToolIcon(keyName)
493
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
494
- style: styles.toolInfo,
495
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
496
- style: [styles.toolName, value && styles.toolNameActive],
497
- children: [getToolLabel(keyName), disabled ? " (MAX 6)" : ""]
498
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
499
- style: styles.toolDescription,
500
- numberOfLines: 1,
501
- children: getToolDescription(keyName)
502
- })]
503
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
504
- style: [styles.pillToggle, value ? styles.pillToggleOn : styles.pillToggleOff, disabled && {
505
- opacity: 0.5
506
- }],
507
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
508
- style: [styles.pillToggleText, value ? styles.pillToggleTextOn : styles.pillToggleTextOff],
509
- children: value ? "ON" : "OFF"
510
- })
511
- })]
512
- })
513
- })
497
+ accessibilityLabel: getToolLabel(keyName),
498
+ style: [styles.toolRow, disabled && styles.rowDisabled],
499
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
500
+ style: styles.toolIconSlot,
501
+ children: getToolIcon(keyName)
502
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
503
+ style: styles.rowInfo,
504
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
505
+ style: styles.rowLabel,
506
+ children: [getToolLabel(keyName), disabled ? " · max 6" : ""]
507
+ }), description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
508
+ style: styles.rowCaption,
509
+ numberOfLines: 1,
510
+ children: description
511
+ }) : null]
512
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSwitch, {
513
+ value: value,
514
+ disabled: disabled,
515
+ onValueChange: () => onToggle()
516
+ })]
514
517
  }, keyName);
515
518
  };
516
519
 
@@ -527,609 +530,252 @@ const DevToolsSettingsModal = ({
527
530
  });
528
531
  };
529
532
 
530
- // Render a global setting toggle card with expandable description
531
- const renderGlobalSettingCard = (settingKey, label, category, shortDescription, fullDescription, recommendation) => {
533
+ /**
534
+ * One global-setting row: name + one-line hint, switch, and a tap-to-expand
535
+ * body holding the full description and recommendation.
536
+ */
537
+ const renderGlobalSettingRow = (settingKey, label, shortDescription, fullDescription, recommendation) => {
532
538
  const value = settings.globalSettings?.[settingKey] ?? false;
533
539
  const isExpanded = expandedSettings.has(settingKey);
534
- const color = _sharedUi.buoyColors.primary;
535
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
536
- style: {
537
- marginBottom: 10
538
- },
539
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
540
- activeOpacity: 0.85,
540
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
541
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
542
+ activeOpacity: 0.7,
541
543
  onPress: () => toggleSettingExpanded(settingKey),
542
- style: [styles.expandableCard, isExpanded && {
543
- borderColor: color,
544
- borderWidth: 2,
545
- shadowColor: color,
546
- shadowOpacity: 0.8,
547
- shadowRadius: 20,
548
- shadowOffset: {
549
- width: 0,
550
- height: 0
551
- },
552
- elevation: 10,
553
- transform: [{
554
- scale: 1.01
555
- }]
556
- }],
544
+ accessibilityLabel: label,
545
+ style: styles.settingRow,
557
546
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
558
- style: styles.expandableCardHeader,
559
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
560
- style: styles.expandableCardCategory,
561
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
562
- style: styles.expandableCardCategoryText,
563
- children: category
564
- })
565
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
566
- style: styles.expandableCardTitle,
567
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
568
- style: styles.expandableCardTitleText,
569
- children: label
570
- }), !isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
571
- style: styles.expandableCardSubtitle,
572
- numberOfLines: 1,
573
- children: shortDescription
574
- })]
575
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
576
- style: styles.expandableCardActions,
577
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
578
- onPress: () => toggleGlobalSetting(settingKey),
579
- activeOpacity: 0.8,
580
- style: [styles.pillToggle, {
581
- backgroundColor: value ? `${_sharedUi.buoyColors.success}33` : _sharedUi.buoyColors.hover,
582
- borderColor: value ? `${_sharedUi.buoyColors.success}88` : _sharedUi.buoyColors.border,
583
- shadowColor: value ? _sharedUi.buoyColors.success : "transparent",
584
- shadowOffset: {
585
- width: 0,
586
- height: 0
587
- },
588
- shadowOpacity: value ? 0.4 : 0,
589
- shadowRadius: value ? 8 : 0
590
- }],
591
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
592
- style: [styles.pillToggleText, {
593
- color: value ? _sharedUi.buoyColors.success : _sharedUi.buoyColors.textMuted
594
- }],
595
- children: value ? "ON" : "OFF"
596
- })
597
- }), isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
598
- size: 18,
599
- color: "#7F91B2"
600
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRightIcon, {
601
- size: 18,
602
- color: "#7F91B2"
603
- })]
604
- })]
605
- }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
606
- style: styles.expandableCardBody,
607
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
608
- style: styles.expandableCardSection,
609
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
610
- style: styles.expandableCardSectionTitle,
611
- children: "DESCRIPTION"
612
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
613
- style: styles.expandableCardSectionText,
614
- children: fullDescription
615
- })]
616
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
617
- style: styles.expandableCardSection,
618
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
619
- style: styles.expandableCardSectionTitle,
620
- children: "RECOMMENDATION"
621
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
622
- style: styles.expandableCardSectionText,
623
- children: recommendation
624
- })]
547
+ style: styles.rowInfo,
548
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
549
+ style: styles.rowLabel,
550
+ children: label
551
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
552
+ style: styles.rowCaption,
553
+ numberOfLines: isExpanded ? 0 : 1,
554
+ children: shortDescription
625
555
  })]
556
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSwitch, {
557
+ value: value,
558
+ onValueChange: () => toggleGlobalSetting(settingKey)
626
559
  })]
627
- })
560
+ }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
561
+ style: styles.settingRowBody,
562
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
563
+ style: styles.settingRowBodyText,
564
+ children: fullDescription
565
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
566
+ style: styles.settingRowBodyHint,
567
+ children: recommendation
568
+ })]
569
+ })]
628
570
  }, settingKey);
629
571
  };
572
+
573
+ /** Section = uppercase label, card, optional footnote(s) under it. */
574
+ const renderSection = (label, card, footnotes) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
575
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSectionLabel, {
576
+ children: label
577
+ }), card, footnotes]
578
+ });
579
+ const storageBadge = storageBackend === "filesystem" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
580
+ label: "File System",
581
+ tone: "accent"
582
+ }) : storageBackend === "asyncstorage" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
583
+ label: "Async Storage",
584
+ tone: "warning"
585
+ }) : storageBackend === "memory" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
586
+ label: "Memory",
587
+ tone: "danger"
588
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
589
+ label: "Loading\u2026",
590
+ tone: "neutral"
591
+ });
630
592
  const renderContent = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
631
593
  style: styles.container,
632
594
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
633
595
  style: styles.scrollContent,
634
596
  showsVerticalScrollIndicator: false,
635
597
  contentContainerStyle: styles.scrollContainer,
636
- children: [activeTab === "floating" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
637
- style: styles.section,
638
- children: Object.entries(settings.floatingTools).map(([key, value]) => renderToolCard(key, value, false, () => toggleFloatingTool(key)))
639
- }), activeTab === "settings" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
640
- style: styles.section,
641
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
642
- style: [styles.storageStatusCard, isStorageExpanded && {
643
- borderColor: storageBackend === "filesystem" ? _sharedUi.buoyColors.success + "60" : storageBackend === "asyncstorage" ? _sharedUi.buoyColors.warning + "60" : _sharedUi.buoyColors.error + "60"
644
- }],
645
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
646
- activeOpacity: 0.8,
598
+ children: [activeTab === "floating" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
599
+ children: renderSection("Floating Tools", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
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
+ padded: true,
609
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.BackgroundSwitcher, {
610
+ embedded: true
611
+ })
612
+ })), renderSection("Storage", /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightCard, {
613
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
614
+ activeOpacity: 0.7,
647
615
  onPress: () => setIsStorageExpanded(!isStorageExpanded),
648
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
649
- style: styles.storageStatusHeader,
650
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
651
- style: [styles.storageStatusIcon, {
652
- backgroundColor: storageBackend === "filesystem" ? _sharedUi.buoyColors.success + "15" : storageBackend === "asyncstorage" ? _sharedUi.buoyColors.warning + "15" : _sharedUi.buoyColors.error + "15"
653
- }],
654
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Database, {
655
- size: 18,
656
- color: storageBackend === "filesystem" ? _sharedUi.buoyColors.success : storageBackend === "asyncstorage" ? _sharedUi.buoyColors.warning : _sharedUi.buoyColors.error
657
- })
658
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
659
- style: styles.storageStatusInfo,
660
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
661
- style: styles.storageStatusLabel,
662
- children: "STORAGE TYPE"
663
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
664
- style: styles.storageStatusBadgeContainer,
665
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
666
- style: [styles.storageStatusBadge, {
667
- backgroundColor: storageBackend === "filesystem" ? _sharedUi.buoyColors.success + "20" : storageBackend === "asyncstorage" ? _sharedUi.buoyColors.warning + "20" : _sharedUi.buoyColors.error + "20",
668
- borderColor: storageBackend === "filesystem" ? _sharedUi.buoyColors.success + "60" : storageBackend === "asyncstorage" ? _sharedUi.buoyColors.warning + "60" : _sharedUi.buoyColors.error + "60"
669
- }],
670
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
671
- style: [styles.storageStatusBadgeText, {
672
- color: storageBackend === "filesystem" ? _sharedUi.buoyColors.success : storageBackend === "asyncstorage" ? _sharedUi.buoyColors.warning : _sharedUi.buoyColors.error
673
- }],
674
- children: storageBackend === "filesystem" ? "FILE SYSTEM" : storageBackend === "asyncstorage" ? "ASYNC STORAGE" : storageBackend === "memory" ? "MEMORY" : "LOADING..."
675
- })
676
- }), storageBackend === "filesystem" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
677
- size: 14,
678
- color: _sharedUi.buoyColors.success
679
- }), storageBackend === "asyncstorage" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
680
- size: 14,
681
- color: _sharedUi.buoyColors.warning
682
- }), storageBackend === "memory" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
683
- size: 14,
684
- color: _sharedUi.buoyColors.error
685
- })]
686
- })]
687
- }), isStorageExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
688
- size: 18,
689
- color: _sharedUi.buoyColors.textMuted
690
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRightIcon, {
691
- size: 18,
692
- color: _sharedUi.buoyColors.textMuted
693
- })]
694
- })
695
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
696
- style: styles.storageStatusDescription,
697
- 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..."
698
- }), storageBackend === "asyncstorage" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
699
- style: styles.adviceHint,
700
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
701
- size: 14,
702
- color: _sharedUi.buoyColors.warning
703
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
704
- style: styles.adviceHintText,
705
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
706
- style: styles.adviceHintBold,
707
- children: "Tip:"
708
- }), " Install", " ", /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
709
- style: styles.adviceHintCode,
710
- children: "expo-file-system"
711
- }), " to persist settings through logout flows."]
616
+ accessibilityLabel: "Storage Type",
617
+ style: styles.settingRow,
618
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
619
+ style: styles.rowInfo,
620
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
621
+ style: styles.rowLabel,
622
+ children: "Storage Type"
623
+ })
624
+ }), storageBadge, isStorageExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
625
+ size: 16,
626
+ color: _sharedUi.night.color.textTertiary
627
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRightIcon, {
628
+ size: 16,
629
+ color: _sharedUi.night.color.textTertiary
712
630
  })]
713
- }), storageBackend === "memory" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
714
- style: styles.adviceHintsContainer,
631
+ }), isStorageExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
715
632
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
716
- style: [styles.adviceHint, {
717
- backgroundColor: _sharedUi.buoyColors.error + "10"
718
- }],
719
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
720
- size: 14,
721
- color: _sharedUi.buoyColors.error
722
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
723
- style: [styles.adviceHintText, {
724
- color: _sharedUi.buoyColors.error
725
- }],
726
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
727
- style: styles.adviceHintBold,
728
- children: "No persistent storage available!"
729
- }), " Settings will reset on every app restart."]
730
- })]
731
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
732
- style: styles.adviceHint,
733
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
734
- size: 14,
735
- color: _sharedUi.buoyColors.primary
736
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
737
- style: styles.adviceHintText,
738
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
739
- style: styles.adviceHintBold,
740
- children: "Best:"
741
- }), " Install", " ", /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
742
- style: styles.adviceHintCode,
743
- children: "expo-file-system"
744
- }), " for logout-safe persistence."]
633
+ style: styles.settingRowBody,
634
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
635
+ style: styles.settingRowBodyText,
636
+ 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…"
637
+ }), storageBackend === "asyncstorage" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
638
+ style: [styles.settingRowBodyText, styles.bodyWarning],
639
+ children: "Install expo-file-system to persist settings through logout flows."
640
+ }), storageBackend === "memory" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
641
+ style: [styles.settingRowBodyText, styles.bodyDanger],
642
+ children: "No persistent storage available \u2014 install expo-file-system (best) or @react-native-async-storage/async-storage."
745
643
  })]
746
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
747
- style: styles.adviceHint,
748
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.HardDrive, {
749
- size: 14,
750
- color: _sharedUi.buoyColors.textMuted
751
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
752
- style: styles.adviceHintText,
644
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSeparator, {}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
645
+ style: styles.storageExpanded,
646
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
647
+ style: styles.storageExpandedHeader,
753
648
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
754
- style: styles.adviceHintBold,
755
- children: "Alternative:"
756
- }), " Install", " ", /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
757
- style: styles.adviceHintCode,
758
- children: "@react-native-async-storage/async-storage"
759
- }), " for basic persistence."]
760
- })]
761
- })]
762
- }), isStorageExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
763
- style: styles.storageExpandedContent,
764
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
765
- style: styles.storageExpandedHeader,
766
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileText, {
767
- size: 14,
768
- color: _sharedUi.buoyColors.primary
769
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
770
- style: styles.storageExpandedTitle,
771
- children: "SAVED SETTINGS"
772
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
773
- style: styles.storageExpandedCount,
774
- children: savedKeysLoading ? "..." : `${savedKeys.length} keys`
775
- })]
776
- }), savedKeysLoading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
777
- style: styles.storageKeyItem,
778
- children: "Loading..."
779
- }) : savedKeys.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
780
- style: styles.storageKeyItemEmpty,
781
- children: "No settings saved yet"
782
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
783
- style: styles.storageKeysList,
784
- nestedScrollEnabled: true,
785
- showsVerticalScrollIndicator: true,
786
- children: savedKeys.map(key => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
787
- style: styles.storageKeyItem,
788
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
789
- style: styles.storageKeyText,
790
- numberOfLines: 1,
791
- children: key.replace("@react_buoy_", "")
792
- })
793
- }, key))
794
- })]
795
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
796
- style: [styles.clearStorageButton, isClearing && styles.clearStorageButtonDisabled, clearSuccess && styles.clearStorageButtonSuccess],
797
- onPress: handleClearStorage,
798
- disabled: isClearing,
799
- activeOpacity: 0.7,
800
- children: [clearSuccess ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
801
- size: 14,
802
- color: _sharedUi.buoyColors.success
803
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
804
- size: 14,
805
- color: isClearing ? _sharedUi.buoyColors.textMuted : _sharedUi.buoyColors.error
806
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
807
- style: [styles.clearStorageButtonText, clearSuccess && {
808
- color: _sharedUi.buoyColors.success
809
- }, isClearing && {
810
- color: _sharedUi.buoyColors.textMuted
811
- }],
812
- children: clearSuccess ? "CLEARED" : isClearing ? "CLEARING..." : "CLEAR ALL SETTINGS"
813
- })]
814
- })]
815
- }), desktopSyncStatus && desktopSyncStatus.state !== "idle" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
816
- style: [styles.storageStatusCard, {
817
- borderColor: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success + "60" : _sharedUi.buoyColors.warning + "60"
818
- }],
819
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
820
- style: styles.storageStatusHeader,
821
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
822
- style: [styles.storageStatusIcon, {
823
- backgroundColor: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success + "15" : _sharedUi.buoyColors.warning + "15"
824
- }],
825
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Globe, {
826
- size: 18,
827
- color: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success : _sharedUi.buoyColors.warning
828
- })
829
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
830
- style: styles.storageStatusInfo,
831
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
832
- style: styles.storageStatusLabel,
833
- children: "DESKTOP SYNC"
834
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
835
- style: styles.storageStatusBadgeContainer,
836
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
837
- style: [styles.storageStatusBadge, {
838
- backgroundColor: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success + "20" : _sharedUi.buoyColors.warning + "20",
839
- borderColor: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success + "60" : _sharedUi.buoyColors.warning + "60"
840
- }],
649
+ style: styles.storageExpandedTitle,
650
+ children: "SAVED SETTINGS"
651
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
652
+ style: styles.storageExpandedCount,
653
+ children: savedKeysLoading ? "…" : `${savedKeys.length} keys`
654
+ })]
655
+ }), savedKeysLoading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
656
+ style: styles.storageKeyEmpty,
657
+ children: "Loading\u2026"
658
+ }) : savedKeys.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
659
+ style: styles.storageKeyEmpty,
660
+ children: "No settings saved yet"
661
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
662
+ style: styles.storageKeysList,
663
+ nestedScrollEnabled: true,
664
+ showsVerticalScrollIndicator: true,
665
+ children: savedKeys.map(key => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
666
+ style: styles.storageKeyItem,
841
667
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
842
- style: [styles.storageStatusBadgeText, {
843
- color: desktopSyncStatus.state === "connected" ? _sharedUi.buoyColors.success : _sharedUi.buoyColors.warning
844
- }],
845
- children: desktopSyncStatus.state === "connected" ? "CONNECTED" : desktopSyncStatus.state === "connecting" ? "CONNECTING..." : "RETRYING"
668
+ style: styles.storageKeyText,
669
+ numberOfLines: 1,
670
+ children: key.replace("@react_buoy_", "")
846
671
  })
847
- }), desktopSyncStatus.state === "connected" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
848
- size: 14,
849
- color: _sharedUi.buoyColors.success
850
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
851
- size: 14,
852
- color: _sharedUi.buoyColors.warning
853
- })]
854
- })]
855
- })]
856
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
857
- style: styles.storageStatusDescription,
858
- children: [desktopSyncStatus.state === "connected" ? "Streaming tool data to Buoy Desktop at " : "Trying to reach Buoy Desktop at ", /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
859
- style: styles.adviceHintCode,
860
- children: desktopSyncStatus.targetURL ?? "(unknown URL)"
861
- }), desktopSyncStatus.state !== "connected" && " — is the desktop app running? Override with the externalSync socketURL option if it runs elsewhere."]
862
- }), desktopSyncStatus.state === "retrying" && desktopSyncStatus.lastError && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
863
- style: styles.adviceHint,
864
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
865
- size: 14,
866
- color: _sharedUi.buoyColors.warning
867
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
868
- style: styles.adviceHintText,
869
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
870
- style: styles.adviceHintBold,
871
- children: "Last error:"
872
- }), " ", desktopSyncStatus.lastError]
873
- })]
874
- })]
875
- }), renderGlobalSettingCard("enableSharedModalDimensions", "SHARED MODAL SIZE", "MODAL", "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. Enable only if you prefer uniform modal sizes across all dev tools."), _reactNative.Platform.OS !== "web" && renderGlobalSettingCard("expandableWindowControls", "EXPAND CONTROLS", "MODAL", "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 — similar to iPad window controls. 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 (e.g. when using a mouse or simulator)."), isDevelopmentMode && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
876
- style: styles.exportConfigCard,
877
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
878
- activeOpacity: 0.85,
879
- onPress: () => setIsExportExpanded(!isExportExpanded),
880
- style: styles.exportConfigHeader,
881
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
882
- style: styles.exportConfigIconContainer,
883
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileCode, {
884
- size: 18,
885
- color: _sharedUi.buoyColors.success
886
- })
887
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
888
- style: styles.exportConfigInfo,
889
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
890
- style: styles.exportConfigLabel,
891
- children: "EXPORT CONFIG"
892
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
893
- style: styles.exportConfigHint,
894
- children: "Save your settings to code"
672
+ }, key))
895
673
  })]
896
674
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
897
- style: styles.exportConfigActions,
898
- children: isExportExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
899
- size: 18,
900
- color: _sharedUi.buoyColors.textMuted
901
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRightIcon, {
902
- size: 18,
903
- color: _sharedUi.buoyColors.textMuted
675
+ style: styles.cardButtonWrap,
676
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightButton, {
677
+ label: clearSuccess ? "Cleared" : isClearing ? "Clearing…" : "Clear All Settings",
678
+ variant: clearSuccess ? "secondary" : "destructive",
679
+ size: "md",
680
+ disabled: isClearing,
681
+ onPress: handleClearStorage,
682
+ icon: clearSuccess ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
683
+ size: 15,
684
+ color: _sharedUi.night.color.accent
685
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
686
+ size: 15,
687
+ color: isClearing ? _sharedUi.night.color.textTertiary : _sharedUi.night.color.danger
688
+ })
904
689
  })
905
690
  })]
906
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
907
- style: styles.exportHintBanner,
908
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
909
- size: 14,
910
- color: _sharedUi.buoyColors.textMuted
911
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
912
- style: styles.exportHintText,
913
- children: "Configure tools, then export as props for your team."
914
- })]
915
- }), isExportExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
916
- style: styles.exportCodeContainer,
691
+ })]
692
+ })), desktopSyncStatus && desktopSyncStatus.state !== "idle" && renderSection("Desktop Sync", /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightCard, {
693
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
694
+ activeOpacity: 0.7,
695
+ onPress: () => toggleSettingExpanded("desktopSync"),
696
+ accessibilityLabel: "Buoy Desktop",
697
+ style: styles.settingRow,
917
698
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
918
- style: styles.exportCodeHeader,
919
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
920
- style: styles.exportCodeTitle,
921
- children: "PROPS TO ADD"
922
- })
923
- }), enabledConfig.floating.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
924
- style: styles.exportJsonBlock,
925
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
926
- style: styles.exportJsonProp,
927
- children: "defaultFloatingTools"
928
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
929
- style: styles.exportJsonEquals,
930
- children: "="
931
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
932
- style: styles.exportJsonBracket,
933
- children: "{"
934
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
935
- style: styles.exportJsonArrayBracket,
936
- children: "["
937
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
938
- style: styles.exportJsonArrayContent,
939
- children: enabledConfig.floating.map((id, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
940
- style: styles.exportJsonArrayItem,
941
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
942
- style: styles.exportJsonString,
943
- children: ["'", id, "'"]
944
- }), index < enabledConfig.floating.length - 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
945
- style: styles.exportJsonComma,
946
- children: ","
947
- })]
948
- }, id))
949
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
950
- style: styles.exportJsonArrayBracket,
951
- children: "]"
952
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
953
- style: styles.exportJsonBracket,
954
- children: "}"
955
- })]
956
- }), enabledConfig.dial.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
957
- style: [styles.exportJsonBlock, enabledConfig.floating.length > 0 && {
958
- marginTop: 8
959
- }],
960
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
961
- style: styles.exportJsonProp,
962
- children: "defaultDialTools"
963
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
964
- style: styles.exportJsonEquals,
965
- children: "="
966
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
967
- style: styles.exportJsonBracket,
968
- children: "{"
969
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
970
- style: styles.exportJsonArrayBracket,
971
- children: "["
972
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
973
- style: styles.exportJsonArrayContent,
974
- children: enabledConfig.dial.map((id, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
975
- style: styles.exportJsonArrayItem,
976
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
977
- style: styles.exportJsonString,
978
- children: ["'", id, "'"]
979
- }), index < enabledConfig.dial.length - 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
980
- style: styles.exportJsonComma,
981
- children: ","
982
- })]
983
- }, id))
984
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
985
- style: styles.exportJsonArrayBracket,
986
- children: "]"
987
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
988
- style: styles.exportJsonBracket,
989
- children: "}"
990
- })]
991
- }), enabledConfig.floating.length === 0 && enabledConfig.dial.length === 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
992
- style: styles.exportJsonBlock,
699
+ style: styles.rowInfo,
993
700
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
994
- style: styles.exportJsonComment,
995
- children: "// No tools enabled"
701
+ style: styles.rowLabel,
702
+ children: "Buoy Desktop"
996
703
  })
997
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
998
- style: styles.exportCodeDescription,
999
- children: ["Add these props to your existing", " ", /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1000
- style: styles.exportCodeInline,
1001
- children: "<FloatingDevTools />"
1002
- }), " ", "component to set team defaults."]
704
+ }), desktopSyncStatus.state === "connected" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
705
+ label: "Connected",
706
+ tone: "accent"
707
+ }) : desktopSyncStatus.state === "connecting" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
708
+ label: "Connecting\u2026",
709
+ tone: "neutral"
710
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
711
+ label: "Retrying",
712
+ tone: "warning"
713
+ }), expandedSettings.has("desktopSync") ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
714
+ size: 16,
715
+ color: _sharedUi.night.color.textTertiary
716
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRightIcon, {
717
+ size: 16,
718
+ color: _sharedUi.night.color.textTertiary
1003
719
  })]
1004
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
1005
- style: [styles.exportCopyButton, copySuccess && styles.exportCopyButtonSuccess],
1006
- onPress: handleCopyConfig,
1007
- activeOpacity: 0.7,
1008
- children: [copySuccess ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
1009
- size: 14,
1010
- color: _sharedUi.buoyColors.success
1011
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Copy, {
1012
- size: 14,
1013
- color: _sharedUi.buoyColors.success
1014
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1015
- style: [styles.exportCopyButtonText, copySuccess && {
1016
- color: _sharedUi.buoyColors.success
1017
- }],
1018
- children: copySuccess ? "COPIED!" : "COPY CONFIG TO CLIPBOARD"
720
+ }), expandedSettings.has("desktopSync") && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
721
+ style: styles.settingRowBody,
722
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
723
+ style: styles.settingRowBodyText,
724
+ children: [desktopSyncStatus.state === "connected" ? "Streaming tool data to Buoy Desktop at " : "Trying to reach Buoy Desktop at ", /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
725
+ style: styles.footnoteMono,
726
+ children: desktopSyncStatus.targetURL ?? "(unknown URL)"
727
+ }), desktopSyncStatus.state !== "connected" && " — is the desktop app running? Override with the externalSync socketURL option if it runs elsewhere."]
728
+ }), desktopSyncStatus.state === "retrying" && desktopSyncStatus.lastError && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
729
+ style: [styles.settingRowBodyText, styles.bodyWarning],
730
+ children: ["Last error: ", desktopSyncStatus.lastError]
1019
731
  })]
1020
732
  })]
1021
- })]
1022
- }), activeTab === "pro" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1023
- style: styles.proContainer,
1024
- children: isPro ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
733
+ })), renderSection("Modal Behavior", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
734
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.NightRows, {
735
+ 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]
736
+ })
737
+ }))]
738
+ }), activeTab === "pro" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
739
+ children: [renderSection("License", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
1025
740
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1026
- style: styles.proSection,
1027
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
1028
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Icon, {
1029
- icon: _sharedUi.Zap,
1030
- color: _sharedUi.buoyColors.primary,
1031
- size: 12
1032
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
1033
- children: "LICENSE STATUS"
1034
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Badge, {
1035
- count: "Active",
1036
- color: _sharedUi.buoyColors.success
1037
- })]
1038
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1039
- style: styles.proSectionContent,
741
+ style: styles.settingRow,
742
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
743
+ style: styles.rowInfo,
1040
744
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1041
- style: styles.proActiveDescription,
1042
- children: "You have full access to all Buoy DevTools features."
745
+ style: styles.rowLabel,
746
+ children: "License Status"
1043
747
  })
748
+ }), isPro ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
749
+ label: "Active",
750
+ tone: "accent"
751
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightBadge, {
752
+ label: "Free",
753
+ tone: "neutral"
1044
754
  })]
1045
755
  })
1046
- }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
1047
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1048
- style: styles.proSection,
1049
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
1050
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Icon, {
1051
- icon: _sharedUi.Zap,
1052
- color: _sharedUi.buoyColors.textMuted,
1053
- size: 12
1054
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
1055
- children: "LICENSE STATUS"
1056
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Badge, {
1057
- count: "Free",
1058
- color: _sharedUi.buoyColors.textMuted
1059
- })]
1060
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1061
- style: styles.proSectionContent,
1062
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1063
- style: styles.proFreeDescription,
1064
- children: "Upgrade to Pro to unlock all features and support development."
1065
- })
1066
- })]
1067
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1068
- style: styles.proSection,
1069
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
1070
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Icon, {
1071
- icon: _sharedUi.CheckCircle2,
1072
- color: _sharedUi.buoyColors.primary,
1073
- size: 12
1074
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
1075
- children: "PRO FEATURES"
1076
- })]
1077
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1078
- style: styles.proSectionContent,
1079
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1080
- style: styles.proFeaturesList,
1081
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1082
- style: styles.proFeatureItem,
1083
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
1084
- size: 14,
1085
- color: _sharedUi.buoyColors.primary
1086
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1087
- style: styles.proFeatureText,
1088
- children: "Advanced Settings"
1089
- })]
1090
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1091
- style: styles.proFeatureItem,
1092
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
1093
- size: 14,
1094
- color: _sharedUi.buoyColors.primary
1095
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1096
- style: styles.proFeatureText,
1097
- children: "Export Configuration"
1098
- })]
1099
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1100
- style: styles.proFeatureItem,
1101
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
1102
- size: 14,
1103
- color: _sharedUi.buoyColors.primary
1104
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1105
- style: styles.proFeatureText,
1106
- children: "Team Defaults"
1107
- })]
1108
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1109
- style: styles.proFeatureItem,
1110
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
1111
- size: 14,
1112
- color: _sharedUi.buoyColors.primary
1113
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1114
- style: styles.proFeatureText,
1115
- children: "Priority Support"
1116
- })]
756
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightFootnote, {
757
+ 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 api.keygen.sh — 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."
758
+ })), !isPro && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
759
+ children: [renderSection("Pro Features", /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightCard, {
760
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightRows, {
761
+ children: ["Advanced Settings", "Priority Support"].map(feature => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
762
+ style: styles.featureRow,
763
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle2, {
764
+ size: 16,
765
+ color: _sharedUi.night.color.accent
766
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
767
+ style: styles.rowLabel,
768
+ children: feature
1117
769
  })]
1118
- })
1119
- })]
1120
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
1121
- style: styles.proUpgradeBtn,
1122
- onPress: () => setShowLicenseModal(true),
1123
- activeOpacity: 0.8,
1124
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
1125
- size: 16,
1126
- color: _sharedUi.buoyColors.base
1127
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1128
- style: styles.proUpgradeBtnText,
1129
- children: "Upgrade to Pro"
1130
- })]
770
+ }, feature))
771
+ })
772
+ })), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightButton, {
773
+ label: "Upgrade to Pro",
774
+ variant: "primary",
775
+ size: "lg",
776
+ onPress: () => setShowLicenseModal(true)
1131
777
  })]
1132
- })
778
+ })]
1133
779
  })]
1134
780
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.LicenseEntryModal, {
1135
781
  visible: showLicenseModal,
@@ -1140,27 +786,25 @@ const DevToolsSettingsModal = ({
1140
786
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.JsModal, {
1141
787
  visible: visible,
1142
788
  onClose: onClose,
789
+ variant: "night",
1143
790
  header: {
1144
791
  showToggleButton: false,
1145
- customContent: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
1146
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
1147
- title: "",
1148
- noMargin: true,
1149
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
1150
- tabs: [{
1151
- key: "floating",
1152
- label: "FLOATING"
1153
- }, {
1154
- key: "settings",
1155
- label: "SETTINGS"
1156
- }, {
1157
- key: "pro",
1158
- label: "PRO"
1159
- }],
1160
- activeTab: activeTab,
1161
- onTabChange: tab => setActiveTab(tab)
1162
- })
1163
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions, {})]
792
+ customContent: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
793
+ style: styles.headerRow,
794
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSegmented, {
795
+ tabs: [{
796
+ key: "floating",
797
+ label: "Floating"
798
+ }, {
799
+ key: "settings",
800
+ label: "Settings"
801
+ }, {
802
+ key: "pro",
803
+ label: "Pro"
804
+ }],
805
+ activeKey: activeTab,
806
+ onChange: tab => setActiveTab(tab)
807
+ })
1164
808
  })
1165
809
  },
1166
810
  initialMode: "bottomSheet",
@@ -1236,6 +880,30 @@ const createDefaultsFromConfig = (defaultFloatingTools, defaultDialTools) => {
1236
880
  * When no saved settings exist, the hook uses the default configuration from
1237
881
  * the DefaultConfigProvider (if available) to determine initial tool states.
1238
882
  */
883
+ /**
884
+ * Session cache for the values the settings modal loads asynchronously on mount.
885
+ *
886
+ * Without it every open renders three times: once with defaults, then again as
887
+ * each of the persisted reads (settings, active tab, storage backend) resolves —
888
+ * and each of those re-renders rebuilds the whole tool list. The values cannot
889
+ * change behind our back (writes go through this module, and settingsBus
890
+ * broadcasts them), so after the first open we can render the real values
891
+ * immediately and only re-render if a read actually came back different.
892
+ */
893
+ let cachedSettings = null;
894
+ let cachedActiveTab = null;
895
+ let cachedStorageBackend = null;
896
+
897
+ /** Cheap structural equality — these objects are small and JSON-safe. */
898
+ const sameSettings = (a, b) => JSON.stringify(a) === JSON.stringify(b);
899
+
900
+ // Every surface that changes settings broadcasts on the bus, so listening here
901
+ // keeps the cache authoritative rather than merely last-seen — otherwise a
902
+ // change made while the modal was unmounted would show stale for one frame.
903
+ // Registered for the app's lifetime on purpose; there is nothing to unsubscribe.
904
+ _settingsBus.settingsBus.addListener(payload => {
905
+ if (payload) cachedSettings = payload;
906
+ });
1239
907
  const useDevToolsSettings = () => {
1240
908
  // Get team default configuration from context
1241
909
  const {
@@ -1297,800 +965,144 @@ exports.useDevToolsSettings = useDevToolsSettings;
1297
965
  const styles = _reactNative.StyleSheet.create({
1298
966
  container: {
1299
967
  flex: 1,
1300
- backgroundColor: _sharedUi.buoyColors.base
1301
- },
1302
- // Header styles matching React Query modal exactly
1303
- headerContainer: {
1304
- flex: 1,
1305
- flexDirection: "row",
1306
- alignItems: "center",
1307
- justifyContent: "space-between",
1308
- paddingHorizontal: 12
968
+ // Transparent so JsModal's night star field shows through everything.
969
+ backgroundColor: "transparent"
1309
970
  },
1310
- tabNavigationContainer: {
1311
- flex: 1,
971
+ // Header — the segmented control, inset so it clears the absolutely
972
+ // positioned window controls on the right.
973
+ headerRow: {
1312
974
  flexDirection: "row",
1313
- backgroundColor: _sharedUi.buoyColors.hover,
1314
- borderRadius: 6,
1315
- padding: 2,
1316
- borderWidth: 1,
1317
- borderColor: _sharedUi.buoyColors.border,
1318
- justifyContent: "space-evenly"
1319
- },
1320
- tabButton: {
1321
- paddingHorizontal: 8,
1322
- paddingVertical: 5,
1323
- borderRadius: 4,
1324
975
  alignItems: "center",
1325
- justifyContent: "center",
1326
- flex: 1,
1327
- marginHorizontal: 1
1328
- },
1329
- tabButtonActive: {
1330
- backgroundColor: _sharedUi.buoyColors.primary + "20",
1331
- borderWidth: 1,
1332
- borderColor: _sharedUi.buoyColors.primary
976
+ paddingLeft: _sharedUi.night.gutter,
977
+ paddingRight: 64,
978
+ paddingBottom: 8
1333
979
  },
1334
- tabButtonInactive: {
1335
- backgroundColor: "transparent"
1336
- },
1337
- tabButtonText: {
1338
- fontSize: 12,
1339
- fontWeight: "600",
1340
- letterSpacing: 0.5,
1341
- fontFamily: "monospace",
1342
- textTransform: "uppercase"
1343
- },
1344
- tabButtonTextActive: {
1345
- color: _sharedUi.buoyColors.primary
1346
- },
1347
- tabButtonTextInactive: {
1348
- color: _sharedUi.buoyColors.textMuted
1349
- },
1350
- // Scroll content
980
+ // Scroll content — labeled groups on a 24pt rhythm (Everlights create screen)
1351
981
  scrollContent: {
1352
982
  flex: 1
1353
983
  },
1354
984
  scrollContainer: {
1355
- paddingTop: 16,
1356
- paddingBottom: 24
1357
- },
1358
- // Sections
1359
- section: {
1360
- marginHorizontal: 16,
1361
- marginBottom: 24
985
+ paddingHorizontal: _sharedUi.night.gutter,
986
+ paddingTop: 8,
987
+ paddingBottom: 24,
988
+ gap: _sharedUi.night.groupGap
1362
989
  },
1363
- sectionHeader: {
990
+ // Rows
991
+ toolRow: {
1364
992
  flexDirection: "row",
1365
993
  alignItems: "center",
1366
- marginBottom: 12,
1367
- paddingHorizontal: 4
1368
- },
1369
- sectionIndicator: {
1370
- width: 3,
1371
- height: 16,
1372
- borderRadius: 2,
1373
- backgroundColor: _sharedUi.buoyColors.primary,
1374
- marginRight: 8,
1375
- shadowColor: _sharedUi.buoyColors.primary,
1376
- shadowOffset: {
1377
- width: 0,
1378
- height: 0
1379
- },
1380
- shadowOpacity: 0.6,
1381
- shadowRadius: 4
1382
- },
1383
- sectionTitle: {
1384
- flex: 1,
1385
- color: _sharedUi.buoyColors.primary,
1386
- fontSize: 13,
1387
- fontWeight: "700",
1388
- letterSpacing: 1.2
1389
- },
1390
- sectionCount: {
1391
- color: _sharedUi.buoyColors.textSecondary,
1392
- fontSize: 11,
1393
- opacity: 0.7
1394
- },
1395
- // Tool Cards - Clean unified Buoy theme
1396
- glassCard: {
1397
- borderRadius: 12,
1398
- paddingVertical: 12,
1399
- paddingHorizontal: 14,
1400
- backgroundColor: _sharedUi.buoyColors.card,
1401
- borderWidth: 1,
1402
- borderColor: _sharedUi.buoyColors.border
1403
- },
1404
- glassCardActive: {
1405
- borderColor: _sharedUi.buoyColors.primary + "50",
1406
- backgroundColor: _sharedUi.buoyColors.primary + "08"
994
+ gap: 12,
995
+ paddingHorizontal: _sharedUi.night.rowPadH,
996
+ paddingVertical: 12
1407
997
  },
1408
- glassCardInner: {
998
+ settingRow: {
1409
999
  flexDirection: "row",
1410
1000
  alignItems: "center",
1411
- gap: 12
1412
- },
1413
- iconContainer: {
1414
- width: 32,
1415
- height: 32,
1416
- borderRadius: 8,
1417
- backgroundColor: _sharedUi.buoyColors.hover,
1418
- alignItems: "center",
1419
- justifyContent: "center"
1420
- },
1421
- iconContainerActive: {
1422
- backgroundColor: _sharedUi.buoyColors.primary + "20"
1423
- },
1424
- toolInfo: {
1425
- flex: 1
1426
- },
1427
- toolName: {
1428
- color: _sharedUi.buoyColors.text,
1429
- fontWeight: "600",
1430
- fontSize: 13
1431
- },
1432
- toolNameActive: {
1433
- color: _sharedUi.buoyColors.text
1001
+ gap: 10,
1002
+ paddingHorizontal: _sharedUi.night.rowPadH,
1003
+ paddingVertical: _sharedUi.night.rowPadV,
1004
+ minHeight: 52
1434
1005
  },
1435
- toolDescription: {
1436
- color: _sharedUi.buoyColors.textMuted,
1437
- fontSize: 11,
1438
- marginTop: 2
1439
- },
1440
- pillToggle: {
1441
- paddingHorizontal: 14,
1442
- paddingVertical: 6,
1443
- borderRadius: 6,
1444
- borderWidth: 1,
1445
- minWidth: 48,
1446
- alignItems: "center"
1447
- },
1448
- pillToggleOn: {
1449
- backgroundColor: _sharedUi.buoyColors.primary + "20",
1450
- borderColor: _sharedUi.buoyColors.primary
1451
- },
1452
- pillToggleOff: {
1453
- backgroundColor: _sharedUi.buoyColors.hover,
1454
- borderColor: _sharedUi.buoyColors.border
1455
- },
1456
- pillToggleText: {
1457
- fontWeight: "600",
1458
- fontSize: 11,
1459
- letterSpacing: 0.5
1460
- },
1461
- pillToggleTextOn: {
1462
- color: _sharedUi.buoyColors.primary
1463
- },
1464
- pillToggleTextOff: {
1465
- color: _sharedUi.buoyColors.textMuted
1466
- },
1467
- closeButton: {
1468
- marginLeft: 8,
1469
- paddingHorizontal: 10,
1470
- paddingVertical: 6,
1471
- borderRadius: 6,
1472
- backgroundColor: _sharedUi.buoyColors.error + "1A",
1473
- borderWidth: 1,
1474
- borderColor: _sharedUi.buoyColors.error + "33"
1475
- },
1476
- closeButtonText: {
1477
- color: _sharedUi.buoyColors.error,
1478
- fontSize: 14,
1479
- fontWeight: "700",
1480
- letterSpacing: 0.5
1481
- },
1482
- // Expandable Settings Card styles
1483
- expandableCard: {
1484
- backgroundColor: _sharedUi.buoyColors.card,
1485
- borderRadius: 8,
1486
- borderWidth: 1,
1487
- borderColor: _sharedUi.buoyColors.border,
1488
- padding: 12
1489
- },
1490
- expandableCardHeader: {
1006
+ featureRow: {
1491
1007
  flexDirection: "row",
1492
1008
  alignItems: "center",
1493
- gap: 12
1009
+ gap: 10,
1010
+ paddingHorizontal: _sharedUi.night.rowPadH,
1011
+ paddingVertical: 12
1494
1012
  },
1495
- expandableCardCategory: {
1496
- backgroundColor: _sharedUi.buoyColors.primary + "20",
1497
- borderWidth: 1,
1498
- borderColor: _sharedUi.buoyColors.primary + "40",
1499
- borderRadius: 4,
1500
- paddingHorizontal: 8,
1501
- paddingVertical: 4
1013
+ rowDisabled: {
1014
+ opacity: 0.4
1502
1015
  },
1503
- expandableCardCategoryText: {
1504
- fontSize: 10,
1505
- fontWeight: "700",
1506
- color: _sharedUi.buoyColors.primary,
1507
- letterSpacing: 0.5
1016
+ toolIconSlot: {
1017
+ width: 24,
1018
+ alignItems: "center"
1508
1019
  },
1509
- expandableCardTitle: {
1020
+ rowInfo: {
1510
1021
  flex: 1,
1511
- paddingHorizontal: 4
1512
- },
1513
- expandableCardTitleText: {
1514
- fontFamily: "monospace",
1515
- fontSize: 12,
1516
- fontWeight: "700",
1517
- color: _sharedUi.buoyColors.text
1518
- },
1519
- expandableCardSubtitle: {
1520
- fontSize: 10,
1521
- color: _sharedUi.buoyColors.textMuted,
1522
- marginTop: 2
1523
- },
1524
- expandableCardActions: {
1525
- flexDirection: "row",
1526
- alignItems: "center",
1527
- gap: 8
1528
- },
1529
- expandableCardBody: {
1530
- marginTop: 12,
1531
- paddingTop: 12,
1532
- borderTopWidth: 1,
1533
- borderTopColor: _sharedUi.buoyColors.border + "50"
1534
- },
1535
- expandableCardSection: {
1536
- marginBottom: 12
1537
- },
1538
- expandableCardSectionTitle: {
1539
- fontSize: 10,
1540
- fontWeight: "700",
1541
- color: _sharedUi.buoyColors.primary,
1542
- letterSpacing: 1,
1543
- marginBottom: 6
1544
- },
1545
- expandableCardSectionText: {
1546
- fontSize: 12,
1547
- color: _sharedUi.buoyColors.textSecondary,
1548
- lineHeight: 18
1549
- },
1550
- // Storage Status Card styles
1551
- storageStatusCard: {
1552
- backgroundColor: _sharedUi.buoyColors.card,
1553
- borderRadius: 8,
1554
- borderWidth: 1,
1555
- borderColor: _sharedUi.buoyColors.border,
1556
- padding: 12,
1557
- marginBottom: 10
1558
- },
1559
- storageStatusHeader: {
1560
- flexDirection: "row",
1561
- alignItems: "center",
1562
- gap: 12,
1563
- marginBottom: 8
1564
- },
1565
- storageStatusIcon: {
1566
- width: 36,
1567
- height: 36,
1568
- borderRadius: 8,
1569
- backgroundColor: _sharedUi.buoyColors.hover,
1570
- alignItems: "center",
1571
- justifyContent: "center"
1022
+ gap: 2
1572
1023
  },
1573
- storageStatusInfo: {
1574
- flex: 1
1024
+ rowLabel: {
1025
+ color: _sharedUi.night.color.text,
1026
+ fontSize: _sharedUi.night.font.row,
1027
+ fontWeight: "500"
1575
1028
  },
1576
- storageStatusLabel: {
1577
- fontSize: 10,
1578
- fontWeight: "700",
1579
- color: _sharedUi.buoyColors.textMuted,
1580
- letterSpacing: 1,
1581
- marginBottom: 4
1029
+ rowCaption: {
1030
+ color: _sharedUi.night.color.textTertiary,
1031
+ fontSize: _sharedUi.night.font.label
1582
1032
  },
1583
- storageStatusBadgeContainer: {
1584
- flexDirection: "row",
1585
- alignItems: "center",
1033
+ // Expanded body under a global-setting row
1034
+ settingRowBody: {
1035
+ paddingHorizontal: _sharedUi.night.rowPadH,
1036
+ paddingBottom: _sharedUi.night.rowPadV,
1586
1037
  gap: 6
1587
1038
  },
1588
- storageStatusBadge: {
1589
- paddingHorizontal: 8,
1590
- paddingVertical: 4,
1591
- borderRadius: 4,
1592
- borderWidth: 1
1593
- },
1594
- storageStatusBadgeText: {
1595
- fontSize: 11,
1596
- fontWeight: "700",
1597
- letterSpacing: 0.5
1039
+ settingRowBodyText: {
1040
+ color: _sharedUi.night.color.textSecondary,
1041
+ fontSize: _sharedUi.night.font.caption,
1042
+ lineHeight: _sharedUi.night.font.caption * 1.45
1598
1043
  },
1599
- storageStatusDescription: {
1600
- fontSize: 11,
1601
- color: _sharedUi.buoyColors.textMuted,
1602
- lineHeight: 16,
1603
- marginBottom: 12
1604
- },
1605
- clearStorageButton: {
1606
- flexDirection: "row",
1607
- alignItems: "center",
1608
- justifyContent: "center",
1609
- gap: 8,
1610
- paddingVertical: 10,
1611
- paddingHorizontal: 16,
1612
- borderRadius: 6,
1613
- backgroundColor: _sharedUi.buoyColors.error + "15",
1614
- borderWidth: 1,
1615
- borderColor: _sharedUi.buoyColors.error + "40"
1044
+ settingRowBodyHint: {
1045
+ color: _sharedUi.night.color.textTertiary,
1046
+ fontSize: _sharedUi.night.font.caption,
1047
+ lineHeight: _sharedUi.night.font.caption * 1.45
1616
1048
  },
1617
- clearStorageButtonDisabled: {
1618
- opacity: 0.5
1049
+ bodyWarning: {
1050
+ color: _sharedUi.night.color.warning
1619
1051
  },
1620
- clearStorageButtonSuccess: {
1621
- backgroundColor: _sharedUi.buoyColors.success + "15",
1622
- borderColor: _sharedUi.buoyColors.success + "40"
1052
+ bodyDanger: {
1053
+ color: _sharedUi.night.color.danger
1623
1054
  },
1624
- clearStorageButtonText: {
1625
- fontSize: 12,
1626
- fontWeight: "700",
1627
- color: _sharedUi.buoyColors.error,
1628
- letterSpacing: 0.5
1629
- },
1630
- // Advice hints
1631
- adviceHintsContainer: {
1632
- gap: 8,
1633
- marginBottom: 12
1634
- },
1635
- adviceHint: {
1636
- flexDirection: "row",
1637
- alignItems: "flex-start",
1638
- gap: 8,
1639
- backgroundColor: _sharedUi.buoyColors.warning + "10",
1640
- borderRadius: 6,
1641
- padding: 10,
1642
- marginBottom: 8
1643
- },
1644
- adviceHintText: {
1645
- flex: 1,
1646
- fontSize: 11,
1647
- color: _sharedUi.buoyColors.textSecondary,
1648
- lineHeight: 16
1055
+ // Buttons that live inside a card
1056
+ cardButtonWrap: {
1057
+ paddingHorizontal: _sharedUi.night.cardPad,
1058
+ paddingTop: 10,
1059
+ paddingBottom: _sharedUi.night.cardPad
1649
1060
  },
1650
- adviceHintBold: {
1651
- fontWeight: "700",
1652
- color: _sharedUi.buoyColors.text
1653
- },
1654
- adviceHintCode: {
1655
- fontFamily: "monospace",
1656
- fontSize: 10,
1657
- color: _sharedUi.buoyColors.info,
1658
- backgroundColor: _sharedUi.buoyColors.info + "15",
1659
- paddingHorizontal: 4,
1660
- borderRadius: 3
1661
- },
1662
- // Expanded storage content
1663
- storageExpandedContent: {
1664
- marginTop: 12,
1665
- paddingTop: 12,
1666
- borderTopWidth: 1,
1667
- borderTopColor: _sharedUi.buoyColors.border + "50"
1061
+ // Storage expanded key list
1062
+ storageExpanded: {
1063
+ paddingHorizontal: _sharedUi.night.rowPadH,
1064
+ paddingVertical: 12,
1065
+ gap: 8
1668
1066
  },
1669
1067
  storageExpandedHeader: {
1670
1068
  flexDirection: "row",
1671
- alignItems: "center",
1672
- gap: 8,
1673
- marginBottom: 10
1069
+ alignItems: "center"
1674
1070
  },
1675
1071
  storageExpandedTitle: {
1676
- fontSize: 10,
1677
- fontWeight: "700",
1678
- color: _sharedUi.buoyColors.primary,
1679
- letterSpacing: 1,
1680
- flex: 1
1072
+ flex: 1,
1073
+ color: _sharedUi.night.color.textSecondary,
1074
+ fontSize: _sharedUi.night.font.micro,
1075
+ fontWeight: "600",
1076
+ letterSpacing: 0.5
1681
1077
  },
1682
1078
  storageExpandedCount: {
1683
- fontSize: 10,
1684
- color: _sharedUi.buoyColors.textMuted
1079
+ color: _sharedUi.night.color.textTertiary,
1080
+ fontSize: _sharedUi.night.font.micro
1685
1081
  },
1686
1082
  storageKeysList: {
1687
- gap: 4,
1688
1083
  maxHeight: 150
1689
1084
  },
1690
1085
  storageKeyItem: {
1691
- backgroundColor: _sharedUi.buoyColors.hover,
1692
- borderRadius: 4,
1693
- paddingHorizontal: 8,
1694
- paddingVertical: 6,
1695
- borderWidth: 1,
1696
- borderColor: _sharedUi.buoyColors.border + "50"
1697
- },
1698
- storageKeyItemEmpty: {
1699
- fontSize: 11,
1700
- color: _sharedUi.buoyColors.textMuted,
1701
- fontStyle: "italic",
1702
- paddingVertical: 8
1086
+ backgroundColor: _sharedUi.night.color.surfaceElevated,
1087
+ borderRadius: _sharedUi.night.radius.segment,
1088
+ paddingHorizontal: 10,
1089
+ paddingVertical: 7,
1090
+ marginBottom: 4
1703
1091
  },
1704
1092
  storageKeyText: {
1705
- fontSize: 11,
1706
- color: _sharedUi.buoyColors.textSecondary,
1093
+ color: _sharedUi.night.color.textSecondary,
1094
+ fontSize: _sharedUi.night.font.micro,
1707
1095
  fontFamily: "monospace"
1708
1096
  },
1709
- // Export Config Card styles
1710
- exportConfigCard: {
1711
- backgroundColor: _sharedUi.buoyColors.card,
1712
- borderRadius: 8,
1713
- borderWidth: 1,
1714
- borderColor: _sharedUi.buoyColors.success + "40",
1715
- padding: 12,
1716
- marginTop: 10
1717
- },
1718
- exportConfigHeader: {
1719
- flexDirection: "row",
1720
- alignItems: "center",
1721
- gap: 12
1722
- },
1723
- exportConfigIconContainer: {
1724
- width: 36,
1725
- height: 36,
1726
- borderRadius: 8,
1727
- backgroundColor: _sharedUi.buoyColors.success + "15",
1728
- alignItems: "center",
1729
- justifyContent: "center"
1730
- },
1731
- exportConfigInfo: {
1732
- flex: 1
1733
- },
1734
- exportConfigLabel: {
1735
- fontSize: 12,
1736
- fontWeight: "700",
1737
- color: _sharedUi.buoyColors.success,
1738
- letterSpacing: 0.5
1739
- },
1740
- exportConfigHint: {
1741
- fontSize: 10,
1742
- color: _sharedUi.buoyColors.textMuted,
1743
- marginTop: 2
1744
- },
1745
- exportConfigActions: {
1746
- flexDirection: "row",
1747
- alignItems: "center"
1748
- },
1749
- exportHintBanner: {
1750
- flexDirection: "row",
1751
- alignItems: "flex-start",
1752
- gap: 8,
1753
- backgroundColor: _sharedUi.buoyColors.warning + "10",
1754
- borderRadius: 6,
1755
- padding: 10,
1756
- marginTop: 12
1757
- },
1758
- exportHintText: {
1759
- flex: 1,
1760
- fontSize: 11,
1761
- color: _sharedUi.buoyColors.textSecondary,
1762
- lineHeight: 16
1763
- },
1764
- exportHintBold: {
1765
- fontWeight: "700",
1766
- color: _sharedUi.buoyColors.warning
1767
- },
1768
- exportCodeContainer: {
1769
- marginTop: 12,
1770
- paddingTop: 12,
1771
- borderTopWidth: 1,
1772
- borderTopColor: _sharedUi.buoyColors.border + "50"
1773
- },
1774
- exportCodeHeader: {
1775
- flexDirection: "row",
1776
- alignItems: "center",
1777
- gap: 8,
1778
- marginBottom: 8
1779
- },
1780
- exportCodeTitle: {
1781
- fontSize: 10,
1782
- fontWeight: "700",
1783
- color: _sharedUi.buoyColors.success,
1784
- letterSpacing: 1
1785
- },
1786
- exportCodeBlock: {
1787
- backgroundColor: _sharedUi.buoyColors.hover,
1788
- borderRadius: 6,
1789
- padding: 12,
1790
- borderWidth: 1,
1791
- borderColor: _sharedUi.buoyColors.border + "50"
1792
- },
1793
- exportCodeText: {
1794
- fontSize: 11,
1795
- color: _sharedUi.buoyColors.text,
1796
- fontFamily: "monospace",
1797
- lineHeight: 18
1798
- },
1799
- exportCodeDescription: {
1800
- fontSize: 10,
1801
- color: _sharedUi.buoyColors.textMuted,
1802
- marginTop: 8,
1803
- lineHeight: 14
1804
- },
1805
- exportCodeInline: {
1806
- fontFamily: "monospace",
1807
- fontSize: 10,
1808
- color: _sharedUi.buoyColors.info,
1809
- backgroundColor: _sharedUi.buoyColors.info + "15",
1810
- paddingHorizontal: 4,
1811
- borderRadius: 3
1812
- },
1813
- exportCopyButton: {
1814
- flexDirection: "row",
1815
- alignItems: "center",
1816
- justifyContent: "center",
1817
- gap: 8,
1818
- paddingVertical: 10,
1819
- paddingHorizontal: 16,
1820
- borderRadius: 6,
1821
- backgroundColor: _sharedUi.buoyColors.success + "15",
1822
- borderWidth: 1,
1823
- borderColor: _sharedUi.buoyColors.success + "40",
1824
- marginTop: 12
1825
- },
1826
- exportCopyButtonSuccess: {
1827
- backgroundColor: _sharedUi.buoyColors.success + "25",
1828
- borderColor: _sharedUi.buoyColors.success + "60"
1829
- },
1830
- exportCopyButtonText: {
1831
- fontSize: 12,
1832
- fontWeight: "700",
1833
- color: _sharedUi.buoyColors.success,
1834
- letterSpacing: 0.5
1835
- },
1836
- // JSON-like viewer styles (syntax highlighting)
1837
- exportJsonBlock: {
1838
- backgroundColor: _sharedUi.buoyColors.hover,
1839
- borderRadius: 6,
1840
- padding: 12,
1841
- borderWidth: 1,
1842
- borderColor: _sharedUi.buoyColors.border + "50",
1843
- flexDirection: "row",
1844
- flexWrap: "wrap",
1845
- alignItems: "center",
1846
- gap: 4
1847
- },
1848
- exportJsonProp: {
1849
- fontSize: 12,
1850
- fontFamily: "monospace",
1851
- color: _sharedUi.buoyColors.info,
1852
- // Cyan for prop names
1853
- fontWeight: "600"
1854
- },
1855
- exportJsonEquals: {
1856
- fontSize: 12,
1857
- fontFamily: "monospace",
1858
- color: _sharedUi.buoyColors.textMuted
1859
- },
1860
- exportJsonBracket: {
1861
- fontSize: 12,
1862
- fontFamily: "monospace",
1863
- color: _sharedUi.buoyColors.warning // Yellow for JSX brackets
1864
- },
1865
- exportJsonArrayBracket: {
1866
- fontSize: 12,
1867
- fontFamily: "monospace",
1868
- color: _sharedUi.buoyColors.text
1869
- },
1870
- exportJsonArrayContent: {
1871
- flexDirection: "row",
1872
- flexWrap: "wrap",
1873
- alignItems: "center",
1874
- gap: 2
1875
- },
1876
- exportJsonArrayItem: {
1877
- flexDirection: "row",
1878
- alignItems: "center"
1879
- },
1880
- exportJsonString: {
1881
- fontSize: 12,
1882
- fontFamily: "monospace",
1883
- color: _sharedUi.buoyColors.success // Green for strings
1884
- },
1885
- exportJsonComma: {
1886
- fontSize: 12,
1887
- fontFamily: "monospace",
1888
- color: _sharedUi.buoyColors.textMuted,
1889
- marginRight: 4
1890
- },
1891
- exportJsonComment: {
1892
- fontSize: 12,
1893
- fontFamily: "monospace",
1894
- color: _sharedUi.buoyColors.textMuted,
1895
- fontStyle: "italic"
1896
- },
1897
- // Pro tab styles - using buoyColors for consistency with other modals
1898
- proContainer: {
1899
- paddingHorizontal: 16,
1900
- paddingTop: 16,
1901
- paddingBottom: 24
1902
- },
1903
- proSection: {
1904
- backgroundColor: _sharedUi.buoyColors.card,
1905
- borderRadius: 8,
1906
- borderWidth: 1,
1907
- borderColor: _sharedUi.buoyColors.border,
1908
- marginBottom: 12,
1909
- overflow: "hidden"
1910
- },
1911
- proSectionContent: {
1912
- paddingHorizontal: 16,
1913
- paddingTop: 8,
1914
- paddingBottom: 16
1915
- },
1916
- proStatsRow: {
1917
- flexDirection: "row",
1918
- justifyContent: "center",
1919
- alignItems: "center",
1920
- paddingVertical: 8
1921
- },
1922
- proStatItem: {
1923
- alignItems: "center",
1924
- paddingHorizontal: 20
1925
- },
1926
- proStatValue: {
1927
- fontSize: 20,
1928
- fontWeight: "700",
1929
- color: _sharedUi.buoyColors.text
1930
- },
1931
- proStatValueDanger: {
1932
- color: _sharedUi.buoyColors.error
1933
- },
1934
- proStatLabel: {
1935
- fontSize: 10,
1936
- color: _sharedUi.buoyColors.textMuted,
1937
- letterSpacing: 0.5,
1938
- marginTop: 2
1939
- },
1940
- proStatDivider: {
1941
- width: 1,
1942
- height: 24,
1943
- backgroundColor: _sharedUi.buoyColors.border
1944
- },
1945
- proEmptyState: {
1946
- alignItems: "center",
1947
- paddingVertical: 20,
1948
- gap: 8
1949
- },
1950
- proEmptyStateText: {
1951
- fontSize: 11,
1952
- color: _sharedUi.buoyColors.textMuted,
1097
+ storageKeyEmpty: {
1098
+ color: _sharedUi.night.color.textTertiary,
1099
+ fontSize: _sharedUi.night.font.caption,
1953
1100
  fontStyle: "italic"
1954
1101
  },
1955
- proErrorState: {
1956
- flexDirection: "row",
1957
- alignItems: "center",
1958
- gap: 8,
1959
- paddingVertical: 12,
1960
- paddingHorizontal: 12,
1961
- backgroundColor: _sharedUi.buoyColors.error + "15",
1962
- borderRadius: 6
1963
- },
1964
- proErrorStateText: {
1965
- fontSize: 11,
1966
- color: _sharedUi.buoyColors.error,
1967
- flex: 1
1968
- },
1969
- proDevicesList: {
1970
- gap: 6
1971
- },
1972
- proDeviceRow: {
1973
- flexDirection: "row",
1974
- alignItems: "center",
1975
- paddingVertical: 8,
1976
- paddingHorizontal: 10,
1977
- backgroundColor: _sharedUi.buoyColors.input,
1978
- borderRadius: 6,
1979
- borderWidth: 1,
1980
- borderColor: _sharedUi.buoyColors.border
1981
- },
1982
- proDeviceRowCurrent: {
1983
- borderColor: _sharedUi.buoyColors.success + "50",
1984
- backgroundColor: _sharedUi.buoyColors.success + "08"
1985
- },
1986
- proDeviceIcon: {
1987
- width: 28,
1988
- height: 28,
1989
- borderRadius: 14,
1990
- backgroundColor: _sharedUi.buoyColors.hover,
1991
- alignItems: "center",
1992
- justifyContent: "center"
1993
- },
1994
- proDeviceInfo: {
1995
- flex: 1,
1996
- marginLeft: 10
1997
- },
1998
- proDeviceNameRow: {
1999
- flexDirection: "row",
2000
- alignItems: "center",
2001
- gap: 6
2002
- },
2003
- proDeviceName: {
2004
- fontSize: 12,
2005
- fontWeight: "600",
2006
- color: _sharedUi.buoyColors.text,
2007
- flexShrink: 1
2008
- },
2009
- proDeviceBadge: {
2010
- backgroundColor: _sharedUi.buoyColors.success + "20",
2011
- paddingHorizontal: 5,
2012
- paddingVertical: 1,
2013
- borderRadius: 3
2014
- },
2015
- proDeviceBadgeText: {
2016
- fontSize: 8,
2017
- fontWeight: "700",
2018
- color: _sharedUi.buoyColors.success,
2019
- letterSpacing: 0.3
2020
- },
2021
- proDeviceDetails: {
2022
- fontSize: 10,
2023
- color: _sharedUi.buoyColors.textMuted,
2024
- marginTop: 2
2025
- },
2026
- proDeviceDate: {
2027
- fontSize: 9,
2028
- color: _sharedUi.buoyColors.textMuted,
2029
- marginTop: 2
2030
- },
2031
- proDeviceRemoveBtn: {
2032
- padding: 6,
2033
- marginLeft: 4
2034
- },
2035
- proRegisterDeviceBtn: {
2036
- flexDirection: "row",
2037
- alignItems: "center",
2038
- justifyContent: "center",
2039
- gap: 8,
2040
- paddingVertical: 10,
2041
- paddingHorizontal: 16,
2042
- borderRadius: 8,
2043
- backgroundColor: _sharedUi.buoyColors.primary + "15",
2044
- borderWidth: 1,
2045
- borderColor: _sharedUi.buoyColors.primary + "40",
2046
- marginTop: 10
2047
- },
2048
- proRegisterDeviceBtnText: {
2049
- fontSize: 12,
2050
- fontWeight: "600",
2051
- color: _sharedUi.buoyColors.primary
2052
- },
2053
- proFreeDescription: {
2054
- fontSize: 11,
2055
- color: _sharedUi.buoyColors.textSecondary,
2056
- lineHeight: 16
2057
- },
2058
- proActiveDescription: {
2059
- fontSize: 11,
2060
- color: _sharedUi.buoyColors.textSecondary,
2061
- lineHeight: 16
2062
- },
2063
- proFeaturesList: {
2064
- gap: 8
2065
- },
2066
- proFeatureItem: {
2067
- flexDirection: "row",
2068
- alignItems: "center",
2069
- gap: 10
2070
- },
2071
- proFeatureText: {
2072
- fontSize: 12,
2073
- color: _sharedUi.buoyColors.text
2074
- },
2075
- proUpgradeBtn: {
2076
- flexDirection: "row",
2077
- alignItems: "center",
2078
- justifyContent: "center",
2079
- gap: 8,
2080
- backgroundColor: _sharedUi.buoyColors.primary,
2081
- borderRadius: 10,
2082
- paddingVertical: 14,
2083
- shadowColor: _sharedUi.buoyColors.primary,
2084
- shadowOffset: {
2085
- width: 0,
2086
- height: 0
2087
- },
2088
- shadowOpacity: 0.4,
2089
- shadowRadius: 8
2090
- },
2091
- proUpgradeBtnText: {
2092
- fontSize: 14,
2093
- fontWeight: "700",
2094
- color: _sharedUi.buoyColors.base
1102
+ // Footnote inline code
1103
+ footnoteMono: {
1104
+ fontFamily: "monospace",
1105
+ fontSize: _sharedUi.night.font.label,
1106
+ color: _sharedUi.night.color.textSecondary
2095
1107
  }
2096
1108
  });