Skip to content

feat(calendar): dynamic Google Calendar picker for multi-calendar selection#45

Merged
ChuckBuilds merged 9 commits intomainfrom
fix/timezone-display
Mar 9, 2026
Merged

feat(calendar): dynamic Google Calendar picker for multi-calendar selection#45
ChuckBuilds merged 9 commits intomainfrom
fix/timezone-display

Conversation

@ChuckBuilds
Copy link
Copy Markdown
Owner

@ChuckBuilds ChuckBuilds commented Feb 25, 2026

Summary

  • Adds get_calendars() method to the calendar plugin using calendarList().list() — returns all calendars accessible via the authenticated Google account
  • Updates config_schema.json to use x-widget: google-calendar-picker on the calendars field, triggering the new dynamic UI in the LEDMatrix web interface
  • Improves per-calendar error log messages to include guidance when a calendar ID is wrong
  • Bumps version to 1.0.2

Why

Users with multiple Google Calendars had no way to know their correct calendar IDs, and errors fetching non-primary calendars were silently swallowed. This makes selection self-service and failures visible.

Pairs with LEDMatrix core PR: ChuckBuilds/LEDMatrix (fix/timezonefinder-tmp-space branch)

Test plan

  • Install calendar plugin v1.0.2 from store
  • Authenticate with Google, open plugin config
  • Click "Load My Calendars" — all Google Calendars appear as checkboxes
  • Select multiple calendars, save, restart service
  • Confirm journalctl -u ledmatrix | grep calendar shows fetches from each selected calendar
  • Confirm events from non-primary calendars appear on display

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Calendar: "Load My Calendars" picker to select Google calendars.
    • Countdown: revamped normalization, image handling, today-highlight styling and improved display behaviors.
    • F1 scoreboard: race times now shown in local timezone.
  • Bug Fixes

    • Improved timezone resolution and expanded error logging for more reliable behavior.
    • Calendar: better error reporting when fetching calendars.
  • Updates

    • Many plugin versions bumped and manifests updated for compatibility.

Chuck and others added 3 commits February 25, 2026 10:36
… display

- All sports plugins: use America/New_York (with DST) instead of naive
  datetime.now() for ESPN API date queries, plus 1-day lookback to catch
  late-night games still in progress — matching the fix already in LEDMatrix
  core (commit cd0c43fb). ESPN anchors its schedule calendar to Eastern US
  time regardless of where the user is located.

- Baseball: inject resolved timezone (plugin config → global config → UTC)
  into self.config early in manager __init__ so game_renderer.py and
  scroll_display.py both get the correct timezone. Fix game_renderer.py
  fallback from 'US/Eastern' to 'UTC' (fallback is now never reached for
  users with a global config timezone set).

- F1: inject resolved timezone into config before constructing F1Renderer.
  Add _to_local_dt() helper to F1Renderer and use it in render_upcoming_race()
  and render_calendar_entry() so session/race dates display in local time
  instead of UTC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- baseball/manager.py: use getattr + targeted AttributeError/TypeError
  instead of broad Exception; log debug on unavailable global timezone
- baseball/game_renderer.py: catch pytz.UnknownTimeZoneError around
  pytz.timezone() and log a warning with the bad tz name before
  falling back to UTC
- basketball/sports.py: update debug log to show full date range
  (formatted_date_yesterday-formatted_date) instead of only today
- f1/requirements.txt: add pytz>=2024.1 (used in f1_renderer.py)
- f1/f1_renderer.py: narrow except Exception → pytz.exceptions.UnknownTimeZoneError
  in _to_local_dt so unexpected errors still surface
- f1/manager.py: extract _resolve_timezone() helper and call it from
  both __init__ and on_config_change() so the renderer always receives
  the correct timezone when config is hot-reloaded

Versions: baseball 1.4.2, basketball 1.4.2, f1 1.1.2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… support

Adds a get_calendars() method to the calendar plugin that calls the
Google calendarList API, enabling users to select calendars from a
dynamic checklist instead of manually typing IDs. Improves error
logging for failed per-calendar fetches.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Updates across multiple plugins: manifest version bumps, sports APIs now query an Eastern Time two-day window, added/expanded timezone error logging, calendar plugin gains a get_calendars() method and schema picker, F1 rendering gets timezone conversion, and countdown plugin receives a large normalization and caching refactor.

Changes

