From b7733e812fadd6007976d262bd1d793572a36ba7 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 29 Aug 2026 11:15:54 +0200 Subject: feat: node workflow redesign — wizard auto-config, reset, MusicBrainz contact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wizard (Electron): - Auto-provisions node config (hub URL + username) from logged-in user - node:start handles both cold start and restart of misconfigured daemon - Waits for daemon to reach 'running', auto-links node key on hub - probeNode accepts intermediate states for wizard progress feedback Reset (meshbay-node reset): - Unlinks node key from hub (DELETE /me/node_key, best-effort) - Stops and disables daemon (systemctl --user disable --now) - Erases ~/.config/meshbay, ~/.local/share/meshbay, ~/.local/state/meshbay MusicBrainz contact: - Resolved from owner's hub email instead of per-node roster config - Removed musicbrainz_contact UI and WebRTC handshake field - Removed set_musicbrainz_contact/musicbrainz_contact from roster Node pairing: - Added operator pairing banner on NodePage - Added operator_paired flag to list_groups Co-Authored-By: Claude Opus 4.6 --- packages/meshbay-hub/src/meshbay_hub/static/app.js | 69 +++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 50a972b..4aab150 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -1130,7 +1130,31 @@ function CreateGroupWizard({ token, username, onCreated, allowPublicGroups = tru } } - // Step 1: Group details + directories + // Node detected but not fully ready — provision config and/or link key, + // then wait for 'running' before showing the group form. + const provisionAttempted = useRef(false); + useEffect(() => { + if (step === 1 && nodeStatus && !nodeStarting + && !provisionAttempted.current + && (nodeStatus.status === 'waiting_for_account' + || nodeStatus.status === 'waiting_for_node_key' + || nodeStatus.status === 'starting')) { + provisionAttempted.current = true; + startNode(); + } + }, [step, nodeStatus, nodeStarting, startNode]); + + if (step === 1 && nodeStatus + && nodeStatus.status !== 'running' && nodeStatus.status !== undefined) { + return html`
+

${t('wizard.title')}

+

${error + ? t('wizard.wrong_account') + : t('wizard.detecting')}

+ ${error && html``} +
`; + } if (step === 1) { const canProceed = name.trim() && roots.length > 0 && enabledApps.length > 0; return html`
@@ -2352,6 +2376,10 @@ function NodePage({ token, username, userId, groups }) { const [attachGroup_, setAttachGroup_] = useState(''); const [attachDir, setAttachDir] = useState(''); const [attachUpload, setAttachUpload] = useState(''); + const [operatorPaired, setOperatorPaired] = useState(false); + const [pairCode, setPairCode] = useState(''); + const [pairStatus, setPairStatus] = useState(''); + const [pairing, setPairing] = useState(false); const transportRef = useRef(null); const connectAndFetch = useCallback(async () => { @@ -2405,6 +2433,7 @@ function NodePage({ token, username, userId, groups }) { console.log('[NodePage] got status:', result.groups?.length, 'groups'); transportRef.current = transport; setNodeGroups(result.groups || []); + setOperatorPaired(!!result.operator_paired); setStatus('connected'); return; } catch (err) { @@ -2437,6 +2466,7 @@ function NodePage({ token, username, userId, groups }) { try { const result = await transport.fetchNodeStatus(); setNodeGroups(result.groups || []); + setOperatorPaired(!!result.operator_paired); } catch {} }, []); @@ -2606,6 +2636,27 @@ function NodePage({ token, username, userId, groups }) { } }, [attachGroup_, attachDir, attachUpload, signFn]); + const doPairOperator = useCallback(async (e) => { + e.preventDefault(); + const code = pairCode.trim(); + if (!code) return; + setPairing(true); + setPairStatus(''); + try { + const transport = transportRef.current; + if (!transport || !transport.connected) throw new Error('Not connected'); + await transport.pairOperator(userId, code); + setPairCode(''); + setPairStatus('paired'); + setOperatorPaired(true); + await refresh(); + } catch (err) { + setPairStatus(err.message); + } finally { + setPairing(false); + } + }, [pairCode, userId, refresh]); + const reloadConfig = useCallback(async () => { const transport = transportRef.current; if (!transport || !transport.connected) return; @@ -2656,6 +2707,22 @@ function NodePage({ token, username, userId, groups }) {
<${NodeServicePanel} onChanged=${connectAndFetch} /> ${actionMsg && html`
${actionMsg}
`} + ${!operatorPaired && html` +
+

${t('node.pair_needed')}

+
+ setPairCode(e.target.value)} + disabled=${pairing} /> + +
+ ${pairStatus === 'paired' + ? html`

${t('node.pair_success')}

` + : pairStatus ? html`

${pairStatus}

` : null} +
+ `} ${(() => { const hubIds = new Set((groups || []).map(g => g.id)); return nodeGroups.map(g => { -- cgit v1.2.3