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:
committed by
GitHub
parent
bfa0f9ee2a
commit
7ef6441bf3
@@ -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 () => {
|
||||
switchRuntimeEndpoint({ apiBaseUrl: 'https://settings-a.example', runtimeKey: 'settings-a' });
|
||||
const saveResult = deferred<SettingsPayload>();
|
||||
|
||||
@@ -130,7 +130,7 @@ const persistToLocalStorage = (settings: DesktopSettings) => {
|
||||
|
||||
if (Array.isArray(settings.projects) && settings.projects.length > 0) {
|
||||
const collapsed = settings.projects
|
||||
.filter((project) => (project as unknown as { sidebarCollapsed?: boolean }).sidebarCollapsed === true)
|
||||
.filter((project) => project.sidebarCollapsed === true)
|
||||
.map((project) => project.id)
|
||||
.filter((id): id is string => typeof id === 'string' && id.length > 0);
|
||||
if (collapsed.length > 0) {
|
||||
@@ -273,13 +273,14 @@ const sanitizeSkillCatalogs = (value: unknown): DesktopSettings['skillCatalogs']
|
||||
if (seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
|
||||
result.push({
|
||||
const catalog: NonNullable<DesktopSettings['skillCatalogs']>[number] = {
|
||||
id,
|
||||
label,
|
||||
source,
|
||||
...(subpath ? { subpath } : {}),
|
||||
...(gitIdentityId ? { gitIdentityId } : {}),
|
||||
});
|
||||
};
|
||||
if (subpath) catalog.subpath = subpath;
|
||||
if (gitIdentityId) catalog.gitIdentityId = gitIdentityId;
|
||||
result.push(catalog);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -393,7 +394,7 @@ const sanitizeProjects = (value: unknown): DesktopSettings['projects'] | undefin
|
||||
project.icon = candidate.icon.trim();
|
||||
}
|
||||
if (candidate.iconImage === null) {
|
||||
(project as unknown as Record<string, unknown>).iconImage = null;
|
||||
project.iconImage = null;
|
||||
} else if (candidate.iconImage && typeof candidate.iconImage === 'object') {
|
||||
const iconImage = candidate.iconImage as Record<string, unknown>;
|
||||
const mime = typeof iconImage.mime === 'string' ? iconImage.mime.trim() : '';
|
||||
@@ -404,18 +405,18 @@ const sanitizeProjects = (value: unknown): DesktopSettings['projects'] | undefin
|
||||
? iconImage.source
|
||||
: null;
|
||||
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) {
|
||||
project.color = candidate.color.trim();
|
||||
}
|
||||
if (candidate.iconBackground === null) {
|
||||
(project as unknown as Record<string, unknown>).iconBackground = null;
|
||||
project.iconBackground = null;
|
||||
} else {
|
||||
const iconBackground = normalizeIconBackground(candidate.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) {
|
||||
@@ -429,7 +430,7 @@ const sanitizeProjects = (value: unknown): DesktopSettings['projects'] | undefin
|
||||
project.lastOpenedAt = candidate.lastOpenedAt;
|
||||
}
|
||||
if (typeof candidate.sidebarCollapsed === 'boolean') {
|
||||
(project as unknown as Record<string, unknown>).sidebarCollapsed = candidate.sidebarCollapsed;
|
||||
project.sidebarCollapsed = candidate.sidebarCollapsed;
|
||||
}
|
||||
result.push(project);
|
||||
}
|
||||
@@ -507,7 +508,7 @@ const sanitizeModelRefs = (value: unknown, limit: number): Array<{ providerID: s
|
||||
};
|
||||
|
||||
const getPersistApi = (): PersistApi | undefined => {
|
||||
const candidate = (useUIStore as unknown as { persist?: PersistApi }).persist;
|
||||
const candidate = useUIStore.persist;
|
||||
if (candidate && typeof candidate === 'object') {
|
||||
return candidate;
|
||||
}
|
||||
@@ -1325,11 +1326,7 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
for (const [providerId, config] of Object.entries(candidate.usageModelGroups)) {
|
||||
if (config && typeof config === 'object') {
|
||||
const typedConfig = config as Record<string, unknown>;
|
||||
const providerConfig: {
|
||||
customGroups?: Array<{id: string; label: string; models: string[]; order: number}>;
|
||||
modelAssignments?: Record<string, string>;
|
||||
renamedGroups?: Record<string, string>;
|
||||
} = {};
|
||||
const providerConfig: NonNullable<DesktopSettings['usageModelGroups']>[string] = {};
|
||||
|
||||
// Parse customGroups
|
||||
if (Array.isArray(typedConfig.customGroups)) {
|
||||
@@ -1875,7 +1872,7 @@ async function _flushSettingsUpdate(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = (await response.json().catch(() => null)) as DesktopSettings | null;
|
||||
const updated = sanitizeWebSettings(await response.json().catch(() => null));
|
||||
if (!isSettingsRuntimeContextCurrent(context)) return;
|
||||
if (updated) {
|
||||
applyDesktopUiPreferences(updated);
|
||||
|
||||
Reference in New Issue
Block a user