diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-24 17:54:11 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-24 17:54:11 +0200 |
| commit | c2f5e0ed7ff22e2e176686d0074b027712e9efa3 (patch) | |
| tree | 09813ffc5498e65f4e6fe113ea58357006f66530 /packages/meshbay-hub/src | |
| parent | 8c780b9928db8145b7fe18ecd137384ccfe25d8f (diff) | |
| download | meshbay-c2f5e0ed7ff22e2e176686d0074b027712e9efa3.tar.gz | |
fix(hub): Create Group wizard steps app selection before the scan wait
"Choosing applications" 404ed with "Group not hosted on this node" on
any real (multi-thousand-file) library — found live creating a group
against a 5787-file MP3 collection while testing the Music app.
daemon.py registers a brand-new group in groups_ctx only once its
initial scan finishes (ui/app.py's index-status docstring already
says so); nothing group-scoped can succeed before that, however many
times it's retried. The wizard's apps step ran *before*
platform.waitForGroupHosted() (which correctly waits up to 30 minutes
for exactly this), protected only by a 5×400ms withRetry meant for a
sub-second race — nowhere close to covering a real scan. The stated
reason for running it early (so a mid-scan joiner never sees a
not-yet-disabled app) doesn't hold either: nobody can join before the
group is hosted, same gate.
Fix: move the apps step after waitForGroupHosted, matching where the
roots/GEK steps already correctly run. No node-side change — the
scan-then-register ordering in daemon.py is intentional and untouched.
npm run sync-ui re-run to propagate to the Electron client. Hub suite:
429 passed, no regressions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KBi7ALLGfwcjBXt57yNMcy
Diffstat (limited to 'packages/meshbay-hub/src')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 46 |
1 files changed, 27 insertions, 19 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index f9bcd43..1530ff5 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -925,12 +925,12 @@ function CreateGroupWizard({ token, username, onCreated }) { { label: t('wizard.step_create_hub'), status: 'pending' }, { 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. + // 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' }); - steps.push({ label: t('wizard.step_index'), 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' }); @@ -994,11 +994,31 @@ function CreateGroupWizard({ token, username, onCreated }) { update('done'); advance(); - // 3. Narrow the enabled apps down, if the operator unchecked any — - // before the scan below, so a member who joins while it is still - // running never briefly sees an app meant to be off. Same - // not-hosted-yet race as steps 4+ below: the group is attached, but - // may not have reached groups_ctx yet. + // 3. Wait for the node's own initial scan of this group to finish — + // the group is not usable for anything below (apps, extra roots, GEK) + // until this finishes: daemon.py registers a brand-new group in + // groups_ctx only once its initial scan completes (ui/app.py's + // index-status docstring — "it is not yet authorized for member + // connections either way"), so nothing scoped to the group can + // succeed before this, no matter how many times it's retried. Can + // take tens of minutes on a slow disk with a large library — the node + // keeps scanning on its own either way (test_hot_reload_survives_ + // client_close.py); this step is only about not lying about it. + update('running'); + await platform.waitForGroupHosted(gid, setIndexProgress); + update('done'); + advance(); + + // 4. Narrow the enabled apps down, if the operator unchecked any. + // 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 + // authorization gate the comment above names), so that concern never + // applied, and placing it here means the retry below is defensive + // rather than the only thing standing between this step and an + // 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( @@ -1007,18 +1027,6 @@ function CreateGroupWizard({ token, username, onCreated }) { advance(); } - // 4. Wait for the node's own initial scan of this group to finish — - // the group is not usable for anything below (extra roots, GEK) until - // this finishes, so nobody lands on a page that looks broken, or hits - // a "not configured" error from racing ahead of it. Can take tens of - // minutes on a slow disk with a large library — the node - // keeps scanning on its own either way (test_hot_reload_survives_ - // client_close.py); this step is only about not lying about it. - update('running'); - await platform.waitForGroupHosted(gid, setIndexProgress); - update('done'); - advance(); - // 5. Add extra roots (if >1) if (roots.length > 1) { update('running'); |