Reduce anti-slop findings in Persistence (#2953)

* chore(ui): reduce persistence anti-slop findings

* test(ui): cover fallback settings response

* fix(ui): preserve usage model group contract
This commit is contained in:
Bohdan Triapitsyn
2026-08-16 19:21:35 +03:00
committed by GitHub
parent bfa0f9ee2a
commit 7ef6441bf3
2 changed files with 55 additions and 17 deletions
+41
View File
@@ -241,6 +241,47 @@ describe('updateDesktopSettings', () => {
} }
}); });
test('sanitizes a successful fallback settings response before applying it', async () => {
const previousFetch = globalThis.fetch;
const fallbackFetch: typeof fetch = async () => new Response(JSON.stringify({ terminalShell: 'zsh' }), {
headers: { 'Content-Type': 'application/json' },
});
try {
globalThis.fetch = fallbackFetch;
useUIStore.getState().setTerminalShell('fish');
await updateDesktopSettings({ terminalShell: 'zsh' });
expect(useUIStore.getState().terminalShell).toBe('zsh');
expect(getSettingsSaveState()).toBe('idle');
} finally {
globalThis.fetch = previousFetch;
}
});
test('reports an error without applying a malformed fallback settings response', async () => {
const previousFetch = globalThis.fetch;
const fallbackFetch: typeof fetch = async () => new Response(JSON.stringify('ok'), {
headers: { 'Content-Type': 'application/json' },
});
const states: string[] = [];
const unsubscribe = subscribeToSettingsSaveState(() => {
states.push(getSettingsSaveState());
});
try {
globalThis.fetch = fallbackFetch;
useUIStore.getState().setTerminalShell('fish');
await updateDesktopSettings({ terminalShell: 'zsh' });
expect(useUIStore.getState().terminalShell).toBe('fish');
expect(states).toEqual(['saving', 'error']);
} finally {
unsubscribe();
globalThis.fetch = previousFetch;
}
});
test('drains a pending save to the previous runtime and ignores its stale response', async () => { test('drains a pending save to the previous runtime and ignores its stale response', async () => {
switchRuntimeEndpoint({ apiBaseUrl: 'https://settings-a.example', runtimeKey: 'settings-a' }); switchRuntimeEndpoint({ apiBaseUrl: 'https://settings-a.example', runtimeKey: 'settings-a' });
const saveResult = deferred<SettingsPayload>(); const saveResult = deferred<SettingsPayload>();
+14 -17
View File
@@ -130,7 +130,7 @@ const persistToLocalStorage = (settings: DesktopSettings) => {
if (Array.isArray(settings.projects) && settings.projects.length > 0) { if (Array.isArray(settings.projects) && settings.projects.length > 0) {
const collapsed = settings.projects const collapsed = settings.projects
.filter((project) => (project as unknown as { sidebarCollapsed?: boolean }).sidebarCollapsed === true) .filter((project) => project.sidebarCollapsed === true)
.map((project) => project.id) .map((project) => project.id)
.filter((id): id is string => typeof id === 'string' && id.length > 0); .filter((id): id is string => typeof id === 'string' && id.length > 0);
if (collapsed.length > 0) { if (collapsed.length > 0) {
@@ -273,13 +273,14 @@ const sanitizeSkillCatalogs = (value: unknown): DesktopSettings['skillCatalogs']
if (seen.has(id)) continue; if (seen.has(id)) continue;
seen.add(id); seen.add(id);
result.push({ const catalog: NonNullable<DesktopSettings['skillCatalogs']>[number] = {
id, id,
label, label,
source, source,
...(subpath ? { subpath } : {}), };
...(gitIdentityId ? { gitIdentityId } : {}), if (subpath) catalog.subpath = subpath;
}); if (gitIdentityId) catalog.gitIdentityId = gitIdentityId;
result.push(catalog);
} }
return result; return result;
@@ -393,7 +394,7 @@ const sanitizeProjects = (value: unknown): DesktopSettings['projects'] | undefin
project.icon = candidate.icon.trim(); project.icon = candidate.icon.trim();
} }
if (candidate.iconImage === null) { if (candidate.iconImage === null) {
(project as unknown as Record<string, unknown>).iconImage = null; project.iconImage = null;
} else if (candidate.iconImage && typeof candidate.iconImage === 'object') { } else if (candidate.iconImage && typeof candidate.iconImage === 'object') {
const iconImage = candidate.iconImage as Record<string, unknown>; const iconImage = candidate.iconImage as Record<string, unknown>;
const mime = typeof iconImage.mime === 'string' ? iconImage.mime.trim() : ''; const mime = typeof iconImage.mime === 'string' ? iconImage.mime.trim() : '';
@@ -404,18 +405,18 @@ const sanitizeProjects = (value: unknown): DesktopSettings['projects'] | undefin
? iconImage.source ? iconImage.source
: null; : null;
if (mime && updatedAt > 0 && source) { if (mime && updatedAt > 0 && source) {
(project as unknown as Record<string, unknown>).iconImage = { mime, updatedAt, source }; project.iconImage = { mime, updatedAt, source };
} }
} }
if (typeof candidate.color === 'string' && candidate.color.trim().length > 0) { if (typeof candidate.color === 'string' && candidate.color.trim().length > 0) {
project.color = candidate.color.trim(); project.color = candidate.color.trim();
} }
if (candidate.iconBackground === null) { if (candidate.iconBackground === null) {
(project as unknown as Record<string, unknown>).iconBackground = null; project.iconBackground = null;
} else { } else {
const iconBackground = normalizeIconBackground(candidate.iconBackground); const iconBackground = normalizeIconBackground(candidate.iconBackground);
if (iconBackground) { if (iconBackground) {
(project as unknown as Record<string, unknown>).iconBackground = iconBackground; project.iconBackground = iconBackground;
} }
} }
if (typeof candidate.addedAt === 'number' && Number.isFinite(candidate.addedAt) && candidate.addedAt >= 0) { if (typeof candidate.addedAt === 'number' && Number.isFinite(candidate.addedAt) && candidate.addedAt >= 0) {
@@ -429,7 +430,7 @@ const sanitizeProjects = (value: unknown): DesktopSettings['projects'] | undefin
project.lastOpenedAt = candidate.lastOpenedAt; project.lastOpenedAt = candidate.lastOpenedAt;
} }
if (typeof candidate.sidebarCollapsed === 'boolean') { if (typeof candidate.sidebarCollapsed === 'boolean') {
(project as unknown as Record<string, unknown>).sidebarCollapsed = candidate.sidebarCollapsed; project.sidebarCollapsed = candidate.sidebarCollapsed;
} }
result.push(project); result.push(project);
} }
@@ -507,7 +508,7 @@ const sanitizeModelRefs = (value: unknown, limit: number): Array<{ providerID: s
}; };
const getPersistApi = (): PersistApi | undefined => { const getPersistApi = (): PersistApi | undefined => {
const candidate = (useUIStore as unknown as { persist?: PersistApi }).persist; const candidate = useUIStore.persist;
if (candidate && typeof candidate === 'object') { if (candidate && typeof candidate === 'object') {
return candidate; return candidate;
} }
@@ -1325,11 +1326,7 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
for (const [providerId, config] of Object.entries(candidate.usageModelGroups)) { for (const [providerId, config] of Object.entries(candidate.usageModelGroups)) {
if (config && typeof config === 'object') { if (config && typeof config === 'object') {
const typedConfig = config as Record<string, unknown>; const typedConfig = config as Record<string, unknown>;
const providerConfig: { const providerConfig: NonNullable<DesktopSettings['usageModelGroups']>[string] = {};
customGroups?: Array<{id: string; label: string; models: string[]; order: number}>;
modelAssignments?: Record<string, string>;
renamedGroups?: Record<string, string>;
} = {};
// Parse customGroups // Parse customGroups
if (Array.isArray(typedConfig.customGroups)) { if (Array.isArray(typedConfig.customGroups)) {
@@ -1875,7 +1872,7 @@ async function _flushSettingsUpdate(): Promise<void> {
return; return;
} }
const updated = (await response.json().catch(() => null)) as DesktopSettings | null; const updated = sanitizeWebSettings(await response.json().catch(() => null));
if (!isSettingsRuntimeContextCurrent(context)) return; if (!isSettingsRuntimeContextCurrent(context)) return;
if (updated) { if (updated) {
applyDesktopUiPreferences(updated); applyDesktopUiPreferences(updated);