events: edit a series' repeat rule (ADR-0032) #104

Merged
nalum merged 3 commits from feat/repeat-rule-editing into main 2026-08-16 19:44:43 +00:00
Owner

Stacked on #103 (merge order #101 → #102 → #103 → this). Closes #77 — the last open item on the #100 plan — with ADR-0032 settling the semantics the issue asked to have decided.

The decision (ADR-0032). The repeat rule (and its until date) is editable for the whole series, through EventService/Update — no new RPC, so the membership guard, audit trail and matrix entry are the ones Update already has. The series re-derives from its own start under the new rule; "change from here forward" is deliberately deferred (it composes later as split-then-edit). Child rows — one-off edits and tombstones — survive where their slot still lands under the new rule; orphans are deleted in the same transaction, riding the parent's UPDATE audit entry. And a rule change is a move: the members hear EVENT_MOVED, because the rule decides where occurrences land exactly as the start time does.

Server. UpdateEventRequest gains repeat (presence = replace, validated like a created rule) and clear_repeat (the series stops repeating), mutually exclusive. A child row refuses a rule of its own (ErrChildRepeat, failed-precondition). The handler moves from svc.Mutate to svc.MutateTx so the orphan pruning shares the rule write's transaction; the moved-detection (whenOf, issue #94) now includes a rule signature. Specs cover pruning, survival, clearing, the mutual exclusion and the child refusal.

Web. The event form prefills the series' rule and shows the RepeatEditor when editing a series (a child row or a plain event keeps the section hidden — recurrence is still not added in an edit). Switching the rule off sends clear_repeat; the span-versus-period validations now guard edits the same as creates.

Android. Mirror for mirror: the edit row carries the parent's rule (toRowForEdit previously shed it), the sheet prefills RepeatRuleEditor for a series, and the repository's updateEvent sends the replacement rule or clear_repeat.

Verified against the live deploy: a weekly Tuesday series with a tombstoned slot was changed to Thursdays — the calendar showed Thursdays only, the orphaned Tuesday tombstone was pruned, and the members received EVENT_MOVED. make check and the Android unit tests are green; on-device Android verification pending, as with the rest of the stack.

Stacked on #103 (merge order #101 → #102 → #103 → this). Closes #77 — the last open item on the #100 plan — with **ADR-0032** settling the semantics the issue asked to have decided. **The decision (ADR-0032).** The repeat rule (and its `until` date) is editable for the **whole series**, through `EventService/Update` — no new RPC, so the membership guard, audit trail and matrix entry are the ones `Update` already has. The series re-derives from its own start under the new rule; "change from here forward" is deliberately deferred (it composes later as split-then-edit). Child rows — one-off edits and tombstones — **survive where their slot still lands** under the new rule; orphans are deleted in the same transaction, riding the parent's UPDATE audit entry. And a rule change is a move: the members hear EVENT_MOVED, because the rule decides where occurrences land exactly as the start time does. **Server.** `UpdateEventRequest` gains `repeat` (presence = replace, validated like a created rule) and `clear_repeat` (the series stops repeating), mutually exclusive. A child row refuses a rule of its own (`ErrChildRepeat`, failed-precondition). The handler moves from `svc.Mutate` to `svc.MutateTx` so the orphan pruning shares the rule write's transaction; the moved-detection (`whenOf`, issue #94) now includes a rule signature. Specs cover pruning, survival, clearing, the mutual exclusion and the child refusal. **Web.** The event form prefills the series' rule and shows the `RepeatEditor` when editing a series (a child row or a plain event keeps the section hidden — recurrence is still not *added* in an edit). Switching the rule off sends `clear_repeat`; the span-versus-period validations now guard edits the same as creates. **Android.** Mirror for mirror: the edit row carries the parent's rule (`toRowForEdit` previously shed it), the sheet prefills `RepeatRuleEditor` for a series, and the repository's `updateEvent` sends the replacement rule or `clear_repeat`. Verified against the live deploy: a weekly Tuesday series with a tombstoned slot was changed to Thursdays — the calendar showed Thursdays only, the orphaned Tuesday tombstone was pruned, and the members received EVENT_MOVED. `make check` and the Android unit tests are green; on-device Android verification pending, as with the rest of the stack.
The recurrence was fixed at creation: the edit flow hid the repeat
control on purpose, because a rule change is not a plain field update
— it re-derives every future occurrence and collides with one-off
edits already made. ADR-0032 settles the semantics: the rule (and its
until date) is editable for the whole series through Update, the
series re-derives from its own start, and existing child rows survive
where their slot still lands under the new rule — orphans are
deleted in the same transaction. A rule change is a move, so the
members hear EVENT_MOVED. Closes #77 for web pending its own commit.
The edit sheet hid the repeat control on purpose; with ADR-0032 the
rule and its until date are editable for the whole series. Switching
the rule off sends clear_repeat; the span-versus-period validations
now guard edits the same as creates.
feat(android): edit a series' repeat rule from the event sheet
Some checks failed
check / commits (pull_request) Successful in 7s
check / go (pull_request) Successful in 8m21s
check / report (pull_request) Successful in 3s
check / web (pull_request) Successful in 11m19s
android / build (pull_request) Successful in 30m37s
android / report (pull_request) Successful in 5s
android / report (push) Has been cancelled
check / go (push) Has been cancelled
check / report (push) Has been cancelled
check / web (push) Has been cancelled
tag / tag (push) Has been cancelled
android / build (push) Has been cancelled
check / commits (push) Has been cancelled
c39fefbbc3
Mirrors the web: with ADR-0032 the rule and its until date are
editable for the whole series; switching it off sends clear_repeat.
The edit row now carries the parent's rule so the sheet prefills
honestly.

Test report

Suite Tests Result Skipped
Unit 1359 ✅ pass 1
Integration 83 ✅ pass —

Coverage: 28.1%

Updated by the check workflow · commit c39fefbbc3

<!-- ci-test-report --> ## Test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit | 1359 | ✅ pass | 1 | | Integration | 83 | ✅ pass | — | **Coverage:** 28.1% <sub>Updated by the check workflow · commit c39fefbbc30c1696e927e4af198aced8a284db9b</sub>

Android test report

Suite Tests Result Skipped
Unit (debug) 31 ✅ pass 0

Coverage: 2.4% of lines

Updated by the android workflow · commit c39fefbbc3

<!-- android-test-report --> ## Android test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit (debug) | 31 | ✅ pass | 0 | **Coverage:** 2.4% of lines <sub>Updated by the android workflow · commit c39fefbbc30c1696e927e4af198aced8a284db9b</sub>
nalum changed target branch from feat/event-reminders to main 2026-08-16 19:44:37 +00:00
nalum merged commit c39fefbbc3 into main 2026-08-16 19:44:43 +00:00
nalum deleted branch feat/repeat-rule-editing 2026-08-16 19:44:43 +00:00
Sign in to join this conversation.
No reviewers
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app!104
No description provided.