feature_flags: Only respect feature flag overrides for Zed staff (#57860)

This PR is a follow-up to #54206 to make it so we only respect the
feature flag overrides for Zed staff (or when running a development
build).

Previously just the UI portion of the feature flags was disabled for
non-staff, but manipulating the settings file by hand would still allow
setting overrides that would be respected.

The only other setting on the "Developer" page of the settings UI was
the performance profiler. This was previously only available for staff,
as the entire "Developer" page was limited to staff, but it seems
reasonable to allow non-staff to see this.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2026-05-27 14:16:04 -04:00 committed by GitHub
parent 6472f018a6
commit bf4e559347
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 85 additions and 57 deletions

View file

@ -215,6 +215,10 @@ pub trait FeatureFlagAppExt {
fn flag_value<T: FeatureFlag>(&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<F>(&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::<FeatureFlagStore>()
.map_or(false, |store| store.overrides_enabled())
}
fn on_flags_ready<F>(&mut self, mut callback: F) -> Subscription
where
F: FnMut(OnFlagsReady, &mut App) + 'static,

View file

@ -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::<T::Value>(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::<T::Value>(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;
}
}
}

View file

@ -62,7 +62,7 @@ macro_rules! concat_sections {
}
pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
let mut pages = vec![
vec![
general_page(cx),
appearance_page(),
keymap_page(),
@ -77,56 +77,58 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
collaboration_page(),
ai_page(cx),
network_page(),
];
use feature_flags::FeatureFlagAppExt as _;
if cx.is_staff() || cfg!(debug_assertions) {
pages.push(developer_page());
}
pages
developer_page(cx),
]
}
fn developer_page() -> SettingsPage {
fn developer_page(cx: &App) -> SettingsPage {
use feature_flags::FeatureFlagAppExt as _;
let mut items: Vec<SettingsPageItem> = 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,
}));
}
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,
}));
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(),
}
}