Skip to content

load_config_defaults ignores nested schema defaults — 2,386 defaults across 37 of 44 plugins never reach the render harness #531

Description

@ChuckBuilds

Summary

load_config_defaults only reads defaults from top-level schema properties. An object property ("type": "object" with its own properties) has no top-level "default" key, so it is skipped entirely — along with every default nested inside it.

Across the 44 first-party plugins installed here, that drops 2,386 schema defaults across 37 plugins. soccer-scoreboard loses 539 of its 565.

The consequence is that check_plugin.py — the visual regression tool, and what CI runs — renders most plugins with a config that does not resemble a real install, while render_plugin_matrix says it does:

# Start from config_schema.json defaults so the plugin behaves like a real# install; explicit caller config still wins over a schema default.config= {"enabled": True, **load_config_defaults(plugin_dir), **(configor {})}

The code

src/plugin_system/testing/loading.py:36-47:

defload_config_defaults(plugin_dir) ->Dict[str, Any]:
"""Extract default values from a plugin's config_schema.json (empty if none)."""
...
defaults: Dict[str, Any] = {}
forkey, propinschema.get('properties', {}).items():
ifisinstance(prop, dict) and'default'inprop:
defaults[key] =prop['default']
returndefaults

No recursion into prop['properties'].

Demonstration

hockey-scoreboard's schema defines nhl.enabled: true and nhl.display_modes.{live,recent,upcoming}: true. What the harness actually loads:

$ python3 -c "
from src.plugin_system.testing.loading import load_config_defaults
d = load_config_defaults('plugin-repos/hockey-scoreboard')
print('nhl.enabled =', d.get('nhl',{}).get('enabled'))
print('nhl.display_modes =', d.get('nhl',{}).get('display_modes'))"
nhl.enabled = None
nhl.display_modes = None

Same for lacrosse-scoreboard. The plugins then fall back to whatever their internal defaults happen to be, which is what the harness ends up testing.

Scale

Defaults kept (top-level) vs dropped (nested), first-party plugins only:

pluginkeptdropped
soccer-scoreboard26539
baseball-scoreboard9251
basketball-scoreboard9246
hockey-scoreboard6207
lacrosse-scoreboard6155
football-scoreboard10152
afl-scoreboard3492
nrl-scoreboard3392
ufc-scoreboard978
f1-scoreboard664
ledmatrix-leaderboard352
stock-news150
ledmatrix-flights5144
masters-tournament1041
ledmatrix-stocks239
odds-ticker138
birdnet-go237
news129
cricket-scoreboard2228
… 18 more
total2386 across 37 of 44 plugins

The plugins that lose the most are exactly the ones whose config is organised by league or by UI section — sports, customization.*, display_options.* — which is to say most of the fleet.

Why it matters beyond tidiness

  1. Goldens encode the wrong baseline. Committed golden images were captured under this partial config, so they pin behaviour no user will see.
  2. Whole features go untested. A plugin whose display_options.* all sit nested is rendered with those options unset at every size, in every CI run.
  3. It produces misleading secondary signals. Comparing each plugin's manifest display_modes against what it exposes under harness defaults shows six sports plugins "missing" modes — baseball-scoreboard exposes 3 of 9, basketball-scoreboard 3 of 12, hockey-scoreboard 2 of 9. Those look like manifest bugs and are not; the league configs simply never arrived. I nearly filed them as plugin defects.

Suggested fix

Recurse, preserving nesting:

def_defaults(props):
out= {}
forkey, propin (propsor {}).items():
ifnotisinstance(prop, dict):
continueifprop.get('type') =='object'and'properties'inprop:
nested=_defaults(prop['properties'])
ifnested:
out[key] =nestedelif'default'inprop:
out[key] =prop['default']
returnout

with a merge that lets caller config override at leaf level rather than replacing whole subtrees — otherwise -c '{"nhl": {"enabled": true}}' would wipe the rest of the nhl defaults.

That is a behaviour change: goldens will shift for the 37 affected plugins, and some may start failing for real reasons. That is the point, but it wants a deliberate --update-golden pass and a look at what newly breaks rather than a quiet merge.

A regression test is easy: assert load_config_defaults on a schema with a nested object returns the nested defaults.

Environment

LEDMatrix v3.3.0-4-g0730d952, 256x64 rig, 44 first-party plugins installed. Found while auditing manifest display_modes against the modes plugins actually expose.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions