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.
This commit is contained in:
vorotamoroz
2026-08-06 08:34:41 +00:00
parent 6a46966957
commit b42df389fb
19 changed files with 489 additions and 155 deletions
@@ -10,6 +10,8 @@ import {
type RemoteDBSettings,
IncompatibleChangesInSpecificPattern,
CompatibleButLossyChanges,
type RemotePreferredTweakResult,
RemotePreferredTweakStatuses,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import { escapeMarkdownValue } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import { AbstractModule } from "@/modules/AbstractModule.ts";
@@ -256,22 +258,21 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
return "IGNORE";
}
async _fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
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<RemotePreferredTweakResult> {
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 };
}
@@ -54,6 +54,36 @@ function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
}
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,
@@ -141,10 +141,11 @@
</Instruction>
<Instruction>
<ExtraItems title={translateMessage("Advanced")}>
<Check
title={translateMessage("Prevent fetching configuration from server")}
bind:value={preventFetchingConfig}
/>
<Check title={translateMessage("Use this device's settings")} bind:value={preventFetchingConfig}>
<InfoNote>
{translateMessage("Skips checking and applying synchronisation settings from the remote.")}
</InfoNote>
</Check>
</ExtraItems>
</Instruction>
<UserDecisions>
@@ -129,7 +129,9 @@
{#if !isP2P}
<Instruction>
<ExtraItems title={msg("Advanced")}>
<Check title={msg("Prevent fetching configuration from server")} bind:value={preventFetchingConfig} />
<Check title={msg("Use this device's settings")} bind:value={preventFetchingConfig}>
<InfoNote>{msg("Skips checking and applying synchronisation settings from the remote.")}</InfoNote>
</Check>
</ExtraItems>
</Instruction>
{/if}