diff --git a/src/common/events.ts b/src/common/events.ts index 220c420e..a03fb1da 100644 --- a/src/common/events.ts +++ b/src/common/events.ts @@ -6,6 +6,8 @@ export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded"; export const EVENT_FILE_SAVED = "file-saved"; /** A received note could not be reflected from the local database into the Vault. */ export const EVENT_VAULT_REFLECTION_FAILED = "vault-reflection-failed"; +/** Device-local SyncGuard preferences changed without changing replication settings. */ +export const EVENT_SYNC_GUARD_PREFERENCES_CHANGED = "sync-guard-preferences-changed"; export const EVENT_LEAF_ACTIVE_CHANGED = "leaf-active-changed"; export const EVENT_REQUEST_OPEN_SETTINGS = "request-open-settings"; @@ -31,6 +33,7 @@ declare global { [EVENT_PLUGIN_LOADED]: undefined; [EVENT_PLUGIN_UNLOADED]: undefined; [EVENT_VAULT_REFLECTION_FAILED]: undefined; + [EVENT_SYNC_GUARD_PREFERENCES_CHANGED]: undefined; [EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined; [EVENT_REQUEST_OPEN_SETTINGS]: undefined; [EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined; diff --git a/src/modules/services/ObsidianSettingService.ts b/src/modules/services/ObsidianSettingService.ts index 0eabbc69..7ef5b2a1 100644 --- a/src/modules/services/ObsidianSettingService.ts +++ b/src/modules/services/ObsidianSettingService.ts @@ -8,6 +8,7 @@ import { type SettingServiceDependencies, } from "@vrtmrz/livesync-commonlib/compat/services/base/SettingService"; import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext"; +import { EVENT_SYNC_GUARD_PREFERENCES_CHANGED } from "@/common/events.ts"; export type SyncGuardSettings = { syncGuardEnabled: boolean; @@ -92,7 +93,9 @@ export class ObsidianSettingService extends Se if (this._syncGuardSettings[key] === value) return; await this.config.setObject(key, value); this._syncGuardSettings = { ...this._syncGuardSettings, [key]: value }; - this.context.events.emitEvent(EVENT_SETTING_SAVED, this.currentSettings()); + // These preferences change only the workspace overlay. Re-applying all + // replication settings would unnecessarily interrupt an active sync. + this.context.events.emitEvent(EVENT_SYNC_GUARD_PREFERENCES_CHANGED); } protected override async saveData(data: ObsidianLiveSyncSettings): Promise { diff --git a/src/modules/services/ObsidianSettingService.unit.spec.ts b/src/modules/services/ObsidianSettingService.unit.spec.ts index db7bfa2d..74cd4977 100644 --- a/src/modules/services/ObsidianSettingService.unit.spec.ts +++ b/src/modules/services/ObsidianSettingService.unit.spec.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { createServiceContext } from "@vrtmrz/livesync-commonlib/context"; import { DEFAULT_SETTINGS, type ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { EVENT_SETTING_SAVED } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents"; +import { EVENT_SYNC_GUARD_PREFERENCES_CHANGED } from "@/common/events.ts"; import type { IAPIService } from "@vrtmrz/livesync-commonlib/compat/services/base/IService"; import { MemoryValueStore } from "@vrtmrz/value-store"; import type { ObsidianServiceContext } from "@/modules/services/ObsidianServiceContext"; @@ -142,7 +143,9 @@ describe("SyncGuard device-local settings", () => { await second.loadSettings(); const initialSaveCount = saveData.mock.calls.length; const changed = vi.fn(); - first.context.events.onEvent(EVENT_SETTING_SAVED, changed); + const unrelatedReplicationSettingsSaved = vi.fn(); + first.context.events.onEvent(EVENT_SYNC_GUARD_PREFERENCES_CHANGED, changed); + first.context.events.onEvent(EVENT_SETTING_SAVED, unrelatedReplicationSettingsSaved); await setSyncGuardSetting(first, "syncGuardEnabled", true); await setSyncGuardSetting(first, "syncGuardMinimumFiles", 8); @@ -153,6 +156,7 @@ describe("SyncGuard device-local settings", () => { await expect(localValues.getObject("first-vault--syncGuardEnabled")).resolves.toBe(true); await expect(localValues.getObject("first-vault--syncGuardMinimumFiles")).resolves.toBe(8); expect(changed).toHaveBeenCalledTimes(2); + expect(unrelatedReplicationSettingsSaved).not.toHaveBeenCalled(); expect(saveData).toHaveBeenCalledTimes(initialSaveCount); }); }); diff --git a/src/serviceFeatures/syncGuard/index.ts b/src/serviceFeatures/syncGuard/index.ts index 72f3544d..d9522fe5 100644 --- a/src/serviceFeatures/syncGuard/index.ts +++ b/src/serviceFeatures/syncGuard/index.ts @@ -4,7 +4,11 @@ import { isAnyNote } from "@vrtmrz/livesync-commonlib/compat/common/utils"; import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions"; import type { LiveSyncCore } from "@/main.ts"; import { getSyncGuardSettings } from "@/modules/services/ObsidianSettingService.ts"; -import { EVENT_SETTING_SAVED, EVENT_VAULT_REFLECTION_FAILED } from "@/common/events"; +import { + EVENT_SETTING_SAVED, + EVENT_SYNC_GUARD_PREFERENCES_CHANGED, + EVENT_VAULT_REFLECTION_FAILED, +} from "@/common/events"; import type { SyncGuardOverlay } from "./SyncGuardOverlay"; import { SyncGuardState } from "./SyncGuardState"; @@ -136,6 +140,10 @@ export function useSyncGuardFeature(host: LiveSyncCore, createOverlay: SyncGuard const removeSettingSavedHandler = services.context.events.onEvent(EVENT_SETTING_SAVED, () => { onSettingsChanged(); }); + const removeSyncGuardPreferencesHandler = services.context.events.onEvent( + EVENT_SYNC_GUARD_PREFERENCES_CHANGED, + onSettingsChanged + ); const replication = services.replicator.replicationStatics; const finite = services.replicator.finiteReplicationActivityCount; @@ -181,6 +189,7 @@ export function useSyncGuardFeature(host: LiveSyncCore, createOverlay: SyncGuard for (const source of pendingSources) source.offChanged(onPendingChanged); overlay.dispose(); removeSettingSavedHandler(); + removeSyncGuardPreferencesHandler(); removeReflectionFailureHandler(); return Promise.resolve(true); }); diff --git a/src/serviceFeatures/syncGuard/syncGuardFeature.unit.spec.ts b/src/serviceFeatures/syncGuard/syncGuardFeature.unit.spec.ts index abc6d1db..3c0f8725 100644 --- a/src/serviceFeatures/syncGuard/syncGuardFeature.unit.spec.ts +++ b/src/serviceFeatures/syncGuard/syncGuardFeature.unit.spec.ts @@ -2,7 +2,11 @@ import { reactiveSource } from "octagonal-wheels/dataobject/reactive"; import { REMOTE_COUCHDB, type EntryDoc } from "@vrtmrz/livesync-commonlib/compat/common/types"; import type { CouchDBReplicationPhase, ReplicationStatics } from "@vrtmrz/livesync-commonlib/replication"; import { createServiceContext } from "@vrtmrz/livesync-commonlib/context"; -import { EVENT_SETTING_SAVED, EVENT_VAULT_REFLECTION_FAILED } from "@/common/events"; +import { + EVENT_SETTING_SAVED, + EVENT_SYNC_GUARD_PREFERENCES_CHANGED, + EVENT_VAULT_REFLECTION_FAILED, +} from "@/common/events"; import { describe, afterEach, expect, it, vi } from "vitest"; import { useSyncGuardFeature } from "./index"; import type { SyncGuardPresentation } from "./SyncGuardState"; @@ -375,10 +379,10 @@ describe("useSyncGuardFeature", () => { await h.parseHandlers[0]([receivedNote("one"), receivedNote("two")]); expect(h.updates[h.updates.length - 1]?.phase).toBe("observing"); h.settings.syncGuardMinimumFiles = 2; - h.events.emitEvent(EVENT_SETTING_SAVED, h.settings as never); + h.events.emitEvent(EVENT_SYNC_GUARD_PREFERENCES_CHANGED); expect(h.updates[h.updates.length - 1]?.phase).toBe("transferring"); h.settings.syncGuardEnabled = false; - h.events.emitEvent(EVENT_SETTING_SAVED, h.settings as never); + h.events.emitEvent(EVENT_SYNC_GUARD_PREFERENCES_CHANGED); expect(h.updates[h.updates.length - 1]?.phase).toBe("idle"); await h.onUnload[0](); });