Honor keepLoaded for services during plugin hot-reload (#9485)

* Honor keepLoaded for services during plugin hot-reload

Plugin reload destroyed every service, including omarchy.lock, which drops the ext-session-lock client while Hyprland still holds the lock and surfaces the crashed-lockscreen fallback.

* Prove keepLoaded service survival with a fixture service

A fresh lock service also reports an empty lastEventAt, so comparing it
across the rescan passed whether or not the instance survived. A fixture
keepLoaded service whose in-memory marker is set before the rescan and
read back after can only pass when the same instance is still mounted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Drop kept services whose plugin no longer declares a service

The _syncServices cleanup only asked whether the plugin was still
installed and enabled, so a kept service whose plugin dropped its
service kind or entry point kept running as a zombie until shell
restart. Apply the same eligibility checks used at creation, and hand
kept instances the refreshed manifest after a rescan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Cover omarchy.media in keepLoaded expectations; note kept services reload on restart

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-09-02 12:33:44 +02:00
1 parent 7eca64e268
commit d3d23fddde
6 files changed
+122 -9

No files matched your search

+27 -4
View File
@@ -329,14 +329,23 @@ ShellRoot {
if (!Array.isArray(m.kinds) || m.kinds.indexOf("service") === -1) continue
if (!m.entryPoints || !m.entryPoints.service) continue
if (!pluginRegistry.isEnabled(id)) continue
if (_services[id]) continue
if (_services[id]) {
// A kept instance outlives the rescan; hand it the fresh manifest.
var kept = _services[id]
if (kept && "manifest" in kept) kept.manifest = m
continue
}
ensureService(id)
}
// Drop services for plugins that have been disabled or removed.
// Drop services for plugins that have been disabled or removed, or that
// no longer declare a service entry point.
for (var existingId in _services) {
var stillThere = plugins[existingId]
var stillService = stillThere && Array.isArray(stillThere.kinds)
&& stillThere.kinds.indexOf("service") !== -1
&& stillThere.entryPoints && stillThere.entryPoints.service
var stillEnabled = stillThere && pluginRegistry.isEnabled(existingId)
if (stillThere && stillEnabled) continue
if (stillService && stillEnabled) continue
var inst = _services[existingId]
if (inst && typeof inst.destroy === "function") inst.destroy()
var next = ({})
@@ -345,12 +354,26 @@ ShellRoot {
}
}
function serviceKeepLoaded(pluginId) {
var plugins = pluginRegistry && pluginRegistry.installedPlugins
var manifest = plugins ? plugins[pluginId] : null
return !!(manifest && manifest.keepLoaded === true)
}
// keepLoaded services (lock, idle, polkit) must survive plugin hot-reload.
// Destroying omarchy.lock drops the ext-session-lock client while Hyprland
// still holds the lock, which surfaces the crashed-lockscreen fallback.
function unloadPluginServices() {
var next = ({})
for (var existingId in _services) {
if (serviceKeepLoaded(existingId)) {
next[existingId] = _services[existingId]
continue
}
var inst = _services[existingId]
if (inst && typeof inst.destroy === "function") inst.destroy()
}
_services = ({})
_services = next
}
Connections {