diff options
Diffstat (limited to 'packages/meshbay-hub')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/group-page.js | 29 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_app_settings_plugin.py | 25 |
2 files changed, 36 insertions, 18 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 fec685b..c59d097 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js @@ -350,15 +350,18 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, tokenCustomized: !!ack.tmdb_token_customized, language: ack.tmdb_language || '', }); - // The plural form when the node speaks it, the old scalars when it - // does not — an MNP 1.0 node sends only the latter, and reading its - // missing `video_directories` as "nothing configured" would empty a - // working Videos tab. - setAppDirectories({ - video: ack.video_directories || [], - music: ack.music_directories || [], - photo: ack.photo_directories || [], - }); + // Every `<app>_directories` the ack carries, keyed by the app's own + // name — read off the ack rather than from a list of app names held + // here, so an application the node knows about is one this page already + // handles. Three names were hardcoded until 2026-09-10 and `helloworld` + // was not among them, so the app that exists to prove a new one needs + // no special-casing had its directories dropped on arrival. The live + // path below (`onAppDirectories`) was always generic; this was the half + // that was not. + setAppDirectories(Object.fromEntries( + Object.keys(ack) + .filter((k) => k.endsWith('_directories')) + .map((k) => [k.slice(0, -'_directories'.length), ack[k] || []]))); setChatDirectory(ack.chat_directory || ''); setChatLinkPreview(ack.chat_link_preview !== false); setMusicbrainzConfig({ @@ -677,9 +680,11 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, setEntries, setNodeDirs, setNodeRoots, applyIndex, isNodeAdmin, operatorPaired, attachRoot, attachDir, userId, setError, onPreview, onRefreshIndex: refreshIndex, onActivity: touchActivity, - // Plural everywhere: Videos and Music read a list now, and Photos always - // did. The scalar `videoRoot`/`audioRoot` shapes survive only on the wire, - // for a node that speaks MNP 1.0 — nothing in the client carries them. + // Plural everywhere, and built from the registry rather than a list of app + // names kept here: Videos and Music read a list, Photos always did, and an + // application added to `APPS` gets its own entry without this file + // changing. The scalar `videoRoot`/`audioRoot` shapes are gone from the + // wire too, so nothing anywhere carries them. ...perAppDirectories, tmdbConfig, musicbrainzConfig, onPlayQueue, diff --git a/packages/meshbay-hub/tests/test_app_settings_plugin.py b/packages/meshbay-hub/tests/test_app_settings_plugin.py index af86f12..940d975 100644 --- a/packages/meshbay-hub/tests/test_app_settings_plugin.py +++ b/packages/meshbay-hub/tests/test_app_settings_plugin.py @@ -256,15 +256,28 @@ def test_each_app_takes_a_list_of_directories(app, prop): def test_the_lists_are_read_under_one_name_each(): """ - One name per app on the ack — `<app>_directories`, always a list. A second - name for the same answer means a page that reads whichever it thinks of - first, and a node that fills only the other one empties a working tab. + One name per app on the ack — `<app>_directories`, always a list — and the + shell names none of them. + + It read three by hand until 2026-09-10, and `helloworld` was not among them: + the app that exists to prove a new one needs no special-casing had its + directories dropped on arrival, which made the plugin claim false exactly + where it is demonstrated. An app the node knows about must be one this page + already handles, and the only way to guarantee that is to read the ack's own + keys instead of a list kept here. + + A second *name* for the same answer would be the older fault: a page reading + whichever it thinks of first, and a node filling only the other one emptying + a working tab. """ page = GROUP_PAGE.read_text(encoding="utf-8") - block = page[page.index("setAppDirectories({"):] + block = page[page.index("setAppDirectories("):] block = block[:block.index("setChatDirectory")] - assert "ack.video_directories" in block and "ack.music_directories" in block - assert "ack.photo_directories" in block + assert "endsWith('_directories')" in block, ( + "the shell must derive the map from the ack's keys, not name apps") + for named in ("ack.video_directories", "ack.music_directories", + "ack.photo_directories"): + assert named not in block, f"{named} is an application named by the shell" for gone in ("ack.video_root", "ack.audio_root", "ack.photo_roots"): assert gone not in block, f"{gone} is a second name for the same answer" |