refactor: reduce code duplication in font settings and sync logic

Address CodeRabbit review feedback:
- Create getEffectiveFont helper to deduplicate getEffectiveFontSans/Mono
- Extract getSettingsFieldValue and hasSettingsFieldChanged helpers
- Create reusable FontSelector component for font selection UI
- Refactor project-theme-section and appearance-section to use FontSelector
This commit is contained in:
Stefan de Vogelaere
2026-01-17 19:30:00 +01:00
parent a01f299597
commit ded5ecf4e9
6 changed files with 150 additions and 165 deletions

View File

@@ -79,6 +79,41 @@ const SETTINGS_FIELDS_TO_SYNC = [
// Fields from setup store to sync
const SETUP_FIELDS_TO_SYNC = ['isFirstRun', 'setupComplete', 'skipClaudeSetup'] as const;
/**
* Helper to extract a settings field value from app state
* Handles special cases for nested/mapped fields
*/
function getSettingsFieldValue(
field: (typeof SETTINGS_FIELDS_TO_SYNC)[number],
appState: ReturnType<typeof useAppStore.getState>
): unknown {
if (field === 'currentProjectId') {
return appState.currentProject?.id ?? null;
}
if (field === 'terminalFontFamily') {
return appState.terminalState.fontFamily;
}
return appState[field as keyof typeof appState];
}
/**
* Helper to check if a settings field changed between states
*/
function hasSettingsFieldChanged(
field: (typeof SETTINGS_FIELDS_TO_SYNC)[number],
newState: ReturnType<typeof useAppStore.getState>,
prevState: ReturnType<typeof useAppStore.getState>
): boolean {
if (field === 'currentProjectId') {
return newState.currentProject?.id !== prevState.currentProject?.id;
}
if (field === 'terminalFontFamily') {
return newState.terminalState.fontFamily !== prevState.terminalState.fontFamily;
}
const key = field as keyof typeof newState;
return newState[key] !== prevState[key];
}
interface SettingsSyncState {
/** Whether initial settings have been loaded from API */
loaded: boolean;
@@ -157,15 +192,7 @@ export function useSettingsSync(): SettingsSyncState {
// Build updates object from current state
const updates: Record<string, unknown> = {};
for (const field of SETTINGS_FIELDS_TO_SYNC) {
if (field === 'currentProjectId') {
// Special handling: extract ID from currentProject object
updates[field] = appState.currentProject?.id ?? null;
} else if (field === 'terminalFontFamily') {
// Special handling: map terminalState.fontFamily to terminalFontFamily
updates[field] = appState.terminalState.fontFamily;
} else {
updates[field] = appState[field as keyof typeof appState];
}
updates[field] = getSettingsFieldValue(field, appState);
}
// Include setup wizard state (lives in a separate store)
@@ -262,13 +289,7 @@ export function useSettingsSync(): SettingsSyncState {
// (migration has already hydrated the store from server/localStorage)
const updates: Record<string, unknown> = {};
for (const field of SETTINGS_FIELDS_TO_SYNC) {
if (field === 'currentProjectId') {
updates[field] = appState.currentProject?.id ?? null;
} else if (field === 'terminalFontFamily') {
updates[field] = appState.terminalState.fontFamily;
} else {
updates[field] = appState[field as keyof typeof appState];
}
updates[field] = getSettingsFieldValue(field, appState);
}
for (const field of SETUP_FIELDS_TO_SYNC) {
updates[field] = setupState[field as keyof typeof setupState];
@@ -322,24 +343,9 @@ export function useSettingsSync(): SettingsSyncState {
// Check if any synced field changed
let changed = false;
for (const field of SETTINGS_FIELDS_TO_SYNC) {
if (field === 'currentProjectId') {
// Special handling: compare currentProject IDs
if (newState.currentProject?.id !== prevState.currentProject?.id) {
changed = true;
break;
}
} else if (field === 'terminalFontFamily') {
// Special handling: compare terminalState.fontFamily
if (newState.terminalState.fontFamily !== prevState.terminalState.fontFamily) {
changed = true;
break;
}
} else {
const key = field as keyof typeof newState;
if (newState[key] !== prevState[key]) {
changed = true;
break;
}
if (hasSettingsFieldChanged(field, newState, prevState)) {
changed = true;
break;
}
}
@@ -413,13 +419,7 @@ export async function forceSyncSettingsToServer(): Promise<boolean> {
const updates: Record<string, unknown> = {};
for (const field of SETTINGS_FIELDS_TO_SYNC) {
if (field === 'currentProjectId') {
updates[field] = appState.currentProject?.id ?? null;
} else if (field === 'terminalFontFamily') {
updates[field] = appState.terminalState.fontFamily;
} else {
updates[field] = appState[field as keyof typeof appState];
}
updates[field] = getSettingsFieldValue(field, appState);
}
const setupState = useSetupStore.getState();
for (const field of SETUP_FIELDS_TO_SYNC) {