Skip to content

feat: per-setting sync control (disable cloud sync for individual settings) #42

Description

@kipavy

Summary

Allow disabling cloud sync for individual settings (VSCode-style), so a device can keep certain preferences local — e.g. a per-device theme while everything else still syncs.

Per-object sync control already exists for entity types (Hosts, Identities, SSH Keys, Folders, Port Forwarding, Snippets) via src/stores/syncPrefsStore.ts — per-type toggles in Settings → Sync plus per-object exclusions with a cloud-off badge. Settings are not part of that system. They sync through a separate path (src/services/user-data/registry.ts): 4 handler domains — Themes, UI Preferences, Shortcuts, App Settings — bundled into settings.json and merged last-write-wins per domain. Today it's all-or-nothing; there's no way to hold back a single setting.

Approach — VSCode-style hybrid

VSCode Settings Sync has two layers, and we mirror them:

  1. Per-domain toggles — the everyday control (like VSCode's category on/off).
  2. Ignored-settings list — an opt-out list of individual setting keys that never sync, even within a synced domain (like settingsSync.ignoredSettings).

Data model

Extend syncPrefsStore (or a sibling slice):

  • syncSettingDomains: Record<string, boolean> — per-domain toggles for the 4 handlers (default true).
  • ignoredSettingKeys: string[] — dotted paths of individual settings to never sync (e.g. themes.active, uiPreferences.uiScale, appSettings.locale).

Settings registry

Each UserDataHandler exposes its addressable leaf keys as { id (dotted path), label }. Flat scalars (UI Preferences, most App Settings — including the toggles map) map 1:1. Collections (custom-themes array, shortcuts array) surface as one or two coarse keys. This registry powers the ignore-list UI.

Filtering — a real "stays on this device" guarantee

Unlike the existing entity toggles (which only gate the push trigger — see note below), settings filtering runs in TS and is authoritative:

  • buildUserDataBundle — drop any domain whose toggle is off; within included domains, delete ignored leaf keys (generic dotted-path delete on the plain exported JSON) before bundling.
  • applyUserDataBundle / mergeUserDataBundle — skip off domains; skip ignored keys when applying remote, so the local value is preserved. Per-domain LWW stays as-is for everything else.
  • Trigger-gate (nice-to-have): settings setters already call scheduleSync (uiStore, appSettingsTimestampStore, …); optionally skip when the changed key is ignored/off. The real guarantee is the bundle filter, not this.

UI (Settings → Sync)

  • A new "Settings" toggle group (4 domains) reusing the existing sync-preferences toggle-list pattern in SyncSection.tsx.
  • An "Ignored settings" manager: a searchable list of all registered settings with per-key sync / don't-sync checkboxes, mirroring VSCode's configure panel.

Phasing

  • Phase 1 — domain toggles + domain-level filtering (small, self-contained).
  • Phase 2 — settings registry + ignored-keys filtering + ignore-list UI.

Out of scope

The existing entity per-object toggle only gates the push trigger (scheduleSync); it does not withhold the object from the uploaded blob — backup_export (Rust, src-tauri/src/commands/sync.rs) reads every .json in the config dir wholesale, so an "excluded" host still reaches the server on the next sync that fires for any reason. That's a separate pre-existing quirk tracked in #43; the settings work here is done properly (filtered in TS) and is not affected by it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions