# 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 ## 3.8.1 Smooth scrolling at the slower speeds, and the fixes and performance work since 3.8.0. Highlights: the default 50 px/s and every other held-frame speed now scroll cleanly (below), Raspberry Pi OS Bookworm is supported alongside Trixie, updates refresh the systemd units, the display control socket gains stages 2 and 3, the shared fetch service lands (stages 1 and 2), and a run of web UI and Plugin Manager fixes. One new module is for plugins: `src.common.sports_game_over` (sports family 5), which the scoreboards adopt by flooring on 3.8.1; the other new modules are core-internal and set no `ledmatrix_min_version` floor. ### Scroll speed These two entries were the reason for this release: on 3.8.0 a slow scroll either stepped or showed a half-pixel tear across the middle of the panel, so only speeds of one pixel per refresh looked right. - The Vegas Scroll Speed slider now says what the panel will do with the speed it is on, and offers the nearest smooth ones to click. Only speeds that advance a whole number of pixels per refresh look smooth, and which those are depends on the panel (`GET /api/v3/config/scroll-speed-advice`, built on `scroll_config.speed_advice()`; it uses the refresh the display measured, not the `limit_refresh_rate_hz` cap). The slider steps by 1 px/s instead of 5. - The default 50 px/s no longer snaps to a stepped 48 px/s (2 px every 5 refreshes, 24 fps) on a 120 Hz panel: `solve_crisp()` now prefers 60 or 40 px/s, which move one pixel at a time. 100 Hz panels are unaffected. (#710) - A held-frame scroll (one pixel every two or more refreshes, such as 50 or 60 px/s on a 100-120 Hz panel) no longer shows a half-pixel step across the middle of the panel. Scan-order compensation ran only at one frame per refresh; a held frame is now presented as a sequence of swaps (`scan_order.refresh_plan()`), so the half of the panel that scans later steps one refresh after the rest. It is skipped when a blit takes more than half a refresh, since the second blit has to land before the next vsync. (#711) ### Web UI: the Display tab is an ES-module page, with a page-visibility service (stage 4) - New `static/v3/js/core/visibility.js`: each page module gets `ctx.visibility` with `whileVisible(start, stop)`, `every(ms, fn)` and `isVisible()`. Work registered there runs only while the page's tab is the active tab and the browser tab is visible, and ends when the page is swapped out, with no teardown code in the page. It reads the active tab from `window.LEDVisibility`, so it agrees with the classic partials that still use that directly. The page registry gained a `mountContext` option for services bound to one mounted page. - The Display tab's inline scripts are now `static/v3/js/pages/display.js`. The partial has no inline script, `onclick` or `onchange` any more. The multi-display sync status poll (every 5 s) runs through `ctx.visibility.every`; the status and scroll-speed hint requests go through `core/api.js` with the page's abort signal, and so does the Vegas order widget's plugin-list request. - Behaviour differences: with sync on, opening the tab asks for the status once instead of twice, and a Display tab loaded while not on screen waits until it is. A login redirect during a poll no longer flashes "Sync status unavailable". The `window.syncStatusInterval` timer id is gone (nothing read it). A pending scroll-hint request or widget retry is dropped when the partial is swapped out. - `window.updateSyncUI` keeps working as a deprecated alias through `window.LEDMatrix` (one console warning). - New suites `test/js/dom/test_visibility_service.js` and `dom/test_display_page.js`; `unit/test_display_partial_ids.js` imports the module instead of slicing the template. ### 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 `