mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
refactor: remove the skin system and the unused src/base_classes package (#615)
* refactor: remove the skin system Skins never rendered with the current scoreboard plugins: the only hook was SportsCore._render_game in src/base_classes, which no plugin builds on, so the UI and store already treated them as unsupported. The owner decided on 2026-09-23 to remove them outright. Removed src/skin_system/ (runtime, base class, fixtures), skins/, scripts/validate_skin.py and their tests; the store's "type": "skin" installer, uninstaller and hide/refuse filters (the official registry lists no skins); SchemaManager.inject_skin_selector; and GET /api/v3/skins. Stored skin/skin_options config values are handled in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(config): drop retired skin/skin_options keys instead of validating them A config.json written while the skin system existed can carry skin and skin_options in any plugin section, and most plugin schemas set additionalProperties: false. They are no longer core plugin properties; RETIRED_PLUGIN_KEYS in schema_manager lists them and drop_retired_plugin_keys removes them (unless the plugin's own schema declares the name) in prepare_plugin_config, which loading, hot reload, GET /plugins/config and both web saves already share, and in validate_config_against_schema for callers that validate a raw section. POST /plugins/config and /config/main also drop them from the stored section they merge into, so they leave config.json on the next save. Tests cover the load path (real PluginManager.load_plugin: no schema warning, not degraded), raw and prepared validation, validate_all_plugin_configs, and the JSON, form and /config/main saves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: remove the unused src/base_classes package No scoreboard plugin builds on src.base_classes: the nine monorepo scoreboards ship their own sports.py and share code through src/common (docs/SPORTS_UNIFICATION.md), and none of the third-party registry plugins imports it. The one import anywhere, baseball-scoreboard's rankings_manager.py, is a lazy import of ESPNDataSource in a class nothing instantiates. Removed the package and the eight test files that only tested it (test_api_extractors, test_data_sources, test_sports_base_characterization, test_sports_capabilities, test_sports_core_promotions, test_sports_logo_cache_bounded, test_sports_modes_promotions, test_sports_odds_fanout). test_common_is_hardware_free no longer lists src.base_classes as a forbidden import, and comments in sports_helpers.py and base_odds_manager.py stop pointing at it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: drop the skin system and src/base_classes from the docs Deletes docs/SKIN_SYSTEM.md and docs/CREATING_SKINS.md and every link to them (docs/README.md, README.md, PLUGIN_DEVELOPMENT_GUIDE.md, the /skins section of REST_API_REFERENCE.md), the skin section of CLAUDE.md and the term in PRODUCT.md. SPORTS_UNIFICATION.md now says src/base_classes was removed and shared code lives in src/common, in the Layering section and the view-model-contract rule. Other docs stop pointing at the removed package. CHANGELOG records both removals under Unreleased. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(store): hide and refuse registry entries that aren't plugins The skin filters went with the skin system, but a custom registry can still list "type": "skin" entries, and installing one as a plugin would unpack it into the plugins directory. PluginStoreManager.is_plugin_entry() (a missing type means plugin) now hides non-plugin entries from the store and custom-registry listings, and install refuses them, in the route with a clear 400 and in _install_plugin_impl for any other caller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,9 @@ test_api_v3_secret_roundtrip.py, so assertions are on config.json itself.
|
||||
- Legacy booleans (#588) were normalized only at load, so posting back what
|
||||
GET /plugins/config returned failed validation.
|
||||
- The JSON save's filter kept only enabled/display_duration/live_priority, so
|
||||
a submitted skin, skin_options or vegas_* tuning key was silently dropped.
|
||||
a submitted vegas_* tuning key was silently dropped.
|
||||
- ``skin`` and ``skin_options`` outlived the skin system in stored configs:
|
||||
every save path must accept a section carrying them, and drop them.
|
||||
- Plugin sections posted to /config/main skipped all of that and were stored
|
||||
verbatim, including values /plugins/config rejects.
|
||||
"""
|
||||
@@ -65,7 +67,9 @@ STORED = {
|
||||
"display": {"brightness": 80, "show_icons": False},
|
||||
"global": {"dynamic_duration": {"enabled": False, "min_duration_seconds": 45}},
|
||||
"vegas_width_pct": 60,
|
||||
# Written by a release that still had the skin system
|
||||
"skin": "neon",
|
||||
"skin_options": {"accent": "#00ff00"},
|
||||
}
|
||||
|
||||
_ATTRS = ('config_manager', 'plugin_manager', 'plugin_store_manager',
|
||||
@@ -174,28 +178,64 @@ class TestCoreOwnedKeysSurviveTheFilter:
|
||||
env.store({"enabled": True, "city": "Paris"})
|
||||
resp = env.save({
|
||||
"vegas_width_pct": 50, "vegas_overflow": "truncate",
|
||||
"vegas_max_width_screens": 2, "skin": "retro",
|
||||
"skin_options": {"accent": "#ff0000"}, "live_priority": True,
|
||||
"vegas_max_width_screens": 2, "live_priority": True,
|
||||
})
|
||||
assert resp.status_code == 200, resp.get_json()
|
||||
stored = env.stored()
|
||||
assert stored["vegas_width_pct"] == 50
|
||||
assert stored["vegas_overflow"] == "truncate"
|
||||
assert stored["vegas_max_width_screens"] == 2
|
||||
assert stored["skin"] == "retro"
|
||||
assert stored["skin_options"] == {"accent": "#ff0000"}
|
||||
assert stored["live_priority"] is True
|
||||
|
||||
def test_stored_core_keys_survive_an_unrelated_save(self, env):
|
||||
assert env.save({"city": "Nice"}).status_code == 200
|
||||
stored = env.stored()
|
||||
assert stored["vegas_width_pct"] == 60 and stored["skin"] == "neon"
|
||||
assert stored["vegas_width_pct"] == 60
|
||||
|
||||
def test_a_non_core_unknown_key_is_still_filtered(self, env):
|
||||
assert env.save({"not_in_schema": 1}).status_code == 200
|
||||
assert "not_in_schema" not in env.stored()
|
||||
|
||||
|
||||
class TestRetiredSkinKeys:
|
||||
"""STORED carries skin/skin_options, and SCHEMA sets
|
||||
additionalProperties: false without declaring them."""
|
||||
|
||||
@staticmethod
|
||||
def _assert_dropped(stored):
|
||||
assert "skin" not in stored and "skin_options" not in stored
|
||||
assert stored["vegas_width_pct"] == 60 # still a core-owned key
|
||||
|
||||
def test_json_save_accepts_and_drops_them(self, env):
|
||||
resp = env.save({"city": "Nice"})
|
||||
assert resp.status_code == 200, resp.get_json()
|
||||
assert env.stored()["city"] == "Nice"
|
||||
self._assert_dropped(env.stored())
|
||||
|
||||
def test_form_save_accepts_and_drops_them(self, env):
|
||||
resp = env.client.post(f"/api/v3/plugins/config?plugin_id={PLUGIN_ID}",
|
||||
data={"city": "Nice"})
|
||||
assert resp.status_code == 200, resp.get_json()
|
||||
assert env.stored()["city"] == "Nice"
|
||||
self._assert_dropped(env.stored())
|
||||
|
||||
def test_config_main_accepts_and_drops_them(self, env):
|
||||
resp = env.client.post("/api/v3/config/main", json={PLUGIN_ID: {"city": "Nice"}})
|
||||
assert resp.status_code == 200, resp.get_json()
|
||||
self._assert_dropped(env.stored())
|
||||
|
||||
def test_submitted_ones_are_not_stored(self, env):
|
||||
env.store({"enabled": True, "city": "Paris", "vegas_width_pct": 60})
|
||||
resp = env.save({"skin": "retro", "skin_options": {"accent": "#ff0000"}})
|
||||
assert resp.status_code == 200, resp.get_json()
|
||||
self._assert_dropped(env.stored())
|
||||
|
||||
def test_get_config_leaves_them_out(self, env):
|
||||
data = env.client.get(f"/api/v3/plugins/config?plugin_id={PLUGIN_ID}").get_json()["data"]
|
||||
assert data["city"] == "Paris"
|
||||
assert "skin" not in data and "skin_options" not in data
|
||||
|
||||
|
||||
class TestLegacyBooleans:
|
||||
LEGACY = {"enabled": True, "city": "Paris", "global": {"dynamic_duration": True}}
|
||||
|
||||
@@ -246,7 +286,7 @@ class TestPluginSectionsInConfigMain:
|
||||
assert stored["city"] == "Lyon"
|
||||
assert stored["display"] == {"brightness": 80, "show_icons": False}
|
||||
assert stored["vegas_overflow"] == "rotate"
|
||||
assert stored["vegas_width_pct"] == 60 and stored["skin"] == "neon"
|
||||
assert stored["vegas_width_pct"] == 60
|
||||
|
||||
def test_secrets_still_go_to_the_secrets_file(self, env):
|
||||
resp = self._post(env, {PLUGIN_ID: {"api_key": "s3cret"}})
|
||||
|
||||
Reference in New Issue
Block a user