Features
- Events you're attending but not organizing are now marked as read-only โ shown with a lock icon on the calendar and in the event drawer
- Read-only event drawer disables all editing fields and hides delete/edit controls, with a clear banner explaining why
- Calendar sync runs as a background job every 15 minutes automatically โ no manual trigger required
- Incremental sync fetches only events that changed since the last sync, reducing API calls by ~98% on subsequent runs
Improvements
- Calendar sync now processes all events across paginated results โ previously capped at 500 events
- Sync runs asynchronously so it no longer blocks the UI while fetching from Google
- Permission errors (like trying to update an event you don't own) are now handled separately from rate limit errors and don't trigger exponential backoff
- Rate limiting uses lower concurrency and inter-request delays to prevent hitting Google API limits
Bug Fixes
- Fixed a feedback loop where calendar sync would endlessly try to push updates to Google for the same events, generating dozens of cascading errors
- Fixed incorrect timestamp handling in the sync path that caused Trilo to always appear newer than Google, triggering unnecessary update attempts
- Fixed a bug where `forbiddenForNonOrganizer` 403 errors were misclassified as rate limit errors, causing unnecessary backoff and circuit breaker trips
- Fixed a race condition in sequential event processing where shared error counters could be mutated concurrently, causing the circuit breaker to trigger unreliably
- Fixed an infinite loop in paginated sync when a page returned a bare array after a paginated response