diff --git a/crates/tui/src/settings.rs b/crates/tui/src/settings.rs index d33aab64..e9af2359 100644 --- a/crates/tui/src/settings.rs +++ b/crates/tui/src/settings.rs @@ -1,9 +1,9 @@ //! Settings system - Persistent user preferences //! -//! Settings are stored at ~/.config/deepseek/settings.toml +//! Settings are stored at ~/.codewhale/settings.toml, with legacy fallbacks. //! //! TUI-specific preferences (theme, keybinds, font_size) that survive project -//! switches are stored separately at ~/.deepseek/tui.toml. See [`TuiPrefs`]. +//! switches are stored separately in tui.toml. See [`TuiPrefs`]. use std::path::PathBuf; @@ -14,6 +14,8 @@ use crate::config::{expand_path, normalize_model_name}; use crate::localization::normalize_configured_locale; use crate::palette::{normalize_hex_rgb_color, normalize_theme_name}; +const SETTINGS_FILE_NAME: &str = "settings.toml"; + // ============================================================================ // TuiPrefs — ~/.deepseek/tui.toml // ============================================================================ @@ -347,15 +349,38 @@ impl Settings { if !config_path.is_empty() { let p = expand_path(config_path); if let Some(parent) = p.parent() { - return Ok(parent.join("settings.toml")); + return Ok(parent.join(SETTINGS_FILE_NAME)); } } } - let config_dir = dirs::config_dir() + let primary = codewhale_config::codewhale_home() + .ok() + .map(|home| home.join(SETTINGS_FILE_NAME)); + if let Some(path) = primary.as_ref() + && path.exists() + { + return Ok(path.clone()); + } + + let legacy_home = codewhale_config::legacy_deepseek_home() + .ok() + .map(|home| home.join(SETTINGS_FILE_NAME)); + if let Some(path) = legacy_home + && path.exists() + { + return Ok(path); + } + + let legacy_config_dir = dirs::config_dir() .context("Failed to resolve config directory: not found.")? - .join("deepseek"); - Ok(config_dir.join("settings.toml")) + .join("deepseek") + .join(SETTINGS_FILE_NAME); + if legacy_config_dir.exists() { + return Ok(legacy_config_dir); + } + + Ok(primary.unwrap_or(legacy_config_dir)) } /// Load settings from disk, or return defaults if not found @@ -471,8 +496,8 @@ impl Settings { // // Only flip `auto` to `off`; respect an explicit `"on"` so users // who upgrade Ptyxis or want to confirm the fix landed upstream - // can override the heuristic from `~/.config/deepseek/settings.toml` - // or `/set synchronized_output on`. + // can override the heuristic from the persisted settings.toml or + // `/set synchronized_output on`. if self.synchronized_output.eq_ignore_ascii_case("auto") && detected_ptyxis_terminal() { self.synchronized_output = "off".to_string(); } @@ -2135,6 +2160,92 @@ mod tests { crate::test_support::lock_test_env() } + struct EnvVarRestore { + key: &'static str, + previous: Option, + } + + impl EnvVarRestore { + fn set(key: &'static str, value: impl AsRef) -> Self { + let previous = std::env::var_os(key); + // SAFETY: tests using this helper hold config_path_test_guard. + unsafe { + std::env::set_var(key, value); + } + Self { key, previous } + } + + fn remove(key: &'static str) -> Self { + let previous = std::env::var_os(key); + // SAFETY: tests using this helper hold config_path_test_guard. + unsafe { + std::env::remove_var(key); + } + Self { key, previous } + } + } + + impl Drop for EnvVarRestore { + fn drop(&mut self) { + // SAFETY: tests using this helper hold config_path_test_guard. + unsafe { + match &self.previous { + Some(value) => std::env::set_var(self.key, value), + None => std::env::remove_var(self.key), + } + } + } + } + + #[test] + fn settings_path_defaults_to_codewhale_home_for_new_writes() { + let _g = config_path_test_guard(); + let tmp = tempfile::tempdir().expect("tempdir"); + let _config_override = EnvVarRestore::remove("DEEPSEEK_CONFIG_PATH"); + let _codewhale_home = EnvVarRestore::set("CODEWHALE_HOME", tmp.path().join(".codewhale")); + let _home = EnvVarRestore::set("HOME", tmp.path()); + + let got = Settings::path().expect("settings path"); + + assert_eq!(got, tmp.path().join(".codewhale").join("settings.toml")); + } + + #[test] + fn settings_path_reads_legacy_deepseek_home_when_present() { + let _g = config_path_test_guard(); + let tmp = tempfile::tempdir().expect("tempdir"); + let legacy_dir = tmp.path().join(".deepseek"); + std::fs::create_dir_all(&legacy_dir).expect("legacy dir"); + std::fs::write(legacy_dir.join("settings.toml"), "low_motion = true\n") + .expect("legacy settings"); + let _config_override = EnvVarRestore::remove("DEEPSEEK_CONFIG_PATH"); + let _codewhale_home = EnvVarRestore::set("CODEWHALE_HOME", tmp.path().join(".codewhale")); + let _home = EnvVarRestore::set("HOME", tmp.path()); + + let got = Settings::path().expect("settings path"); + + assert_eq!(got, legacy_dir.join("settings.toml")); + } + + #[test] + fn settings_path_keeps_platform_config_dir_as_last_legacy_fallback() { + let _g = config_path_test_guard(); + let tmp = tempfile::tempdir().expect("tempdir"); + let _config_override = EnvVarRestore::remove("DEEPSEEK_CONFIG_PATH"); + let _codewhale_home = EnvVarRestore::set("CODEWHALE_HOME", tmp.path().join(".codewhale")); + let _home = EnvVarRestore::set("HOME", tmp.path()); + + let config_dir = dirs::config_dir().expect("config dir"); + let legacy_settings = config_dir.join("deepseek").join("settings.toml"); + std::fs::create_dir_all(legacy_settings.parent().expect("parent")) + .expect("legacy config dir"); + std::fs::write(&legacy_settings, "low_motion = true\n").expect("legacy settings"); + + let got = Settings::path().expect("settings path"); + + assert_eq!(got, legacy_settings); + } + #[test] fn tui_prefs_defaults_are_dark_theme_zero_font() { let prefs = TuiPrefs::default(); diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index d907e7d7..8ee44f61 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -50,7 +50,7 @@ pub enum OnboardingState { Welcome, /// Pick the UI locale before any other config decisions (#566). /// Defaults to auto-detection from `LC_ALL` / `LANG`; explicit picks - /// land in `~/.deepseek/settings.toml` via `Settings::set("locale", …)`. + /// land in the persisted settings.toml via `Settings::set("locale", …)`. Language, ApiKey, TrustDirectory, @@ -2156,7 +2156,7 @@ impl App { } /// Apply a locale tag selected from the onboarding language picker (#566). - /// Persists the value to `~/.deepseek/settings.toml` and immediately + /// Persists the value to settings.toml and immediately /// re-resolves `ui_locale` so the rest of onboarding renders in the new /// language. `App` doesn't keep `Settings` resident — it loads on entry /// and rewrites on exit, mirroring the pattern used by the `/config` @@ -2170,7 +2170,7 @@ impl App { Ok(()) } - /// Locale tag currently persisted in `~/.deepseek/settings.toml` (or + /// Locale tag currently persisted in settings.toml (or /// `"auto"` when no settings file exists). Used by the onboarding /// language picker to highlight the current selection without `App` /// having to keep `Settings` resident. diff --git a/crates/tui/src/tui/model_picker.rs b/crates/tui/src/tui/model_picker.rs index a6cc22f9..7d1af66b 100644 --- a/crates/tui/src/tui/model_picker.rs +++ b/crates/tui/src/tui/model_picker.rs @@ -543,8 +543,7 @@ mod tests { initial_input: None, }; let mut app = App::new(options, &Config::default()); - // App::new merges in `~/.config/deepseek/settings.toml` / - // `Application Support/deepseek/settings.toml`, which can override + // App::new merges in the user's persisted settings.toml, which can override // the model, effort, and provider with whatever the developer // happens to have saved. Pin all three back to known values so // the picker tests below exercise the picker logic, not the diff --git a/docs/ACCESSIBILITY.md b/docs/ACCESSIBILITY.md index cdb2382d..036abd7f 100644 --- a/docs/ACCESSIBILITY.md +++ b/docs/ACCESSIBILITY.md @@ -46,7 +46,9 @@ The same toggles are reachable from the command palette: * `/settings set calm_mode on` * `/settings set status_indicator off` -Settings written this way persist to `~/.config/deepseek/settings.toml`. +Settings written this way persist to `~/.codewhale/settings.toml` on new +installs, with legacy `~/.deepseek/settings.toml` and platform config-dir +settings kept as compatibility fallbacks. The `NO_ANIMATIONS` env var still wins at startup if it's set, so unsetting the env var is the way to honor your saved choice. diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index e1698f2a..e8463569 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -497,7 +497,9 @@ round-trip intact. codewhale also stores user preferences in: -- `~/.config/deepseek/settings.toml` +- `~/.codewhale/settings.toml` on new installs +- `~/.deepseek/settings.toml` or the legacy platform config-dir + `deepseek/settings.toml` when an existing settings file is present Notable settings include `auto_compact` (default `false`), which opts into replacement-style summarization before the active model limit. The trigger