From 83814629501e2aec71950951ec1cf0651cabb01f Mon Sep 17 00:00:00 2001 From: Artem Ovchinnikov <53350796+Akilaydin@users.noreply.github.com> Date: Wed, 21 Oct 2026 21:49:24 +0300 Subject: [PATCH] fix(syncguard): preserve bypass during duplicate resume --- src/serviceFeatures/syncGuard/index.ts | 5 +++++ .../syncGuard/syncGuardFeature.unit.spec.ts | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/serviceFeatures/syncGuard/index.ts b/src/serviceFeatures/syncGuard/index.ts index f8a0ee0b..379de0d1 100644 --- a/src/serviceFeatures/syncGuard/index.ts +++ b/src/serviceFeatures/syncGuard/index.ts @@ -66,6 +66,11 @@ export function useSyncGuardFeature(host: LiveSyncCore, createOverlay: SyncGuard guard.disable(); return; } + // A duplicate resume in the same active catch-up must honour a user's + // explicit decision to continue editing rather than silently re-lock. + if (guard.wasBypassed && services.replicator.couchDbCatchUpActivityCount.value > 0) { + return; + } // Duplicate resume notifications or a visibility transition must not discard an // already active guard and expose a partly reflected Vault to the user. if (guard.presentation.phase === "transferring" || guard.presentation.phase === "applying" || diff --git a/src/serviceFeatures/syncGuard/syncGuardFeature.unit.spec.ts b/src/serviceFeatures/syncGuard/syncGuardFeature.unit.spec.ts index 3601009b..a5a82230 100644 --- a/src/serviceFeatures/syncGuard/syncGuardFeature.unit.spec.ts +++ b/src/serviceFeatures/syncGuard/syncGuardFeature.unit.spec.ts @@ -266,4 +266,17 @@ describe("useSyncGuardFeature", () => { expect(h.updates[h.updates.length - 1]?.phase).toBe("idle"); await h.onUnload[0](); }); + + it("preserves manual bypass across duplicate resume while the same CouchDB pull remains active", async () => { + const h = setup({ minimumFiles: 2 }); + await h.onResumed[0](); + h.couchDbCatchUpActivityCount.value = 1; + await h.parseHandlers[0]([receivedNote("first"), receivedNote("second")]); + expect(h.updates[h.updates.length - 1]?.phase).toBe("transferring"); + h.bypass(); + await h.onResumed[0](); + await h.parseHandlers[0]([receivedNote("third"), receivedNote("fourth")]); + expect(h.updates[h.updates.length - 1]?.phase).toBe("idle"); + await h.onUnload[0](); + }); });