fix(calendars): make the Settings toggle the one visibility model (#75)

Reminders never fired for a calendar hidden at system level and nothing hinted
at it: the provider only schedules reminder alarms for Calendars.VISIBLE=1,
while Calendula filtered with its own disabledCalendarIds pref and parsed
isVisibleInSystem without ever using it — two models that could disagree
indefinitely.

Settings → Calendars now writes Calendars.VISIBLE, one calendar per update
(CalendarProvider2 skips its own checkNextAlarm() reschedule for any selection
that isn't _id=), and every display predicate reads isVisibleInSystem. The
drawer's filter sheet stays a purely in-app declutter and still leaves reminders
alone.

With VISIBLE=0 the provider creates no alert rows, so there is nothing left to
suppress: the disabled-calendar gates, SuppressedReminderStore and the re-enable
recovery are gone. A one-shot migration reconciles the retired set with the app's
state winning — enabled in-app and syncing gets shown, disabled gets hidden,
everything else untouched — so the upgrade changes nothing the user sees.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-25 12:44:56 +02:00
parent 97f650da14
commit 271693c2b6
31 changed files with 628 additions and 557 deletions

View File

@@ -1,7 +1,15 @@
# Design: One calendar-visibility model (reminders silently never fire)
Status: **planned** on `fix/calendar-visibility-model`
Status: **implemented** on `fix/calendar-visibility-model` (on-device review owed)
Date: 2026-07-25
Built as specified, with three notes: `CalendarSource` gained a `syncsEvents`
flag (the migration's `sync_events = 1` condition needs it, and #76 will label
those rows); the migration runs from `CalendulaApp.onCreate` *and* from
`PermissionViewModel.onGranted`, so a launch that starts without the calendar
permissions still migrates the moment they are granted; and the Settings hint
string was reworded rather than dropped — it described the retired app-only
behaviour, so leaving it would have been the one piece of false text on screen.
Tracking: Codeberg #75. Split out of this work: #76 (read-only / not-synced
communication), #77 (accounts merged by name).