@buoy-gg/core 7.0.36 → 7.0.39

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 (103) hide show
  1. package/README.md +57 -69
  2. package/bin/buoy.js +71 -20
  3. package/lib/commonjs/Buoy.js +3 -16
  4. package/lib/commonjs/floatingMenu/AppHost.js +1 -1
  5. package/lib/commonjs/floatingMenu/AppHost.web.js +1 -1
  6. package/lib/commonjs/floatingMenu/AppIcon.web.js +53 -0
  7. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +12 -5
  8. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +14 -34
  9. package/lib/commonjs/floatingMenu/FloatingDevTools.js +32 -48
  10. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +24 -4
  11. package/lib/commonjs/floatingMenu/FloatingMenu.js +1 -1
  12. package/lib/commonjs/floatingMenu/ToolHost.js +1 -0
  13. package/lib/commonjs/floatingMenu/autoExternalSync.js +3 -2
  14. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +43 -10
  15. package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +18 -5
  16. package/lib/commonjs/floatingMenu/dial/dialUsageStore.js +13 -17
  17. package/lib/commonjs/floatingMenu/dial/onboardingConstants.js +37 -65
  18. package/lib/commonjs/floatingMenu/dial/onboardingConstants.web.js +37 -29
  19. package/lib/commonjs/floatingMenu/floatingTools.js +23 -889
  20. package/lib/commonjs/floatingMenu/floatingTools.web.js +24 -514
  21. package/lib/commonjs/floatingMenu/optionalRequire.js +4 -0
  22. package/lib/commonjs/telemetry/installPing.js +7 -0
  23. package/lib/commonjs/version.js +1 -1
  24. package/lib/module/Buoy.js +3 -16
  25. package/lib/module/floatingMenu/AppHost.js +1 -1
  26. package/lib/module/floatingMenu/AppHost.web.js +1 -1
  27. package/lib/module/floatingMenu/AppIcon.web.js +47 -0
  28. package/lib/module/floatingMenu/DevToolsSettingsModal.js +13 -6
  29. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +15 -35
  30. package/lib/module/floatingMenu/FloatingDevTools.js +35 -49
  31. package/lib/module/floatingMenu/FloatingDevTools.web.js +24 -3
  32. package/lib/module/floatingMenu/FloatingMenu.js +1 -1
  33. package/lib/module/floatingMenu/ToolHost.js +1 -0
  34. package/lib/module/floatingMenu/autoExternalSync.js +3 -2
  35. package/lib/module/floatingMenu/dial/DialDevTools.js +43 -10
  36. package/lib/module/floatingMenu/dial/DialDevTools.web.js +18 -5
  37. package/lib/module/floatingMenu/dial/dialUsageStore.js +14 -18
  38. package/lib/module/floatingMenu/dial/onboardingConstants.js +1 -62
  39. package/lib/module/floatingMenu/dial/onboardingConstants.web.js +1 -25
  40. package/lib/module/floatingMenu/floatingTools.js +15 -890
  41. package/lib/module/floatingMenu/floatingTools.web.js +1 -517
  42. package/lib/module/floatingMenu/optionalRequire.js +4 -0
  43. package/lib/module/telemetry/installPing.js +7 -0
  44. package/lib/module/version.js +1 -1
  45. package/lib/typescript/commonjs/Buoy.d.ts +14 -3
  46. package/lib/typescript/commonjs/Buoy.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/floatingMenu/AppIcon.web.d.ts +14 -0
  48. package/lib/typescript/commonjs/floatingMenu/AppIcon.web.d.ts.map +1 -0
  49. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +4 -14
  52. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts +4 -19
  54. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/floatingMenu/ToolHost.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts +3 -4
  57. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts +3 -15
  61. package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts +1 -29
  63. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.web.d.ts +1 -12
  65. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.web.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts +6 -55
  67. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts +3 -53
  69. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts.map +1 -1
  70. package/lib/typescript/commonjs/floatingMenu/optionalRequire.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/telemetry/installPing.d.ts +8 -1
  72. package/lib/typescript/commonjs/telemetry/installPing.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/version.d.ts +1 -1
  74. package/lib/typescript/module/Buoy.d.ts +14 -3
  75. package/lib/typescript/module/Buoy.d.ts.map +1 -1
  76. package/lib/typescript/module/floatingMenu/AppIcon.web.d.ts +14 -0
  77. package/lib/typescript/module/floatingMenu/AppIcon.web.d.ts.map +1 -0
  78. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  79. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  80. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +4 -14
  81. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  82. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts +4 -19
  83. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  84. package/lib/typescript/module/floatingMenu/ToolHost.d.ts.map +1 -1
  85. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts +3 -4
  86. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
  87. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  88. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
  89. package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts +3 -15
  90. package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts.map +1 -1
  91. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts +1 -29
  92. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts.map +1 -1
  93. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.web.d.ts +1 -12
  94. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.web.d.ts.map +1 -1
  95. package/lib/typescript/module/floatingMenu/floatingTools.d.ts +6 -55
  96. package/lib/typescript/module/floatingMenu/floatingTools.d.ts.map +1 -1
  97. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts +3 -53
  98. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts.map +1 -1
  99. package/lib/typescript/module/floatingMenu/optionalRequire.d.ts.map +1 -1
  100. package/lib/typescript/module/telemetry/installPing.d.ts +8 -1
  101. package/lib/typescript/module/telemetry/installPing.d.ts.map +1 -1
  102. package/lib/typescript/module/version.d.ts +1 -1
  103. package/package.json +6 -6
