aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/specs/2026-10-13-calendar-design.md153
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.