Skip to content

fix(docs-tooling): let shot attrs reach nested plugin state - #423

Merged
ChuckBuilds merged 3 commits into
mainfrom
fix/docs-attrs-nested
Sep 4, 2026
Merged

fix(docs-tooling): let shot attrs reach nested plugin state#423
ChuckBuilds merged 3 commits into
mainfrom
fix/docs-attrs-nested

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Why

The attrs seam (added in #386) could only set attributes on the plugin instance itself:

setattr(instance, name, value)

The sports scoreboards keep their per-mode state on sub-managers, not on the plugin:

self._managers = {"live": ..., "recent": ..., "upcoming": ...}
# and the drawing reads self._managers["live"].current_game

so a shot could not reach the game it wanted to draw, and the render came out blank. That architecture is shared by the six scoreboards still to be documented (nrl, lacrosse, football, hockey, basketball, soccer), so this blocks all of them.

What changed

An attrs key may now be a dotted path:

"attrs": { "_managers.live.current_game": { ... } }

Each segment is tried as a mapping key and as an attribute, so both self._managers["live"] and self.live_manager resolve. The final assignment uses __setitem__ when the owner is a mapping that does not already carry the name.

A path that does not exist is skipped rather than raised — a shots file shared across panel sizes should not fail because one plugin shapes its state differently.

Verification

Exercised the resolver directly against a stand-in shaped like the scoreboards:

nested set  -> {'id': 'x'}      # _managers.live.current_game
plain set   -> 7                # unchanged behaviour
missing path is skipped, not raised -> ok

And for regression, the two merged plugins that use flat attrs still reproduce byte-identically:

on-air    up to date
calendar  up to date

Scope

Tooling only — no plugin, schema or manifest touched. This does not by itself make nrl-scoreboard render: its sub-manager needs more seeded state than current_game alone, which is the next thing to work out.

🤖 Generated with Claude Code

The attrs seam could only set attributes on the plugin instance itself. The
sports scoreboards keep their per-mode state on sub-managers rather than on
the plugin -- self._managers["live"].current_game -- so a shot could not reach
the game it wanted to draw, and every render came out blank. That architecture
is shared by the six scoreboards still to be documented.

An attrs key may now be a dotted path. Each segment is tried as a mapping key
and as an attribute, so both self._managers["live"] and self.live_manager
resolve, and the final assignment uses __setitem__ when the owner is a mapping
that does not already carry the name.

A path that does not exist is skipped rather than raised: a shots file shared
across panel sizes should not fail because one plugin shapes its state
differently.

Verified in isolation -- a nested path sets through a dict of objects, a plain
top-level name still sets, and a missing path is a no-op. The on-air and
calendar renders, which both use flat attrs, still reproduce byte-identically
under --check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 954a70a6-b8f5-414c-93b3-7df79683ac57


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.

@codacy-production

codacy-production Bot commented Sep 4, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 18 complexity

Metric Results
Complexity 18

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.

ChuckBuilds and others added 2 commits September 3, 2026 22:35
A shots file can only carry JSON, but plugin state is not all strings and
lists. The sports renderers call logo_path.parent, so a logo field seeded as a
string raises AttributeError inside _load_and_resize_logo; the exception is
caught per-team, the card silently fails to draw, and the panel comes out
blank with nothing in the harness output to say why.

Values whose key ends in _path now become pathlib.Path, alongside the existing
rule that a three-integer list becomes a tuple. Both now recurse into nested
dicts and lists, so a seeded game record is converted throughout rather than
only at the top level.

Verified against nrl-scoreboard: seeding its live sub-manager and calling that
manager's display() directly renders 1892 lit pixels, where the same seed with
string paths rendered none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pairs with LEDMatrix#522, which adds --display-mode to the core's
render_plugin.py. A shot may now name which screen to draw:

    {"name": "live", "display_mode": "nrl_live"}

Without it, a plugin declaring several modes renders whichever its
no-argument display() path picks -- which for the sports scoreboards is
nothing at all, since their state lives on per-mode sub-managers.

The key is omitted from the command line unless set, so every existing shot
list behaves exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
…renders (#442)

The README explained the MQTT side well -- payload format, topic
wildcards, five Home Assistant examples -- but never listed the plugin's
own settings, and had no images. All 20 schema leaves are now
documented, verified by a token audit, with three renders.

The five that were missing: mqtt.client_id, mqtt.keepalive,
text.scroll_gap_width, update_interval and display_duration.

display_duration is worth its own note. The core reads it as a plugin's
screen time (base_plugin.py:406), but this plugin overrides
get_display_duration() and returns display.default_duration instead, so
setting the root key alone changes nothing. The two defaults agree at
10s, which hides it until someone sets one and not the other. The README
now says which one to set.

update_interval, by contrast, is live: the core schedules update() from
it (plugin_manager.py:809). Both looked dead to a grep of the plugin
alone -- neither name appears in manager.py -- and grepping the core is
what separated the two.

Also documented: the two font routes. text.font_path takes any path,
customization.message_text.font offers the bundled faces as a dropdown,
and the customization block wins when its font loads. Colours come from
text either way, since customization carries no colour keys.

check_plugin.py passes 8/8 with no FAIL.

Carries the docs-tooling changes from #423 and the frame-runner
--display-mode change from #441, which these renders depend on.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
…ers (#439)

The README covered the five top-level settings well but said nothing
about the nine customization keys -- three text elements, each with a
font, size and colour -- which is the whole of the plugin's appearance
control. All 14 schema leaves are now documented, verified by a token
audit, with three renders.

The renders also make a practical point the README could not: on a
128-wide panel the default PressStart2P at 8px runs past the edge once
the view count reaches seven digits. Dropping the three lines to
4x6-font.ttf at 6 fits, and the side-by-side shows both.

Also corrected: the logo lookup is described as it actually works --
assets/youtube_logo.png resolved against the process working directory
first, then the LEDMatrix install root -- rather than "in project root".
And the API-key failure has a visible symptom worth naming, the panel
showing "YT: Update API Key", which the old troubleshooting section did
not mention.

Kept the ChuckBuilds links block, the API-quota arithmetic, and the
secrets-file split for the key.

check_plugin.py passes with no FAIL.

Carries the docs-tooling changes from #423, which this render depends on.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
…#436)

The README documented six of the ten leagues by pasting a near-identical
config block for each, covered a fraction of the 636 settings, and had
no images. It now documents every schema leaf -- verified by a token
audit -- with six renders, and keeps the ChuckBuilds links block, the
supported-league table, the custom-league codes, the World Cup section,
the TEAMS.md pointer and the non-favorite dwell table.

Ten league blocks, 53 leaves each, identical except enabled. One
<league> section collapses 477 of the 636 leaves.

leagues.<slug>.display_options does nothing. Nothing in this plugin or
in the core reads it; _adapt_config_for_manager takes show_records,
show_ranking and show_odds from the plugin config root instead. Proved
by render in both directions -- toggling the per-league copy gives
byte-identical panels, toggling the root key draws and removes the
records. That is 30 settings, and it is the opposite precedence to every
other scoreboard here, so a config pattern carried across sets the wrong
one. Filed as #435; the README documents the behaviour that exists.

scroll_settings in the same blocks looks equally unreferenced but is
live -- scroll_display.py delegates to the core base class, which reads
it. Grepping the core is what separated the two.

Soccer has the richest status model of the set, and it was undocumented
beyond a list in the World Cup section: 1H, 2H, HALF, ET1, ET2, ETH and
PEN while live, then Final, F/ET or F/Pen. Both now have renders.

check_plugin.py passes 24/24 with no FAIL.

Carries the docs-tooling changes from #423, which this render depends on.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
…ders (#434)

The README covered a fraction of the 274 settings and had no images. It
now documents every schema leaf -- verified by a token audit -- with
five renders, and keeps the ChuckBuilds links block, the four team
abbreviation lists, the background-service notes, the NCAA season-data
caveat, the game-limits behaviour and the non-favorite dwell table.

Four leagues. Only two defaults differ across them -- enabled, and
show_ranking, which is on for the two college leagues -- so that is a
short table plus one <league> section rather than 200 duplicated rows.

The two NCAA blocks carry a march_madness section the NBA and WNBA do
not have: seeds in place of AP rankings, the round abbreviation, the
bracket region, a tournament-game cap, and a tournament_mode override
that is otherwise decided from the calendar. It now has its own section
and a three-way render.

Also newly documented:

- The mode toggles are show_live / show_recent / show_upcoming here, as
  in football, not live / recent / upcoming as in hockey and lacrosse.
  A display_modes block copied across silently sets nothing.
- mode_durations has no live_mode_duration in this plugin, unlike the
  football, hockey and lacrosse scoreboards.
- other_games_min_quality is live for the college leagues, which have a
  poll, and inert for the NBA and WNBA, which do not.

check_plugin.py passes 24/24 with no FAIL.

Carries the docs-tooling changes from #423, which this render depends on.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
…#433)

The README ran to 894 lines and still covered a fraction of the 226
settings, with no images. It now documents every schema leaf -- verified
by a token audit -- with five renders, and keeps the ChuckBuilds links
block, the rotation and resume semantics, the non-favorite dwell table,
the exclude-teams note and all three example configurations.

Three leagues, 53 leaves each. The two NCAA blocks are identical; the
NHL block differs in nine defaults, so that is a table rather than 106
duplicated rows.

Two settings do not work, and the README now says so rather than
describing them as features:

- show_powerplay is resolved into the manager config and power_play is
  extracted onto the game dict, but nothing in this plugin or in the
  core draws either. No value changes the render. Filed as #431.
- Scroll and Vegas cards gate the shot line on a flat show_shots key at
  the league root, which the schema never declares, so it reads False
  whatever show_shots_on_goal is set to. Switch mode is unaffected.
  Filed as #432.

Also documented: the per-league display_options copy wins over the
defaults copy and several of its defaults differ -- show_shots_on_goal
is false under defaults and true under nhl -- which presents as a
setting that does nothing.

check_plugin.py passes with no FAIL.

Carries the docs-tooling changes from #423, which this render depends on.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
…rs (#430)

* docs(football-scoreboard): document all 177 settings, with real renders

The README was long but incomplete: it documented perhaps half the
settings, carried a "Version History" section still claiming v2.0.7
against a manifest at 3.4.1, and had no images. It now covers every
schema leaf -- verified by a token audit -- with seven renders.

Kept in full: dynamic team resolution and the AP_TOP_* warning, the
rotation and resume semantics, the measured turnover example, the
ranked/divisions reasoning including why broadcast was retired, the
worked example, and the celebration banner table.

Three things it did not say:

- The mode toggles here are show_live / show_recent / show_upcoming,
  not live / recent / upcoming as in every other scoreboard. A
  display_modes block copied across silently sets nothing.
- Five defaults differ between nfl and ncaa_fb -- enabled,
  live_game_duration, upcoming_games_to_show,
  other_upcoming_games_to_show and show_ranking. Documented as a table
  rather than duplicating 52 rows per league.
- The live card's down-and-distance, red-zone colour, possession icon
  and timeout pips were undocumented, and are the plugin's most
  distinctive output.

layout_mode: adaptive now has a side-by-side render against classic on
a 128x64 panel, which is where the difference actually shows.

Dropped the stale version-history section rather than correcting it;
the manifest versions array is the real record.

check_plugin.py passes 24/24 with no FAIL.

Carries the docs-tooling changes from #423, which this render depends on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(football-scoreboard): keep the ChuckBuilds links block

The rewrite collapsed the YouTube, Instagram, Discord and sponsorship
links into a shields badge. They are the project owner support links,
not decoration, so restore them verbatim at the top.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
)

The README covered roughly 55 of the plugin's 131 settings and had no
images at all. It now documents every schema leaf -- verified by a
token audit against config_schema.json -- and shows each of the three
display modes, the show_records toggle, and the card at four panel
sizes.

Three things the old README got wrong or left out:

- The seven selection limits (recent_games_to_show and friends) are
  declared twice, at the config root and under game_limits, and both
  are read. game_limits wins where present. Same for show_records /
  show_ranking / show_odds under display_options, and for
  show_favorite_teams_only under filtering. Setting the root copy while
  the nested one exists looks like a setting that does nothing.
- other_games_min_quality's documented choices included "broadcast",
  which the enum does not offer.
- The scroll_settings block is unreachable (#422); the README now says
  so rather than describing six knobs that do nothing.

mode_durations and customization.favorite_result_colors are read by the
core, not by this plugin, so they look dead to a grep of this tree. The
README now says where they are read.

Renders come from docs/assets/nrl-scoreboard/shots.json. The fixture
seeds games onto the per-mode sub-managers rather than the plugin --
every mode's display() returns early unless games_list is populated,
and the live path also needs live_games -- and passes display_mode,
without which the plugin's no-argument path selects nothing and draws a
blank panel. The bundled PEN/MEL logo files are grey placeholders, so
the crests render as blocks; the README says so.

check_plugin.py passes on all eight panel sizes for all three modes.

Carries the docs-tooling changes from #423 (display_mode shot key,
dotted-path attrs, *_path coercion), which this render depends on.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@ChuckBuilds
ChuckBuilds merged commit b48524d into main Sep 4, 2026
3 checks passed
@ChuckBuilds
ChuckBuilds deleted the fix/docs-attrs-nested branch September 4, 2026 20:26
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
…rs (#428)

* docs(lacrosse-scoreboard): document all 170 settings, with real renders

The README documented a fraction of the plugin's 170 settings and had no
images. It now covers every schema leaf -- verified by a token audit --
and shows the three men's display modes, both leagues side by side, the
show_records toggle, and the card at four panel sizes.

The two league blocks are identical in every setting except one:
ncaa_mens defaults to enabled and ncaa_womens does not. Documenting one
<league> table rather than two identical sets keeps 104 of the 170
leaves readable instead of duplicated.

Two things the old README did not say:

- display_options.show_records and show_ranking default to true per
  league, while defaults.show_records and defaults.show_ranking default
  to false, and the per-league copy wins. Because the UI writes schema
  defaults on save, changing the defaults copy appears to do nothing.
  Both the settings reference and troubleshooting now say so.
- The favorites key here is teams.favorite_teams_only, not
  show_favorite_teams_only as in the single-league scoreboards, and it
  defaults to off rather than on.

other_games_min_quality is live in this plugin -- NCAA lacrosse has a
national poll to rank against -- unlike in nrl, where it is inert.
other_games_divisions stays inert; it is a college football taxonomy.

Renders come from docs/assets/lacrosse-scoreboard/shots.json. Lacrosse
keeps one manager per league and mode as a plain attribute rather than
in a dict, and only builds them when that league's enabled flag is set,
so the fixture turns both leagues on. Real ESPN crests, not placeholders.

check_plugin.py passes with no FAIL.

Carries the docs-tooling changes from #423, which this render depends on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(lacrosse-scoreboard): keep the lax_ prefix upgrade notice

The rewrite dropped the breaking-change notice from 1.1.0, when the
display modes gained a lax_ prefix to stop colliding with the NCAA
hockey modes. Anyone still holding an old pin in display_durations or
rotation_order needs it, and the CHANGELOG mapping is the only other
place it is written down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(lacrosse-scoreboard): repair the manifest after the merge

The merge from main left the 1.24.2 entry holding both sides bodies --
two notes keys, two changelog keys, and a missing comma between them --
so manifest.json was not valid JSON and the version gate could not read
it.

Both sides had added a 1.24.2: main from the font fix in #409, this
branch from the README rewrite. Rebuilt from main manifest, which is
intact, with the docs change moved to a new 1.24.3 entry above it. That
keeps main 1.24.2 exactly as merged and follows the shape the other docs
PRs used on main -- the new entry carries the previous entry notes and
changelog plus its own changes array.

plugins.json regenerated: latest_version 1.24.3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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