feat: surface per-calendar sync errors from /api/caldav/events

The server now reports per-calendar sync failures (e.g. expired
credentials) alongside an otherwise-successful events response, via an
"errors" array. Previously such failures were silently swallowed, so a
calendar could appear enabled while showing zero events with no
explanation.

- Decode the new "errors" field into SyncError (source/name/message),
  following the existing JSONSerialization + from(json:) convention.
- CalendarStore.syncErrors captures these on successful personal
  fetches (loadEvents, prefetchBackground); left untouched on a hard
  fetch failure so it isn't conflated with lastError. Group overlay
  fetches never populate it (the endpoint doesn't apply there).
- CalendarHostView shows a second red warning banner alongside the
  existing lastError banner, reusing the same visual style.
This commit is contained in:
Scarriffle
2026-07-02 18:25:47 +02:00
parent 4e9ae83299
commit 8ed474663e
4 changed files with 69 additions and 7 deletions

View File

@@ -18,6 +18,26 @@ struct EventPerson: Hashable {
}
}
/// A partial-sync failure reported alongside an otherwise-successful
/// `/api/caldav/events` response: one specific calendar didn't sync (e.g.
/// expired credentials) even though it's still enabled. Distinct from a
/// hard fetch failure (`CalendarStore.lastError`) the request itself
/// succeeded, just not every source within it.
struct SyncError: Hashable {
let source: String
let name: String
let message: String
static func from(json: [String: Any]) -> SyncError? {
guard
let source = json["source"] as? String,
let name = json["name"] as? String,
let message = json["message"] as? String
else { return nil }
return SyncError(source: source, name: name, message: message)
}
}
struct CalEvent: Identifiable, Hashable {
let id: String
let url: String