Files
BIPMaterialManager/docs/plans/2026-03-03-settings-partial-save-implementation.md
Misaka e23cf71f78 feat: add material type management feature
- Add MaterialTypeManagementDialog component for managing material type keywords
- Add MaterialsTypeToBeDeletedDAO for database operations
- Add material-type-handler IPC handlers
- Update CleanerPage with type management button
- Add database fix scripts for AUTO_INCREMENT
- Update documentation for settings partial save and validation flow

Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
2026-03-03 22:51:11 +08:00

26 KiB
Raw Permalink Blame History

Settings Partial Save Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Fix settings save to only update modified fields, preventing unintended overwrites of unmodified .env configuration values.

Architecture: Implement partial save strategy using deep merge + whitelist validation. ConfigManager validates fields, merges with current config, backs up .env, and atomically writes changes.

Tech Stack: TypeScript 5.9, Electron 39, Vitest, Node.js fs module


Task 1: Add Utility Functions to ConfigManager

Files:

  • Modify: src/main/services/config/config-manager.ts

Step 1: Write failing test for deep merge

Create test file: tests/main/services/config/config-manager.test.ts

import { describe, it, expect } from 'vitest'
import { ConfigManager } from '@/services/config/config-manager'
import type { SettingsData } from '@/types/settings.types'

describe('ConfigManager - deep merge utilities', () => {
  it('should deep merge objects, updating only specified fields', async () => {
    const manager = ConfigManager.getInstance()
    await manager.initialize()

    // Setup initial state
    const initial: SettingsData = {
      erp: {
        url: 'http://old.com',
        username: 'user1',
        password: 'pass1',
        headless: true,
        ignoreHttpsErrors: true,
        autoCloseBrowser: true
      },
      database: {
        dbType: 'mysql',
        server: '',
        mysqlHost: 'localhost',
        mysqlPort: 3306,
        database: 'db',
        username: 'user',
        password: ''
      },
      paths: { dataDir: '/data', defaultOutput: 'out.xlsx', validationOutput: 'validation.xlsx' },
      extraction: {
        batchSize: 100,
        verbose: true,
        autoConvert: true,
        mergeBatches: true,
        enableDbPersistence: true
      },
      validation: {
        dataSource: 'database_full',
        batchSize: 2000,
        matchMode: 'substring',
        enableCrud: false,
        defaultManager: ''
      },
      ui: { fontFamily: 'Arial', fontSize: 12, productionIdInputWidth: 20 },
      execution: { dryRun: false }
    }

    // Load initial settings
    await manager.saveAllSettings(initial)

    // Partial update
    const partial = {
      erp: { url: 'http://new.com' }
    }

    const result = await manager.savePartialSettings(partial)

    expect(result.success).toBe(true)

    const current = manager.getAllSettings()

    // Updated field
    expect(current.erp.url).toBe('http://new.com')

    // Preserved fields
    expect(current.erp.username).toBe('user1')
    expect(current.database.dbType).toBe('mysql')
    expect(current.paths.dataDir).toBe('/data')
  })
})

Step 2: Run test to verify it fails

Run: cd D:/Node/ERPAuto-settings-fix && npm test -- tests/main/services/config/config-manager.test.ts

Expected: FAIL - "savePartialSettings is not a function"

Step 3: Add helper functions to ConfigManager

In src/main/services/config/config-manager.ts, add after the DEFAULT_SETTINGS constant (around line 78):

/**
 * Check if value is a plain object
 */
function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

/**
 * Deep merge two objects, only updating fields present in target
 * Preserves all fields from source that are not in target
 */
function deepMerge<T>(source: T, target: Partial<T>): T {
  const result = { ...source }

  for (const key in target) {
    if (key in target) {
      const targetValue = target[key]
      const sourceValue = result[key]

      if (isObject(targetValue) && isObject(sourceValue)) {
        result[key] = deepMerge(sourceValue, targetValue)
      } else if (targetValue !== undefined) {
        result[key] = targetValue as T[Extract<keyof T, string>]
      }
    }
  }

  return result
}

/**
 * UI editable field whitelist
 * Fields that can be modified through the settings UI
 */
const UI_EDITABLE_FIELDS: string[] = [
  'erp.url',
  'erp.username',
  'erp.password'
  // Add more fields as UI expands
]

