From 3c44f55f6b0aba77c7ad57d0a5ebe3e55b409473 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Tue, 25 Aug 2026 18:18:48 +0200 Subject: fix(hub,node): Create Group wizard silently skipped apps, and lost track of scanning progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two real-world bugs found together while testing multi-root group creation: - CreateGroupWizard only sent the enabled-apps PUT when the operator had *unchecked* something, assuming "every box left checked" already matched the node's own default (Roster.DEFAULT_APPS = chat, files). It doesn't — so leaving every app checked, the common case, silently left Videos/Music/Photos disabled on the node. Now sent unconditionally. - The wizard's "add extra roots" step never polled index-status, so once step 3 (which only watches the first/upload root) finished, the progress bar froze while the node kept scanning the remaining roots for minutes, unwatched. Added waitForRootsIndexed (platform.js), mirroring waitForGroupHosted's own race handling. That fix exposed a deeper one: indexer.py's _scan_root() only flipped `progress.scanning` on *after* walking the directory and stat()-ing every file — both off-loop, but slow enough on a large root that a poller's grace period (waitForRootsIndexed's 5s) could expire before ever observing `scanning: true` (confirmed against production logs: a GEK-init step fired 5.058s after a root started scanning, matching the grace period almost exactly). The stat() pass was also a synchronous loop directly on the asyncio event loop — blocking the whole daemon (WebRTC, chat, admin UI) for as long as it took on a root with many files. Both fixed: `scanning` now flips on before the walk starts, and stat()-ing is now off-loop too (_size_files). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_013XSohfUQQiaE77qyFLgSv3 --- packages/meshbay-hub/src/meshbay_hub/static/app.js | 37 ++++++++++++++-------- 1 file changed, 24 insertions(+), 13 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 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(); } -- cgit v1.2.3