Skip to content

fix(calendar): show today's events instead of only future ones#99

Merged
ChuckBuilds merged 12 commits intomainfrom
fix/calendar-show-today-events
Apr 10, 2026
Merged

fix(calendar): show today's events instead of only future ones#99
ChuckBuilds merged 12 commits intomainfrom
fix/calendar-show-today-events

Conversation

@ChuckBuilds
Copy link
Copy Markdown
Owner

@ChuckBuilds ChuckBuilds commented Apr 10, 2026

Summary

  • Changed the Google Calendar API timeMin parameter from "right now" to "start of today" (in the user's configured timezone)
  • Events that already started earlier today now appear instead of being filtered out
  • If today has no events, upcoming future events are still shown as fallback (existing behavior)

Test plan

  • Verify events that started earlier today still appear on the display
  • Confirm future events show when today has no events
  • Check timezone handling: start-of-day respects the configured timezone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Masters Tournament plugin updated to v2.3.0 with optional Vegas-style fun-fact rendering and improved font handling.
    • Calendar plugin now queries events from the start of the current day in your configured timezone.
  • Bug Fixes / Improvements

    • Smoother fun-fact scrolling and reliable line-count based advancement.
    • Dynamic, compact layouts for narrow panels and improved hole-card rendering and image handling.

Chuck and others added 10 commits April 9, 2026 18:16
…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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c81e34ed-fe56-49ad-8552-2c5e013deef7

📥 Commits

Reviewing files that changed from the base of the PR and between 9de66c7 and c64c8a8.

📒 Files selected for processing (1)
  • plugins/calendar/manager.py

📝 Walkthrough

Walkthrough

Bumps 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

Cohort / File(s) Summary
Version & Metadata
plugins.json, plugins/masters-tournament/manifest.json
Updated last_updated and bumped masters-tournament plugin version from 2.2.72.3.0; updated release date in manifest.
Calendar Manager
plugins/calendar/manager.py
Replaced per-calendar now-timestamp with a single time_min computed once per update cycle targeting start-of-day in the plugin timezone (falls back to UTC start-of-day).
Masters Tournament Manager
plugins/masters-tournament/manager.py
Changed default fun-fact advance interval from 2s → 3s; advance logic now uses renderer.get_fun_fact_line_count(...); Vegas rendering gated by display_modes.fun_facts.enabled and uses render_fun_fact_vegas(...).
Masters Tournament Renderer
plugins/masters-tournament/masters_renderer.py
Added repository assets/fonts to search dirs, BDF temp-dir cleanup with atexit, compact layouts for height ≤32, text wrapping helper (_wrap_text), get_fun_fact_line_count, render_fun_fact_vegas, and dynamic schedule pagination/layout adjustments.
Masters Tournament Enhanced Renderer
plugins/masters-tournament/masters_renderer_enhanced.py
Refactored hole-card compact layout to compute text_w/img_w, conditionally show image/divider based on min widths, and adjust text positioning/wrapping for image-less compact mode.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(calendar): show today's events instead of only future ones' accurately describes the main change - updating the calendar plugin to display events from the start of the current day rather than only from the current moment.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/calendar-show-today-events

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: 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 | 🟠 Major

Bump API version patch for this bug-fix change.

This file implements a bug fix, but API Version is still 1.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: Compute time_min once per update() call.

time_min is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2b1361e and 9de66c7.

📒 Files selected for processing (6)
  • plugins.json
  • plugins/calendar/manager.py
  • plugins/masters-tournament/manager.py
  • plugins/masters-tournament/manifest.json
  • plugins/masters-tournament/masters_renderer.py
  • plugins/masters-tournament/masters_renderer_enhanced.py

Chuck and others added 2 commits April 10, 2026 14:59
- 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>
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