/**
 * Validate that settings only contain editable fields
 */
function validateEditableFields(settings: Partial<SettingsData>): {
  valid: boolean
  invalidFields: string[]
} {
  const invalidFields: string[] = []

  for (const [section, values] of Object.entries(settings)) {
    if (values && typeof values === 'object') {
      for (const field of Object.keys(values)) {
        const fieldPath = `${section}.${field}`
        if (!UI_EDITABLE_FIELDS.includes(fieldPath)) {
          invalidFields.push(fieldPath)
        }
      }
    }
  }

  return {
    valid: invalidFields.length === 0,
    invalidFields
  }
}

Step 4: Run test to verify it still fails (method not implemented yet)

Run: npm test -- tests/main/services/config/config-manager.test.ts

Expected: FAIL - "savePartialSettings is not a function"

Step 5: Commit

cd D:/Node/ERPAuto-settings-fix
git add src/main/services/config/config-manager.ts tests/main/services/config/config-manager.test.ts
git commit -m "feat: add deep merge and validation utility functions to ConfigManager"

Task 2: Add Backup and Restore Mechanism

Files:

  • Modify: src/main/services/config/config-manager.ts

Step 1: Write test for backup functionality

Add to tests/main/services/config/config-manager.test.ts:

describe('ConfigManager - backup and restore', () => {
  it('should create backup before saving', async () => {
    const manager = ConfigManager.getInstance()
    await manager.initialize()

    const backupSuccess = await manager['backupEnvFile']()

    expect(backupSuccess).toBe(true)

    // Check backup file exists
    const fs = await import('fs')
    const path = await import('path')
    const backupPath = path.resolve(process.cwd(), '.env.backup')

    expect(fs.existsSync(backupPath)).toBe(true)
  })

  it('should restore from backup when save fails', async () => {
    const manager = ConfigManager.getInstance()
    await manager.initialize()

    // Create initial state
    const initial = manager.getAllSettings()
    const originalUrl = initial.erp.url

    // Mock fs.writeFileSync to fail
    const fs = await import('fs')
    const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {
      throw new Error('Disk full')
    })

    const result = await manager.savePartialSettings({
      erp: { url: 'http://should-not-save.com' }
    })

    expect(result.success).toBe(false)

    // Restore should have happened
    const current = manager.getAllSettings()
    expect(current.erp.url).toBe(originalUrl)

    writeFileSyncSpy.mockRestore()
  })
})

Step 2: Run test to verify it fails

Run: npm test -- tests/main/services/config/config-manager.test.ts

Expected: FAIL - "backupEnvFile is not a function"

Step 3: Add backup path property and methods to ConfigManager class

In ConfigManager class, modify constructor (around line 88) to add backupPath:

export class ConfigManager {
  private static instance: ConfigManager | null = null
  private envPath: string
  private backupPath: string  // ADD THIS LINE
  private configCache: Map<string, string> = new Map()
  private initialized: boolean = false

  private constructor() {
    if (this.initialized) {
      return
    }
    this.envPath = path.resolve(__dirname, '../../.env')
    this.backupPath = path.resolve(__dirname, '../../.env.backup')  // ADD THIS LINE
    this.initialized = true
  }

Add private methods at the end of the class (before getInstance()):

  /**
   * Backup current .env file
   */
  private async backupEnvFile(): Promise<boolean> {
    try {
      if (fs.existsSync(this.envPath)) {
        fs.copyFileSync(this.envPath, this.backupPath)
        log.debug('Backup created', { path: this.backupPath })
        return true
      }
      return false
    } catch (error) {
      log.error('Failed to backup .env file', { error })
      return false
    }
  }

  /**
   * Restore .env file from backup
   */
  private async restoreBackup(): Promise<boolean> {
    try {
      if (fs.existsSync(this.backupPath)) {
        fs.copyFileSync(this.backupPath, this.envPath)
        await this.loadEnvFile()
        log.info('Restored from backup')
        return true
      }
      return false
    } catch (error) {
      log.error('Failed to restore backup', { error })
      return false
    }
  }

Step 4: Run test to verify it passes

Run: npm test -- tests/main/services/config/config-manager.test.ts

Expected: PASS

Step 5: Commit

cd D:/Node/ERPAuto-settings-fix
git add src/main/services/config/config-manager.ts tests/main/services/config/config-manager.test.ts
git commit -m "feat: add backup and restore mechanism to ConfigManager"

Task 3: Implement savePartialSettings Method

Files:

