Skip to content

feat(plugins): apply config edits live without a display restart (4 plugins)#166

Open
rpierce99 wants to merge 1 commit into
ChuckBuilds:mainfrom
rpierce99:feat/plugin-config-hot-reload
Open

feat(plugins): apply config edits live without a display restart (4 plugins)#166
rpierce99 wants to merge 1 commit into
ChuckBuilds:mainfrom
rpierce99:feat/plugin-config-hot-reload

Conversation

@rpierce99

@rpierce99 rpierce99 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Problem

Most plugin settings only took effect after restarting the display service. The core already hot-reloads config.json (a 2s file-watcher in ConfigService notifies BasePlugin.on_config_change), but the base on_config_change only swaps self.config — it doesn't re-derive a plugin's settings, and it can't rebuild the components a plugin builds from config at construction. So editing a setting in the web UI appeared to do nothing until a restart.

Most first-party plugins never override on_config_change. This adds it to four of them so edits apply live.

What each plugin now refreshes live

  • ledmatrix-flights — data source, center/radius/zoom, units, max-aircraft and filters, proximity/live-priority. Rebuilds the data-source fetcher, route-enrichment provider and renderer (all captured config at construction) and invalidates the cached map background so a new center/radius/zoom re-tiles. Runtime state (tracked aircraft, trails, API counters, records, proximity state machine) is left untouched.
  • ledmatrix-elections — state, filters, providers, scroll speed, interrupt settings. Rebuilds providers + race store (so a changed state/provider/vote-override applies), reconfigures the scroll helper in place, and forces a re-fetch.
  • baseball-scoreboard / football-scoreboard — favorite teams, league enable/disable, durations, live priority, switch/scroll display modes. Re-derives the league/display settings and rebuilds the per-league managers, league registry, scroll manager and rotation modes. Old managers are cleaned up (HTTP sessions closed) before replacement, and per-game progress tracking is reset since manager keys can change.

Genuinely hardware-level settings (panel geometry, etc.) are intentionally not made live — those still warrant a clean restart.

Tests

Each plugin gets a test_config_reload.py that instantiates the plugin with stub managers, calls on_config_change, and asserts the derived settings/components update. Each was verified to fail against the base behavior and pass with the override.

  • Ran the core plugin safety harness (check_plugin.py) against all four at every size — no crashes/overflow, all PASS.
  • Existing ledmatrix-elections suites still pass (72 + 61).
  • Module-collision gate passes.

Notes

Versions bumped (minor) and plugins.json synced. The same one-method pattern extends to the remaining plugins in follow-ups. (clock-simple was prepared too but is held back for a separate PR: it has a pre-existing 64×32 / 64×64 overflow — unrelated to config reload — that the safety harness flags, and that should be fixed on its own.)

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds on_config_change() live hot-reload support to five plugins (baseball-scoreboard, clock-simple, football-scoreboard, ledmatrix-elections, ledmatrix-flights). Each plugin also gains a _cleanup_managers() helper (where applicable), a regression test script, a manifest version bump, and corresponding plugins.json registry metadata updates.

Changes

Live Config Hot-Reload Across Plugins

