Merge origin/main into claude/plugin-hang-containment

Conflict with #678 in the plugin config-change callback: keep #678's
override (a plugin loaded for on-demand sees enabled: True) and apply the
result through the locked apply_config_change. The #678 test now asserts
on that call, which delivers the override to on_config_change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-29 19:31:09 -04:00
co-authored by Claude Opus 5.5
9 changed files with 730 additions and 29 deletions
+12
View File
@@ -30,6 +30,18 @@ accepts both, but the store flags the old spelling as deprecated
mid-screen and mid-Vegas included, so the route now only starts the service
when it is not running. `POST /display/on-demand/stop` reads
`stop_service` as a boolean, so `"false"` no longer stops the service.
- On-demand works for a disabled plugin. The display only loads enabled
plugins, so "Preview on display" on a disabled plugin's config page (which
says the plugin will be enabled for the preview) failed with
`invalid-mode`. The display now loads the plugin live for the session,
without writing `enabled` to `config.json`, and unloads it when on-demand
is stopped, expires or moves to another plugin. A plugin that fails to
load reports on-demand status `error` with `load-failed`. A session
restored after a restart unloads its disabled plugin the same way; it used
to stay loaded until the next restart.
- A stop request now clears an on-demand error. After a failed request,
`/display/on-demand/status` kept reporting `status: error` for up to two
minutes even after a stop.
- One hung plugin no longer stops every plugin from updating. The single
update worker waited on each plugin's lock with no time limit, and the
render thread holds that lock while it runs the plugin's display(); a