summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-25 00:29:03 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-25 00:29:03 +0200
commit06c101154d544290d27f18d6fc08fb5f58a4e5d5 (patch)
treebbcb6b863bdc7b722e5605bf3ff975cdfa022ee0
parenta41be5c5d6212f4eadc54bc17354311b0655a5a4 (diff)
downloadmeshbay-06c101154d544290d27f18d6fc08fb5f58a4e5d5.tar.gz
fix(hub): poll for a root count change instead of trusting one fetch
Point 1 (directory list not updating without a full page reload) turned out to still reproduce after the earlier onRefreshIndex fix — that one addressed the Videos/Music root pickers (nodeDirs), but this section's own list reads a different field entirely (ops.list_groups returns the *runtime* root set, groups_ctx[gid]["roots"]) that only gets replaced once _reload_config_inner's retarget actually finishes. /api/reload itself is fire-and-forget on the node by design (ops.start_reload's own docstring — a brand-new group's initial scan can take minutes, the caller must not block on it), so a single loadNodeInfo() call right after can land in the gap before that replacement happens and show the pre-change count. Both the add and remove handlers now poll /api/groups (up to ~4s, every 400ms) until the root count actually matches what the action should have produced, rather than fetching once and hoping the timing worked out.
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-settings.js36
1 files changed, 34 insertions, 2 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
index 34f94f9..a4db4cb 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
@@ -157,6 +157,36 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
}, [groupId]);
useEffect(() => { loadNodeInfo(); }, [loadNodeInfo]);
+
+ /**
+ * Poll /api/groups until the root count actually matches what an
+ * add/remove just did, instead of trusting a single loadNodeInfo() call
+ * right after /api/reload. Found live: /api/reload is fire-and-forget on
+ * the node (ops.start_reload schedules the real work and returns
+ * immediately, deliberately — a brand-new group's initial scan can take
+ * minutes, see its own docstring) — and the list this section renders
+ * (ops.list_groups) reads the *runtime* root set
+ * (groups_ctx[gid]["roots"]), which only gets replaced once
+ * _reload_config_inner's retarget actually finishes, not the config-file
+ * list add_root/remove_root already updated synchronously. A single
+ * fetch right after can land in that gap and show the old count.
+ */
+ const waitForRootCount = useCallback(async (expectedCount) => {
+ for (let i = 0; i < 10; i++) {
+ try {
+ const data = await platform.node.call('GET', '/api/groups');
+ const ng = (data.groups || []).find(g => g.id === groupId);
+ const roots = (ng && ng.roots) || [];
+ if (roots.length === expectedCount) {
+ setNodeRoots(roots);
+ if (ng) setNodeGroupName(ng.name || '');
+ return true;
+ }
+ } catch { /* keep trying — the node may be mid-reload */ }
+ await new Promise(r => setTimeout(r, 400));
+ }
+ return false;
+ }, [groupId]);
const [inviteCode, setInviteCode] = useState(null);
const [pairCode, setPairCode] = useState('');
const [pairStatus, setPairStatus] = useState('');
@@ -976,13 +1006,14 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
disabled=${nodeBusy}
onClick=${async () => {
if (!confirm(t('node.root_remove_confirm', { name: r.name }))) return;
+ const countBefore = nodeRoots.length;
setNodeBusy(true); setNodeMsg('');
try {
await platform.node.call('DELETE',
'/api/groups/' + groupId + '/roots/' + encodeURIComponent(r.name));
await platform.node.call('POST', '/api/reload');
setNodeMsg(t('node.root_removed'));
- await loadNodeInfo();
+ await waitForRootCount(countBefore - 1);
// Folders (unlike files) only ever arrive via a full
// index_sync, never index_delta (daemon.py's ongoing
// push has no `dirs` field) — without this, the
@@ -1000,6 +1031,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
onClick=${async () => {
const chosen = await platform.rootPicker.choose();
if (!chosen) return;
+ const countBefore = nodeRoots.length;
setNodeBusy(true); setNodeMsg(''); setNodeIndexProgress(null);
try {
await platform.node.call('POST',
@@ -1013,7 +1045,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
// an unchanged screen while it happens.
await platform.watchIndexProgress(groupId, setNodeIndexProgress);
setNodeMsg(t('node.root_added'));
- await loadNodeInfo();
+ await waitForRootCount(countBefore + 1);
// See the matching comment on root removal above — a new
// folder needs a full index_sync to show up anywhere that
// reads `nodeDirs` (the Videos/Music root pickers), not