Files
codex/codex-rs/tui/src/chatwidget/permissions_menu.rs
T
Eric Traut 1333f4a689 Align TUI permissions labels with app (#25017)
## Summary

The desktop app now presents the on-request permissions mode as `Ask for
approval` and the manual-review-backed mode as `Approve for me`. The TUI
still exposed older/internal labels like `Default` and `Auto-review`,
which made the same underlying settings look different across clients.

This updates the TUI UX copy to match the app without changing the
underlying default behavior. Fresh threads continue to use the existing
on-request approval mode, now displayed as `Ask for approval`.

The label changes cover `/permissions`, explicit profile permissions
menus, status surfaces, config persistence history/error text, and the
corresponding TUI snapshots.

### Before
<img width="1181" height="119" alt="Screenshot 2026-05-28 at 10 19
47 PM"
src="https://github.com/user-attachments/assets/0664846b-b6dd-4931-b4dd-d0af0d42058e"
/>
<img width="523" height="19" alt="Screenshot 2026-05-28 at 10 21 29 PM"
src="https://github.com/user-attachments/assets/7899c33e-b35d-4684-8389-97e357803423"
/>

### After
<img width="1216" height="117" alt="Screenshot 2026-05-28 at 10 19
32 PM"
src="https://github.com/user-attachments/assets/015aab43-ac97-411f-8031-75cdd887251b"
/>
<img width="567" height="18" alt="Screenshot 2026-05-28 at 10 20 24 PM"
src="https://github.com/user-attachments/assets/28b6422c-b823-4298-b221-c83d46d09d66"
/>
2026-05-29 11:06:40 -07:00

172 lines
6.1 KiB
Rust

use super::*;
impl ChatWidget {
pub(super) fn open_permission_profiles_popup(&mut self) {
let active_profile_id = self
.config
.permissions
.active_permission_profile()
.map(|profile| profile.id);
let presets = builtin_approval_presets();
let Some(read_only) = presets.iter().find(|preset| preset.id == "read-only") else {
self.add_error_message(
"Internal error: missing the 'read-only' approval preset.".to_string(),
);
return;
};
let Some(default) = presets.iter().find(|preset| preset.id == "auto") else {
self.add_error_message(
"Internal error: missing the 'auto' approval preset.".to_string(),
);
return;
};
let Some(full_access) = presets.iter().find(|preset| preset.id == "full-access") else {
self.add_error_message(
"Internal error: missing the 'full-access' approval preset.".to_string(),
);
return;
};
let mut items = vec![
self.builtin_permission_mode_selection_item(
default,
":workspace",
default
.description
.replace(" (Identical to Agent mode)", ""),
AskForApproval::from(default.approval),
ApprovalsReviewer::User,
),
];
if self.config.features.enabled(Feature::GuardianApproval) {
items.push(self.builtin_permission_mode_selection_item(
default,
":workspace",
AUTO_REVIEW_DESCRIPTION.to_string(),
AskForApproval::OnRequest,
ApprovalsReviewer::AutoReview,
));
}
items.push(self.builtin_permission_mode_selection_item(
full_access,
":danger-no-sandbox",
full_access.description.to_string(),
AskForApproval::from(full_access.approval),
ApprovalsReviewer::User,
));
items.push(self.builtin_permission_mode_selection_item(
read_only,
":read-only",
read_only.description.to_string(),
AskForApproval::from(read_only.approval),
ApprovalsReviewer::User,
));
items.extend(
self.config
.custom_permission_profiles
.iter()
.map(|profile| {
Self::permission_profile_selection_item(
&profile.id,
&profile.id,
profile
.description
.as_deref()
.unwrap_or("Configured permission profile."),
active_profile_id.as_deref(),
)
}),
);
self.bottom_pane.show_selection_view(SelectionViewParams {
title: Some("Update Model Permissions".to_string()),
footer_hint: Some(standard_popup_hint_line()),
items,
header: Box::new(()),
..Default::default()
});
}
fn builtin_permission_mode_selection_item(
&self,
preset: &ApprovalPreset,
id: &str,
description: String,
approval_policy: AskForApproval,
approvals_reviewer: ApprovalsReviewer,
) -> SelectionItem {
let label = match (preset.id, approvals_reviewer) {
("auto", ApprovalsReviewer::AutoReview) => APPROVE_FOR_ME_LABEL,
("auto", ApprovalsReviewer::User) => ASK_FOR_APPROVAL_LABEL,
_ => preset.label,
};
let active_profile_id = self
.config
.permissions
.active_permission_profile()
.map(|profile| profile.id);
let current_approval =
AskForApproval::from(self.config.permissions.approval_policy.value());
let current_reviewer = self.config.approvals_reviewer;
let profile_id = id.to_string();
let selection = PermissionProfileSelection {
profile_id,
approval_policy: Some(approval_policy),
approvals_reviewer: Some(approvals_reviewer),
display_label: label.to_string(),
};
SelectionItem {
name: label.to_string(),
description: Some(description),
is_current: active_profile_id.as_deref() == Some(id)
&& current_approval == approval_policy
&& current_reviewer == approvals_reviewer,
actions: self.permission_mode_actions(
preset,
label.to_string(),
approvals_reviewer,
Some(selection),
/*return_to_permissions*/ true,
),
dismiss_on_select: true,
disabled_reason: self
.config
.permissions
.approval_policy
.can_set(&approval_policy.to_core())
.err()
.map(|err| err.to_string())
.or_else(|| {
self.config
.permissions
.can_set_permission_profile(&preset.permission_profile)
.err()
.map(|err| err.to_string())
}),
..Default::default()
}
}
fn permission_profile_selection_item(
label: &str,
id: &str,
description: &str,
active_profile_id: Option<&str>,
) -> SelectionItem {
let id_for_action = id.to_string();
let selection = PermissionProfileSelection {
profile_id: id_for_action.clone(),
approval_policy: None,
approvals_reviewer: None,
display_label: id_for_action,
};
SelectionItem {
name: label.to_string(),
description: Some(description.to_string()),
is_current: active_profile_id == Some(id),
actions: Self::permission_profile_selection_actions(selection),
dismiss_on_select: true,
..Default::default()
}
}
}