docs/long-term-plans/caldav-vevent-expansion-design.md
Status: Planned
Extend the existing CalDAV provider to support VEVENT (calendar events) alongside VTODO (tasks). This gives self-hosted calendar users (Nextcloud, Radicale, Baikal, Fastmail) two-way event sync with no new auth infrastructure — the same basic auth that already works for VTODOs.
@nextcloud/cdav-library already supports findByType('VEVENT') and findByTypeInTimeRange('VEVENT', from, to). ical.js is already used for parsing.| Decision | Choice | Rationale |
|---|---|---|
| Provider approach | Extend existing CalDAV provider | Single provider handles both VTODOs and VEVENTs from the same server connection |
| Event behavior | Configurable per-provider: banners (default) or auto-import as tasks | Matches user request; reuses existing isAutoImportForCurrentDay pattern from ICAL provider |
| Data model | Reuse CalendarIntegrationEvent + CalDAV-specific wrapper for etag/URL | Shares display layer with ICAL provider; adds sync metadata for write-back |
| Auth | Same basic auth as VTODO — no changes | Already works, already implemented |
| Sync direction | Two-way (configurable per field, like VTODO) | Consistent with existing CalDAV sync behavior |
CalDAV provider (src/app/features/issue/providers/caldav/):
@nextcloud/cdav-library + ical.jsCaldavSyncAdapterService implements IssueSyncAdapter<CaldavCfg>SyncDirection = 'off' | 'pullOnly' | 'pushOnly' | 'both')ICAL provider (src/app/features/issue/providers/calendar/):
.ics URLsCalendarIntegrationEvent model: { id, calProviderId, title, description, start, duration, isAllDay }isAutoImportForCurrentDay flag for auto-creating tasks from eventsshowBannerBeforeThreshold)vtodo subcomponent).ics URLs)// Existing CaldavCfg, extended:
interface CaldavCfg extends BaseIssueProviderCfg {
caldavUrl: string | null;
resourceName: string | null;
username: string | null;
password: string | null;
categoryFilter: string | null;
// Existing VTODO sync
twoWaySync?: CaldavTwoWaySyncCfg;
// New VEVENT support
includeEvents?: boolean; // Enable VEVENT fetching (default: false)
eventBehavior?: 'banners' | 'auto-import'; // How events appear in SP
eventTwoWaySync?: CaldavEventTwoWaySyncCfg; // Field-level sync for events
showBannerBeforeThreshold?: number | null; // Minutes before event to show banner
eventCheckInterval?: number; // Poll interval for events (ms)
}
interface CaldavEventTwoWaySyncCfg {
title?: SyncDirection;
description?: SyncDirection;
// VEVENTs don't have "completed" — status is confirmed/tentative/cancelled
// Mapping: task done → event cancelled (configurable)
markDoneAs?: 'cancelled' | 'none';
}
// Extends CalendarIntegrationEvent with CalDAV sync metadata
interface CaldavCalendarEvent extends CalendarIntegrationEvent {
etag_hash: number; // For change detection (same pattern as VTODO)
item_url: string; // CalDAV object URL for write-back
status?: 'CONFIRMED' | 'TENTATIVE' | 'CANCELLED';
location?: string;
categories?: string[];
}
CalDAV Server
↕ (Basic Auth, same connection as VTODOs)
CaldavClientService
├── _getAllTodos() → existing VTODO flow → tasks
└── _getAllEvents() → NEW VEVENT flow:
↓
CalendarIntegrationEvent[]
↓
┌───────────────────────┐
│ eventBehavior config │
├───────────────────────┤
│ 'banners' → display as timeline banners (like ICAL provider)
│ 'auto-import' → create tasks from events (like isAutoImportForCurrentDay)
└───────────────────────┘
↓ (if task created and two-way sync enabled)
CaldavEventSyncAdapter → writes status changes back to server
New methods (parallel to existing VTODO methods):
// Query VEVENTs from CalDAV server
_getAllEvents(calendar, timeRangeStart, timeRangeEnd): CaldavCalendarEvent[]
// Parse a VEVENT from ical.js component
_mapEvent(veventObject): CaldavCalendarEvent
// Update a VEVENT on the server
_updateEvent(cfg, event, changes): Promise<void>
The existing findByTypeInTimeRange('VEVENT', from, to) method on the calendar object handles the CalDAV REPORT query. No new protocol code needed.
CaldavEventSyncAdapterService implements IssueSyncAdapter<CaldavCfg>:
CALDAV_EVENT_FIELD_MAPPINGS = [
{ spField: 'title', issueField: 'summary', label: 'Title' },
{ spField: 'notes', issueField: 'description', label: 'Description' },
// No direct 'isDone' mapping — VEVENTs use status: CONFIRMED/CANCELLED
];
When a user marks a task (created from a VEVENT) as done:
markDoneAs === 'cancelled': set VEVENT STATUS to CANCELLEDmarkDoneAs === 'none': don't write back done statusThe existing CalendarIntegrationEffects.pollChanges$ currently only handles ICAL providers. It needs to also poll CalDAV providers that have includeEvents: true:
eventCheckInterval), call CaldavClientService._getAllEvents() for the relevant time windowCalendarIntegrationEvent[] through the same display pipeline as ICAL eventseventBehavior === 'auto-import', create tasks (reusing existing isAutoImportForCurrentDay logic)A single CalDAV provider instance connects to one calendar resource. That resource may contain both VTODOs and VEVENTs. The provider handles both:
| Aspect | VTODOs (existing) | VEVENTs (new) |
|---|---|---|
| Queried as | calendar.calendarQuery() with VTODO comp-filter | calendar.findByTypeInTimeRange('VEVENT', from, to) |
| Displayed as | Tasks in backlog/today list | Calendar banners or imported tasks |
| Two-way fields | isDone, title, notes | title, description, done→cancelled |
| Change detection | ETag hash | ETag hash (same mechanism) |
| Time window | All open todos (no time filter) | Current day/week (configurable) |
_getAllEvents() and _mapEvent() to CaldavClientServiceCaldavCfg with includeEvents flagCalendarIntegrationEvent display pipelineauto-import behaviorCaldavEventSyncAdapterService| Provider | Target Users | Auth | API | Status |
|---|---|---|---|---|
| ICAL (existing) | Anyone with a public .ics URL | None | HTTP GET | Done (read-only) |
| CalDAV VTODO (existing) | Self-hosted calendar users | Basic auth | CalDAV | Done (two-way) |
| CalDAV VEVENT (this doc) | Self-hosted calendar users | Basic auth | CalDAV | Planned |
| Google Calendar (separate doc) | Mainstream users | OAuth 2.0 (hybrid proxy) | REST API v3 | Planned |
CalDAV VEVENT and Google Calendar are complementary:
CalendarIntegrationEvent display layer and configurable import behaviorsrc/app/features/issue/providers/caldav/src/app/features/issue/providers/calendar/src/app/features/calendar-integration/calendar-integration.effects.tssrc/app/features/calendar-integration/calendar-integration.model.tssrc/app/features/issue/two-way-sync/issue-sync-adapter.interface.tssrc/app/features/issue/two-way-sync/issue-two-way-sync.effects.tsdocs/long-term-plans/google-calendar-provider-design.mddocs/long-term-plans/calendar-two-way-sync-technical-analysis.md