aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/node-page.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-07 21:25:47 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-07 22:22:51 +0200
commite833fe1bfc8eb6f66cc5dc53997cc4158bab583f (patch)
tree6874dfd09cb210eaf61e4f5761d0bad23c9cd0a3 /packages/meshbay-hub/src/meshbay_hub/static/node-page.js
parent92e25ffcc5edf5d1a9996bfb921b5a95b826134b (diff)
downloadmeshbay-e833fe1bfc8eb6f66cc5dc53997cc4158bab583f.tar.gz
fix: set the Windows node up at sign-in, and stop it for real
Found by the first Windows beta tester, then reproduced on a clean install. After a service-mode install nothing set the node up for the account that signed in: the boot task started a node that quit ("hub.username not set"), and the sidebar showed Node / Create group only once the hub held a node key. The only way to the wizard that provisions was the home page's welcome card, which an account already in a group never sees. The way out was `meshbay-node init` and the key pasted on the profile page -- which is also what PACKAGING-GUIDE.md told people to do. - main.js `node:ensure`, called by app.js at sign-in: provisions, starts and links the node this build ships (Windows, bundled node only). A node set up for another account, or an account linked to another node, is left alone. node:start waits for it, so the two never race. - The sidebar shows the Node section when a node exists on this machine. - The Node page's status is the node's: its control API and the process list, not the service task's state (a node started from a terminal ran while the page said Stopped). Stop says Stopped only once no meshbay-node.exe is left, and stays offered for a process that answers nothing. - CLI stop kills the pid that answered when a graceful stop does not finish, and fails with the reason when a node process is still there. - The daemon ends its process 3s after _shutdown(): Python's exit waited for a busy indexer thread, with the control API already closed. Armed by main() only, never by a daemon run inside a test. - node.toml is read as utf-8-sig (PowerShell 5.1 writes a BOM), and a config that cannot be read is logged instead of dying silently in service mode. - "Pair this browser" queues the code for the next group of this node to open instead of saying "Paired successfully"; no banner before a group. - test_e2e_windows_app.py (opt-in, MESHBAY_WIN_E2E=1) drives the installed app against a throwaway hub: fresh account to linked node, Stop, Start, Restart, checked against the real processes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/node-page.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/node-page.js23
1 files changed, 15 insertions, 8 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/node-page.js b/packages/meshbay-hub/src/meshbay_hub/static/node-page.js
index 9d230c1..04f4abc 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/node-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/node-page.js
@@ -6,7 +6,7 @@ import { t } from './i18n.js';
import { ask } from './ask.js';
import * as platform from './platform.js';
import { Icon } from './icon.js';
-import { HUB } from './hub-client.js';
+import { HUB, session } from './hub-client.js';
// ── Node management (D5) ────────────────────────────────────────────────────
//
@@ -108,6 +108,9 @@ function NodeServicePanel({ onChanged, token, username }) {
const KNOWN_STATES = ['active', 'inactive', 'failed', 'activating', 'deactivating'];
const stateKey = KNOWN_STATES.includes(info.activeState) ? info.activeState : 'unknown';
const running = info.activeState === 'active' || info.activeState === 'activating';
+ // A node process that does not answer ('unknown': on its way up or down, or
+ // stuck) is still one to stop -- only "nothing there" takes Stop away.
+ const stoppable = info.activeState !== 'inactive';
const dot = info.activeState === 'active' ? 'online'
: info.activeState === 'failed' ? 'offline' : 'unknown';
const label = info.installed ? t('node.service_state_' + stateKey)
@@ -132,7 +135,7 @@ function NodeServicePanel({ onChanged, token, username }) {
onClick=${() => act('start', () => platform.node.start({ hubUrl: HUB, username, token }))}>
${busy === 'start' ? t('node.service_starting') : t('node.service_start')}</button>
${info.installed && html`
- <button class="btn btn-small btn-secondary" disabled=${!!busy || !running}
+ <button class="btn btn-small btn-secondary" disabled=${!!busy || !stoppable}
onClick=${() => act('stop', () => platform.node.service.stop())}>
${busy === 'stop' ? t('node.service_stopping') : t('node.service_stop')}</button>
<button class="btn btn-small btn-secondary" disabled=${!!busy}
@@ -520,13 +523,17 @@ export function NodePage({ groups, token, username }) {
setPairBusy(true);
setPairStatus('');
try {
+ // An operator code is redeemed over MNP, with the account's identity in
+ // a group -- this page has no transport. It used to store the code where
+ // nothing read it and say "Paired successfully", and the banner came back
+ // on the next refresh. Queued the way the Create Group wizard queues it:
+ // the next group of this node to open redeems it.
const result = await nodeOp('pairOperator');
if (result && result.code) {
await platform.node.setPairingCode(result.code);
+ session.pendingJoinCode = result.code;
}
- setPairStatus('paired');
- setOperatorPaired(true);
- await refresh();
+ setPairStatus('pending');
} catch (err) {
setPairStatus(platform.bridgeMessage(err));
} finally {
@@ -712,15 +719,15 @@ export function NodePage({ groups, token, username }) {
</div>
<${NodeServicePanel} onChanged=${fetchStatus} token=${token} username=${username} />
${actionMsg && html`<div class="node-message">${actionMsg}</div>`}
- ${operatorPaired === false && html`
+ ${operatorPaired === false && nodeGroups.length > 0 && html`
<div class="node-pair-banner">
<p>${t('node.pair_needed')}</p>
<button class="btn btn-primary btn-small"
disabled=${pairBusy}
onClick=${doPairOperator}>
${pairBusy ? t('node.settings_saving') : t('node.pair_button')}</button>
- ${pairStatus === 'paired'
- ? html`<p class="success-msg">${t('node.pair_success')}</p>`
+ ${pairStatus === 'pending'
+ ? html`<p class="success-msg">${t('node.pair_pending')}</p>`
: pairStatus ? html`<p class="error-msg">${pairStatus}</p>` : null}
</div>
`}