packages/shared was never a real pnpm workspace package (no package.json); it was referenced only through the @shared TypeScript path alias. Relocate it under src/ via git mv (143 files, detected as pure renames).
Repoint the @shared alias and include globs to src/shared across electron.vite.config.ts, tsconfig.{json,node,web}.json and vitest.config.ts; update scripts/check-custom-exts.ts, scripts/update-languages.ts, the eslint.config.mjs generated-file globs, the data-classify generator output targets, .github/CODEOWNERS path rules, and CLAUDE.md/docs/source-comment references.
The @shared alias name is unchanged, so all 1403 @shared/* import sites resolve without modification. Verified with typecheck:node, typecheck:web and the full test suite (700 files, 9739 tests passing).
10 KiB
Boot Config Schema Guide
This guide explains how to add new boot config keys and covers the V1-to-V2 data migration pipeline.
When to Use BootConfig
BootConfig is for a very narrow set of configuration items. Before adding a key here, ask:
| Question | If Yes | If No |
|---|---|---|
| Must it load before the lifecycle system takes over? | BootConfig | Preference |
| Does it affect process-level behavior (Chromium flags, data directory)? | BootConfig | Preference |
Can it wait for the BeforeReady lifecycle phase? |
Preference | BootConfig |
| Can it be changed at runtime without restart? | Preference | BootConfig |
Rule of thumb: If the setting can wait until the lifecycle's BeforeReady phase, it belongs in Preference. BootConfig is only for settings that must be available before the lifecycle system even starts. Keep it minimal.
Key Naming Conventions
Boot config keys follow the same naming convention as preferences.
Format
namespace.key_name — at least 2 segments separated by dots.
Rules:
- Lowercase letters, numbers, and underscores only
- Pattern:
/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/ - Use dots for hierarchy, underscores for multi-word names
Examples
| Valid | Invalid | Reason |
|---|---|---|
app.disable_hardware_acceleration |
disableHardwareAcceleration |
Missing dot separator |
app.user_data_path |
App.userDataPath |
Uppercase, camelCase |
chromium.gpu_compositing |
gpu |
Single segment |
Adding a New Boot Config Key
Step 1: Add to Schema Interface
File: src/shared/data/bootConfig/bootConfigSchemas.ts
The schema interface and defaults live side by side. The current shape looks like this — follow the same pattern for any new key (matching naming convention, matching entry in DefaultBootConfig):
export interface BootConfigSchema {
'app.disable_hardware_acceleration': boolean
'app.user_data_path': Record<string, string>
}
export const DefaultBootConfig: BootConfigSchema = {
'app.disable_hardware_acceleration': false,
'app.user_data_path': {}
}
Note: If this file is currently auto-generated by the V1-to-V2 migration pipeline, add your key outside the
=== AUTO-GENERATED CONTENT ===markers, or coordinate with the migration toolchain (see V1 to V2 Data Migration below).
Step 2: Add Custom Types (if needed)
File: src/shared/data/bootConfig/bootConfigTypes.ts
For simple types (boolean, string, number), no changes needed — the type is inferred from the schema. For custom types, define them alongside BootConfigKey:
export type BootConfigKey = keyof BootConfigSchema
// Custom types if needed
export type GpuMode = 'auto' | 'disabled' | 'software'
Step 3: Use in Early Boot Code (if needed)
Only for settings that must take effect before lifecycle:
File: src/main/index.ts
import { bootConfigService } from '@main/data/bootConfig'
// Apply before app.whenReady()
if (bootConfigService.get('app.disable_hardware_acceleration')) {
app.disableHardwareAcceleration()
}
Step 4: Access from Renderer / Lifecycle Services
No additional wiring needed. The BootConfigPreferenceKeys mapped type automatically adds the BootConfig. prefix, making the key available through the unified preference API:
// Renderer — works immediately after adding to schema
const [disableHardwareAcceleration, setDisableHardwareAcceleration] = usePreference(
'BootConfig.app.disable_hardware_acceleration'
)
// Main process lifecycle service
const disableHardwareAcceleration = preferenceService.get('BootConfig.app.disable_hardware_acceleration')
For detailed usage of usePreference, see Preference Usage Guide.
V1 to V2 Data Migration
This section covers the migration toolchain used to move legacy data from V1 (Redux/ElectronStore/Dexie) into the V2 boot config system. This is not the regular path for adding new keys.
Overview
The v2-refactor-temp/tools/data-classify/ directory contains the code generation pipeline for migrating legacy data into V2 systems. classification.json is the single source of truth that classifies every legacy key into its target system (Preference, BootConfig, Cache, or DataApi).
How It Works
- Each item in
classification.jsonwith"category": "bootConfig"maps a legacy key to a boot config key - The generator reads these classifications and produces:
src/shared/data/bootConfig/bootConfigSchemas.ts— schema interface and defaultssrc/main/data/migration/v2/migrators/mappings/BootConfigMappings.ts— legacy-to-new key mappings
- At migration time,
BootConfigMigratorreads values from legacy sources (Redux, ElectronStore, Dexie settings, localStorage, and the legacy home config file) and writes them tobootConfigService
Migration Sources
| Source | Accessor | Example |
|---|---|---|
| Redux Store | ReduxStateReader with category + dot-path |
settings.disableHardwareAcceleration |
| ElectronStore | ConfigManager.get(key) |
Direct key lookup |
| Dexie settings | Key-value table | Direct key lookup |
| localStorage | localStorage.getItem(key) |
Direct key lookup |
| Legacy home config file | LegacyHomeConfigReader |
~/.cherrystudio/config/config.json (appDataPath field only) |
Config-file source mappings are manually maintained. The
data-classifytoolchain'sclassification.jsondoesn't model config-file sources yet. In two places, a small hand-maintained list complements the classification-driven pipeline:
- Schema keys:
MANUAL_BOOT_CONFIG_ITEMSat the top ofv2-refactor-temp/tools/data-classify/scripts/generate-boot-config.js— these items are merged with the classification-derived items and emitted intobootConfigSchemas.tsas part of the normal auto-generated output. The resulting schema file is fully auto-generated (no manual sections).- Mappings: inline
configFileMappingsinsideBootConfigMigrator.loadMigrationItems()— a smallReadonlyArray<{ originalKey: string; targetKey: BootConfigKey }>whoseBootConfigKeyannotation is the regen safety net: if the schema losesapp.user_data_path, this array fails to compile at its declaration site.To add a new config-file-sourced key in the future: add an entry to
MANUAL_BOOT_CONFIG_ITEMSin the generator, add the matching entry toBootConfigMigrator.loadMigrationItems()'sconfigFileMappings, and runnpm run generate.
Adding a Migration Mapping
To migrate a legacy key to boot config:
- Add or update the item in
classification.json:
{
"originalKey": "disableHardwareAcceleration",
"source": "redux",
"category": "bootConfig",
"status": "classified",
"targetKey": "app.disable_hardware_acceleration",
"targetType": "boolean",
"defaultValue": false,
"reduxCategory": "settings"
}
- Regenerate mappings:
cd v2-refactor-temp/tools/data-classify && npm run generate
- Verify the generated output in
BootConfigMappings.ts
Current Mappings
| Legacy Source | Legacy Key | Target Key |
|---|---|---|
Redux (settings) |
disableHardwareAcceleration |
app.disable_hardware_acceleration |
Config file (~/.cherrystudio/config/config.json) |
appDataPath |
app.user_data_path |
Known Limitation: AppImage / Windows Portable Executable Path
The v1 ~/.cherrystudio/config/config.json stores appDataPath as an array of { executablePath, dataPath } entries keyed by executable path. On AppImage Linux builds and Windows portable builds, src/main/utils/init.ts:51-60 writes a special executablePath that differs from app.getPath('exe'):
- AppImage:
path.dirname(process.env.APPIMAGE) + '/cherry-studio.appimage' - Windows portable:
process.env.PORTABLE_EXECUTABLE_DIR + '/cherry-studio-portable.exe'
LegacyHomeConfigReader does NOT reproduce this normalization — array entries are migrated verbatim with their original executablePath key, and the legacy-string fallback uses the raw app.getPath('exe'). This is harmless in the current PR because nothing yet reads app.user_data_path. But the follow-up PR that rewires initAppDataDir() to consume it MUST normalize the exe path using the same logic in src/main/utils/init.ts:51-60, otherwise migrated records under AppImage/portable will never match the lookup key.
File Structure
| File | Purpose |
|---|---|
src/shared/data/bootConfig/bootConfigSchemas.ts |
Schema interface and default values |
src/shared/data/bootConfig/bootConfigTypes.ts |
BootConfigKey type, BootConfigPreferenceKeys mapped type |
src/main/data/bootConfig/BootConfigService.ts |
Service implementation |
src/main/data/bootConfig/types.ts |
BootConfigLoadError type |
v2-refactor-temp/tools/data-classify/data/classification.json |
Migration source of truth |
v2-refactor-temp/tools/data-classify/scripts/generate-boot-config.js |
Schema generator (migration) |
src/main/data/migration/v2/migrators/BootConfigMigrator.ts |
Migration executor |
src/main/data/migration/v2/migrators/mappings/BootConfigMappings.ts |
Auto-generated migration mappings |
Best Practices
- Keep BootConfig minimal — most settings belong in Preference. Only use BootConfig for settings that must load before the lifecycle system takes over.
- Provide sensible defaults — BootConfigService uses defaults on first launch (missing file). A missing default means the key is unusable on first launch.
- Follow naming conventions — use the same
namespace.key_namepattern as preferences for consistency. - Process-level settings require restart — document this in the UI when exposing boot config settings to users.
Related Documentation
- Boot Config Overview - Architecture and timing
- Preference Schema Guide - Adding preference keys (non-boot)
- Preference Usage Guide -
usePreferencehook and service API - V2 Migration Guide - Full migration system documentation