Significant progress. Performing cleanup now

This commit is contained in:
Ryan Ward
2026-05-11 14:31:02 -05:00
parent fe105e0369
commit 140da74f99
37 changed files with 843 additions and 1220 deletions
+1 -287
View File
@@ -43,17 +43,15 @@ use crate::ui_components::buttons::icon_button;
use crate::view_components::{Dropdown, DropdownItem};
use crate::{
appearance::Appearance,
auth::auth_manager::AuthManager,
channel::ChannelState,
report_if_error, send_telemetry_from_ctx,
server::telemetry::TelemetryEvent,
settings::{AISettings, PrivacySettings},
terminal::safe_mode_settings::{SafeModeEnabled, SafeModeSettings},
ui_components::icons::Icon,
util::links::PRIVACY_POLICY_URL,
workspaces::{
user_workspaces::UserWorkspaces,
workspace::{AdminEnablementSetting, CustomerType, UgcCollectionEnablementSetting},
workspace::{CustomerType, UgcCollectionEnablementSetting},
},
};
@@ -97,25 +95,7 @@ const TELEMETRY_FREE_TIER_NOTE: &str =
const TELEMETRY_DOCS_URL: &str =
"https://docs.warp.dev/support-and-community/privacy-and-security/privacy#what-telemetry-data-does-warp-collect-and-why";
const DATA_MANAGEMENT_TITLE: &str = "Manage your data";
const DATA_MANAGEMENT_DESCRIPTION: &str =
"At any time, you may choose to delete your Warp account permanently. \
You will no longer be able to use Warp.";
const DATA_MANAGEMENT_LINK_TEXT: &str = "Visit the data management page";
const PRIVACY_POLICY_TITLE: &str = "Privacy policy";
const PRIVACY_POLICY_LINK_TEXT: &str = "Read Warp's privacy policy";
pub fn data_management_url(custom_token: Option<&str>) -> String {
match custom_token {
Some(token) => format!(
"{}/data_management?customToken={}",
ChannelState::server_root_url(),
token
),
None => format!("{}/data_management", ChannelState::server_root_url(),),
}
}
pub struct PrivacyPageView {
page: PageType<Self>,
@@ -241,13 +221,10 @@ impl PrivacyPageView {
Box::new(SecretRedactionWidget::default()),
Box::new(AppAnalyticsWidget::default()),
Box::new(CrashReportsWidget::default()),
Box::new(CloudConversationStorageWidget::default()),
];
if ContextFlag::NetworkLogConsole.is_enabled() {
widgets.push(Box::new(NetworkLogWidget::default()));
}
widgets.push(Box::new(DataManagementWidget::default()));
widgets.push(Box::new(PrivacyPolicyWidget::default()));
PageType::new_uncategorized(widgets, Some("Privacy"))
}
@@ -327,17 +304,6 @@ impl PrivacyPageView {
ctx.notify();
}
fn toggle_cloud_conversation_storage(&mut self, ctx: &mut ViewContext<Self>) {
let privacy_settings_handle = PrivacySettings::handle(ctx);
let old_value = privacy_settings_handle
.as_ref(ctx)
.is_cloud_conversation_storage_enabled;
ctx.update_model(&privacy_settings_handle, |privacy_settings, ctx| {
privacy_settings.set_is_cloud_conversation_storage_enabled(!old_value, ctx);
});
ctx.notify();
}
fn queue_regex_removal(&mut self, idx: usize, ctx: &mut ViewContext<Self>) {
// Check if this removal is already pending
if self.pending_regex_removals.contains(&idx) {
@@ -503,10 +469,8 @@ pub enum PrivacyPageAction {
SetSecretDisplayMode(SecretDisplayMode),
ToggleTelemetry,
ToggleCrashReporting,
ToggleCloudConversationStorage,
LaunchNetworkLogging,
RemoveCustomRegex(usize),
OpenDataManagementWebpage,
AddAllRecommendedRegexes,
ShowAddRegexModal,
AddRecommendedRegex(usize),
@@ -584,19 +548,10 @@ impl TypedActionView for PrivacyPageView {
}
PrivacyPageAction::ToggleTelemetry => self.toggle_telemetry(ctx),
PrivacyPageAction::ToggleCrashReporting => self.toggle_crash_reporting(ctx),
PrivacyPageAction::ToggleCloudConversationStorage => {
self.toggle_cloud_conversation_storage(ctx)
}
PrivacyPageAction::LaunchNetworkLogging => self.launch_network_logging(ctx),
PrivacyPageAction::RemoveCustomRegex(idx) => {
self.queue_regex_removal(*idx, ctx);
}
PrivacyPageAction::OpenDataManagementWebpage => {
AuthManager::handle(ctx).update(ctx, |auth_manager, ctx| {
auth_manager
.open_url_maybe_with_anonymous_token(ctx, Box::new(data_management_url));
});
}
PrivacyPageAction::AddAllRecommendedRegexes => {
// First process any pending removals
if !self.pending_regex_removals.is_empty() {
@@ -1673,120 +1628,6 @@ impl SettingsWidget for CrashReportsWidget {
}
}
#[derive(Default)]
struct CloudConversationStorageWidget {
switch_state: SwitchStateHandle,
}
impl SettingsWidget for CloudConversationStorageWidget {
type View = PrivacyPageView;
fn search_terms(&self) -> &str {
"sync cloud conversation store storage ai agent"
}
fn should_render(&self, app: &AppContext) -> bool {
if !FeatureFlag::CloudConversations.is_enabled() {
return false;
}
// Hide the toggle entirely when AI is disabled: the setting has no
// effect without AI (no agent conversations are produced), so showing
// it is confusing.
if !AISettings::as_ref(app).is_any_ai_enabled(app) {
return false;
}
let privacy_settings = PrivacySettings::as_ref(app);
!privacy_settings.is_telemetry_force_enabled()
}
fn render(
&self,
_view: &Self::View,
appearance: &Appearance,
app: &AppContext,
) -> Box<dyn Element> {
let ui_builder = appearance.ui_builder();
let privacy_settings = PrivacySettings::as_ref(app);
let org_setting =
UserWorkspaces::as_ref(app).get_cloud_conversation_storage_enablement_setting();
let (toggle_state, is_checked) = match org_setting {
AdminEnablementSetting::Enable => (ToggleState::Disabled, true),
AdminEnablementSetting::Disable => (ToggleState::Disabled, false),
AdminEnablementSetting::RespectUserSetting => (
ToggleState::Enabled,
privacy_settings.is_cloud_conversation_storage_enabled,
),
};
let switch = ui_builder
.switch(self.switch_state.clone())
.check(is_checked);
let switch = if matches!(toggle_state, ToggleState::Enabled) {
switch
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(PrivacyPageAction::ToggleCloudConversationStorage)
})
.finish()
} else {
switch
.with_tooltip(TooltipConfig {
text: "This setting is managed by your organization.".to_string(),
styles: ui_builder.default_tool_tip_styles(),
})
.disable()
.build()
.finish()
};
Flex::column()
.with_child(render_body_item::<PrivacyPageAction>(
"Store AI conversations in the cloud".into(),
None,
LocalOnlyIconState::Hidden,
toggle_state,
appearance,
switch,
None,
))
.with_child(
ui_builder
.paragraph(
if is_checked {
"Agent conversations can be shared with others and are retained \
when you log in on different devices. This data is only stored \
for product functionality, and Warp will not use it for analytics."
} else {
"Agent conversations are only stored locally on your machine, are \
lost upon logout, and cannot be shared. Note: conversation data \
for ambient agents are still stored in the cloud."
}
.to_owned(),
)
.with_style(UiComponentStyles {
font_color: Some(
appearance
.theme()
.sub_text_color(appearance.theme().surface_2())
.into_solid(),
),
margin: Some(
Coords::default()
.top(styles::DESCRIPTION_NEGATIVE_MARGIN_OFFSET)
.bottom(styles::DESCRIPTION_MARGIN_BOTTOM),
),
..Default::default()
})
.build()
.finish(),
)
.finish()
}
}
#[derive(Default)]
struct NetworkLogWidget {
link_mouse_state: MouseStateHandle,
@@ -1865,133 +1706,6 @@ impl SettingsWidget for NetworkLogWidget {
}
}
#[derive(Default)]
struct DataManagementWidget {
link_mouse_state: MouseStateHandle,
}
impl SettingsWidget for DataManagementWidget {
type View = PrivacyPageView;
fn search_terms(&self) -> &str {
"data management delete account"
}
fn render(
&self,
_view: &Self::View,
appearance: &Appearance,
_app: &AppContext,
) -> Box<dyn Element> {
let ui_builder = appearance.ui_builder();
Flex::column()
.with_child(render_body_item::<PrivacyPageAction>(
DATA_MANAGEMENT_TITLE.into(),
None,
// Not rendering a setting, so no need to show local only icon state.
LocalOnlyIconState::Hidden,
ToggleState::Enabled,
appearance,
Empty::new().finish(),
None,
))
.with_child(
ui_builder
.paragraph(DATA_MANAGEMENT_DESCRIPTION)
.with_style(UiComponentStyles {
font_color: Some(
appearance
.theme()
.sub_text_color(appearance.theme().surface_2())
.into_solid(),
),
margin: Some(
Coords::default()
.top(styles::DESCRIPTION_NEGATIVE_MARGIN_OFFSET)
.bottom(styles::DESCRIPTION_LINE_MARGIN_BOTTOM),
),
..Default::default()
})
.build()
.finish(),
)
.with_child(
Align::new(
appearance
.ui_builder()
.link(
DATA_MANAGEMENT_LINK_TEXT.into(),
None,
Some(Box::new(|ctx| {
ctx.dispatch_typed_action(
PrivacyPageAction::OpenDataManagementWebpage,
);
})),
self.link_mouse_state.clone(),
)
.soft_wrap(false)
.build()
.with_margin_bottom(styles::DESCRIPTION_MARGIN_BOTTOM)
.finish(),
)
.left()
.finish(),
)
.finish()
}
}
#[derive(Default)]
struct PrivacyPolicyWidget {
link_mouse_state: MouseStateHandle,
}
impl SettingsWidget for PrivacyPolicyWidget {
type View = PrivacyPageView;
fn search_terms(&self) -> &str {
"privacy policy terms"
}
fn render(
&self,
_view: &Self::View,
appearance: &Appearance,
_app: &AppContext,
) -> Box<dyn Element> {
Flex::column()
.with_child(render_body_item::<PrivacyPageAction>(
PRIVACY_POLICY_TITLE.into(),
None,
// Not rendering a setting, so no need to show local only icon state.
LocalOnlyIconState::Hidden,
ToggleState::Enabled,
appearance,
Empty::new().finish(),
None,
))
.with_child(
Align::new(
appearance
.ui_builder()
.link(
PRIVACY_POLICY_LINK_TEXT.into(),
Some(PRIVACY_POLICY_URL.into()),
None,
self.link_mouse_state.clone(),
)
.soft_wrap(false)
.build()
.with_margin_bottom(styles::DESCRIPTION_MARGIN_BOTTOM)
.finish(),
)
.left()
.finish(),
)
.finish()
}
}
pub fn init_actions_from_parent_view<T: Action + Clone>(
app: &mut AppContext,
context: &ContextPredicate,