fix(ui): bound settings mutation history
Skip mutation history when no settings operation is in flight and merge updates that share the same operation visibility window. This keeps stale-response reconciliation unchanged while preventing history from growing with repeated debounced updates. Add a repeated-update regression covering an older pending load and latest-value reconciliation. Tests: bun test src/lib/persistence.test.ts
This commit is contained in:
@@ -641,6 +641,43 @@ describe('updateDesktopSettings', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('preserves only the latest settings values across repeated pending updates', async () => {
|
||||
const loadedSettings = deferred<{ settings: SettingsPayload; source: 'web' | 'vscode' }>();
|
||||
registerSettingsApi(async (changes) => changes as SettingsPayload, () => loadedSettings.promise);
|
||||
invalidateSettingsCache();
|
||||
const syncedSettings: SettingsPayload[] = [];
|
||||
const handleSettingsSynced = (event: Event) => {
|
||||
syncedSettings.push((event as CustomEvent<SettingsPayload>).detail);
|
||||
};
|
||||
getWindow().addEventListener('openchamber:settings-synced', handleSettingsSynced);
|
||||
|
||||
try {
|
||||
const sync = syncDesktopSettings();
|
||||
const updates = Array.from({ length: 100 }, (_, index) => updateDesktopSettings({
|
||||
activeProjectId: `project-${index}`,
|
||||
showReasoningTraces: index % 2 === 0,
|
||||
}));
|
||||
|
||||
loadedSettings.resolve({
|
||||
settings: {
|
||||
activeProjectId: 'stale-project',
|
||||
showReasoningTraces: true,
|
||||
draftStartersCraftGoalAdded: true,
|
||||
draftStartersScheduleTaskAdded: true,
|
||||
},
|
||||
source: 'web',
|
||||
});
|
||||
await sync;
|
||||
|
||||
expect(syncedSettings.at(-1)?.activeProjectId).toBe('project-99');
|
||||
expect(syncedSettings.at(-1)?.showReasoningTraces).toBe(false);
|
||||
|
||||
await Promise.all(updates);
|
||||
} finally {
|
||||
getWindow().removeEventListener('openchamber:settings-synced', handleSettingsSynced);
|
||||
}
|
||||
});
|
||||
|
||||
test('applies model selector settings from server settings', async () => {
|
||||
getWindow();
|
||||
const settings = {
|
||||
|
||||
@@ -1679,7 +1679,19 @@ class SettingsMutationTracker {
|
||||
|
||||
record(changes: Partial<DesktopSettings>): number {
|
||||
this.revision += 1;
|
||||
this.mutations.push({ revision: this.revision, changes });
|
||||
if (this.operations.size > 0) {
|
||||
const latest = this.mutations.at(-1);
|
||||
// A new segment is only needed when an operation started after the last one.
|
||||
const crossedOperationBoundary = latest
|
||||
? [...this.operations].some((operation) => operation.revision >= latest.revision)
|
||||
: true;
|
||||
if (latest && !crossedOperationBoundary) {
|
||||
latest.revision = this.revision;
|
||||
latest.changes = { ...latest.changes, ...changes };
|
||||
} else {
|
||||
this.mutations.push({ revision: this.revision, changes });
|
||||
}
|
||||
}
|
||||
return this.revision;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user