fix(calendar): show today's events instead of only future ones#99
fix(calendar): show today's events instead of only future ones#99ChuckBuilds merged 12 commits intomainfrom
Conversation
…cache guard
Addresses three user reports from Discord.
## Report 3 (JScottyR): Vegas scroll cards spanning the full panel
On a 64×64 × 5-panel chain (320×64), masters was rendering each player
card at the full panel size, so "Vegas scroll" mode showed one giant card
at a time instead of a ticker of smaller fixed-size blocks. Every other
sports scoreboard in the repo follows a fixed-block convention — football,
hockey, and baseball all default `game_card_width=128` regardless of the
physical display width, with cards constructed at that explicit size.
Fix:
- New top-level config key `scroll_card_width` (default 128, min 32,
max 256) in config_schema.json with a description referencing the
Vegas scroll ticker mode.
- `manager.py` reads it as `self._scroll_card_width` in `__init__` (and
on hot-reload), then passes `card_width=self._scroll_card_width,
card_height=self.display_height` to each `render_*` call inside
`get_vegas_content()`.
- `render_player_card()`, `render_hole_card()`, and `render_fun_fact()`
in `masters_renderer.py` now accept optional `card_width`/`card_height`
parameters. When provided, the body uses local `w`/`h`/`cw`/`ch`
variables instead of `self.width`/`self.height`, and `is_wide_short`
is recomputed per-card (so a 128×64 block on a 320×64 panel is
aspect 2.0 and uses the standard vertical-stack layout, while the
same panel's full-screen modes still use the two-column wide-short
layout for the leaderboard, schedule, etc).
- `_render_player_card_wide_short()` takes the same `w`/`h` overrides
and parameterizes every `self.width`/`self.height` reference.
- `_draw_gradient_bg()` accepts optional `width`/`height` so each render
can allocate its own image at the override size.
- Enhanced renderer overrides (`render_player_card`, `render_hole_card`)
delegate to `super()` when called with explicit card dimensions, since
the enhanced round-scores block and left-panel-plus-image layouts are
designed for full-panel rendering and don't fit at block sizes.
Verified locally at every `(parent, card)` combination in
`{(320,64), (384,64), (192,48), (128,64), (64,32)} × {(128,64), (128,48),
(80,64), (64,32)}`: every returned image's `.size` exactly matches the
override. A simulated `get_vegas_content()` on a 320×64 parent with
default `scroll_card_width=128` produces 33 cards (10 players, 18 holes,
5 facts), ALL at exactly 128×64. A 5-card scroll strip concatenated
side-by-side renders as a 640×64 image that looks like the football
scoreboard ticker pattern — headshot + name + country + score + pos/thru
per card, cleanly repeated.
## Reports 1 & 2 (Fish Man): defensive stale-cache guard
Reports 1 (`'str' object has no attribute 'tzinfo'`) and 2 (`'<=' not
supported between instances of 'float' and 'NoneType'`) are both already
fixed in PR #95 via `_rehydrate_meta()` and `_NEVER_EXPIRE` respectively,
but neither has merged to `main` yet — a user who pulled the plugin-store
update to 2.1.2 after PR #94 merged is running the exact version where
both errors fire.
Residual risk even after PR #95 / this branch ships: the disk cache file
`/var/cache/ledmatrix/masters_tournament_meta.json` persists from the
broken 2.1.2 run. The core `CacheManager` logs the `<= None` error and
returns None (cache miss) rather than re-raising, but the log noise
persists every tick until the file is overwritten by a successful fetch.
Added `MastersDataSource._safe_cache_get()` that wraps every
`cache_manager.get()` call in a try/except, treats any exception as a
cache miss, and logs a single warning per instance (not per tick)
pointing at the stale file path. Replaced all 9 `cache_manager.get()`
call sites in `masters_data.py` with the safe wrapper.
Verified with a stub `BrokenCache` that raises `TypeError('<=' not
supported...)` on every `.get()` AND a blocked network: the plugin
still initializes without crashing, returns the computed second-Thursday-
of-April fallback meta, falls back to mock leaderboard data, and logs
the stale-cache warning exactly once.
## Other
- Bumps manifest `2.2.4 → 2.2.5`.
- test_plugin_standalone.py: 45/45 still passing.
Depends on PR #95 being merged first (same branch base).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s wordmark assets
## Hole card layout (bundled with Vegas scroll fix)
The earlier Vegas scroll fix (2.2.5) had the enhanced `render_hole_card`
delegating to the base class when called with `card_width`/`card_height`
overrides. The base class uses a different layout (horizontal header +
centered image + footer) that's neither the user's desired "1 text column
+ image" for larger cards nor the "2 text columns, no image" for smaller
ones — so Vegas scroll hole cards looked inconsistent with the full-panel
layouts.
Rewrote the enhanced `render_hole_card` to:
1. Accept `card_width`/`card_height` directly and render at those
dimensions.
2. Choose between two layouts based on the EFFECTIVE card dimensions
(not `self.tier`), using thresholds `cw >= 96 AND ch >= 40`:
* **Large enough for image** → single left-column text
[Hole #, Name, Par, Yards] + hole image on the right, zone
badge in the bottom-right. Split into a new helper
`_render_hole_card_with_image()`.
* **Smaller canvases** → two text columns: [# / Name] | [Par /
Yards / Zone], no image. `_render_hole_card_compact()` now
also accepts `cw`/`ch` overrides.
3. `left_w` (text column width) now scales with `cw` via
`max(38, min(56, cw // 3))` so 256x64 cards get a roomier text
column than 128x48 blocks.
Verified across the full size matrix:
* 192x48 full panel → IMAGE (#12 + Golden Bell + Par 3 + 155y + map + AMEN CORNER)
* 128x64 full panel → IMAGE
* 256x64 full panel → IMAGE
* 320x64 Vegas 128x64 → IMAGE (inside a scrollable block, not full-panel)
* 320x64 Vegas 128x48 → IMAGE
* 192x48 Vegas 128x48 → IMAGE
* 320x64 Vegas 80x64 → COMPACT 2-col (too narrow for image)
* 128x32 full panel → COMPACT 2-col
* 128x32 Vegas 80x32 → COMPACT 2-col
* 64x32 full panel → COMPACT 2-col
Both layouts have the user's requested content layout:
* Image layout: one text column [Hole #, Hole Name, Par, Yards]
* Compact layout: col 1 [Hole # + Name], col 2 [Par, Yards, Zone]
## Masters wordmark assets
Extracted from `masters-icons.ttf` (IcoMoon icon font from masters.com).
The font has 143 glyphs in the U+E900–U+E98F Private Use Area, mostly
UI icons, but scanning for unusually-wide advance widths revealed two
wordmarks:
* U+E954 (5.95 em) - the iconic MASTERS wordmark with the Augusta
National contour + fairway flag + "Masters" in italic serif
* U+E95B (4.89 em) - AUGUSTA wordmark in matching italic serif
Added to `plugins/masters-tournament/assets/masters/logos/`:
* wordmark_32.png 191x32 white on transparent
* wordmark_48.png 286x49
* wordmark_64.png 381x64
* wordmark_128.png 762x128
* augusta_wordmark_32.png 156x30
* augusta_wordmark_48.png 235x45
Assets only — not yet wired into the renderer. A future change can
swap these in for the current `masters_logo_*.png` in countdown,
player card headers, etc.
Bumps manifest 2.2.5 → 2.2.6.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…card
Previous attempts to use 5by7.regular.ttf via _load_font_sized() resulted
in washed-out anti-aliased text because PIL's TTF renderer smooths small
pixel fonts. The 5x7.bdf asset in the LEDMatrix core assets dir is a
true fixed-size bitmap font, and PIL.BdfFontFile can convert it to PIL
format for pixel-perfect 1:1 rendering.
Changes:
- Added _load_bdf_font(filename) in masters_renderer.py. Converts
BDF → PIL format once per process via tempfile.mkdtemp(), caches
the loaded ImageFont in _BDF_FONT_CACHE (keyed by filename), and
stores failure results so missing files don't re-hit the disk.
Uses a local `from PIL import BdfFontFile` import since it's only
needed by this helper.
- Enhanced renderer imports _load_bdf_font alongside _load_font_sized.
- _render_hole_card_compact() now loads 5x7.bdf for both text_font
and hole_font. The BDF renders at its native 7px height with no
anti-aliasing, so glyphs look crisp and bold. Falls back to
self.font_detail / self.font_body when the BDF file isn't on the
search path.
- The previous 4x6 code is kept in a commented block directly
underneath so we can flip fonts back in one edit if users report
issues with 5x7 on actual hardware.
Verified:
- _load_bdf_font("5x7.bdf") returns a valid ImageFont, 'Ag' bbox
height exactly 7px, second call hits the cache (same object id).
- _load_bdf_font("nonexistent.bdf") returns None cleanly.
- Render matrix across full panels (64x32, 128x32, 128x48, 128x64,
192x48, 256x64) and Vegas scroll blocks (128x64, 128x48, 128x32,
80x32) all produce expected layouts:
* ch >= 48 → IMAGE layout (left-panel text + hole image, unchanged)
* ch < 48 → COMPACT 2-col (# + Name left, Par + Yards right)
- Compact layouts now render with pixel-perfect 5x7 glyphs; the
narrower-per-glyph 5x7 font also lets "AMEN CORNER" fit fully on
a 128-wide compact card where 4x6 was truncating to "AMEN COR".
- test_plugin_standalone.py: 45/45 passing.
Note: 5x7.bdf is NOT copied into the plugin repo — it already lives
in the core LEDMatrix assets dir which the plugin's FONT_SEARCH_DIRS
already knows about. If a future deployment doesn't ship the core
fonts, the fallback to self.font_detail keeps the hole card working.
Bumps manifest 2.2.6 → 2.2.7.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…2, 256x32) Addresses multiple layout issues reported on 2-chain and 4-chain 64x32 displays where the large tier's vertical measurements overflowed 32px. - Add compact tier overrides for wide-short <=32px (header=8, footer=5, row=7) with appropriately sized fonts - Hole card compact layout: text stacked left, course image on right (replaces two-column text-only layout) - Fun facts in vegas mode: render as single-line wide cards for natural horizontal scroll reveal instead of truncating - Fun facts: respect user's enabled setting in vegas mode - Fun facts: calculate scroll steps from display height instead of hardcoding 5; increase advance interval to 3s - Tee times: stack player names vertically on 48+ displays; compact single-line layout on <48px - BDF font search: add Path(__file__)-relative path so 5x7.bdf is found regardless of working directory Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, width guards, temp cleanup - Fun facts scroll: compute max_scroll from actual wrapped line count via new get_fun_fact_line_count() instead of fixed 15//visible heuristic - Bump manifest version to 2.3.0 for new behavior/config changes - Clamp hole card column widths: fall back to text-only layout when card is too narrow for a useful image column (< 20px) - Register atexit cleanup for BDF temp directory so masters_bdf_* dirs don't accumulate in /tmp Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # plugins.json # plugins/masters-tournament/manager.py # plugins/masters-tournament/manifest.json # plugins/masters-tournament/masters_renderer.py # plugins/masters-tournament/masters_renderer_enhanced.py
…act wrap helper - Fix off-by-one in fun facts scroll: use >= instead of > so the fact advances immediately after showing the last scroll position - BDF temp cleanup: log on failure instead of silently swallowing, use try/finally so cache is always cleared - Extract _wrap_text() helper used by both get_fun_fact_line_count() and render_fun_fact() to eliminate duplicate wrapping logic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…imes When player_rows < len(players), fold remaining players into the last visible line as a comma-separated string instead of silently dropping them. Handles edge case where player_rows == 1 by folding all players into that single line. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s in _wrap_text Words wider than max_w (e.g. long unbroken tokens) are now split character-by-character so no wrapped line exceeds the target width. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…events appear The API query used timeMin=now (current UTC instant), which excluded any event that had already started earlier in the day. Changing timeMin to midnight of the current day (in the user's configured timezone) ensures today's events are always visible, and upcoming events still appear as fallback when today has none. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughBumps masters-tournament to 2.3.0 and updates plugins.json timestamp. Calendar manager now queries events from the start of “today” in the plugin timezone (UTC fallback). Masters changes adjust fun-fact timing/advancement, add Vegas rendering, extend font search and BDF temp cleanup, and adapt layouts for narrow/short panels. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/calendar/manager.py (1)
16-16:⚠️ Potential issue | 🟠 MajorBump API version patch for this bug-fix change.
This file implements a bug fix, but
API Versionis still1.0.0. Please increment to the next patch version (e.g.,1.0.1).As per coding guidelines,
**/plugins/**/*.py: Bump PATCH version (1.2.x) for bug fixes, performance improvements, documentation updates, or minor tweaks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/calendar/manager.py` at line 16, Update the API version string from "API Version: 1.0.0" to the next patch release "API Version: 1.0.1" in the plugins/calendar/manager.py module to reflect the bug-fix change; locate the literal line containing the "API Version: 1.0.0" token and replace it with "API Version: 1.0.1".
🧹 Nitpick comments (1)
plugins/calendar/manager.py (1)
428-439: Computetime_minonce perupdate()call.
time_minis recalculated inside the calendar loop, which can create inconsistent fetch windows if the loop crosses midnight. Compute it once before iterating calendars.♻️ Proposed refactor
try: # Fetch events from all configured calendars all_events = [] + # Use one stable lower bound for all calendars in this update cycle + if self.timezone and pytz: + local_now = datetime.now(self.timezone) + start_of_today = local_now.replace(hour=0, minute=0, second=0, microsecond=0) + time_min = start_of_today.astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%S') + 'Z' + else: + time_min = datetime.utcnow().replace( + hour=0, minute=0, second=0, microsecond=0 + ).isoformat() + 'Z' for calendar_id in self.calendars: try: - # Use start of today (in user's timezone) so already-started events still appear - if self.timezone and pytz: - local_now = datetime.now(self.timezone) - start_of_today = local_now.replace(hour=0, minute=0, second=0, microsecond=0) - time_min = start_of_today.astimezone(pytz.utc).strftime('%Y-%m-%dT%H:%M:%S') + 'Z' - else: - time_min = datetime.utcnow().replace( - hour=0, minute=0, second=0, microsecond=0 - ).isoformat() + 'Z' events_result = self.service.events().list( calendarId=calendar_id, timeMin=time_min,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/calendar/manager.py` around lines 428 - 439, In update(), time_min is being recomputed inside the calendar loop which can yield inconsistent windows if midnight passes; compute time_min once at the start of the update() function (before iterating over calendars) using the same logic currently used (checking self.timezone and pytz, computing start_of_today and converting to UTC or using datetime.utcnow()), then reuse that single time_min value for each call to self.service.events().list(calendarId=..., timeMin=time_min, ...) so all calendars fetch with the same consistent time boundary.
🤖 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/masters-tournament/manifest.json`:
- Around line 47-49: The manifest's release-metadata is inconsistent: the new
"version": "2.3.0" has "released": "2026-04-10" but the "last_updated" field
still shows "2026-04-09"; update the "last_updated" value in manifest.json to
"2026-04-10" (to match the released date) so the entry is internally consistent
and will sync correctly via update_registry.py.
In `@plugins/masters-tournament/masters_renderer.py`:
- Around line 1103-1114: In the word-wrapping loop inside masters_renderer.py,
before you start character-breaking an oversized token in the "if
self._text_width(draw, word, font) > max_w:" branch, flush any existing
current_line into lines and reset current_line to empty so the long word's
characters don't get appended onto the previous word; then iterate the word's
characters starting from an empty current_line (use the existing inner loop
logic that builds test = current_line + ch and appends to lines when overflow
occurs). Ensure you still continue the outer loop after processing the broken
word.
---
Outside diff comments:
In `@plugins/calendar/manager.py`:
- Line 16: Update the API version string from "API Version: 1.0.0" to the next
patch release "API Version: 1.0.1" in the plugins/calendar/manager.py module to
reflect the bug-fix change; locate the literal line containing the "API Version:
1.0.0" token and replace it with "API Version: 1.0.1".
---
Nitpick comments:
In `@plugins/calendar/manager.py`:
- Around line 428-439: In update(), time_min is being recomputed inside the
calendar loop which can yield inconsistent windows if midnight passes; compute
time_min once at the start of the update() function (before iterating over
calendars) using the same logic currently used (checking self.timezone and pytz,
computing start_of_today and converting to UTC or using datetime.utcnow()), then
reuse that single time_min value for each call to
self.service.events().list(calendarId=..., timeMin=time_min, ...) so all
calendars fetch with the same consistent time boundary.
🪄 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
Run ID: 6de0a2fe-2161-4f28-8905-5d1278cfdf87
📒 Files selected for processing (6)
plugins.jsonplugins/calendar/manager.pyplugins/masters-tournament/manager.pyplugins/masters-tournament/manifest.jsonplugins/masters-tournament/masters_renderer.pyplugins/masters-tournament/masters_renderer_enhanced.py
- calendar: bump API version to 1.0.1 for the timeMin bug-fix - calendar: hoist time_min computation above the calendar loop so all calendars query with the same consistent time boundary - masters: fix last_updated in manifest.json to match 2.3.0 release date - masters: flush current_line before character-breaking an oversized word in _wrap_text so previous text isn't glued to the broken characters Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert masters-tournament manifest and renderer changes that were incorrectly included in this calendar-only branch. Those fixes will be applied on a separate branch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
timeMinparameter from "right now" to "start of today" (in the user's configured timezone)Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes / Improvements