@@ -4,10 +4,10 @@ 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, Info, Layers, ChevronRightIcon, ChevronDown, getStorageBackendType, persistentStorage, Trash2, CheckCircle2, LicenseEntryModal, BackgroundSwitcher, night, NightCard, NightRows, NightSectionLabel, NightFootnote, NightSwitch, NightButton, NightSegmented, NightBadge, NightSeparator, monoFont } from "@buoy-gg/shared-ui";
7
+ import { SentryIcon, WifiCircuitIcon, ImageOverlayIcon, RenderCountIcon, Info, Layers, ChevronRightIcon, ChevronDown, getStorageBackendType, persistentStorage, Trash2, CheckCircle2, LicenseEntryModal, BackgroundSwitcher, night, NightCard, NightRows, NightSectionLabel, NightFootnote, NightSwitch, NightButton, NightSegmented, NightBadge, NightSeparator, monoFont } 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
+ import { renderAppIcon } from "./AppIcon";
11
11
  import { JsModal, devToolsStorageKeys } from "@buoy-gg/shared-ui";
12
12
  import { useSafeAreaInsets } from "@buoy-gg/shared-ui";
13
13
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -374,10 +374,9 @@ export const DevToolsSettingsModal = ({
374
374
  size: 18
375
375
  });
376
376
  case "sentry":
