From 005f3cf83559eaf84fd307584477c40676be1dd3 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 6 Sep 2026 19:27:38 +0200 Subject: fix(client): degrade against a node still speaking MNP 1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_011pvMdvLBG92jyhvD5pD6us --- packages/meshbay-hub/src/meshbay_hub/static/group-page.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-page.js') 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 -- cgit v1.2.3