Files
CherryHQ-cherry-studio/docs/references/data/preference-usage.md
fullex 9b451b87a9 refactor(paths): add @application path alias for main/core/application
Align with the existing @logger alias convention by introducing
@application as a short alias for src/main/core/application. This
reduces import verbosity across ~130 main-process files while keeping
4 intentional sub-path imports (@main/core/application/Application)
unchanged to preserve the vi.mock bypass mechanism in tests.

Configured in tsconfig.node.json and electron.vite.config.ts; Vitest
inherits the alias automatically.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-11 22:06:00 -07:00

6.7 KiB

Preference Usage Guide

This guide covers how to use the Preference system in React components and services.

React Hooks

usePreference (Single Preference)

import { usePreference } from '@data/hooks/usePreference'

// Basic usage - optimistic updates (default)
const [theme, setTheme] = usePreference('app.theme.mode')

// Update the value
await setTheme('dark')

// With pessimistic updates (wait for confirmation)
const [apiKey, setApiKey] = usePreference('api.key', { optimistic: false })

usePreferences (Multiple Preferences)

import { usePreferences } from '@data/hooks/usePreference'

// Read multiple preferences at once
const { theme, language, fontSize } = usePreferences([
  'app.theme.mode',
  'app.language',
  'chat.message.font_size'
])

Update Strategies

Optimistic Updates (Default)

UI updates immediately, then syncs to database. Automatic rollback on failure.

const [theme, setTheme] = usePreference('app.theme.mode')

const handleThemeChange = async (newTheme: string) => {
  try {
    await setTheme(newTheme) // UI updates immediately
  } catch (error) {
    // UI automatically rolls back
    console.error('Theme update failed:', error)
  }
}

Best for:

  • Frequent changes (theme, font size)
  • Non-critical settings
  • Better perceived performance

Pessimistic Updates

Waits for database confirmation before updating UI.

const [apiKey, setApiKey] = usePreference('api.key', { optimistic: false })

const handleApiKeyChange = async (newKey: string) => {
  try {
    await setApiKey(newKey) // Waits for DB confirmation
    toast.success('API key saved')
  } catch (error) {
    toast.error('Failed to save API key')
  }
}

Best for:

  • Security-sensitive settings (API keys, passwords)
  • Settings that affect external services
  • When confirmation feedback is important

PreferenceService Direct Usage

For non-React code or batch operations.

Renderer Process

import { preferenceService } from '@data/PreferenceService'

// Get single preference
const theme = await preferenceService.get('app.theme.mode')

// Get multiple preferences
const settings = await preferenceService.getMultiple([
  'app.theme.mode',
  'app.language'
])
// Returns: { 'app.theme.mode': 'dark', 'app.language': 'en' }

// Get with default value
const fontSize = await preferenceService.get('chat.message.font_size') ?? 14

Set Preferences

// Set single preference (optimistic by default)
await preferenceService.set('app.theme.mode', 'dark')

// Set with pessimistic update
await preferenceService.set('api.key', 'secret', { optimistic: false })

// Set multiple preferences at once
await preferenceService.setMultiple({
  'app.theme.mode': 'dark',
  'app.language': 'en',
  'chat.message.font_size': 16
})

Subscribe to Changes

// Subscribe to preference changes (useful in services)
const unsubscribe = preferenceService.subscribe('app.theme.mode', (newValue) => {
  logger.info('Theme changed to:', { theme: newValue })
})

// Cleanup when done
unsubscribe()

Main Process

In the main process, PreferenceService is lifecycle-managed. Access it via application.get():

import { application } from '@application'

const preferenceService = application.get('PreferenceService')

// Get/set preferences
const theme = preferenceService.get('app.theme.mode')
preferenceService.set('app.theme.mode', 'dark')

// Subscribe to changes
const unsubscribe = preferenceService.subscribeChange('app.theme.mode', (newValue) => {
  logger.info('Theme changed to:', { theme: newValue })
})

Note: Do NOT import PreferenceService directly in main process code. The renderer @data/PreferenceService is a separate singleton for the renderer process only.

Common Patterns

Settings Form

function SettingsForm() {
  const [theme, setTheme] = usePreference('app.theme.mode')
  const [language, setLanguage] = usePreference('app.language')
  const [fontSize, setFontSize] = usePreference('chat.message.font_size')

  return (
    <form>
      <select value={theme} onChange={e => setTheme(e.target.value)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
        <option value="system">System</option>
      </select>

      <select value={language} onChange={e => setLanguage(e.target.value)}>
        <option value="en">English</option>
        <option value="zh">中文</option>
      </select>

      <input
        type="number"
        value={fontSize}
        onChange={e => setFontSize(Number(e.target.value))}
        min={12}
        max={24}
      />
    </form>
  )
}

Feature Toggle

function ChatMessage({ message }) {
  const [showTimestamp] = usePreference('chat.display.show_timestamp')

  return (
    <div className="message">
      <p>{message.content}</p>
      {showTimestamp && <span className="timestamp">{message.createdAt}</span>}
    </div>
  )
}

Conditional Rendering Based on Settings

function App() {
  const [theme] = usePreference('app.theme.mode')
  const [sidebarPosition] = usePreference('app.sidebar.position')

  return (
    <div className={`app theme-${theme}`}>
      {sidebarPosition === 'left' && <Sidebar />}
      <MainContent />
      {sidebarPosition === 'right' && <Sidebar />}
    </div>
  )
}

Batch Settings Update

async function resetToDefaults() {
  await preferenceService.setMultiple({
    'app.theme.mode': 'system',
    'app.language': 'en',
    'chat.message.font_size': 14,
    'chat.display.show_timestamp': true
  })
}

Adding New Preference Keys

For a complete guide on adding new preferences, including naming conventions and design principles, see Preference Schema Guide.

Quick summary:

  1. Add custom types to preferenceTypes.ts (if needed)
  2. Add key to PreferenceSchemas interface in preferenceSchemas.ts
  3. Add default value to DefaultPreferences
  4. Use in code with full type safety

Best Practices

  1. Choose update strategy wisely: Optimistic for UX, pessimistic for critical settings
  2. Batch related updates: Use setMultiple when changing multiple related settings
  3. Provide sensible defaults: All preferences should have default values
  4. Keep values atomic: One preference = one logical setting
  5. Use consistent naming: Follow domain.feature.setting pattern

Preference vs Other Storage

Scenario Use
User theme preference usePreference('app.theme.mode')
Window position usePersistCache (can be lost without impact)
API key usePreference with pessimistic updates
Search history usePersistCache (nice to have)
Conversation history DataApiService (business data)