diff options
| -rw-r--r-- | docs/superpowers/specs/2026-10-13-calendar-design.md | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-10-13-calendar-design.md b/docs/superpowers/specs/2026-10-13-calendar-design.md new file mode 100644 index 0000000..792e951 --- /dev/null +++ b/docs/superpowers/specs/2026-10-13-calendar-design.md @@ -0,0 +1,153 @@ +# Calendar panel design + +A calendar panel that drops from the island's clock pill: a month grid beside +the selected day's agenda, and an editor that creates, edits and deletes +events in the vdirsyncer calendars. This is sub-project 3 of the roadmap in +`2026-10-07-island-core-design.md`. + +The model is Modesty's calendar menu, read for ideas only (GPLv3, this repo is +GPLv2-only). + +## Scope + +In v1: + +- Month grid with event dots, month navigation, today and selection marked. +- Agenda of the selected day. +- Create, edit and delete events: title, calendar, date or date range, start + and end time or all day, simple recurrence, one alarm, location, notes. +- Simple recurrence: none, daily, weekly, monthly, yearly, with an optional + until-date. Edit and delete act on the whole series. +- Live refresh when the calendar files change, whoever changed them. +- A sync after every write. + +Not in v1: per-occurrence edits (`RECURRENCE-ID`, `EXDATE`, "this and +following"); rich recurrence rules beyond a plain `FREQ` and `UNTIL`; more than +one alarm per event from the form; a keybind; an upcoming-event hint on the +clock pill. Measured on the live calendars: no event uses `RECURRENCE-ID` or +`EXDATE`, so the cut loses nothing in use today. + +## Where it lives + + desktop/island/calendar/ + calctl Python: list, new, edit, delete; JSON in and out + test_calctl.py plain asserts against temporary vdirs + Calendar.qml singleton: calctl list, the file watch, writes + CalendarPanel.qml month grid left, agenda or editor right + EventEditor.qml the form + +The island is the only consumer, so the backend sits beside its QML. + +## Backend: calctl + +`/usr/bin/python3` with `icalendar` and `dateutil`, both already installed for +cal-notif; nothing else outside the standard library. Calendars come from the +khal config's `[calendars]` section (name, path, colour), so there is no +second list to keep in step. khal and cal-notif keep working untouched: all +three read the same vdir. + +khal was the roadmap's first idea for reading. It is not used: 0.11 has no JSON +output, and its sqlite cache is a second view of the same files. + +Commands: + + calctl calendars [{name, color}] + calctl list <from> <to> occurrences in [from, to), local time + calctl new < event.json prints the new event's uid + calctl edit <uid> < event.json + calctl delete <uid> + +`list` expands recurrence with `dateutil.rrule` and returns one object per +occurrence: uid, calendar, title, start, end, allDay, location, notes, repeat, +until, alarm. `repeat` is `none`, `daily`, `weekly`, `monthly`, `yearly`, or +`custom` for any rule richer than a plain `FREQ` with optional `UNTIL`. +`alarm` is `none`, `10m`, `1h`, `1d`, or `custom` for anything else (several +alarms, another offset, an absolute trigger, an alarm after the start). + +Writes: + +- `new` writes `<uid>.ics` into the chosen calendar's directory. Timed events + carry a `TZID` for the local zone with its VTIMEZONE; all-day events are + `DATE` values with an exclusive end, per RFC 5545. +- `edit` loads the file and changes only the properties the form owns. Other + properties, VTIMEZONEs, a `custom` rule and `custom` alarms survive. Picking + a preset alarm replaces every VALARM; picking a preset repeat replaces the + RRULE. A calendar change moves the file to the other directory. +- `delete` removes the file. +- Every write goes to a temporary file in the same directory and is renamed + into place, so a reader never sees half a file. +- After a write, `vdirsyncer sync` is started detached, its output appended to + the existing sync log. A failed sync is not an error for calctl: the cron + run every 30 minutes retries. + +Errors go to stderr with exit 1; stdout is JSON only on success. + +## Shell side + +`Calendar.qml` is a singleton. It runs `calctl list` for the six weeks the +month grid shows, and again on month navigation. It watches both calendar +directories with `inotifywait` for `close_write,moved_to,delete`, debounced, +the same shape as the mail count's xapian watch, so an edit from khal, the +phone via cron sync, or the panel itself all repaint the grid. Writes go +through it: `create`, `update` and `remove` run calctl and report the stderr +line on failure. + +The clock pill gains a click that sets the island's `panel` to `"calendar"`. +The pill opens to about 620px; its flick and VM dots are unchanged at rest. + +**Left: the month.** Seven columns, weeks from Monday (khal's +`firstweekday = 0`). Days outside the month dimmed, today in the accent's +text colour, the selected day outlined. A day with events carries a dot per +calendar that has events that day, in that calendar's khal colour mapped to a +Theme colour. Header with the month name and previous/next arrows. + +**Right: the agenda.** The selected day's events in start order, all-day +first, each with a calendar-coloured bar, its time range and title. A click +opens it in the editor. "+ New event" at the bottom opens an empty editor for +that day. + +**The editor** replaces the agenda. While it is open the month grid is its +date picker: a click sets the date, a shift-click a second day sets an end +date for a multi-day event. Fields: + +- title; +- calendar, one chip per calendar; +- start and end time, two `HH:MM` text fields, scroll steps 15 minutes; an + all-day chip hides them; +- repeat: none, day, week, month, year, plus `custom` shown when the event has + a richer rule and not selectable; an optional until-date set by clicking the + grid after choosing "until"; +- alarm: none, 10m, 1h, 1d, plus `custom` shown when not a preset and not + selectable; when cal-notif's `overrides.toml` has the event's uid, a note + says cal-notif's own alarm wins; +- location, notes; +- Delete (a second click confirms), Cancel, Save. + +A new event defaults to the next whole hour, one hour long, the default +calendar from the khal config, alarm 1h (the most common alarm in the live +calendars, 84 of 97 events with one). + +Escape closes the editor first and the panel second. + +## Errors + +A failed calctl keeps the editor open with its stderr line under the buttons, +so nothing typed is lost. A list that fails keeps the last good month and +shows the line in the agenda. + +Sync conflicts follow the vdirsyncer pair's `conflict_resolution = "b wins"`: +an event edited here and on the server before the next sync keeps the server's +version. That is the existing behaviour, documented, not handled. + +## Testing + +- `test_calctl.py`: list across a month boundary, all-day and multi-day, + yearly expansion of an event created years ago, `custom` detection for a + rich rule and a non-preset alarm, edit preserving unknown properties and a + custom alarm, a calendar move, delete, atomic write leaving no temp file. + Run against temporary vdirs with a temporary khal config; never against the + live calendars. +- A smoke check of the live shell log after wiring. +- The user checks on screen: grid, agenda, create, edit, move between + calendars, repeat, alarm, delete, and an edit made in khal repainting the + panel. |
