From d4935aa2a28fcbab8c3556e3532e53667092701e Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 11 Sep 2026 14:17:55 +0200 Subject: feat: gate the create-group wizard on whether a node is bundled MeshBay Light has no bundled meshbay-node.exe, so the create-group wizard (which assumes it can start a local node) needs its own signal, not just platform.node.available. main.js exposes it over IPC (node:bundled) by checking the packaged resources directory rather than trusting a build-time constant; preload.js and platform.js carry it through the usual contextBridge/wrapper path. winCanElevateServiceMode() replaces the two prior 'app.isPackaged' checks for whether the app can offer service-mode elevation -- Light is packaged but has no service-mode.ps1 to elevate into, so packaged alone was already the wrong test even before this target existed. create-group-page.js gates the wizard step that starts a node on the new capability instead of hiding the whole feature; node-page.js's comment fix is unrelated cosmetic drift caught in the same pass. Co-Authored-By: Claude Sonnet 5 --- packages/meshbay-client/src/main.js | 50 +++++++++++++++++++++++++++++----- packages/meshbay-client/src/preload.js | 4 +++ 2 files changed, 47 insertions(+), 7 deletions(-) (limited to 'packages/meshbay-client/src') diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js index 83cf772..d7e3e7b 100644 --- a/packages/meshbay-client/src/main.js +++ b/packages/meshbay-client/src/main.js @@ -984,6 +984,22 @@ function registerBridge() { } }); + // Does THIS build ship its own node, as opposed to one merely reachable on + // PATH (findNodeBinary's fallback, meant for a dev venv -- not "this + // installer provisioned a node"). The "Light" target (electron-builder + // .light.yml) ships no node-runtime extraResource at all; the renderer + // uses this to fall back to the browser-only "create a group" form + // instead of the wizard that assumes a local node it can link right there + // (create-group-page.js) -- see C:\Users\admin\devel\light-client.md 5.3. + // Unaffected off win32 and in dev: only a packaged Windows build can even + // be Light, so everything else keeps today's behaviour unconditionally. + function hasBundledNode() { + if (process.platform === 'win32' && app.isPackaged) { + return fs.existsSync(path.join(process.resourcesPath, 'node-runtime', 'meshbay-node.exe')); + } + return true; + } + function findNodeBinary() { if (process.platform === 'win32') { // A packaged Windows build carries the frozen daemon as an @@ -1232,6 +1248,8 @@ function registerBridge() { return { installed: Boolean(bin) }; }); + ipcMain.handle('node:bundled', () => hasBundledNode()); + // The systemd unit's own view of the node, for the status panel at the top // of the Node page. Deliberately not `probeNode()`: that asks the daemon's // own HTTP API, which cannot answer while the daemon is stopped or crash- @@ -1245,6 +1263,18 @@ function registerBridge() { ipcMain.handle('node:service-status', () => nodeServiceStatus()); + // service-mode.ps1 is an extraResource present in a packaged Full build, + // absent from a packaged Light one (nothing to run as a service) and from + // an unpackaged dev run. app.isPackaged alone used to gate this, which is + // wrong for Light: it would enable the "background service" option and + // only fail when actually clicked (winElevateServiceMode's own existsSync + // check below, with an actionable error) -- correct but a dead click the + // Node page should not offer in the first place. + function winCanElevateServiceMode() { + return app.isPackaged + && fs.existsSync(path.join(process.resourcesPath, 'service-mode.ps1')); + } + async function nodeServiceStatus() { if (process.platform === 'win32') { const svc = await winServiceTaskStatus(); @@ -1259,11 +1289,12 @@ function registerBridge() { activeState: running ? 'active' : 'inactive', subState: svc.state, // Whether switching startup mode can actually elevate right now — - // service-mode.ps1 is an extraResource, only present in a packaged - // build. Already installed here, so removing it always works - // regardless; this only gates the Node page offering to switch - // *into* service mode. - canElevate: app.isPackaged, + // service-mode.ps1 is an extraResource, present in a packaged Full + // build but not a Light one (no node to run as a service at all). + // Already installed here, so removing it always works regardless; + // this only gates the Node page offering to switch *into* service + // mode. + canElevate: winCanElevateServiceMode(), }; } // Per-user Startup mode. `installed` used to be winAutostartInstalled(), @@ -1275,12 +1306,17 @@ function registerBridge() { const [p, bin] = await Promise.all([probeNode(), findNodeBinary()]); return { supported: true, - mode: 'startup', + // Only claim "startup mode" once a node was actually found (bundled + // or on PATH) -- a Light install with none at all would otherwise + // show a working-looking autostart dropdown for a node that does + // not exist. `showStartupRow` in node-page.js is gated on this being + // a string, so `null` here hides that whole row. + mode: bin ? 'startup' : null, installed: Boolean(bin), autostart: winAutostartInstalled(), activeState: p ? 'active' : 'inactive', subState: p ? 'running' : '', - canElevate: app.isPackaged, + canElevate: winCanElevateServiceMode(), }; } if (process.platform !== 'linux') return { supported: false }; diff --git a/packages/meshbay-client/src/preload.js b/packages/meshbay-client/src/preload.js index 13d4f37..c9c9fe0 100644 --- a/packages/meshbay-client/src/preload.js +++ b/packages/meshbay-client/src/preload.js @@ -105,6 +105,10 @@ contextBridge.exposeInMainWorld('meshbay', { node: { detect: () => ipcRenderer.invoke('node:detect'), installed: () => ipcRenderer.invoke('node:installed'), + // Whether THIS build ships its own node (Full) or not (Light) -- + // distinct from `installed`, which also counts one merely found on + // PATH. Windows only; other platforms always resolve true. + bundled: () => ipcRenderer.invoke('node:bundled'), start: (opts) => ipcRenderer.invoke('node:start', opts), call: (method, path, body) => ipcRenderer.invoke('node:call', method, path, body), pairingCode: () => ipcRenderer.invoke('node:pairing-code'), -- cgit v1.2.3