diff --git a/crates/services/src/services/config/mod.rs b/crates/services/src/services/config/mod.rs index 8bb52450..e3de32c2 100644 --- a/crates/services/src/services/config/mod.rs +++ b/crates/services/src/services/config/mod.rs @@ -14,14 +14,14 @@ pub enum ConfigError { ValidationError(String), } -pub type Config = versions::v6::Config; -pub type NotificationConfig = versions::v6::NotificationConfig; -pub type EditorConfig = versions::v6::EditorConfig; -pub type ThemeMode = versions::v6::ThemeMode; -pub type SoundFile = versions::v6::SoundFile; -pub type EditorType = versions::v6::EditorType; -pub type GitHubConfig = versions::v6::GitHubConfig; -pub type UiLanguage = versions::v6::UiLanguage; +pub type Config = versions::v7::Config; +pub type NotificationConfig = versions::v7::NotificationConfig; +pub type EditorConfig = versions::v7::EditorConfig; +pub type ThemeMode = versions::v7::ThemeMode; +pub type SoundFile = versions::v7::SoundFile; +pub type EditorType = versions::v7::EditorType; +pub type GitHubConfig = versions::v7::GitHubConfig; +pub type UiLanguage = versions::v7::UiLanguage; /// Will always return config, trying old schemas or eventually returning default pub async fn load_config_from_file(config_path: &PathBuf) -> Config { diff --git a/crates/services/src/services/config/versions/mod.rs b/crates/services/src/services/config/versions/mod.rs index 5af9879e..9e0985bf 100644 --- a/crates/services/src/services/config/versions/mod.rs +++ b/crates/services/src/services/config/versions/mod.rs @@ -4,3 +4,4 @@ pub(super) mod v3; pub(super) mod v4; pub(super) mod v5; pub(super) mod v6; +pub(super) mod v7; diff --git a/crates/services/src/services/config/versions/v7.rs b/crates/services/src/services/config/versions/v7.rs new file mode 100644 index 00000000..a9a01dd1 --- /dev/null +++ b/crates/services/src/services/config/versions/v7.rs @@ -0,0 +1,131 @@ +use anyhow::Error; +use executors::{executors::BaseCodingAgent, profile::ExecutorProfileId}; +use serde::{Deserialize, Serialize}; +use strum_macros::EnumString; +use ts_rs::TS; +pub use v6::{EditorConfig, EditorType, GitHubConfig, NotificationConfig, SoundFile, UiLanguage}; + +use crate::services::config::versions::v6; + +#[derive(Debug, Clone, Serialize, Deserialize, TS, EnumString)] +#[ts(use_ts_enum)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +pub enum ThemeMode { + Light, + Dark, + System, +} + +#[derive(Clone, Debug, Serialize, Deserialize, TS)] +pub struct Config { + pub config_version: String, + pub theme: ThemeMode, + pub executor_profile: ExecutorProfileId, + pub disclaimer_acknowledged: bool, + pub onboarding_acknowledged: bool, + pub github_login_acknowledged: bool, + pub telemetry_acknowledged: bool, + pub notifications: NotificationConfig, + pub editor: EditorConfig, + pub github: GitHubConfig, + pub analytics_enabled: Option, + pub workspace_dir: Option, + pub last_app_version: Option, + pub show_release_notes: bool, + #[serde(default)] + pub language: UiLanguage, +} + +impl Config { + pub fn from_previous_version(raw_config: &str) -> Result { + let old_config = match serde_json::from_str::(raw_config) { + Ok(cfg) => cfg, + Err(e) => { + tracing::error!("❌ Failed to parse config: {}", e); + tracing::error!(" at line {}, column {}", e.line(), e.column()); + return Err(e.into()); + } + }; + + // Map old theme modes to new simplified theme modes + let theme = match old_config.theme { + v6::ThemeMode::Light => ThemeMode::Light, + v6::ThemeMode::Dark => ThemeMode::Dark, + v6::ThemeMode::System => ThemeMode::System, + // Map all color themes to System (respects user's OS preference) + v6::ThemeMode::Purple + | v6::ThemeMode::Green + | v6::ThemeMode::Blue + | v6::ThemeMode::Orange + | v6::ThemeMode::Red => { + tracing::info!( + "Migrating color theme {:?} to System theme", + old_config.theme + ); + ThemeMode::System + } + }; + + Ok(Self { + config_version: "v7".to_string(), + theme, + executor_profile: old_config.executor_profile, + disclaimer_acknowledged: old_config.disclaimer_acknowledged, + onboarding_acknowledged: old_config.onboarding_acknowledged, + github_login_acknowledged: old_config.github_login_acknowledged, + telemetry_acknowledged: old_config.telemetry_acknowledged, + notifications: old_config.notifications, + editor: old_config.editor, + github: old_config.github, + analytics_enabled: old_config.analytics_enabled, + workspace_dir: old_config.workspace_dir, + last_app_version: old_config.last_app_version, + show_release_notes: old_config.show_release_notes, + language: old_config.language, + }) + } +} + +impl From for Config { + fn from(raw_config: String) -> Self { + if let Ok(config) = serde_json::from_str::(&raw_config) + && config.config_version == "v7" + { + return config; + } + + match Self::from_previous_version(&raw_config) { + Ok(config) => { + tracing::info!("Config upgraded to v7"); + config + } + Err(e) => { + tracing::warn!("Config migration failed: {}, using default", e); + Self::default() + } + } + } +} + +impl Default for Config { + fn default() -> Self { + Self { + config_version: "v7".to_string(), + theme: ThemeMode::System, + executor_profile: ExecutorProfileId::new(BaseCodingAgent::ClaudeCode), + disclaimer_acknowledged: false, + onboarding_acknowledged: false, + github_login_acknowledged: false, + telemetry_acknowledged: false, + notifications: NotificationConfig::default(), + editor: EditorConfig::default(), + github: GitHubConfig::default(), + analytics_enabled: None, + workspace_dir: None, + last_app_version: None, + show_release_notes: false, + language: UiLanguage::default(), + } + } +} diff --git a/frontend/src/components/theme-provider.tsx b/frontend/src/components/theme-provider.tsx index 9b159a7b..3b3f8d6d 100644 --- a/frontend/src/components/theme-provider.tsx +++ b/frontend/src/components/theme-provider.tsx @@ -33,15 +33,7 @@ export function ThemeProvider({ useEffect(() => { const root = window.document.documentElement; - root.classList.remove( - 'light', - 'dark', - 'purple', - 'green', - 'blue', - 'orange', - 'red' - ); + root.classList.remove('light', 'dark'); if (theme === ThemeMode.SYSTEM) { const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') diff --git a/frontend/src/utils/theme.ts b/frontend/src/utils/theme.ts index 5dbf8414..3ce43a25 100644 --- a/frontend/src/utils/theme.ts +++ b/frontend/src/utils/theme.ts @@ -18,6 +18,6 @@ export function getActualTheme( : 'light'; } - // All other themes (DARK, PURPLE, GREEN, BLUE, ORANGE, RED) have dark backgrounds + // ThemeMode.DARK return 'dark'; } diff --git a/shared/types.ts b/shared/types.ts index 6a211b0f..b1768ecd 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -90,7 +90,7 @@ export type Config = { config_version: string, theme: ThemeMode, executor_profil export type NotificationConfig = { sound_enabled: boolean, push_enabled: boolean, sound_file: SoundFile, }; -export enum ThemeMode { LIGHT = "LIGHT", DARK = "DARK", SYSTEM = "SYSTEM", PURPLE = "PURPLE", GREEN = "GREEN", BLUE = "BLUE", ORANGE = "ORANGE", RED = "RED" } +export enum ThemeMode { LIGHT = "LIGHT", DARK = "DARK", SYSTEM = "SYSTEM" } export type EditorConfig = { editor_type: EditorType, custom_command: string | null, };