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>
3.1 KiB
Settings sync contract (Web / iOS / Android)
For a human-facing description of each theme colour parameter (and the
.themeimport/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 plussync_flags: a fully-resolved{key: bool}map covering exactly the keys above (stored overrides on top ofDEFAULT_SYNC). Clients read this map verbatim — no client-side defaults.PUT /api/settings/accepts a partialsync_flagsmap (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 perNULLABLE_OVERRIDES).
Client rules
Each client keeps a local copy of every syncable value (UserDefaults / DataStore-SharedPreferences / localStorage) so that "not synced" works per device.
- On login / launch / foreground:
GET /api/settings/→ values +sync_flags. - 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. - 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. - 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.
- 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.