From b9f57921cdddd50af217e952fbe593796ee77631 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 20:57:19 -0400 Subject: [PATCH] Add advanced settings surface --- crates/ely_app/src/shell/internal_pages.rs | 2 + .../src/shell/internal_pages/advanced.rs | 242 ++++++++++++++++++ .../src/shell/internal_pages/settings.rs | 6 + crates/ely_browser_core/src/navigation.rs | 4 + .../ely_browser_core/tests/settings_routes.rs | 21 ++ 5 files changed, 275 insertions(+) create mode 100644 crates/ely_app/src/shell/internal_pages/advanced.rs diff --git a/crates/ely_app/src/shell/internal_pages.rs b/crates/ely_app/src/shell/internal_pages.rs index 4284205..fb37e0a 100644 --- a/crates/ely_app/src/shell/internal_pages.rs +++ b/crates/ely_app/src/shell/internal_pages.rs @@ -1,4 +1,5 @@ mod about; +mod advanced; mod appearance; mod bookmarks; mod crash; @@ -75,6 +76,7 @@ impl ElyShell { } "ely://about" => self.render_about_page(snapshot), "ely://settings" => self.render_settings_page(snapshot, cx), + "ely://settings/advanced" => self.render_advanced_page(snapshot), "ely://settings/appearance" => self.render_appearance_page(snapshot), "ely://settings/general" => self.render_general_page(snapshot, cx), "ely://settings/sidebar-tabs" => self.render_sidebar_tabs_page(snapshot, cx), diff --git a/crates/ely_app/src/shell/internal_pages/advanced.rs b/crates/ely_app/src/shell/internal_pages/advanced.rs new file mode 100644 index 0000000..0296483 --- /dev/null +++ b/crates/ely_app/src/shell/internal_pages/advanced.rs @@ -0,0 +1,242 @@ +use ely_browser_core::BrowserSnapshot; +use ely_design_system::colors; +use ely_domain::{ArchivePolicy, Space}; +use gpui::{AnyElement, IntoElement, ParentElement, Styled, div, px, rgb}; +use gpui_component::{IconName, StyledExt, scroll::ScrollableElement}; + +use super::{ElyShell, download_labels::download_policy_label, render_canvas_surface}; + +impl ElyShell { + pub(super) fn render_advanced_page(&mut self, snapshot: &BrowserSnapshot) -> AnyElement { + render_canvas_surface( + div() + .size_full() + .p_8() + .flex() + .flex_col() + .gap_5() + .child(render_advanced_header(snapshot)) + .child(render_advanced_summary(snapshot)) + .child(render_advanced_rows(snapshot)), + ) + } +} + +fn render_advanced_header(snapshot: &BrowserSnapshot) -> AnyElement { + div() + .flex() + .items_end() + .justify_between() + .gap_4() + .child( + div() + .min_w_0() + .flex() + .flex_col() + .gap_2() + .child(div().text_size(px(26.0)).text_color(rgb(colors::INK)).child("Advanced")) + .child( + div() + .text_sm() + .truncate() + .text_color(rgb(colors::MUTED)) + .child(format!("Space: {}", snapshot.active_space_name)), + ), + ) + .child( + div() + .flex() + .items_center() + .gap_2() + .text_xs() + .font_semibold() + .text_color(rgb(colors::MUTED)) + .child(IconName::Inspector) + .child(format!("{} policies", advanced_policy_count())), + ) + .into_any_element() +} + +fn render_advanced_summary(snapshot: &BrowserSnapshot) -> AnyElement { + div() + .rounded_md() + .border_1() + .border_color(rgb(colors::HAIRLINE)) + .bg(rgb(colors::CANVAS_SOFT)) + .px_4() + .py_3() + .flex() + .items_center() + .justify_between() + .gap_4() + .child( + div() + .min_w_0() + .flex() + .items_center() + .gap_3() + .child(div().text_color(rgb(colors::PRIMARY)).child(IconName::Inspector)) + .child( + div() + .min_w_0() + .flex() + .flex_col() + .gap_1() + .child( + div() + .text_sm() + .font_semibold() + .text_color(rgb(colors::INK)) + .child("Local Runtime"), + ) + .child(div().text_xs().truncate().text_color(rgb(colors::MUTED)).child( + format!( + "{} space / {} profile", + snapshot.active_space_name, snapshot.active_profile_name + ), + )), + ), + ) + .child(div().text_xs().font_semibold().text_color(rgb(colors::SUCCESS)).child("Local")) + .into_any_element() +} + +fn render_advanced_rows(snapshot: &BrowserSnapshot) -> AnyElement { + let active_space = snapshot.spaces.iter().find(|space| space.id() == &snapshot.active_space_id); + + div() + .flex_1() + .min_h_0() + .flex() + .flex_col() + .overflow_y_scrollbar() + .border_t_1() + .border_color(rgb(colors::HAIRLINE)) + .child(advanced_row( + IconName::Eye, + "History Recording", + snapshot.history_recording_policy.status(), + snapshot.history_recording_policy.detail(), + )) + .child(advanced_row( + IconName::Star, + "Favorite Limit", + snapshot.favorite_limit.label(), + snapshot.favorite_limit.detail(), + )) + .child(render_sidebar_width_row(active_space)) + .child(render_archive_policy_row(active_space)) + .child(advanced_row( + IconName::Folder, + "Download Policy", + download_policy_label(&snapshot.active_download_policy), + "Active Profile download destination policy", + )) + .child(advanced_row( + IconName::Globe, + "Sync Objects", + snapshot.sync_status.objects().len().to_string(), + "Object scopes tracked by local Sync state", + )) + .child(advanced_row( + IconName::Asterisk, + "Installed Plugins", + snapshot.installed_plugins.len().to_string(), + "Verified plugins registered in Browser Core", + )) + .child(advanced_row( + IconName::Inspector, + "Audit Events", + audit_event_count(snapshot).to_string(), + "Plugin and site permission audit records", + )) + .into_any_element() +} + +fn render_sidebar_width_row(active_space: Option<&Space>) -> AnyElement { + let value = active_space + .map(|space| format!("{} px", space.sidebar_width_px())) + .unwrap_or_else(|| "Unavailable".to_string()); + advanced_row(IconName::PanelLeft, "Sidebar Width", value, "Current Space sidebar width") +} + +fn render_archive_policy_row(active_space: Option<&Space>) -> AnyElement { + let value = active_space + .map(|space| archive_policy_label(space.archive_policy()).to_string()) + .unwrap_or_else(|| "Unavailable".to_string()); + advanced_row(IconName::Inbox, "Auto Archive", value, "Current Space idle unpinned tab policy") +} + +fn advanced_row( + icon: IconName, + label: &'static str, + value: impl Into, + detail: impl Into, +) -> AnyElement { + let value = value.into(); + let detail = detail.into(); + + div() + .py_3() + .border_b_1() + .border_color(rgb(colors::HAIRLINE)) + .flex() + .items_center() + .justify_between() + .gap_4() + .child( + div() + .min_w_0() + .flex() + .items_center() + .gap_3() + .child(div().text_color(rgb(colors::MUTED_SOFT)).child(icon)) + .child( + div() + .min_w_0() + .flex() + .flex_col() + .gap_1() + .child( + div() + .text_sm() + .font_semibold() + .truncate() + .text_color(rgb(colors::INK)) + .child(label), + ) + .child( + div().text_xs().truncate().text_color(rgb(colors::MUTED)).child(detail), + ), + ), + ) + .child( + div() + .max_w(px(280.0)) + .truncate() + .text_sm() + .font_semibold() + .text_color(rgb(colors::INK)) + .child(value), + ) + .into_any_element() +} + +fn archive_policy_label(policy: &ArchivePolicy) -> &'static str { + match policy { + ArchivePolicy::Manual => "Manual", + ArchivePolicy::IdleDays(0) => "Today", + ArchivePolicy::IdleDays(1) => "1 day", + ArchivePolicy::IdleDays(7) => "7 days", + ArchivePolicy::IdleDays(30) => "30 days", + ArchivePolicy::IdleDays(_) => "Custom", + } +} + +fn audit_event_count(snapshot: &BrowserSnapshot) -> usize { + snapshot.plugin_audit_events.len() + snapshot.site_permission_audit_events.len() +} + +fn advanced_policy_count() -> usize { + 8 +} diff --git a/crates/ely_app/src/shell/internal_pages/settings.rs b/crates/ely_app/src/shell/internal_pages/settings.rs index e65f09f..93d139d 100644 --- a/crates/ely_app/src/shell/internal_pages/settings.rs +++ b/crates/ely_app/src/shell/internal_pages/settings.rs @@ -90,6 +90,12 @@ const SETTINGS_ROUTES: &[SettingsRoute] = &[ detail: "Build identity and Elydora release manifest contract.", route: "ely://settings/updates", }, + SettingsRoute { + icon: IconName::Inspector, + title: "Advanced", + detail: "Local runtime policies and audit counters.", + route: "ely://settings/advanced", + }, SettingsRoute { icon: IconName::Asterisk, title: "Plugins", diff --git a/crates/ely_browser_core/src/navigation.rs b/crates/ely_browser_core/src/navigation.rs index 70aa81b..579a769 100644 --- a/crates/ely_browser_core/src/navigation.rs +++ b/crates/ely_browser_core/src/navigation.rs @@ -29,6 +29,7 @@ fn internal_page_title(url: &str) -> Option<&'static str> { url if SiteOrigin::from_site_route(url).ok().flatten().is_some() => Some("Site Settings"), "ely://about" => Some("About ELY Browser"), "ely://settings" => Some("Settings"), + "ely://settings/advanced" => Some("Advanced Settings"), "ely://settings/general" => Some("General Settings"), "ely://settings/appearance" => Some("Appearance Settings"), "ely://settings/sidebar-tabs" => Some("Sidebar & Tabs Settings"), @@ -234,6 +235,9 @@ pub(crate) fn settings_page_url(query: &str) -> Result, CoreErro fn settings_page_route(query: &str) -> Option<&'static str> { match query { "settings" => Some("ely://settings"), + "advanced" | "advanced settings" | "runtime" | "diagnostics" | "diagnostic" => { + Some("ely://settings/advanced") + } "general" | "browser" | "new tab" | "new-tab" | "startup" => Some("ely://settings/general"), "appearance" | "theme" | "visual" | "design" | "colors" => { Some("ely://settings/appearance") diff --git a/crates/ely_browser_core/tests/settings_routes.rs b/crates/ely_browser_core/tests/settings_routes.rs index 0d5d237..a1b6193 100644 --- a/crates/ely_browser_core/tests/settings_routes.rs +++ b/crates/ely_browser_core/tests/settings_routes.rs @@ -66,6 +66,27 @@ fn settings_scoped_search_opens_appearance_page() -> Result<(), Box> Ok(()) } +#[test] +fn settings_scoped_search_opens_advanced_page() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + + core.set_command_query("@settings advanced"); + let intent = core.submit_command()?; + let active_tab = core.active_tab()?; + + assert_eq!( + intent, + Some(CommandIntent::ScopedSearch { + scope: CommandScope::Settings, + query: "advanced".to_string(), + }) + ); + assert_eq!(active_tab.title(), "Advanced Settings"); + assert_eq!(active_tab.url().as_str(), "ely://settings/advanced"); + assert_eq!(core.snapshot()?.command_query, ""); + Ok(()) +} + #[test] fn settings_scoped_search_opens_shortcuts_page() -> Result<(), Box> { let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;