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-settings.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-settings.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/group-settings.js | 24 |
1 files changed, 21 insertions, 3 deletions
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 5d0ab07..f8b4aa5 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -361,7 +361,8 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, enabledApps, onEnabledApps, scanSettings, onScanSettings, entries, nodeDirs, - appSettings, onAppDirectories, onRefreshIndex, + appSettings, nodeSupportsAppOps, + onAppDirectories, onRefreshIndex, onPaired, onLeft }) { const [members, setMembers] = useState([]); const [adminId, setAdminId] = useState(''); @@ -647,7 +648,14 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, if (!transport || !transport.connected) { throw new Error(t('node.root_no_route')); } - await transport.setAppDirectories(appKey, paths, adminSignFn); + // A node too old for the generic op still answers the three per-app + // messages that came before it, so an operator on one keeps the ability + // they had rather than being handed a control that times out. + if (transport.supportsAppOps) { + await transport.setAppDirectories(appKey, paths, adminSignFn); + } else { + await transport.setAppDirectoriesLegacy(appKey, paths, adminSignFn); + } if (onAppDirectories) onAppDirectories(appKey, paths); }, [transportRef, adminSignFn, onAppDirectories]); @@ -845,11 +853,19 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, <p class="settings-hint">${t('settings_node.shared_directories_hint')}</p> ${!connected && nodeDetected && html` <p class="settings-hint">${t('settings_node.roots_offline_hint')}</p>`} + ${/* Read-only against a node that predates the root operations: + writable, removable, eject and plug have no older equivalent + to fall back to, and an unknown message type is dropped + unanswered — a thirty-second wait ending in a timeout, with + nothing on screen to say the node simply cannot do it. */''} + ${connected && !nodeSupportsAppOps && !nodeDetected && html` + <p class="settings-hint">${t('settings_node.roots_node_too_old')}</p>`} <${SharedDirectoriesTable} roots=${effectiveRoots} groupId=${groupId} transport=${transportRef.current} signFn=${adminSignFn} + readOnly=${connected && !nodeSupportsAppOps && !nodeDetected} nodeDetected=${nodeDetected} onRootsChange=${loadNodeInfo} onRefreshIndex=${onRefreshIndex} /> @@ -876,7 +892,9 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, disabled=${appsBusy} onChange=${() => toggleApp(app.key)} /> `}> - ${activeApps.includes(app.key) + ${!nodeSupportsAppOps && app.key === 'chat' + ? html`<p class="settings-hint">${t('settings_node.app_node_too_old')}</p>` + : activeApps.includes(app.key) ? html`<${app.Settings} roots=${effectiveRoots} dirs=${folderOptions} settings=${appSettings} |