From b42df389fb0bf6e6f8c3b51a461bcb6a29b39706 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 6 Aug 2026 08:34:41 +0000 Subject: [PATCH] Distinguish absent and unreadable remote settings during setup Use typed Commonlib outcomes to guide Fetch and Rebuild without treating a new remote as a failed read. Preserve automatic synchronisation choices when scheduled initialisation is cancelled. --- docs/quick_setup.md | 4 +- docs/setup_object_storage.md | 2 +- ...age-setup-missing-remote-configuration.png | Bin 20728 -> 19085 bytes ...ick-setup-missing-remote-configuration.png | Bin 20728 -> 19085 bytes package-lock.json | 8 +- package.json | 2 +- .../messages/LiveSyncProvisionalMessages.ts | 12 + .../ModuleResolveMismatchedTweaks.ts | 33 +- ...ModuleResolveMismatchedTweaks.unit.spec.ts | 30 ++ .../dialogs/FetchEverything.svelte | 9 +- .../dialogs/RebuildEverything.svelte | 4 +- src/serviceFeatures/redFlag.simpleFetch.ts | 19 +- src/serviceFeatures/redFlag.ts | 191 ++++++++---- src/serviceFeatures/redFlag.unit.spec.ts | 286 +++++++++++++++--- test/e2e-obsidian/runner/setupUri.ts | 16 +- .../scripts/couchdb-manual-setup-workflow.ts | 4 +- .../object-storage-setup-uri-workflow.ts | 4 +- .../scripts/setup-uri-workflow.ts | 14 +- updates.md | 6 + 19 files changed, 489 insertions(+), 155 deletions(-) diff --git a/docs/quick_setup.md b/docs/quick_setup.md index a77b3834..afac78e8 100644 --- a/docs/quick_setup.md +++ b/docs/quick_setup.md @@ -39,7 +39,7 @@ Use this path only when the remote database is new, or when this device is inten ![Final server overwrite warning](../images/quick-setup/guide-quick-setup-first-rebuild-confirmation.png) -8. A newly provisioned database may show `Fetch Remote Configuration Failed` because it does not contain a saved preferred configuration yet. If this is a genuinely new setup, select `Skip and proceed`. Otherwise, stop and investigate before continuing. +8. A newly provisioned database may show `No Synchronisation Settings Found`. Select `Use this device's settings` for the intended new database. If you expected existing settings, cancel and check the selected remote. Cancellation restarts with Vault and database reflection paused; keep LiveSync disabled until the remote has been checked. If `Could Not Read Synchronisation Settings` appears, retry, or use this device's settings to continue the overwrite. The overwrite still requires a working connection. ![Expected missing remote configuration choice for a new database](../images/quick-setup/guide-quick-setup-missing-remote-configuration.png) @@ -134,7 +134,7 @@ Use this path when CouchDB is ready but a Setup URI is unavailable. It configure 8. Select `Create or connect to database and continue`. Onboarding requires this connection test to succeed. 9. Review `Setup Complete: Preparing to Initialise Server`, then select `Restart and Initialise Server`. 10. Read the final overwrite warning. Select `I Understand, Overwrite Server` only when this device is intentionally the source of truth and a current backup exists. -11. A newly created database can show `Fetch Remote Configuration Failed` because it does not yet contain a saved preferred configuration. Select `Skip and proceed` only for this known new database. +11. A newly created database can show `No Synchronisation Settings Found`. Select `Use this device's settings` for this known new database. If you expected existing settings, cancel and check the selected remote. Cancellation restarts with Vault and database reflection paused; keep LiveSync disabled until the remote has been checked. If `Could Not Read Synchronisation Settings` appears, retry, or use this device's settings to continue the overwrite. The overwrite still requires a working connection. 12. Acknowledge `All optional features are disabled`, then keep Obsidian open until the initialisation progress has cleared. Create and synchronise an ordinary test note. Once it has reached CouchDB, follow [Create a Setup URI for another device](#create-a-setup-uri-for-another-device), then [Add another device](#add-another-device). This keeps the second device aligned with the remote profile and encryption settings which the first device actually applied. diff --git a/docs/setup_object_storage.md b/docs/setup_object_storage.md index f29f60f1..0244a766 100644 --- a/docs/setup_object_storage.md +++ b/docs/setup_object_storage.md @@ -50,7 +50,7 @@ Use a new bucket prefix, or a prefix whose contents you deliberately intend to r ![Final Object Storage overwrite confirmation](../images/object-storage-setup/guide-object-storage-setup-first-rebuild-confirmation.png) -6. A new prefix may show `Fetch Remote Configuration Failed` because it has no saved configuration. Select `Skip and proceed` only for a genuinely new prefix. Otherwise, stop and check the endpoint, credentials, bucket, and prefix. +6. A new prefix may show `No Synchronisation Settings Found`. Select `Use this device's settings` for a genuinely new prefix. If you expected existing settings, cancel and check the endpoint, credentials, bucket, and prefix. Cancellation restarts with Vault and database reflection paused; keep LiveSync disabled until the remote has been checked. If `Could Not Read Synchronisation Settings` appears, retry, or use this device's settings to continue the overwrite. The overwrite still requires a working connection. ![Expected missing remote configuration for a new Object Storage prefix](../images/object-storage-setup/guide-object-storage-setup-missing-remote-configuration.png) diff --git a/images/object-storage-setup/guide-object-storage-setup-missing-remote-configuration.png b/images/object-storage-setup/guide-object-storage-setup-missing-remote-configuration.png index 79f205fc77c685bb7f15896a45081e60afd4ea4b..6d816e23dec6744ab1aab0caf1b6bcb20c3dcb2d 100644 GIT binary patch literal 19085 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 literal 20728 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 literal 20728 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 X5?8$Rikuzz<|lJg8 { - const replicator = await this.services.replicator.getNewReplicator(trialSetting); - if (!replicator) { - this._log("The remote type is not supported for fetching preferred tweak values.", LOG_LEVEL_NOTICE); - return false; - } - if (await replicator.tryConnectRemote(trialSetting)) { - const preferred = await replicator.getRemotePreferredTweakValues(trialSetting); - if (preferred) { - return preferred; + async _fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise { + try { + const replicator = await this.services.replicator.getNewReplicator(trialSetting); + if (!replicator) { + this._log("The remote type does not support preferred tweak values.", LOG_LEVEL_NOTICE); + return { status: RemotePreferredTweakStatuses.UNSUPPORTED }; } - this._log("Failed to get the preferred tweak values from the remote server.", LOG_LEVEL_NOTICE); - return false; + return await replicator.getRemotePreferredTweakValues(trialSetting); + } catch (ex) { + this._log("Failed to get the preferred tweak values from the remote.", LOG_LEVEL_NOTICE); + return { + status: RemotePreferredTweakStatuses.UNAVAILABLE, + error: ex, + }; } - this._log("Failed to connect to the remote server.", LOG_LEVEL_NOTICE); - return false; } async _checkAndAskUseRemoteConfiguration( @@ -281,8 +282,8 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule { return { result: false, requireFetch: false }; } const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting); - if (preferred) { - return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred); + if (preferred.status === RemotePreferredTweakStatuses.AVAILABLE) { + return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred.values); } return { result: false, requireFetch: false }; } diff --git a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.unit.spec.ts b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.unit.spec.ts index 21f773e0..0f394d8b 100644 --- a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.unit.spec.ts +++ b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.unit.spec.ts @@ -54,6 +54,36 @@ function createModule(settingsOverride: Partial = {}) { } describe("ModuleResolvingMismatchedTweaks", () => { + it("returns an unconfigured remote result without a separate connection preflight", async () => { + const { module, core } = createModule(); + const tryConnectRemote = vi.fn(async () => true); + const getRemotePreferredTweakValues = vi.fn(async () => ({ + status: "not-configured" as const, + reason: "milestone-missing" as const, + })); + core._services.replicator = { + getNewReplicator: vi.fn(async () => ({ tryConnectRemote, getRemotePreferredTweakValues })), + }; + + await expect(module._fetchRemotePreferredTweakValues(core.settings)).resolves.toEqual({ + status: "not-configured", + reason: "milestone-missing", + }); + expect(getRemotePreferredTweakValues).toHaveBeenCalledOnce(); + expect(tryConnectRemote).not.toHaveBeenCalled(); + }); + + it("returns unsupported when no replicator implements the remote type", async () => { + const { module, core } = createModule(); + core._services.replicator = { + getNewReplicator: vi.fn(async () => undefined), + }; + + await expect(module._fetchRemotePreferredTweakValues(core.settings)).resolves.toEqual({ + status: "unsupported", + }); + }); + it("should enable and auto-accept compatible mismatches when the preference is undefined", async () => { const { module, core, askSelectStringDialogue, applyPartial } = createModule({ autoAcceptCompatibleTweak: undefined, diff --git a/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte b/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte index c62a1792..36ea945c 100644 --- a/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte +++ b/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte @@ -141,10 +141,11 @@ - + + + {translateMessage("Skips checking and applying synchronisation settings from the remote.")} + + diff --git a/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte b/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte index 68221d1e..093df286 100644 --- a/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte +++ b/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte @@ -129,7 +129,9 @@ {#if !isP2P} - + + {msg("Skips checking and applying synchronisation settings from the remote.")} + {/if} diff --git a/src/serviceFeatures/redFlag.simpleFetch.ts b/src/serviceFeatures/redFlag.simpleFetch.ts index 214cad2e..c629cdfe 100644 --- a/src/serviceFeatures/redFlag.simpleFetch.ts +++ b/src/serviceFeatures/redFlag.simpleFetch.ts @@ -10,7 +10,11 @@ import { synchroniseAllFilesBetweenDBandStorage, type FullScanOptions, } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner"; -import { adjustSettingToRemoteIfNeeded, processVaultInitialisation } from "./redFlag"; +import { + adjustSettingToRemoteIfNeeded, + cancelScheduledInitialisation, + processVaultInitialisation, +} from "./redFlag"; export const SIMPLE_FETCH_STAGE1_REMOTE_WINS = "Overwrite all with remote files"; export const SIMPLE_FETCH_STAGE1_NEWER_WINS = "Compare time and take newer"; @@ -194,9 +198,7 @@ export async function askAndPerformFastSetupOnScheduledFetchAll( if (result === "cancelled") { log("Fetch cancelled by user.", LOG_LEVEL_NOTICE); clearRememberedSimpleFetchMode(host); - await cleanupFlag(); - host.services.appLifecycle.performRestart(); - return false; + return await cancelScheduledInitialisation(host, cleanupFlag); } if (result === "aborted") { log("Fetch exited by user.", LOG_LEVEL_NOTICE); @@ -208,9 +210,14 @@ export async function askAndPerformFastSetupOnScheduledFetchAll( return undefined; // Let the detailed setup flow handle it. } + const settings = host.services.setting.currentSettings(); + if (!(await adjustSettingToRemoteIfNeeded(host, log, { preventFetchingConfig: false }, settings))) { + log("Fetch initialisation cancelled by user.", LOG_LEVEL_NOTICE); + clearRememberedSimpleFetchMode(host); + return await cancelScheduledInitialisation(host, cleanupFlag); + } + return await processVaultInitialisation(host, log, async () => { - const settings = host.services.setting.currentSettings(); - await adjustSettingToRemoteIfNeeded(host, log, { preventFetchingConfig: false }, settings); // 1. Perform fast DB fetch (download remote DB content to local DB) await host.serviceModules.rebuilder.$fetchLocalDBFast(false); diff --git a/src/serviceFeatures/redFlag.ts b/src/serviceFeatures/redFlag.ts index 67399057..ae42b509 100644 --- a/src/serviceFeatures/redFlag.ts +++ b/src/serviceFeatures/redFlag.ts @@ -10,7 +10,10 @@ import RebuildEverything from "@/modules/features/SetupWizard/dialogs/RebuildEve import { extractObject } from "octagonal-wheels/object"; import { REMOTE_MINIO, REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const"; import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/settings"; -import { TweakValuesShouldMatchedTemplate } from "@vrtmrz/livesync-commonlib/compat/common/models/tweak.definition"; +import { + RemotePreferredTweakStatuses, + TweakValuesShouldMatchedTemplate, +} from "@vrtmrz/livesync-commonlib/compat/common/models/tweak.definition"; import type { FetchEverythingResult, RebuildEverythingResult, @@ -19,6 +22,7 @@ import { askAndPerformFastSetupOnScheduledFetchAll } from "./redFlag.simpleFetch import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString"; import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/remote-configurations"; import { isP2PMainRemote } from "@/common/remoteConfiguration"; +import { $msg } from "@/common/translation"; /** * Flag file handler interface, similar to target filter pattern. @@ -29,6 +33,8 @@ interface FlagFileHandler { handle: () => Promise; } +type InitialisationOperation = "fetch" | "rebuild"; + export async function isFlagFileExist(host: NecessaryServices, path: string) { const redFlagExist = await host.serviceModules.storageAccess.isExists( host.serviceModules.storageAccess.normalisePath(path) @@ -158,9 +164,7 @@ export function createFetchAllFlagHandler( await host.services.UI.dialogManager.openWithExplicitCancel(FetchEverything); if (method === "cancelled") { log("Fetch everything cancelled by user.", LOG_LEVEL_NOTICE); - await cleanupFlag(); - host.services.appLifecycle.performRestart(); - return false; + return await cancelScheduledInitialisation(host, cleanupFlag); } const { vault, extra } = method; const settings = await Promise.resolve(host.services.setting.currentSettings()); @@ -185,9 +189,11 @@ export function createFetchAllFlagHandler( }, } as const; + if (!(await adjustSettingToRemoteIfNeeded(host, log, extra, settings))) { + log("Fetch initialisation cancelled by user.", LOG_LEVEL_NOTICE); + return await cancelScheduledInitialisation(host, cleanupFlag); + } return await processVaultInitialisation(host, log, async () => { - const settings = host.services.setting.currentSettings(); - await adjustSettingToRemoteIfNeeded(host, log, extra, settings); const vaultStateToAction = mapVaultStateToAction[vault]; const { makeLocalChunkBeforeSync, makeLocalFilesBeforeSync } = vaultStateToAction; log( @@ -217,93 +223,153 @@ export function createFetchAllFlagHandler( /** * Adjust setting to remote configuration. * @param config current configuration to retrieve remote preferred config - * @returns updated configuration if applied, otherwise null. + * @param operation operation which will consume the selected configuration + * @returns whether initialisation may continue. */ export async function adjustSettingToRemote( host: NecessaryServices<"tweakValue" | "UI" | "setting", never>, log: LogFunction, - config: ObsidianLiveSyncSettings -) { - // Fetch remote configuration unless prevented. - const SKIP_FETCH = "Skip and proceed"; - const RETRY_FETCH = "Retry (recommended)"; - let canProceed = false; - do { - const remoteTweaks = await host.services.tweakValue.fetchRemotePreferred(config); - if (!remoteTweaks) { + config: ObsidianLiveSyncSettings, + operation: InitialisationOperation = "fetch" +): Promise { + while (true) { + const remoteResult = await host.services.tweakValue.fetchRemotePreferred(config); + if (remoteResult.status === RemotePreferredTweakStatuses.NOT_CONFIGURED) { + const useDeviceSettings = $msg("Use this device's settings"); + const cancelInitialisation = $msg("Cancel"); + log(`Remote synchronisation settings are not configured (${remoteResult.reason}).`, LOG_LEVEL_INFO); const choice = await host.services.UI.confirm.askSelectStringDialogue( - "Could not fetch configuration from remote. If you are new to the Self-hosted LiveSync, this might be expected. If not, you should check your network or server settings.", - [SKIP_FETCH, RETRY_FETCH] as const, + $msg( + "The selected remote has no saved synchronisation settings. This is normal for a new remote. Use this device's settings, or cancel if you expected existing settings." + ), + [useDeviceSettings, cancelInitialisation] as const, { - defaultAction: RETRY_FETCH, + defaultAction: useDeviceSettings, timeout: 0, - title: "Fetch Remote Configuration Failed", + title: $msg("No Synchronisation Settings Found"), } ); - if (choice === SKIP_FETCH) { - canProceed = true; - } - } else { - const necessary = extractObject(TweakValuesShouldMatchedTemplate, remoteTweaks); - // Check if any necessary tweak value is different from current config. - const differentItems = Object.entries(necessary).filter(([key, value]) => { - return config[key as keyof ObsidianLiveSyncSettings] !== value; - }); - if (differentItems.length === 0) { - log("Remote configuration matches local configuration. No changes applied.", LOG_LEVEL_NOTICE); - } else { - await host.services.UI.confirm.askSelectStringDialogue( - "Your settings differed slightly from the server's. The plug-in has supplemented the incompatible parts with the server settings!", - ["OK"] as const, + return choice === useDeviceSettings; + } + if (remoteResult.status === RemotePreferredTweakStatuses.UNAVAILABLE) { + const retryRemoteSettings = $msg("Retry"); + const useDeviceSettings = $msg("Use this device's settings"); + const cancelInitialisation = $msg("Cancel"); + log("Could not read synchronisation settings from the remote.", LOG_LEVEL_NOTICE); + log(remoteResult.error, LOG_LEVEL_VERBOSE); + if (operation === "rebuild") { + const choice = await host.services.UI.confirm.askSelectStringDialogue( + $msg( + "Could not read the remote's synchronisation settings. Retry, or continue the overwrite with this device's settings. A working connection is still required." + ), + [retryRemoteSettings, useDeviceSettings, cancelInitialisation] as const, { - defaultAction: "OK", + defaultAction: retryRemoteSettings, timeout: 0, + title: $msg("Could Not Read Synchronisation Settings"), } ); + if (choice === retryRemoteSettings) continue; + return choice === useDeviceSettings; } - - config = { - ...config, - ...(Object.fromEntries(differentItems) as Partial), - } satisfies ObsidianLiveSyncSettings; - await host.services.setting.applyExternalSettings(config, true); - log("Remote configuration applied.", LOG_LEVEL_NOTICE); - canProceed = true; - const updatedConfig = host.services.setting.currentSettings(); - return updatedConfig; + const choice = await host.services.UI.confirm.askSelectStringDialogue( + $msg( + "Could not read the remote's synchronisation settings. Check the connection and credentials, then retry." + ), + [retryRemoteSettings, cancelInitialisation] as const, + { + defaultAction: retryRemoteSettings, + timeout: 0, + title: $msg("Could Not Read Synchronisation Settings"), + } + ); + if (choice === retryRemoteSettings) continue; + return false; } - } while (!canProceed); + if (remoteResult.status === RemotePreferredTweakStatuses.UNSUPPORTED) { + log("Remote synchronisation settings are not supported by this remote type.", LOG_LEVEL_INFO); + return true; + } + + const remoteTweaks = remoteResult.values; + const necessary = extractObject(TweakValuesShouldMatchedTemplate, remoteTweaks); + // Check if any necessary tweak value is different from current config. + const differentItems = Object.entries(necessary).filter(([key, value]) => { + return config[key as keyof ObsidianLiveSyncSettings] !== value; + }); + if (differentItems.length === 0) { + log("Remote configuration matches local configuration. No changes applied.", LOG_LEVEL_NOTICE); + } else { + await host.services.UI.confirm.askSelectStringDialogue( + "Your settings differed slightly from the server's. The plug-in has supplemented the incompatible parts with the server settings!", + ["OK"] as const, + { + defaultAction: "OK", + timeout: 0, + } + ); + } + + config = { + ...config, + ...(Object.fromEntries(differentItems) as Partial), + } satisfies ObsidianLiveSyncSettings; + await host.services.setting.applyExternalSettings(config, true); + log("Remote configuration applied.", LOG_LEVEL_NOTICE); + return true; + } } /** * Adjust setting to remote if needed. * @param extra result of dialogues that may contain preventFetchingConfig flag (e.g, from FetchEverything or RebuildEverything) * @param config current configuration to retrieve remote preferred config + * @param operation operation which will consume the selected configuration */ export async function adjustSettingToRemoteIfNeeded( host: NecessaryServices<"tweakValue" | "UI" | "setting", never>, log: LogFunction, - extra: { preventFetchingConfig: boolean }, - config: ObsidianLiveSyncSettings -) { - if (extra && extra.preventFetchingConfig) { - return; + extra: { preventFetchingConfig: boolean } | null, + config: ObsidianLiveSyncSettings, + operation: InitialisationOperation = "fetch" +): Promise { + if (extra?.preventFetchingConfig) { + return true; } // P2P has no centralised remote configuration; skip to avoid a spurious // "Failed to connect to the remote server" error dialog. if (config.remoteType === REMOTE_P2P) { log("Remote configuration fetch skipped (P2P mode).", LOG_LEVEL_INFO); - return; + return true; } - // Remote configuration fetched and applied. - if (await adjustSettingToRemote(host, log, config)) { - config = host.services.setting.currentSettings(); - } else { + const canProceed = await adjustSettingToRemote(host, log, config, operation); + if (!canProceed) { log("Remote configuration not applied.", LOG_LEVEL_NOTICE); } - // log(JSON.stringify(config), LOG_LEVEL_VERBOSE); + return canProceed; +} + +/** + * Cancel a scheduled Fetch or Rebuild without changing the selected automatic + * synchronisation mode. The persisted Scram switches keep both reflection + * directions paused until the existing start-up dialogue resumes them. + */ +export async function cancelScheduledInitialisation( + host: NecessaryServices<"setting" | "appLifecycle", never>, + cleanupFlag: () => Promise +): Promise { + await host.services.setting.applyPartial( + { + suspendFileWatching: true, + suspendParseReplicationResult: true, + }, + true + ); + await cleanupFlag(); + host.services.appLifecycle.performRestart(); + return false; } /** @@ -393,12 +459,13 @@ export function createRebuildFlagHandler( >(RebuildEverything, { isP2P: isP2PMainRemote(settings) }); if (method === "cancelled") { log("Rebuild everything cancelled by user.", LOG_LEVEL_NOTICE); - await cleanupFlag(); - host.services.appLifecycle.performRestart(); - return false; + return await cancelScheduledInitialisation(host, cleanupFlag); } const { extra } = method; - await adjustSettingToRemoteIfNeeded(host, log, extra, settings); + if (!(await adjustSettingToRemoteIfNeeded(host, log, extra, settings, "rebuild"))) { + log("Rebuild initialisation cancelled by user.", LOG_LEVEL_NOTICE); + return await cancelScheduledInitialisation(host, cleanupFlag); + } return await processVaultInitialisation(host, log, async () => { await host.serviceModules.rebuilder.$rebuildEverything(); await cleanupFlag(); diff --git a/src/serviceFeatures/redFlag.unit.spec.ts b/src/serviceFeatures/redFlag.unit.spec.ts index de8d998c..85803c56 100644 --- a/src/serviceFeatures/redFlag.unit.spec.ts +++ b/src/serviceFeatures/redFlag.unit.spec.ts @@ -68,6 +68,8 @@ const createLoggerMock = (): LogFunction => { return vi.fn(); }; +const availableRemoteTweaks = (values: Record) => ({ status: "available", values }) as const; + const createStorageAccessMock = () => { const files: Set = new Set(); return { @@ -149,7 +151,9 @@ const createRebuilderMock = () => { const createTweakValueMock = () => { return { - fetchRemotePreferred: vi.fn(() => Promise.resolve(null)), + fetchRemotePreferred: vi.fn(() => + Promise.resolve({ status: "unavailable", error: new Error("Remote unavailable") }) + ), }; }; @@ -459,9 +463,9 @@ describe("Red Flag Feature", () => { .mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS) .mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ - batchSave: false, - } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ batchSave: false }) + ); const handler = createFetchAllFlagHandler(host as any, log); const result = await handler.handle(); @@ -487,9 +491,9 @@ describe("Red Flag Feature", () => { backup: "backup_skipped", extra: { preventFetchingConfig: false }, }); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ - batchSave: false, - } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ batchSave: false }) + ); const handler = createFetchAllFlagHandler(host as any, log); const result = await handler.handle(); @@ -506,9 +510,9 @@ describe("Red Flag Feature", () => { host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false); const handler = createFetchAllFlagHandler(host as any, log); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ - batchSave: false, - } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ batchSave: false }) + ); const result = await handler.handle(); expect(result).toBe(false); @@ -525,9 +529,9 @@ describe("Red Flag Feature", () => { .mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS) .mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ - batchSave: false, - } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ batchSave: false }) + ); const handler = createFetchAllFlagHandler(host as any, log); const result = await handler.handle(); @@ -763,7 +767,7 @@ describe("Red Flag Feature", () => { host.mocks.ui.confirm.confirmWithMessage .mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS) .mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_CLEANUP); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue({ batchSave: false } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue(availableRemoteTweaks({ batchSave: false })); host.mocks.rebuilder.$fetchLocalDBFast.mockRejectedValueOnce(new Error("offline")); await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag); @@ -781,7 +785,7 @@ describe("Red Flag Feature", () => { host.mocks.ui.confirm.confirmWithMessage .mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS) .mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue({ batchSave: false } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue(availableRemoteTweaks({ batchSave: false })); await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag); @@ -831,6 +835,65 @@ describe("Red Flag Feature", () => { expect(host.mocks.rebuilder.$fetchLocalDBFast).not.toHaveBeenCalled(); }); + it("should preserve automatic synchronisation choices and enter Scram when quick Fetch is cancelled", async () => { + const host = createHostMock(); + const cleanupFlag = vi.fn().mockResolvedValue(undefined); + Object.assign(host.mocks.setting.settings, { + liveSync: true, + periodicReplication: true, + syncOnSave: true, + syncOnEditorSave: true, + syncOnStart: true, + syncOnFileOpen: true, + syncAfterMerge: true, + suspendParseReplicationResult: false, + }); + host.mocks.ui.confirm.confirmWithMessage + .mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS) + .mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + status: "not-configured", + reason: "milestone-missing", + }); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Cancel"); + + const result = await askAndPerformFastSetupOnScheduledFetchAll( + host as any, + createLoggerMock(), + cleanupFlag + ); + + expect(result).toBe(false); + expect(host.mocks.rebuilder.$fetchLocalDBFast).not.toHaveBeenCalled(); + expect(host.mocks.setting.suspendAllSync).not.toHaveBeenCalled(); + expect(host.mocks.setting.applyPartial).toHaveBeenCalledWith( + { + suspendFileWatching: true, + suspendParseReplicationResult: true, + }, + true + ); + expect(host.mocks.setting.currentSettings()).toMatchObject({ + liveSync: true, + periodicReplication: true, + syncOnSave: true, + syncOnEditorSave: true, + syncOnStart: true, + syncOnFileOpen: true, + syncAfterMerge: true, + suspendFileWatching: true, + suspendParseReplicationResult: true, + }); + expect(cleanupFlag).toHaveBeenCalledOnce(); + expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalledOnce(); + expect(host.mocks.setting.applyPartial.mock.invocationCallOrder[0]).toBeLessThan( + cleanupFlag.mock.invocationCallOrder[0] + ); + expect(cleanupFlag.mock.invocationCallOrder[0]).toBeLessThan( + host.mocks.appLifecycle.performRestart.mock.invocationCallOrder[0] + ); + }); + it("should reboot and return false when sync has failures and user chooses rerun", async () => { const host = createHostMock(); const log = createLoggerMock(); @@ -839,7 +902,9 @@ describe("Red Flag Feature", () => { host.mocks.ui.confirm.confirmWithMessage .mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS) .mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ batchSave: false } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ batchSave: false }) + ); (synchroniseAllFilesBetweenDBandStorage as any).mockResolvedValueOnce(false); host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Reboot to re-run the process"); @@ -859,7 +924,9 @@ describe("Red Flag Feature", () => { host.mocks.ui.confirm.confirmWithMessage .mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS) .mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ batchSave: false } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ batchSave: false }) + ); (synchroniseAllFilesBetweenDBandStorage as any).mockResolvedValueOnce(false); host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce( "Finalise the process and resume normal operation" @@ -1017,9 +1084,9 @@ describe("Red Flag Feature", () => { const host = createHostMock(); const config = { batchSave: true } as any; - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ - batchSave: false, - } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ batchSave: false }) + ); await adjustSettingToRemoteIfNeeded( host as any, @@ -1047,7 +1114,9 @@ describe("Red Flag Feature", () => { const differentConfig = { [key]: differentValue, }; - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(differentConfig as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks(differentConfig) + ); host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("OK"); await adjustSettingToRemote(host as any, createLoggerMock(), config); @@ -1074,7 +1143,9 @@ describe("Red Flag Feature", () => { const differentConfig = { [key]: differentValue, }; - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(differentConfig as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks(differentConfig) + ); host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("OK"); await adjustSettingToRemote(host as any, createLoggerMock(), config); @@ -1084,32 +1155,71 @@ describe("Red Flag Feature", () => { } ); - it("should show dialog when remote fetch fails", async () => { + it("should explain that missing remote settings are normal for a new database without offering retry", async () => { const host = createHostMock(); const log = createLoggerMock(); const config = { batchSave: true } as any; - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(null); - host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Skip and proceed"); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + status: "not-configured", + reason: "milestone-missing", + }); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Use this device's settings"); - await adjustSettingToRemote(host as any, log, config); + await expect(adjustSettingToRemote(host as any, log, config)).resolves.toBe(true); - expect(host.mocks.ui.confirm.askSelectStringDialogue).toHaveBeenCalled(); + expect(host.mocks.ui.confirm.askSelectStringDialogue).toHaveBeenCalledWith( + "The selected remote has no saved synchronisation settings. This is normal for a new remote. Use this device's settings, or cancel if you expected existing settings.", + ["Use this device's settings", "Cancel"], + { + defaultAction: "Use this device's settings", + timeout: 0, + title: "No Synchronisation Settings Found", + } + ); + expect(host.mocks.tweakValue.fetchRemotePreferred).toHaveBeenCalledOnce(); + expect(host.mocks.setting.applyExternalSettings).not.toHaveBeenCalled(); }); - it("should retry when user selects retry option", async () => { + it("should retry only when remote settings are unavailable", async () => { const host = createHostMock(); const log = createLoggerMock(); const config = { batchSave: true } as any; + const failure = new Error("network failed"); host.mocks.tweakValue.fetchRemotePreferred - .mockResolvedValueOnce(null) - .mockResolvedValueOnce({ batchSave: false } as any); - host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Retry (recommended)"); + .mockResolvedValueOnce({ status: "unavailable", error: failure }) + .mockResolvedValueOnce({ status: "available", values: { batchSave: false } } as any); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Retry"); - await adjustSettingToRemote(host as any, log, config); + await expect(adjustSettingToRemote(host as any, log, config)).resolves.toBe(true); expect(host.mocks.tweakValue.fetchRemotePreferred).toHaveBeenCalledTimes(2); + expect(host.mocks.ui.confirm.askSelectStringDialogue).toHaveBeenCalledWith( + "Could not read the remote's synchronisation settings. Check the connection and credentials, then retry.", + ["Retry", "Cancel"], + { + defaultAction: "Retry", + timeout: 0, + title: "Could Not Read Synchronisation Settings", + } + ); + }); + + it("should cancel initialisation instead of proceeding after an unavailable remote", async () => { + const host = createHostMock(); + const config = { batchSave: true } as any; + + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + status: "unavailable", + error: new Error("network failed"), + }); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Cancel"); + + await expect(adjustSettingToRemote(host as any, createLoggerMock(), config)).resolves.toBe(false); + + expect(host.mocks.tweakValue.fetchRemotePreferred).toHaveBeenCalledOnce(); + expect(host.mocks.setting.applyExternalSettings).not.toHaveBeenCalled(); }); it("should log when no changes needed", async () => { @@ -1117,9 +1227,9 @@ describe("Red Flag Feature", () => { const log = createLoggerMock(); const config = { batchSave: false } as any; - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ - batchSave: false, - } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ batchSave: false }) + ); await adjustSettingToRemote(host as any, log, config); @@ -1131,8 +1241,11 @@ describe("Red Flag Feature", () => { const log = createLoggerMock(); const config = { batchSave: true } as any; - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(null); - host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Skip and proceed"); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + status: "not-configured", + reason: "preferred-values-missing", + }); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Use this device's settings"); await adjustSettingToRemoteIfNeeded(host as any, log, null as any, config); @@ -1460,6 +1573,93 @@ describe("Red Flag Feature", () => { }); describe("flagHandlerToEventHandler integration", () => { + it("should stop a detailed Fetch when remote-setting initialisation is cancelled", async () => { + const host = createHostMock(); + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_DETAILED); + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ + vault: "independent", + extra: { preventFetchingConfig: false }, + }); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + status: "not-configured", + reason: "milestone-missing", + }); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Cancel"); + + const result = await createFetchAllFlagHandler(host as any, createLoggerMock()).handle(); + + expect(result).toBe(false); + expect(host.mocks.rebuilder.$fetchLocal).not.toHaveBeenCalled(); + expect(host.mocks.setting.suspendAllSync).not.toHaveBeenCalled(); + expect(host.mocks.setting.applyPartial).toHaveBeenCalledWith( + { + suspendFileWatching: true, + suspendParseReplicationResult: true, + }, + true + ); + expect(host.mocks.storageAccess.files.has(FlagFilesOriginal.FETCH_ALL)).toBe(false); + expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalledOnce(); + }); + + it("should stop Rebuild before deleting local data when remote-setting initialisation is cancelled", async () => { + const host = createHostMock(); + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ + extra: { preventFetchingConfig: false }, + }); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + status: "not-configured", + reason: "milestone-missing", + }); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Cancel"); + + const result = await createRebuildFlagHandler(host as any, createLoggerMock()).handle(); + + expect(result).toBe(false); + expect(host.mocks.rebuilder.$rebuildEverything).not.toHaveBeenCalled(); + expect(host.mocks.setting.suspendAllSync).not.toHaveBeenCalled(); + expect(host.mocks.setting.applyPartial).toHaveBeenCalledWith( + { + suspendFileWatching: true, + suspendParseReplicationResult: true, + }, + true + ); + expect(host.mocks.storageAccess.files.has(FlagFilesOriginal.REBUILD_ALL)).toBe(false); + expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalledOnce(); + }); + + it("should let Rebuild use this device's settings when remote settings are unavailable", async () => { + const host = createHostMock(); + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ + extra: { preventFetchingConfig: false }, + }); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + status: "unavailable", + error: new Error("network failed"), + }); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Use this device's settings"); + + const result = await createRebuildFlagHandler(host as any, createLoggerMock()).handle(); + + expect(result).toBe(true); + expect(host.mocks.ui.confirm.askSelectStringDialogue).toHaveBeenCalledWith( + "Could not read the remote's synchronisation settings. Retry, or continue the overwrite with this device's settings. A working connection is still required.", + ["Retry", "Use this device's settings", "Cancel"], + { + defaultAction: "Retry", + timeout: 0, + title: "Could Not Read Synchronisation Settings", + } + ); + expect(host.mocks.rebuilder.$rebuildEverything).toHaveBeenCalledOnce(); + expect(host.mocks.storageAccess.files.has(FlagFilesOriginal.REBUILD_ALL)).toBe(false); + expect(host.mocks.appLifecycle.performRestart).not.toHaveBeenCalled(); + }); + it("should return true when flag does not exist", async () => { const host = createHostMock(); const log = createLoggerMock(); @@ -1476,7 +1676,7 @@ describe("Red Flag Feature", () => { const log = createLoggerMock(); host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({}); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(availableRemoteTweaks({})); host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_DETAILED); host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce("cancelled"); @@ -1554,9 +1754,7 @@ describe("Red Flag Feature", () => { it("should handle fetchAll flag with flagHandlerToEventHandler identical", async () => { const host = createHostMock(); const log = createLoggerMock(); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue({ - customChunkSize: 1, - } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue(availableRemoteTweaks({ customChunkSize: 1 })); host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_DETAILED); @@ -1574,9 +1772,9 @@ describe("Red Flag Feature", () => { it("should handle rebuildAll flag with flagHandlerToEventHandler", async () => { const host = createHostMock(); const log = createLoggerMock(); - host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ - customChunkSize: 1, - } as any); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce( + availableRemoteTweaks({ customChunkSize: 1 }) + ); host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ extra: {} }); diff --git a/test/e2e-obsidian/runner/setupUri.ts b/test/e2e-obsidian/runner/setupUri.ts index 0c1697be..10764fbc 100644 --- a/test/e2e-obsidian/runner/setupUri.ts +++ b/test/e2e-obsidian/runner/setupUri.ts @@ -254,17 +254,23 @@ export async function confirmRebuild(port: number, captures: SetupCaptureNames): return screenshot; } -export async function skipMissingRemoteConfiguration(port: number, captures: SetupCaptureNames): Promise { - const title = "Fetch Remote Configuration Failed"; +export async function continueWithoutRemoteSettings(port: number, captures: SetupCaptureNames): Promise { + const title = "No Synchronisation Settings Found"; const screenshot = await captureGuideDialogue( port, `guide-${captures.guide}-missing-remote-configuration.png`, title ); await withObsidianPage(port, async (page) => { - await modalByTitle(page, title) - .getByRole("button", { name: "Skip and proceed" }) - .click({ timeout: uiTimeoutMs }); + const modal = modalByTitle(page, title); + await modal + .getByText("This is normal for a new remote.", { exact: false }) + .waitFor({ state: "visible", timeout: uiTimeoutMs }); + await modal.getByRole("button", { name: "Cancel", exact: true }).waitFor({ + state: "visible", + timeout: uiTimeoutMs, + }); + await modal.getByRole("button", { name: "Use this device's settings" }).click({ timeout: uiTimeoutMs }); }); return screenshot; } diff --git a/test/e2e-obsidian/scripts/couchdb-manual-setup-workflow.ts b/test/e2e-obsidian/scripts/couchdb-manual-setup-workflow.ts index 2a662a6f..ef44ae87 100644 --- a/test/e2e-obsidian/scripts/couchdb-manual-setup-workflow.ts +++ b/test/e2e-obsidian/scripts/couchdb-manual-setup-workflow.ts @@ -25,7 +25,7 @@ import { modalByTitle, resumeCompatibilityReviewIfShown, selectRadioOption, - skipMissingRemoteConfiguration, + continueWithoutRemoteSettings, type SetupArtifact, } from "../runner/setupUri.ts"; import { captureObsidianPage, withObsidianPage } from "../runner/ui.ts"; @@ -278,7 +278,7 @@ async function main(): Promise { screenshots.push(...(await enterManualCouchDBSettings(session.remoteDebuggingPort, couchDb, dbName))); screenshots.push(await captureAndStartInitialisation(session.remoteDebuggingPort, "new", captures)); screenshots.push(await confirmRebuild(session.remoteDebuggingPort, captures)); - screenshots.push(await skipMissingRemoteConfiguration(session.remoteDebuggingPort, captures)); + screenshots.push(await continueWithoutRemoteSettings(session.remoteDebuggingPort, captures)); screenshots.push(await acknowledgeDisabledOptionalFeatures(session.remoteDebuggingPort, captures)); const state = await finishInitialisation(session.remoteDebuggingPort, context.cliBinary, session.cliEnv); await resumeCompatibilityReviewIfShown(session.remoteDebuggingPort); diff --git a/test/e2e-obsidian/scripts/object-storage-setup-uri-workflow.ts b/test/e2e-obsidian/scripts/object-storage-setup-uri-workflow.ts index 19276252..40d1b63c 100644 --- a/test/e2e-obsidian/scripts/object-storage-setup-uri-workflow.ts +++ b/test/e2e-obsidian/scripts/object-storage-setup-uri-workflow.ts @@ -29,7 +29,7 @@ import { finishInitialisation, generateSetupURIFromDevice, resumeCompatibilityReviewIfShown, - skipMissingRemoteConfiguration, + continueWithoutRemoteSettings, type SetupArtifact, type SetupCaptureNames, } from "../runner/setupUri.ts"; @@ -226,7 +226,7 @@ async function main(): Promise { screenshots.push(await enterSetupURI(portA, "new", bootstrapArtifact, captures)); screenshots.push(await captureAndStartInitialisation(portA, "new", captures)); screenshots.push(await confirmRebuild(portA, captures)); - screenshots.push(await skipMissingRemoteConfiguration(portA, captures)); + screenshots.push(await continueWithoutRemoteSettings(portA, captures)); screenshots.push(await acknowledgeDisabledOptionalFeatures(portA, captures)); const firstState = await finishInitialisation(portA, context.cliBinary, sessionA.cliEnv); await resumeCompatibilityReviewIfShown(portA); diff --git a/test/e2e-obsidian/scripts/setup-uri-workflow.ts b/test/e2e-obsidian/scripts/setup-uri-workflow.ts index a53ca5ac..3a42db44 100644 --- a/test/e2e-obsidian/scripts/setup-uri-workflow.ts +++ b/test/e2e-obsidian/scripts/setup-uri-workflow.ts @@ -300,8 +300,8 @@ async function confirmRebuild(port: number): Promise { return screenshot; } -async function skipMissingRemoteConfiguration(port: number): Promise { - const title = "Fetch Remote Configuration Failed"; +async function continueWithoutRemoteSettings(port: number): Promise { + const title = "No Synchronisation Settings Found"; const screenshot = await captureObsidianDialogue( port, "setup-uri-first-missing-remote-configuration.png", @@ -309,16 +309,20 @@ async function skipMissingRemoteConfiguration(port: number): Promise { const modal = modalByTitle(page, title); await modal.waitFor({ state: "visible", timeout: uiTimeoutMs }); await modal - .getByText("If you are new to the Self-hosted LiveSync, this might be expected.", { + .getByText("This is normal for a new remote.", { exact: false, }) .waitFor({ state: "visible", timeout: uiTimeoutMs }); + await modal.getByRole("button", { name: "Cancel", exact: true }).waitFor({ + state: "visible", + timeout: uiTimeoutMs, + }); } ); await captureGuideDialogue(port, "guide-quick-setup-missing-remote-configuration.png", title); await withObsidianPage(port, async (page) => { await modalByTitle(page, title) - .getByRole("button", { name: "Skip and proceed" }) + .getByRole("button", { name: "Use this device's settings" }) .click({ timeout: uiTimeoutMs }); }); return screenshot; @@ -736,7 +740,7 @@ async function main(): Promise { await enterSetupURI(session.remoteDebuggingPort, "new", artifact); screenshots.push(await captureAndStartInitialisation(session.remoteDebuggingPort, "new")); screenshots.push(await confirmRebuild(session.remoteDebuggingPort)); - screenshots.push(await skipMissingRemoteConfiguration(session.remoteDebuggingPort)); + screenshots.push(await continueWithoutRemoteSettings(session.remoteDebuggingPort)); screenshots.push(await acknowledgeDisabledOptionalFeatures(session.remoteDebuggingPort)); const firstCompletion = await finishInitialisation( session.remoteDebuggingPort, diff --git a/updates.md b/updates.md index 2d52c0d4..d7af0250 100644 --- a/updates.md +++ b/updates.md @@ -12,6 +12,12 @@ Earlier releases remain available in the 0.25 release history and the legacy rel ## Unreleased +### Setup and compatibility + +#### Fixed + +- Initial setup now distinguishes an empty remote with no saved synchronisation settings from a failed remote read. New remotes can use this device's settings without an unnecessary retry; Fetch pauses on unreadable settings, while Rebuild can explicitly continue with this device's settings. Cancelling preserves the selected automatic synchronisation mode and restarts with Vault and database reflection paused (#1064). Thank you to @mateus2k2 for the follow-up report! + ## 1.0.5 5th August, 2026