Cohort / File(s) Summary
Manifests & Registry
plugins.json, plugins/*-scoreboard/manifest.json
Bumped top-level plugin versions to new releases (ledmatrix_min 2.0.0 entries, released 2026-03-02), updated last_updated fields and added new versions entries across many plugins.
Sports — date range & TZ
plugins/baseball-scoreboard/sports.py, plugins/football-scoreboard/sports.py, plugins/hockey-scoreboard/sports.py
_fetch_todays_games now anchors to America/New_York, computes yesterday and today, and queries APIs with a "{yesterday}-{today}" date range (limit increased).
Timezone resolution error handling
plugins/baseball-scoreboard/manager.py, plugins/f1-scoreboard/manager.py
Added broader exception catching around global timezone resolution; logs unexpected exceptions with logger.exception while preserving UTC fallback.
Calendar — API & schema
plugins/calendar/manager.py, plugins/calendar/config_schema.json
Added public get_calendars() to return Google Calendar list (id, summary, primary) with exception handling; updated config schema with x-widget: "google-calendar-picker" and improved calendars description.
F1 — renderer timezone conversion
plugins/f1-scoreboard/f1_renderer.py
Added _to_local_dt(utc_iso_str) using pytz to convert UTC ISO datetimes to configured local timezone (UTC fallback); replaced direct fromisoformat usages with helper.
Baseball manager init
plugins/baseball-scoreboard/manager.py
Expanded exception handling in init when reading global timezone to include general exceptions and log stack traces.
Countdown — major refactor
plugins/countdown/manager.py, plugins/countdown/README.md
Large normalization and validation workflow for countdown configs: ID generation, _parse_bool, signature-based cache invalidation, per-countdown image caching, font (re)registration, improved error displays, and many runtime/formatting robustness fixes; README text updated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the primary feature addition: dynamic Google Calendar picker for multi-calendar selection. This is the main change across the calendar plugin.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/timezone-display

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/hockey-scoreboard/sports.py (1)

856-858: ⚠️ Potential issue | 🟡 Minor

Stale log message after date-range change.

The fetch now covers a 2-day range (yesterday → today) but the log still reads "todays games", which will mislead debugging.

🛠️ Proposed fix
-            self.logger.info(
-                f"Fetched {len(events)} todays games for {self.sport} - {self.league}"
-            )
+            self.logger.info(
+                f"Fetched {len(events)} games ({formatted_date_yesterday}-{formatted_date}) "
+                f"for {self.sport} - {self.league}"
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/hockey-scoreboard/sports.py` around lines 856 - 858, Update the stale
log message that still says "todays games": change the self.logger.info call
that uses f"Fetched {len(events)} todays games for {self.sport} - {self.league}"
to accurately describe the new 2-day range (e.g., "fetched {len(events)} games
for yesterday and today" or "fetched {len(events)} games for the last 2 days")
so logs reflect the actual fetch window; locate the self.logger.info line in the
method that populates events and replace the wording while keeping the same
variables ({len(events)}, self.sport, self.league).
🧹 Nitpick comments (2)
plugins/baseball-scoreboard/sports.py (1)

851-865: Update logging to reflect the actual dates range.

This block now queries yesterday-today, but the debug log still says “on date …” (single day).

Proposed tweak
             formatted_date = now.strftime("%Y%m%d")
             formatted_date_yesterday = yesterday.strftime("%Y%m%d")
+            date_range = f"{formatted_date_yesterday}-{formatted_date}"
             # Fetch todays games only
             url = f"https://site.api.espn.com/apis/site/v2/sports/{self.sport}/{self.league}/scoreboard"
-            self.logger.debug(f"Fetching today's games for {self.sport}/{self.league} on date {formatted_date}")
+            self.logger.debug(
+                f"Fetching games for {self.sport}/{self.league} on ESPN date range {date_range} (ET)"
+            )
             response = self.session.get(
                 url,
-                params={"dates": f"{formatted_date_yesterday}-{formatted_date}", "limit": 1000},
+                params={"dates": date_range, "limit": 1000},
                 headers=self.headers,
                 timeout=10,
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/baseball-scoreboard/sports.py` around lines 851 - 865, The debug log
in the ESPN schedule fetch incorrectly states a single date while the request
uses a date range; update the logger.debug call in the fetch block (where tz,
now, yesterday, formatted_date, formatted_date_yesterday and url are defined) to
include the full dates range (e.g., formatted_date_yesterday and formatted_date
or the actual "dates" param) so the message accurately reflects the params sent
to self.session.get.
plugins/football-scoreboard/sports.py (1)

851-865: Align the debug message with the new date-range request.

The request now sends a yesterday-today range, but the debug log still reports a single date. This makes troubleshooting harder.

Proposed tweak
             formatted_date = now.strftime("%Y%m%d")
             formatted_date_yesterday = yesterday.strftime("%Y%m%d")
+            date_range = f"{formatted_date_yesterday}-{formatted_date}"
             # Fetch todays games only
             url = f"https://site.api.espn.com/apis/site/v2/sports/{self.sport}/{self.league}/scoreboard"
-            self.logger.debug(f"Fetching today's games for {self.sport}/{self.league} on date {formatted_date}")
+            self.logger.debug(
+                f"Fetching games for {self.sport}/{self.league} on ESPN date range {date_range} (ET)"
+            )
             response = self.session.get(
                 url,
-                params={"dates": f"{formatted_date_yesterday}-{formatted_date}", "limit": 1000},
+                params={"dates": date_range, "limit": 1000},
                 headers=self.headers,
                 timeout=10,
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/football-scoreboard/sports.py` around lines 851 - 865, The debug
message incorrectly states "Fetching today's games..." while the request uses a
date range; update the logger.debug in the method that builds the ESPN
scoreboard request to reflect the actual params by including both
formatted_date_yesterday and formatted_date (or the combined range string used
in params) so the log shows the full
"{formatted_date_yesterday}-{formatted_date}" range for the GET to url
(referencing tz, now, yesterday, formatted_date, formatted_date_yesterday, and
self.logger.debug).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins.json`:
- Around line 175-178: Update the calendar plugin's version to 1.1.0 to reflect
the new get_calendars() feature and google-calendar-picker schema change: change
the "version" field in the calendar plugin manifest (the manifest's version
entry for the calendar plugin) to "1.1.0" and update the "latest_version" key
currently set to "1.0.2" in the plugins registry (the plugins.json entry for the
calendar plugin) to "1.1.0"; also search for other occurrences of "1.0.2"
related to the calendar plugin and replace them with "1.1.0".

In `@plugins/baseball-scoreboard/manager.py`:
- Around line 96-108: The timezone lookup currently only catches AttributeError
and TypeError when calling config_manager.get_timezone(), which can allow other
exceptions to crash init; modify the try/except around the get_timezone() call
(the block that references cache_manager, config_manager.get_timezone(), and
self.config) to catch a broader Exception, log the exception details via
self.logger.debug or self.logger.exception (including the error message), and
then continue to set self.config["timezone"] = global_tz or "UTC" so
initialization remains fail-safe.

In `@plugins/baseball-scoreboard/manifest.json`:
- Line 4: Update manifest.json so the "versions" array includes the new "1.5.4"
entry as the first (most recent) element and ensure the top entry matches the
"version" field; prepend "1.5.4" to the "versions" array (the array named
versions in manifest.json) so the array's first item and the "version" property
are in sync.

In `@plugins/basketball-scoreboard/manifest.json`:
- Line 4: The manifest.json's top-level "version" value was changed to "1.5.4"
but the "versions" array still lists "1.5.3" as the most recent; update the
"versions" array so its first entry is "1.5.4" (prepend the new version at the
top) to keep the "version" field and the "versions" array in sync, ensuring the
newest release appears first.

In `@plugins/calendar/config_schema.json`:
- Around line 31-32: The plugin added a new feature ("x-widget":
"google-calendar-picker" in config_schema.json) so update the plugin manifest to
a MINOR bump: open the plugin's manifest.json and change the current patch
release to the next minor (e.g., 1.1.0) and prepend that new version string to
the "versions" array so the release history reflects a minor-version addition
rather than a patch.

In `@plugins/calendar/manager.py`:
- Around line 373-385: New feature get_calendars was added and config schema
changes were included, so bump the MINOR version (increment the middle digit and
set patch to 0) for this plugin; locate the plugin's config_schema.json and any
version metadata associated with the manager (reference: get_calendars in
manager.py) and update the "version" field from X.Y.Z to X.(Y+1).0 (and update
any release/changelog entries if present) to reflect a MINOR release.
- Around line 373-385: Wrap the Google API call inside get_calendars in a
try/except so API failures don't bubble up: surround the call to
self.service.calendarList().list().execute() (and subsequent processing) with a
try block, catch Exception as e, log the error (using an existing logger on the
class, e.g. self.logger.exception or module logging) with context like "Failed
to fetch calendar list", and return an empty list [] to degrade gracefully while
keeping the existing calendar item mapping logic unchanged.
- Line 433: Replace the self.logger.error call in the except block that
currently logs "Error fetching events from calendar '{calendar_id}': {e} —
verify this calendar ID is correct and accessible under your Google account"
with self.logger.exception so the traceback is preserved; locate the call to
self.logger.error in plugins/calendar/manager.py (the one referencing
calendar_id and variable e, likely inside the calendar event fetch method) and
change it to self.logger.exception with the same descriptive message so
exception context is included in the logs.

In `@plugins/calendar/manifest.json`:
- Line 4: Update the plugin manifest version to a MINOR release: change the
"version" value in plugins/calendar/manifest.json from "1.0.2" to "1.1.0" (and
update any other identical occurrences around the same file referenced in the
review, e.g., the repeated entries at lines 36-40) to reflect this
feature/schema addition; ensure the version string is exactly "1.1.0" and no
other metadata is modified.

In `@plugins/f1-scoreboard/f1_renderer.py`:
- Around line 81-89: The _to_local_dt function must defensively handle naive
datetimes returned by datetime.fromisoformat: after parsing
(datetime.fromisoformat) check if dt.tzinfo is None and, if so, explicitly
assign/normalize it to UTC (use pytz.UTC or an equivalent UTC tz) before calling
dt.astimezone(local_tz); keep the existing logic that resolves local_tz via
self.config.get and falls back to pytz.UTC on UnknownTimeZoneError
(pytz.timezone and pytz.UTC), then call astimezone(local_tz) on the now-aware
datetime.

In `@plugins/f1-scoreboard/manager.py`:
- Around line 109-121: The _resolve_timezone method can crash if
config_manager.get_timezone() raises exceptions other than
AttributeError/TypeError; update the try/except around
config_manager.get_timezone() in _resolve_timezone to catch Exception as e (or a
broader set that includes runtime errors), log the caught exception (e.g.,
self.logger.debug or self.logger.exception with the error message and context)
and then fall back to returning "UTC"; this change should reference the
_resolve_timezone function, the config_manager variable obtained from
cache_manager, and the get_timezone() call so you adjust the correct block.

In `@plugins/f1-scoreboard/manifest.json`:
- Line 4: The manifest's top-level "version" value is out of sync with the
"versions" array; add a new entry for "1.2.2" as the first element of the
"versions" array (including its release metadata and updated "last_updated"
timestamp), ensure that the new top entry matches the top-level "version" field
(set to "1.2.2"), and remove or update any stale references to "1.2.1" so the
array and the "version" field are consistent (update release notes/date in the
new versions[0] entry accordingly).

In `@plugins/football-scoreboard/manifest.json`:
- Line 4: The manifest.json's "version" field was bumped to "2.3.4" but the
"versions" array still has "2.3.3" as its top entry; update the "versions" array
to prepend the new entry "2.3.4" (so the most recent version is first) and
ensure the top element matches the "version" field; edit the "versions" array in
manifest.json to add "2.3.4" above "2.3.3" so both the "version" property and
the first item of "versions" are in sync.

In `@plugins/hockey-scoreboard/manifest.json`:
- Line 4: Prepend a new entry to the versions array so its top element matches
the bumped "version" field: add an object with released (ISO date or current
release date), version set to "1.2.4", and ledmatrix_min (minimum supported
ledmatrix version) as the first array element in versions, ensuring the
versions[0].version equals the manifest's "version" value; keep existing older
entries intact below it.

In `@plugins/soccer-scoreboard/manifest.json`:
- Line 4: The manifest's "version" field was bumped to "1.4.4" but the
"versions" array wasn't updated; update the top of the "versions" array in
plugins/soccer-scoreboard/manifest.json by prepending a new entry for "1.4.4" so
the first element in "versions" matches the "version" field, ensuring the newest
version appears first and both values stay in sync.

In `@plugins/ufc-scoreboard/manifest.json`:
- Line 4: The manifest's "version" field was updated to "1.2.3" but the
"versions" array still lists "1.2.2" as the latest; update the top of the
"versions" array to prepend a new entry for "1.2.3" so the first element matches
the "version" field, ensuring the "version" property and the first item in the
"versions" array (in manifest.json) remain in sync.

---

Outside diff comments:
In `@plugins/hockey-scoreboard/sports.py`:
- Around line 856-858: Update the stale log message that still says "todays
games": change the self.logger.info call that uses f"Fetched {len(events)}
todays games for {self.sport} - {self.league}" to accurately describe the new
2-day range (e.g., "fetched {len(events)} games for yesterday and today" or
"fetched {len(events)} games for the last 2 days") so logs reflect the actual
fetch window; locate the self.logger.info line in the method that populates
events and replace the wording while keeping the same variables ({len(events)},
self.sport, self.league).

---

Nitpick comments:
In `@plugins/baseball-scoreboard/sports.py`:
- Around line 851-865: The debug log in the ESPN schedule fetch incorrectly
states a single date while the request uses a date range; update the
logger.debug call in the fetch block (where tz, now, yesterday, formatted_date,
formatted_date_yesterday and url are defined) to include the full dates range
(e.g., formatted_date_yesterday and formatted_date or the actual "dates" param)
so the message accurately reflects the params sent to self.session.get.

In `@plugins/football-scoreboard/sports.py`:
- Around line 851-865: The debug message incorrectly states "Fetching today's
games..." while the request uses a date range; update the logger.debug in the
method that builds the ESPN scoreboard request to reflect the actual params by
including both formatted_date_yesterday and formatted_date (or the combined
range string used in params) so the log shows the full
"{formatted_date_yesterday}-{formatted_date}" range for the GET to url
(referencing tz, now, yesterday, formatted_date, formatted_date_yesterday, and
self.logger.debug).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 977ed48 and 4415e51.

📒 Files selected for processing (22)
  • plugins.json
  • plugins/baseball-scoreboard/game_renderer.py
  • plugins/baseball-scoreboard/manager.py
  • plugins/baseball-scoreboard/manifest.json
  • plugins/baseball-scoreboard/sports.py
  • plugins/basketball-scoreboard/manifest.json
  • plugins/basketball-scoreboard/sports.py
  • plugins/calendar/config_schema.json
  • plugins/calendar/manager.py
  • plugins/calendar/manifest.json
  • plugins/f1-scoreboard/f1_renderer.py
  • plugins/f1-scoreboard/manager.py
  • plugins/f1-scoreboard/manifest.json
  • plugins/f1-scoreboard/requirements.txt
  • plugins/football-scoreboard/manifest.json
  • plugins/football-scoreboard/sports.py
  • plugins/hockey-scoreboard/manifest.json
  • plugins/hockey-scoreboard/sports.py
  • plugins/soccer-scoreboard/manifest.json
  • plugins/soccer-scoreboard/sports.py
  • plugins/ufc-scoreboard/manifest.json
  • plugins/ufc-scoreboard/sports.py

- Calendar: bump to 1.1.0, add get_calendars(), google-calendar-picker schema,
  wrap calendarList in try/except, use logger.exception for event fetch errors
- Registry: plugins.json calendar latest_version 1.1.0
- Manifests: prepend version entries for baseball, basketball, f1, football,
  hockey, soccer, ufc so version matches versions[0]
- Baseball/F1: catch broad Exception in get_timezone, log and fallback to UTC
- F1 renderer: treat naive datetimes as UTC in _to_local_dt
- Sports: debug logs show date range; hockey info log says 'last 2 days'

Made-with: Cursor
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
plugins/baseball-scoreboard/manager.py (1)

96-111: Timezone fallback logic looks solid; minor logging cleanup suggested.

The broad exception handling and fallback to UTC is well-implemented, addressing the previous review concern. However, the static analysis hint is valid:

logger.exception() automatically includes the full exception traceback in the log output, making the %s placeholder with e redundant.

🧹 Proposed fix to remove redundant exception argument
         try:
             global_tz = config_manager.get_timezone()
         except Exception as e:
             self.logger.exception(
-                "Failed to read global timezone from config_manager.get_timezone(): %s. Falling back to UTC.",
-                e,
+                "Failed to read global timezone from config_manager.get_timezone(). Falling back to UTC."
             )
     self.config["timezone"] = global_tz or "UTC"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/baseball-scoreboard/manager.py` around lines 96 - 111, The
logger.exception call in the timezone resolution should not pass the exception
object as a formatting argument because logger.exception already logs the
traceback; update the exception handling in the block that calls
config_manager.get_timezone() (references: cache_manager, config_manager,
config_manager.get_timezone(), self.logger.exception, self.config) to call
self.logger.exception with a plain descriptive message (e.g., "Failed to read
global timezone from config_manager.get_timezone(); falling back to UTC.") and
remove the redundant "%s" and e argument, then leave the existing fallback
assignment self.config["timezone"] = global_tz or "UTC" unchanged.
plugins/football-scoreboard/manifest.json (1)

4-4: Align last_updated with the new release date.

Line 4 and Lines 27-31 indicate a 2026-03-02 release, but Line 243 still shows last_updated: "2026-02-24". Updating it keeps registry metadata consistent.

Proposed tweak
-  "last_updated": "2026-02-24",
+  "last_updated": "2026-03-02",

Also applies to: 27-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/football-scoreboard/manifest.json` at line 4, Update the manifest's
last_updated field to match the new release date shown by the version/release
metadata (change last_updated from "2026-02-24" to "2026-03-02"); locate the
"last_updated" property in plugins/football-scoreboard/manifest.json and make
the date consistent with the release entries around the "version": "2.3.4" and
the release block that lists "2026-03-02".
plugins/basketball-scoreboard/manifest.json (1)

4-4: Update last_updated to match the new release record.

Line 4 and Lines 21-25 now reflect a 2026-03-02 release, but Line 74 still has last_updated: "2026-02-24". Consider syncing this metadata.

Proposed tweak
-  "last_updated": "2026-02-24",
+  "last_updated": "2026-03-02",

Also applies to: 21-25

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/basketball-scoreboard/manifest.json` at line 4, The manifest's
metadata is inconsistent: the "version" is 1.5.4 and several release fields show
2026-03-02, but the "last_updated" field still reads "2026-02-24"; update the
last_updated value to "2026-03-02" so it matches the new release record and
ensure any other occurrences of last_updated in this manifest are synchronized;
verify the "version" and release date blocks (references: "version" and
"last_updated") are consistent across the file.
plugins/f1-scoreboard/manager.py (1)

109-123: Remove redundant exception object from logging.exception() call.

logger.exception() automatically captures and logs the current exception's traceback via sys.exc_info(). Passing e as an argument is redundant and flagged by static analysis (TRY401).

♻️ Proposed fix
         try:
             tz = config_manager.get_timezone()
         except Exception as e:
-            self.logger.exception(
-                "Failed to read global timezone from config_manager.get_timezone(): %s. Falling back to UTC.",
-                e,
-            )
+            self.logger.exception(
+                "Failed to read global timezone from config_manager.get_timezone(). Falling back to UTC."
+            )
     return tz or "UTC"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/f1-scoreboard/manager.py` around lines 109 - 123, The
logger.exception call in _resolve_timezone currently passes the caught exception
object (e) as a formatting argument to self.logger.exception, which is redundant
and causes static analysis warnings; update the except block around
config_manager.get_timezone() so it calls self.logger.exception with a single
message (e.g. "Failed to read global timezone from
config_manager.get_timezone(): Falling back to UTC.") and remove the extra
exception argument so the traceback is still captured automatically by
logger.exception.
plugins/calendar/manager.py (1)

440-444: Drop redundant exception object from logger.exception.

At Line 441, self.logger.exception(...) already records traceback/exception context. Passing e as %s duplicates noise in logs.

🧹 Proposed cleanup
                     self.logger.exception(
-                        "Error fetching events from calendar '%s': %s - verify this calendar ID is correct and accessible under your Google account",
-                        calendar_id,
-                        e,
+                        "Error fetching events from calendar '%s' - verify this calendar ID is correct and accessible under your Google account",
+                        calendar_id,
                     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/calendar/manager.py` around lines 440 - 444, Remove the redundant
exception object passed into the logger.exception call: in the block where
self.logger.exception is invoked with calendar_id and the exception variable e,
drop the extra "e" argument and keep a single formatted message referencing
calendar_id (logger.exception already logs traceback/exception context). Update
the self.logger.exception invocation so it only formats the message with
calendar_id and relies on logger.exception to record the exception details.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/calendar/config_schema.json`:
- Around line 26-33: The calendars schema object contains a duplicated
"x-widget" key; remove the redundant "x-widget" entry so the object only has a
single "x-widget" property (keep the first occurrence) in the calendars schema
definition to resolve the ambiguity and lint error—locate the calendars schema
in config_schema.json and delete the second "x-widget" declaration.

In `@plugins/calendar/manager.py`:
- Around line 378-389: The calendar list call only fetches the first page;
update the logic in the method that calls
self.service.calendarList().list().execute() to iterate pages using
nextPageToken: initialize page_token = None, call .list(pageToken=page_token)
(or omit when None), merge each response's "items" into a single calendars list,
set page_token = response.get("nextPageToken") and loop until it's falsy, then
map each calendar to the existing dict shape (id, summary, primary) and return
the combined list; reference the existing usage of
self.service.calendarList().list(), "items", and "nextPageToken" when
implementing the loop.

---

Nitpick comments:
In `@plugins/baseball-scoreboard/manager.py`:
- Around line 96-111: The logger.exception call in the timezone resolution
should not pass the exception object as a formatting argument because
logger.exception already logs the traceback; update the exception handling in
the block that calls config_manager.get_timezone() (references: cache_manager,
config_manager, config_manager.get_timezone(), self.logger.exception,
self.config) to call self.logger.exception with a plain descriptive message
(e.g., "Failed to read global timezone from config_manager.get_timezone();
falling back to UTC.") and remove the redundant "%s" and e argument, then leave
the existing fallback assignment self.config["timezone"] = global_tz or "UTC"
unchanged.

In `@plugins/basketball-scoreboard/manifest.json`:
- Line 4: The manifest's metadata is inconsistent: the "version" is 1.5.4 and
several release fields show 2026-03-02, but the "last_updated" field still reads
"2026-02-24"; update the last_updated value to "2026-03-02" so it matches the
new release record and ensure any other occurrences of last_updated in this
manifest are synchronized; verify the "version" and release date blocks
(references: "version" and "last_updated") are consistent across the file.

In `@plugins/calendar/manager.py`:
- Around line 440-444: Remove the redundant exception object passed into the
logger.exception call: in the block where self.logger.exception is invoked with
calendar_id and the exception variable e, drop the extra "e" argument and keep a
single formatted message referencing calendar_id (logger.exception already logs
traceback/exception context). Update the self.logger.exception invocation so it
only formats the message with calendar_id and relies on logger.exception to
record the exception details.

In `@plugins/f1-scoreboard/manager.py`:
- Around line 109-123: The logger.exception call in _resolve_timezone currently
passes the caught exception object (e) as a formatting argument to
self.logger.exception, which is redundant and causes static analysis warnings;
update the except block around config_manager.get_timezone() so it calls
self.logger.exception with a single message (e.g. "Failed to read global
timezone from config_manager.get_timezone(): Falling back to UTC.") and remove
the extra exception argument so the traceback is still captured automatically by
logger.exception.

In `@plugins/football-scoreboard/manifest.json`:
- Line 4: Update the manifest's last_updated field to match the new release date
shown by the version/release metadata (change last_updated from "2026-02-24" to
"2026-03-02"); locate the "last_updated" property in
plugins/football-scoreboard/manifest.json and make the date consistent with the
release entries around the "version": "2.3.4" and the release block that lists
"2026-03-02".

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4415e51 and c011ef8.

📒 Files selected for processing (17)
  • plugins.json
  • plugins/baseball-scoreboard/manager.py
  • plugins/baseball-scoreboard/manifest.json
  • plugins/baseball-scoreboard/sports.py
  • plugins/basketball-scoreboard/manifest.json
  • plugins/calendar/config_schema.json
  • plugins/calendar/manager.py
  • plugins/calendar/manifest.json
  • plugins/f1-scoreboard/f1_renderer.py
  • plugins/f1-scoreboard/manager.py
  • plugins/f1-scoreboard/manifest.json
  • plugins/football-scoreboard/manifest.json
  • plugins/football-scoreboard/sports.py
  • plugins/hockey-scoreboard/manifest.json
  • plugins/hockey-scoreboard/sports.py
  • plugins/soccer-scoreboard/manifest.json
  • plugins/ufc-scoreboard/manifest.json
🚧 Files skipped from review as they are similar to previous changes (8)
  • plugins/hockey-scoreboard/manifest.json
  • plugins/hockey-scoreboard/sports.py
  • plugins.json
  • plugins/f1-scoreboard/manifest.json
  • plugins/baseball-scoreboard/manifest.json
  • plugins/soccer-scoreboard/manifest.json
  • plugins/calendar/manifest.json
  • plugins/ufc-scoreboard/manifest.json

Chuck added 3 commits March 2, 2026 13:34
- Calendar: fix duplicate x-widget, add paginated get_calendars, simplify logger.exception usage
- Baseball/F1: logger.exception no longer passes exception arg for timezone fallback
- Basketball/Football: last_updated dates match latest release versions

Made-with: Cursor
Preserve timezone fallback fixes while incorporating upstream registry and plugin version updates so fix/timezone-display can merge cleanly.

Made-with: Cursor
Parse string booleans safely, enforce unique countdown IDs, and broaden cache invalidation to include image-affecting settings; also clarify README wording for explicit image_path configuration.

Made-with: Cursor
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@plugins/countdown/manager.py`:
- Around line 167-171: The runtime cache seeding (used_ids populated from
cached_images and countdown_values) is causing unchanged incoming IDs to be
treated as collisions and renamed; modify the dedupe logic so existing runtime
IDs are not reserved against incoming IDs. When building used_ids from
cached_images and countdown_values, exclude any IDs that are present in the
current incoming IDs set (or, alternatively, when assigning/renaming in the
dedupe routine, first check if the incoming id is already in incoming_ids and
allow it to remain). Update the code paths that reference used_ids and the
renaming/generation logic so preserved incoming IDs (from the incoming
list/variable used for import) are not considered collisions by used_ids.
- Around line 121-127: The string-to-boolean parsing branch (the
isinstance(value, str) block) currently returns True for any unrecognized
string; change it to return the provided default instead of True for
unknown/typod inputs. Keep the existing normalized checks for
("false","0","off","no","") -> False and ("true","1","on","yes") -> True, but
replace the final unconditional "return True" with "return default" so unknown
normalized values (e.g. "flase") do not invert the setting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4d04d35-ddc7-4603-884f-184b3109f8a3

📥 Commits

Reviewing files that changed from the base of the PR and between eb60b8d and 5f23c42.

📒 Files selected for processing (2)
  • plugins/countdown/README.md
  • plugins/countdown/manager.py
✅ Files skipped from review due to trivial changes (1)
  • plugins/countdown/README.md

Comment on lines +74 to +75
self.countdowns = self._normalize_countdowns(config.get('countdowns', []))
self._countdown_signature = self._build_countdown_signature(self.countdowns)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

manifest_path="$(fd -p 'manifest.json' plugins/countdown | head -n1 || true)"
if [[ -z "${manifest_path}" ]]; then
  echo "No countdown manifest found under plugins/countdown"
  exit 1
fi

echo "Manifest file: ${manifest_path}"
echo "=== PR diff for manifest ==="
git diff -- "${manifest_path}" | sed -n '1,220p'

echo "=== Current version fields ==="
python - "${manifest_path}" <<'PY'
import json, re, sys, pathlib
p = pathlib.Path(sys.argv[1])
data = json.loads(p.read_text())
version = data.get("version")
versions = data.get("versions") or []
top = versions[0] if versions else None

print("version:", version)
print("top versions entry:", top)

semver = re.compile(r"^\d+\.\d+\.\d+$")
print("version_is_semver:", bool(isinstance(version, str) and semver.match(version)))

if isinstance(top, dict):
    top_version = top.get("version")
    print("top_entry_matches_version:", top_version == version)
else:
    print("top_entry_matches_version: false (missing/invalid top versions entry)")
PY

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 313


🏁 Script executed:

# Check the current manifest.json content
cat plugins/countdown/manifest.json

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 805


🏁 Script executed:

# Check what changed in manager.py
git diff plugins/countdown/manager.py

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 55


🏁 Script executed:

# Check plugins/countdown directory contents
ls -la plugins/countdown/

# Get file size and line count for manager.py
wc -l plugins/countdown/manager.py

# Read lines 70-80 from manager.py to verify the mentioned code
sed -n '70,80p' plugins/countdown/manager.py

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 1170


🏁 Script executed:

# Check git status to understand branch state
git status | head -30

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 307


Manifest structure and versioning incomplete. The plugins/countdown/manifest.json requires two critical updates:

  1. Bump version to 1.1.0 — Lines 74-75 in manager.py introduce feature-level functionality (countdown normalization and signature building), which requires a MINOR version bump per coding guidelines.

  2. Add missing versions array — The manifest is missing the required versions array. Add it with the new version entry at the top, following the structure: { "released": "YYYY-MM-DD", "version": "1.1.0", "ledmatrix_min": "<required_version>" }. Ensure the version field in the root of the manifest stays synchronized with the top entry in versions.

@ChuckBuilds ChuckBuilds merged commit 7c143a2 into main Mar 9, 2026
1 check passed
@ChuckBuilds ChuckBuilds deleted the fix/timezone-display branch March 9, 2026 01:10
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