* fix(plugins): don't treat transient absence from /api/plugins as uninstall
The backend clears its plugin registry at the start of load_plugins()
and repopulates it incrementally while HTTP stays up, so every backend
restart (desktop: Audio Quality soundfont switch, LAN toggle, update
restart) serves a window of partial — even empty — /api/plugins
responses. loadPlugins() treated absence from the current response as
an uninstall, with three destructive consequences for still-loaded
plugins:
1. Their settings-panel and screen DOM were wiped while their
_loadedPluginScripts entry survived, so the NEXT refetch failed the
DOM-existence check and re-evaluated the plugin's screen.js
mid-session. For the desktop audio_engine plugin that re-ran init()
against the surviving native audio chain and exactly duplicated
every VST/NAM/IR stage (the alpha testers' "chain duplicates after
leaving the Audio menu" / blown-out gain reports).
2. _reconcilePluginStyles dropped their stylesheet, leaving them
visible but unstyled until they reappeared.
3. The stale-contribution sweep unmounted their UI contributions and
unregistered their capability participant with no re-registration
path (plugin scripts don't re-run thanks to the loadedScripts
guard).
Absence is now a non-signal everywhere in loadPlugins: the DOM wipe and
style reconcile are scoped to plugins the response actually names, and
the absence sweep is removed. Present plugins still fully re-sync via
_registerLegacyPluginUiContributions each round; failed plugins are
present in the response and still cleaned up; nav is rebuilt from the
response so genuinely uninstalled plugins drop out of it, and their
(un-unloadable) already-evaluated scripts keep their DOM until reload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: update idempotence contract to the absence-is-not-uninstall invariant
The removed-plugin sweep contract pinned the old behavior this branch
deletes; pin the new invariant instead (no absence sweep + respondedIds
scoping on the DOM/style reconcilers).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>