mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-09-07 15:07:17 +00:00
SyncDeviceData's syncPresets/syncRecents unconditionally overwrote the datastore with whatever the speaker's live :8090 API returned at that instant, with no check against what's already stored. If the speaker's own local cache was stale or incomplete at that moment (e.g. right after a burst of preset writes, or shortly after a reboot before the speaker resyncs with Marge), Sync would silently persist that bad snapshot over good data. A reporter's fresh #614 repro showed the account's /full response dropping from 6 to 5 presets right after a Sync click, consistent with this mechanism. SyncDeviceData now diffs a fresh live fetch against what's stored before writing anything; if applying would shrink either list, it returns the diff (via the new SyncResourceDiff/SyncResult types) without writing unless the caller passes confirmed=true. HandleInitialSync surfaces this as a 409 with the diff JSON; every call (confirmed or not) re-fetches live from the speaker, so a confirmed retry re-checks reality rather than replaying a stale snapshot. Sources sync is left unconditional, as before -- lower risk in practice and out of scope for this fix. fetchLivePresets/fetchLiveRecents are extracted pure-fetch helpers; syncPresets/syncRecents keep their unconditional-apply behavior (used directly by existing tests) since the button-driven path now goes through the diff/confirm guard instead. Adds TestSyncDeviceData_DestructiveSyncRequiresConfirmation covering both the refusal and the confirmed-retry path. Frontend wiring (script.js's startSync + real per-resource result rendering, replacing the current hardcoded "OK" text) is a follow-up commit on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>