hypercal 2.2.4

Tasks grow into a full app with lists, subtasks, repeats, reminders and
CalDAV sync, availability and booking links for people without an account,
a history for every event, a list of your signed-in sessions, Prometheus
metrics, and a new app frame with a side panel of shortcuts.

**Tasks.** Tasks live in lists, each with its own colour and default
reminders, and can have subtasks, a status, a start and due date, a priority,
categories and a repeat (from the due date or from completion). Reminders
arrive by push and on Android. Tasks show in every calendar view and can be
dragged onto a date. The new Tasks app manages them, and a task list can be
docked beside the calendar. Each list is its own CalDAV collection, VTODOs
round-trip losslessly, and a list can be linked to a remote CalDAV task list
and kept in sync. The MCP endpoint understands lists, subtasks and repeats
too. Database migration 0050 adds task lists and the new task columns; done
tasks become COMPLETED.

**Availability and booking links.** Share a link that shows only when you
are busy, as a web page or as a VFREEBUSY feed for calendar apps, with no
titles or calendar names. A booking link goes further: visitors without an
account pick an open slot from your availability rules, and the event lands
in a calendar you choose. Concurrent requests for one slot produce exactly
one booking. Visitors get a link to cancel from any device, and your own
CalDAV clients see them as an attendee. Set working hours and links up in
Settings > Availability; admins can switch the feature off. Migrations 0043
to 0045 add the tables.

**Busy time.** Each calendar can opt in or out of counting as busy. By
default subscriptions and birthdays no longer count, and accepted or
tentative invitations do. With people selected, Find a time suggests the
next times you are all free. Migration 0042.

**Event history.** Every change to an event is recorded, whichever way it
came in (app, CalDAV, MCP, extension, app password), and shown in the
editor, where any earlier version can be restored. Removed attachments are
kept while a version can still restore them. History and the Trash are
pruned hourly: admins set the caps (50 versions, 180 days of history, 30
days in the Trash by default) and each account can keep less in
Settings > Data. Migration 0047.

**Sessions.** Settings lists where you are signed in, by browser and system,
with when each session was last used, and signs any of them out.
Migration 0048.

**App frame and side panel.** Contacts, settings and admin open as pages in
one app frame, and Tasks and scheduling polls are apps beside the calendar.
The calendar's actions sit as grouped pills in the top bar. On desktop a
side panel on the right opens Contacts, Find a time, Tasks, Polls and
Invitations; Settings > Display > Side panel orders or hides it.

**Smaller things.** A default event length per calendar (migration 0046),
a choosable snap step for drags and keyboard nudges, the sidebar remembers
its state per device, and confirmations that guard undoable actions can be
turned off in Settings > Confirmations.

**Self-hosting.**

- `GET /metrics` serves Prometheus metrics: HTTP requests by route, sync
  passes, DAV requests, subscription fetches, reminder lag and deliveries,
  backup age and failures, database size and process gauges. It answers 404
  until `METRICS_TOKEN` (at least 32 characters) is set. `METRICS_LISTEN`
  serves it on a second listener without a token; a non-loopback address
  also needs `METRICS_LISTEN_ALLOW_PUBLIC`. See the monitoring docs.
- `GET /healthz` answers 503 when the database does not, and the image's
  HEALTHCHECK uses it.
- `RATE_LIMITS` overrides any named rate limit as `name=max/seconds`,
  comma-separated. Unknown names fail the boot.
- Linked calendars and address books remember their last successful sync
  (migration 0049).

Migrations 0042 to 0050 run on first boot.

**Fixes.** Offline edits to one occurrence merge against what the client
saw, tied search results keep a stable order, Find a time no longer offers
the repeat of an hour before availability opens, and an event moved with
the keyboard stays selected when cards slide under the pointer.