//! Machine-local user preferences and project-owned presentation settings. use serde::{Deserialize, Serialize}; /// Preferred interface when a new human-facing agent terminal is opened. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum DefaultAgentTerminalInterface { /// Provider's native interactive terminal UI. NativeTui, /// IdeA's structured/custom CLI chat surface. IdeaCli, } /// User-selected IdeA interface language. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum IdeaLanguage { /// French interface. Fr, /// English interface. En, } /// Global, machine-local IdeA preferences. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct IdeaUserSettings { /// Default for newly launched human-facing agent terminals. #[serde(default)] pub default_agent_terminal_interface: Option, /// Preferred interface language. Absence preserves the product default. #[serde(default)] pub language: Option, } /// Supported icon catalogue for project visual identity. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum ProjectIconSet { /// Unicode emoji catalogue. Emoji, /// Devicon language and platform catalogue. Devicon, /// Simple Icons brand catalogue. SimpleIcons, /// Lucide general-purpose icon catalogue. Lucide, /// IdeA-owned built-in icons. Idea, } /// Maximum byte length of a stable project icon name. pub const MAX_PROJECT_ICON_NAME_LEN: usize = 64; /// Validated reference to one icon in a known catalogue. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", try_from = "UncheckedProjectIconRef")] pub struct ProjectIconRef { set: ProjectIconSet, name: String, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct UncheckedProjectIconRef { set: ProjectIconSet, name: String, } impl ProjectIconRef { /// Builds a validated stable icon reference. pub fn new(set: ProjectIconSet, name: impl Into) -> Result { let name = name.into(); let bytes = name.as_bytes(); let valid_name = !name.is_empty() && name.len() <= MAX_PROJECT_ICON_NAME_LEN && bytes.first().is_some_and(u8::is_ascii_alphanumeric) && bytes.last().is_some_and(u8::is_ascii_alphanumeric) && bytes .iter() .copied() .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.')); if !valid_name { return Err(format!( "project icon name must be 1..={MAX_PROJECT_ICON_NAME_LEN} ASCII letters, digits, '-', '_' or '.', starting and ending with a letter or digit" )); } Ok(Self { set, name }) } /// Returns the owning icon catalogue. #[must_use] pub const fn set(&self) -> ProjectIconSet { self.set } /// Returns the stable catalogue icon name. #[must_use] pub fn name(&self) -> &str { &self.name } } impl TryFrom for ProjectIconRef { type Error = String; fn try_from(value: UncheckedProjectIconRef) -> Result { Self::new(value.set, value.name) } } /// Strict project theme colour (`#RRGGBB`). #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub struct ThemeColor(String); impl ThemeColor { /// Validates and builds a project theme colour. pub fn new(value: impl Into) -> Result { let value = value.into(); let valid = value.len() == 7 && value.starts_with('#') && value.as_bytes()[1..].iter().all(u8::is_ascii_hexdigit); valid .then_some(Self(value)) .ok_or_else(|| "themeColor must match #RRGGBB".to_owned()) } /// Returns the validated wire representation. #[must_use] pub fn as_str(&self) -> &str { &self.0 } } impl TryFrom for ThemeColor { type Error = String; fn try_from(value: String) -> Result { Self::new(value) } } impl From for String { fn from(value: ThemeColor) -> Self { value.0 } } /// Settings owned by one project and stored below its `.ideai` directory. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ProjectSettings { /// Optional visual theme colour; absence keeps the product fallback. #[serde(default)] pub theme_color: Option, /// Optional validated icon used as the project's visual identity. #[serde(default)] pub icon: Option, }