aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-06 19:27:38 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-06 19:27:38 +0200
commit005f3cf83559eaf84fd307584477c40676be1dd3 (patch)
treead1668e1e99f3e0cb03f559cfdec6d27cf742d5b /packages/meshbay-hub/src/meshbay_hub/static/group-page.js
parent36ef1b76dce56a40650f890156eb137536ef015d (diff)
downloadmeshbay-005f3cf83559eaf84fd307584477c40676be1dd3.tar.gz
fix(client): degrade against a node still speaking MNP 1.0
The SPA is served by the hub, so deploying the hub puts this client in front of every node — including the ones not updated yet. That window is the normal state for as long as an operator takes, and for a node someone else runs it may be indefinite. Three controls were broken across it, and the failure mode is quiet: an unknown message type is logged by the node and never answered, so the click produces a thirty-second wait ending in a timeout with nothing on screen to say the node simply cannot do this. Files' Upload button read `root.writable`, which a 1.0 node does not send — it says `upload`, the same answer under the older name. The button disappeared on every un-upgraded node. It reads both now, and still respects an explicit `writable: false` rather than falling through to the legacy flag. The per-app folder pickers spoke `app_directories`. Videos, Music and Photos each had their own message before that and those still work, so the page chooses by version: an operator on an older node keeps the ability they had. `video_root` and `audio_root` hold one folder, so several are refused with a reason rather than stored as the first and silently truncated. Root management — writable, removable, eject, plug — has no older equivalent to route to, so the table goes read-only with a line saying why and pointing at the `meshbay-node root` commands. Chat's two settings are new with nothing before them and are hidden the same way. None of this was inferred from a payload's shape: `_checkNodeVersion` already parsed the node's version and threw it away, and it is kept now. Coupling a capability to whether some field happens to be present is how a flag flips because an unrelated payload changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pvMdvLBG92jyhvD5pD6us
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-page.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-page.js6
1 files changed, 6 insertions, 0 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 e762fc8..513792b 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
@@ -140,6 +140,10 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// destination rather than a set of folders it reads.
const [chatDirectory, setChatDirectory] = useState('');
const [chatLinkPreview, setChatLinkPreview] = useState(true);
+ // Whether this node speaks the operations MNP 1.1 added. False for one that
+ // predates them, and the Settings page then offers what that node can
+ // actually do rather than controls whose messages it drops unanswered.
+ const [nodeSupportsAppOps, setNodeSupportsAppOps] = useState(false);
// MusicBrainz on/off (per-group) — docs/musicbay.md §3.2.
const [musicbrainzConfig, setMusicbrainzConfig] = useState(null);
const onPlayQueue = useCallback((tracks, startIndex) => {
@@ -342,6 +346,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
|| (ack.audio_root ? [ack.audio_root] : []),
photo: ack.photo_directories || ack.photo_roots || [],
});
+ setNodeSupportsAppOps(transport.supportsAppOps);
setChatDirectory(ack.chat_directory || '');
setChatLinkPreview(ack.chat_link_preview !== false);
setMusicbrainzConfig({
@@ -790,6 +795,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
onScanSettings=${(s) => setScanSettings(s)}
entries=${entries} nodeDirs=${nodeDirs}
appSettings=${appSettings}
+ nodeSupportsAppOps=${nodeSupportsAppOps}
${/* The saving pane already knows what it asked for; this is so
the page's own copy moves at the same time, rather than
waiting for the ack it will not be handed (transport.js