pastilhasandClaude Opus 5 bfb12c2dde drop the jsonBody workaround — the platform parses these bodies now
this plugin read ctx.req.json() directly because registerProxiedPrefix claimed
the whole of /api/caldav, so bodyParser skipped the plugin's OWN routes and every
field read as missing.

the platform's proxy takes a proxiedSubPath now, so this claims only
/api/caldav/_officer — what the sidecar actually answers — and /passwords and
/provision/ios get their bodies the ordinary way.

verified end to end: POST /api/caldav/passwords with a label came back with the
label, and _officer/collections still reaches the sidecar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-27 08:48:53 +00:00
2026-08-27 07:36:16 +00:00

Calendar

Calendars and contacts on your own machine, synced to your phone over CalDAV and CardDAV.

  • Radicale, not a container. A ~2MB Python package, spawned on loopback by this plugin's sidecar. It was a Docker service only because the old app store made everything one.
  • Per user. The sync door resolves an app password to one account and tells Radicale that account's id is the principal; [rights] type = owner_only does the rest. Every user gets their own calendars and their own contacts, in their own directory, invisible to everyone else.
  • Phones work. DAVx5, iOS and Thunderbird, including RFC 6764 autodiscovery from a bare domain.

Two screens, one plugin

/calendar and /contacts. They are two places you go rather than two sections of one page, which is why this is the first plugin to declare screens in its manifest — one sidecar, one permission, one install, two routes and two dock tiles.

What it claims from the platform, and why

Four paths outside /api, above the account gate:

Path Why it cannot sit behind the session
/dav/* DAVx5, iOS and Thunderbird send HTTP Basic on every request and have nowhere to put a JWT
/dav/provision/*.mobileconfig Safari is handed a URL by the phone app and has no credential; the one-shot token in the path IS the authentication
/.well-known/caldav RFC 6764 — probed with a bare domain before the client has any credential
/.well-known/carddav the same, for contacts

"Above the account gate" is not "open". The sync door demands a scoped DAV app password and resolves it to a user before anything reaches Radicale.

Every one of those is in the platform's RESERVED_ROOT, claimable only by a first-party plugin. A third-party plugin is confined to /p/<publisher>/ so it can never shadow a core route.

Extracted from the platform on 2026-08-27

It was core from 2026-08-19. What moved: the sidecar, the four routers, the panel apps, the app-password table, and the settings form — which became a modal on the Calendar screen, because a plugin ships panels and cannot contribute a settings section.

Two things deliberately did not change. The table is still dav_app_passwords rather than caldav_app_passwords: installs already hold real rows, and the platform's prefix convention is not worth a data migration on a live machine. And the panel names still resolve through the platform's translation catalogs, so the six locales they had as core screens survive.

S
Description
Calendars and contacts on your own machine, synced to your phone over CalDAV and CardDAV.
Readme
777 KiB
Languages
TypeScript 100%