feat(sync): per-profile sync mode toggle (Phase 1.f.desktop.3) - #194
Conversation
Adds the user-facing "off-switch" the enqueue hooks #192 shipped need to be controllable. Per-profile sync mode persisted in profile_setting['sync.mode']; the existing sync::hooks gate now short-circuits when mode = Local even with a JWT configured. ## Backend (~250 LOC) - crate::sync::mode — SyncMode enum (Local, Hybrid), read/write helpers around profile_setting, 5 unit tests covering fresh-profile default, round-trip both directions, unknown-storage-value fallback, and const as_str/from_storage symmetry. - sync::hooks::enqueue_op gate: if no JWT skip; if SyncMode::Local skip; otherwise lamport::next + queue::enqueue. Fresh profile defaults to Hybrid so the post-sign-in flow Just Works. - New Tauri commands sync_get_mode and sync_set_mode (canonical string round-trip, rejects unknown modes with 400-style error). - sync_get_queue_state extended with a mode field so the Settings card renders both queue stats AND the active mode in one round-trip. ## Frontend (~120 LOC) - src/lib/tauri/serverAuth.ts wrapper: SyncMode type + syncGetMode + syncSetMode. - ServerAccountCard gains a radio under the JWT section, gated on signedIn && mode loaded so we don't flash an empty group during hydration. Promise.all([serverGetStatus, syncGetMode]) on initial load batches the two reads. - syncGetMode failure during initial hydration is caught and the radio simply stays hidden rather than blowing up the whole card (race against profile-switch). ## i18n - settings.serverAccount.{modeLabel, modes.hybrid.label, modes.hybrid.description, modes.local.label, modes.local.description} propagated to all 17 locales per the CLAUDE.md convention. ## Why no Server-connected mode? RFC-001 listed a third "thin-client" mode where reads come from HTTP instead of local SQLite. Deferred — waveflow-web already covers the thin-client use case, and routing desktop reads through HTTP forfeits the value the local audio engine + file scanner provide. The SyncMode enum is intentionally open-shaped so a future ServerOnly variant lands without touching the persistence + gate logic. ## Test plan - cargo test -p waveflow --lib sync::mode (5/5 green) - cargo clippy -p waveflow --all-targets -- -D warnings - cargo fmt -p waveflow --check - bun run typecheck - bun run lint - Manual smoke (requires #190 + waveflow-web #18 deployed): - Sign in to a profile → Settings → Compte serveur shows radio defaulted to "Hybrid" - Create a playlist → sync_get_queue_state shows pending_count: 1 - Flip to Local → pending_count stays 1, no new ops enqueue - Update playlist name → pending_count stays 1 (queue gated by mode) - Flip back to Hybrid → update playlist again → pending_count: 2 Signed-off-by: InstaZDLL <github.105mh@8shield.net>
📝 WalkthroughWalkthroughPR ajoute un système de mode de synchronisation (Hybrid / Local) par profil : enum et persistence SQLite, nouvelles commandes Tauri pour lire/écrire le mode, court-circuit de la queue backend en mode Local, wrapper TypeScript IPC, sélecteur radio dans Settings et traductions pour plusieurs locales. ChangesSync Mode Configuration & UI
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/crates/app/src/sync/mode.rs`:
- Around line 110-113: La requête UPSERT sur la table profile_setting n’inclut
pas value_type dans le SET de ON CONFLICT; modifie la clause ON CONFLICT de la
chaîne SQL (celle qui commence par "INSERT INTO profile_setting ... ON
CONFLICT(key) DO UPDATE") pour mettre à jour aussi value_type en plus de value
et updated_at (par exemple ajouter value_type = excluded.value_type dans la
liste des colonnes mises à jour) afin de garantir que le type enregistré reste
cohérent avec la valeur.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f64e67dd-7d0e-443b-9648-d301f1201663
📒 Files selected for processing (24)
src-tauri/crates/app/src/commands/sync.rssrc-tauri/crates/app/src/lib.rssrc-tauri/crates/app/src/sync/hooks.rssrc-tauri/crates/app/src/sync/mod.rssrc-tauri/crates/app/src/sync/mode.rssrc/components/views/settings/ServerAccountCard.tsxsrc/i18n/locales/ar.jsonsrc/i18n/locales/de.jsonsrc/i18n/locales/en.jsonsrc/i18n/locales/es.jsonsrc/i18n/locales/fr.jsonsrc/i18n/locales/hi.jsonsrc/i18n/locales/id.jsonsrc/i18n/locales/it.jsonsrc/i18n/locales/ja.jsonsrc/i18n/locales/ko.jsonsrc/i18n/locales/nl.jsonsrc/i18n/locales/pt-BR.jsonsrc/i18n/locales/pt.jsonsrc/i18n/locales/ru.jsonsrc/i18n/locales/tr.jsonsrc/i18n/locales/zh-CN.jsonsrc/i18n/locales/zh-TW.jsonsrc/lib/tauri/serverAuth.ts
@coderabbitai on PR #194 flagged that the mode::write UPSERT didn't refresh value_type on conflict. For sync.mode specifically value_type never drifts (mode::write is the only writer and always inserts 'string'), but the fix is four characters and closes the class of bugs where a hypothetical future writer puts a wrong type in the row that this UPSERT would then silently preserve. Cheap defence in depth — same shape every future UPSERT against profile_setting should adopt. Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Adds the user-facing "off-switch" the enqueue hooks #192 shipped need to be controllable. Per-profile sync mode persisted in `profile_setting['sync.mode']`; the existing `sync::hooks` gate now short-circuits when mode = `Local` even with a JWT configured.
Tighter scope than the original RFC-001 spec — see "Why no Server-connected mode?" below for the rationale.
Backend (~250 LOC)
Frontend (~120 LOC)
i18n
Why no `Server-connected` mode?
RFC-001 listed a third "thin-client" mode where reads come from HTTP instead of local SQLite. Deferred — `waveflow-web` already covers the thin-client use case, and routing desktop reads through HTTP forfeits the value the local audio engine + file scanner provide. The `SyncMode` enum is intentionally open-shaped (`from_storage` falls back to `Hybrid` for any unknown value) so a future `ServerOnly` variant lands without touching the persistence + gate logic.
Test plan
Summary by CodeRabbit
Nouvelles Fonctionnalités
Corrections