Layer / File(s) Summary
clock-simple: refactor init + on_config_change
plugins/clock-simple/manager.py, plugins/clock-simple/manifest.json, plugins/clock-simple/test_config_reload.py
SimpleClock extracts _apply_config() and _reset_render_cache() helpers; on_config_change() re-derives timezone/format/color and clears the render cache. Manifest bumped to 1.1.0 with regression test covering field updates and cache invalidation.
baseball-scoreboard: on_config_change + _cleanup_managers
plugins/baseball-scoreboard/manager.py, plugins/baseball-scoreboard/manifest.json, plugins/baseball-scoreboard/test_config_reload.py
Adds on_config_change() to re-resolve timezone, update enablement/timing/priority, call _cleanup_managers() to tear down existing league managers via cleanup(), rebuild managers and registry, recreate the scroll manager, recompute rotation modes, and reset cycle/scroll tracking state. Manifest bumped to 1.7.0 with regression test.
football-scoreboard: on_config_change + _cleanup_managers
plugins/football-scoreboard/manager.py, plugins/football-scoreboard/manifest.json, plugins/football-scoreboard/test_config_reload.py
Mirrors the baseball-scoreboard pattern: _cleanup_managers() nulls existing managers after calling cleanup(), and on_config_change() rebuilds managers, scroll manager, and rotation modes while resetting duration/cycle tracking. Manifest bumped to 2.4.0 with full regression test.
ledmatrix-elections: on_config_change
plugins/ledmatrix-elections/manager.py, plugins/ledmatrix-elections/manifest.json, plugins/ledmatrix-elections/test_config_reload.py
Adds on_config_change() to ElectionPlugin that re-reads all config fields, rebuilds providers and RaceStore, reconfigures scroll helper parameters, and clears cached race/ticker/interrupt state including resetting _last_update. Manifest bumped to 1.1.0 with regression test.
ledmatrix-flights: on_config_change
plugins/ledmatrix-flights/manager.py, plugins/ledmatrix-flights/manifest.json, plugins/ledmatrix-flights/test_config_reload.py
Adds on_config_change() to FlightTrackerPlugin that re-normalizes FlightAware config, re-derives all config-driven fields, conditionally loads flight records, rebuilds _fetcher/_enrichment/_renderer, invalidates cached map background state, and refreshes rotation modes. Manifest bumped to 1.10.0 with regression test.
plugins.json registry metadata
plugins.json
Top-level last_updated and per-plugin last_updated/latest_version fields advanced to 2026-06-15 for all five affected plugins.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ChuckBuilds/ledmatrix-plugins#39: Introduced the scroll/display mode configuration fields that on_config_change() in the baseball-scoreboard and football-scoreboard managers now re-parses during live reloads.
  • ChuckBuilds/ledmatrix-plugins#44: Added timezone resolution logic to baseball-scoreboard/manager.py that the new on_config_change() re-invokes to re-derive timezone at runtime.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding live config reload capability to four plugins. It accurately summarizes the primary objective and matches the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production

codacy-production Bot commented Jun 16, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 20 complexity

Metric Results
Complexity 20

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Four installed plugins had no on_config_change override, so the base
BasePlugin only swapped self.config and their derived settings (and the
components that captured config at construction) kept startup values until
the display service was restarted. This adds on_config_change to each so the
2s config-file watcher applies edits immediately:

- ledmatrix-flights: re-derive scalars and rebuild the data-source fetcher,
  route enrichment and renderer; invalidate the cached map so a new
  center/radius/zoom re-tiles.
- ledmatrix-elections: re-derive filters, rebuild providers + race store,
  reconfigure the scroll helper, and force a re-fetch.
- baseball-scoreboard / football-scoreboard: re-derive league/display
  settings and rebuild the per-league managers (cleaning up old HTTP
  sessions first), league registry, scroll manager and rotation modes so
  favorite teams, league enable/disable, durations and live priority apply
  live. Per-game progress tracking is reset since manager keys may change.

