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/tests/test_app_settings_plugin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'packages/meshbay-hub/tests/test_app_settings_plugin.py') diff --git a/packages/meshbay-hub/tests/test_app_settings_plugin.py b/packages/meshbay-hub/tests/test_app_settings_plugin.py index 00a07b0..1837c28 100644 --- a/packages/meshbay-hub/tests/test_app_settings_plugin.py +++ b/packages/meshbay-hub/tests/test_app_settings_plugin.py @@ -226,9 +226,12 @@ def test_the_page_performs_exactly_one_app_specific_operation(): # The page's own settings, which belong to no app: which apps are enabled # at all, and how hard the node works watching its disk. page_level = {"setAppsEnabled", "setScanSettings"} - assert calls - page_level == {"setAppDirectories"}, ( + # Both are the same generic operation; the second is what a node too old + # for it understands, chosen by version rather than by app. + generic = {"setAppDirectories", "setAppDirectoriesLegacy"} + assert calls - page_level == generic, ( f"the settings page performs app-specific operations: " - f"{sorted(calls - page_level - {'setAppDirectories'})}") + f"{sorted(calls - page_level - generic)}") # ── The apps read a list ──────────────────────────────────────────────────── -- cgit v1.2.3