fix(files): address autosave migration and review regressions
Seed omitted autoSaveEnabled from the hydrated client preference (including legacy localStorage) instead of resetting everyone to enabled. Restore SVG non-editable flags, treat clean draft saves as success, and throw again from disposed content-cache owners while keeping runtime-switch cache invalidation. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
co-authored by
Serhii Dziupin
parent
fc4db0c656
commit
be572c1692
@@ -51,10 +51,10 @@ describe('shouldAllowFileDraftSave', () => {
|
||||
expect(shouldAllowFileDraftSave(ready)).toBe(true);
|
||||
});
|
||||
|
||||
test('refuses incomplete load, binary, or clean draft', () => {
|
||||
test('refuses incomplete load or binary; clean draft is a successful no-op', () => {
|
||||
expect(shouldAllowFileDraftSave({ ...ready, fileLoading: true })).toBe(false);
|
||||
expect(shouldAllowFileDraftSave({ ...ready, loadedFilePath: null })).toBe(false);
|
||||
expect(shouldAllowFileDraftSave({ ...ready, isNonEditableBinary: true })).toBe(false);
|
||||
expect(shouldAllowFileDraftSave({ ...ready, isDirty: false })).toBe(false);
|
||||
expect(shouldAllowFileDraftSave({ ...ready, isDirty: false })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,12 +38,18 @@ export type FileEditorSaveDraftGate = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether saveDraft may write. Refuses empty drafts against stale content and any binary target.
|
||||
* Whether saveDraft may proceed.
|
||||
* - Clean drafts return true ("nothing to save" is success) so callers like the
|
||||
* unsaved-changes dialog and Ctrl+S do not treat a no-op as failure.
|
||||
* - Incomplete loads and binary targets return false (refused).
|
||||
*/
|
||||
export function shouldAllowFileDraftSave(gate: FileEditorSaveDraftGate): boolean {
|
||||
if (!gate.selectedFilePath || !gate.isDirty) {
|
||||
if (!gate.selectedFilePath) {
|
||||
return false;
|
||||
}
|
||||
if (!gate.isDirty) {
|
||||
return true;
|
||||
}
|
||||
if (gate.fileLoading || gate.loadedFilePath !== gate.selectedFilePath || gate.isNonEditableBinary) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -528,16 +528,43 @@ describe('updateDesktopSettings', () => {
|
||||
expect(saveCalls.some((changes) => changes.autoSaveEnabled === false)).toBe(true);
|
||||
});
|
||||
|
||||
test('resets omitted autoSaveEnabled to the default enabled state', async () => {
|
||||
test('seeds omitted autoSaveEnabled from the hydrated client preference', async () => {
|
||||
getWindow();
|
||||
invalidateSettingsCache();
|
||||
useUIStore.getState().setAutoSaveEnabled(false);
|
||||
registerSettingsApi(async () => ({}), async () => ({
|
||||
const saveCalls: Array<Partial<SettingsPayload>> = [];
|
||||
registerSettingsApi(async (changes) => {
|
||||
saveCalls.push(changes);
|
||||
return { ...changes } as SettingsPayload;
|
||||
}, async () => ({
|
||||
settings: { draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true },
|
||||
source: 'web',
|
||||
}));
|
||||
|
||||
await syncDesktopSettings();
|
||||
await delay(500);
|
||||
|
||||
expect(useUIStore.getState().autoSaveEnabled).toBe(false);
|
||||
expect(saveCalls.some((changes) => changes.autoSaveEnabled === false)).toBe(true);
|
||||
});
|
||||
|
||||
test('seeds default autoSaveEnabled when omitted and client still has the default', async () => {
|
||||
getWindow();
|
||||
invalidateSettingsCache();
|
||||
useUIStore.getState().setAutoSaveEnabled(true);
|
||||
const saveCalls: Array<Partial<SettingsPayload>> = [];
|
||||
registerSettingsApi(async (changes) => {
|
||||
saveCalls.push(changes);
|
||||
return { ...changes } as SettingsPayload;
|
||||
}, async () => ({
|
||||
settings: { draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true },
|
||||
source: 'web',
|
||||
}));
|
||||
|
||||
await syncDesktopSettings();
|
||||
await delay(500);
|
||||
|
||||
expect(useUIStore.getState().autoSaveEnabled).toBe(true);
|
||||
expect(saveCalls.some((changes) => changes.autoSaveEnabled === true)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1728,6 +1728,12 @@ export const syncDesktopSettings = async (): Promise<void> => {
|
||||
if (!isSettingsRuntimeContextCurrent(context)) return;
|
||||
const shouldPersistCraftGoalMigration = settings.draftStartersCraftGoalAdded !== true
|
||||
|| settings.draftStartersScheduleTaskAdded !== true;
|
||||
// `autoSaveEnabled` is new to the settings backend. Until the server has a
|
||||
// value, materialize would invent the client default (true) and overwrite a
|
||||
// deliberate legacy "off" preference migrated from
|
||||
// `openchamber:files:auto-save-enabled`. Prefer the hydrated store value and
|
||||
// seed the backend once so later omitted→default authority is correct.
|
||||
const shouldSeedAutoSaveEnabled = typeof settings.autoSaveEnabled !== 'boolean';
|
||||
const authoritativeSettings = materializeAuthoritativeUiSettings(settings);
|
||||
try {
|
||||
persistToLocalStorage(settings);
|
||||
@@ -1736,6 +1742,9 @@ export const syncDesktopSettings = async (): Promise<void> => {
|
||||
}
|
||||
await waitForHydration();
|
||||
if (!isSettingsRuntimeContextCurrent(context)) return;
|
||||
if (shouldSeedAutoSaveEnabled) {
|
||||
authoritativeSettings.autoSaveEnabled = useUIStore.getState().autoSaveEnabled;
|
||||
}
|
||||
if (settings.draftStarters === undefined) {
|
||||
useUIStore.setState({ globalDraftStarters: null });
|
||||
}
|
||||
@@ -1744,12 +1753,19 @@ export const syncDesktopSettings = async (): Promise<void> => {
|
||||
} catch (error) {
|
||||
console.warn('applyDesktopUiPreferences failed:', error);
|
||||
}
|
||||
const migrationPatch: Partial<DesktopSettings> = {};
|
||||
if (shouldPersistCraftGoalMigration) {
|
||||
await updateDesktopSettings({
|
||||
...(authoritativeSettings.draftStarters ? { draftStarters: authoritativeSettings.draftStarters } : {}),
|
||||
draftStartersCraftGoalAdded: true,
|
||||
draftStartersScheduleTaskAdded: true,
|
||||
});
|
||||
if (authoritativeSettings.draftStarters) {
|
||||
migrationPatch.draftStarters = authoritativeSettings.draftStarters;
|
||||
}
|
||||
migrationPatch.draftStartersCraftGoalAdded = true;
|
||||
migrationPatch.draftStartersScheduleTaskAdded = true;
|
||||
}
|
||||
if (shouldSeedAutoSaveEnabled) {
|
||||
migrationPatch.autoSaveEnabled = authoritativeSettings.autoSaveEnabled;
|
||||
}
|
||||
if (Object.keys(migrationPatch).length > 0) {
|
||||
await updateDesktopSettings(migrationPatch);
|
||||
if (!isSettingsRuntimeContextCurrent(context)) return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user