From 8d190eddda2c74afa3dc043f8ec8020ffd7b5a94 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sat, 1 Aug 2026 03:36:11 +0300 Subject: [PATCH] chore: remove experiments from settings tui --- docs/next/CHANGELOG.md | 1 + .../src/content/docs/ja/session-state.mdx | 2 +- .../src/content/docs/session-state.mdx | 2 +- .../src/content/docs/zh-cn/session-state.mdx | 2 +- src/app/config_io.rs | 21 --- src/app/input/mod.rs | 6 - src/app/input/settings.rs | 155 +----------------- src/app/mod.rs | 25 --- src/app/state.rs | 39 ----- src/ui/settings.rs | 125 +------------- 10 files changed, 10 insertions(+), 368 deletions(-) diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 5bdc5cc2..8296adf9 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -6,6 +6,7 @@ - Added `ui.tab_bar_position = "bottom"` to place the desktop tab row below terminal panes. ### Changed +- Experimental options are no longer exposed in the Settings TUI and remain available through the config file. - Agent status indicators now use the same static workspace marks across the sidebar, navigator, and mobile views, eliminating continuous spinner rendering while agents work. - Relicensed Herdr from AGPL-3.0-or-later to Apache-2.0. diff --git a/docs/next/website/src/content/docs/ja/session-state.mdx b/docs/next/website/src/content/docs/ja/session-state.mdx index 2567fc3f..ed3e73a6 100644 --- a/docs/next/website/src/content/docs/ja/session-state.mdx +++ b/docs/next/website/src/content/docs/ja/session-state.mdx @@ -38,7 +38,7 @@ Herdr サーバーが停止して再起動すると、元のペインのプロ ペイン画面履歴は、サーバーの完全な再起動後に直近のターミナル内容を復元します。復元されるのは Herdr が表示できるものであって、元のプロセスではありません。 -ペイン出力にはシークレット、トークン、プロンプト、コマンド出力が含まれうるため、これはデフォルトで無効です。Settings > Experiments > pane screen history から、または次の設定で有効にします: +ペイン出力にはシークレット、トークン、プロンプト、コマンド出力が含まれうるため、これはデフォルトで無効です。設定ファイルで有効にします: ```toml [experimental] diff --git a/docs/next/website/src/content/docs/session-state.mdx b/docs/next/website/src/content/docs/session-state.mdx index 87987b2d..222e280b 100644 --- a/docs/next/website/src/content/docs/session-state.mdx +++ b/docs/next/website/src/content/docs/session-state.mdx @@ -38,7 +38,7 @@ Snapshot restore does not preserve running shells, servers, tests, or arbitrary Pane screen history restores recent terminal contents after a full server restart. It restores what Herdr can show, not the old process. -This is off by default because pane output can include secrets, tokens, prompts, and command output. Enable it from Settings > Experiments > pane screen history or with: +This is off by default because pane output can include secrets, tokens, prompts, and command output. Enable it in the config file: ```toml [experimental] diff --git a/docs/next/website/src/content/docs/zh-cn/session-state.mdx b/docs/next/website/src/content/docs/zh-cn/session-state.mdx index cf796d0c..1c4663ae 100644 --- a/docs/next/website/src/content/docs/zh-cn/session-state.mdx +++ b/docs/next/website/src/content/docs/zh-cn/session-state.mdx @@ -38,7 +38,7 @@ herdr 窗格屏幕历史在服务器完全重启后恢复最近的终端内容。它恢复的是 Herdr 能展示的内容,而不是原来的进程。 -它默认关闭,因为窗格输出可能包含密钥、令牌、提示词和命令输出。可以在 Settings > Experiments > pane screen history 中开启,或者: +它默认关闭,因为窗格输出可能包含密钥、令牌、提示词和命令输出。可以在配置文件中开启: ```toml [experimental] diff --git a/src/app/config_io.rs b/src/app/config_io.rs index 95014b30..59077b6f 100644 --- a/src/app/config_io.rs +++ b/src/app/config_io.rs @@ -92,27 +92,6 @@ impl App { } } - pub(super) fn save_pane_history_persistence(&mut self, enabled: bool) { - if self.update_config_file("pane screen history", |content| { - crate::config::upsert_section_bool(content, "experimental", "pane_history", enabled) - }) { - self.apply_config_from_disk(false); - } - } - - pub(super) fn save_switch_ascii_input_source_in_prefix(&mut self, enabled: bool) { - if self.update_config_file("prefix ascii input source", |content| { - crate::config::upsert_section_bool( - content, - "experimental", - "switch_ascii_input_source_in_prefix", - enabled, - ) - }) { - self.apply_config_from_disk(false); - } - } - pub(super) fn save_agent_panel_sort(&mut self, sort: crate::app::state::AgentPanelSort) { let value = match sort { crate::app::state::AgentPanelSort::Spaces => { diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs index 570e8b8c..40bc82fb 100644 --- a/src/app/input/mod.rs +++ b/src/app/input/mod.rs @@ -344,12 +344,6 @@ impl App { SettingsAction::SaveAgentBorderLabels(enabled) => { self.save_agent_border_labels(enabled) } - SettingsAction::SavePaneHistory(enabled) => { - self.save_pane_history_persistence(enabled) - } - SettingsAction::SaveSwitchAsciiInputSourceInPrefix(enabled) => { - self.save_switch_ascii_input_source_in_prefix(enabled) - } SettingsAction::InstallRecommendedIntegrations => { self.install_recommended_integrations() } diff --git a/src/app/input/settings.rs b/src/app/input/settings.rs index 9651b075..326d841b 100644 --- a/src/app/input/settings.rs +++ b/src/app/input/settings.rs @@ -3,7 +3,7 @@ use ratatui::layout::Rect; use crate::{ app::{ - state::{AppState, ExperimentSetting, SettingsSection, THEME_NAMES}, + state::{AppState, SettingsSection, THEME_NAMES}, App, Mode, }, config::ToastDelivery, @@ -17,25 +17,9 @@ pub(super) enum SettingsAction { SaveSound(bool), SaveToastDelivery(ToastDelivery), SaveAgentBorderLabels(bool), - SavePaneHistory(bool), - SaveSwitchAsciiInputSourceInPrefix(bool), InstallRecommendedIntegrations, } -/// Map an Experiments row index to the toggle action that flips it. -fn experiment_toggle_action(state: &AppState, idx: usize) -> Option { - match ExperimentSetting::ALL.get(idx).copied()? { - ExperimentSetting::PaneHistory => Some(SettingsAction::SavePaneHistory( - !ExperimentSetting::PaneHistory.enabled(state), - )), - ExperimentSetting::SwitchAsciiInputSourceInPrefix => { - Some(SettingsAction::SaveSwitchAsciiInputSourceInPrefix( - !ExperimentSetting::SwitchAsciiInputSourceInPrefix.enabled(state), - )) - } - } -} - impl App { pub(crate) fn handle_settings_key(&mut self, key: KeyEvent) { let previous_section = self.state.settings.section; @@ -47,12 +31,6 @@ impl App { SettingsAction::SaveAgentBorderLabels(enabled) => { self.save_agent_border_labels(enabled) } - SettingsAction::SavePaneHistory(enabled) => { - self.save_pane_history_persistence(enabled) - } - SettingsAction::SaveSwitchAsciiInputSourceInPrefix(enabled) => { - self.save_switch_ascii_input_source_in_prefix(enabled) - } SettingsAction::InstallRecommendedIntegrations => { self.install_recommended_integrations() } @@ -171,7 +149,7 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti state.settings.list.selected = usize::from(!state.sound_enabled()); } KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => { - state.settings.section = SettingsSection::Experiments; + state.settings.section = SettingsSection::Integrations; state.settings.list.selected = 0; } _ => match super::modal::modal_action_from_key(&key, super::modal::SETTINGS_ACTIONS) { @@ -251,30 +229,6 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti } } }, - SettingsSection::Experiments => match key.code { - KeyCode::Up | KeyCode::Char('k') => state.settings.list.move_prev(), - KeyCode::Down | KeyCode::Char('j') => { - state.settings.list.move_next(ExperimentSetting::ALL.len()) - } - KeyCode::Enter | KeyCode::Char(' ') => { - return experiment_toggle_action(state, state.settings.list.selected); - } - KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => { - state.settings.section = SettingsSection::Integrations; - state.settings.list.selected = 0; - } - KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => { - state.settings.section = SettingsSection::Theme; - state.settings.list.selected = current_theme_index(&state.theme_name); - } - _ => { - if let Some(super::modal::ModalAction::Close) = - super::modal::modal_action_from_key(&key, super::modal::SETTINGS_ACTIONS) - { - cancel_settings(state); - } - } - }, SettingsSection::Integrations => match key.code { KeyCode::Enter | KeyCode::Char(' ') if integrations_need_install(state) => { return Some(SettingsAction::InstallRecommendedIntegrations); @@ -284,8 +238,8 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti state.settings.list.selected = usize::from(!state.agent_border_labels_enabled()); } KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => { - state.settings.section = SettingsSection::Experiments; - state.settings.list.selected = 0; + state.settings.section = SettingsSection::Theme; + state.settings.list.selected = current_theme_index(&state.theme_name); } _ => match super::modal::modal_action_from_key(&key, super::modal::SETTINGS_ACTIONS) { Some(super::modal::ModalAction::Apply) => return apply_settings(state), @@ -312,7 +266,6 @@ pub(crate) fn open_settings_at(state: &mut AppState, section: SettingsSection) { SettingsSection::Sound => usize::from(!state.sound_enabled()), SettingsSection::Toast => toast_delivery_index(state.toast_delivery()), SettingsSection::PaneLabels => usize::from(!state.agent_border_labels_enabled()), - SettingsSection::Experiments => 0, SettingsSection::Integrations => 0, }; state.mode = Mode::Settings; @@ -407,14 +360,6 @@ impl AppState { None } } - SettingsSection::Experiments => { - let list_y = area.y + 3; - if row >= list_y && row < list_y + ExperimentSetting::ALL.len() as u16 { - Some((row - list_y) as usize) - } else { - None - } - } SettingsSection::Integrations => None, } } @@ -431,7 +376,6 @@ impl AppState { SettingsSection::PaneLabels => { usize::from(!self.agent_border_labels_enabled()) } - SettingsSection::Experiments => 0, SettingsSection::Integrations => 0, }); return None; @@ -455,7 +399,6 @@ impl AppState { let enabled = idx == 0; Some(SettingsAction::SaveAgentBorderLabels(enabled)) } - SettingsSection::Experiments => experiment_toggle_action(self, idx), SettingsSection::Integrations => None, }; } @@ -543,46 +486,7 @@ mod tests { } #[test] - fn settings_experiments_toggles_pane_history() { - let mut state = state_with_workspaces(&["test"]); - state.pane_history_persistence = false; - open_settings_at(&mut state, SettingsSection::Experiments); - - let action = update_settings_state( - &mut state, - KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), - ); - - assert_eq!(action, Some(SettingsAction::SavePaneHistory(true))); - assert_eq!(state.mode, Mode::Settings); - } - - #[test] - fn settings_experiments_down_then_toggle_switches_ascii_input_source() { - let mut state = state_with_workspaces(&["test"]); - state.switch_ascii_input_source_in_prefix = false; - open_settings_at(&mut state, SettingsSection::Experiments); - - update_settings_state( - &mut state, - KeyEvent::new(KeyCode::Down, KeyModifiers::empty()), - ); - assert_eq!(state.settings.list.selected, 1); - - let action = update_settings_state( - &mut state, - KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), - ); - - assert_eq!( - action, - Some(SettingsAction::SaveSwitchAsciiInputSourceInPrefix(true)) - ); - assert_eq!(state.mode, Mode::Settings); - } - - #[test] - fn settings_tab_cycle_places_experiments_last() { + fn settings_tab_cycle_wraps_after_integrations() { let mut state = state_with_workspaces(&["test"]); open_settings_at(&mut state, SettingsSection::PaneLabels); @@ -592,24 +496,12 @@ mod tests { ); assert_eq!(state.settings.section, SettingsSection::Integrations); - update_settings_state( - &mut state, - KeyEvent::new(KeyCode::Tab, KeyModifiers::empty()), - ); - assert_eq!(state.settings.section, SettingsSection::Experiments); - update_settings_state( &mut state, KeyEvent::new(KeyCode::Tab, KeyModifiers::empty()), ); assert_eq!(state.settings.section, SettingsSection::Theme); - update_settings_state( - &mut state, - KeyEvent::new(KeyCode::BackTab, KeyModifiers::empty()), - ); - assert_eq!(state.settings.section, SettingsSection::Experiments); - update_settings_state( &mut state, KeyEvent::new(KeyCode::BackTab, KeyModifiers::empty()), @@ -653,43 +545,6 @@ mod tests { assert_eq!(app.state.settings.list.selected, 0); } - #[test] - fn settings_mouse_click_toggles_pane_history() { - let mut app = app_for_mouse_test(); - app.state.pane_history_persistence = false; - open_settings_at(&mut app.state, SettingsSection::Experiments); - - let area = app.state.settings_content_rect(); - let action = app.state.handle_settings_mouse(mouse( - MouseEventKind::Down(crossterm::event::MouseButton::Left), - area.x + 2, - area.y + 3, - )); - - assert_eq!(action, Some(SettingsAction::SavePaneHistory(true))); - assert_eq!(app.state.settings.list.selected, 0); - } - - #[test] - fn settings_mouse_click_toggles_switch_ascii_input_source_row() { - let mut app = app_for_mouse_test(); - app.state.switch_ascii_input_source_in_prefix = false; - open_settings_at(&mut app.state, SettingsSection::Experiments); - - let area = app.state.settings_content_rect(); - let action = app.state.handle_settings_mouse(mouse( - MouseEventKind::Down(crossterm::event::MouseButton::Left), - area.x + 2, - area.y + 4, - )); - - assert_eq!( - action, - Some(SettingsAction::SaveSwitchAsciiInputSourceInPrefix(true)) - ); - assert_eq!(app.state.settings.list.selected, 1); - } - #[test] fn integration_update_badge_only_tracks_outdated_recommendations() { let mut state = state_with_workspaces(&["test"]); diff --git a/src/app/mod.rs b/src/app/mod.rs index f4bc5008..442df528 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3326,31 +3326,6 @@ mod tests { let _ = std::fs::remove_dir_all(path.parent().unwrap()); } - #[test] - fn settings_save_pane_history_persists_then_applies_live_config() { - let _guard = config_env_lock().lock().unwrap(); - let path = temp_config_path("settings-save-pane-history"); - std::fs::create_dir_all(path.parent().unwrap()).unwrap(); - std::fs::write(&path, "onboarding = false\n").unwrap(); - std::env::set_var(crate::config::CONFIG_PATH_ENV_VAR, &path); - - let mut app = test_app(); - assert!(!app.persist_pane_history); - assert!(!app.state.pane_history_persistence); - - app.save_pane_history_persistence(true); - - assert!(app.persist_pane_history); - assert!(app.state.pane_history_persistence); - let content = std::fs::read_to_string(&path).unwrap(); - assert!(content.contains("[experimental]")); - assert!(content.contains("pane_history = true")); - assert!(app.state.config_diagnostic.is_none()); - - std::env::remove_var(crate::config::CONFIG_PATH_ENV_VAR); - let _ = std::fs::remove_dir_all(path.parent().unwrap()); - } - #[test] fn reload_config_keeps_current_state_on_invalid_toml() { let _guard = config_env_lock().lock().unwrap(); diff --git a/src/app/state.rs b/src/app/state.rs index 975144b4..602bd9a4 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -994,7 +994,6 @@ pub enum SettingsSection { Sound, Toast, PaneLabels, - Experiments, Integrations, } @@ -1005,7 +1004,6 @@ impl SettingsSection { Self::Toast, Self::PaneLabels, Self::Integrations, - Self::Experiments, ]; pub fn label(self) -> &'static str { @@ -1014,40 +1012,11 @@ impl SettingsSection { Self::Sound => "sound", Self::Toast => "toasts", Self::PaneLabels => "pane labels", - Self::Experiments => "experiments", Self::Integrations => "integrations", } } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ExperimentSetting { - PaneHistory, - SwitchAsciiInputSourceInPrefix, -} - -impl ExperimentSetting { - pub(crate) const ALL: [Self; 2] = [Self::PaneHistory, Self::SwitchAsciiInputSourceInPrefix]; - - pub(crate) fn label(self) -> &'static str { - match self { - Self::PaneHistory => "pane screen history", - Self::SwitchAsciiInputSourceInPrefix => { - "switch to ascii input source in prefix (macOS/Windows)" - } - } - } - - pub(crate) fn enabled(self, state: &AppState) -> bool { - match self { - Self::PaneHistory => state.pane_history_persistence_enabled(), - Self::SwitchAsciiInputSourceInPrefix => { - state.switch_ascii_input_source_in_prefix_enabled() - } - } - } -} - /// All built-in theme names in display order. pub const THEME_NAMES: &[&str] = &[ "catppuccin", @@ -1624,14 +1593,6 @@ impl AppState { self.show_agent_labels_on_pane_borders } - pub fn pane_history_persistence_enabled(&self) -> bool { - self.pane_history_persistence - } - - pub fn switch_ascii_input_source_in_prefix_enabled(&self) -> bool { - self.switch_ascii_input_source_in_prefix - } - pub(crate) fn pane_exposes_host_cursor( &self, _ws_idx: usize, diff --git a/src/ui/settings.rs b/src/ui/settings.rs index 8177d874..5052c91c 100644 --- a/src/ui/settings.rs +++ b/src/ui/settings.rs @@ -11,10 +11,7 @@ use super::widgets::{ render_action_button, render_modal_choice_list, render_panel_shell, ActionButtonSpec, }; use crate::{ - app::{ - state::{ExperimentSetting, Palette}, - AppState, - }, + app::{state::Palette, AppState}, config::ToastDelivery, }; @@ -149,9 +146,6 @@ pub(super) fn render_settings_overlay(app: &AppState, frame: &mut Frame, area: R app.settings.list.selected, ); } - SettingsSection::Experiments => { - render_settings_experiments(app, frame, content_area); - } SettingsSection::Integrations => { render_settings_integrations(app, frame, content_area); } @@ -412,120 +406,3 @@ fn render_settings_toggle( 1, ); } - -fn render_settings_experiments(app: &AppState, frame: &mut Frame, area: Rect) { - let p = &app.palette; - let [desc_area, _, list_area] = Layout::vertical([ - Constraint::Length(2), - Constraint::Length(1), - Constraint::Min(1), - ]) - .areas::<3>(area); - - super::widgets::render_modal_description( - frame, - desc_area, - "optional features that are off by default", - Style::default().fg(p.overlay1), - ); - - for (idx, setting) in ExperimentSetting::ALL.iter().copied().enumerate() { - let marker = if setting.enabled(app) { "[✓]" } else { "[ ]" }; - let style = if app.settings.list.selected == idx { - Style::default() - .bg(p.surface0) - .fg(p.text) - .add_modifier(Modifier::BOLD) - } else { - Style::default().fg(p.subtext0) - }; - let row = Rect::new(list_area.x, list_area.y + idx as u16, list_area.width, 1); - frame.render_widget( - Paragraph::new(format!(" {} {marker}", setting.label())).style(style), - row, - ); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::app::{state::SettingsSection, Mode}; - use ratatui::{backend::TestBackend, Terminal}; - - #[test] - fn experiments_pane_history_uses_settings_checkmark_marker() { - let mut app = AppState::test_new(); - app.pane_history_persistence = true; - app.settings.section = SettingsSection::Experiments; - app.settings.list.selected = 0; - app.mode = Mode::Settings; - - let mut terminal = - Terminal::new(TestBackend::new(80, 24)).expect("test terminal should initialize"); - terminal - .draw(|frame| render_settings_overlay(&app, frame, Rect::new(0, 0, 80, 24))) - .expect("settings overlay should render"); - - let rendered = terminal - .backend() - .buffer() - .content() - .iter() - .map(|cell| cell.symbol()) - .collect::(); - - assert!(rendered.contains("pane screen history [✓]")); - assert!(!rendered.contains("[x]")); - } - - #[test] - fn experiments_pane_history_keeps_empty_checkbox_marker_when_disabled() { - let mut app = AppState::test_new(); - app.pane_history_persistence = false; - app.settings.section = SettingsSection::Experiments; - app.settings.list.selected = 0; - app.mode = Mode::Settings; - - let mut terminal = - Terminal::new(TestBackend::new(80, 24)).expect("test terminal should initialize"); - terminal - .draw(|frame| render_settings_overlay(&app, frame, Rect::new(0, 0, 80, 24))) - .expect("settings overlay should render"); - - let rendered = terminal - .backend() - .buffer() - .content() - .iter() - .map(|cell| cell.symbol()) - .collect::(); - - assert!(rendered.contains("pane screen history [ ]")); - } - - #[test] - fn experiments_renders_switch_ascii_input_source_row() { - let mut app = AppState::test_new(); - app.switch_ascii_input_source_in_prefix = true; - app.settings.section = SettingsSection::Experiments; - app.settings.list.selected = 1; - app.mode = Mode::Settings; - - let mut terminal = - Terminal::new(TestBackend::new(80, 24)).expect("test terminal should initialize"); - terminal - .draw(|frame| render_settings_overlay(&app, frame, Rect::new(0, 0, 80, 24))) - .expect("settings overlay should render"); - - let rendered = terminal - .backend() - .buffer() - .content() - .iter() - .map(|cell| cell.symbol()) - .collect::(); - - assert!(rendered.contains("switch to ascii input source in prefix (macOS/Windows) [✓]")); - } -}