feat: experimental menu (#8071)

This will automatically render any `Stage::Beta` features.

The change only gets applied to the *next session*. This started as a
bug but actually this is a good thing to prevent out of distribution
push

<img width="986" height="288" alt="Screenshot 2025-12-15 at 15 38 35"
src="https://github.com/user-attachments/assets/78b7a71d-0e43-4828-a118-91c5237909c7"
/>


<img width="509" height="109" alt="Screenshot 2025-12-15 at 17 35 44"
src="https://github.com/user-attachments/assets/6933de52-9b66-4abf-b58b-a5f26d5747e2"
/>
This commit is contained in:
jif-oai
2025-12-17 17:08:03 +00:00
committed by GitHub
parent 9352c6b235
commit ac6ba286aa
15 changed files with 577 additions and 38 deletions

View File

@@ -1738,6 +1738,71 @@ fn render_bottom_popup(chat: &ChatWidget, width: u16) -> String {
lines.join("\n")
}
#[test]
fn experimental_features_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None);
let features = vec![
BetaFeatureItem {
feature: Feature::GhostCommit,
name: "Ghost snapshots".to_string(),
description: "Capture undo snapshots each turn.".to_string(),
enabled: false,
},
BetaFeatureItem {
feature: Feature::ShellTool,
name: "Shell tool".to_string(),
description: "Allow the model to run shell commands.".to_string(),
enabled: true,
},
];
let view = ExperimentalFeaturesView::new(features, chat.app_event_tx.clone());
chat.bottom_pane.show_view(Box::new(view));
let popup = render_bottom_popup(&chat, 80);
assert_snapshot!("experimental_features_popup", popup);
}
#[test]
fn experimental_features_toggle_saves_on_exit() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None);
let expected_feature = Feature::GhostCommit;
let view = ExperimentalFeaturesView::new(
vec![BetaFeatureItem {
feature: expected_feature,
name: "Ghost snapshots".to_string(),
description: "Capture undo snapshots each turn.".to_string(),
enabled: false,
}],
chat.app_event_tx.clone(),
);
chat.bottom_pane.show_view(Box::new(view));
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert!(
rx.try_recv().is_err(),
"expected no updates until exiting the popup"
);
chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
let mut updates = None;
while let Ok(event) = rx.try_recv() {
if let AppEvent::UpdateFeatureFlags {
updates: event_updates,
} = event
{
updates = Some(event_updates);
break;
}
}
let updates = updates.expect("expected UpdateFeatureFlags event");
assert_eq!(updates, vec![(expected_feature, true)]);
}
#[test]
fn model_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5-codex"));