  • Modify: src/main/services/config/config-manager.ts

Step 1: Write comprehensive test for savePartialSettings

Add to tests/main/services/config/config-manager.test.ts:

describe('ConfigManager.savePartialSettings', () => {
  it('should save only specified fields and preserve others', async () => {
    const manager = ConfigManager.getInstance()
    await manager.initialize()

    // Setup initial state with multiple categories
    await manager.saveAllSettings({
      erp: {
        url: 'http://old.com',
        username: 'user1',
        password: 'pass1',
        headless: true,
        ignoreHttpsErrors: true,
        autoCloseBrowser: true
      },
      database: {
        dbType: 'mysql',
        server: '',
        mysqlHost: '192.168.1.1',
        mysqlPort: 3306,
        database: 'testdb',
        username: 'dbuser',
        password: ''
      },
      paths: { dataDir: '/old/path', defaultOutput: 'out.xlsx', validationOutput: 'val.xlsx' },
      extraction: {
        batchSize: 50,
        verbose: true,
        autoConvert: true,
        mergeBatches: true,
        enableDbPersistence: true
      },
      validation: {
        dataSource: 'database_full',
        batchSize: 1000,
        matchMode: 'exact',
        enableCrud: false,
        defaultManager: ''
      },
      ui: { fontFamily: 'Tahoma', fontSize: 14, productionIdInputWidth: 25 },
      execution: { dryRun: true }
    })

    // Update only ERP URL
    const result = await manager.savePartialSettings({
      erp: { url: 'http://new.com' }
    })

    expect(result.success).toBe(true)

    const current = manager.getAllSettings()

    // Verify updated field
    expect(current.erp.url).toBe('http://new.com')

    // Verify preserved ERP fields
    expect(current.erp.username).toBe('user1')
    expect(current.erp.password).toBe('pass1')

    // Verify preserved other categories
    expect(current.database.dbType).toBe('mysql')
    expect(current.database.mysqlHost).toBe('192.168.1.1')
    expect(current.paths.dataDir).toBe('/old/path')
    expect(current.extraction.batchSize).toBe(50)
    expect(current.ui.fontFamily).toBe('Tahoma')
  })

  it('should reject updates to non-whitelisted fields', async () => {
    const manager = ConfigManager.getInstance()
    await manager.initialize()

    const result = await manager.savePartialSettings({
      database: { dbType: 'postgres' }
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('不允许修改')
    expect(result.error).toContain('database.dbType')
  })

  it('should handle nested object updates correctly', async () => {
    const manager = ConfigManager.getInstance()
    await manager.initialize()

    await manager.saveAllSettings({
      erp: {
        url: 'http://test.com',
        username: 'u',
        password: 'p',
        headless: false,
        ignoreHttpsErrors: false,
        autoCloseBrowser: false
      },
      database: {
        dbType: 'mysql',
        server: '',
        mysqlHost: 'localhost',
        mysqlPort: 3306,
        database: 'db',
        username: 'user',
        password: ''
      },
      paths: { dataDir: '/data', defaultOutput: 'out.xlsx', validationOutput: 'val.xlsx' },
      extraction: {
        batchSize: 100,
        verbose: true,
        autoConvert: true,
        mergeBatches: true,
        enableDbPersistence: true
      },
      validation: {
        dataSource: 'database_full',
        batchSize: 2000,
        matchMode: 'substring',
        enableCrud: false,
        defaultManager: ''
      },
      ui: { fontFamily: 'Arial', fontSize: 12, productionIdInputWidth: 20 },
      execution: { dryRun: false }
    })

    // Update multiple ERP fields at once
    const result = await manager.savePartialSettings({
      erp: {
        url: 'http://updated.com',
        username: 'newuser',
        password: 'newpass'
      }
    })

    expect(result.success).toBe(true)

    const current = manager.getAllSettings()

    expect(current.erp.url).toBe('http://updated.com')
    expect(current.erp.username).toBe('newuser')
    expect(current.erp.password).toBe('newpass')
    expect(current.erp.headless).toBe(false) // preserved
  })

  it('should restore backup on save failure', async () => {
    const manager = ConfigManager.getInstance()
    await manager.initialize()

    const originalUrl = manager.getAllSettings().erp.url

    // Mock save to fail
    const originalSave = manager.save.bind(manager)
    vi.spyOn(manager, 'save').mockResolvedValueOnce(false)

    const result = await manager.savePartialSettings({
      erp: { url: 'http://should-not-apply.com' }
    })

    expect(result.success).toBe(false)
    expect(result.error).toContain('保存配置失败')

    // Verify rollback
    expect(manager.getAllSettings().erp.url).toBe(originalUrl)

    manager.save.mockRestore()
  })
})

Step 2: Run test to verify it fails

Run: npm test -- tests/main/services/config/config-manager.test.ts

Expected: FAIL - "savePartialSettings is not a function" or implementation incomplete

Step 3: Implement savePartialSettings method

Add this public method to ConfigManager class (after saveAllSettings method, around line 483):

  /**
   * Save partial settings (only update provided fields)
   * Preserves all existing fields not included in the update
   */
  public async savePartialSettings(
    settings: Partial<SettingsData>
  ): Promise<{ success: boolean; error?: string }> {
    try {
      // Step 1: Validate field whitelist
      const validation = validateEditableFields(settings)
      if (!validation.valid) {
        log.warn('Attempted to save non-editable fields', {
          invalidFields: validation.invalidFields
        })
        return {
          success: false,
          error: `包含不允许修改的字段:${validation.invalidFields.join(', ')}`
        }
      }

      // Step 2: Read current settings
      const currentSettings = this.getAllSettings()

      // Step 3: Deep merge
      const mergedSettings = deepMerge(currentSettings, settings)

      // Step 4: Backup and save
      const backupSuccess = await this.backupEnvFile()
      if (!backupSuccess) {
        log.warn('Failed to backup .env file, proceeding with caution')
      }

      const saveSuccess = await this.saveAllSettings(mergedSettings)

      if (!saveSuccess) {
        // Save failed, attempt restore
        await this.restoreBackup()
        return {
          success: false,
          error: '保存配置失败,已恢复原配置'
        }
      }

      log.info('Settings saved successfully', {
        updatedFields: Object.keys(settings)
      })

      return { success: true }
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error'
      log.error('Error in savePartialSettings', { error: message })
      await this.restoreBackup()
      return {
        success: false,
        error: `保存配置时发生错误:${message}`
      }
    }
  }

Step 4: Run test to verify it passes

Run: npm test -- tests/main/services/config/config-manager.test.ts

Expected: PASS

Step 5: Run typecheck

Run: cd D:/Node/ERPAuto-settings-fix && npm run typecheck:node

Expected: PASS (no type errors)

Step 6: Commit

cd D:/Node/ERPAuto-settings-fix
git add src/main/services/config/config-manager.ts tests/main/services/config/config-manager.test.ts
git commit -m "feat: implement savePartialSettings with validation and rollback"

Task 4: Update IPC Handler to Use Partial Save

Files:

  • Modify: src/main/ipc/settings-handler.ts

Step 1: Update settings:saveSettings handler

Find the settings:saveSettings handler (around line 83) and replace it:

/**
 * Save settings (updated to use partial save)
 */
ipcMain.handle(
  'settings:saveSettings',
  async (_event, settings: Partial<SettingsData>): Promise<SaveSettingsResult> => {
    try {
      log.info('Saving settings', {
        sections: Object.keys(settings)
      })

      // Use partial save method
      const result = await configManager.savePartialSettings(settings)

      if (result.success) {
        log.info('Settings saved successfully')
        return { success: true }
      } else {
        log.warn('Failed to save settings', {
          error: result.error
        })
        return {
          success: false,
          error: result.error || '保存设置失败'
        }
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error'
      log.error('Error saving settings', { error: message })
      return {
        success: false,
        error: `保存设置失败:${message}`
      }
    }
  }
)

Step 2: Run typecheck

Run: cd D:/Node/ERPAuto-settings-fix && npm run typecheck:node

Expected: PASS

Step 3: Commit

cd D:/Node/ERPAuto-settings-fix
git add src/main/ipc/settings-handler.ts
git commit -m "feat: update settings handler to use savePartialSettings"

Task 5: Update Frontend to Send Only Necessary Fields

Files:

  • Modify: src/renderer/src/pages/SettingsPage.tsx

Step 1: Update handleSaveSettings to send partial settings

Find the handleSaveSettings function (around line 61) and replace it:

const handleSaveSettings = async () => {
  try {
    // Only send UI-supported fields (double safety)
    const partialSettings = {
      erp: {
        url: settings.erp?.url,
        username: settings.erp?.username,
        password: settings.erp?.password
      }
    }

    const result = await window.electron.settings.saveSettings(partialSettings as any)

    if (result.success) {
      setIsModified(false)
      showMessage('success', '设置保存成功')
    } else {
      showMessage('error', result.error || '保存失败')
    }
  } catch (error) {
    showMessage('error', '保存设置时发生错误')
  }
}

Step 2: Run typecheck

Run: cd D:/Node/ERPAuto-settings-fix && npm run typecheck:web

Expected: PASS

Step 3: Commit

cd D:/Node/ERPAuto-settings-fix
git add src/renderer/src/pages/SettingsPage.tsx
git commit -m "feat: send only ERP fields from settings page (defensive programming)"

Task 6: Manual Testing and Verification

Files:

  • Manual test procedure

Step 1: Prepare test environment

cd D:/Node/ERPAuto-settings-fix

# Create a test .env file with all fields
cat > .env << 'EOF'
# Test Configuration
ERP_URL=http://original-test.com
ERP_USERNAME=testuser
ERP_PASSWORD=testpass
ERP_HEADLESS=true
ERP_IGNORE_HTTPS_ERRORS=true
ERP_AUTO_CLOSE_BROWSER=true

DB_TYPE=mysql
DB_NAME=testdb
DB_USERNAME=dbuser
DB_PASSWORD=dbpass
DB_MYSQL_HOST=192.168.100.50
DB_MYSQL_PORT=3306
DB_MYSQL_CHARSET=utf8mb4

PATH_DATA_DIR=/test/data
PATH_DEFAULT_OUTPUT=test.xlsx
PATH_VALIDATION_OUTPUT=validation.xlsx

EXTRACTION_BATCH_SIZE=100
EXTRACTION_VERBOSE=true
EXTRACTION_AUTO_CONVERT=true
EXTRACTION_MERGE_BATCHES=true
EXTRACTION_ENABLE_DB_PERSISTENCE=true

VALIDATION_DATA_SOURCE=database_full
VALIDATION_USE_DATABASE=true
VALIDATION_BATCH_SIZE=2000
VALIDATION_ENABLE_CRUD=false
VALIDATION_DEFAULT_MANAGER=admin
VALIDATION_MATCH_MODE=substring

UI_FONT_FAMILY=TestFont
UI_FONT_SIZE=11
UI_PRODUCTION_ID_INPUT_WIDTH=15

EXECUTION_DRYRUN=false
EOF

Step 2: Start development server

Run: npm run dev

Step 3: Navigate to settings page

  1. Login to the application
  2. Navigate to Settings page
  3. Modify only ERP URL to http://modified-test.com
  4. Click "保存并应用配置"

Step 4: Verify .env file preservation

Check .env file:

cat .env

Expected results:

  • ERP_URL should be http://modified-test.com (CHANGED)
  • DB_TYPE should still be mysql (PRESERVED)
  • VALIDATION_MATCH_MODE should still be substring (PRESERVED)
  • All other fields should remain unchanged

Step 5: Test whitelist validation

Add test code to temporarily send invalid field:

// In SettingsPage.tsx handleSaveSettings, temporarily add:
const partialSettings = {
  erp: {
    url: settings.erp?.url,
    username: settings.erp?.username,
    password: settings.erp?.password
  },
  database: { dbType: 'postgres' } // Should be rejected
}

Click save, should see error: "包含不允许修改的字段database.dbType"

Remove test code after verification.

Step 6: Test rollback mechanism

Simulate save failure by temporarily making .env read-only:

chmod -w .env  # On Linux/Mac
# or on Windows with file properties

Attempt to save settings, should see error: "保存配置失败,已恢复原配置"

Verify .env content unchanged, then restore write permissions:

chmod +w .env  # On Linux/Mac

Step 7: Document test results

Create test report:

cat > docs/test-reports/settings-partial-save-manual-test.md << 'EOF'
# Settings Partial Save - Manual Test Report

**Date:** 2026-03-03
**Tester:** [Your Name]
**Branch:** fix/settings-partial-save

## Test Results

### Test 1: Partial Field Preservation
- [x] Modified ERP URL only
- [x] Verified DB_TYPE unchanged
- [x] Verified all other fields preserved

### Test 2: Whitelist Validation
- [x] Attempted to modify database.dbType
- [x] Received error message about unauthorized field
- [x] No changes applied to .env

### Test 3: Backup and Rollback
- [x] Backup file created before save
- [x] Save failure triggered rollback
- [x] Original configuration restored

## Conclusion
All manual tests passed successfully.
EOF

Step 8: Commit

cd D:/Node/ERPAuto-settings-fix
git add docs/test-reports/settings-partial-save-manual-test.md
git commit -m "test: add manual test report for settings partial save"

Task 7: Update Documentation

Files:

  • Create: docs/settings-partial-save.md
  • Update: README.md (if applicable)

Step 1: Create feature documentation

Create docs/settings-partial-save.md:

# Settings Partial Save Feature

## Overview

The settings system now implements partial save functionality to prevent unintended overwrites of configuration values.

## How It Works

1. **Field Whitelist**: Only fields exposed in the UI can be modified
2. **Deep Merge**: Updates are merged with existing config, preserving unmodified fields
3. **Backup & Rollback**: Config is backed up before save; failures trigger automatic rollback

## Editable Fields

Currently editable via UI:

- `erp.url` - ERP system URL
- `erp.username` - ERP login username
- `erp.password` - ERP login password

## Adding New Editable Fields

To add a new field to the UI:

1. Add field to whitelist in `src/main/services/config/config-manager.ts`:

```typescript
const UI_EDITABLE_FIELDS: string[] = [
  'erp.url',
  'erp.username',
  'erp.password',
  'database.dbType' // Add new field here
]
```
  1. Add UI input in src/renderer/src/pages/SettingsPage.tsx
  2. Update handleSaveSettings to include the new field

API

savePartialSettings(settings: Partial)

Saves only the provided fields, preserving all existing configuration.

Returns: { success: boolean, error?: string }

Validation:

  • Checks whitelist before applying changes
  • Returns error for unauthorized fields

Error Handling

  • Unauthorized field: Returns error message listing invalid fields
  • Save failure: Automatically restores from backup
  • Backup failure: Logs warning, continues with save

Backup File

Location: .env.backup (in project root)

Created before every save operation. Used for rollback on failure.


**Step 2: Update CLAUDE.md if needed**

Add to "Development Commands" or "Architecture Overview" sections if there's relevant information about config management.

**Step 3: Commit**

```bash
cd D:/Node/ERPAuto-settings-fix
git add docs/settings-partial-save.md
git commit -m "docs: add settings partial save feature documentation"

Task 8: Final Verification and Cleanup

Files:

  • All modified files

Step 1: Run full test suite

Run: cd D:/Node/ERPAuto-settings-fix && npm test

Expected: All tests pass

Step 2: Run type checking

Run: npm run typecheck

Expected: No type errors

Step 3: Run linting

Run: npm run lint

Expected: No linting errors (or fix if present)

Step 4: Build verification

Run: npm run build

Expected: Build succeeds without errors

Step 5: Review all changes

cd D:/Node/ERPAuto-settings-fix
git diff dev --stat

Verify all changes are expected.

Step 6: Final commit

cd D:/Node/ERPAuto-settings-fix
git add -A
git commit -m "chore: final verification and cleanup for settings partial save feature"

Summary

This implementation plan fixes the settings save issue through:

  1. Deep merge utilities that preserve unmodified fields
  2. Field whitelist validation to prevent unauthorized changes
  3. Backup and rollback mechanism for safe saves
  4. Updated IPC handler with partial save support
  5. Frontend defensive programming (sends only necessary fields)
  6. Comprehensive unit and manual testing
  7. Complete documentation

Total estimated implementation time: 2-3 hours

Key files modified:

  • src/main/services/config/config-manager.ts (core logic)
  • src/main/ipc/settings-handler.ts (IPC layer)
  • src/renderer/src/pages/SettingsPage.tsx (frontend)
  • tests/main/services/config/config-manager.test.ts (tests)