diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-25 18:18:54 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-25 18:18:54 +0200 |
| commit | 2af320ba4da49547176ef7e4c081956c33841958 (patch) | |
| tree | 6b79cb92c785378b855da3862a3bf371abe701bc /packages/meshbay-hub/src | |
| parent | bbc76ad4da3ca61f820b9605ec9228c7a9357352 (diff) | |
| parent | 3c44f55f6b0aba77c7ad57d0a5ebe3e55b409473 (diff) | |
| download | meshbay-2af320ba4da49547176ef7e4c081956c33841958.tar.gz | |
Merge branch 'fix/wizard-apps-and-progress'
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013XSohfUQQiaE77qyFLgSv3
Diffstat (limited to 'packages/meshbay-hub/src')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 37 | ||||
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/platform.js | 40 |
2 files changed, 64 insertions, 13 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 1530ff5..5ec1ac8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -926,11 +926,15 @@ function CreateGroupWizard({ token, username, onCreated }) { { label: t('wizard.step_attach'), status: 'pending' }, ]; steps.push({ label: t('wizard.step_index'), status: 'pending' }); - // Only a step at all when it does something — the common case (every - // app left on, the default) has nothing to set and no reason to show a - // step for it. After indexing (see the execution order below for why). - if (enabledApps.length < APPS.length) - steps.push({ label: t('wizard.step_apps'), status: 'pending' }); + // Always a step: the node's own default for a brand-new group is + // `chat, files` only (Roster.DEFAULT_APPS) — narrower than "every app + // checked" here, which is this wizard's own default. Skipping this call + // whenever nothing was *unchecked* used to assume those two defaults + // agreed; they don't, so leaving every box checked — the common, + // recommended case — silently left Videos/Music/Photos disabled on the + // node (found live 2026-08-25: no `set_enabled_apps`/"Enabled apps for + // group" ever logged for a group created with every app left on). + steps.push({ label: t('wizard.step_apps'), status: 'pending' }); if (roots.length > 1) steps.push({ label: t('wizard.step_add_roots'), status: 'pending' }); steps.push({ label: t('wizard.step_gek'), status: 'pending' }); @@ -1009,7 +1013,10 @@ function CreateGroupWizard({ token, username, onCreated }) { update('done'); advance(); - // 4. Narrow the enabled apps down, if the operator unchecked any. + // 4. Set the enabled apps — unconditionally (see the step-list + // comment above on why "only if narrowed" was wrong: the node's own + // default is not "every app", so leaving every box checked must still + // be told to the node explicitly). // Used to run *before* the scan above, reasoning that a member // joining mid-scan should never briefly see an app meant to be off — // but nobody can join before the group is hosted either (same @@ -1019,13 +1026,11 @@ function CreateGroupWizard({ token, username, onCreated }) { // indefinite "Group not hosted on this node" (found live against a // real, several-thousand-file library: withRetry's five attempts // don't come close to covering a scan that takes minutes). - if (enabledApps.length < APPS.length) { - update('running'); - await withRetry(() => platform.node.call( - 'PUT', `/api/groups/${gid}/apps`, { apps: enabledApps })); - update('done'); - advance(); - } + update('running'); + await withRetry(() => platform.node.call( + 'PUT', `/api/groups/${gid}/apps`, { apps: enabledApps })); + update('done'); + advance(); // 5. Add extra roots (if >1) if (roots.length > 1) { @@ -1038,6 +1043,12 @@ function CreateGroupWizard({ token, username, onCreated }) { upload: i === uploadIdx, })); } + // Each add above only schedules its scan (platform.js's + // waitForRootsIndexed docstring) — wait for it to actually finish, + // reusing the same progress bar step 3 fed, or this step reports + // "done" while the node is still hashing gigabytes behind the + // scenes (found live 2026-08-25). + await platform.waitForRootsIndexed(gid, setIndexProgress); update('done'); advance(); } diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js index a5312e1..78e27eb 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js @@ -328,6 +328,46 @@ export async function waitForGroupHosted(groupId, onProgress, } /** + * Create Group wizard only: after adding extra roots (step 5), wait for + * whatever scanning that triggers to actually finish, showing progress along + * the way. `POST /api/groups/{id}/roots` (ui/app.py) schedules its rescan as + * a detached background task and returns as soon as the config write is + * done — "every add-root call resolved" is not "the node is done indexing". + * Found live (2026-08-25): a multi-root group's later, larger roots kept + * scanning for minutes after the wizard had already moved on to GEK init and + * pairing, with no progress shown anywhere — the disk was working, the UI + * just never asked again. + * + * Same race as waitForGroupHosted above, one level down: the very first + * poll can land in the gap between "the last add-root call returned" and + * "its background reload actually started scanning", which reads as "not + * scanning" for the wrong reason (nothing left to do) rather than the right + * one (hasn't started yet). Waits up to `graceMs` for scanning to be + * observed at least once before trusting a "not scanning" answer — after + * that, the first "not scanning" really does mean finished, because the + * node scans one root at a time (indexer.py's single-worker executor) and + * nothing here adds more roots once this call starts. + */ +export async function waitForRootsIndexed(groupId, onProgress, + { intervalMs = 500, graceMs = 5000 } = {}) { + const graceDeadline = Date.now() + graceMs; + let sawScanning = false; + for (;;) { + let status; + try { + status = await node.call('GET', `/api/groups/${groupId}/index-status`); + } catch { + return; // the node went away mid-poll — same stance as watchIndexProgress + } + if (onProgress) onProgress(status); + if (status.scanning) sawScanning = true; + if (sawScanning && !status.scanning) return; + if (!sawScanning && Date.now() > graceDeadline) return; + await new Promise((r) => setTimeout(r, intervalMs)); + } +} + +/** * LAN cast relay — re-serve decrypted video segments over HTTP so a * Chromecast or Smart TV on the same Wi-Fi can play the stream. * |