@buoy-gg/core 7.0.16 → 7.0.18

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 (77) hide show
  1. package/lib/commonjs/floatingMenu/AppHost.js +23 -3
  2. package/lib/commonjs/floatingMenu/AppHost.web.js +6 -1
  3. package/lib/commonjs/floatingMenu/AppIcon.js +91 -0
  4. package/lib/commonjs/floatingMenu/FloatingDevTools.js +15 -9
  5. package/lib/commonjs/floatingMenu/FloatingMenu.js +14 -7
  6. package/lib/commonjs/floatingMenu/ToolHost.js +274 -0
  7. package/lib/commonjs/floatingMenu/allToolsRegistry.js +7 -0
  8. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +16 -0
  9. package/lib/commonjs/floatingMenu/autoExternalSync.js +21 -2
  10. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +15 -7
  11. package/lib/commonjs/index.js +27 -0
  12. package/lib/commonjs/toolkit.js +584 -0
  13. package/lib/commonjs/toolkitComponents.js +241 -0
  14. package/lib/module/floatingMenu/AppHost.js +23 -3
  15. package/lib/module/floatingMenu/AppHost.web.js +6 -1
  16. package/lib/module/floatingMenu/AppIcon.js +84 -0
  17. package/lib/module/floatingMenu/FloatingDevTools.js +15 -9
  18. package/lib/module/floatingMenu/FloatingMenu.js +14 -7
  19. package/lib/module/floatingMenu/ToolHost.js +266 -0
  20. package/lib/module/floatingMenu/allToolsRegistry.js +8 -1
  21. package/lib/module/floatingMenu/autoDiscoverPresets.js +16 -0
  22. package/lib/module/floatingMenu/autoExternalSync.js +21 -2
  23. package/lib/module/floatingMenu/dial/DialDevTools.js +15 -7
  24. package/lib/module/index.js +6 -0
  25. package/lib/module/toolkit.js +62 -0
  26. package/lib/module/toolkitComponents.js +230 -0
  27. package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/floatingMenu/AppHost.web.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts +3 -0
  30. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/floatingMenu/AppIcon.d.ts +30 -0
  32. package/lib/typescript/commonjs/floatingMenu/AppIcon.d.ts.map +1 -0
  33. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/floatingMenu/ToolHost.d.ts +77 -0
  36. package/lib/typescript/commonjs/floatingMenu/ToolHost.d.ts.map +1 -0
  37. package/lib/typescript/commonjs/floatingMenu/allToolsRegistry.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts +8 -1
  40. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +1 -1
  42. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/floatingMenu/types.d.ts +62 -2
  45. package/lib/typescript/commonjs/floatingMenu/types.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/index.d.ts +2 -0
  47. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/toolkit.d.ts +31 -0
  49. package/lib/typescript/commonjs/toolkit.d.ts.map +1 -0
  50. package/lib/typescript/commonjs/toolkitComponents.d.ts +61 -0
  51. package/lib/typescript/commonjs/toolkitComponents.d.ts.map +1 -0
  52. package/lib/typescript/module/floatingMenu/AppHost.d.ts.map +1 -1
  53. package/lib/typescript/module/floatingMenu/AppHost.web.d.ts.map +1 -1
  54. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts +3 -0
  55. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts.map +1 -1
  56. package/lib/typescript/module/floatingMenu/AppIcon.d.ts +30 -0
  57. package/lib/typescript/module/floatingMenu/AppIcon.d.ts.map +1 -0
  58. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  59. package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -1
  60. package/lib/typescript/module/floatingMenu/ToolHost.d.ts +77 -0
  61. package/lib/typescript/module/floatingMenu/ToolHost.d.ts.map +1 -0
  62. package/lib/typescript/module/floatingMenu/allToolsRegistry.d.ts.map +1 -1
  63. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  64. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts +8 -1
  65. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
  66. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +1 -1
  67. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -1
  68. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  69. package/lib/typescript/module/floatingMenu/types.d.ts +62 -2
  70. package/lib/typescript/module/floatingMenu/types.d.ts.map +1 -1
  71. package/lib/typescript/module/index.d.ts +2 -0
  72. package/lib/typescript/module/index.d.ts.map +1 -1
  73. package/lib/typescript/module/toolkit.d.ts +31 -0
  74. package/lib/typescript/module/toolkit.d.ts.map +1 -0
  75. package/lib/typescript/module/toolkitComponents.d.ts +61 -0
  76. package/lib/typescript/module/toolkitComponents.d.ts.map +1 -0
  77. package/package.json +19 -5
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Badge = Badge;
7
+ exports.DiffViewer = DiffViewer;
8
+ exports.InfoRow = InfoRow;
9
+ exports.StatusDot = StatusDot;
10
+ exports.methodColor = methodColor;
11
+ exports.statusColor = statusColor;
12
+ exports.typeColor = typeColor;
13
+ var _react = _interopRequireDefault(require("react"));
14
+ var _reactNative = require("react-native");
15
+ var _sharedUi = require("@buoy-gg/shared-ui");
16
+ var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
17
+ var _jsxRuntime = require("react/jsx-runtime");
18
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
19
+ /**
20
+ * Facade primitives for the public custom-tools toolkit (see toolkit.ts).
21
+ *
22
+ * shared-ui grew several overlapping treatments of the same jobs (seven badge
23
+ * variants, four copy buttons, two diff viewers...). The public surface merges
24
+ * each job into ONE component with variants, implemented here on toolTheme —
25
+ * shared-ui's internals stay unchanged for first-party tools.
26
+ */
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Semantic color helpers — pair with <Badge>/<StatusDot> instead of exporting
30
+ // a preset component per domain.
31
+ // ---------------------------------------------------------------------------
32
+ const STATUS_COLORS = {
33
+ success: _sharedUi.toolTheme.colors.success,
34
+ active: _sharedUi.toolTheme.colors.success,
35
+ connected: _sharedUi.toolTheme.colors.success,
36
+ error: _sharedUi.toolTheme.colors.error,
37
+ failed: _sharedUi.toolTheme.colors.error,
38
+ warning: _sharedUi.toolTheme.colors.warning,
39
+ stale: _sharedUi.toolTheme.colors.warning,
40
+ pending: _sharedUi.toolTheme.colors.info,
41
+ loading: _sharedUi.toolTheme.colors.info,
42
+ fetching: _sharedUi.toolTheme.colors.info,
43
+ info: _sharedUi.toolTheme.colors.info,
44
+ inactive: _sharedUi.toolTheme.colors.textMuted,
45
+ idle: _sharedUi.toolTheme.colors.textMuted
46
+ };
47
+
48
+ /** Map a status word ("success", "pending", "stale"...) to a semantic color. */
49
+ function statusColor(status) {
50
+ return STATUS_COLORS[status.toLowerCase()] ?? _sharedUi.toolTheme.colors.textMuted;
51
+ }
52
+ const METHOD_COLORS = {
53
+ GET: _sharedUi.toolTheme.colors.info,
54
+ POST: _sharedUi.toolTheme.colors.success,
55
+ PUT: _sharedUi.toolTheme.colors.warning,
56
+ PATCH: _sharedUi.toolTheme.colors.warning,
57
+ DELETE: _sharedUi.toolTheme.colors.error
58
+ };
59
+
60
+ /** Map an HTTP method to its conventional color. */
61
+ function methodColor(method) {
62
+ return METHOD_COLORS[method.toUpperCase()] ?? _sharedUi.toolTheme.colors.textSecondary;
63
+ }
64
+ const TYPE_COLORS = {
65
+ string: _sharedUi.toolTheme.colors.success,
66
+ number: _sharedUi.toolTheme.colors.info,
67
+ boolean: _sharedUi.toolTheme.colors.warning,
68
+ object: _sharedUi.toolTheme.colors.accent,
69
+ array: _sharedUi.toolTheme.colors.accent,
70
+ function: _sharedUi.toolTheme.colors.error,
71
+ null: _sharedUi.toolTheme.colors.textMuted,
72
+ undefined: _sharedUi.toolTheme.colors.textMuted
73
+ };
74
+
75
+ /** Map a JS value-type name to its display color. */
76
+ function typeColor(type) {
77
+ return TYPE_COLORS[type.toLowerCase()] ?? _sharedUi.toolTheme.colors.textSecondary;
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Badge — the one chip. Color it with statusColor()/methodColor()/typeColor()
82
+ // (or any hex); shape/typography are props, not sibling components.
83
+ // ---------------------------------------------------------------------------
84
+
85
+ function Badge({
86
+ children,
87
+ color = _sharedUi.toolTheme.colors.textSecondary,
88
+ icon,
89
+ size = "small",
90
+ pill = false,
91
+ mono = false,
92
+ style
93
+ }) {
94
+ const isText = typeof children === "string" || typeof children === "number";
95
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
96
+ style: [badgeStyles.base, size === "medium" && badgeStyles.medium, pill && badgeStyles.pill, {
97
+ backgroundColor: color + "1A",
98
+ borderColor: color + "40"
99
+ }, style],
100
+ children: [icon, isText ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
101
+ style: [badgeStyles.text, size === "medium" && badgeStyles.textMedium, mono && badgeStyles.textMono, {
102
+ color
103
+ }],
104
+ numberOfLines: 1,
105
+ children: children
106
+ }) : children]
107
+ });
108
+ }
109
+ const badgeStyles = _reactNative.StyleSheet.create({
110
+ base: {
111
+ flexDirection: "row",
112
+ alignItems: "center",
113
+ alignSelf: "flex-start",
114
+ gap: 4,
115
+ borderWidth: 1,
116
+ borderRadius: _sharedUi.toolTheme.radius.sm,
117
+ paddingHorizontal: 6,
118
+ paddingVertical: 2
119
+ },
120
+ medium: {
121
+ paddingHorizontal: 8,
122
+ paddingVertical: 3
123
+ },
124
+ pill: {
125
+ borderRadius: _sharedUi.toolTheme.radius.pill,
126
+ minWidth: 20,
127
+ justifyContent: "center"
128
+ },
129
+ text: {
130
+ fontSize: 10,
131
+ fontWeight: "600",
132
+ letterSpacing: 0.3
133
+ },
134
+ textMedium: {
135
+ fontSize: 12
136
+ },
137
+ textMono: {
138
+ fontFamily: _sharedUi.toolTheme.text.mono.fontFamily
139
+ }
140
+ });
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // StatusDot — the bare status circle (list rows, connection indicators).
144
+ // ---------------------------------------------------------------------------
145
+
146
+ function StatusDot({
147
+ status,
148
+ color,
149
+ size = 8,
150
+ style
151
+ }) {
152
+ const dotColor = color ?? (status ? statusColor(status) : _sharedUi.toolTheme.colors.textMuted);
153
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
154
+ style: [{
155
+ width: size,
156
+ height: size,
157
+ borderRadius: size / 2,
158
+ backgroundColor: dotColor
159
+ }, style]
160
+ });
161
+ }
162
+
163
+ // ---------------------------------------------------------------------------
164
+ // InfoRow — the label/value line of every detail screen, with optional copy.
165
+ // ---------------------------------------------------------------------------
166
+
167
+ function InfoRow({
168
+ label,
169
+ value,
170
+ copyable = false,
171
+ style
172
+ }) {
173
+ const isText = typeof value === "string" || typeof value === "number";
174
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
175
+ style: [infoRowStyles.row, style],
176
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
177
+ style: infoRowStyles.label,
178
+ numberOfLines: 1,
179
+ children: label
180
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
181
+ style: infoRowStyles.value,
182
+ children: isText ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
183
+ style: infoRowStyles.valueText,
184
+ numberOfLines: 2,
185
+ children: String(value)
186
+ }) : value
187
+ }), copyable && value != null && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CopyButton, {
188
+ value: value,
189
+ size: 12
190
+ })]
191
+ });
192
+ }
193
+ const infoRowStyles = _reactNative.StyleSheet.create({
194
+ row: {
195
+ flexDirection: "row",
196
+ alignItems: "center",
197
+ gap: _sharedUi.toolTheme.spacing.sm,
198
+ paddingVertical: 3
199
+ },
200
+ label: {
201
+ ..._sharedUi.toolTheme.text.label,
202
+ color: _sharedUi.toolTheme.colors.textMuted,
203
+ minWidth: 72
204
+ },
205
+ value: {
206
+ flex: 1,
207
+ flexDirection: "row",
208
+ alignItems: "center"
209
+ },
210
+ valueText: {
211
+ ..._sharedUi.toolTheme.text.mono,
212
+ color: _sharedUi.toolTheme.colors.text
213
+ }
214
+ });
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // DiffViewer — one before/after component; the rendering strategy is a mode.
218
+ // ---------------------------------------------------------------------------
219
+
220
+ function DiffViewer({
221
+ oldValue,
222
+ newValue,
223
+ mode = "tree",
224
+ showUnchanged = true,
225
+ scrollEnabled = false,
226
+ height = 220
227
+ }) {
228
+ if (mode === "text") {
229
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.SplitDiffViewer, {
230
+ oldValue: oldValue,
231
+ newValue: newValue,
232
+ height: height
233
+ });
234
+ }
235
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.TreeDiffViewer, {
236
+ oldValue: oldValue,
237
+ newValue: newValue,
238
+ showUnchanged: showUnchanged,
239
+ scrollEnabled: scrollEnabled
240
+ });
241
+ }
@@ -4,7 +4,9 @@ import { persistentStorage, setExpandableWindowControls } from "@buoy-gg/shared-
4
4
  import React, { createContext, useCallback, useContext, useMemo, useState, useEffect, useRef } from "react";
