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