fix(sports): auto-heal the stale "timezone": "UTC" left in saved configs - #237
Conversation
Users who updated to the previous release still saw UTC start times until they
hand-edited config.json, because the pre-fix write-back bug had already
persisted "timezone": "UTC" into their saved plugin config, where an explicit
plugin-level value outranks everything else.
That stale value is now detected and ignored automatically whenever the global
or system timezone disagrees, with a warning naming the source and zone it used
instead. No config edit, and no config *write* either -- this is a runtime
interpretation, so the plugin never mutates the dict the core owns (that
mutation is what created the mess in the first place).
The heuristic is scoped to the two plugins that could actually produce the
artifact. Only baseball-scoreboard and football-scoreboard ever wrote a
timezone back; in the other eight a plugin-level "UTC" can only have come from
the user, so it is honored verbatim. _HAD_WRITEBACK_BUG records this per plugin
and their tests assert the opposite behaviors.
Etc/UTC is the unambiguous opt-in for genuinely wanting UTC -- a spelling the
old bug could never have written, so it is always honored. Documented in the
schema description and README of the two affected plugins, and named in the
warning itself.
Also fixes a real gap in the previous release, in all ten plugins: the core's
ConfigManager.get_timezone() is self.config.get('timezone', 'UTC'), so it hands
back "UTC" for a global config that has no timezone key at all. Resolution took
that at face value and so could never reach the host system zone -- the very
backstop that release added. It now reads the raw config dict and treats an
absent key as absent.
Verified against the shipping core (ChuckBuilds/LEDMatrix @ e2acbfb): a new
_RealCoreConfigManager test double reproduces get_timezone()'s defaulting
behavior. 17 tests per plugin, 170 total. Module-collision check passes; the
nine pre-existing failures in these plugins are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSnspNZceRCdpUtJh2Co6e
📝 WalkthroughWalkthroughUpdated timezone resolution across ten scoreboard plugins. The changes distinguish missing global timezone keys from explicit UTC, validate timezone names, handle stale UTC artifacts, add regression coverage, and publish patch releases dated 2026-08-02. ChangesTimezone resolution updates
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant PluginResolver
participant GlobalConfig
participant SystemTimezone
PluginResolver->>GlobalConfig: Read raw timezone key
GlobalConfig-->>PluginResolver: Explicit value or missing key
PluginResolver->>SystemTimezone: Resolve downstream fallback
SystemTimezone-->>PluginResolver: IANA timezone
PluginResolver-->>PluginResolver: Validate and select first usable source
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 270 |
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.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
plugins/afl-scoreboard/afl_timezone.py (1)
134-147: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNarrow the blind
except Exceptionin_validated().Static analysis flags this as BLE001.
pytz.timezone()raisespytz.UnknownTimeZoneErrorfor invalid names; catching bareExceptionhere can silently swallow unrelated bugs (e.g. issues inside pytz internals) as "invalid timezone" instead of surfacing them.♻️ Proposed fix
try: pytz.timezone(name) - except Exception: + except pytz.UnknownTimeZoneError: log.warning("Ignoring invalid timezone %r from %s", name, source) return NoneSince this pattern is duplicated in the sibling
_timezone.pymodules for the other nine plugins (per the shared architecture), the same narrowing would apply there too if in scope.🤖 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/afl-scoreboard/afl_timezone.py` around lines 134 - 147, Replace the broad exception handler in _validated() with a catch for pytz.UnknownTimeZoneError only, preserving the existing warning and None return for invalid timezone names while allowing unrelated pytz errors to propagate. Apply the change only to this function unless sibling modules are explicitly in scope.Source: Linters/SAST tools
plugins/basketball-scoreboard/basketball_timezone.py (1)
56-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDead
_WRITEBACK_FIXED_INconstant in the seven plugins where the heuristic is disabled.These modules set
_HAD_WRITEBACK_BUG = False, so_WRITEBACK_FIXED_INis never read, and its comment ("Release that fixed the write-back") contradicts the module docstring stating the plugin never wrote back. Either drop the constant or reword the comment to say it is unused while the heuristic is off. Same copied-header issue: the"ESPN/MLB"phrasing at L3-6 in these non-baseball modules should name the actual feed.
plugins/basketball-scoreboard/basketball_timezone.py#L56-L58: remove the unused constant (or note it is inert) and fix"ESPN/MLB"at L3-6.plugins/f1-scoreboard/f1_timezone.py#L56-L58: same; F1 sessions do not come from MLB.plugins/hockey-scoreboard/hockey_timezone.py#L56-L58: same.plugins/lacrosse-scoreboard/lacrosse_timezone.py#L56-L58: same.plugins/nrl-scoreboard/nrl_timezone.py#L56-L58: same.plugins/soccer-scoreboard/soccer_timezone.py#L56-L58: same.plugins/ufc-scoreboard/ufc_timezone.py#L56-L58: same.🤖 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/basketball-scoreboard/basketball_timezone.py` around lines 56 - 58, Remove the unused _WRITEBACK_FIXED_IN constant and its misleading write-back comment from the timezone modules, while preserving _HAD_WRITEBACK_BUG = False. Apply this in plugins/basketball-scoreboard/basketball_timezone.py, plugins/f1-scoreboard/f1_timezone.py, plugins/hockey-scoreboard/hockey_timezone.py, plugins/lacrosse-scoreboard/lacrosse_timezone.py, plugins/nrl-scoreboard/nrl_timezone.py, plugins/soccer-scoreboard/soccer_timezone.py, and plugins/ufc-scoreboard/ufc_timezone.py; also replace the copied “ESPN/MLB” header wording in each module with the actual feed or sport.
🤖 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/README.md`:
- Around line 40-45: Update the stale-“UTC” troubleshooting guidance in the
README to state that saved "timezone": "UTC" values are automatically ignored
when they conflict with the global or system timezone, while Etc/UTC remains
honored; remove the outdated claim that these values override everything or
require manual clearing.
---
Nitpick comments:
In `@plugins/afl-scoreboard/afl_timezone.py`:
- Around line 134-147: Replace the broad exception handler in _validated() with
a catch for pytz.UnknownTimeZoneError only, preserving the existing warning and
None return for invalid timezone names while allowing unrelated pytz errors to
propagate. Apply the change only to this function unless sibling modules are
explicitly in scope.
In `@plugins/basketball-scoreboard/basketball_timezone.py`:
- Around line 56-58: Remove the unused _WRITEBACK_FIXED_IN constant and its
misleading write-back comment from the timezone modules, while preserving
_HAD_WRITEBACK_BUG = False. Apply this in
plugins/basketball-scoreboard/basketball_timezone.py,
plugins/f1-scoreboard/f1_timezone.py,
plugins/hockey-scoreboard/hockey_timezone.py,
plugins/lacrosse-scoreboard/lacrosse_timezone.py,
plugins/nrl-scoreboard/nrl_timezone.py,
plugins/soccer-scoreboard/soccer_timezone.py, and
plugins/ufc-scoreboard/ufc_timezone.py; also replace the copied “ESPN/MLB”
header wording in each module with the actual feed or sport.
🪄 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 Plus
Run ID: 09330a16-5fea-42db-86b2-060b1c64c84e
📒 Files selected for processing (38)
plugins.jsonplugins/afl-scoreboard/afl_timezone.pyplugins/afl-scoreboard/manifest.jsonplugins/afl-scoreboard/test_timezone_resolution.pyplugins/baseball-scoreboard/CHANGELOG.mdplugins/baseball-scoreboard/README.mdplugins/baseball-scoreboard/baseball_timezone.pyplugins/baseball-scoreboard/config_schema.jsonplugins/baseball-scoreboard/manifest.jsonplugins/baseball-scoreboard/test_timezone_resolution.pyplugins/basketball-scoreboard/basketball_timezone.pyplugins/basketball-scoreboard/manifest.jsonplugins/basketball-scoreboard/test_timezone_resolution.pyplugins/f1-scoreboard/f1_timezone.pyplugins/f1-scoreboard/manifest.jsonplugins/f1-scoreboard/test_timezone_resolution.pyplugins/football-scoreboard/CHANGELOG.mdplugins/football-scoreboard/README.mdplugins/football-scoreboard/config_schema.jsonplugins/football-scoreboard/football_timezone.pyplugins/football-scoreboard/manifest.jsonplugins/football-scoreboard/test_timezone_resolution.pyplugins/hockey-scoreboard/hockey_timezone.pyplugins/hockey-scoreboard/manifest.jsonplugins/hockey-scoreboard/test_timezone_resolution.pyplugins/lacrosse-scoreboard/CHANGELOG.mdplugins/lacrosse-scoreboard/lacrosse_timezone.pyplugins/lacrosse-scoreboard/manifest.jsonplugins/lacrosse-scoreboard/test_timezone_resolution.pyplugins/nrl-scoreboard/manifest.jsonplugins/nrl-scoreboard/nrl_timezone.pyplugins/nrl-scoreboard/test_timezone_resolution.pyplugins/soccer-scoreboard/manifest.jsonplugins/soccer-scoreboard/soccer_timezone.pyplugins/soccer-scoreboard/test_timezone_resolution.pyplugins/ufc-scoreboard/manifest.jsonplugins/ufc-scoreboard/test_timezone_resolution.pyplugins/ufc-scoreboard/ufc_timezone.py
…EADME Review feedback from CodeRabbit on #237, all four valid: - _validated() caught bare Exception around pytz.timezone(). Now catches UnknownTimeZoneError for the expected case; anything else is logged with exc_info instead of being silently reclassified as "invalid timezone". Kept non-propagating rather than letting it escape: this runs in the render path, and a mislabelled zone beats taking the display down. - _WRITEBACK_FIXED_IN is inert in the eight plugins with _HAD_WRITEBACK_BUG = False, where its old comment ("release that fixed the write-back") also contradicted the docstring saying the plugin never wrote back. Reworded to say it is inert and why it stays -- removing it would leave the shared resolver body referencing an undefined name if the flag were ever flipped. - The nine non-baseball modules claimed start times "arrive from ESPN/MLB", copied from baseball; they are ESPN-only. F1 and UFC additionally described a scroll-mode game_renderer.py neither plugin ships, and called their sessions/fights "games". - baseball README troubleshooting still told users to clear a stuck "timezone": "UTC" by hand and said an explicit value "overrides everything else" -- both untrue as of 1.20.1, and directly contradicting the note added earlier in the same file. No version bump: these plugins are already bumped against main in this PR. 170 tests still pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QSnspNZceRCdpUtJh2Co6e
|
All four review points were valid — fixed in README troubleshooting (the actionable one). Correct, and worse than a stale doc: the section told users to clear a stuck Blind Inert Copied Not addressing the Docstring Coverage 47.34% pre-merge warning. The bulk of the diff is test functions whose names are self-describing and which already carry docstrings where the intent isn't obvious; adding boilerplate to reach an 80% threshold would add noise, not information. Happy to revisit if that threshold reflects a repo standard rather than a bot default. No version bump for this commit — these ten plugins are already bumped against Generated by Claude Code |
Resolved version-mechanics conflicts: manifests and plugins.json taken from main (versions corrected in the follow-up commit); the two scoreboard CHANGELOGs keep main's history with the timezone-fix note re-headed to the new patch version. The *_timezone.py fix content applied cleanly.
Corrects the stale version bumps from #237's original commits, which targeted numbers main has since passed (via #234/#239) — 4 were collisions and 2 were downgrades. Each is now one patch above main's current version: baseball 1.20.2->1.20.3, football 2.9.2->2.9.3, hockey 1.5.2->1.5.3, basketball 1.8.2->1.8.3, soccer 2.5.1->2.5.2, ufc 1.3.1->1.3.2, lacrosse 1.5.1->1.5.2, nrl 1.1.1->1.1.2, afl 1.1.1->1.1.2, f1 1.7.1->1.7.2 Patch, per CONTRIBUTING.md: a bug fix with no schema additions. plugins.json regenerated; verified every version is strictly above main and the registry matches all ten manifests. The 10 test_timezone_resolution.py suites pass (17 each). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/afl-scoreboard/manifest.json (1)
56-56: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winUpdate the manifest freshness date for the new releases.
manifest.jsonlast_updatedmust match the latest version release date. These scoreboard manifests release2026-08-02, but still recordlast_updateddates from July.
plugins/afl-scoreboard/manifest.json#L56-L56: Setlast_updatedto2026-08-02.plugins/baseball-scoreboard/manifest.json#L296-L296: Setlast_updatedto2026-08-02.plugins/basketball-scoreboard/manifest.json#L124-L124: Setlast_updatedto2026-08-02.plugins/f1-scoreboard/manifest.json#L241-L241: Setlast_updatedto2026-08-02.plugins/football-scoreboard/manifest.json#L166-L166: Setlast_updatedto2026-08-02.plugins/hockey-scoreboard/manifest.json#L178-L178: Setlast_updatedto2026-08-02.plugins/lacrosse-scoreboard/manifest.json#L118-L118: Setlast_updatedto2026-08-02.plugins/nrl-scoreboard/manifest.json#L68-L68: Setlast_updatedto2026-08-02.plugins/soccer-scoreboard/manifest.json#L161-L161: Setlast_updatedto2026-08-02.plugins/ufc-scoreboard/manifest.json#L98-L98: Setlast_updatedto2026-08-02.🤖 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/afl-scoreboard/manifest.json` at line 56, Update the last_updated manifest field to the latest release date, 2026-08-02, in plugins/afl-scoreboard/manifest.json:56-56, plugins/baseball-scoreboard/manifest.json:296-296, plugins/basketball-scoreboard/manifest.json:124-124, plugins/f1-scoreboard/manifest.json:241-241, plugins/football-scoreboard/manifest.json:166-166, plugins/hockey-scoreboard/manifest.json:178-178, plugins/lacrosse-scoreboard/manifest.json:118-118, plugins/nrl-scoreboard/manifest.json:68-68, plugins/soccer-scoreboard/manifest.json:161-161, and plugins/ufc-scoreboard/manifest.json:98-98.
🤖 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.
Outside diff comments:
In `@plugins/afl-scoreboard/manifest.json`:
- Line 56: Update the last_updated manifest field to the latest release date,
2026-08-02, in plugins/afl-scoreboard/manifest.json:56-56,
plugins/baseball-scoreboard/manifest.json:296-296,
plugins/basketball-scoreboard/manifest.json:124-124,
plugins/f1-scoreboard/manifest.json:241-241,
plugins/football-scoreboard/manifest.json:166-166,
plugins/hockey-scoreboard/manifest.json:178-178,
plugins/lacrosse-scoreboard/manifest.json:118-118,
plugins/nrl-scoreboard/manifest.json:68-68,
plugins/soccer-scoreboard/manifest.json:161-161, and
plugins/ufc-scoreboard/manifest.json:98-98.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 77288237-4ea5-4367-a08f-583e011b2470
📒 Files selected for processing (26)
plugins.jsonplugins/afl-scoreboard/afl_timezone.pyplugins/afl-scoreboard/manifest.jsonplugins/baseball-scoreboard/CHANGELOG.mdplugins/baseball-scoreboard/README.mdplugins/baseball-scoreboard/baseball_timezone.pyplugins/baseball-scoreboard/config_schema.jsonplugins/baseball-scoreboard/manifest.jsonplugins/basketball-scoreboard/basketball_timezone.pyplugins/basketball-scoreboard/manifest.jsonplugins/f1-scoreboard/f1_timezone.pyplugins/f1-scoreboard/manifest.jsonplugins/football-scoreboard/CHANGELOG.mdplugins/football-scoreboard/config_schema.jsonplugins/football-scoreboard/football_timezone.pyplugins/football-scoreboard/manifest.jsonplugins/hockey-scoreboard/hockey_timezone.pyplugins/hockey-scoreboard/manifest.jsonplugins/lacrosse-scoreboard/lacrosse_timezone.pyplugins/lacrosse-scoreboard/manifest.jsonplugins/nrl-scoreboard/manifest.jsonplugins/nrl-scoreboard/nrl_timezone.pyplugins/soccer-scoreboard/manifest.jsonplugins/soccer-scoreboard/soccer_timezone.pyplugins/ufc-scoreboard/manifest.jsonplugins/ufc-scoreboard/ufc_timezone.py
🚧 Files skipped from review as they are similar to previous changes (12)
- plugins/baseball-scoreboard/config_schema.json
- plugins/football-scoreboard/manifest.json
- plugins/baseball-scoreboard/README.md
- plugins/football-scoreboard/config_schema.json
- plugins/baseball-scoreboard/baseball_timezone.py
- plugins.json
- plugins/afl-scoreboard/afl_timezone.py
- plugins/nrl-scoreboard/nrl_timezone.py
- plugins/football-scoreboard/football_timezone.py
- plugins/lacrosse-scoreboard/lacrosse_timezone.py
- plugins/f1-scoreboard/f1_timezone.py
- plugins/ufc-scoreboard/ufc_timezone.py
Minor bumps (new user-facing feature) for the seven plugins #235 actually changes, each above main's post-#237 version: afl 1.2.0, baseball 1.21.0, basketball 1.9.0, football 2.10.0, hockey 1.6.0, lacrosse 1.6.0, nrl 1.2.0 odds-ticker is intentionally NOT bumped: its only change in #235 was the NHL picker correction (UTA->UTAH, +Seattle) that came from #234 and is already in main, so it has no net change here. The original bump list also targeted numbers main has since passed via #234/#236/#237/#239; corrected. plugins.json regenerated; every changed plugin is strictly above main. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
* Fix wrong ESPN team codes in pickers and help text
Several plugins documented — or in one case offered in a picker — team
abbreviations that ESPN does not use, so copying them matched no team and
the plugin silently showed nothing.
odds-ticker's NHL picker was the only one where the user could not work
around it: it listed UTA (a retired code, labelled with the club's former
name "Utah Hockey Club" rather than "Utah Mammoth") and omitted the
Seattle Kraken entirely, so that team could not be selected at all. The
enum and labels are now generated from ESPN's team endpoint and match it
exactly at 32 teams.
The rest are description-only corrections to the favorite_teams examples:
basketball NBA GSW -> GS (Golden State Warriors)
basketball WNBA NYL -> NY (New York Liberty)
basketball WNBA LAS -> LA (Los Angeles Sparks)
basketball NCAAW UCONN -> CONN (UConn Huskies)
basketball NCAAW SCAR -> SC (South Carolina Gamecocks)
football NCAAFB BAMA -> ALA (Alabama Crimson Tide)
hockey NCAAWH WISC -> WIS (Wisconsin Badgers)
Each description now also says these are ESPN's codes and are not always
the ones you would guess, since that is the underlying trap.
Every code here was verified against
site.api.espn.com/apis/site/v2/sports/{sport}/{league}/teams?limit=1000.
The limit matters: without it the default page size truncates the NCAA
responses (362 of 755 teams) and makes valid codes look wrong.
Left alone deliberately: lacrosse-scoreboard's WISC/MINN/OSU and
BU/BC/MICH examples, and baseball-scoreboard's MiLB DUR/SWB/NOR, because
ESPN's lacrosse team endpoints return zero teams and the MiLB one 404s.
Unverifiable, so not guessed at.
No rendering code changed; the safety harness passes for all four plugins
at every size.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
* Explain an empty scoreboard instead of leaving the user guessing
Favorite teams are matched by exact ESPN abbreviation, so a code that is
not real matches no game and the plugin shows nothing — with no hint that
the code is the problem. Out of season, a perfectly correct code produces
the identical empty screen. The two were indistinguishable from the logs,
which is how a user ends up asking whether their config is broken when it
is only July.
Each of these plugins now says which case it is:
WARNING NFL favorite team 'GBP' is not a NFL team code. Closest match
is 'GB' (Green Bay Packers). Every code this league accepts is
listed at https://site.api.espn.com/.../nfl/teams?limit=1000.
INFO NFL favorite teams TB look correct, but the league has nothing
on until 06 August 2026. An empty display until then is
expected, not a configuration problem.
INFO NCAA Baseball favorite teams UGA look correct, but the season
has finished and the next one's fixtures are not published yet.
Suggestions rank word-initial matches first, because string similarity is
useless at three characters: 'MUN' scores identically against 'MAN' and
'SUN', so Manchester United and Sunderland tie and the answer is a coin
flip. Fragments are handled too ('BAMA' is inside 'Alabama' but
abbreviates nothing in it), and a code that only differs in case is told
so rather than guessed at.
Reading the schedule turned out to be the subtle part, and both traps are
real ESPN behaviour confirmed against live endpoints:
- An out-of-season league does not return an empty scoreboard. ESPN rolls
forward to the next day with fixtures, so in July the NHL endpoint
returns seven September games. Emptiness cannot be the signal.
- A *finished* season rolls nowhere and returns its last game instead,
months in the past — so dates must be filtered before the soonest one
means anything. Filtering on "later than now" then wrongly drops games
that started earlier today and reports a live slate as a dead season,
so the window is the last 24 hours.
Verified against every league these plugins cover: MLB, AFL, NRL and WNBA
correctly stay quiet; NFL, NCAA football, NHL, NBA and NCAA men's
basketball report their start dates; NCAA baseball and NCAA women's
hockey report finished seasons.
Safety, since this runs inside update():
- It runs on a daemon thread, so it never delays a frame.
- Once per league per process, re-armed only when the config changes.
- Every failure path is swallowed to a debug line. A plugin whose ESPN
endpoint returns no teams at all (college lacrosse) draws no conclusion
rather than calling a valid code wrong.
Each plugin ships its own copy of the module, since the loader gives
plugins no shared library to import from, under a plugin-unique name per
the module-collision rule. A test asserts the copies stay byte-identical
while they live in one checkout.
Tested: 30 unit tests; safety harness 24/24 PASS per plugin (168 renders,
zero failures); module-collision check clean. Validated end-to-end on real
hardware, where all four message paths appeared as intended with no errors.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
* chore: version the favorite-team diagnostics above current main
Minor bumps (new user-facing feature) for the seven plugins #235 actually
changes, each above main's post-#237 version:
afl 1.2.0, baseball 1.21.0, basketball 1.9.0, football 2.10.0,
hockey 1.6.0, lacrosse 1.6.0, nrl 1.2.0
odds-ticker is intentionally NOT bumped: its only change in #235 was the NHL
picker correction (UTA->UTAH, +Seattle) that came from #234 and is already in
main, so it has no net change here. The original bump list also targeted
numbers main has since passed via #234/#236/#237/#239; corrected. plugins.json
regenerated; every changed plugin is strictly above main.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
---------
Co-authored-by: Claude <noreply@anthropic.com>
Pull Request
Summary
Follow-up to #228. Users who updated to those versions still saw UTC start times until they hand-edited
config.json, because the pre-fix write-back bug had already persisted"timezone": "UTC"into their saved plugin config — and an explicit plugin-level value outranks everything else. #228 stopped the plugin writing that value but did nothing about the copies already on disk.That stale value is now detected and ignored automatically. No manual edit, and no config write either — this is a runtime interpretation, so the plugin never mutates the dict the core owns. That mutation is what created the mess in the first place.
Also fixes a real gap in #228 affecting all ten plugins (see below) — the system-zone fallback it added could never actually be reached.
Type of change
Plugin(s) affected
All ten from #228, patch bumps: baseball 1.20.1, football 2.9.1, hockey 1.5.1, basketball 1.8.1, soccer 2.4.1, ufc 1.3.1, lacrosse 1.5.1, nrl 1.1.1, afl 1.1.1, f1 1.6.1.
Related issues
Follow-up to #228 — user reports of the leftover
"UTC"after updating.What changed
1. The stale-artifact heuristic, scoped to the two plugins that can have one
A bare
"UTC"is honored only when nothing downstream disagrees. If the global or system timezone says otherwise, it's treated as the artifact it almost certainly is — it only ever got written on resolution failure — and the downstream value wins, with a warning naming both.Only
baseball-scoreboardandfootball-scoreboardever wrote a timezone back. In the other eight a plugin-level"UTC"can only have come from the user, so overriding it would be plain wrong; there it's honored verbatim._HAD_WRITEBACK_BUGrecords this per plugin, and the two groups' test suites assert opposite behavior for the same input.Etc/UTCis the unambiguous opt-in for genuinely wanting UTC — a spelling the old bug could never have produced, so it's always honored. It's in the schema description, the README, and named in the warning itself.2.
ConfigManager.get_timezone()'s own default was masking a missing global settingVerified against the shipping core (
ChuckBuilds/LEDMatrix@e2acbfb):It returns
"UTC"for a config with notimezonekey at all, indistinguishable from a deliberate choice. #228 took that at face value, so resolution latched onto it and could never reach the host system zone — the very backstop #228 added. Resolution now reads the raw config dict (get_config()/load_config(), both cheap —get_configreturns the in-memory dict andload_confighas an mtime fast path) and treats an absent key as absent.get_timezone()is consulted only for cores exposing no raw config.This one applies to all ten plugins and is arguably the more consequential of the two fixes.
Test plan
EMULATOR=true python3 run.py)scripts/dev_server.py)17 tests per plugin, 170 total, all passing. Six are new, including a
_RealCoreConfigManagerdouble that reproduces the shipping core'sget_timezone()defaulting behavior rather than an idealized version:"UTC"ignored when the global config disagrees / when only the system zone disagrees"UTC"kept when the global and system zone agree — a genuinely-UTC device is never dragged off UTCEtc/UTChonored even against a disagreeing globaltimezonekey falls through to the system zone (the fix(sports): stop rendering event start times in UTC (baseball + 9 plugins) #228 gap)"UTC"is never overridden, and beats the system zoneAlso run:
check_module_collisions.pyOK across 42 plugins; everytest_*.pyin all ten plugins before and after — the nine that fail need the coresrcpackage and font assets absent here and fail identically onmain.Not verified on hardware or in the emulator.
Required for plugin changes
versioninplugins/<id>/manifest.json— all ten, each with aversions[]entryclass_namematches the actual class inmanager.py(unchanged)entry_pointmatches the real file (unchanged)README.mdif config keys changed — baseball and football document the leftover-UTCbehavior and theEtc/UTCopt-inconfig_schema.jsonis the source of truth — no new keys; thetimezonedescription now covers theEtc/UTCopt-in on the two affected pluginsplugins.json)Checklist
CONTRIBUTING.mdCONTRIBUTING.mdandCODE_OF_CONDUCT.mdNotes for reviewer
The heuristic is the deliberate trade-off here. A bare
"UTC"genuinely is ambiguous, and I said as much in #228 when I chose not to auto-heal. What changed is evidence: real users are hitting it, and the artifact only ever appeared on resolution failure, which makes "UTC while your global says Chicago" overwhelmingly likely to be the bug rather than intent. The heuristic is gated three ways to keep the blast radius tight — only the two plugins that could produce it, only when something downstream actively disagrees, and never againstEtc/UTC.Why not a config migration that deletes the line. A plugin can't durably remove it:
BasePluginhas no save-config hook, and the web UI's save path replaces each plugin block wholesale from the schema-generated form, so an in-memory delete gets overwritten on the next save. Deleting it properly belongs in the core'sConfigManageras a one-shot migration with a persisted marker — cleaner, and it would tidyconfig.jsonfor real, but it needs a core release and reaches users only when they update the core. This ships through the plugin store today. The two aren't exclusive; the core migration would still be worth doing.A note on #228's root-cause story. Reading the core confirmed
CacheManagerdoes construct its ownConfigManager, socache_manager.config_managernormally exists — meaning the plugin-manager lookup #228 added may not have been the operative fix for every reporter. Theget_timezone()defaulting bug fixed here is a likelier explanation for some of them: any install whose global config lacks atimezonekey got"UTC"from the core and had no path to the system zone. Both fixes are correct and independent; I'm flagging it so the #228 changelog entry isn't read as the whole story.Generated by Claude Code
Summary by CodeRabbit
Bug Fixes
UTCvalues are ignored when conflicting with current settings, while intentionalUTCandEtc/UTCchoices remain supported.Documentation
Updates