5
5
  import { BackHandler, Modal, StyleSheet, View } from "react-native";
6
6
  import { resolveOpenAppsState } from "./AppHostLogic.js";
7
+ import { renderAppIcon } from "./AppIcon.js";
7
8
  import { useMinimizedTools } from "./MinimizedToolsContext.js";
9
+ import { ToolModalShell } from "./ToolHost.js";
8
10
  import { useDevToolsSettings } from "./DevToolsSettingsModal";
9
11
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
10
12
  const AppHostContext = /*#__PURE__*/createContext(null);
@@ -63,10 +65,11 @@ export const AppHostProvider = ({
63
65
  }) => {
64
66
  const appDef = installedAppsRef.current.find(app => app.id === appId);
65
67
  if (appDef) {
66
- const resolvedIcon = typeof appDef.icon === "function" ? appDef.icon({
68
+ const resolvedIcon = renderAppIcon(appDef.icon, {
67
69
  slot: "dial",
68
- size: 20
69
- }) : appDef.icon;
70
+ size: 20,
71
+ color: appDef.color
72
+ });
70
73
  open({
71
74
  id: appDef.id,
72
75
  title: appDef.name,
@@ -76,6 +79,7 @@ export const AppHostProvider = ({
76
79
  singleton: appDef.singleton,
77
80
  icon: resolvedIcon,
78
81
  color: appDef.color,
82
+ modal: appDef.modal,
79
83
  minimized: minimized // Preserve minimized state
80
84
  });
81
85
  }
@@ -250,6 +254,22 @@ const AppRenderer = ({
250
254
  enableSharedModalDimensions: true
251
255
  } : {})
252
256
  };
257
+
258
+ // Declarative chrome: the component is a plain body — wrap it in the shared
259
+ // modal shell (JsModal + managed header + search + toasts).
260
+ if (app.modal && (app.launchMode === "self-modal" || !app.launchMode)) {
261
+ return /*#__PURE__*/_jsx(ToolModalShell, {
262
+ app: app,
263
+ config: app.modal,
264
+ visible: isVisible,
265
+ zIndex: zIndex,
266
+ onClose: onClose,
267
+ onMinimize: onMinimize,
268
+ initialModalState: app.restoreState,
269
+ enableSharedModalDimensions: globalEnableSharedModalDimensions === true,
270
+ bodyProps: mergedProps
271
+ });
272
+ }
253
273
  if (app.launchMode === "self-modal") {
254
274
  return /*#__PURE__*/_jsx(Comp, {
255
275
  ...mergedProps,
@@ -25,6 +25,7 @@
25
25
  import { persistentStorage } from "@buoy-gg/shared-ui";
26
26
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
27
27
  import { resolveOpenAppsState } from "./AppHostLogic.js";
28
+ import { renderAppIcon } from "./AppIcon.js";
28
29
  import { getBubbleAnchorPosition } from "./floatingTools.web.js";
29
30
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
30
31
  const AppHostContext = /*#__PURE__*/createContext(null);
@@ -81,7 +82,11 @@ export const AppHostProvider = ({
81
82
  }) => {
82
83
  const appDef = installedAppsRef.current.find(app => app.id === appId);
83
84
  if (appDef && appDef.component) {
84
- const resolvedIcon = typeof appDef.icon === "function" ? appDef.icon(20) : appDef.icon;
85
+ const resolvedIcon = renderAppIcon(appDef.icon, {
86
+ slot: "dial",
87
+ size: 20,
88
+ color: appDef.color
89
+ });
85
90
  open({
86
91
  id: appDef.id,
87
92
  title: appDef.name,
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Icon resolution for `InstalledApp.icon`.
5
+ *
6
+ * Custom tools may provide an icon three ways:
7
+ * - a component `({ slot, size }) => node` (dynamic, hook-capable)
8
+ * - a pre-built ReactNode
9
+ * - a PLAIN STRING — "AUTH", "KIT", "🗑️" — rendered as an auto-fitting text
10
+ * icon so simple tools never need an icon library.
11
+ *
12
+ * Every menu surface (dial, row, minimize stack, restore path) resolves icons
13
+ * through `renderAppIcon` so all three forms behave identically.
14
+ */
15
+ import React from "react";
16
+ import { Text, View } from "react-native";
17
+ import { toolTheme } from "@buoy-gg/shared-ui";
18
+ import { jsx as _jsx } from "react/jsx-runtime";
19
+ /** Visible glyphs, not UTF-16 units — "🗑️" is one glyph, not three. */
20
+ function glyphCount(label) {
21
+ // Strip variation selectors + zero-width joiners before counting.
22
+ const stripped = label.replace(/[\uFE00-\uFE0F\u200D]/g, "");
23
+ return Math.max(Array.from(stripped).length, 1);
24
+ }
25
+ export function AppTextIcon({
26
+ label,
27
+ size,
28
+ color = toolTheme.colors.text
29
+ }) {
30
+ const chars = glyphCount(label);
31
+ // Short labels stay square; longer ones ("AUTH") get a wider box — the
32
+ // dial/row layouts have breathing room and the label reads better than a
33
+ // squashed square would. The computed font size is what fits the box on
34
+ // web; adjustsFontSizeToFit covers native font-metric drift.
35
+ const maxWidth = chars <= 2 ? size : size * 1.6;
36
+ const fontSize = Math.max(7, Math.min(size * (chars === 1 ? 0.7 : 0.62), maxWidth / (0.62 * chars)));
37
+ return /*#__PURE__*/_jsx(View, {
38
+ style: {
39
+ minWidth: size,
40
+ maxWidth,
41
+ height: size,
42
+ alignItems: "center",
43
+ justifyContent: "center"
44
+ },
45
+ children: /*#__PURE__*/_jsx(Text, {
46
+ numberOfLines: 1,
47
+ adjustsFontSizeToFit: true,
48
+ minimumFontScale: 0.5,
49
+ style: {
50
+ color,
51
+ fontSize,
52
+ fontWeight: "800",
53
+ letterSpacing: 0.5,
54
+ textAlign: "center"
55
+ },
56
+ children: label
57
+ })
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Resolve any `InstalledApp.icon` form to a renderable node. Function icons
63
+ * become elements (createElement — hooks keep working); strings become
64
+ * auto-fitting text icons; nodes pass through.
65
+ */
66
+ export function renderAppIcon(icon, ctx) {
67
+ if (icon == null) return icon;
68
+ if (typeof icon === "function") {
69
+ return /*#__PURE__*/React.createElement(icon, {
70
+ slot: ctx.slot,
71
+ size: ctx.size,
72
+ state: ctx.state,
73
+ actions: ctx.actions
74
+ });
75
+ }
76
+ if (typeof icon === "string" || typeof icon === "number") {
77
+ return /*#__PURE__*/_jsx(AppTextIcon, {
78
+ label: String(icon),
79
+ size: ctx.size,
80
+ color: ctx.color
81
+ });
82
+ }
83
+ return icon;
84
+ }
@@ -6,6 +6,7 @@ import { AppHostProvider } from "./AppHost";
6
6
  import { FloatingMenu } from "./FloatingMenu.js";
7
7
  import { AppOverlay } from "./AppHost";
8
8
  import { autoDiscoverPresets, autoDiscoverPresetsWithCustom } from "./autoDiscoverPresets.js";
9
+ import { renderAppIcon } from "./AppIcon.js";
9
10
  import { DevToolsVisibilityProvider } from "./DevToolsVisibilityContext.js";
10
11
  import { reportOptionalRequireError } from "./optionalRequire.js";
11
12
  import { HintsProvider, LicenseEntryModal, normalizeEnvironment } from "@buoy-gg/shared-ui";
@@ -226,6 +227,11 @@ export const FloatingDevTools = ({
226
227
  // identity (and re-ran discovery) on every parent render.
227
228
  const finalApps = useMemo(() => userApps ? autoDiscoverPresetsWithCustom(userApps) : autoDiscoverPresets(), [userApps]);
228
229
 
230
+ // Custom tools that declared a sync adapter — registered with the broker as
231
+ // `custom:<id>` by AutoExternalSync. Memoized so the sync tools map (keyed
232
+ // by identity) doesn't rebuild every render.
233
+ const syncApps = useMemo(() => finalApps.filter(app => app.sync), [finalApps]);
234
+
229
235
  // Check if debug-borders is installed and auto-render the overlay
230
236
  const DebugBordersOverlay = useMemo(() => {
231
237
  try {
@@ -362,13 +368,11 @@ export const FloatingDevTools = ({
362
368
  const getToolIcon = useCallback(id => {
363
369
  const tool = finalApps.find(app => app.id === id);
364
370
  if (!tool) return null;
365
- if (typeof tool.icon === "function") {
366
- return tool.icon({
367
- slot: "dial",
368
- size: 20
369
- });
370
- }
371
- return tool.icon;
371
+ return renderAppIcon(tool.icon, {
372
+ slot: "dial",
373
+ size: 20,
374
+ color: tool.color
375
+ });
372
376
  }, [finalApps]);
373
377
 
374
378
  // Gate: Free tier only works in development mode
@@ -390,7 +394,8 @@ export const FloatingDevTools = ({
390
394
  options: typeof externalSync === "object" ? externalSync : undefined,
391
395
  requiredEnvVars: requiredEnvVars,
392
396
  isPro: isLicensed,
393
- licenseKey: licenseKey
397
+ licenseKey: licenseKey,
398
+ customApps: syncApps
394
399
  })]
395
400
  });
396
401
  }
@@ -430,7 +435,8 @@ export const FloatingDevTools = ({
430
435
  options: typeof externalSync === "object" ? externalSync : undefined,
431
436
  requiredEnvVars: requiredEnvVars,
432
437
  isPro: isLicensed,
433
- licenseKey: licenseKey
438
+ licenseKey: licenseKey,
439
+ customApps: syncApps
434
440
  })]
435
441
  })
436
442
  })
@@ -4,6 +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
8
  import { DialDevTools } from "./dial/DialDevTools";
8
9
  import { EnvironmentIndicator, persistentStorage, useHintsDisabled, devToolsStorageKeys, buoyColors } from "@buoy-gg/shared-ui";
9
10
  import { useDevToolsSettings } from "./DevToolsSettingsModal";
@@ -168,12 +169,13 @@ export const FloatingMenu = ({
168
169
 
169
170
  // Only open modal if not a toggle-only tool
170
171
  if (app.launchMode !== "toggle-only") {
171
- // Resolve the icon for minimize stack display
172
- // IMPORTANT: Use React.createElement for function components to preserve hooks
173
- const resolvedIcon = typeof app.icon === "function" ? /*#__PURE__*/React.createElement(app.icon, {
172
+ // Resolve the icon for minimize stack display (createElement keeps
173
+ // function-icon hooks working; strings become text icons).
174
+ const resolvedIcon = renderAppIcon(app.icon, {
174
175
  slot: "dial",
175
- size: 20
176
- }) : app.icon;
176
+ size: 20,
177
+ color: app.color
178
+ });
177
179
  open({
178
180
  id: app.id,
179
181
  title: app.name,
@@ -182,7 +184,8 @@ export const FloatingMenu = ({
182
184
  launchMode: app.launchMode ?? "self-modal",
183
185
  singleton: app.singleton ?? true,
184
186
  icon: resolvedIcon,
185
- color: app.color
187
+ color: app.color,
188
+ modal: app.modal
186
189
  });
187
190
  }
188
191
  };
@@ -280,7 +283,11 @@ export const FloatingMenu = ({
280
283
  actions: mergedActions
281
284
  });
282
285
  }
283
- return app.icon;
286
+ return renderAppIcon(app.icon, {
287
+ slot: "row",
288
+ size: 16,
289
+ color: app.color
290
+ });
284
291
  })()
285
292
  }, `row-${app.id}`))]
286
293
  })