# 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 ### Shared fetch service (stage 1) Core's own HTTP fetch paths now go through one service, so the plugins that use them get pooling, merging, host budgets and per-plugin request counts without a code change. Return values, exceptions, cache keys, TTLs and retry policies are unchanged. - **What goes through it.** `APIHelper.get`/`post`, `fetch_espn_scoreboard` and its date chunks (`src/common/espn_dates.py` -- every scoreboard's live, recent and upcoming fetch, and `SportsFetchMixin._fetch_season_directly`), `BackgroundDataService` and `BaseOddsManager.get_odds`. Plugins' own `requests` calls are not covered yet. - **Shared connection pools.** Core sessions with the same retry policy mount one shared adapter, so the odds managers (one per scoreboard league manager), the background service and the APIHelpers reuse one connection pool per host. Headers, cookies and auth stay per session. - **Merged requests.** Identical GETs in flight at once (same URL and query, effective headers, timeout and retry policy) go out once; the others get a copy of that response or the same exception. `BackgroundDataService`'s own request opts out (`share_in_flight=False`): it cancels and replaces fetches, and already merges by cache key. - **Host budgets.** Per-host token buckets, `fetch_service.rate_limits` in `config.json` (new optional section in the template). ESPN hosts default to 20 requests/s with a burst of 200, far above normal traffic; no request waits longer than `max_wait_seconds` (2 s). Other hosts are unthrottled. - **Conditional GET.** A response with `ETag` or `Last-Modified` is kept in a small bounded store (64 entries, 4 MB, 1 MB each) and revalidated; a `304` is returned to the caller as the original `200`. ESPN sends neither validator today, so on ESPN this is dormant. - **Counters.** Requests, merged, bytes, 304s, errors, HTTP errors, adapter retries, throttled requests and seconds waited, per plugin and per host. Which plugin made a request comes from a context variable the plugin executor and plugin loader set (carried across the background service's and `espn_dates`' worker threads), or else from the plugin directory on the stack, so a plugin's own threads count too. The display publishes them to the shared cache at most once a minute on change; read them at `GET /api/v3/plugins/fetch-stats`. - `fetch_service` is a core config section (`src/core_config_keys.py`). ### New modules - `src/common/fetch_service.py` -- the fetch service above. Core-internal in this release: plugins reach it through `APIHelper` and `espn_dates`, and should not import it directly until a plugin-facing API ships (stage 3), so it sets no `ledmatrix_min_version` floor. ### Tooling - Golden trace tests for the display loop. `test/test_run_loop_golden.py` runs the real `DisplayController.run()` against fake plugins on a fake clock (`test/_run_loop_harness.py`), with no hardware and no real sleeps, and compares which mode was shown, for how long and why it ended with `test/fixtures/run_loop_golden/`. It has 15 scenarios: rotation, empty and failing modes, dynamic duration, live priority, on-demand (including pinned and resumed after a restart), the schedule and dim schedule, WiFi notices, sync follower and Vegas. The whole file runs in about a second. This is stage 1 of restructuring `run()`, described in `docs/RUN_LOOP_REDESIGN.md`. The other part of stage 1 is internal and changes no behaviour: twelve blocks of `run()` move into named helpers (`_dispatch_first_frame`, `_resolve_durations`, `_resolve_active_mode`, `_needs_high_fps`, `_advance_after_screen` and others), and the traces are identical before and after the move. ### Fixes - A plugin whose `display()` raises now opens its circuit breaker. The first frame of each screen goes through the plugin executor, which caught the exception and returned False. The display read that as "no content" and recorded a success, which reset the plugin's failure streak, so the breaker never tripped. The plugin stayed in rotation and logged a traceback on every screen. The raise now counts as a failure, so after three in a row the plugin leaves rotation until the cooldown ends, the same as a raising `update()`. The display still moves straight on to the next mode. A hung `display()` is still recorded once, as a hang. ## 3.8.0 Live Vegas elements: plugin content that keeps changing while it scrolls (scores on the scoreboards' cards, the flight map's gliding aircraft, the weather radar's loop), with live games kept in the ticker by default. Also stable/beta update channels, the display control socket, the systemd display watchdog, optional web login, ES-module web UI pages, sports consolidation stage 4, and the removal of the 35 plugin APIs deprecated since 3.5.0 (see Removed). ### New modules A plugin may import these via `src.*` once it floors on 3.8.0 (and should guard the import, since the loader's version check is advisory). - `src/plugin_system/vegas_elements.py` -- `VegasElement`, the unit a plugin's `get_vegas_elements()` returns (also re-exported from `base_plugin`). See "Live Vegas elements" below. - `src/plugin_system/testing/vegas.py` -- the harness for those hooks: `render_vegas_elements`, `check_vegas_elements`, `render_vegas_timeline`. - `src/common/sports_vegas.py` -- what a scoreboard needs for live cards: `game_key`, `game_fingerprint`, `dedupe_games`, `VegasCardCache`, `StickyOdds`, `finished_games`. - `src/common/sports_plugin_host.py`, `sports_live_scroll.py`, `sports_display_rules.py` and `sports_font_path.py` -- sports consolidation stage 4; see "New modules (sports consolidation stage 4)" below. - `src/display_watchdog.py`, `src/plugin_system/plugin_catalog.py`, `src/plugin_system/plugin_runtime.py`, `src/plugin_system/field_model.py` and the `src/ipc/` package -- new core modules (described below) that plugins do not normally import. ### Web UI: ES modules and one form model (stage 1) - The web UI gains a native ES-module layer, loaded with `