diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index dadcab383f091e..f983917ae25464 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -215,6 +215,10 @@ pub trait FeatureFlagAppExt { fn flag_value(&self) -> T::Value; fn is_staff(&self) -> bool; + /// Whether feature flag overrides from settings are honored for the + /// current user. Overrides are a staff-only affordance. + fn feature_flag_overrides_enabled(&self) -> bool; + fn on_flags_ready(&mut self, callback: F) -> Subscription where F: FnMut(OnFlagsReady, &mut App) + 'static; @@ -253,6 +257,11 @@ impl FeatureFlagAppExt for App { .unwrap_or(false) } + fn feature_flag_overrides_enabled(&self) -> bool { + self.try_global::() + .map_or(false, |store| store.overrides_enabled()) + } + fn on_flags_ready(&mut self, mut callback: F) -> Subscription where F: FnMut(OnFlagsReady, &mut App) + 'static, diff --git a/crates/feature_flags/src/store.rs b/crates/feature_flags/src/store.rs index a8376de7e3a878..552d025f01e274 100644 --- a/crates/feature_flags/src/store.rs +++ b/crates/feature_flags/src/store.rs @@ -96,6 +96,16 @@ impl FeatureFlagStore { self.staff } + /// Whether feature flag overrides from settings should be honored. + /// + /// Overrides are a staff-only affordance, so non-staff users in release + /// builds can't flip flags through `settings.json` or the settings UI. + /// Debug builds are always treated as staff, and `ZED_DISABLE_STAFF` + /// forces the user to be treated as non-staff for testing. + pub fn overrides_enabled(&self) -> bool { + (cfg!(debug_assertions) || self.staff) && !*ZED_DISABLE_STAFF + } + pub fn server_flags_received(&self) -> bool { self.server_flags_received } @@ -158,8 +168,12 @@ impl FeatureFlagStore { return Some(T::Value::on_variant()); } - if let Some(override_key) = FeatureFlagsSettings::get_global(cx).overrides.get(T::NAME) { - return variant_from_key::(override_key); + // Only apply overrides when they are specifically enabled. + if self.overrides_enabled() { + if let Some(override_key) = FeatureFlagsSettings::get_global(cx).overrides.get(T::NAME) + { + return variant_from_key::(override_key); + } } // Staff default: resolve to the enabled variant. @@ -194,15 +208,18 @@ impl FeatureFlagStore { return on_variant_key; } - if let Some(requested) = FeatureFlagsSettings::get_global(cx) - .overrides - .get(descriptor.name) - { - if let Some(variant) = (descriptor.variants)() - .into_iter() - .find(|v| v.override_key == requested.as_str()) + // Only apply overrides when they are specifically enabled. + if self.overrides_enabled() { + if let Some(requested) = FeatureFlagsSettings::get_global(cx) + .overrides + .get(descriptor.name) { - return variant.override_key; + if let Some(variant) = (descriptor.variants)() + .into_iter() + .find(|v| v.override_key == requested.as_str()) + { + return variant.override_key; + } } } diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 3eb1ec94512d5c..1a38337fb492fb 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -62,7 +62,7 @@ macro_rules! concat_sections { } pub(crate) fn settings_data(cx: &App) -> Vec { - let mut pages = vec![ + vec![ general_page(cx), appearance_page(), keymap_page(), @@ -77,56 +77,58 @@ pub(crate) fn settings_data(cx: &App) -> Vec { collaboration_page(), ai_page(cx), network_page(), - ]; + developer_page(cx), + ] +} +fn developer_page(cx: &App) -> SettingsPage { use feature_flags::FeatureFlagAppExt as _; - if cx.is_staff() || cfg!(debug_assertions) { - pages.push(developer_page()); + + let mut items: Vec = Vec::new(); + + // Feature flag overrides are a staff-only affordance, so only surface the section when the overrides are enabled. + if cx.feature_flag_overrides_enabled() { + items.push(SettingsPageItem::SectionHeader("Feature Flags")); + items.push(SettingsPageItem::SubPageLink(SubPageLink { + title: "Feature Flags".into(), + r#type: Default::default(), + description: None, + json_path: Some("feature_flags"), + in_json: true, + files: USER, + render: crate::pages::render_feature_flags_page, + })); } - pages -} + items.push(SettingsPageItem::SectionHeader("Instrumentation")); + items.push(SettingsPageItem::SettingItem(SettingItem { + title: "Performance Profiler", + description: "Collect timing data for foreground and background executor tasks so they can be inspected via `zed: open performance profiler`. May lead to increased memory usage.", + field: Box::new(SettingField { + json_path: Some("instrumentation.performance_profiler.enabled"), + pick: |settings_content| { + settings_content + .instrumentation + .as_ref() + .and_then(|i| i.performance_profiler.as_ref()) + .and_then(|p| p.enabled.as_ref()) + }, + write: |settings_content, value, _| { + settings_content + .instrumentation + .get_or_insert_default() + .performance_profiler + .get_or_insert_default() + .enabled = value; + }, + }), + metadata: None, + files: USER, + })); -fn developer_page() -> SettingsPage { SettingsPage { title: "Developer", - items: Box::new([ - SettingsPageItem::SectionHeader("Feature Flags"), - SettingsPageItem::SubPageLink(SubPageLink { - title: "Feature Flags".into(), - r#type: Default::default(), - description: None, - json_path: Some("feature_flags"), - in_json: true, - files: USER, - render: crate::pages::render_feature_flags_page, - }), - SettingsPageItem::SectionHeader("Instrumentation"), - SettingsPageItem::SettingItem(SettingItem { - title: "Performance Profiler", - description: "Collect timing data for foreground and background executor tasks so they can be inspected via `zed: open performance profiler`. May lead to increased memory usage.", - field: Box::new(SettingField { - json_path: Some("instrumentation.performance_profiler.enabled"), - pick: |settings_content| { - settings_content - .instrumentation - .as_ref() - .and_then(|i| i.performance_profiler.as_ref()) - .and_then(|p| p.enabled.as_ref()) - }, - write: |settings_content, value, _| { - settings_content - .instrumentation - .get_or_insert_default() - .performance_profiler - .get_or_insert_default() - .enabled = value; - }, - }), - metadata: None, - files: USER, - }), - ]), + items: items.into_boxed_slice(), } }