From 9a61ff4a79e7a6490b15e4973c62777db79108b1 Mon Sep 17 00:00:00 2001 From: Tom You Date: Mon, 6 Jul 2026 23:54:33 -0500 Subject: [PATCH] feat(core): add persisted app config model --- rabby-core/src/config/mod.rs | 84 ++++++++++++++++++++++++++++++++++++ rabby-core/src/lib.rs | 2 + 2 files changed, 86 insertions(+) create mode 100644 rabby-core/src/config/mod.rs diff --git a/rabby-core/src/config/mod.rs b/rabby-core/src/config/mod.rs new file mode 100644 index 0000000..88768e4 --- /dev/null +++ b/rabby-core/src/config/mod.rs @@ -0,0 +1,84 @@ +use crate::{ConnectionKind, Profile}; +use std::collections::HashSet; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ShortcutBinding { + pub command: String, + pub binding: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AppConfig { + pub profiles: Vec, + pub theme_id: String, + pub shortcuts: Vec, + pub restore_workspace: bool, +} + +impl AppConfig { + pub fn default_linux() -> Self { + let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".to_string()); + Self { + profiles: vec![Profile::new( + "local", + "Local Shell", + ConnectionKind::LocalShell, + &shell, + ) + .expect("default local profile is valid")], + theme_id: "rabby-dark".to_string(), + shortcuts: Vec::new(), + restore_workspace: true, + } + } + + pub fn validate(&self) -> Result<(), String> { + if self.theme_id.trim().is_empty() { + return Err("theme id is required".to_string()); + } + + let mut profile_ids = HashSet::new(); + for profile in &self.profiles { + if !profile_ids.insert(profile.id.as_str()) { + return Err(format!("duplicate profile id: {}", profile.id)); + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ConnectionKind, Profile}; + + #[test] + fn default_config_includes_local_shell_profile() { + let config = AppConfig::default_linux(); + assert_eq!(config.theme_id, "rabby-dark"); + assert!(config.restore_workspace); + assert!(config + .profiles + .iter() + .any(|profile| profile.kind == ConnectionKind::LocalShell)); + } + + #[test] + fn duplicate_profile_ids_fail_validation() { + let mut config = AppConfig::default_linux(); + config.profiles.push( + Profile::new("local", "Duplicate", ConnectionKind::LocalShell, "/bin/sh").unwrap(), + ); + let err = config.validate().unwrap_err(); + assert!(err.contains("duplicate profile")); + } + + #[test] + fn empty_theme_id_fails_validation() { + let mut config = AppConfig::default_linux(); + config.theme_id = " ".to_string(); + let err = config.validate().unwrap_err(); + assert!(err.contains("theme")); + } +} diff --git a/rabby-core/src/lib.rs b/rabby-core/src/lib.rs index 7b02599..830e48a 100644 --- a/rabby-core/src/lib.rs +++ b/rabby-core/src/lib.rs @@ -3,9 +3,11 @@ //! This crate is intentionally dependency-light so terminal/profile/session behavior can be //! unit-tested without starting the desktop shell. +pub mod config; pub mod feature; pub mod workspace; +pub use config::{AppConfig, ShortcutBinding}; pub use feature::{ all_features, feature_by_key, feature_matrix, FeatureArea, FeatureSpec, FeatureStatus, };