# Changelog Notable changes to the LEDMatrix core. The version below is the value of `src.__version__`, which the plugin loader reports to compatibility checks and which plugin manifests reference via `ledmatrix_min_version`. **Why this file exists:** the plugin monorepo bundles fallback copies of several core modules (see `docs/plugin-development/08-shared-sports-code.md` in [ledmatrix-plugins](https://github.com/ChuckBuilds/ledmatrix-plugins)). A plugin may delete its bundled copy only when its manifest floors on the first core release that ships the module — which requires module additions to be recorded here, against a version number. When you add a module plugins will import via `src.*`, note it in the Unreleased section and bump `src/__init__.py` in the release that ships it. **Use `ledmatrix_min_version` in manifests, not `ledmatrix_min`.** The loader accepts both, but the store flags the old spelling as deprecated (`store_manager.py`) and only the new one is in `schema/manifest_schema.json`. ## Unreleased ### Removed: the cache-key mailboxes (control socket stage 5) -- breaking The control socket (`docs/IPC_CONTROL_SOCKET.md`) is now the only way the web interface sends the display a command. The file mailboxes it fell back to for one release are gone. - **`display_on_demand_request` and `plugin_error_clear_request` are no longer written or read.** The display stops polling the on-demand mailbox (`MailboxWatch`, `MAILBOX_POLL_INTERVAL_WITH_SOCKET`, `_consume_on_demand_request` and the persisted `display_on_demand_processed_id` guard are removed), and the error publisher stops reading the clear request. `CacheManager.file_signature`, `src.cache_manager.MailboxWatch`, `src.ipc.client.should_fall_back` and `src.error_aggregator.ERROR_CLEAR_REQUEST_KEY` are removed. - **A write to either key is dropped, with one warning per writer.** `CacheManager.save_cache` (and so `set`) refuses `RETIRED_MAILBOX_KEYS` and logs `Ignored a write to the retired '' cache key by plugin ''`, naming the plugin from the call stack or the request. Plugins must use `BasePlugin.request_on_demand()` / `end_on_demand()` (3.8.1). In the official monorepo, birdnet-go, mqtt-notifications, on-air and pomodoro-timer still write the mailbox, but only as their fallback when those methods are missing or answer `None`. - **On-demand routes without a listening display.** `POST /api/v3/display/on-demand/start` with the service stopped starts it (when `start_service`, the default) and sends the request once the display's socket answers, waiting up to 45 s (10 s for a service that is running but has no socket yet); otherwise it answers `400` (`start_service` false) or `503` with `socket_error`. Every other socket failure (`unknown_command` from an older display, `disabled`/`unsupported`, `busy`, a timeout) is a `503`. `/stop` answers `503` when no display is listening, unless `stop_service` stops the service. `transport` is always `"socket"`; the `"mailbox"` value is gone. - **`POST /api/v3/errors/clear` without the socket answers `503`** (with `context.socket_error` and a message saying why) instead of recording a request. `clear_pending` in the error routes is now always `false`; `src.error_aggregator.read_error_report()` returns only the snapshot, and `error_summary_from_report()` / `plugin_health_from_report()` / `request_error_clear()` lose their clear-request arguments. - **Kept:** the display still writes `display_current_state`, `display_on_demand_state`, `plugin_runtime_snapshot` and the heartbeat file, because the web interface reads them whenever the socket cannot answer (a stopped or starting display, a web user not yet in the socket's group, Windows), and `display_on_demand_config`, its own record for resuming a session after a restart. - **Windows and `LEDMATRIX_CONTROL_SOCKET=off`:** with no socket, the web interface can no longer start or stop on-demand sessions or clear errors on a running display (the mailbox used to carry them). ### Plugins ask for the screen in-process: `request_on_demand()` / `end_on_demand()` The in-process way in that stage 5 of the control socket needed (`docs/IPC_CONTROL_SOCKET.md`, "Plugins in the display process"). - **`BasePlugin.request_on_demand(mode=None, duration=None, pinned=False)`** shows the plugin now, and **`BasePlugin.end_on_demand()`** gives the screen back. Both are safe from any thread (an MQTT callback, a timer thread): `PluginManager.request_on_demand()` / `end_on_demand()` hand the request to `DisplayController.submit_plugin_on_demand()`, which only queues it (at most 32) and wakes the render thread through the control socket's flag (`ControlServer.wake()`). The render thread applies it with the socket's commands, through the same handler as a web on-demand request, so it lands within a frame rather than on the mailbox's once-a-second look. Both return the request id, or `None` when no display runs in the process (the web interface, `scripts/check_plugin.py`) or the queue is full. - **A plugin's stop ends only its own session.** A mailbox stop still ends any session, whoever started it. - **Older cores.** Plugins detect the methods with `hasattr` and write the `display_on_demand_request` mailbox when they are missing or answer `None`; the pattern is in `docs/PLUGIN_API_REFERENCE.md` ("On-demand display"). The display still reads the mailbox for plugins that write it. ### Web UI: Schedule and General are ES-module pages (stage 3) - The Schedule and General tabs follow stage 2 (#727): their inline `