Files
calendula/.planning/STATE.md
Jean-Luc Makiola f0e2e12939 feat(edit): event editing — shared form, scoped recurring writes, recurrence picker (v1.3)
The create form (v1.2) now edits: a pencil on the detail screen (writable
calendars only, contextual WRITE upgrade like delete) opens it prefilled via
EventDetail.toEditForm; populated sections always show, the calendar is
fixed, and a dirty-check writes only changed columns (pristine saves are
no-ops). Saving a dirty recurring event parks in SaveUiState.AwaitingScope
and asks how far the change reaches (Google model): "only this event" =
modified-occurrence exception via CONTENT_EXCEPTION_URI (empty optionals as
explicit NULLs since the provider clones the parent row), "this and all
following" = series split (insert new event first, then truncate), "all
events" = series-row update with the time delta applied to the series
DTSTART. A changed rule drops the exception option. Delete gained the same
middle scope.

Recurrence: EventForm.rrule + SimpleRecurrence (FREQ/INTERVAL/UNTIL/COUNT +
weekly BYDAY with locale-ordered weekday toggles) behind a picker on create
and edit; unrepresentable rules render humanized (shared ui/common
RecurrenceText) and survive verbatim. UNTIL validation flags rules ending
before the event starts.

Provider lessons baked in (verified on-device via adb probes): instance
caches regenerate only from an update's own values, so truncation sends the
full time-column set (truncateSeries) — RRULE-only updates left a stale
duplicate occurrence on the split day; UNTIL is written as the local end of
day in UTC (toRRule(zone), previousLocalDayEndUtcMillis) so UTC+x zones
can't leak an extra day. Reminder edits reconcile against actual provider
rows, keeping untouched rows' methods.

Tests: RecurrenceTest (parse/render/round-trip, truncation), update/exception
mapper paths, repository pass-throughs, prefill + populatedFields, raw-title
mapper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:57:32 +02:00

4.2 KiB
Raw Blame History

Calendula — Current State

Last updated: 2026-06-11

Status

Milestone: v2.0 — Write support (milestone 2, in progress) Phase: v1.3.0 (edit event) shipped 2026-06-11 after four on-device review rounds (BYDAY toggles, scoped recurring writes, scope-at-save flip, stale-instances split bugfix). Milestone 2 runs in four slices (docs/superpowers/plans/2026-06-11-03-write-support.md); v2.0 (quick-add, conflict dialog, polish) is the remaining slice, v1.4 (reminder notifications) comes first.

Progress

  • Design spec written and committed (docs/superpowers/specs/2026-06-08-calendar-app-design.md)

  • V1 design decisions resolved (App name "Calendula", icon, seed color)

  • Plan 01 written and executed — foundation lands (theme, icon, i18n, Hilt, DataStore, CI green)

  • Plan 02 written and executed — data layer + permission flow + debug screen

  • Month view (S1) — 6-week grid, event dots, today marker, swipe nav, three states (replaces debug screen)

  • Week view (S2) — time schedule with overlap-resolved lanes, all-day strip, swipe nav, three states

  • Day view (S3) — single-column slice reusing the week layout

  • View-switcher (M1) wired — cycles Month ↔ Week ↔ Day

  • Event-detail screen (S4) — full-screen, humanized recurrence

  • Filter sheet (M3) — per-calendar visibility, grouped by account, persisted, applied centrally in the repository

  • Settings (M4) — appearance (theme, dynamic colour, week start), language (per-app locales), about

  • [~] Jump-to-date (M2) — cut from scope; "Today" half shipped in v0.5, date-picker dropped

  • Full event read (v0.6) — reminders, status, availability, access level, attendee role + self-response, foreign timezone, and linkified description URLs in the detail view; new domain enums + mapper unit tests. (A dedicated URL field was cut — no CalendarContract column backs it.)

  • v1.1 write foundation — WRITE_CALENDAR (onboarding asks READ+WRITE, only READ gates; contextual upgrade for v1.0 installs), read-only-calendar detection (CALENDAR_ACCESS_LEVELcanModifyContents, actions hidden for WebCal/birthday calendars), delete from the detail screen (recurring: "only this event" via cancelled exception / "all events in the series"), repository + mapper tests

  • v1.2 create event — full-screen EventEditScreen (title, all-day, M3 date/time pickers with duration-preserving start moves, writable-only calendar picker preselecting the last-used calendar, location, description), "+" FAB on all three views prefilled with the visible day, insertEvent with provider-correct all-day normalisation (UTC midnights, exclusive end), domain/mapper/repository tests

  • v1.3 edit event (shipped 2026-06-11) — EventEditScreen reused for edit (detail-screen Edit action, canModify-gated, contextual WRITE upgrade), dirty-checked partial update on the Events row (recurring: series DTSTART moves by the user's delta, DURATION instead of DTEND), reminder diff by minutes (kept rows keep their method), simple recurrence picker (FREQ/INTERVAL/UNTIL/COUNT; complex RRULEs preserved verbatim and shown humanized), EventFormField.Recurrence incl. settings default, recurrence also available on create; domain/mapper/repository tests. Review round 1: weekly BYDAY day-toggles in the custom picker ("every week on Mon+Fri"). Review rounds 24: occurrence edit pulled forward from v2.0 and made three-way like delete ("this" = exception row via CONTENT_EXCEPTION_URI, "this and following" = series split, "all" = series update); delete equally three-way (truncation via RRULE UNTIL); the edit-scope question moved to save time (Google model) — dirty recurring saves park in SaveUiState.AwaitingScope, a changed rule drops the "only this event" option

Next

  1. v1.4 — reminder notifications (essential for sole-app use): EVENT_REMINDER receiver + notification channel, POST_NOTIFICATIONS, onboarding step with default-on toggle + duplicate-reminder warning (Etar model)
  2. v2.0 — quick-add sheet, conflict dialog, polish pass, milestone release
  3. Monitor the F-Droid build/publish for v1.1.0 v1.3.0