diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-06 19:27:38 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-06 19:27:38 +0200 |
| commit | 005f3cf83559eaf84fd307584477c40676be1dd3 (patch) | |
| tree | ad1668e1e99f3e0cb03f559cfdec6d27cf742d5b /packages/meshbay-hub/src/meshbay_hub/static/group-page.js | |
| parent | 36ef1b76dce56a40650f890156eb137536ef015d (diff) | |
| download | meshbay-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.js | 6 |
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 |