From 73490d331179cb640828b8625abb75208cd7ae5f Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 21 Aug 2026 17:50:08 +0200 Subject: feat: D.6 first-run wizard — detect, start, link, group, gek-init, pair MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Desktop client guides new users through the full onboarding sequence without a terminal: detect local node → start daemon (systemd with direct-start fallback for dev) → link node key to hub → create group → attach directories → gek-init → operator pair. - node:detect returns configured field to distinguish missing vs stopped - node:start tries systemctl first, checks is-active, falls back to spawning the binary directly when the unit crashes (dev mode) - probeNode() extracts the shared config→token→fetch pattern - SetupWelcome on empty HomePage links to /create-group - CreateGroupWizard step 0 handles node detection and start - platform.js exposes node.installed() and node.start() - 12 setup.* i18n keys added to all 10 locales - Integration test for node:start fallback logic Co-Authored-By: Claude Opus 4.6 --- packages/meshbay-hub/src/meshbay_hub/static/app.js | 82 ++++++++++++++++++---- 1 file changed, 67 insertions(+), 15 deletions(-) (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 7780d08..4b5ccd3 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -906,7 +906,13 @@ function NotificationFeed({ notifications, onMarkRead, onPurge }) { } function HomePage({ groups, notifications, onMarkRead, onPurge }) { + const [setupDismissed, setSetupDismissed] = useState(false); + if (groups.length === 0) { + if (platform.isNative && !setupDismissed) { + return html`<${SetupWelcome} + onDismiss=${() => setSetupDismissed(true)} />`; + } return html`

${t('home.welcome')}

@@ -1033,6 +1039,22 @@ function ExplorePage({ token, myGroupIds }) { `; } +// ── First-run welcome (Electron-only, shown once on empty home) ───────────── + +function SetupWelcome({ onDismiss }) { + return html`
+

${t('setup.welcome_title')}

+

+ ${t('setup.welcome_message')}

+
+ + ${t('setup.create_group')} + +
+
`; +} + // ── Create Group Page ──────────────────────────────────────────────────────── function CreateGroupPage(props) { @@ -1130,6 +1152,7 @@ function CreateGroupFormSimple({ token, onCreated }) { function CreateGroupWizard({ token, onCreated }) { const [step, setStep] = useState(0); // 0=node check, 1=details, 2=setup, 3=done const [nodeStatus, setNodeStatus] = useState(null); // null=loading, object=result + const [nodeStarting, setNodeStarting] = useState(false); const [error, setError] = useState(''); // Step 1 fields @@ -1144,30 +1167,50 @@ function CreateGroupWizard({ token, onCreated }) { const [setupError, setSetupError] = useState(''); const [groupId, setGroupId] = useState(''); + const linkNodeKey = useCallback(async (pk) => { + if (!pk) return; + try { + await hubFetch('/v1/users/me/node_key', { + method: 'PUT', token, body: { pk_node_ed25519: pk }, + }); + } catch { /* already linked or same key */ } + }, [token]); + // Step 0: detect node const detectNode = useCallback(async () => { setNodeStatus(null); setError(''); try { const result = await platform.node.detect(); - setNodeStatus(result); if (result.detected) { - // Auto-link node key to hub if not already done - if (result.pk_node_ed25519) { - try { - await hubFetch('/v1/users/me/node_key', { - method: 'PUT', token, - body: { pk_node_ed25519: result.pk_node_ed25519 }, - }); - } catch { /* already linked or same key */ } - } + await linkNodeKey(result.pk_node_ed25519); + setNodeStatus(result); setStep(1); + return; } + // Not responding — check if the unit is installed (for the Start button) + const inst = await platform.node.installed(); + setNodeStatus({ detected: false, configured: result.configured, installed: inst.installed }); } catch (err) { setError(err.message); setNodeStatus({ detected: false }); } - }, [token]); + }, [token, linkNodeKey]); + + const startNode = useCallback(async () => { + setNodeStarting(true); + setError(''); + try { + const result = await platform.node.start(); + await linkNodeKey(result.pk_node_ed25519); + setNodeStatus({ detected: true, ...result }); + setNodeStarting(false); + setStep(1); + } catch (err) { + setError(platform.bridgeMessage(err)); + setNodeStarting(false); + } + }, [linkNodeKey]); useEffect(() => { detectNode(); }, [detectNode]); @@ -1279,12 +1322,21 @@ function CreateGroupWizard({ token, onCreated }) {
`; } if (!nodeStatus.detected) { + const canStart = nodeStatus.installed || nodeStatus.configured; return html`

${t('wizard.title')}

-

${t('wizard.node_not_found')}

- ${error && html`
${error}
`} -
- `} +
`; -- cgit v1.2.3