Each plugin gains a regression test that fails against the base behavior and
passes with the override. Versions bumped + plugins.json synced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rpierce99 rpierce99 force-pushed the feat/plugin-config-hot-reload branch from 6397855 to 16e965b Compare June 16, 2026 05:13
@rpierce99 rpierce99 changed the title feat(plugins): apply config edits live without a display restart feat(plugins): apply config edits live without a display restart (4 plugins) Jun 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/baseball-scoreboard/manager.py`:
- Around line 278-279: The inconsistency between how self.enabled and
self.is_enabled are initialized during config reloads can unintentionally
re-enable runtime behavior on partial config updates. Line 278 correctly
preserves the prior state of self.enabled when the "enabled" key is missing by
using getattr(self, "enabled", True) as a fallback, but line 279 hard-defaults
self.is_enabled to True instead. Fix this by updating line 279 to follow the
same pattern as line 278: use getattr(self, "is_enabled", True) as the fallback
value instead of just True, so that self.is_enabled also preserves its prior
state when the config key is omitted.

In `@plugins/football-scoreboard/manifest.json`:
- Line 4: The version string in the get_info() method in manager.py is out of
sync with the updated version in manifest.json. Update the version value
returned by the get_info() method from "2.1.1" to "2.4.0" to ensure the
runtime-reported plugin version matches the manifest version and maintains
consistency for diagnostics and UI consumers.

In `@plugins/ledmatrix-flights/manager.py`:
- Around line 409-410: The configuration update for tracked_flights_cfg does not
clean up stale entries from the runtime tracked_flight_data dictionary when
flights are removed from the config during hot-reload. After updating
tracked_flights_cfg on line 409, add logic to identify and remove any entries
from tracked_flight_data that no longer exist in the updated configuration by
comparing the tracked flight identifiers between the old and new config values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 688b6c86-75df-497a-8430-7717abc641cb

📥 Commits

Reviewing files that changed from the base of the PR and between 7ced2b3 and 6397855.

📒 Files selected for processing (16)
  • plugins.json
  • plugins/baseball-scoreboard/manager.py
  • plugins/baseball-scoreboard/manifest.json
  • plugins/baseball-scoreboard/test_config_reload.py
  • plugins/clock-simple/manager.py
  • plugins/clock-simple/manifest.json
  • plugins/clock-simple/test_config_reload.py
  • plugins/football-scoreboard/manager.py
  • plugins/football-scoreboard/manifest.json
  • plugins/football-scoreboard/test_config_reload.py
  • plugins/ledmatrix-elections/manager.py
  • plugins/ledmatrix-elections/manifest.json
  • plugins/ledmatrix-elections/test_config_reload.py
  • plugins/ledmatrix-flights/manager.py
  • plugins/ledmatrix-flights/manifest.json
  • plugins/ledmatrix-flights/test_config_reload.py

Comment on lines +278 to +279
self.enabled = self.config.get("enabled", getattr(self, "enabled", True))
self.is_enabled = self.config.get("enabled", True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep runtime enable flags consistent during partial config reloads.

Line 278 preserves prior state when enabled is omitted, but Line 279 hard-defaults to True. That can unintentionally re-enable runtime behavior on partial config payloads.

Suggested fix
-        self.enabled = self.config.get("enabled", getattr(self, "enabled", True))
-        self.is_enabled = self.config.get("enabled", True)
+        enabled_flag = self.config.get("enabled", getattr(self, "is_enabled", True))
+        self.enabled = enabled_flag
+        self.is_enabled = enabled_flag
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/baseball-scoreboard/manager.py` around lines 278 - 279, The
inconsistency between how self.enabled and self.is_enabled are initialized
during config reloads can unintentionally re-enable runtime behavior on partial
config updates. Line 278 correctly preserves the prior state of self.enabled
when the "enabled" key is missing by using getattr(self, "enabled", True) as a
fallback, but line 279 hard-defaults self.is_enabled to True instead. Fix this
by updating line 279 to follow the same pattern as line 278: use getattr(self,
"is_enabled", True) as the fallback value instead of just True, so that
self.is_enabled also preserves its prior state when the config key is omitted.

"id": "football-scoreboard",
"name": "Football Scoreboard",
"version": "2.3.5",
"version": "2.4.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Sync runtime-reported plugin version with manifest version.

Line 4 bumps this manifest to 2.4.0, but plugins/football-scoreboard/manager.py Line 2428 still reports "version": "2.1.1" in get_info(). This creates a cross-file metadata contract mismatch for diagnostics/UI/registry consumers.

🔧 Proposed fix
--- a/plugins/football-scoreboard/manager.py
+++ b/plugins/football-scoreboard/manager.py
@@
-                "version": "2.1.1",
+                "version": "2.4.0",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/football-scoreboard/manifest.json` at line 4, The version string in
the get_info() method in manager.py is out of sync with the updated version in
manifest.json. Update the version value returned by the get_info() method from
"2.1.1" to "2.4.0" to ensure the runtime-reported plugin version matches the
manifest version and maintains consistency for diagnostics and UI consumers.

Comment on lines +409 to +410
self.tracked_flights_cfg = self.config.get('tracked_flights', [])
self.anchor_airport = self.config.get('anchor_airport', '')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reconcile tracked-flight runtime state when config changes.

Line 409 updates tracked_flights_cfg, but removed/replaced tracked identifiers are never pruned from tracked_flight_data. After hot-reload, stale tracked flights can keep rendering even though they were removed from config.

💡 Proposed fix
         self.tracked_flights_cfg = self.config.get('tracked_flights', [])
         self.anchor_airport = self.config.get('anchor_airport', '')
         self.route_cache_ttl = self.config.get('route_cache_ttl', 300)
+
+        # Reconcile tracked-flight state with the new config so removed identifiers
+        # do not keep rendering after live reload.
+        configured_tracked = set(self.tracked_flights_cfg[:3])
+        for ident in list(self.tracked_flight_data.keys()):
+            if ident not in configured_tracked:
+                self.tracked_flight_data.pop(ident, None)
+        if self._tracking_index >= max(1, len(self.tracked_flight_data)):
+            self._tracking_index = 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ledmatrix-flights/manager.py` around lines 409 - 410, The
configuration update for tracked_flights_cfg does not clean up stale entries
from the runtime tracked_flight_data dictionary when flights are removed from
the config during hot-reload. After updating tracked_flights_cfg on line 409,
add logic to identify and remove any entries from tracked_flight_data that no
longer exist in the updated configuration by comparing the tracked flight
identifiers between the old and new config values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant