Compare commits
base: makiolaj:release/v2.19.4
makiolaj:main
makiolaj:release/v2.19.4
makiolaj:fix/stale-exdate-on-series-retime
makiolaj:fix/fossify-import
makiolaj:renovate/kotlinxdatetime
makiolaj:renovate/composebom
makiolaj:renovate/appcompat
makiolaj:renovate/fastlane-2.x
makiolaj:renovate/agp
makiolaj:release/v2.20.0
makiolaj:feat/164-week-day-ellipsis
makiolaj:fix/214-widget-remoteviews-size
makiolaj:fix/180-declined-invitations
makiolaj:renovate/ghcr.io-renovatebot-renovate-44.x
makiolaj:renovate/gradle-9.x
makiolaj:renovate/work
makiolaj:renovate/lifecyclecompose
makiolaj:renovate/kotlin
makiolaj:renovate/ghcr.io-renovatebot-renovate-43.x
makiolaj:v2.19.3
makiolaj:v2.19.2
makiolaj:v2.19.1
makiolaj:v2.19.0
makiolaj:v2.18.1
makiolaj:v2.18.0
makiolaj:v2.17.1
makiolaj:v2.16.0
makiolaj:v2.15.0
makiolaj:v2.14.1
makiolaj:v2.14.0
makiolaj:v2.13.1
makiolaj:v2.13.0
makiolaj:v2.12.0
makiolaj:v2.11.2
makiolaj:v2.11.1
makiolaj:v2.11.0
makiolaj:v2.10.0
makiolaj:v2.9.0
makiolaj:v2.8.0
makiolaj:v2.7.5
makiolaj:v2.7.4
makiolaj:v2.7.3
makiolaj:v2.7.2
makiolaj:v2.7.1
makiolaj:v2.7.0
makiolaj:v2.6.0
makiolaj:v2.5.0
makiolaj:v2.4.0
makiolaj:v2.3.0
makiolaj:v2.2.0
makiolaj:v2.1.0
makiolaj:v2.0.0
makiolaj:v1.4.0
makiolaj:v1.3.0
makiolaj:v1.2.1
makiolaj:v1.2.0
makiolaj:v1.1.0
makiolaj:v1.0.0
makiolaj:v0.6.0
makiolaj:v0.5.0
makiolaj:v0.4.0
makiolaj:v0.3.0
makiolaj:v0.2.1
makiolaj:v0.2.0
makiolaj:v0.1.1
makiolaj:v0.1.0
..
compare: makiolaj:2359efaef61b4361de45dec4f60ed586eacdfdc7
makiolaj:release/v2.19.4
makiolaj:fix/stale-exdate-on-series-retime
makiolaj:main
makiolaj:fix/fossify-import
makiolaj:renovate/kotlinxdatetime
makiolaj:renovate/composebom
makiolaj:renovate/appcompat
makiolaj:renovate/fastlane-2.x
makiolaj:renovate/agp
makiolaj:release/v2.20.0
makiolaj:feat/164-week-day-ellipsis
makiolaj:fix/214-widget-remoteviews-size
makiolaj:fix/180-declined-invitations
makiolaj:renovate/ghcr.io-renovatebot-renovate-44.x
makiolaj:renovate/gradle-9.x
makiolaj:renovate/work
makiolaj:renovate/lifecyclecompose
makiolaj:renovate/kotlin
makiolaj:renovate/ghcr.io-renovatebot-renovate-43.x
makiolaj:v2.19.3
makiolaj:v2.19.2
makiolaj:v2.19.1
makiolaj:v2.19.0
makiolaj:v2.18.1
makiolaj:v2.18.0
makiolaj:v2.17.1
makiolaj:v2.16.0
makiolaj:v2.15.0
makiolaj:v2.14.1
makiolaj:v2.14.0
makiolaj:v2.13.1
makiolaj:v2.13.0
makiolaj:v2.12.0
makiolaj:v2.11.2
makiolaj:v2.11.1
makiolaj:v2.11.0
makiolaj:v2.10.0
makiolaj:v2.9.0
makiolaj:v2.8.0
makiolaj:v2.7.5
makiolaj:v2.7.4
makiolaj:v2.7.3
makiolaj:v2.7.2
makiolaj:v2.7.1
makiolaj:v2.7.0
makiolaj:v2.6.0
makiolaj:v2.5.0
makiolaj:v2.4.0
makiolaj:v2.3.0
makiolaj:v2.2.0
makiolaj:v2.1.0
makiolaj:v2.0.0
makiolaj:v1.4.0
makiolaj:v1.3.0
makiolaj:v1.2.1
makiolaj:v1.2.0
makiolaj:v1.1.0
makiolaj:v1.0.0
makiolaj:v0.6.0
makiolaj:v0.5.0
makiolaj:v0.4.0
makiolaj:v0.3.0
makiolaj:v0.2.1
makiolaj:v0.2.0
makiolaj:v0.1.1
makiolaj:v0.1.0
4 Commits
release/v2
...
2359efaef6
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 2359efaef6 |
Merge remote-tracking branch 'origin/release/v2.19.4' into fix/234-edit-single-occurrence
# Conflicts: # CHANGELOG.md |
|||
| c0b7ad29ef |
docs: trim the #234 comments to the load-bearing facts
The detach path's KDoc and inline comments restated the bug narrative that already lives in the commit message and the issue. Keep what a reader of the code needs — why the branch exists, what the detached row loses, and why the insert precedes the EXDATE update — and drop the rest. |
|||
| bc49730ea5 |
fix: guard the detach path against a second copy, and say what it costs (#234)
Review pass on the detach path. Three changes, none to the write shape itself. A second detach of the same occurrence is now refused. Reached from a stale screen still pointing at the parent, it used to succeed twice over: the EXDATE merge folds the repeated stamp away and the parent update still reports one row changed, so the save looked fine and left a *second* standalone copy of one occurrence, this time with neither copy in the series to make it obvious. The occurrence is already gone from the series at that point, so the write reports it as gone — and EventEditViewModel now maps NoSuchEventException from the write to the same "this event no longer exists" answer its pre-check already gives, instead of a bare "couldn't save" for something that isn't there. The rollback comment claimed more than the code delivers. ContentResolver.update returns rows touched, not occurrences excluded, so the zero check catches the series row vanishing mid-write and nothing else — an EXDATE the provider's expansion fails to match reports success and leaves the duplicate standing. Renamed the variable to match, and the catch now mirrors moveEvent's Throwable idiom rather than inventing a narrower one for the same insert-then-roll-back shape. The rest is honesty about what a detached row can't do, since none of it is recoverable later and the KDoc previously mentioned only the calendar-move case: a whole-series delete leaves it standing where an exception row would have gone with the parent; a series-wide *time* edit moves the generated instances but not the absolute-instant EXDATE hole, so the occurrence returns alongside its copy; and building the row from the form rather than cloning the parent drops ORGANIZER, STATUS and the organizer/resource attendee rows, exactly as moveEvent does. The EXDATE staleness is not new — a #47 delete resurrects the same way — but a duplicate is a louder symptom than a resurrection, and it wants fixing at the series-update end, not here. Also notes why the branch predicate is stricter than deleteOccurrence's: EXDATE only means something on a row that recurs, so a row without an RRULE keeps the old path rather than having a recurrence set written onto a one-off event. Tests: the tautological "detached == copy(rrule = null)" assertion is replaced by one that pins every edited field through to the inserted columns, and exdateContains gets its own cases (timed, all-day, absent, a neighbouring occurrence, and a multi-entry list with whitespace). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| a1d1894f84 |
fix: detach the occurrence when editing one instance of an unsynced series (#234)
"Edit only this event" wrote a modified-occurrence exception unconditionally. That is the right shape for a synced series and the wrong one for everything else: an exception attaches to its parent through ORIGINAL_SYNC_ID, so on a row with no _sync_id the link never forms — the insert fails or lands an orphan, the generic catch in EventEditViewModel turns it into a snackbar, and the scope dialog just closes again. To the reporter that read as "nothing happens, ever", with a stray copy of the event left behind the one time the insert did land. This is the same constraint deleteOccurrence has documented since #47, and the same calendars: a local calendar, Calendula's own contact special-date calendars, and — the reporter's case — a Google calendar whose rows the sync adapter has not stamped yet, which is exactly why it "shows as on-device". updateOccurrence now branches on _sync_id the way deleteOccurrence does. A synced series keeps the exception path untouched; its write shape is load- bearing and verified. A series without one gets the occurrence excluded from the parent via EXDATE and the edited values inserted as a standalone event on the same calendar — a detached instance, minus the RECURRENCE-ID the provider has no way to store here. The cost is honest and worth naming: the edited occurrence stops travelling with its series. The alternative is an edit that silently does nothing. Two things shape the write. The parent update reuses buildOccurrenceExdateValues unchanged, so it keeps carrying the whole time/recurrence set — an EXDATE-only update is not a recurrence change to the provider and leaves the expanded instances standing (#47's first quirk). And the form's RRULE is stripped before the insert: the exception path gets an inherited rule cleared for free by DTSTART + DURATION, but nothing clears one here, so leaving it would insert a second *series* overlapping the first. Ordering is chosen for the failure cases, not the happy path. The insert runs first, so a failure there leaves the series completely untouched — the discipline updateEventFromOccurrence already follows. If the EXDATE update then fails, the new row is a visible duplicate of an occurrence still in the series, so it is rolled back (best effort) before the failure surfaces. The reverse order could strand an occurrence excluded from its series with nothing standing in for it, turning an edit into a silent delete. Also makes a failed save legible, since this bug was invisible precisely because it wasn't: the failure snackbar gets the long duration instead of a flash, and the catch logs the scope and event id — never the form's content — so a failure leaves something to report. Adds JVM tests for the detached shape: the rule is dropped and the row becomes a one-off with DTEND, all-day stays on UTC midnights, and the detached row's DTSTART agrees with the EXDATE stamp that removes it from the parent, timed and all-day. The provider behaviour itself still needs a device. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |