fix: improve OpenCode settings handling
Improves OpenCode CLI and shortcut settings flows Updates runtime API and persistence handling Adds coverage for settings helper behavior
This commit is contained in:
@@ -125,6 +125,10 @@ const issueDesktopClientToken = async (): Promise<string> => {
|
||||
return typeof payload?.token === 'string' ? payload.token.trim() : '';
|
||||
};
|
||||
|
||||
const shouldUseDesktopShellPasswordLogin = (): boolean => {
|
||||
return isDesktopShell() && !isLocalDesktopRuntime();
|
||||
};
|
||||
|
||||
const issueDesktopClientTokenViaShell = async (password: string, trustDevice: boolean): Promise<string> => {
|
||||
if (!isDesktopShell() || typeof window === 'undefined') {
|
||||
return '';
|
||||
@@ -220,7 +224,7 @@ const LoadingScreen: React.FC = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const ErrorScreen: React.FC<ErrorScreenProps> = ({ onRetry, errorType = 'network', retryAfter }) => {
|
||||
const ErrorScreen: React.FC<ErrorScreenProps> = ({ onRetry, errorType = 'network', retryAfter, children }) => {
|
||||
const { t } = useI18n();
|
||||
const isRateLimit = errorType === 'rate-limit';
|
||||
const minutes = retryAfter ? Math.ceil(retryAfter / 60) : 1;
|
||||
@@ -243,6 +247,7 @@ const ErrorScreen: React.FC<ErrorScreenProps> = ({ onRetry, errorType = 'network
|
||||
<Button type="button" onClick={onRetry} className="w-full max-w-xs">
|
||||
{t('sessionAuth.error.retry')}
|
||||
</Button>
|
||||
{children}
|
||||
</div>
|
||||
</AuthShell>
|
||||
);
|
||||
@@ -258,6 +263,7 @@ interface ErrorScreenProps {
|
||||
onRetry: () => void;
|
||||
errorType?: 'network' | 'rate-limit';
|
||||
retryAfter?: number;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SessionAuthGate: React.FC<SessionAuthGateProps> = ({ children }) => {
|
||||
@@ -381,6 +387,12 @@ export const SessionAuthGate: React.FC<SessionAuthGateProps> = ({ children }) =>
|
||||
setIsTunnelLocked(false);
|
||||
} catch (error) {
|
||||
console.warn('Failed to check session status:', error);
|
||||
if (shouldUseDesktopShellPasswordLogin()) {
|
||||
setState('locked');
|
||||
setRetryAfter(undefined);
|
||||
setIsTunnelLocked(false);
|
||||
return;
|
||||
}
|
||||
setState('error');
|
||||
setIsTunnelLocked(false);
|
||||
}
|
||||
@@ -529,6 +541,16 @@ export const SessionAuthGate: React.FC<SessionAuthGateProps> = ({ children }) =>
|
||||
setState('error');
|
||||
} catch (error) {
|
||||
console.warn('Failed to submit UI password:', error);
|
||||
const clientToken = shouldUseDesktopShellPasswordLogin()
|
||||
? await issueDesktopClientTokenViaShell(password, trustDevice)
|
||||
: '';
|
||||
if (clientToken) {
|
||||
setPassword('');
|
||||
setIsTunnelLocked(false);
|
||||
await applyDesktopClientToken(clientToken);
|
||||
setState('authenticated');
|
||||
return;
|
||||
}
|
||||
setErrorMessage(t('sessionAuth.error.networkRetry'));
|
||||
setIsTunnelLocked(false);
|
||||
setState('error');
|
||||
@@ -620,7 +642,18 @@ export const SessionAuthGate: React.FC<SessionAuthGateProps> = ({ children }) =>
|
||||
}
|
||||
|
||||
if (state === 'error') {
|
||||
return <ErrorScreen onRetry={() => void checkStatus()} errorType="network" />;
|
||||
return (
|
||||
<ErrorScreen onRetry={() => void checkStatus()} errorType="network">
|
||||
{showHostSwitcher && (
|
||||
<div className="w-full max-w-xs">
|
||||
<DesktopHostSwitcherInline />
|
||||
<p className="mt-1 text-center typography-micro text-muted-foreground">
|
||||
{t('sessionAuth.locked.hostSwitcherHint')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</ErrorScreen>
|
||||
);
|
||||
}
|
||||
|
||||
if (state === 'rate-limited') {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import {
|
||||
formatShortcutForDisplay,
|
||||
getCustomizableShortcutActions,
|
||||
@@ -70,6 +71,10 @@ export const KeyboardShortcutsSettings: React.FC = () => {
|
||||
conflictActionId: string;
|
||||
} | null>(null);
|
||||
|
||||
const persistShortcutOverrides = React.useCallback((nextOverrides: Record<string, ShortcutCombo>) => {
|
||||
void updateDesktopSettings({ shortcutOverrides: nextOverrides });
|
||||
}, []);
|
||||
|
||||
const findConflict = React.useCallback((actionId: string, combo: ShortcutCombo): string | null => {
|
||||
const normalized = normalizeCombo(combo);
|
||||
for (const action of actions) {
|
||||
@@ -93,7 +98,9 @@ export const KeyboardShortcutsSettings: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextOverrides = { ...shortcutOverrides, [actionId]: normalized };
|
||||
setShortcutOverride(actionId, normalized);
|
||||
persistShortcutOverrides(nextOverrides);
|
||||
setPendingOverwrite(null);
|
||||
setErrorText('');
|
||||
setWarningText(isRiskyBrowserShortcut(normalized) ? t('settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut') : '');
|
||||
@@ -102,15 +109,21 @@ export const KeyboardShortcutsSettings: React.FC = () => {
|
||||
delete rest[actionId];
|
||||
return rest;
|
||||
});
|
||||
}, [findConflict, setShortcutOverride, t]);
|
||||
}, [findConflict, persistShortcutOverrides, setShortcutOverride, shortcutOverrides, t]);
|
||||
|
||||
const confirmOverwrite = React.useCallback(() => {
|
||||
if (!pendingOverwrite) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextOverrides = {
|
||||
...shortcutOverrides,
|
||||
[pendingOverwrite.conflictActionId]: UNASSIGNED_SHORTCUT,
|
||||
[pendingOverwrite.actionId]: pendingOverwrite.combo,
|
||||
};
|
||||
setShortcutOverride(pendingOverwrite.conflictActionId, UNASSIGNED_SHORTCUT);
|
||||
setShortcutOverride(pendingOverwrite.actionId, pendingOverwrite.combo);
|
||||
persistShortcutOverrides(nextOverrides);
|
||||
setPendingOverwrite(null);
|
||||
setErrorText('');
|
||||
setWarningText(isRiskyBrowserShortcut(pendingOverwrite.combo) ? t('settings.openchamber.keyboardShortcuts.warning.riskyBrowserShortcut') : '');
|
||||
@@ -119,10 +132,13 @@ export const KeyboardShortcutsSettings: React.FC = () => {
|
||||
delete rest[pendingOverwrite.actionId];
|
||||
return rest;
|
||||
});
|
||||
}, [pendingOverwrite, setShortcutOverride, t]);
|
||||
}, [pendingOverwrite, persistShortcutOverrides, setShortcutOverride, shortcutOverrides, t]);
|
||||
|
||||
const resetOne = React.useCallback((actionId: string) => {
|
||||
const nextOverrides = { ...shortcutOverrides };
|
||||
delete nextOverrides[actionId];
|
||||
clearShortcutOverride(actionId);
|
||||
persistShortcutOverrides(nextOverrides);
|
||||
setDraftByAction((current) => {
|
||||
const rest = { ...current };
|
||||
delete rest[actionId];
|
||||
@@ -131,7 +147,7 @@ export const KeyboardShortcutsSettings: React.FC = () => {
|
||||
setPendingOverwrite(null);
|
||||
setErrorText('');
|
||||
setWarningText('');
|
||||
}, [clearShortcutOverride]);
|
||||
}, [clearShortcutOverride, persistShortcutOverrides, shortcutOverrides]);
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
@@ -145,6 +161,7 @@ export const KeyboardShortcutsSettings: React.FC = () => {
|
||||
className="!font-normal"
|
||||
onClick={() => {
|
||||
resetAllShortcutOverrides();
|
||||
persistShortcutOverrides({});
|
||||
setDraftByAction({});
|
||||
setPendingOverwrite(null);
|
||||
setErrorText('');
|
||||
|
||||
@@ -82,6 +82,11 @@ export const OpenCodeCliSettings: React.FC = () => {
|
||||
}
|
||||
}, [t, value]);
|
||||
|
||||
const handleShowUpdateNotificationsChange = React.useCallback((enabled: boolean) => {
|
||||
setShowOpenCodeUpdateNotifications(enabled);
|
||||
void updateDesktopSettings({ showOpenCodeUpdateNotifications: enabled });
|
||||
}, [setShowOpenCodeUpdateNotifications]);
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<div className="mb-1 px-1">
|
||||
@@ -147,7 +152,7 @@ export const OpenCodeCliSettings: React.FC = () => {
|
||||
<label className="flex cursor-pointer items-center gap-2 py-1.5">
|
||||
<Checkbox
|
||||
checked={showOpenCodeUpdateNotifications}
|
||||
onChange={setShowOpenCodeUpdateNotifications}
|
||||
onChange={handleShowUpdateNotificationsChange}
|
||||
ariaLabel={t('settings.openchamber.opencodeCli.field.showUpdateNotificationsAria')}
|
||||
/>
|
||||
<span className="typography-ui-label text-foreground">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { runtimeFetch } from '@/lib/runtime-fetch';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { getSafeStorage } from '@/stores/utils/safeStorage';
|
||||
import {
|
||||
resolveOpenCodeUpdateVersion,
|
||||
@@ -119,6 +120,7 @@ export const OpenCodeUpdateToast: React.FC = () => {
|
||||
label: t('opencodeUpdate.toast.actions.dismiss'),
|
||||
onClick: () => {
|
||||
getSafeStorage().setItem(UPDATE_TOAST_DISMISSED_VERSION_KEY, version);
|
||||
void updateDesktopSettings({ openCodeUpdateToastDismissedVersion: version });
|
||||
toast.dismiss(UPDATE_TOAST_ID);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -648,6 +648,8 @@ export interface SettingsPayload {
|
||||
queueModeEnabled?: boolean;
|
||||
gitmojiEnabled?: boolean;
|
||||
inputSpellcheckEnabled?: boolean;
|
||||
showOpenCodeUpdateNotifications?: boolean;
|
||||
openCodeUpdateToastDismissedVersion?: string;
|
||||
showToolFileIcons?: boolean;
|
||||
showExpandedBashTools?: boolean;
|
||||
showExpandedEditTools?: boolean;
|
||||
@@ -663,6 +665,7 @@ export interface SettingsPayload {
|
||||
padding?: number;
|
||||
cornerRadius?: number;
|
||||
inputBarOffset?: number;
|
||||
shortcutOverrides?: Record<string, string>;
|
||||
diffLayoutPreference?: 'dynamic' | 'inline' | 'side-by-side';
|
||||
diffViewMode?: 'single' | 'stacked';
|
||||
gitChangesViewMode?: 'flat' | 'tree';
|
||||
|
||||
@@ -131,6 +131,8 @@ export type DesktopSettings = {
|
||||
pwaOrientation?: 'system' | 'portrait' | 'landscape';
|
||||
mobileKeyboardMode?: MobileKeyboardMode;
|
||||
inputSpellcheckEnabled?: boolean;
|
||||
showOpenCodeUpdateNotifications?: boolean;
|
||||
openCodeUpdateToastDismissedVersion?: string;
|
||||
showToolFileIcons?: boolean;
|
||||
showExpandedBashTools?: boolean;
|
||||
showExpandedEditTools?: boolean;
|
||||
@@ -151,6 +153,7 @@ export type DesktopSettings = {
|
||||
padding?: number;
|
||||
cornerRadius?: number;
|
||||
inputBarOffset?: number;
|
||||
shortcutOverrides?: Record<string, string>;
|
||||
|
||||
favoriteModels?: Array<{ providerID: string; modelID: string }>;
|
||||
recentModels?: Array<{ providerID: string; modelID: string }>;
|
||||
|
||||
@@ -98,6 +98,14 @@ const persistToLocalStorage = (settings: DesktopSettings) => {
|
||||
if (typeof settings.mobileKeyboardMode === 'string') {
|
||||
setStoredMobileKeyboardMode(settings.mobileKeyboardMode);
|
||||
}
|
||||
if (typeof settings.openCodeUpdateToastDismissedVersion === 'string') {
|
||||
const version = settings.openCodeUpdateToastDismissedVersion.trim();
|
||||
if (version) {
|
||||
localStorage.setItem('opencode-update-toast-dismissed-version', version);
|
||||
} else {
|
||||
localStorage.removeItem('opencode-update-toast-dismissed-version');
|
||||
}
|
||||
}
|
||||
if (settings.sttProvider === 'browser' || settings.sttProvider === 'server') {
|
||||
localStorage.setItem('sttProvider', settings.sttProvider);
|
||||
}
|
||||
@@ -167,6 +175,27 @@ const sanitizeSkillCatalogs = (value: unknown): DesktopSettings['skillCatalogs']
|
||||
return result;
|
||||
};
|
||||
|
||||
const sanitizeShortcutOverrides = (value: unknown): Record<string, string> | undefined => {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const result: Record<string, string> = {};
|
||||
for (const [key, combo] of Object.entries(value)) {
|
||||
const normalizedKey = typeof key === 'string' ? key.trim() : '';
|
||||
const normalizedCombo = typeof combo === 'string' ? combo.trim() : '';
|
||||
if (!normalizedKey || !normalizedCombo) continue;
|
||||
result[normalizedKey] = normalizedCombo;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const areStringRecordsEqual = (left: Record<string, string>, right: Record<string, string>): boolean => {
|
||||
const leftEntries = Object.entries(left);
|
||||
const rightEntries = Object.entries(right);
|
||||
if (leftEntries.length !== rightEntries.length) return false;
|
||||
return leftEntries.every(([key, value]) => right[key] === value);
|
||||
};
|
||||
|
||||
const HEX_COLOR_PATTERN = /^#(?:[\da-fA-F]{3}|[\da-fA-F]{6})$/;
|
||||
|
||||
const normalizeIconBackground = (value: unknown): string | null => {
|
||||
@@ -417,6 +446,12 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
||||
if (typeof settings.inputSpellcheckEnabled === 'boolean' && settings.inputSpellcheckEnabled !== store.inputSpellcheckEnabled) {
|
||||
store.setInputSpellcheckEnabled(settings.inputSpellcheckEnabled);
|
||||
}
|
||||
if (
|
||||
typeof settings.showOpenCodeUpdateNotifications === 'boolean'
|
||||
&& settings.showOpenCodeUpdateNotifications !== store.showOpenCodeUpdateNotifications
|
||||
) {
|
||||
store.setShowOpenCodeUpdateNotifications(settings.showOpenCodeUpdateNotifications);
|
||||
}
|
||||
if (typeof settings.showToolFileIcons === 'boolean' && settings.showToolFileIcons !== store.showToolFileIcons) {
|
||||
store.setShowToolFileIcons(settings.showToolFileIcons);
|
||||
}
|
||||
@@ -510,6 +545,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
||||
if (typeof settings.inputBarOffset === 'number' && Number.isFinite(settings.inputBarOffset) && settings.inputBarOffset !== store.inputBarOffset) {
|
||||
store.setInputBarOffset(settings.inputBarOffset);
|
||||
}
|
||||
if (settings.shortcutOverrides && !areStringRecordsEqual(settings.shortcutOverrides, store.shortcutOverrides)) {
|
||||
useUIStore.setState({ shortcutOverrides: settings.shortcutOverrides });
|
||||
}
|
||||
if (typeof settings.mobileKeyboardMode === 'string') {
|
||||
const mode = normalizeMobileKeyboardMode(settings.mobileKeyboardMode, store.mobileKeyboardMode);
|
||||
if (mode !== store.mobileKeyboardMode) {
|
||||
@@ -910,6 +948,12 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.inputSpellcheckEnabled === 'boolean') {
|
||||
result.inputSpellcheckEnabled = candidate.inputSpellcheckEnabled;
|
||||
}
|
||||
if (typeof candidate.showOpenCodeUpdateNotifications === 'boolean') {
|
||||
result.showOpenCodeUpdateNotifications = candidate.showOpenCodeUpdateNotifications;
|
||||
}
|
||||
if (typeof candidate.openCodeUpdateToastDismissedVersion === 'string') {
|
||||
result.openCodeUpdateToastDismissedVersion = candidate.openCodeUpdateToastDismissedVersion.trim().slice(0, 128);
|
||||
}
|
||||
if (typeof candidate.showToolFileIcons === 'boolean') {
|
||||
result.showToolFileIcons = candidate.showToolFileIcons;
|
||||
}
|
||||
@@ -977,10 +1021,13 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.inputBarOffset === 'number' && Number.isFinite(candidate.inputBarOffset)) {
|
||||
result.inputBarOffset = candidate.inputBarOffset;
|
||||
}
|
||||
const shortcutOverrides = sanitizeShortcutOverrides(candidate.shortcutOverrides);
|
||||
if (shortcutOverrides) {
|
||||
result.shortcutOverrides = shortcutOverrides;
|
||||
}
|
||||
if (typeof candidate.mobileKeyboardMode === 'string') {
|
||||
const mode = normalizeMobileKeyboardMode(candidate.mobileKeyboardMode, undefined);
|
||||
if (mode) {
|
||||
result.mobileKeyboardMode = mode;
|
||||
if (candidate.mobileKeyboardMode === 'native' || candidate.mobileKeyboardMode === 'resize-content') {
|
||||
result.mobileKeyboardMode = candidate.mobileKeyboardMode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,26 @@ export const createSettingsHelpers = (dependencies) => {
|
||||
const STT_SERVER_URL_MAX_LENGTH = 2048;
|
||||
const STT_MODEL_MAX_LENGTH = 256;
|
||||
const STT_LANGUAGE_MAX_LENGTH = 64;
|
||||
const VERSION_STRING_MAX_LENGTH = 128;
|
||||
const SHORTCUT_OVERRIDE_KEY_MAX_LENGTH = 128;
|
||||
const SHORTCUT_OVERRIDE_VALUE_MAX_LENGTH = 128;
|
||||
const PWA_ORIENTATION_VALUES = new Set(['system', 'portrait', 'landscape']);
|
||||
const MOBILE_KEYBOARD_MODE_VALUES = new Set(['native', 'resize-content']);
|
||||
|
||||
const sanitizeShortcutOverrides = (value) => {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
const result = {};
|
||||
for (const [rawKey, rawValue] of Object.entries(value)) {
|
||||
const key = typeof rawKey === 'string' ? rawKey.trim() : '';
|
||||
const combo = typeof rawValue === 'string' ? rawValue.trim() : '';
|
||||
if (!key || !combo) continue;
|
||||
result[key.slice(0, SHORTCUT_OVERRIDE_KEY_MAX_LENGTH)] = combo.slice(0, SHORTCUT_OVERRIDE_VALUE_MAX_LENGTH);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const normalizePwaAppName = (value, fallback = '') => {
|
||||
if (typeof value !== 'string') {
|
||||
return fallback;
|
||||
@@ -350,7 +367,7 @@ export const createSettingsHelpers = (dependencies) => {
|
||||
result.pwaOrientation = normalizePwaOrientation(candidate.pwaOrientation, undefined);
|
||||
}
|
||||
if (typeof candidate.mobileKeyboardMode === 'string') {
|
||||
const mode = normalizeMobileKeyboardMode(candidate.mobileKeyboardMode, undefined);
|
||||
const mode = normalizeMobileKeyboardMode(candidate.mobileKeyboardMode, null);
|
||||
if (mode) {
|
||||
result.mobileKeyboardMode = mode;
|
||||
}
|
||||
@@ -364,6 +381,13 @@ export const createSettingsHelpers = (dependencies) => {
|
||||
if (typeof candidate.inputSpellcheckEnabled === 'boolean') {
|
||||
result.inputSpellcheckEnabled = candidate.inputSpellcheckEnabled;
|
||||
}
|
||||
if (typeof candidate.showOpenCodeUpdateNotifications === 'boolean') {
|
||||
result.showOpenCodeUpdateNotifications = candidate.showOpenCodeUpdateNotifications;
|
||||
}
|
||||
if (typeof candidate.openCodeUpdateToastDismissedVersion === 'string') {
|
||||
const version = candidate.openCodeUpdateToastDismissedVersion.trim();
|
||||
result.openCodeUpdateToastDismissedVersion = version.slice(0, VERSION_STRING_MAX_LENGTH);
|
||||
}
|
||||
if (typeof candidate.showToolFileIcons === 'boolean') {
|
||||
result.showToolFileIcons = candidate.showToolFileIcons;
|
||||
}
|
||||
@@ -437,6 +461,11 @@ export const createSettingsHelpers = (dependencies) => {
|
||||
result.inputBarOffset = Math.max(0, Math.min(100, Math.round(candidate.inputBarOffset)));
|
||||
}
|
||||
|
||||
const shortcutOverrides = sanitizeShortcutOverrides(candidate.shortcutOverrides);
|
||||
if (shortcutOverrides) {
|
||||
result.shortcutOverrides = shortcutOverrides;
|
||||
}
|
||||
|
||||
const favoriteModels = sanitizeModelRefs(candidate.favoriteModels, 64);
|
||||
if (favoriteModels) {
|
||||
result.favoriteModels = favoriteModels;
|
||||
|
||||
@@ -94,6 +94,54 @@ describe('settings helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts shortcut overrides as a persisted shared setting', () => {
|
||||
const helpers = createTestHelpers();
|
||||
|
||||
expect(helpers.sanitizeSettingsUpdate({
|
||||
shortcutOverrides: {
|
||||
open_settings: 'mod+comma',
|
||||
new_chat: '__unassigned__',
|
||||
invalid: 123,
|
||||
empty: '',
|
||||
},
|
||||
})).toEqual({
|
||||
shortcutOverrides: {
|
||||
open_settings: 'mod+comma',
|
||||
new_chat: '__unassigned__',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves empty shortcut overrides when resetting all shortcuts', () => {
|
||||
const helpers = createTestHelpers();
|
||||
|
||||
expect(helpers.sanitizeSettingsUpdate({ shortcutOverrides: {} })).toEqual({
|
||||
shortcutOverrides: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts OpenCode update notification preference as a persisted shared setting', () => {
|
||||
const helpers = createTestHelpers();
|
||||
|
||||
expect(helpers.sanitizeSettingsUpdate({ showOpenCodeUpdateNotifications: false })).toEqual({
|
||||
showOpenCodeUpdateNotifications: false,
|
||||
});
|
||||
expect(helpers.sanitizeSettingsUpdate({ showOpenCodeUpdateNotifications: true })).toEqual({
|
||||
showOpenCodeUpdateNotifications: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts dismissed OpenCode update toast version as a persisted shared setting', () => {
|
||||
const helpers = createTestHelpers();
|
||||
|
||||
expect(helpers.sanitizeSettingsUpdate({ openCodeUpdateToastDismissedVersion: ' 1.16.0 ' })).toEqual({
|
||||
openCodeUpdateToastDismissedVersion: '1.16.0',
|
||||
});
|
||||
expect(helpers.sanitizeSettingsUpdate({ openCodeUpdateToastDismissedVersion: '' })).toEqual({
|
||||
openCodeUpdateToastDismissedVersion: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects non-boolean collapsibleThinkingBlocks values', () => {
|
||||
const helpers = createTestHelpers();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user