@buoy-gg/core 7.0.31 → 7.0.33
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.
- package/lib/commonjs/floatingMenu/FloatingDevTools.js +24 -1
- package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +21 -5
- package/lib/commonjs/floatingMenu/autoExternalSync.js +32 -17
- package/lib/commonjs/floatingMenu/deviceIdentity.js +173 -0
- package/lib/commonjs/floatingMenu/dial/DialDevTools.js +5 -46
- package/lib/commonjs/floatingMenu/dial/DialIcon.js +2 -46
- package/lib/commonjs/version.js +1 -1
- package/lib/module/floatingMenu/FloatingDevTools.js +24 -1
- package/lib/module/floatingMenu/autoDiscoverPresets.js +21 -5
- package/lib/module/floatingMenu/autoExternalSync.js +32 -17
- package/lib/module/floatingMenu/deviceIdentity.js +163 -0
- package/lib/module/floatingMenu/dial/DialDevTools.js +6 -47
- package/lib/module/floatingMenu/dial/DialIcon.js +2 -46
- package/lib/module/version.js +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +38 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts +12 -2
- package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/deviceIdentity.d.ts +66 -0
- package/lib/typescript/commonjs/floatingMenu/deviceIdentity.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts +0 -7
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/commonjs/version.d.ts +1 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +38 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts +12 -2
- package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/deviceIdentity.d.ts +66 -0
- package/lib/typescript/module/floatingMenu/deviceIdentity.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts +0 -7
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/module/version.d.ts +1 -1
- package/package.json +5 -5
- package/lib/commonjs/floatingMenu/allToolsRegistry.js +0 -159
- package/lib/module/floatingMenu/allToolsRegistry.js +0 -155
- package/lib/typescript/commonjs/floatingMenu/allToolsRegistry.d.ts +0 -42
- package/lib/typescript/commonjs/floatingMenu/allToolsRegistry.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/allToolsRegistry.d.ts +0 -42
- package/lib/typescript/module/floatingMenu/allToolsRegistry.d.ts.map +0 -1
|
@@ -46,6 +46,13 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
46
46
|
* Storage key configuration for monitoring
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Ask Buoy's config, mirrored structurally so core never depends on the
|
|
51
|
+
* optional `@buoy-gg/ask-buoy` package (it is required lazily, like every other
|
|
52
|
+
* tool). The object is handed to `createAskBuoyTool` untouched, so any field
|
|
53
|
+
* that package accepts passes through — the index signature is the seam.
|
|
54
|
+
*/
|
|
55
|
+
|
|
49
56
|
/**
|
|
50
57
|
* Props for FloatingDevTools component.
|
|
51
58
|
*
|
|
@@ -112,6 +119,7 @@ const FloatingDevTools = ({
|
|
|
112
119
|
apps,
|
|
113
120
|
requiredEnvVars,
|
|
114
121
|
requiredStorageKeys,
|
|
122
|
+
askBuoy,
|
|
115
123
|
children,
|
|
116
124
|
disableHints = false,
|
|
117
125
|
defaultFloatingTools,
|
|
@@ -232,8 +240,23 @@ const FloatingDevTools = ({
|
|
|
232
240
|
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/storage", error);
|
|
233
241
|
}
|
|
234
242
|
}
|
|
243
|
+
|
|
244
|
+
// The documented one-prop Ask Buoy setup. Same lazy-require shape as env
|
|
245
|
+
// and storage above: the configured tool replaces the bare auto-discovered
|
|
246
|
+
// preset by id, and an app without @buoy-gg/ask-buoy installed gets the
|
|
247
|
+
// usual optional-require report instead of a crash.
|
|
248
|
+
if (askBuoy) {
|
|
249
|
+
try {
|
|
250
|
+
const {
|
|
251
|
+
createAskBuoyTool
|
|
252
|
+
} = require("@buoy-gg/ask-buoy");
|
|
253
|
+
overrides.push(createAskBuoyTool(askBuoy));
|
|
254
|
+
} catch (error) {
|
|
255
|
+
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/ask-buoy", error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
235
258
|
return overrides;
|
|
236
|
-
}, [requiredEnvVars, requiredStorageKeys]);
|
|
259
|
+
}, [requiredEnvVars, requiredStorageKeys, askBuoy]);
|
|
237
260
|
|
|
238
261
|
// Combine user apps with config overrides
|
|
239
262
|
const userApps = (0, _react.useMemo)(() => {
|
|
@@ -189,6 +189,22 @@ function autoDiscoverPresets() {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
},
|
|
192
|
+
// Sentry
|
|
193
|
+
{
|
|
194
|
+
name: "@buoy-gg/sentry",
|
|
195
|
+
loader: () => {
|
|
196
|
+
try {
|
|
197
|
+
// @ts-ignore - Dynamic import that may not exist
|
|
198
|
+
const {
|
|
199
|
+
sentryToolPreset
|
|
200
|
+
} = require("@buoy-gg/sentry");
|
|
201
|
+
return sentryToolPreset;
|
|
202
|
+
} catch (e) {
|
|
203
|
+
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/sentry", e);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
},
|
|
192
208
|
// Time Machine
|
|
193
209
|
{
|
|
194
210
|
name: "@buoy-gg/time-machine",
|
|
@@ -223,16 +239,16 @@ function autoDiscoverPresets() {
|
|
|
223
239
|
},
|
|
224
240
|
// Ask Buoy (AI chat)
|
|
225
241
|
{
|
|
226
|
-
name: "@buoy-gg/
|
|
242
|
+
name: "@buoy-gg/ask-buoy",
|
|
227
243
|
loader: () => {
|
|
228
244
|
try {
|
|
229
245
|
// @ts-ignore - Dynamic import that may not exist
|
|
230
246
|
const {
|
|
231
|
-
|
|
232
|
-
} = require("@buoy-gg/
|
|
233
|
-
return
|
|
247
|
+
askBuoyToolPreset
|
|
248
|
+
} = require("@buoy-gg/ask-buoy");
|
|
249
|
+
return askBuoyToolPreset;
|
|
234
250
|
} catch (e) {
|
|
235
|
-
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/
|
|
251
|
+
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/ask-buoy", e);
|
|
236
252
|
return null;
|
|
237
253
|
}
|
|
238
254
|
}
|
|
@@ -11,6 +11,7 @@ var _react = require("react");
|
|
|
11
11
|
var _reactNative = require("react-native");
|
|
12
12
|
var _optionalRequire = require("./optionalRequire.js");
|
|
13
13
|
var _appSyncAdapter = require("./sync/appSyncAdapter.js");
|
|
14
|
+
var _deviceIdentity = require("./deviceIdentity.js");
|
|
14
15
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
15
16
|
/**
|
|
16
17
|
* Zero-config device→desktop sync for FloatingDevTools.
|
|
@@ -24,8 +25,10 @@ var _sharedUi = require("@buoy-gg/shared-ui");
|
|
|
24
25
|
* package, discovered with the same guarded-require pattern as
|
|
25
26
|
* autoDiscoverPresets. No <DevToolsSync> wiring needed in the app.
|
|
26
27
|
*
|
|
27
|
-
* Identity defaults come from
|
|
28
|
-
*
|
|
28
|
+
* Identity defaults come from deviceIdentity.ts: the app name (expo-constants)
|
|
29
|
+
* plus a persisted per-install tail, so two devices on one build register as
|
|
30
|
+
* two devices — "MyApp (iPhone 17 Pro · 2c1d)" / "myapp-ios-3f9a2c1d".
|
|
31
|
+
* Overridable via the `externalSync` prop.
|
|
29
32
|
*/
|
|
30
33
|
|
|
31
34
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
@@ -48,6 +51,7 @@ let zustandModule = null;
|
|
|
48
51
|
let jotaiModule = null;
|
|
49
52
|
let eventsModule = null;
|
|
50
53
|
let consoleModule = null;
|
|
54
|
+
let sentryModule = null;
|
|
51
55
|
let envModule = null;
|
|
52
56
|
let highlightUpdatesModule = null;
|
|
53
57
|
let debugBordersModule = null;
|
|
@@ -113,6 +117,11 @@ try {
|
|
|
113
117
|
} catch (e) {
|
|
114
118
|
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/console", e);
|
|
115
119
|
}
|
|
120
|
+
try {
|
|
121
|
+
sentryModule = require("@buoy-gg/sentry");
|
|
122
|
+
} catch (e) {
|
|
123
|
+
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/sentry", e);
|
|
124
|
+
}
|
|
116
125
|
try {
|
|
117
126
|
envModule = require("@buoy-gg/env");
|
|
118
127
|
} catch (e) {
|
|
@@ -178,13 +187,12 @@ function getAppName() {
|
|
|
178
187
|
const constants = expoConstantsModule?.default ?? expoConstantsModule;
|
|
179
188
|
return constants?.expoConfig?.name ?? constants?.manifest2?.extra?.expoClient?.name ?? null;
|
|
180
189
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
};
|
|
190
|
+
|
|
191
|
+
/** expo-constants' `deviceName`: the simulator's name on a sim, the model on Android. */
|
|
192
|
+
function getHardwareName() {
|
|
193
|
+
const constants = expoConstantsModule?.default ?? expoConstantsModule;
|
|
194
|
+
const name = constants?.deviceName;
|
|
195
|
+
return typeof name === "string" && name.length > 0 ? name : null;
|
|
188
196
|
}
|
|
189
197
|
/**
|
|
190
198
|
* Build the `toolId -> sync adapter` map from whatever tool packages the app
|
|
@@ -248,12 +256,16 @@ function AutoExternalSync({
|
|
|
248
256
|
requiredEnvVars,
|
|
249
257
|
customApps
|
|
250
258
|
});
|
|
251
|
-
const identity = (0,
|
|
252
|
-
|
|
259
|
+
const identity = (0, _deviceIdentity.useDeviceIdentity)(options, {
|
|
260
|
+
appName: getAppName(),
|
|
261
|
+
hardwareName: getHardwareName(),
|
|
262
|
+
platform: _reactNative.Platform.OS
|
|
263
|
+
});
|
|
264
|
+
const deviceId = identity?.deviceId ?? null;
|
|
253
265
|
const {
|
|
254
266
|
socket
|
|
255
267
|
} = useExternalSyncSocket({
|
|
256
|
-
deviceName:
|
|
268
|
+
deviceName: identity?.deviceName ?? "Buoy Device",
|
|
257
269
|
// Older external-sync versions predate getDefaultSocketURL and require
|
|
258
270
|
// an explicit URL — keep the literal fallback for them.
|
|
259
271
|
socketURL: options?.socketURL ?? externalSyncModule.getDefaultSocketURL?.() ?? "http://localhost:42831",
|
|
@@ -342,6 +354,9 @@ function useToolAdapterMapInner(queryAdapter, requiredEnvVars, customApps) {
|
|
|
342
354
|
if (consoleModule?.consoleSyncAdapter) {
|
|
343
355
|
map.console = consoleModule.consoleSyncAdapter;
|
|
344
356
|
}
|
|
357
|
+
if (sentryModule?.sentrySyncAdapter) {
|
|
358
|
+
map.sentry = sentryModule.sentrySyncAdapter;
|
|
359
|
+
}
|
|
345
360
|
// Lazy: time-machine imports `@buoy-gg/core` (useToolHost), and this file
|
|
346
361
|
// is loaded from core's index. Requiring it at module top-level is a
|
|
347
362
|
// require cycle (LogBox on every reload). Waiting until first render
|
|
@@ -365,14 +380,14 @@ function useToolAdapterMapInner(queryAdapter, requiredEnvVars, customApps) {
|
|
|
365
380
|
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/scenarios", e);
|
|
366
381
|
}
|
|
367
382
|
// Lazy, same require-cycle reason as time-machine and scenarios: the
|
|
368
|
-
//
|
|
383
|
+
// ask-buoy package imports `@buoy-gg/core` for the tool-host hooks.
|
|
369
384
|
try {
|
|
370
|
-
const
|
|
371
|
-
if (
|
|
372
|
-
map
|
|
385
|
+
const askBuoyModule = require("@buoy-gg/ask-buoy");
|
|
386
|
+
if (askBuoyModule?.askBuoySyncAdapter) {
|
|
387
|
+
map["ask-buoy"] = askBuoyModule.askBuoySyncAdapter;
|
|
373
388
|
}
|
|
374
389
|
} catch (e) {
|
|
375
|
-
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/
|
|
390
|
+
(0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/ask-buoy", e);
|
|
376
391
|
}
|
|
377
392
|
if (envModule?.createEnvSyncAdapter) {
|
|
378
393
|
map.env = envModule.createEnvSyncAdapter(requiredEnvVars ?? []);
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.INSTALL_ID_LENGTH = void 0;
|
|
7
|
+
exports.buildDeviceIdentity = buildDeviceIdentity;
|
|
8
|
+
exports.generateInstallId = generateInstallId;
|
|
9
|
+
exports.getInstallId = getInstallId;
|
|
10
|
+
exports.loadInstallId = loadInstallId;
|
|
11
|
+
exports.resetInstallIdForTests = resetInstallIdForTests;
|
|
12
|
+
exports.useDeviceIdentity = useDeviceIdentity;
|
|
13
|
+
var _react = require("react");
|
|
14
|
+
var _reactNative = require("react-native");
|
|
15
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
16
|
+
/**
|
|
17
|
+
* How a device introduces itself to the Buoy Desktop broker.
|
|
18
|
+
*
|
|
19
|
+
* The id used to be `<app-slug>-<platform>` — a BUILD identity, not a device
|
|
20
|
+
* identity. Every phone and simulator running the same app registered under
|
|
21
|
+
* the same id, and the broker (which deliberately lets one device hold
|
|
22
|
+
* several sockets for Fast Refresh) treated each newcomer as a reconnect and
|
|
23
|
+
* handed it the identity. Actions then landed on whichever device connected
|
|
24
|
+
* most recently, silently. See DEVICE_IDENTITY_COLLISION.md.
|
|
25
|
+
*
|
|
26
|
+
* Now the id carries a per-install tail minted once and persisted:
|
|
27
|
+
*
|
|
28
|
+
* deviceId = "<app-slug>-<platform>-<8 hex>" e.g. acme-app-ios-3f9a2c1d
|
|
29
|
+
* deviceName = "<App Name> (<hardware> · <last 4>)" e.g. Acme App (iPhone 17 Pro · 2c1d)
|
|
30
|
+
*
|
|
31
|
+
* The tail is what makes two devices on one build two devices. The name's
|
|
32
|
+
* hardware label comes from expo-constants' `deviceName` when available (the
|
|
33
|
+
* simulator's own name on a sim, "iPhone" on iOS 16+ hardware, the model on
|
|
34
|
+
* Android) and falls back to the platform; the 4-char tail is always on so
|
|
35
|
+
* two identical simulators never share a label.
|
|
36
|
+
*
|
|
37
|
+
* Persistence: native goes through shared's `persistentStorage` (file system
|
|
38
|
+
* → AsyncStorage → memory); web uses `sessionStorage` so every browser TAB
|
|
39
|
+
* is its own device — `localStorage` is shared across tabs and would collide
|
|
40
|
+
* exactly the way the old default did. The id does not survive a reinstall
|
|
41
|
+
* on purpose: it crosses an unauthenticated LAN socket, and deleting the app
|
|
42
|
+
* should reset it.
|
|
43
|
+
*
|
|
44
|
+
* An explicit `externalSync.deviceId` bypasses all of this; whoever pins an
|
|
45
|
+
* id owns its uniqueness.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/** Minimal getItem/setItem surface, so storage can be injected in tests. */
|
|
49
|
+
|
|
50
|
+
const INSTALL_ID_LENGTH = exports.INSTALL_ID_LENGTH = 8;
|
|
51
|
+
|
|
52
|
+
/** 8 lowercase hex chars — short enough to read, random enough for a LAN. */
|
|
53
|
+
function generateInstallId() {
|
|
54
|
+
let out = "";
|
|
55
|
+
while (out.length < INSTALL_ID_LENGTH) {
|
|
56
|
+
out += (Math.random() * 16 | 0).toString(16);
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function slugify(name) {
|
|
61
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
62
|
+
}
|
|
63
|
+
/** Pure: turns the inputs into the id/name pair the broker sees. */
|
|
64
|
+
function buildDeviceIdentity({
|
|
65
|
+
appName,
|
|
66
|
+
hardwareName,
|
|
67
|
+
platform,
|
|
68
|
+
installId,
|
|
69
|
+
deviceId
|
|
70
|
+
}) {
|
|
71
|
+
const slug = slugify(appName ?? "buoy") || "buoy";
|
|
72
|
+
const label = appName ?? "Buoy Device";
|
|
73
|
+
const hardware = hardwareName?.trim() || platform;
|
|
74
|
+
if (deviceId) {
|
|
75
|
+
// The app pinned its own id: it can also pin its own name, so keep the
|
|
76
|
+
// default name free of a tail that would refer to an id nobody sees.
|
|
77
|
+
return {
|
|
78
|
+
deviceId,
|
|
79
|
+
deviceName: `${label} (${hardware})`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const id = `${slug}-${platform}-${installId}`;
|
|
83
|
+
return {
|
|
84
|
+
deviceId: id,
|
|
85
|
+
deviceName: `${label} (${hardware} · ${installId.slice(-4)})`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Web: every tab is a device, so the id lives in the tab's sessionStorage.
|
|
90
|
+
function sessionStore() {
|
|
91
|
+
const g = globalThis;
|
|
92
|
+
try {
|
|
93
|
+
return g.sessionStorage ?? null;
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The persisted per-install tail: read it, or mint and store it on the first
|
|
100
|
+
* dial. Storage failures degrade to a fresh in-memory id rather than blocking
|
|
101
|
+
* the socket — a per-launch id still beats the old colliding one.
|
|
102
|
+
*/
|
|
103
|
+
async function loadInstallId({
|
|
104
|
+
platform = _reactNative.Platform.OS,
|
|
105
|
+
store,
|
|
106
|
+
mint = generateInstallId
|
|
107
|
+
} = {}) {
|
|
108
|
+
const key = _sharedUi.devToolsStorageKeys.sync.deviceId();
|
|
109
|
+
const target = store === undefined ? platform === "web" ? sessionStore() : _sharedUi.persistentStorage : store;
|
|
110
|
+
if (!target) return mint();
|
|
111
|
+
try {
|
|
112
|
+
const existing = await target.getItem(key);
|
|
113
|
+
if (existing && existing.length > 0) return existing;
|
|
114
|
+
const fresh = mint();
|
|
115
|
+
await target.setItem(key, fresh);
|
|
116
|
+
return fresh;
|
|
117
|
+
} catch {
|
|
118
|
+
return mint();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let installIdPromise = null;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Process-wide memo so every mount (and every Fast Refresh of the component
|
|
125
|
+
* that calls it) shares one storage round-trip and one id.
|
|
126
|
+
*/
|
|
127
|
+
function getInstallId() {
|
|
128
|
+
if (!installIdPromise) installIdPromise = loadInstallId();
|
|
129
|
+
return installIdPromise;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Tests only. */
|
|
133
|
+
function resetInstallIdForTests() {
|
|
134
|
+
installIdPromise = null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The identity to dial with. Null until the per-install id is read from
|
|
138
|
+
* storage (one round-trip on first mount; memoized process-wide after) —
|
|
139
|
+
* both sync hooks no-op on a null id, so the socket simply dials a beat
|
|
140
|
+
* later. An explicit `deviceId` skips the read entirely; an explicit
|
|
141
|
+
* `deviceName` is used verbatim.
|
|
142
|
+
*/
|
|
143
|
+
function useDeviceIdentity(options, env) {
|
|
144
|
+
const pinnedId = options?.deviceId;
|
|
145
|
+
const pinnedName = options?.deviceName;
|
|
146
|
+
const loadId = env.loadId ?? getInstallId;
|
|
147
|
+
const platform = env.platform ?? _reactNative.Platform.OS;
|
|
148
|
+
const [installId, setInstallId] = (0, _react.useState)(null);
|
|
149
|
+
(0, _react.useEffect)(() => {
|
|
150
|
+
if (pinnedId) return;
|
|
151
|
+
let cancelled = false;
|
|
152
|
+
loadId().then(id => {
|
|
153
|
+
if (!cancelled) setInstallId(id);
|
|
154
|
+
});
|
|
155
|
+
return () => {
|
|
156
|
+
cancelled = true;
|
|
157
|
+
};
|
|
158
|
+
}, [pinnedId, loadId]);
|
|
159
|
+
return (0, _react.useMemo)(() => {
|
|
160
|
+
if (!pinnedId && !installId) return null;
|
|
161
|
+
const identity = buildDeviceIdentity({
|
|
162
|
+
appName: env.appName,
|
|
163
|
+
hardwareName: env.hardwareName,
|
|
164
|
+
platform,
|
|
165
|
+
installId: installId ?? "",
|
|
166
|
+
deviceId: pinnedId
|
|
167
|
+
});
|
|
168
|
+
return pinnedName ? {
|
|
169
|
+
...identity,
|
|
170
|
+
deviceName: pinnedName
|
|
171
|
+
} : identity;
|
|
172
|
+
}, [pinnedId, pinnedName, installId, env.appName, env.hardwareName, platform]);
|
|
173
|
+
}
|
|
@@ -9,7 +9,6 @@ var _reactNative = require("react-native");
|
|
|
9
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
10
|
var _DialIcon = require("./DialIcon.js");
|
|
11
11
|
var _DialHyperspace = require("./DialHyperspace.js");
|
|
12
|
-
var _allToolsRegistry = require("../allToolsRegistry.js");
|
|
13
12
|
var _DialPagination = require("./DialPagination.js");
|
|
14
13
|
var _dialUsageStore = require("./dialUsageStore.js");
|
|
15
14
|
var _DevToolsSettingsModal = require("../DevToolsSettingsModal");
|
|
@@ -261,38 +260,6 @@ const DialDevTools = ({
|
|
|
261
260
|
return map;
|
|
262
261
|
}, [dialApps, actions, open, onClose]);
|
|
263
262
|
|
|
264
|
-
// Tools from the global registry that are NOT installed in this app.
|
|
265
|
-
// These are appended after all available tools so paging "next" eventually
|
|
266
|
-
// reveals them. They are display-only (real icon + real name + "Unavailable").
|
|
267
|
-
const unavailableTools = (0, _react.useMemo)(() => {
|
|
268
|
-
const installedIds = new Set(dialApps.map(a => a.id));
|
|
269
|
-
return _allToolsRegistry.ALL_TOOLS_REGISTRY.filter(t => !installedIds.has(t.id));
|
|
270
|
-
}, [dialApps]);
|
|
271
|
-
|
|
272
|
-
// Build stable IconType entries for each unavailable tool.
|
|
273
|
-
const unavailableIconsById = (0, _react.useMemo)(() => {
|
|
274
|
-
const map = new Map();
|
|
275
|
-
for (const tool of unavailableTools) {
|
|
276
|
-
map.set(tool.id, {
|
|
277
|
-
id: tool.id,
|
|
278
|
-
name: tool.name,
|
|
279
|
-
// Pre-render the icon at the standard GLYPH size (not the slot size —
|
|
280
|
-
// the slot also holds the label + "Unavailable" caption, so a glyph
|
|
281
|
-
// rendered at DIAL_ICON_SIZE overflows the slot and looks ~2x bigger
|
|
282
|
-
// than installed tools' icons).
|
|
283
|
-
icon: tool.renderIcon(_floatingToolsCore.DIAL_ICON_GLYPH_SIZE),
|
|
284
|
-
iconComponent: undefined,
|
|
285
|
-
color: tool.color,
|
|
286
|
-
unavailable: true,
|
|
287
|
-
// Tapping an unavailable tool is intentionally a no-op: the dial stays
|
|
288
|
-
// open and no modal launches, signalling the tool is not installed.
|
|
289
|
-
// Future: add a brief Toast with an install link here.
|
|
290
|
-
onPress: () => {}
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
return map;
|
|
294
|
-
}, [unavailableTools]);
|
|
295
|
-
|
|
296
263
|
// Snapshot the usage-ranked order when the dial opens. It stays stable while
|
|
297
264
|
// open so icons don't jump positions mid-interaction.
|
|
298
265
|
const [rankedIds, setRankedIds] = (0, _react.useState)(() => (0, _dialUsageStore.getRankedToolIds)(dialApps.map(a => a.id)));
|
|
@@ -312,8 +279,9 @@ const DialDevTools = ({
|
|
|
312
279
|
};
|
|
313
280
|
}, [dialApps]);
|
|
314
281
|
|
|
315
|
-
// Page count spans
|
|
316
|
-
|
|
282
|
+
// Page count spans the installed tools (usage-ranked). Only tools the app
|
|
283
|
+
// actually has appear in the dial — nothing is shown for uninstalled ones.
|
|
284
|
+
const pageCount = Math.max(1, Math.ceil(rankedIds.length / _floatingToolsCore.MAX_DIAL_SLOTS));
|
|
317
285
|
const [currentPage, setCurrentPage] = (0, _react.useState)(0);
|
|
318
286
|
const safePage = Math.min(currentPage, pageCount - 1);
|
|
319
287
|
|
|
@@ -321,18 +289,13 @@ const DialDevTools = ({
|
|
|
321
289
|
// snapshotted on open, so each icon's page and slot are fixed for the
|
|
322
290
|
// session — which lets us mount all icons once and paginate purely by
|
|
323
291
|
// toggling visibility (no remounts on page change).
|
|
324
|
-
//
|
|
325
|
-
// Order: usage-ranked available tools first, unavailable tools appended at
|
|
326
|
-
// the very end. This guarantees unavailable items always appear on the last
|
|
327
|
-
// pages and never displace available tools.
|
|
328
292
|
const allDialIcons = (0, _react.useMemo)(() => {
|
|
329
|
-
|
|
330
|
-
return allIds.map(id => iconsById.get(id) ?? unavailableIconsById.get(id)).filter(icon => Boolean(icon)).map((icon, i) => ({
|
|
293
|
+
return rankedIds.map(id => iconsById.get(id)).filter(icon => Boolean(icon)).map((icon, i) => ({
|
|
331
294
|
icon,
|
|
332
295
|
page: Math.floor(i / _floatingToolsCore.MAX_DIAL_SLOTS),
|
|
333
296
|
slot: i % _floatingToolsCore.MAX_DIAL_SLOTS
|
|
334
297
|
}));
|
|
335
|
-
}, [rankedIds, iconsById
|
|
298
|
+
}, [rankedIds, iconsById]);
|
|
336
299
|
|
|
337
300
|
// Empty slot indices for the current page (only the last page can be
|
|
338
301
|
// partial). These are cheap placeholder dots.
|
|
@@ -535,10 +498,6 @@ const DialDevTools = ({
|
|
|
535
498
|
});
|
|
536
499
|
};
|
|
537
500
|
const handleIconPress = icon => {
|
|
538
|
-
// Unavailable tools are display-only. Return early so the dial stays open
|
|
539
|
-
// and no modal or animation is triggered. The dimmed appearance already
|
|
540
|
-
// communicates that the tool is not installed.
|
|
541
|
-
if (icon.unavailable) return;
|
|
542
501
|
setSelectedIcon(1);
|
|
543
502
|
const interaction = _floatingToolsCore.dialAnimationConfig?.interaction ?? {
|
|
544
503
|
iconSelect: {
|
|
@@ -185,35 +185,6 @@ const DialIcon = ({
|
|
|
185
185
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
186
186
|
style: styles.emptyDot
|
|
187
187
|
})
|
|
188
|
-
}) : icon.unavailable ?
|
|
189
|
-
/*#__PURE__*/
|
|
190
|
-
// ── Unavailable tool ──────────────────────────────────────────────────
|
|
191
|
-
// Renders the real icon + real name at NORMAL styling (visually identical
|
|
192
|
-
// to an installed tool). The ONLY signal that it's unavailable is the small
|
|
193
|
-
// muted "Unavailable" caption under the name. It's non-interactive: no
|
|
194
|
-
// Pressable, and the dial stays open when tapped because
|
|
195
|
-
// DialDevTools.handleIconPress returns early for unavailable icons.
|
|
196
|
-
(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
197
|
-
style: styles.pressable,
|
|
198
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
199
|
-
style: styles.iconWrapper,
|
|
200
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
201
|
-
style: styles.iconClamp,
|
|
202
|
-
children: icon.icon
|
|
203
|
-
})
|
|
204
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
205
|
-
style: styles.label,
|
|
206
|
-
numberOfLines: 1,
|
|
207
|
-
adjustsFontSizeToFit: true,
|
|
208
|
-
minimumFontScale: 0.7,
|
|
209
|
-
children: icon.name.toUpperCase()
|
|
210
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
211
|
-
style: styles.unavailableCaption,
|
|
212
|
-
numberOfLines: 1,
|
|
213
|
-
adjustsFontSizeToFit: true,
|
|
214
|
-
minimumFontScale: 0.6,
|
|
215
|
-
children: "Unavailable"
|
|
216
|
-
})]
|
|
217
188
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
218
189
|
onPress: () => onPress(icon),
|
|
219
190
|
onPressIn: handlePressIn,
|
|
@@ -292,8 +263,8 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
292
263
|
},
|
|
293
264
|
/**
|
|
294
265
|
* Hard clamp for PRE-RENDERED icon nodes (icon.icon) — the path where the
|
|
295
|
-
* glyph's size is baked in by whoever created the node (
|
|
296
|
-
*
|
|
266
|
+
* glyph's size is baked in by whoever created the node (consumer custom
|
|
267
|
+
* tools) rather than controlled here. The
|
|
297
268
|
* fixed overflow-hidden box guarantees an oversized node can never blow past
|
|
298
269
|
* its slot and dwarf neighboring icons — worst case it center-crops. The
|
|
299
270
|
* iconComponent path stays unclipped: we render it at DIAL_ICON_GLYPH_SIZE
|
|
@@ -327,20 +298,5 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
327
298
|
backgroundColor: _floatingToolsCore.dialColors.emptyDotBackground,
|
|
328
299
|
borderWidth: _floatingToolsCore.dialStyles.emptySlot.borderWidth,
|
|
329
300
|
borderColor: _floatingToolsCore.dialColors.emptyDotBorder
|
|
330
|
-
},
|
|
331
|
-
// ── Unavailable tool styles ────────────────────────────────────────────────
|
|
332
|
-
/**
|
|
333
|
-
* Small muted caption shown under the (normal-styled) tool name — the only
|
|
334
|
-
* signal that the tool is not installed. Kept compact (smaller than the label)
|
|
335
|
-
* so the extra line doesn't disturb the arc layout. Uses the shared muted
|
|
336
|
-
* theme token rather than a hardcoded color.
|
|
337
|
-
*/
|
|
338
|
-
unavailableCaption: {
|
|
339
|
-
fontSize: _floatingToolsCore.dialStyles.icon.label.fontSize - 1,
|
|
340
|
-
fontWeight: _floatingToolsCore.dialStyles.icon.label.fontWeight,
|
|
341
|
-
letterSpacing: _floatingToolsCore.dialStyles.icon.label.letterSpacing,
|
|
342
|
-
fontFamily: _floatingToolsCore.dialStyles.icon.label.fontFamily,
|
|
343
|
-
color: _floatingToolsCore.dialColors.iconLabelMuted,
|
|
344
|
-
textTransform: "uppercase"
|
|
345
301
|
}
|
|
346
302
|
});
|
package/lib/commonjs/version.js
CHANGED
|
@@ -15,4 +15,4 @@ exports.SDK_VERSION = void 0;
|
|
|
15
15
|
* Drift is prevented by a test that fails when this disagrees with
|
|
16
16
|
* package.json, so a version bump that forgets this file cannot ship.
|
|
17
17
|
*/
|
|
18
|
-
const SDK_VERSION = exports.SDK_VERSION = "7.0.
|
|
18
|
+
const SDK_VERSION = exports.SDK_VERSION = "7.0.33";
|
|
@@ -42,6 +42,13 @@ import { externalSyncOptions, shouldMountExternalSync } from "./externalSyncGate
|
|
|
42
42
|
* Storage key configuration for monitoring
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Ask Buoy's config, mirrored structurally so core never depends on the
|
|
47
|
+
* optional `@buoy-gg/ask-buoy` package (it is required lazily, like every other
|
|
48
|
+
* tool). The object is handed to `createAskBuoyTool` untouched, so any field
|
|
49
|
+
* that package accepts passes through — the index signature is the seam.
|
|
50
|
+
*/
|
|
51
|
+
|
|
45
52
|
/**
|
|
46
53
|
* Props for FloatingDevTools component.
|
|
47
54
|
*
|
|
@@ -108,6 +115,7 @@ export const FloatingDevTools = ({
|
|
|
108
115
|
apps,
|
|
109
116
|
requiredEnvVars,
|
|
110
117
|
requiredStorageKeys,
|
|
118
|
+
askBuoy,
|
|
111
119
|
children,
|
|
112
120
|
disableHints = false,
|
|
113
121
|
defaultFloatingTools,
|
|
@@ -228,8 +236,23 @@ export const FloatingDevTools = ({
|
|
|
228
236
|
reportOptionalRequireError("@buoy-gg/storage", error);
|
|
229
237
|
}
|
|
230
238
|
}
|
|
239
|
+
|
|
240
|
+
// The documented one-prop Ask Buoy setup. Same lazy-require shape as env
|
|
241
|
+
// and storage above: the configured tool replaces the bare auto-discovered
|
|
242
|
+
// preset by id, and an app without @buoy-gg/ask-buoy installed gets the
|
|
243
|
+
// usual optional-require report instead of a crash.
|
|
244
|
+
if (askBuoy) {
|
|
245
|
+
try {
|
|
246
|
+
const {
|
|
247
|
+
createAskBuoyTool
|
|
248
|
+
} = require("@buoy-gg/ask-buoy");
|
|
249
|
+
overrides.push(createAskBuoyTool(askBuoy));
|
|
250
|
+
} catch (error) {
|
|
251
|
+
reportOptionalRequireError("@buoy-gg/ask-buoy", error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
231
254
|
return overrides;
|
|
232
|
-
}, [requiredEnvVars, requiredStorageKeys]);
|
|
255
|
+
}, [requiredEnvVars, requiredStorageKeys, askBuoy]);
|
|
233
256
|
|
|
234
257
|
// Combine user apps with config overrides
|
|
235
258
|
const userApps = useMemo(() => {
|
|
@@ -185,6 +185,22 @@ export function autoDiscoverPresets() {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
},
|
|
188
|
+
// Sentry
|
|
189
|
+
{
|
|
190
|
+
name: "@buoy-gg/sentry",
|
|
191
|
+
loader: () => {
|
|
192
|
+
try {
|
|
193
|
+
// @ts-ignore - Dynamic import that may not exist
|
|
194
|
+
const {
|
|
195
|
+
sentryToolPreset
|
|
196
|
+
} = require("@buoy-gg/sentry");
|
|
197
|
+
return sentryToolPreset;
|
|
198
|
+
} catch (e) {
|
|
199
|
+
reportOptionalRequireError("@buoy-gg/sentry", e);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
188
204
|
// Time Machine
|
|
189
205
|
{
|
|
190
206
|
name: "@buoy-gg/time-machine",
|
|
@@ -219,16 +235,16 @@ export function autoDiscoverPresets() {
|
|
|
219
235
|
},
|
|
220
236
|
// Ask Buoy (AI chat)
|
|
221
237
|
{
|
|
222
|
-
name: "@buoy-gg/
|
|
238
|
+
name: "@buoy-gg/ask-buoy",
|
|
223
239
|
loader: () => {
|
|
224
240
|
try {
|
|
225
241
|
// @ts-ignore - Dynamic import that may not exist
|
|
226
242
|
const {
|
|
227
|
-
|
|
228
|
-
} = require("@buoy-gg/
|
|
229
|
-
return
|
|
243
|
+
askBuoyToolPreset
|
|
244
|
+
} = require("@buoy-gg/ask-buoy");
|
|
245
|
+
return askBuoyToolPreset;
|
|
230
246
|
} catch (e) {
|
|
231
|
-
reportOptionalRequireError("@buoy-gg/
|
|
247
|
+
reportOptionalRequireError("@buoy-gg/ask-buoy", e);
|
|
232
248
|
return null;
|
|
233
249
|
}
|
|
234
250
|
}
|