fix(syncguard): notify local preference changes without reapplying sync settings

This commit is contained in:
Artem Ovchinnikov committed 2026-10-29 13:09:26 +03:00
1 parent 3eabc2b305
commit ed6408cc5a
5 files changed
+29 -6

No files matched your search

+3
View File
@@ -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;
@@ -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<T extends ObsidianServiceContext> 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<void> {
@@ -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);
});
});
+10 -1
View File
@@ -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);
});
@@ -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]();
});