caldav leaves the platform

calendar and contacts are a plugin now — plugins/caldav, its own repository like
every other one. a fresh machine runs five processes instead of seven and gets a
calendar by installing one plugin, which is the whole point: the platform stops
carrying a python calendar server for people who may not want one.

what left: the radicale sidecar, four routers, the panel apps, the
dav_app_passwords table, the two screens, the two dock tiles, the permission
registry entry, the settings section, and radicale from every package list.

three platform things had to change first, each committed on its own:
6b5c87da a plugin can be more than one screen; e0df28ff a first-party plugin can
claim a reserved root path. this commit adds the third, `personal` on a plugin
permission — a member granted READ must still mint and revoke their own app
password, because that password is how their own phone reaches their own
calendar. those rows are theirs and nobody else's.

two fixes the work surfaced rather than caused. the dock pinned mountPrefix, so
a multi-screen plugin pinned a tile pointing at /caldav — a route no plugin
serves. and the OPTIONS carve-out was a literal list of four DAV paths; it asks
the claim registry now, so it follows whoever owns the route.

/calendar stays in DEFAULT_DOCK_PATHS. it is installed by default and a fresh
dock that silently dropped it would read as a downgrade.

docs:check earned its keep twice here: it caught spotlight still offering
/settings/integrations/dav-app-passwords after the section was deleted, and the
permission count going 22 -> 21. neither is greppable and both were already wrong
when it ran.

verified against alpha, which is still on the pre-extraction code: /.well-known/
caldav 301s, /dav/ answers 401 with the same WWW-Authenticate realm byte for
byte, /api/caldav/passwords 401s, and OPTIONS reaches the DAV router rather than
being answered as a CORS preflight. installed here end to end — schema applied,
sidecar started, two tiles with their own artwork behind one permission.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-27 04:54:42 +00:00
co-authored by Claude Opus 5
parent e0df28ff5d
commit 52f209c69f
45 changed files with 108 additions and 2277 deletions
@@ -187,17 +187,10 @@ export const CORE_DOCK_ITEMS: CoreDockItem[] = [
// `kind: 'app'` — so `canVisit()` hides the tile from anyone not granted it, which is the same treatment
// every other grantable tile here gets.
{ labelKey: 'nav.sendOff', to: '/sendoff', icon: Send, color: '#22d3ee' },
// Core since 2026-08-19. These were contributed by the app-store `caldav` entry until calendar and
// contacts became core, and deleting that entry took their tiles with it — the routes kept working
// while the dock and the settings list went silent about them, which is exactly the shape of failure
// this file's comment above warns about, arrived at from the other direction.
//
// Artwork rather than glyphs since 2026-08-19: both were redrawn to the icon brief, so they carry
// the same voxel mark the phone apps show. `tile: 'bare'` for the same reason a plugin's artwork
// does — the art brings its own frame, and a coloured square behind it reads as a mistake. `color`
// still earns its place: the active glow, the indicator dot and the settings tint all use it.
{ labelKey: 'nav.calendar', to: '/calendar', image: '/icons/calendar.png', tile: 'bare', color: '#ef4444' },
{ labelKey: 'nav.contacts', to: '/contacts', image: '/icons/contacts.png', tile: 'bare', color: '#8b5cf6' },
// Calendar and Contacts are NOT here. They were core tiles from 2026-08-19 until caldav became a
// plugin on 2026-08-27, and they come back through `dockItemsFromPlugins` — one tile per screen, each
// with its own artwork, filtered by the same `calendar` permission. A plugin contributing two tiles is
// what `screens` in a manifest is for.
// Core because the tailnet is the perimeter — origin checking was removed on the grounds that the
// tailnet stands in its place, so administering it cannot be an optional extra. It is `kind: 'admin'`,
// and DashboardLayout filters every tile through canVisit(), so a member never sees this one.
@@ -282,6 +275,8 @@ export function dockItemsFromPlugins(plugins: PluginManifest[]): DockItem[] {
// this list is what every install has and most people want visible; a calendar is now in that category.
// Contacts is deliberately NOT here — it is core and available in dock settings, but a second tile for
// the same sidecar in everyone's default dock is a crowded default rather than a useful one.
// `/calendar` stays in the default arrangement even though it is a plugin route now: it is installed by
// default and a fresh dock that silently dropped it would look like a downgrade.
export const DEFAULT_DOCK_PATHS = ['/', '/files', '/terminal', '/dashboards', '/chat', '/calendar'];
/**
@@ -178,13 +178,9 @@ const SETTINGS_ITEMS: SettingsItem[] = [
icon: KeyRound,
},
{ id: 'set:apify', labelKey: 'settings.sections.apify.label', to: '/settings/integrations/apify', icon: Wrench },
{
id: 'set:dav',
labelKey: 'settings.sections.davSync.label',
to: '/settings/integrations/dav-app-passwords',
hintKey: 'settings.sections.davSync.hint',
icon: CalendarDays,
},
// DAV sync setup left with the caldav plugin on 2026-08-27 — it is a modal on the Calendar screen now,
// and Spotlight has no way to open a plugin's modal. `bun run docs:check` caught this one: it compares
// every settings route Spotlight offers against the sections that actually exist.
// Users — UserManagement
{
id: 'set:users',