From d45dced4a73f52c591832b83b0d9e9c91a4404a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 8 May 2026 05:21:42 -0400 Subject: [PATCH] Bind spaces to default profiles --- .../src/shell/internal_pages/spaces.rs | 37 +++++++++------ crates/ely_browser_core/src/state.rs | 45 ++++++++++++------ crates/ely_browser_core/tests/spaces.rs | 46 +++++++++++++++++++ crates/ely_domain/src/space.rs | 20 +++++++- 4 files changed, 120 insertions(+), 28 deletions(-) create mode 100644 crates/ely_browser_core/tests/spaces.rs diff --git a/crates/ely_app/src/shell/internal_pages/spaces.rs b/crates/ely_app/src/shell/internal_pages/spaces.rs index 9b3d6fc..269ca38 100644 --- a/crates/ely_app/src/shell/internal_pages/spaces.rs +++ b/crates/ely_app/src/shell/internal_pages/spaces.rs @@ -1,6 +1,6 @@ use ely_browser_core::BrowserSnapshot; use ely_design_system::colors; -use ely_domain::{ArchivePolicy, Space, SpaceId}; +use ely_domain::{ArchivePolicy, Profile, Space, SpaceId}; use gpui::{ AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString, Styled, div, px, rgb, @@ -109,13 +109,13 @@ fn render_active_space_summary(snapshot: &BrowserSnapshot) -> AnyElement { .text_color(rgb(colors::INK)) .child(active_space.name().to_string()), ) - .child(div().text_xs().truncate().text_color(rgb(colors::MUTED)).child( - format!( - "{} - {}", - accent_label(active_space.accent_hex()), - archive_policy_label(active_space.archive_policy()) - ), - )), + .child( + div() + .text_xs() + .truncate() + .text_color(rgb(colors::MUTED)) + .child(space_detail_label(active_space, &snapshot.profiles)), + ), ), ) .child(div().text_xs().font_semibold().text_color(rgb(colors::SUCCESS)).child("Active")) @@ -132,7 +132,7 @@ fn render_spaces_list(snapshot: &BrowserSnapshot, cx: &mut Context) -> .border_t_1() .border_color(rgb(colors::HAIRLINE)) .children(snapshot.spaces.iter().enumerate().map(|(index, space)| { - render_space_row(index, space, space.id() == &snapshot.active_space_id, cx) + render_space_row(index, space, snapshot, space.id() == &snapshot.active_space_id, cx) })) .into_any_element() } @@ -140,6 +140,7 @@ fn render_spaces_list(snapshot: &BrowserSnapshot, cx: &mut Context) -> fn render_space_row( index: usize, space: &Space, + snapshot: &BrowserSnapshot, active: bool, cx: &mut Context, ) -> AnyElement { @@ -174,7 +175,7 @@ fn render_space_row( .text_xs() .truncate() .text_color(rgb(colors::MUTED)) - .child(space_detail_label(space)), + .child(space_detail_label(space, &snapshot.profiles)), ), ), ) @@ -227,14 +228,24 @@ fn space_avatar(space: &Space) -> AnyElement { .into_any_element() } -fn space_detail_label(space: &Space) -> String { +fn space_detail_label(space: &Space, profiles: &[Profile]) -> String { format!( - "{} - {}", + "{} - {} - {}", accent_label(space.accent_hex()), - archive_policy_label(space.archive_policy()) + archive_policy_label(space.archive_policy()), + default_profile_label(space, profiles) ) } +fn default_profile_label(space: &Space, profiles: &[Profile]) -> String { + let Some(profile) = profiles.iter().find(|profile| profile.id() == space.default_profile_id()) + else { + return "Default profile unavailable".to_string(); + }; + + format!("Default profile: {}", profile.name()) +} + fn accent_label(accent_hex: u32) -> String { format!("#{accent_hex:06X}") } diff --git a/crates/ely_browser_core/src/state.rs b/crates/ely_browser_core/src/state.rs index 3e07a17..f75500b 100644 --- a/crates/ely_browser_core/src/state.rs +++ b/crates/ely_browser_core/src/state.rs @@ -106,13 +106,14 @@ pub struct BrowserCore { impl BrowserCore { pub fn new(config: InitialBrowserConfig) -> Result { - let space = Space::new(config.space_name, config.space_icon, 0xf54e00); let profile = Profile::new(config.profile_name, 0x26251e, ProfileKind::Standard); + let active_profile_id = profile.id().clone(); + let space = + Space::new(config.space_name, config.space_icon, 0xf54e00, active_profile_id.clone()); let new_tab_destination = config.new_tab_destination; let new_tab_url = new_tab_destination.url()?; let new_tab_title = tab_title(&new_tab_url); let active_space_id = space.id().clone(); - let active_profile_id = profile.id().clone(); let tab = BrowserTab::new( TabId::new(), active_space_id.clone(), @@ -162,13 +163,10 @@ impl BrowserCore { icon: impl Into, accent_hex: u32, ) -> Result { - let space = Space::new(name, icon, accent_hex); + let space = Space::new(name, icon, accent_hex, self.active_profile_id.clone()); let space_id = space.id().clone(); - let tab = self.build_tab_for( - space_id.clone(), - self.active_profile_id.clone(), - self.new_tab_url()?, - ); + let default_profile_id = space.default_profile_id().clone(); + let tab = self.build_tab_for(space_id.clone(), default_profile_id, self.new_tab_url()?); let tab_id = tab.id().clone(); self.spaces.push(space); @@ -200,11 +198,14 @@ impl BrowserCore { return Ok(tab_id); } - let tab = self.build_tab_for( - space_id.clone(), - self.active_profile_id.clone(), - self.new_tab_url()?, - ); + let default_profile_id = self + .spaces + .iter() + .find(|space| space.id() == space_id) + .ok_or_else(|| CoreError::SpaceNotFound { id: space_id.clone() })? + .default_profile_id() + .clone(); + let tab = self.build_tab_for(space_id.clone(), default_profile_id, self.new_tab_url()?); let tab_id = tab.id().clone(); self.tabs.push(tab); self.select_tab(&tab_id)?; @@ -233,6 +234,24 @@ impl BrowserCore { Ok(()) } + pub fn set_space_default_profile( + &mut self, + space_id: &SpaceId, + profile_id: &ProfileId, + ) -> Result<(), CoreError> { + if !self.profiles.iter().any(|profile| profile.id() == profile_id) { + return Err(CoreError::ProfileNotFound { id: profile_id.clone() }); + } + + let space = self + .spaces + .iter_mut() + .find(|space| space.id() == space_id) + .ok_or_else(|| CoreError::SpaceNotFound { id: space_id.clone() })?; + space.set_default_profile_id(profile_id.clone()); + Ok(()) + } + pub fn set_search_engine(&mut self, search_engine: SearchEngine) { self.search_engine = search_engine; } diff --git a/crates/ely_browser_core/tests/spaces.rs b/crates/ely_browser_core/tests/spaces.rs new file mode 100644 index 0000000..c7338ef --- /dev/null +++ b/crates/ely_browser_core/tests/spaces.rs @@ -0,0 +1,46 @@ +use std::error::Error; + +use ely_browser_core::{BrowserCore, CoreError, InitialBrowserConfig}; +use ely_domain::{ProfileId, ProfileKind}; + +#[test] +fn created_space_binds_current_profile_as_default() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let research_profile_id = core.create_profile("Research", 0x9fc9a2, ProfileKind::Standard)?; + + let research_space_id = core.create_space("Research", "R", 0x9fc9a2)?; + let snapshot = core.snapshot()?; + let Some(research_space) = + snapshot.spaces.iter().find(|space| space.id() == &research_space_id) + else { + return Err("missing research space".into()); + }; + + assert_eq!(research_space.default_profile_id(), &research_profile_id); + assert_eq!(snapshot.active_profile_id, research_profile_id); + Ok(()) +} + +#[test] +fn space_default_profile_updates_with_profile_validation() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let work_space_id = core.snapshot()?.active_space_id; + let research_profile_id = core.create_profile("Research", 0x9fc9a2, ProfileKind::Standard)?; + + core.set_space_default_profile(&work_space_id, &research_profile_id)?; + let snapshot = core.snapshot()?; + let Some(work_space) = snapshot.spaces.iter().find(|space| space.id() == &work_space_id) else { + return Err("missing work space".into()); + }; + + assert_eq!(work_space.default_profile_id(), &research_profile_id); + + let missing_profile_id = ProfileId::new(); + let error = match core.set_space_default_profile(&work_space_id, &missing_profile_id) { + Err(error) => error, + Ok(_) => return Err("space default profile should require an existing profile".into()), + }; + + assert_eq!(error, CoreError::ProfileNotFound { id: missing_profile_id }); + Ok(()) +} diff --git a/crates/ely_domain/src/space.rs b/crates/ely_domain/src/space.rs index 810c8f5..a077d76 100644 --- a/crates/ely_domain/src/space.rs +++ b/crates/ely_domain/src/space.rs @@ -1,4 +1,4 @@ -use crate::SpaceId; +use crate::{ProfileId, SpaceId}; #[derive(Clone, Debug, Eq, PartialEq)] pub enum ArchivePolicy { @@ -12,17 +12,24 @@ pub struct Space { name: String, icon: String, accent_hex: u32, + default_profile_id: ProfileId, archive_policy: ArchivePolicy, } impl Space { #[must_use] - pub fn new(name: impl Into, icon: impl Into, accent_hex: u32) -> Self { + pub fn new( + name: impl Into, + icon: impl Into, + accent_hex: u32, + default_profile_id: ProfileId, + ) -> Self { Self { id: SpaceId::new(), name: name.into(), icon: icon.into(), accent_hex, + default_profile_id, archive_policy: ArchivePolicy::Manual, } } @@ -47,6 +54,15 @@ impl Space { self.accent_hex } + #[must_use] + pub fn default_profile_id(&self) -> &ProfileId { + &self.default_profile_id + } + + pub fn set_default_profile_id(&mut self, profile_id: ProfileId) { + self.default_profile_id = profile_id; + } + #[must_use] pub fn archive_policy(&self) -> &ArchivePolicy { &self.archive_policy