diff --git a/code-rs/config/src/types.rs b/code-rs/config/src/types.rs index 39fd0a442f5..d79b2a653ed 100644 --- a/code-rs/config/src/types.rs +++ b/code-rs/config/src/types.rs @@ -25,6 +25,7 @@ use std::collections::HashMap; use std::fmt; use schemars::JsonSchema; +use serde::Deserializer; use serde::Deserialize; use serde::Serialize; @@ -664,7 +665,7 @@ pub struct Tui { /// /// Using alternate screen provides a cleaner fullscreen experience but prevents /// scrollback in terminal multiplexers like Zellij that follow the xterm spec. - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_alt_screen_mode")] pub alternate_screen: AltScreenMode, /// Ordered list of status line item identifiers. @@ -692,7 +693,7 @@ pub struct Tui { /// /// When set, overrides automatic light/dark theme detection. /// Use `/theme` in the TUI or see `$CODEX_HOME/themes` for custom themes. - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_tui_theme")] pub theme: Option, /// Preferred layout for resume/fork session picker results. @@ -740,6 +741,41 @@ pub struct ExternalConfigMigrationPrompts { pub project_last_prompted_at: BTreeMap, } +fn deserialize_alt_screen_mode<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum AltScreenCompat { + Mode(AltScreenMode), + LegacyBool(bool), + } + + match AltScreenCompat::deserialize(deserializer)? { + AltScreenCompat::Mode(mode) => Ok(mode), + AltScreenCompat::LegacyBool(true) => Ok(AltScreenMode::Always), + AltScreenCompat::LegacyBool(false) => Ok(AltScreenMode::Never), + } +} + +fn deserialize_tui_theme<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum TuiThemeCompat { + Name(String), + LegacyTable { name: String }, + } + + Ok(Option::::deserialize(deserializer)?.map(|theme| match theme { + TuiThemeCompat::Name(name) => name, + TuiThemeCompat::LegacyTable { name } => name, + })) +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct Notice { diff --git a/code-rs/core/src/config/config_tests.rs b/code-rs/core/src/config/config_tests.rs index 25f6697c7f5..cb2ecca6bd2 100644 --- a/code-rs/core/src/config/config_tests.rs +++ b/code-rs/core/src/config/config_tests.rs @@ -624,6 +624,53 @@ terminal_resize_reflow_max_rows = 9000 ); } +#[test] +fn config_toml_deserializes_legacy_tui_alternate_screen_bool() { + let enabled_toml = r#" +[tui] +alternate_screen = true +"#; + let enabled_cfg: ConfigToml = toml::from_str(enabled_toml) + .expect("TOML deserialization should accept legacy alternate_screen bool"); + assert_eq!( + enabled_cfg + .tui + .expect("tui config should deserialize") + .alternate_screen, + AltScreenMode::Always + ); + + let disabled_toml = r#" +[tui] +alternate_screen = false +"#; + let disabled_cfg: ConfigToml = toml::from_str(disabled_toml) + .expect("TOML deserialization should accept legacy alternate_screen bool"); + assert_eq!( + disabled_cfg + .tui + .expect("tui config should deserialize") + .alternate_screen, + AltScreenMode::Never + ); +} + +#[test] +fn config_toml_deserializes_tui_alternate_screen_string_mode() { + let toml = r#" +[tui] +alternate_screen = "always" +"#; + let cfg: ConfigToml = toml::from_str(toml) + .expect("TOML deserialization should accept alternate_screen string mode"); + assert_eq!( + cfg.tui + .expect("tui config should deserialize") + .alternate_screen, + AltScreenMode::Always + ); +} + #[tokio::test] async fn runtime_config_defaults_model_availability_nux() { let cfg = Config::load_from_base_config_with_overrides( @@ -2155,6 +2202,21 @@ theme = "dracula" ); } +#[test] +fn tui_theme_deserializes_legacy_table_from_toml() { + let cfg = r#" +[tui.theme] +name = "dark-oled-black-pro" +is_dark = true +"#; + let parsed = toml::from_str::(cfg) + .expect("TOML deserialization should accept legacy TUI theme table"); + assert_eq!( + parsed.tui.as_ref().and_then(|t| t.theme.as_deref()), + Some("dark-oled-black-pro"), + ); +} + #[test] fn tui_theme_defaults_to_none() { let cfg = r#" diff --git a/code-rs/features/src/lib.rs b/code-rs/features/src/lib.rs index 6c8ce49894d..d40ac5447ae 100644 --- a/code-rs/features/src/lib.rs +++ b/code-rs/features/src/lib.rs @@ -422,6 +422,9 @@ impl Features { "image_detail_original" => { continue; } + "skills" => { + continue; + } "use_legacy_landlock" => { self.record_legacy_usage_force( "features.use_legacy_landlock", diff --git a/code-rs/features/src/tests.rs b/code-rs/features/src/tests.rs index 5464fa7a61a..c85a09ab208 100644 --- a/code-rs/features/src/tests.rs +++ b/code-rs/features/src/tests.rs @@ -66,6 +66,14 @@ fn image_detail_original_is_removed_and_disabled_by_default() { assert_eq!(Feature::ImageDetailOriginal.default_enabled(), false); } +#[test] +fn skills_feature_key_is_accepted_as_removed_noop() { + let mut features = Features::with_defaults(); + features.apply_map(&BTreeMap::from([("skills".to_string(), true)])); + + assert_eq!(features.enabled(Feature::ShellTool), true); +} + #[test] fn code_mode_only_requires_code_mode() { let mut features = Features::with_defaults();