summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-06 23:01:36 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-06 23:01:36 +0200
commit13ccc4a16c604f46ed10b6c5dbfbc8fdd7d008c0 (patch)
treeecf819878b8f17596af5533e6845028680f1815d /packages/meshbay-hub/src
parentd6e1cc19a09df35988f6a90c226c94f2fdf6b209 (diff)
downloadmeshbay-13ccc4a16c604f46ed10b6c5dbfbc8fdd7d008c0.tar.gz
fix: a root change reaches every client without a page reload
The directory table travelled on `index_sync` alone — a *full* index, which the node only ever sends on request. Every ongoing change went out as an `index_delta`, which carried files and nothing else. So the message that says "something changed" was the one message that could not say a root had. The acks hid it: `root_add_ack` and friends broadcast the new table to whoever is connected, so the common cases looked right. What that could not cover was the operator's own client, where the ack landed and was then overwritten — the table calls `onRefreshIndex` after an add, that fetch returns the set from *before* the node's reload (fire-and-forget, because a rescan is minutes on a real library), and `applyIndex` writes it over what the ack had just delivered. The new directory appeared for one paint and vanished. Two halves. `index_delta` now carries the roots table, sealed with the rest and identical to `index_sync`'s — additive, so a 1.0 client sees a field it does not read. And the table no longer refreshes the index after a root change: the ack gives it the new set immediately, and the delta the node pushes when the scan finishes gives it again, along with the files. The test that pins it uses an *eject* as its case, because an eject changes no file at all — the entries freeze — so its delta is empty of additions, deletions and updates. Without the table it says literally nothing, which is how a library disappearing from under a group went unannounced to everyone but whoever pressed the button. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pvMdvLBG92jyhvD5pD6us
Diffstat (limited to 'packages/meshbay-hub/src')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-page.js7
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-settings.js24
2 files changed, 25 insertions, 6 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
index a1e6411..0a43724 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
@@ -244,6 +244,13 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// enrichment (duration/thumb_hash/display_title/...) arriving for a file
// already in the table — same id, new fields (see group_index.py diff()).
const applyIndexDelta = useCallback((deltaMsg) => {
+ // The roots table rides on the delta as of MNP 1.1. Before that it
+ // travelled only on a full index_sync, which is sent on request — so a
+ // root added, removed, ejected or plugged by anyone left every other
+ // client's directory table stale until they reloaded the page.
+ if (Array.isArray(deltaMsg.roots) && deltaMsg.roots.length) {
+ setNodeRoots(deltaMsg.roots);
+ }
setEntries((prev) => {
const deletions = new Set(deltaMsg.deletions || []);
const kept = prev.filter((e) => !deletions.has(e.id));
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 ce36a40..9646405 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
@@ -42,7 +42,8 @@ import * as platform from './platform.js';
* nodeDetected — whether the loopback node API answers
* readOnly — suppress every edit control
* onRootsChange — called after a change, to re-read the loopback list
- * onRefreshIndex — full index refresh, needed after an add or a remove
+ * onRefreshIndex — full index refresh. Not called after a root change: see
+ * `run()` for why the node's own push is what settles it
* mode — "live" (default) or "local"
* localRoots / onLocalRootsChange — the array, in "local" mode
*/
@@ -106,18 +107,29 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
const rootUrl = (name, suffix = '') =>
'/api/groups/' + groupId + '/roots/' + encodeURIComponent(name) + suffix;
- const run = useCallback(async (work, { refreshIndex = false } = {}) => {
+ // Deliberately no index refresh after a root change.
+ //
+ // Adding a root makes the node reload, which rescans — minutes on a real
+ // library — and the reload is fire-and-forget for that reason. Fetching the
+ // index in the moment after therefore returns the set from *before* it, and
+ // `applyIndex` writes that over the roots the ack had just delivered: the
+ // new directory appeared for one paint and vanished, which is what "it only
+ // shows up after a refresh" was.
+ //
+ // Nothing is lost by waiting. The ack carries the new table immediately, and
+ // the delta the node pushes when the scan finishes carries it again along
+ // with the files.
+ const run = useCallback(async (work) => {
setBusy(true); setMsg('');
try {
await work();
if (onRootsChange) await onRootsChange();
- if (refreshIndex && onRefreshIndex) await onRefreshIndex();
return true;
} catch (err) {
setMsg(platform.bridgeMessage(err));
return false;
} finally { setBusy(false); }
- }, [onRootsChange, onRefreshIndex]);
+ }, [onRootsChange]);
const doUpdateRoot = useCallback(async (rootName, updates) => {
if (isLocal) {
@@ -171,7 +183,7 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
await platform.node.call('DELETE', rootUrl(rootName));
await platform.node.call('POST', '/api/reload');
} else throw new Error(t('node.root_no_route'));
- }, { refreshIndex: true });
+ });
if (ok) setMsg(t('node.root_removed'));
}, [isLocal, localRoots, onLocalRootsChange, overMnp, overLoopback,
transport, groupId, signFn, run]);
@@ -203,7 +215,7 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn,
await platform.node.call('POST', '/api/reload');
await platform.watchIndexProgress(groupId, setIndexProgress);
} else throw new Error(t('node.root_no_route'));
- }, { refreshIndex: true });
+ });
}, [isLocal, localRoots, onLocalRootsChange, overMnp, overLoopback,
transport, groupId, signFn, run]);