refactor(cli): fully remove React anti patterns, improve type safety and fix UX oversights in SettingsDialog.tsx (#18963)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
Pyush Sinha
2026-03-02 13:30:58 -08:00
committed by GitHub
parent 18d0375a7f
commit 8133d63ac6
14 changed files with 589 additions and 1390 deletions
+152 -280
View File
@@ -4,11 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type {
Settings,
LoadedSettings,
LoadableSettingScope,
} from '../config/settings.js';
import type { Settings } from '../config/settings.js';
import type {
SettingDefinition,
SettingsSchema,
@@ -52,9 +48,6 @@ function clearFlattenedSchema() {
_FLATTENED_SCHEMA = undefined;
}
/**
* Get all settings grouped by category
*/
export function getSettingsByCategory(): Record<
string,
Array<SettingDefinition & { key: string }>
@@ -75,25 +68,16 @@ export function getSettingsByCategory(): Record<
return categories;
}
/**
* Get a setting definition by key
*/
export function getSettingDefinition(
key: string,
): (SettingDefinition & { key: string }) | undefined {
return getFlattenedSchema()[key];
}
/**
* Check if a setting requires restart
*/
export function requiresRestart(key: string): boolean {
return getFlattenedSchema()[key]?.requiresRestart ?? false;
}
/**
* Get the default value for a setting
*/
export function getDefaultValue(key: string): SettingsValue {
return getFlattenedSchema()[key]?.default;
}
@@ -120,9 +104,6 @@ export function getEffectiveDefaultValue(
return getDefaultValue(key);
}
/**
* Get all setting keys that require restart
*/
export function getRestartRequiredSettings(): string[] {
return Object.values(getFlattenedSchema())
.filter((definition) => definition.requiresRestart)
@@ -130,35 +111,55 @@ export function getRestartRequiredSettings(): string[] {
}
/**
* Recursively gets a value from a nested object using a key path array.
* Get restart-required setting keys that are also visible in the dialog.
* Non-dialog restart keys (e.g. parent container objects like mcpServers, tools)
* are excluded because users cannot change them through the dialog.
*/
export function getNestedValue(
obj: Record<string, unknown>,
path: string[],
): unknown {
const [first, ...rest] = path;
if (!first || !(first in obj)) {
return undefined;
}
const value = obj[first];
if (rest.length === 0) {
return value;
}
if (value && typeof value === 'object' && value !== null) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return getNestedValue(value as Record<string, unknown>, rest);
}
return undefined;
export function getDialogRestartRequiredSettings(): string[] {
return Object.values(getFlattenedSchema())
.filter(
(definition) =>
definition.requiresRestart && definition.showInDialog !== false,
)
.map((definition) => definition.key);
}
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
function isSettingsValue(value: unknown): value is SettingsValue {
if (value === undefined) return true;
if (value === null) return false;
const type = typeof value;
return (
type === 'string' ||
type === 'number' ||
type === 'boolean' ||
type === 'object'
);
}
/**
* Get the effective value for a setting, considering inheritance from higher scopes
* Always returns a value (never undefined) - falls back to default if not set anywhere
* Gets a value from a nested object using a key path array iteratively.
*/
export function getNestedValue(obj: unknown, path: string[]): unknown {
let current = obj;
for (const key of path) {
if (!isRecord(current) || !(key in current)) {
return undefined;
}
current = current[key];
}
return current;
}
/**
* Get the effective value for a setting falling back to the default value
*/
export function getEffectiveValue(
key: string,
settings: Settings,
mergedSettings: Settings,
): SettingsValue {
const definition = getSettingDefinition(key);
if (!definition) {
@@ -168,33 +169,19 @@ export function getEffectiveValue(
const path = key.split('.');
// Check the current scope's settings first
let value = getNestedValue(settings as Record<string, unknown>, path);
if (value !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return value as SettingsValue;
}
// Check the merged settings for an inherited value
value = getNestedValue(mergedSettings as Record<string, unknown>, path);
if (value !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return value as SettingsValue;
const value = getNestedValue(settings, path);
if (value !== undefined && isSettingsValue(value)) {
return value;
}
// Return default value if no value is set anywhere
return definition.default;
}
/**
* Get all setting keys from the schema
*/
export function getAllSettingKeys(): string[] {
return Object.keys(getFlattenedSchema());
}
/**
* Get settings by type
*/
export function getSettingsByType(
type: SettingsType,
): Array<SettingDefinition & { key: string }> {
@@ -203,9 +190,6 @@ export function getSettingsByType(
);
}
/**
* Get settings that require restart
*/
export function getSettingsRequiringRestart(): Array<
SettingDefinition & {
key: string;
@@ -223,22 +207,22 @@ export function isValidSettingKey(key: string): boolean {
return key in getFlattenedSchema();
}
/**
* Get the category for a setting
*/
export function getSettingCategory(key: string): string | undefined {
return getFlattenedSchema()[key]?.category;
}
/**
* Check if a setting should be shown in the settings dialog
*/
export function shouldShowInDialog(key: string): boolean {
return getFlattenedSchema()[key]?.showInDialog ?? true; // Default to true for backward compatibility
}
export function getDialogSettingKeys(): string[] {
return Object.values(getFlattenedSchema())
.filter((definition) => definition.showInDialog !== false)
.map((definition) => definition.key);
}
/**
* Get all settings that should be shown in the dialog, grouped by category
* Get all settings that should be shown in the dialog, grouped by category like "Advanced", "General", etc.
*/
export function getDialogSettingsByCategory(): Record<
string,
@@ -262,9 +246,6 @@ export function getDialogSettingsByCategory(): Record<
return categories;
}
/**
* Get settings by type that should be shown in the dialog
*/
export function getDialogSettingsByType(
type: SettingsType,
): Array<SettingDefinition & { key: string }> {
@@ -274,197 +255,30 @@ export function getDialogSettingsByType(
);
}
/**
* Get all setting keys that should be shown in the dialog
*/
export function getDialogSettingKeys(): string[] {
return Object.values(getFlattenedSchema())
.filter((definition) => definition.showInDialog !== false)
.map((definition) => definition.key);
}
// ============================================================================
// BUSINESS LOGIC UTILITIES (Higher-level utilities for setting operations)
// ============================================================================
/**
* Get the current value for a setting in a specific scope
* Always returns a value (never undefined) - falls back to default if not set anywhere
*/
export function getSettingValue(
key: string,
settings: Settings,
mergedSettings: Settings,
): boolean {
const definition = getSettingDefinition(key);
if (!definition) {
return false; // Default fallback for invalid settings
}
const value = getEffectiveValue(key, settings, mergedSettings);
// Ensure we return a boolean value, converting from the more general type
if (typeof value === 'boolean') {
return value;
}
return false; // Final fallback
}
/**
* Check if a setting value is modified from its default
*/
export function isSettingModified(key: string, value: boolean): boolean {
const defaultValue = getDefaultValue(key);
// Handle type comparison properly
if (typeof defaultValue === 'boolean') {
return value !== defaultValue;
}
// If default is not a boolean, consider it modified if value is true
return value === true;
}
/**
* Check if a setting exists in the original settings file for a scope
*/
export function settingExistsInScope(
export function isInSettingsScope(
key: string,
scopeSettings: Settings,
): boolean {
const path = key.split('.');
const value = getNestedValue(scopeSettings as Record<string, unknown>, path);
const value = getNestedValue(scopeSettings, path);
return value !== undefined;
}
/**
* Recursively sets a value in a nested object using a key path array.
*/
function setNestedValue(
obj: Record<string, unknown>,
path: string[],
value: unknown,
): Record<string, unknown> {
const [first, ...rest] = path;
if (!first) {
return obj;
}
if (rest.length === 0) {
obj[first] = value;
return obj;
}
if (!obj[first] || typeof obj[first] !== 'object') {
obj[first] = {};
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
setNestedValue(obj[first] as Record<string, unknown>, rest, value);
return obj;
}
/**
* Set a setting value in the pending settings
*/
export function setPendingSettingValue(
key: string,
value: boolean,
pendingSettings: Settings,
): Settings {
const path = key.split('.');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const newSettings = JSON.parse(JSON.stringify(pendingSettings));
setNestedValue(newSettings, path, value);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return newSettings;
}
/**
* Generic setter: Set a setting value (boolean, number, string, etc.) in the pending settings
*/
export function setPendingSettingValueAny(
key: string,
value: SettingsValue,
pendingSettings: Settings,
): Settings {
const path = key.split('.');
const newSettings = structuredClone(pendingSettings);
setNestedValue(newSettings, path, value);
return newSettings;
}
/**
* Check if any modified settings require a restart
*/
export function hasRestartRequiredSettings(
modifiedSettings: Set<string>,
): boolean {
return Array.from(modifiedSettings).some((key) => requiresRestart(key));
}
/**
* Get the restart required settings from a set of modified settings
*/
export function getRestartRequiredFromModified(
modifiedSettings: Set<string>,
): string[] {
return Array.from(modifiedSettings).filter((key) => requiresRestart(key));
}
/**
* Save modified settings to the appropriate scope
*/
export function saveModifiedSettings(
modifiedSettings: Set<string>,
pendingSettings: Settings,
loadedSettings: LoadedSettings,
scope: LoadableSettingScope,
): void {
modifiedSettings.forEach((settingKey) => {
const path = settingKey.split('.');
const value = getNestedValue(
pendingSettings as Record<string, unknown>,
path,
);
if (value === undefined) {
return;
}
const existsInOriginalFile = settingExistsInScope(
settingKey,
loadedSettings.forScope(scope).settings,
);
const isDefaultValue = value === getDefaultValue(settingKey);
if (existsInOriginalFile || !isDefaultValue) {
loadedSettings.setValue(scope, settingKey, value);
}
});
}
/**
* Get the display value for a setting, showing current scope value with default change indicator
* Appends a star (*) to settings that exist in the scope
*/
export function getDisplayValue(
key: string,
settings: Settings,
scopeSettings: Settings,
_mergedSettings: Settings,
modifiedSettings: Set<string>,
pendingSettings?: Settings,
): string {
// Prioritize pending changes if user has modified this setting
const definition = getSettingDefinition(key);
const existsInScope = isInSettingsScope(key, scopeSettings);
let value: SettingsValue;
if (pendingSettings && settingExistsInScope(key, pendingSettings)) {
// Show the value from the pending (unsaved) edits when it exists
value = getEffectiveValue(key, pendingSettings, {});
} else if (settingExistsInScope(key, settings)) {
// Show the value defined at the current scope if present
value = getEffectiveValue(key, settings, {});
if (existsInScope) {
value = getEffectiveValue(key, scopeSettings);
} else {
// Fall back to the schema default when the key is unset in this scope
value = getDefaultValue(key);
}
@@ -475,50 +289,108 @@ export function getDisplayValue(
valueString = option?.label ?? `${value}`;
}
// Check if value is different from default OR if it's in modified settings OR if there are pending changes
const defaultValue = getDefaultValue(key);
const isChangedFromDefault = value !== defaultValue;
const isInModifiedSettings = modifiedSettings.has(key);
// Mark as modified if setting exists in current scope OR is in modified settings
if (settingExistsInScope(key, settings) || isInModifiedSettings) {
return `${valueString}*`; // * indicates setting is set in current scope
}
if (isChangedFromDefault || isInModifiedSettings) {
return `${valueString}*`; // * indicates changed from default value
if (existsInScope) {
return `${valueString}*`;
}
return valueString;
}
/**
* Check if a setting doesn't exist in current scope (should be greyed out)
*/
export function isDefaultValue(key: string, settings: Settings): boolean {
return !settingExistsInScope(key, settings);
/**Utilities for parsing Settings that can be inline edited by the user typing out values */
function tryParseJsonStringArray(input: string): string[] | null {
try {
const parsed: unknown = JSON.parse(input);
if (
Array.isArray(parsed) &&
parsed.every((item): item is string => typeof item === 'string')
) {
return parsed;
}
return null;
} catch {
return null;
}
}
/**
* Check if a setting value is inherited (not set at current scope)
*/
export function isValueInherited(
key: string,
settings: Settings,
_mergedSettings: Settings,
): boolean {
return !settingExistsInScope(key, settings);
function tryParseJsonObject(input: string): Record<string, unknown> | null {
try {
const parsed: unknown = JSON.parse(input);
if (isRecord(parsed) && !Array.isArray(parsed)) {
return parsed;
}
return null;
} catch {
return null;
}
}
/**
* Get the effective value for display, considering inheritance
* Always returns a boolean value (never undefined)
*/
export function getEffectiveDisplayValue(
key: string,
settings: Settings,
mergedSettings: Settings,
): boolean {
return getSettingValue(key, settings, mergedSettings);
function parseStringArrayValue(input: string): string[] {
const trimmed = input.trim();
if (trimmed === '') return [];
return (
tryParseJsonStringArray(trimmed) ??
input
.split(',')
.map((p) => p.trim())
.filter((p) => p.length > 0)
);
}
function parseObjectValue(input: string): Record<string, unknown> | null {
const trimmed = input.trim();
if (trimmed === '') {
return null;
}
return tryParseJsonObject(trimmed);
}
export function parseEditedValue(
type: SettingsType,
newValue: string,
): SettingsValue | null {
if (type === 'number') {
if (newValue.trim() === '') {
return null;
}
const numParsed = Number(newValue.trim());
if (Number.isNaN(numParsed)) {
return null;
}
return numParsed;
}
if (type === 'array') {
return parseStringArrayValue(newValue);
}
if (type === 'object') {
return parseObjectValue(newValue);
}
return newValue;
}
export function getEditValue(
type: SettingsType,
rawValue: SettingsValue,
): string | undefined {
if (rawValue === undefined) {
return undefined;
}
if (type === 'array' && Array.isArray(rawValue)) {
return rawValue.join(', ');
}
if (type === 'object' && rawValue !== null && typeof rawValue === 'object') {
return JSON.stringify(rawValue);
}
return undefined;
}
export const TEST_ONLY = { clearFlattenedSchema };