From d6e1cc19a09df35988f6a90c226c94f2fdf6b209 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 6 Sep 2026 22:20:46 +0200 Subject: fix(node): `root list` printed "?" for every path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `RootSet.describe()` feeds two audiences that want opposite things. The index payload goes to every member and has always deliberately carried no paths — a member is told what exists and whether it is readable, not that the library sits under someone's home directory. The loopback API answers the operator themselves, over a channel that already requires their machine and the run token, and the path is exactly what they asked for. The `root list` CLI I added in Phase 1 read `path` from the member form, so it printed a placeholder for every directory. Nothing caught it: the CLI reads a dict, the API returns a dict, and neither end states which keys it owes. `describe(with_paths=True)` is the operator's view and `list_groups` is the only caller. The shared-directories table has the same hole over MNP — the roots there come from the index payload — so its Path column now appears only when a path is actually present, rather than rendering a column of blanks. The test asserts both halves, because they pull opposite ways: one that only checked the operator sees paths would be satisfied by leaking them to every member. It reads the indexer's source for the member side, and compares the CLI's key reads against what the payload offers for the other — checked to fail in each direction independently. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011pvMdvLBG92jyhvD5pD6us --- .../meshbay-hub/src/meshbay_hub/static/group-settings.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'packages/meshbay-hub/src') 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 f8b4aa5..ce36a40 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -88,6 +88,14 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, const displayRoots = serverRoots.map(r => optimistic[r.name] ? { ...r, ...optimistic[r.name] } : r); + // Paths are the operator's own view and do not cross MNP: the roots table + // rides in the index payload, which every member receives, and it tells them + // what exists and whether it is readable — never where on the operator's + // disk it lives. So the column appears when the answer is actually + // available (the loopback API, which is already this machine only) and is + // left out otherwise, rather than printing a row of blanks. + const hasPaths = displayRoots.some((r) => r.path); + // Which door a change goes through. MNP first: it is the only one that // exists for an operator on the web, and it is signed, which the loopback // API is not (it is authorized by being on localhost with the run token). @@ -261,7 +269,7 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, ${t('node.directory')} - ${t('node.root_path')} + ${hasPaths && html`${t('node.root_path')}`} ${canEdit && html`${t('node.root_rw')}`} ${canEdit && !isLocal && html`${t('node.removable')}`} @@ -283,10 +291,8 @@ function SharedDirectoriesTable({ roots, groupId, transport, signFn, ${!isLocal && r.available === false && !r.ejected && html` ${t('node.unavailable')}`} - ${/* Two roots can never share a name, so the name is the identity — - but it is the *basename*, and two libraries under different - parents look identical without this. */''} - ${r.path || ''} + ${hasPaths && html` + ${r.path || ''}`} ${canEdit && html` <${ToggleSwitch} checked=${!!r.writable} disabled=${busy || !!r.ejected} -- cgit v1.2.3