377
- return /*#__PURE__*/_jsx(SentryBugIcon, {
377
+ return /*#__PURE__*/_jsx(SentryIcon, {
378
378
  size: 18,
379
- colorPreset: "red",
380
- noBackground: true
379
+ color: "#9945FF"
381
380
  });
382
381
  case "storage":
383
382
  return /*#__PURE__*/_jsx(StorageIcon, {
@@ -595,6 +594,7 @@ export const DevToolsSettingsModal = ({
595
594
  contentContainerStyle: styles.scrollContainer,
596
595
  children: [activeTab === "floating" && /*#__PURE__*/_jsx(_Fragment, {
597
596
  children: renderSection("Floating Tools", /*#__PURE__*/_jsx(NightCard, {
597
+ glass: true,
598
598
  children: /*#__PURE__*/_jsx(NightRows, {
599
599
  children: Object.entries(settings.floatingTools).map(([key, value]) => renderToolRow(key, value, false, () => toggleFloatingTool(key)))
600
600
  })
@@ -603,11 +603,13 @@ export const DevToolsSettingsModal = ({
603
603
  }))
604
604
  }), activeTab === "settings" && /*#__PURE__*/_jsxs(_Fragment, {
605
605
  children: [renderSection("Background", /*#__PURE__*/_jsx(NightCard, {
606
+ glass: true,
606
607
  padded: true,
607
608
  children: /*#__PURE__*/_jsx(BackgroundSwitcher, {
608
609
  embedded: true
609
610
  })
610
611
  })), renderSection("Storage", /*#__PURE__*/_jsxs(NightCard, {
612
+ glass: true,
611
613
  children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
612
614
  activeOpacity: 0.7,
613
615
  onPress: () => setIsStorageExpanded(!isStorageExpanded),
@@ -688,6 +690,7 @@ export const DevToolsSettingsModal = ({
688
690
  })]
689
691
  })]
690
692
  })), desktopSyncStatus && desktopSyncStatus.state !== "idle" && renderSection("Desktop Sync", /*#__PURE__*/_jsxs(NightCard, {
693
+ glass: true,
691
694
  children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
692
695
  activeOpacity: 0.7,
693
696
  onPress: () => toggleSettingExpanded("desktopSync"),
@@ -729,12 +732,14 @@ export const DevToolsSettingsModal = ({
729
732
  })]
730
733
  })]
731
734
  })), renderSection("Modal Behavior", /*#__PURE__*/_jsx(NightCard, {
735
+ glass: true,
732
736
  children: /*#__PURE__*/_jsxs(NightRows, {
733
737
  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
738
  })
735
739
  }))]
736
740
  }), activeTab === "pro" && /*#__PURE__*/_jsxs(_Fragment, {
737
741
  children: [renderSection("License", /*#__PURE__*/_jsx(NightCard, {
742
+ glass: true,
738
743
  children: /*#__PURE__*/_jsxs(View, {
739
744
  style: styles.settingRow,
740
745
  children: [/*#__PURE__*/_jsx(View, {
@@ -755,6 +760,7 @@ export const DevToolsSettingsModal = ({
755
760
  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
761
  })), !isPro && /*#__PURE__*/_jsxs(_Fragment, {
757
762
  children: [renderSection("Pro Features", /*#__PURE__*/_jsx(NightCard, {
763
+ glass: true,
758
764
  children: /*#__PURE__*/_jsx(NightRows, {
759
765
  children: ["Advanced Settings", "Priority Support"].map(feature => /*#__PURE__*/_jsxs(View, {
760
766
  style: styles.featureRow,
@@ -790,6 +796,7 @@ export const DevToolsSettingsModal = ({
790
796
  customContent: /*#__PURE__*/_jsx(View, {
791
797
  style: styles.headerRow,
792
798
  children: /*#__PURE__*/_jsx(NightSegmented, {
799
+ glass: true,
793
800
  tabs: [{
794
801
  key: "floating",
795
802
  label: "Floating"
@@ -1079,7 +1086,7 @@ const styles = StyleSheet.create({
1079
1086
  maxHeight: 150
1080
1087
  },
1081
1088
  storageKeyItem: {
1082
- backgroundColor: night.color.surfaceElevated,
1089
+ backgroundColor: night.color.glassElevated,
1083
1090
  borderRadius: night.radius.segment,
1084
1091
  paddingHorizontal: 10,
1085
1092
  paddingVertical: 7,
@@ -27,7 +27,7 @@ getToolLabel, getToolDescription,
27
27
  globalSettingsConfig
28
28
  // Types
29
29
  } from '@buoy-gg/floating-tools-react';
30
- import { QueryIcon, EnvIcon, StorageIcon, NetworkIcon, RoutesIcon, HighlighterIcon, ReduxIcon, EventsIcon, WifiIcon, ImageOverlayIcon, ZustandIcon, JotaiIcon } from '@buoy-gg/floating-tools-core';
30
+ import { QueryIcon, SentryIcon, EnvIcon, StorageIcon, NetworkIcon, RoutesIcon, HighlighterIcon, ReduxIcon, EventsIcon, WifiIcon, ImageOverlayIcon, ZustandIcon, JotaiIcon } from '@buoy-gg/floating-tools-core';
31
31
  // JsModal.web + storage keys via the consumer's shared-ui web shim — the
32
32
  // settings sheet is a real JsModal on web now, same as native. The
33
33
  // BackgroundSwitcher is the same RN component native mounts: it renders under
@@ -61,6 +61,9 @@ const night = {
61
61
  surfaceElevated: '#161618',
62
62
  border: '#1A1A1C',
63
63
  separator: 'rgba(255, 255, 255, 0.08)',
64
+ glass: 'rgba(5, 5, 5, 0.50)',
65
+ glassElevated: 'rgba(255, 255, 255, 0.06)',
66
+ glassBorder: 'rgba(255, 255, 255, 0.07)',
64
67
  fillTertiary: 'rgba(255, 255, 255, 0.12)',
65
68
  buttonSurface: 'rgba(8, 10, 14, 0.85)',
66
69
  button: '#1C1C1C',
@@ -147,28 +150,6 @@ const Sparkles = ({
147
150
  })]
148
151
  });
149
152
 
150
- /** Fallback for shared-ui SentryBugIcon (native sentry row icon, red). */
151
- const AlertTriangle = ({
152
- size = 24,
153
- color = 'currentColor'
154
- }) => /*#__PURE__*/_jsxs("svg", {
155
- width: size,
156
- height: size,
157
- viewBox: "0 0 24 24",
158
- fill: "none",
159
- stroke: color,
160
- strokeWidth: "2",
161
- strokeLinecap: "round",
162
- strokeLinejoin: "round",
163
- children: [/*#__PURE__*/_jsx("path", {
164
- d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"
165
- }), /*#__PURE__*/_jsx("path", {
166
- d: "M12 9v4"
167
- }), /*#__PURE__*/_jsx("path", {
168
- d: "M12 17h.01"
169
- })]
170
- });
171
-
172
153
  /** shared-ui Info icon (native default tool icon). */
173
154
  const Info = ({
174
155
  size = 24,
@@ -305,10 +286,9 @@ function ToolIcon({
305
286
  size: size
306
287
  });
307
288
  case 'sentry':
308
- // native: SentryBugIcon colorPreset="red" noBackground
309
- return /*#__PURE__*/_jsx(AlertTriangle, {
289
+ return /*#__PURE__*/_jsx(SentryIcon, {
310
290
  size: size,
311
- color: night.danger
291
+ color: "#9945FF"
312
292
  });
313
293
  case 'storage':
314
294
  // native passes bgColor=night surface for the BIF knock-out parts
@@ -444,11 +424,11 @@ function Footnote({
444
424
  });
445
425
  }
446
426
 
447
- /** native NightCard: #0C0C0C, radius 14, 1px #1A1A1C, clipped */
427
+ /** native NightCard glass: 50% black, radius 14, 1px white-alpha hairline, clipped */
448
428
  const cardStyle = {
449
- backgroundColor: night.surface,
429
+ backgroundColor: night.glass,
450
430
  borderRadius: 14,
451
- border: `1px solid ${night.border}`,
431
+ border: `1px solid ${night.glassBorder}`,
452
432
  overflow: 'hidden'
453
433
  };
454
434
 
@@ -793,8 +773,8 @@ export function SettingsModal({
793
773
  const displayTabs = settingsTabs.filter(tab => tab.key !== 'pro');
794
774
 
795
775
  // Segmented control rendered inside the JsModal header — native passes a
796
- // NightSegmented as header.customContent the same way. Track = night bg at
797
- // radius 10 / padding 3; active thumb = surfaceElevated at radius 8 with
776
+ // NightSegmented glass as header.customContent the same way. Track = glass
777
+ // at radius 10 / padding 3; active thumb = glassElevated at radius 8 with
798
778
  // the accent underline glow (the Everlights All/Day tabs).
799
779
  const tabHeader = /*#__PURE__*/_jsx("div", {
800
780
  style: {
@@ -809,9 +789,9 @@ export function SettingsModal({
809
789
  style: {
810
790
  flex: 1,
811
791
  display: 'flex',
812
- backgroundColor: night.bg,
792
+ backgroundColor: night.glass,
813
793
  borderRadius: 10,
814
- border: `1px solid ${night.border}`,
794
+ border: `1px solid ${night.glassBorder}`,
815
795
  padding: 3,
816
796
  boxSizing: 'border-box'
817
797
  },
@@ -829,7 +809,7 @@ export function SettingsModal({
829
809
  padding: '8px 0',
830
810
  borderRadius: 8,
831
811
  border: 'none',
832
- backgroundColor: isActive ? night.surfaceElevated : 'transparent',
812
+ backgroundColor: isActive ? night.glassElevated : 'transparent',
833
813
  color: isActive ? night.text : night.textSecondary,
834
814
  fontSize: 13,
835
815
  fontWeight: isActive ? 600 : 500,
@@ -1014,7 +994,7 @@ export function SettingsModal({
1014
994
  },
1015
995
  children: savedKeys.map(key => /*#__PURE__*/_jsx("div", {
1016
996
  style: {
1017
- backgroundColor: night.surfaceElevated,
997
+ backgroundColor: night.glassElevated,
1018
998
  borderRadius: 8,
1019
999
  padding: '7px 10px',
1020
1000
  marginBottom: 4
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- import React, { useMemo, useCallback, useEffect, useState } from "react";
3
+ import { FloatingToolHost } from "@buoy-gg/shared-ui";
4
+ import { AccountSetup } from "@buoy-gg/shared-ui";
5
+ import React, { useMemo, useCallback, useEffect } from "react";
4
6
  import { View, StyleSheet } from "react-native";
5
7
  import { AppHostProvider } from "./AppHost";
6
8
  import { FloatingMenu } from "./FloatingMenu.js";
@@ -9,10 +11,10 @@ import { ToolOpenerBridge } from "./ToolOpenerBridge.js";
9
11
  import { autoDiscoverPresets, autoDiscoverPresetsWithCustom } from "./autoDiscoverPresets.js";
10
12
  import { sendInstallPing } from "../telemetry/installPing.js";
11
13
  import { isTelemetryEnabled } from "../Buoy.js";
12
- import { renderAppIcon } from "./AppIcon.js";
14
+ import { renderAppIcon } from "./AppIcon";
13
15
  import { DevToolsVisibilityProvider } from "./DevToolsVisibilityContext.js";
14
16
  import { reportOptionalRequireError } from "./optionalRequire.js";
15
- import { HintsProvider, LicenseEntryModal, normalizeEnvironment } from "@buoy-gg/shared-ui";
17
+ import { HintsProvider, normalizeEnvironment } from "@buoy-gg/shared-ui";
16
18
  import { MinimizedToolsProvider } from "./MinimizedToolsContext.js";
17
19
  import { validateDialConfig } from "./defaultConfig.js";
18
20
  import { DefaultConfigProvider } from "./DefaultConfigContext.js";
@@ -120,7 +122,7 @@ export const FloatingDevTools = ({
120
122
  disableHints = false,
121
123
  defaultFloatingTools,
122
124
  defaultDialTools,
123
- requireLicense = false,
125
+ requireLicense: _requireLicense,
124
126
  licenseKey: licenseKeyProp,
125
127
  zustandStores,
126
128
  environment,
@@ -138,24 +140,27 @@ export const FloatingDevTools = ({
138
140
  isLicensed
139
141
  } = useProAccess();
140
142
 
141
- // Get full license state for requireLicense gating
143
+ // Account admission is mandatory; paid feature gates remain separate.
142
144
  const {
143
145
  licenseKey,
144
146
  isInitialized,
145
- tier
147
+ tier,
148
+ access
146
149
  } = useLicense();
150
+ const configuredMatches = licenseKeyProp === undefined || licenseKeyProp.trim().toUpperCase() === (licenseKey ?? "");
151
+ const accountAdmitted = configuredMatches && access.status === "admitted";
147
152
 
148
153
  // Anonymous install ping — dev-only, once a day, no project or tool data.
149
- // Deliberately waits for the license to resolve so the tier it reports is
150
- // the real one rather than the "anonymous" the manager starts at.
154
+ // Deliberately waits for admission to resolve so what it reports is the
155
+ // real outcome (the account's tier, or "locked") rather than the manager's
156
+ // starting state.
151
157
  useEffect(() => {
152
- if (!isInitialized) return;
158
+ if (!isInitialized || access.status === "initializing") return;
153
159
  void sendInstallPing({
154
- tier,
160
+ tier: access.status === "admitted" ? tier : "locked",
155
161
  enabled: isTelemetryEnabled()
156
162
  });
157
- }, [isInitialized, tier]);
158
- const [showLicenseModal, setShowLicenseModal] = useState(false);
163
+ }, [isInitialized, access.status, tier]);
159
164
 
160
165
  // Initialize license manager on mount, and set/clear license key when prop changes.
161
166
  // The `licenseKey` prop is the source of truth WHEN PROVIDED:
@@ -163,28 +168,12 @@ export const FloatingDevTools = ({
163
168
  // - "" (blank) → EXPLICITLY clear the cached license (key was removed)
164
169
  // - undefined → don't touch it (the app may set a key via Buoy.init())
165
170
  useEffect(() => {
166
- if (licenseKeyProp && licenseKeyProp.trim() !== "") {
167
- LicenseManager.initialize().then(() => LicenseManager.setLicenseKey(licenseKeyProp)).catch(() => {});
168
- } else if (licenseKeyProp === "") {
169
- // Explicit removal: wipe the 30-day cache so Pro actually turns off.
170
- LicenseManager.initialize().then(() => LicenseManager.clearLicense()).catch(() => {});
171
- } else {
172
- LicenseManager.initialize();
173
- }
171
+ void LicenseManager.configure(licenseKeyProp).catch(() => {});
174
172
  }, [licenseKeyProp]);
175
173
 
176
- // Show license modal if requireLicense is enabled and no valid license
177
- useEffect(() => {
178
- if (requireLicense && isInitialized && !licenseKey) {
179
- setShowLicenseModal(true);
180
- } else if (licenseKey) {
181
- setShowLicenseModal(false);
182
- }
183
- }, [requireLicense, isInitialized, licenseKey]);
184
-
185
174
  // Register zustand stores once on mount if provided
186
175
  useEffect(() => {
187
- if (!zustandStores) return;
176
+ if (!zustandStores || access.status === "locked") return;
188
177
  try {
189
178
  const {
190
179
  watchStores
@@ -193,7 +182,7 @@ export const FloatingDevTools = ({
193
182
  } catch (e) {
194
183
  reportOptionalRequireError("@buoy-gg/zustand", e);
195
184
  }
196
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
185
+ }, [zustandStores, access.status]);
197
186
 
198
187
  // Normalize dial tools configuration (truncates to max 6 with warning if needed)
199
188
  const normalizedDialTools = useMemo(() => {
@@ -450,17 +439,16 @@ export const FloatingDevTools = ({
450
439
 
451
440
  // Gate: only a PAID license works outside development.
452
441
  //
453
- // Both untiered surfaces below it — anonymous (no key) and free (a key on the
454
- // Free policy) — stay dev-only. `isLicensed` is safe to gate on because
455
- // `isPro` is a strict mirror of `tier === "pro"`, so a free key reports false
456
- // here even though it validates perfectly well.
442
+ // A free key (and, of course, no key at all) stays dev-only. `isLicensed` is
443
+ // safe to gate on because `isPro` is a strict mirror of `tier === "pro"`, so
444
+ // a free key reports false here even though it validates perfectly well.
457
445
  //
458
446
  // When Business on-device login lands, this becomes the boundary that a
459
447
  // logged-in QA/support seat crosses WITHOUT a key in the build — i.e.
460
448
  // `isLicensed || tierSource === "session"`. Nothing else should.
461
449
  const isDevelopment = typeof __DEV__ !== "undefined" && __DEV__;
462
450
  if (!isDevelopment && !isLicensed) {
463
- // Anonymous or free user in production - don't render DevTools.
451
+ // Free or unadmitted user in production - don't render DevTools.
464
452
  // NOTE for whoever relaxes this for the free tier: desktop sync does NOT
465
453
  // ride on this check. It re-tests `isLicensed` itself (externalSyncGate),
466
454
  // so opening this door does not open a broker socket. Keep it that way.
@@ -487,10 +475,16 @@ export const FloatingDevTools = ({
487
475
  requiredEnvVars: requiredEnvVars,
488
476
  customApps: syncApps
489
477
  });
478
+ if (!accountAdmitted) {
479
+ return /*#__PURE__*/_jsxs(_Fragment, {
480
+ children: [access.status === "initializing" && /*#__PURE__*/_jsxs(_Fragment, {
481
+ children: [ReduxAutoInstrument && /*#__PURE__*/_jsx(ReduxAutoInstrument, {}), ConsoleRoot && /*#__PURE__*/_jsx(ConsoleRoot, {}), RouteTracker && /*#__PURE__*/_jsx(RouteTracker, {}), ReactQueryTracker && /*#__PURE__*/_jsx(ReactQueryTracker, {})]
482
+ }), !headless && /*#__PURE__*/_jsx(AccountSetup, {})]
483
+ });
484
+ }
490
485
 
491
486
  // Headless (sync-only): tool adapters + route tracking + desktop sync, no
492
- // on-device UI. requireLicense is intentionally skipped — there's nothing
493
- // to gate. All hooks above already ran, so hook order is preserved, and
487
+ // on-device UI. Account admission above applies to this path too. All hooks above already ran, so hook order is preserved, and
494
488
  // AutoExternalSync still obeys `syncEnabled` (the broker must never be
495
489
  // contacted from a shipped build that didn't ask for it).
496
490
  if (headless) {
@@ -506,16 +500,6 @@ export const FloatingDevTools = ({
506
500
  });
507
501
  }
508
502
 
509
- // Gate: If requireLicense is enabled and no license, show only the license modal
510
- if (requireLicense && isInitialized && !licenseKey) {
511
- return /*#__PURE__*/_jsx(LicenseEntryModal, {
512
- visible: showLicenseModal,
513
- onClose: () => {} // Can't close without entering a license
514
- ,
515
- onSuccess: () => setShowLicenseModal(false)
516
- });
517
- }
518
-
519
503
  // Session blocking is handled inline by JsModal's SessionBlockedOverlay
520
504
  // No need to replace the entire UI - tools still render, but content is blocked
521
505
 
@@ -538,7 +522,9 @@ export const FloatingDevTools = ({
538
522
  ...props,
539
523
  apps: finalApps,
540
524
  environment: resolvedEnvironment
541
- }), props.hidden ? null : /*#__PURE__*/_jsx(AppOverlay, {}), /*#__PURE__*/_jsx(ToolOpenerBridge, {})]
525
+ }), props.hidden ? null : /*#__PURE__*/_jsxs(_Fragment, {
526
+ children: [/*#__PURE__*/_jsx(AppOverlay, {}), /*#__PURE__*/_jsx(FloatingToolHost, {})]
527
+ }), /*#__PURE__*/_jsx(ToolOpenerBridge, {})]
542
528
  })
543
529
  }), children, DebugBordersOverlay && /*#__PURE__*/_jsx(DebugBordersOverlay, {}), HighlightUpdatesOverlay && /*#__PURE__*/_jsx(HighlightUpdatesOverlay, {}), ImageOverlayOverlay && /*#__PURE__*/_jsx(ImageOverlayOverlay, {}), PerfMonitorOverlay && /*#__PURE__*/_jsx(PerfMonitorOverlay, {}), JsTopOverlay && /*#__PURE__*/_jsx(JsTopOverlay, {}), ImpersonateOverlay && /*#__PURE__*/_jsx(ImpersonateOverlay, {}), ScenariosOverlay && /*#__PURE__*/_jsx(ScenariosOverlay, {}), ReduxAutoInstrument && /*#__PURE__*/_jsx(ReduxAutoInstrument, {}), ConsoleRoot && /*#__PURE__*/_jsx(ConsoleRoot, {}), RouteTracker && /*#__PURE__*/_jsx(RouteTracker, {}), ReactQueryTracker && /*#__PURE__*/_jsx(ReactQueryTracker, {}), toolRegistry, syncEnabled && /*#__PURE__*/_jsx(AutoExternalSync, {
544
530
  options: externalSyncOptions(externalSync),
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
 
3
+ import { FloatingToolHost } from "@buoy-gg/shared-ui/floating";
4
+ import { LicenseManager, useLicense } from "@buoy-gg/license";
5
+ import { AccountSetup } from "@buoy-gg/shared-ui";
3
6
  /**
4
7
  * FloatingDevTools - Unified dev tools component for React DOM Web.
5
8
  *
@@ -84,7 +87,7 @@ function MenuLauncherIcon({
84
87
  // Main Component
85
88
  // =============================
86
89
 
87
- export function FloatingDevTools(props) {
90
+ function AdmittedFloatingDevTools(props) {
88
91
  // The tool host wraps everything so dial presses can open tool components
89
92
  // into the AppOverlay (web twin of the native AppHostProvider/AppOverlay).
90
93
  return /*#__PURE__*/_jsx(AppHostProvider, {
@@ -369,10 +372,28 @@ function FloatingDevToolsInner({
369
372
  onSettingsChange: _newSettings => {
370
373
  // Settings are already saved by the hook
371
374
  }
372
- }), /*#__PURE__*/_jsx(AppOverlay, {})]
375
+ }), /*#__PURE__*/_jsx(AppOverlay, {}), /*#__PURE__*/_jsx(FloatingToolHost, {})]
373
376
  });
374
377
  }
375
378
 
376
379
  // =============================
377
380
  // Re-exports for convenience
378
- // =============================
381
+ // =============================
382
+
383
+ export function FloatingDevTools(props) {
384
+ const {
385
+ access,
386
+ isPro,
387
+ licenseKey
388
+ } = useLicense();
389
+ useEffect(() => {
390
+ void LicenseManager.configure(props.licenseKey);
391
+ }, [props.licenseKey]);
392
+ const matches = props.licenseKey === undefined || props.licenseKey.trim().toUpperCase() === (licenseKey ?? "");
393
+ const dev = typeof __DEV__ !== "undefined" ? __DEV__ : process.env.NODE_ENV !== "production";
394
+ if (!dev && !isPro) return null;
395
+ if (!matches || access.status !== "admitted") return props.headless ? null : /*#__PURE__*/_jsx(AccountSetup, {});
396
+ return /*#__PURE__*/_jsx(AdmittedFloatingDevTools, {
397
+ ...props
398
+ });
399
+ }
@@ -4,7 +4,7 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
4
4
  import { TouchableOpacity, StyleSheet, View } from "react-native";
5
5
  import { absoluteFill } from "@buoy-gg/shared-ui";
6
6
  import { FloatingTools, UserStatus } from "./floatingTools";
7
- import { renderAppIcon } from "./AppIcon.js";
7
+ import { renderAppIcon } from "./AppIcon";
8
8
  import { DialDevTools } from "./dial/DialDevTools";
9
9
  import { EnvironmentIndicator, persistentStorage, useHintsDisabled, devToolsStorageKeys, buoyColors } from "@buoy-gg/shared-ui";
10
10
  import { useDevToolsSettings } from "./DevToolsSettingsModal";
@@ -189,6 +189,7 @@ export function ToolModalShell({
189
189
  subtitle: subtitle
190
190
  }), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
191
191
  children: [!isSearchActive && actions, searchable && !isSearchActive && /*#__PURE__*/_jsx(HeaderSearchButton, {
192
+ variant: "chip",
192
193
  testID: `tool-search-${app.id}`,
193
194
  onPress: () => setIsSearchActive(true)
194
195
  })]
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import { useEffect, useMemo, useRef } from "react";
21
21
  import { Platform } from "react-native";
22
+ import { LicenseManager } from "@buoy-gg/license";
22
23
  import { reportOptionalRequireError } from "./optionalRequire.js";
23
24
  import { appSyncAdapter } from "./sync/appSyncAdapter.js";
24
25
  import { useDeviceIdentity } from "./deviceIdentity.js";
@@ -285,11 +286,11 @@ export function AutoExternalSync({
285
286
  useEffect(() => {
286
287
  if (!socket) return;
287
288
  const emitLicense = () => {
289
+ if (!LicenseManager.hasAccountAccess() || !socket.connected) return;
288
290
  socket.emit("device-license", {
289
291
  isPro: licenseRef.current.isPro ? "1" : "0",
290
292
  // Brokers older than the tier split ignore this; newer ones prefer it.
291
- tier: licenseRef.current.tier ?? (licenseRef.current.isPro ? "pro" : "anonymous"),
292
- licenseKey: licenseRef.current.licenseKey ?? ""
293
+ tier: licenseRef.current.tier ?? (licenseRef.current.isPro ? "pro" : "free")
293
294
  });
294
295
  };
295
296
  if (socket.connected) emitLicense();
@@ -11,7 +11,7 @@ import { getRankedToolIds, isDialUsageLoaded, loadDialUsage, recordToolUsage } f
11
11
  import { persistentStorage, useHintsDisabled, devToolsStorageKeys, buoyColors, ToolBackground, useToolBackground } from "@buoy-gg/shared-ui";
12
12
  import { DevToolsSettingsModal, useDevToolsSettings } from "../DevToolsSettingsModal";
13
13
  import { useProAccess } from "@buoy-gg/license";
14
- import { renderAppIcon } from "../AppIcon.js";
14
+ import { renderAppIcon } from "../AppIcon";
15
15
  import { useAppHost } from "../AppHost";
16
16
  import { OnboardingTooltip } from "./OnboardingTooltip";
17
17
  import { getDialLayout, MAX_DIAL_SLOTS, DIAL_BUTTON_SIZE, dialAnimationConfig, dialColors } from "@buoy-gg/floating-tools-core";
@@ -21,6 +21,14 @@ import { getDialLayout, MAX_DIAL_SLOTS, DIAL_BUTTON_SIZE, dialAnimationConfig, d
21
21
  // snapshot goes stale when the window resizes after load (web/desktop).
22
22
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
23
23
  const BUTTON_SIZE = DIAL_BUTTON_SIZE;
24
+ /** The dial face is a circle, so its centre is the middle of the measured square. */
25
+ const DIAL_BACKGROUND_FOCUS = {
26
+ x: 0.5,
27
+ y: 0.5
28
+ };
29
+ // Radius kept clear of grid spokes around the glass hub (its outer disc is
30
+ // 0.75 × BUTTON_SIZE, plus a breath).
31
+ const HUB_CLEARANCE = BUTTON_SIZE * 0.75 + 6;
24
32
 
25
33
  /**
26
34
  * The centre button's idle pulse, off by design.
@@ -97,6 +105,11 @@ export const DialDevTools = ({
97
105
  },
98
106
  gridLine: {
99
107
  width: circleSize
108
+ },
109
+ // Each spoke is two segments that stop short of the glass hub, so the
110
+ // six lines never meet in the middle where the background shows through.
111
+ spoke: {
112
+ width: circleSize / 2 - HUB_CLEARANCE
100
113
  }
101
114
  }), [circleSize]);
102
115
 
@@ -222,7 +235,7 @@ export const DialDevTools = ({
222
235
  iconComponent: typeof app.icon === "function" ? app.icon : undefined,
223
236
  color: app.color ?? buoyColors.primary,
224
237
  onPress: () => {
225
- // Record usage so frequently/recently used tools rank toward page 1.
238
+ // Put this tool first the next time the dial opens.
226
239
  void recordToolUsage(app.id);
227
240
 
228
241
  // Call the app's onPress callback if provided, passing actions for
@@ -586,16 +599,23 @@ export const DialDevTools = ({
586
599
  style: styles.gridPattern,
587
600
  children: Array.from({
588
601
  length: 6
589
- }).map((_, i) => /*#__PURE__*/_jsx(View, {
602
+ }).map((_, i) => /*#__PURE__*/_jsxs(View, {
590
603
  style: [styles.gridLine, sizeStyles.gridLine, {
591
604
  transform: [{
592
605
  rotate: `${i * 60}deg`
593
606
  }]
594
- }]
607
+ }],
608
+ children: [/*#__PURE__*/_jsx(View, {
609
+ style: [styles.spoke, sizeStyles.spoke, styles.spokeLeft]
610
+ }), /*#__PURE__*/_jsx(View, {
611
+ style: [styles.spoke, sizeStyles.spoke, styles.spokeRight]
612
+ })]
595
613
  }, i))
596
614
  }), backgroundId === "hyperspace" ? /*#__PURE__*/_jsx(DialHyperspace, {
597
615
  size: circleSize
598
- }) : /*#__PURE__*/_jsx(ToolBackground, {})]
616
+ }) : /*#__PURE__*/_jsx(ToolBackground, {
617
+ focus: DIAL_BACKGROUND_FOCUS
618
+ })]
599
619
  }), allDialIcons.filter(({
600
620
  page
601
621
  }) => page === safePage).map(({
@@ -753,9 +773,20 @@ const styles = StyleSheet.create({
753
773
  },
754
774
  gridLine: {
755
775
  position: "absolute",
776
+ height: 1
777
+ },
778
+ spoke: {
779
+ position: "absolute",
780
+ top: 0,
756
781
  height: 1,
757
782
  backgroundColor: dialColors.dialGridLine
758
783
  },
784
+ spokeLeft: {
785
+ left: 0
786
+ },
787
+ spokeRight: {
788
+ right: 0
789
+ },
759
790
  buttonContainer: {
760
791
  zIndex: 1,
761
792
  backgroundColor: "transparent",
@@ -766,6 +797,10 @@ const styles = StyleSheet.create({
766
797
  height: BUTTON_SIZE * 1.5,
767
798
  borderRadius: BUTTON_SIZE
768
799
  },
800
+ // The hub is glass: no rings, no opaque fill. The outer disc is a barely-
801
+ // there shade and the inner button a soft puck, so the tool background runs
802
+ // through the middle of the dial while BUOY / PRO stay legible. The spokes
803
+ // stop at HUB_CLEARANCE so nothing draws under it.
769
804
  buttonGradient: {
770
805
  width: "100%",
771
806
  height: "100%",
@@ -773,7 +808,7 @@ const styles = StyleSheet.create({
773
808
  alignItems: "center",
774
809
  justifyContent: "center",
775
810
  padding: 4,
776
- backgroundColor: dialColors.dialBackground,
811
+ backgroundColor: dialColors.hubHalo,
777
812
  position: "relative",
778
813
  overflow: "hidden"
779
814
  },
@@ -800,14 +835,12 @@ const styles = StyleSheet.create({
800
835
  borderRadius: BUTTON_SIZE
801
836
  },
802
837
  buttonBorder: {
803
- backgroundColor: dialColors.dialGridLine,
838
+ backgroundColor: dialColors.hubFill,
804
839
  alignItems: "center",
805
840
  justifyContent: "center",
806
841
  width: BUTTON_SIZE * 1.2,
807
842
  height: BUTTON_SIZE * 1.2,
808
- borderRadius: BUTTON_SIZE * 0.6,
809
- borderWidth: 2,
810
- borderColor: dialColors.dialBorder
843
+ borderRadius: BUTTON_SIZE * 0.6
811
844
  },
812
845
  button: {
813
846
  width: BUTTON_SIZE,
@@ -548,6 +548,8 @@ export function DialMenu({
548
548
  // Computed values
549
549
  const buttonContainerSize = layout.buttonSize * dialStyles.centerButton.containerRatio;
550
550
  const buttonBorderSize = layout.buttonSize * dialStyles.centerButton.borderRatio;
551
+ // Grid spokes stop this far from the centre: the hub's outer disc plus a breath.
552
+ const hubClearance = layout.buttonSize * dialStyles.centerButton.containerRatio / 2 + 6;
551
553
  const isAnimating = entranceComplete && !isExiting;
552
554
  return /*#__PURE__*/_jsxs("div", {
553
555
  role: "dialog",
@@ -623,10 +625,19 @@ export function DialMenu({
623
625
  left: 0,
624
626
  right: 0,
625
627
  height: dialStyles.grid.lineHeight,
626
- backgroundColor: dialColors.dialGridLine,
627
628
  transform: `rotate(${deg}deg)`,
628
629
  transformOrigin: 'center'
629
- }
630
+ },
631
+ children: ['left', 'right'].map(side => /*#__PURE__*/_jsx("div", {
632
+ style: {
633
+ position: 'absolute',
634
+ top: 0,
635
+ [side]: 0,
636
+ width: `calc(50% - ${hubClearance}px)`,
637
+ height: dialStyles.grid.lineHeight,
638
+ backgroundColor: dialColors.dialGridLine
639
+ }
640
+ }, side))
630
641
  }, deg)), paddedIcons.map((icon, index) => /*#__PURE__*/_jsx(DialIconItem, {
631
642
  icon: icon,
632
643
  index: index,
@@ -646,7 +657,10 @@ export function DialMenu({
646
657
  display: 'flex',
647
658
  alignItems: 'center',
648
659
  justifyContent: 'center',
649
- backgroundColor: dialColors.dialBackground,
660
+ // Glass hub — see the native twin: a barely-there shade on the outer
661
+ // disc, a soft puck inside, no rings, so the tool background runs
662
+ // through the middle of the dial.
663
+ backgroundColor: dialColors.hubHalo,
650
664
  borderRadius: layout.buttonSize,
651
665
  overflow: 'hidden',
652
666
  transform: `scale(${centerButtonScale * centerPulseScale})`,
@@ -685,8 +699,7 @@ export function DialMenu({
685
699
  width: buttonBorderSize,
686
700
  height: buttonBorderSize,
687
701
  borderRadius: layout.buttonSize * dialStyles.centerButton.borderRadiusRatio,
688
- backgroundColor: dialColors.dialGridLine,
689
- border: `${dialStyles.centerButton.borderWidth}px solid ${dialColors.dialBorder}`,
702
+ backgroundColor: dialColors.hubFill,
690
703
  display: 'flex',
691
704
  alignItems: 'center',
692
705
  justifyContent: 'center'