Files
Calendarr/backend/SETTINGS_SYNC.md
Scarriffle 6316ed3a6b feat(web): theme import/export, more themable colors, hideable local/ical calendars
Theme:
- Export/import themes via UI as <date>_<time>.theme (JSON, readable keys,
  link to THEME.md). Import is partial-aware: only present params are written
  (server accepts partial); prompts before importing files with unknown params.
- Dynamic favicon + theme-color tinted to the primary colour on load/save.
- New themable colours: general hover-highlight, day hover/selected/bg,
  today background, plus two unified sidebar action-icon colours
  (inactive/active) covering bell, hide, delete and read-only icons.
- All new colours are per-setting syncable; documented in THEME.md.

UX:
- Styled confirm dialog (#modal-confirm) replaces window.confirm() for
  calendar delete and account disconnect.
- Birthday/local calendars and iCal subscriptions can now be hidden from the
  sidebar via Settings (new sidebar_hidden column + hide toggle).

Backend: additive nullable columns + idempotent migrations for user_settings
colours and local_calendars/ical_subscriptions.sidebar_hidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 13:20:30 +02:00

3.1 KiB

Settings sync contract (Web / iOS / Android)

For a human-facing description of each theme colour parameter (and the .theme import/export format), see ../THEME.md.

Per-setting, cross-device synchronisation of user settings. The server is the sole authority for which settings sync; clients must not duplicate that logic. This document is the shared contract all three clients implement identically.

Canonical keys & default flags

Source of truth: DEFAULT_SYNC in backend/routers/settings_router.py. Keys use the server's snake_case field names.

Key Kind Default sync
default_view enum ON
week_start_day enum ON
dim_past_events bool ON
hour_height enum(int) ON
primary_color accent_color today_color text_color line_color bg_color month_divider_color month_label_color color hex ON
default_event_duration_minutes enum(int) ON
default_reminder_minutes enum(int, null=off) ON
language enum OFF
share_calendar_icon icon key OFF
cache_months enum(int) OFF
month_view_paged bool OFF

Settings not in this list are never synced by this mechanism:

  • Account-wide settings (private_event_visibility, group_visible_calendar_id, directory_hidden) — one value per account, always identical everywhere; they keep their existing dedicated endpoints/UI, not a sync toggle.
  • Platform-exclusive device prefs (e.g. iOS liquid_glass) — stay device-local.
  • Identity/security, calendar/account management, admin.

API

  • GET /api/settings/ returns every value plus sync_flags: a fully-resolved {key: bool} map covering exactly the keys above (stored overrides on top of DEFAULT_SYNC). Clients read this map verbatim — no client-side defaults.
  • PUT /api/settings/ accepts a partial sync_flags map (merged account-wide, unknown keys ignored, untouched flags preserved) and partial value fields (exclude_unset; text_color/line_color/bg_color/… treated as nullable-reset per NULLABLE_OVERRIDES).

Client rules

Each client keeps a local copy of every syncable value (UserDefaults / DataStore-SharedPreferences / localStorage) so that "not synced" works per device.

  1. On login / launch / foreground: GET /api/settings/ → values + sync_flags.
  2. Pull: for each syncable key, if sync_flags[key] is ON, adopt the server value into the local copy; if OFF, keep the local value.
  3. Push (debounced, read-modify-write): start from the current server snapshot, overwrite only keys whose flag is ON with the local value, PUT. Never push a key whose flag is OFF.
  4. Toggle a flag ON: set the flag true and push this device's current local value (it becomes the shared value). OFF: set false; keep the local value.
  5. Global "share everything": set all syncable flags true and push all local values. Global off: set all false.

The flag map itself is always account-wide and always fetched fresh; it is what a client consults to decide what to send/receive.