Skip to content

feat: add timezone support for schedules and dim schedule feature - #218

Merged
ChuckBuilds merged 3 commits into
mainfrom
feature/schedule-timezone-and-dim-schedule
Jan 29, 2026
Merged

feat: add timezone support for schedules and dim schedule feature#218
ChuckBuilds merged 3 commits into
mainfrom
feature/schedule-timezone-and-dim-schedule

Conversation

@ChuckBuilds

@ChuckBuildsChuckBuilds commented Jan 29, 2026

Copy link
Copy Markdown
Owner
  • Fix timezone handling in _check_schedule() to use configured timezone instead of system time (addresses schedule offset issues)
  • Add dim schedule feature for automatic brightness dimming:
    • New dim_schedule config section with brightness level and time windows
    • Smart interaction: dim schedule won't turn display on if it's off
    • Supports both global and per-day modes like on/off schedule
  • Add set_brightness() and get_brightness() methods to DisplayManager for runtime brightness control
  • Add REST API endpoints: GET/POST /api/v3/config/dim-schedule
  • Add web UI for dim schedule configuration in schedule settings page

Summary by CodeRabbit

Release Notes

New Features

  • Automatic display dimming based on scheduled time periods with adjustable brightness levels (0-100%)
  • Support for both global scheduling and per-day overrides with custom start/end times for each weekday
  • Web interface controls for configuring and managing dim schedules with timezone-aware operation
  • Enhanced brightness management with per-day scheduling flexibility

✏️ Tip: You can customize this high-level summary in your review settings.

- Fix timezone handling in _check_schedule() to use configured timezone
instead of system time (addresses schedule offset issues)
- Add dim schedule feature for automatic brightness dimming:
- New dim_schedule config section with brightness level and time windows
- Smart interaction: dim schedule won't turn display on if it's off
- Supports both global and per-day modes like on/off schedule
- Add set_brightness() and get_brightness() methods to DisplayManager
for runtime brightness control
- Add REST API endpoints: GET/POST /api/v3/config/dim-schedule
- Add web UI for dim schedule configuration in schedule settings page
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@ChuckBuilds has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 36 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

A new dimming schedule feature is introduced, enabling timezone-aware brightness control based on configurable time windows. Configuration, API endpoints, backend brightness logic, and web UI components are added to support global and per-day dimming schedules.

Changes

