github olivierlambert/calrs v1.7.0
v1.7.0: OOM fix, explicit event-type timezone, team cross-TZ availability

6 hours ago

Correctness and resilience release. Fixes an OOM-triggering infinite loop in slot computation, adds explicit per-event-type timezones, makes team slot grids honour each member's personal working hours across timezones, and parallelizes per-member CalDAV syncs with per-source deduplication.

Added

  • Explicit timezone on event types (issue #50) — each event type now carries its own IANA timezone column; availability rules are interpreted in that timezone rather than silently inheriting the creator's profile. New picker in the event-type form. Migration 046_event_type_timezone backfills existing rows with the current account owner's timezone, so upgrades preserve behaviour.
  • Per-member working hours on team events — team slot grids intersect each member's personal user_availability_rules (converted from the member's own timezone into the event's host timezone) with the event-type's rules. Members without explicit personal hours stay unconstrained. Prevents the scenario where a team event pinned to Paris would offer 09:00 Paris bookings to a US-based member whose actual working day is 09:00 Chicago (= 16:00 Paris).
  • Member timezone shown on event-type priority list — the Member Priority / Required Members section now renders each member's timezone under their name. Makes mis-configured user timezones immediately visible.

Fixed

  • Infinite slot loop → OOM when availability window ends near midnightcompute_slots_from_rules walked its inner cursor as a NaiveTime, and NaiveTime + Duration wraps at 24h. On a rule ending at 23:00 with a 60-minute slot duration, cursor + slot_duration wrapped to 00:00 (still ≤ 23:00 as a time-of-day), producing an infinite loop until the kernel OOM-killed the process (~4-minute CPU spike, ~9 GB RAM, ~240 GB of SQLite re-reads under memory pressure). Cursor now walks as NaiveDateTime so midnight rolls into the next day cleanly.
  • Dashboard Decline button no-op on pending bookings (#51) — cancel_booking filtered on status = 'confirmed', so clicking Decline on a pending booking matched zero rows and silently redirected. Now branches on status: confirmed → cancelled (CalDAV delete + emails), pending → declined (guest decline notice only). Mirrors the email-token decline flow.

Performance

  • Parallel per-member CalDAV sync on team / dynamic-group slot pages — fans out via tokio::task::JoinSet, guarded by a per-source async mutex inside sync_if_stale: same-source concurrent calls serialize and the loser skips after re-checking staleness. At most one CalDAV fetch per source is in flight at any time across the whole process. Team booking pages no longer serialize on the slowest member's CalDAV server.

Internal

  • Migration 046_event_type_timezone with per-row backfill
  • New helper normalize_event_type_tz validates IANA submissions
  • Regression tests: get_host_tz_prefers_explicit_event_type_timezone, compute_slots_terminates_with_window_ending_at_23_00, chicago_member_is_busy_at_paris_morning, member_without_personal_rules_is_unconstrained, source_lock_identity, sync_if_stale_serializes_on_per_source_lock
  • 545 tests total (up from 537 in 1.6.0), all green on pre-commit
  • Verified end-to-end against a copy of a production DB: the previously-OOMing team booking URL now responds in under a second with flat RSS

Full changelog: https://github.com/olivierlambert/calrs/blob/v1.7.0/CHANGELOG.md#170---2026-04-24

Don't miss a new calrs release

NewReleases is sending notifications on new releases.