From 324b0f1a6e050ebfdacd9f3a9f6e74df11ba2070 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, 10 Jul 2026 16:00:30 -0400 Subject: [PATCH] fix(profiles): scope the sidebar to the active profile --- crates/ely_browser_core/src/state.rs | 13 ++-- .../tests/private_profile_isolation.rs | 65 +++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 crates/ely_browser_core/tests/private_profile_isolation.rs diff --git a/crates/ely_browser_core/src/state.rs b/crates/ely_browser_core/src/state.rs index c5658f3..8ab086a 100644 --- a/crates/ely_browser_core/src/state.rs +++ b/crates/ely_browser_core/src/state.rs @@ -420,13 +420,18 @@ impl BrowserCore { } fn favorites(&self) -> Vec { - self.tabs.iter().filter(|tab| tab.flags().favorite).cloned().collect() + self.tabs + .iter() + .filter(|tab| tab.profile_id() == &self.active_profile_id) + .filter(|tab| tab.flags().favorite) + .cloned() + .collect() } fn visible_tabs(&self) -> Vec { - tab_order::sorted_tabs( - self.tabs.iter().filter(|tab| tab.space_id() == &self.active_space_id), - ) + tab_order::sorted_tabs(self.tabs.iter().filter(|tab| { + tab.space_id() == &self.active_space_id && tab.profile_id() == &self.active_profile_id + })) } fn record_tab_activity(&mut self, tab_id: &TabId, active_at: SystemTime) { diff --git a/crates/ely_browser_core/tests/private_profile_isolation.rs b/crates/ely_browser_core/tests/private_profile_isolation.rs new file mode 100644 index 0000000..580d4bb --- /dev/null +++ b/crates/ely_browser_core/tests/private_profile_isolation.rs @@ -0,0 +1,65 @@ +use std::error::Error; + +use ely_browser_core::{BrowserCore, InitialBrowserConfig}; +use ely_domain::UrlText; + +/// A private profile can be created inside the main window (`>new-private-profile`). +/// Its tabs and favorites must never appear once a standard profile is active — +/// the sidebar is scoped to the active profile like every other surface +/// (bookmarks, history, downloads, permissions). PRD §8.11. +#[test] +fn private_profile_tabs_do_not_leak_into_a_standard_profile() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let default_profile_id = core.active_tab()?.profile_id().clone(); + core.open_tab(UrlText::parse("https://example.com/work")?); + + core.set_command_query(">new-private-profile Private"); + core.submit_command()?; + let private_tab = core.open_tab(UrlText::parse("https://example.com/secret")?); + core.toggle_active_tab_favorite()?; + + core.select_profile(&default_profile_id)?; + let snapshot = core.snapshot()?; + + assert!( + snapshot.tabs.iter().all(|tab| tab.id() != &private_tab), + "private tab must not appear in the standard profile sidebar", + ); + assert!( + snapshot.tabs.iter().all(|tab| tab.profile_id() == &default_profile_id), + "every visible tab must belong to the active profile", + ); + assert!( + snapshot.favorites.iter().all(|tab| tab.profile_id() == &default_profile_id), + "private favorites must not appear in the standard profile", + ); + assert!( + snapshot.tabs.iter().any(|tab| tab.url().as_str() == "https://example.com/work"), + "the standard profile's own tab must still be visible", + ); + Ok(()) +} + +/// Switching back into the private profile still shows its own tabs — the fix +/// scopes visibility, it does not destroy the private session mid-window. +#[test] +fn private_profile_still_sees_its_own_tabs_when_active() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let default_profile_id = core.active_tab()?.profile_id().clone(); + + core.set_command_query(">new-private-profile Private"); + core.submit_command()?; + let private_profile_id = core.active_tab()?.profile_id().clone(); + let private_tab = core.open_tab(UrlText::parse("https://example.com/secret")?); + assert_ne!(private_profile_id, default_profile_id); + + core.select_profile(&default_profile_id)?; + core.select_profile(&private_profile_id)?; + let snapshot = core.snapshot()?; + + assert!( + snapshot.tabs.iter().any(|tab| tab.id() == &private_tab), + "the private profile must still see its own tab when active", + ); + Ok(()) +}