Cohort / File(s)Summary
Configuration
config/config.template.json
New dim_schedule configuration object with enabled flag, brightness level, mode selection, and time windows for global or per-day dimming overrides.
Display Brightness Control
src/display_manager.py
Added set_brightness(brightness: int) and get_brightness() public methods to control and read display brightness with clamping and fallback handling.
Display Controller Logic
src/display_controller.py
Introduced _check_dim_schedule() method with timezone-aware brightness calculation; integrated into main loop alongside existing schedule checks; added brightness state tracking and resilience for invalid timezones and times.
API Endpoints
web_interface/blueprints/api_v3.py
Added GET/POST endpoints for /config/dim-schedule with comprehensive validation logic for global and per-day modes, time format checking, and at-least-one-day enforcement for per-day configuration.
Web Interface
web_interface/blueprints/pages_v3.py, web_interface/templates/v3/partials/schedule.html
Extended schedule template route to pass dim_schedule_config and normal_brightness; added new "Dim Schedule Settings" UI form with brightness slider, date/time picker widget, and response handler for form submission feedback.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Client as Browser
participant API as API Endpoint
participant ConfigMgr as Config Manager
participant Controller as Display Controller
participant Manager as Display Manager
participant Hardware as Hardware Matrix
User->>Client: Save Dim Schedule
Client->>API: POST /config/dim-schedule
API->>API: Validate mode & times
alt Global Mode
API->>API: Validate start/end times
API->>API: Remove per-day settings
else Per-Day Mode
API->>API: Validate per-day times<br/>(Mon-Sun)
API->>API: Ensure ≥1 day enabled
end
API->>ConfigMgr: Update config atomically
ConfigMgr->>ConfigMgr: Write dim_schedule
API-->>Client: Success response
Client-->>User: Show notification
Note over Controller: Main Loop (continuous)
Controller->>Controller: _check_dim_schedule()
Controller->>Controller: Calculate brightness<br/>from schedule & timezone
alt Display Active & Dimmed
Controller->>Manager: set_brightness(target)
Manager->>Manager: Clamp 0-100
Manager->>Hardware: matrix.brightness = value
Hardware-->>Manager: Return success
Manager-->>Controller: Boolean result
Controller->>Controller: Log brightness change
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Beneath the moonlit web we code,
A dimmer path for nighttime's mode,
With timezone whispers, brightness flows,
From dusk 'til dawn, the schedule glows,
Per-day precision, soft and true—
Bright days ahead, and dimmer nights for you!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately reflects the main changes: adding timezone support for schedules and introducing a dim schedule feature. Both aspects are clearly present in the changeset.
Docstring Coverage✅ PassedDocstring coverage is 92.86% 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 feature/schedule-timezone-and-dim-schedule

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/display_controller.py`:
- Around line 546-627: The dim-schedule mode string comparison in
_check_dim_schedule only recognizes "per-day" so configs using "per_day" are
ignored; update the mode handling in _check_dim_schedule to treat both "per-day"
and "per_day" (e.g., normalize mode = mode.replace('_','-') or check for both
values) so use_per_day becomes True when either form is present, ensuring
day_config (days_config[current_day]) is honored and per-day start_time/end_time
are applied.
In `@web_interface/blueprints/api_v3.py`:
- Around line 405-411: The code currently casts data.get('dim_brightness')
directly to int which raises ValueError/TypeError for empty or non-numeric
input; update the parsing for dim_brightness to validate first (e.g., check for
None/empty and that the value is numeric or can be parsed) and wrap the int
conversion in a try/except that catches ValueError/TypeError, returning
error_response(ErrorCode.VALIDATION_ERROR, "dim_brightness must be an integer
between 0 and 100", status_code=400) if parsing fails or if the resulting int is
outside 0–100; keep the existing branch that returns the validation error when
the numeric range check fails.
🧹 Nitpick comments (2)
src/display_manager.py (1)

177-215: Tighten exception handling and include stack traces in brightness accessors.

Both methods catch broad Exception, and get_brightness() swallows errors silently. Narrowing to expected exception types and logging with stack traces will make hardware issues diagnosable; adding a small fail-fast validation for brightness prevents type errors from leaking into the driver.

♻️ Proposed refactor
 def set_brightness(self, brightness: int) -> bool:
@@
- try:+ try:
if self.matrix is None:
logger.warning("Cannot set brightness in fallback mode")
return False
+ try:+ brightness = int(brightness)+ except (TypeError, ValueError):+ logger.warning("Invalid brightness value '%s' (expected 0-100)", brightness)+ return False+
# Clamp to valid range
brightness = max(0, min(100, brightness))
@@
- except Exception as e:- logger.error(f"Error setting brightness: {e}")+ except (AttributeError, ValueError, TypeError, RuntimeError) as e:+ logger.exception("Error setting brightness: %s", e)
return False
@@
def get_brightness(self) -> int:
@@
- except Exception:- return -1+ except (AttributeError, ValueError, TypeError, RuntimeError):+ logger.exception("Error getting brightness")+ return -1
As per coding guidelines, validate inputs and handle errors early (Fail Fast principle), catch specific exceptions, and include stack traces in error logs.
web_interface/blueprints/api_v3.py (1)

356-383: Handle config-load failures explicitly and log with context

Line 378 catches Exception, which hides config-load issues and makes Pi debugging harder. Please catch config-related exceptions explicitly and log with a [DIM SCHEDULE] context tag; keep a narrow fallback for truly unexpected errors.

🔧 Suggested adjustment
 def get_dim_schedule_config():
"""Get current dim schedule configuration"""
+ from src.exceptions import ConfigError
try:
if not api_v3.config_manager:
return error_response(
ErrorCode.CONFIG_LOAD_FAILED,
'Config manager not initialized',
status_code=500
)
@@
- except Exception as e:- return error_response(- ErrorCode.CONFIG_LOAD_FAILED,- f"Error loading dim schedule configuration: {str(e)}",- status_code=500- )+ except (ConfigError, json.JSONDecodeError, OSError, PermissionError) as e:+ logger.exception("[DIM SCHEDULE] Failed to load config")+ return error_response(+ ErrorCode.CONFIG_LOAD_FAILED,+ f"Error loading dim schedule configuration: {e}",+ status_code=500+ )+ except Exception:+ logger.exception("[DIM SCHEDULE] Unexpected error loading dim schedule configuration")+ return error_response(+ ErrorCode.CONFIG_LOAD_FAILED,+ "Unexpected error loading dim schedule configuration",+ status_code=500+ )

As per coding guidelines: Catch specific exceptions, not bare except: statements; Use structured logging with context (e.g., "[NHL Recent]") for logging messages; Implement comprehensive logging for remote debugging on Raspberry Pi.

Comment threadsrc/display_controller.py
Comment threadweb_interface/blueprints/api_v3.py Outdated
Chuckand others added 2 commits January 29, 2026 17:43
- Normalize mode string in _check_dim_schedule to handle both "per-day"
and "per_day" variants
- Add try/except around dim_brightness int conversion to handle invalid
input gracefully
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ints
- display_manager.py: Add fail-fast input validation, catch specific
exceptions (AttributeError, TypeError, ValueError), add [BRIGHTNESS]
context tags, include stack traces in error logs
- api_v3.py: Catch specific config exceptions (FileNotFoundError,
JSONDecodeError, IOError), add [DIM SCHEDULE] context tags for
Pi debugging, include stack traces
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ChuckBuilds
ChuckBuilds merged commit 14c50f3 into mainJan 29, 2026
1 check passed
@ChuckBuilds
ChuckBuilds deleted the feature/schedule-timezone-and-dim-schedule branch January 29, 2026 23:12
ChuckBuilds pushed a commit that referenced this pull request May 23, 2026
- get_system_version (alert #218): replaced str(e) with generic message;
exception still logged via logger.error(exc_info=True)
- execute_system_action (alert #216): removed str(e) and full
traceback.format_exc() from the HTTP response — the full stack trace
was being sent directly to clients; replaced with generic message and
proper logger.error call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ChuckBuilds pushed a commit that referenced this pull request May 24, 2026
py/flask-debug (#214):
- debug_web_manual.py: read debug mode from LEDMATRIX_FLASK_DEBUG env var
instead of hardcoded True
py/stack-trace-exposure (#216, #218):
- api_v3.py execute_system_action: remove subprocess stdout/stderr from
HTTP responses; log via logger instead
- api_v3.py get_git_version: validate output matches safe ref format
(^[a-zA-Z0-9._-]+$) before including in response
- api_v3.py: remove all remaining traceback.format_exc() dead variables
and print() debug calls (replaced with logger.debug/warning)
py/reflective-xss (#207, #208, #209, #210, #211, #212):
- api_v3.py: remove plugin_id from all error/success response messages
(uninstall, install, update, health, not-found responses)
- pages_v3.py load_partial: return static "Partial not found" message
instead of echoing partial_name
- pages_v3.py _load_starlark_config_partial: add app_id regex validation,
use static error messages instead of f-strings with app_id
py/path-injection (#187#206):
- pages_v3.py _load_plugin_config_partial: resolve plugins_base and
validate _plugin_dir with relative_to() before all file operations;
same for assets metadata directory
- pages_v3.py _load_starlark_config_partial: resolve starlark_base and
validate schema_file/config_file paths with relative_to()
- plugin_loader.py _find_plugin_directory: resolve plugins_dir and
validate strategy-2 candidates with relative_to()
- plugin_loader.py install_dependencies: resolve plugin_dir first, then
construct requirements_file and marker_path from resolved base
- plugin_loader.py load_module: resolve plugin_dir with strict=True and
validate entry_file with relative_to() before exec_module
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request May 24, 2026
…ss install handler warning (#346)
* fix(web-ui): fix quick actions not firing, add toast feedback, suppress install handler warning
- base.html: add htmx:afterSettle listener to set data-loaded on tab
containers after HTMX swaps their content, preventing the overview
partial from being re-fetched (and handlers lost) on every tab switch
- base.html: call htmx.process() in loadOverviewDirect/loadPluginsDirect
fallbacks so buttons get HTMX handlers even if HTMX finished its
initial body scan before the fallback fetch completed
- overview.html + index.html (11 buttons): replace event.detail.xhr.responseJSON
(undefined in HTMX 1.9.x) with JSON.parse(event.detail.xhr.responseText)
so quick action toast notifications actually fire
- plugins_manager.js: add guarded htmx:afterSettle listener that only calls
attachInstallButtonHandler when #install-plugin-from-url is in the DOM,
eliminating the spurious console warning on non-plugin tab loads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(web-ui): ensure quick-action toasts always fire even on xhr/parse failure
Replace silent catch(e){} in all 11 hx-on:htmx:after-request handlers with a
pattern that sets default message/status before the try block and calls
showNotification(m,s) unconditionally after it, so a fallback toast is shown
whenever xhr is absent or responseText is not valid JSON.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(web-ui): show error toast on non-JSON 4xx/5xx quick-action responses
In the catch block of all 11 hx-on:htmx:after-request handlers, check
xhr.status >= 400 and downgrade s to 'error' so a failed action that
returns an HTML error page (or other non-JSON body) surfaces as an error
toast instead of the optimistic 'success'/'info' default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(web-ui): guard setTimeout fallback for attachInstallButtonHandler
The 500ms fallback setTimeout was calling attachInstallButtonHandler()
unconditionally even when the plugins partial wasn't in the DOM, causing
a spurious console.warn on every page load. Add the same element-existence
check already present on the htmx:afterSettle listener.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix backup API 404s, hardware status 500, and HTMX loading race
- Add all backup API routes to api_v3.py: preview, list, export,
validate, restore (with plugin reinstall), download, delete
- Fix PermissionError on /hardware/status: return graceful 200 instead
of 500 when the status file is owned by a different user; also fix
root cause by writing the file world-readable (0o644) in display_manager
- Fix HTMX race: dispatch htmx:ready window event from HTMX onload
callback; loadTabContent now waits for that event instead of
immediately falling back to direct fetch (eliminating the
"HTMX not available" console warning on initial load)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Cancel HTMX fallback timers when htmx:ready fires
The 5-second setTimeout fallbacks for plugins and overview were firing
before the htmx:ready event arrived, logging spurious warnings. Each
timer now self-cancels via htmx:ready so the fallback only triggers
when HTMX genuinely fails to load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Address review feedback: error leaks, ok:false, htmx:ready coverage
- Backup endpoints: replace raw str(e) in user-facing responses with a
generic message; full exception still logged via exc_info=True
- hardware/status: change ok:null to ok:false for PermissionError and
json.JSONDecodeError so the UI's hw.ok===false check triggers correctly
- base.html: dispatch htmx:ready from the fallback load path so any
deferred listeners fire on CDN-fallback loads too
- loadTabContent: also listen for htmx-load-failed so overview/wifi/plugins
fall back to direct fetch when HTMX is completely unavailable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Treat system-managed pip packages as satisfied for dependency marker
When a plugin's requirements.txt includes a package installed via the
system package manager (dnf/apt), pip fails with 'uninstall-no-record-file'
because it can't replace the system-tracked copy. The package is present
and functional, but the missing marker caused the install to be retried
on every service restart.
Detect this specific error pattern: if the only pip failure is
uninstall-no-record-file, write the .dependencies_installed marker and
log a warning instead of returning False, suppressing the repeated warning.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix uninstall-no-record-file detection condition
The previous check used a string replacement that left 'error:' in the
remaining text, causing the condition to always evaluate false. Simplify
to a direct substring check: if 'uninstall-no-record-file' appears in pip
stderr the affected package is installed at the system level and we write
the marker, suppressing the repeated warning on every restart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Resolve CodeQL security findings in backup API
Path traversal (CWE-22):
- backup_download: switch from send_file(user-tainted-path) to
send_from_directory(_BACKUP_EXPORT_DIR, filename); Flask uses
werkzeug safe_join internally which CodeQL recognises as a sanitizer
- backup_delete: enumerate the export directory and match by name so
entry.unlink() operates on a filesystem-derived Path rather than one
constructed from user input; _safe_backup_path still guards first
Information exposure through exceptions (CWE-209):
- backup_validate: err_msg from validate_backup() can embed exception
strings containing temp-file paths; log the detail, return a generic
'Invalid or corrupted backup file' to the client
- Other backup endpoints: already fixed (str(e) -> generic message);
CodeQL alerts will clear on next scan
plugin_loader.py:185 (path traversal): false positive — requirements_file
is constructed from plugin_dir returned by find_plugin_directory() (a
filesystem scan), not from raw HTTP request input; no change needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix pre-existing information exposure in version and action endpoints
- get_system_version (alert #218): replaced str(e) with generic message;
exception still logged via logger.error(exc_info=True)
- execute_system_action (alert #216): removed str(e) and full
traceback.format_exc() from the HTTP response — the full stack trace
was being sent directly to clients; replaced with generic message and
proper logger.error call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix remaining GitHub CodeQL security alerts
- py/stack-trace-exposure: Remove str(e) and traceback.format_exc() from
all HTTP responses across api_v3.py, pages_v3.py, and app.py; replace
with generic messages and logger.error(exc_info=True)
- py/reflective-xss: Escape partial_name via markupsafe.escape in the
load_partial 404 response
- py/path-injection: Add regex validation of plugin_id before filesystem
use in _load_plugin_config_partial
- py/incomplete-url-substring-sanitization: Replace 'github.com' in
substring checks with urlparse hostname comparison in store_manager.py
- py/clear-text-logging-sensitive-data: Remove football-scoreboard debug
prints and sensitive request-body prints from update endpoint
- js/bad-tag-filter: Replace script-only regex in BaseWidget.sanitizeValue
with DOM-based textContent stripping that removes all HTML
- js/incomplete-sanitization: Fix escapeAttr to properly encode &, ", ',
<, > using HTML entities instead of backslash escaping
- js/prototype-pollution-utility: Add __proto__/constructor/prototype
key guards to deepMerge function in plugins_manager.js
- app.py error handlers: Always return generic messages; remove debug-mode
branches that could expose tracebacks in production
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix three remaining CodeQL path-injection and info-exposure alerts
- plugin_loader.py: resolve plugin_dir with strict=True and validate
marker_path with relative_to() before any filesystem writes, giving
CodeQL the positive sanitization pattern it requires (py/path-injection)
- api_v3.py _safe_backup_path: replace substring negative checks with a
strict positive regex (^[a-zA-Z0-9][a-zA-Z0-9._-]{0,200}\.zip$) that
CodeQL recognises as sanitising the user-supplied filename
(py/path-injection)
- api_v3.py backup_validate: whitelist known-safe manifest fields before
returning JSON, preventing any exception strings captured inside
validate_backup() from reaching the HTTP response (py/stack-trace-exposure)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Resolve 29 open CodeQL security alerts across 5 files
py/flask-debug (#214):
- debug_web_manual.py: read debug mode from LEDMATRIX_FLASK_DEBUG env var
instead of hardcoded True
py/stack-trace-exposure (#216, #218):
- api_v3.py execute_system_action: remove subprocess stdout/stderr from
HTTP responses; log via logger instead
- api_v3.py get_git_version: validate output matches safe ref format
(^[a-zA-Z0-9._-]+$) before including in response
- api_v3.py: remove all remaining traceback.format_exc() dead variables
and print() debug calls (replaced with logger.debug/warning)
py/reflective-xss (#207, #208, #209, #210, #211, #212):
- api_v3.py: remove plugin_id from all error/success response messages
(uninstall, install, update, health, not-found responses)
- pages_v3.py load_partial: return static "Partial not found" message
instead of echoing partial_name
- pages_v3.py _load_starlark_config_partial: add app_id regex validation,
use static error messages instead of f-strings with app_id
py/path-injection (#187#206):
- pages_v3.py _load_plugin_config_partial: resolve plugins_base and
validate _plugin_dir with relative_to() before all file operations;
same for assets metadata directory
- pages_v3.py _load_starlark_config_partial: resolve starlark_base and
validate schema_file/config_file paths with relative_to()
- plugin_loader.py _find_plugin_directory: resolve plugins_dir and
validate strategy-2 candidates with relative_to()
- plugin_loader.py install_dependencies: resolve plugin_dir first, then
construct requirements_file and marker_path from resolved base
- plugin_loader.py load_module: resolve plugin_dir with strict=True and
validate entry_file with relative_to() before exec_module
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix 15 remaining CodeQL path-injection and stack-trace-exposure alerts
Switch from resolve()+relative_to() to os.path.basename() reassignment,
which CodeQL recognizes as a path sanitizer that breaks the taint chain.
Also remove exception objects from backup_manager validate_backup return
strings to eliminate the stack-trace-exposure taint source.
Fixes alerts #227, #233, #234, #235, #237, #238, #239, #240, #241,
#242, #243, #244, #245, #246, #247.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix broken logger format string and leaked exception in config save error
- pages_v3.py: plain string was used instead of %-style substitution,
so every manifest-read failure logged the literal "{plugin_id}"
- api_v3.py save_main_config: exception message was still leaking
through the error response; replace with generic message (consistent
with the rest of the CodeQL sweep in this PR)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for freeto 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

@ChuckBuilds