From 80b67a78e4cc61be4fcd1a6ac24ee8bcd096e3bb Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Tue, 23 Jun 2026 01:33:44 +0200 Subject: [PATCH] Preserve sync state after re-pairing Signed-off-by: Zoorpha --- src/main.ts | 7 +++++-- src/state.ts | 8 ++++++++ tests/state.test.ts | 22 +++++++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/main.ts b/src/main.ts index e0b4fcd..64cf63e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,7 +7,7 @@ import { RustShareVaultSyncSettingTab } from './ui/settings-tab'; import { StatusBar } from './ui/status-bar'; import { RustShareAPI } from './api'; import { SyncEngine } from './sync'; -import { SyncState, createEmptySyncState, migrateSyncState, pruneTombstones } from './state'; +import { SyncState, createEmptySyncState, migrateSyncState, pruneTombstones, retargetSyncStateDevice } from './state'; import { SyncQueue, SyncOperation } from './sync-queue'; import { syncLog } from './sync-log'; import { detectCloudSyncFolder, shouldIgnorePath, loadDesktopAuthToken, isValidUuid } from './utils'; @@ -294,10 +294,13 @@ export default class RustShareVaultSyncPlugin extends Plugin { } // Initialize sync state - if (!this.syncState || this.syncState.vault_id !== vaultId || this.syncState.device_id !== deviceId) { + if (this.syncState?.vault_id === vaultId && this.syncState.device_id !== deviceId) { + this.syncState = retargetSyncStateDevice(this.syncState, deviceId, this.settings.deviceName); + } else if (!this.syncState || this.syncState.vault_id !== vaultId) { this.syncState = createEmptySyncState(vaultId, deviceId, this.settings.deviceName); } + await this.saveSettings(); this.statusBar.updateStatus('connected'); } catch (e) { this.statusBar.updateStatus('error', `Connect failed: ${e}`); diff --git a/src/state.ts b/src/state.ts index 9a52b3d..fb85e05 100644 --- a/src/state.ts +++ b/src/state.ts @@ -34,6 +34,14 @@ export function createEmptySyncState(vaultId: string, deviceId: string, deviceNa }; } +export function retargetSyncStateDevice(state: SyncState, deviceId: string, deviceName: string): SyncState { + return { + ...state, + device_id: deviceId, + device_name: deviceName, + }; +} + export function migrateSyncState(state: Record): SyncState { const version = typeof state.version === 'number' ? state.version : 0; if (version === 1) { diff --git a/tests/state.test.ts b/tests/state.test.ts index fa9a29e..f3504df 100644 --- a/tests/state.test.ts +++ b/tests/state.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { createEmptySyncState } from '../src/state'; +import { createEmptySyncState, retargetSyncStateDevice } from '../src/state'; describe('createEmptySyncState', () => { it('returns state with correct defaults and empty files/tombstones', () => { @@ -13,3 +13,23 @@ describe('createEmptySyncState', () => { expect(state.tombstones).toEqual({}); }); }); + +describe('retargetSyncStateDevice', () => { + it('keeps vault file history when a re-paired device gets a new id', () => { + const state = createEmptySyncState('vault-123', 'old-device', 'Old Laptop'); + state.last_server_rev = 7; + state.files['note.md'] = { + sha256: 'abc', + server_rev: 6, + last_synced_at: '2026-06-23T00:00:00.000Z', + }; + + const retargeted = retargetSyncStateDevice(state, 'new-device', 'New Laptop'); + + expect(retargeted.device_id).toBe('new-device'); + expect(retargeted.device_name).toBe('New Laptop'); + expect(retargeted.vault_id).toBe('vault-123'); + expect(retargeted.last_server_rev).toBe(7); + expect(retargeted.files['note.md']).toEqual(state.files['note.md']); + }); +});