diff --git a/crates/op-editor-ui/src/widgets/agent_settings_mcp.rs b/crates/op-editor-ui/src/widgets/agent_settings_mcp.rs index 9886532d5..f5595dcfa 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_mcp.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_mcp.rs @@ -99,11 +99,11 @@ fn cli_cell_rect(content: Rect, idx: usize) -> Rect { } } -pub fn hit_test(content: Rect, scrolled: Point2D) -> McpHit { +pub fn hit_test(content: Rect, settings: &AgentSettings, scrolled: Point2D) -> McpHit { if rect_contains(server_button_rect(content), scrolled) { return McpHit::ToggleServer; } - if rect_contains(port_field_rect(content), scrolled) { + if !settings.mcp_server.running && rect_contains(port_field_rect(content), scrolled) { return McpHit::FocusPort; } for (i, cli) in McpCli::ALL.iter().enumerate() { @@ -229,7 +229,8 @@ fn paint_server_card( Point2D::new(port_field_x - 8.0 - port_label_w, mid_y + 4.0), ); let port_field = port_field_rect(content); - let focused = matches!(settings.focus, Some(SettingsFocus::McpPort)); + let port_editable = !running; + let focused = port_editable && matches!(settings.focus, Some(SettingsFocus::McpPort)); let port_str = if focused { ui.settings_input_draft.clone() } else { @@ -247,7 +248,11 @@ fn paint_server_card( &port_str, "system-ui", 12.0, - to_jian(theme.foreground), + to_jian(if port_editable { + theme.foreground + } else { + theme.muted_foreground + }), Point2D::new(0.0, 0.0), ); let port_x = port_field.origin.x + (PORT_FIELD_W - port_w) / 2.0; diff --git a/crates/op-editor-ui/src/widgets/agent_settings_panel.rs b/crates/op-editor-ui/src/widgets/agent_settings_panel.rs index 769962406..b09be9c28 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_panel.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_panel.rs @@ -148,7 +148,7 @@ impl<'a> AgentSettingsPanel<'a> { } } AgentSettingsTab::Mcp => { - match agent_settings_mcp::hit_test(content_rect(panel), scrolled) { + match agent_settings_mcp::hit_test(content_rect(panel), &self.settings, scrolled) { McpHit::ToggleServer => return AgentSettingsHit::ToggleMcpServer, McpHit::ToggleCli(cli) => return AgentSettingsHit::ToggleMcpCli(cli), McpHit::FocusPort => return AgentSettingsHit::FocusMcpPort, diff --git a/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs b/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs index b6f568d38..bc99bbd07 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs @@ -131,3 +131,21 @@ fn hit_test_resolves_builtin_agent_compact_edit_button() { AgentSettingsHit::EditBuiltinAgent(0) ); } + +#[test] +fn mcp_port_field_is_not_focusable_while_server_is_running() { + let mut state = EditorState::default(); + state.editor_ui.agent_settings.tab = AgentSettingsTab::Mcp; + state.editor_ui.agent_settings.mcp_server.running = true; + let panel = AgentSettingsPanel::for_editor(&state); + let rect = panel.rect(1200.0, 800.0); + let content_x = rect.origin.x + 200.0 + 24.0; + let content_y = rect.origin.y + 24.0; + let content_w = rect.size.x - 200.0 - 48.0; + let server_card_top = content_y + 36.0; + let button_x = content_x + content_w - 16.0 - 72.0; + let port_x = button_x - 8.0 - 64.0; + let point = crate::Point2D::new(port_x + 32.0, server_card_top + 26.0); + + assert_eq!(panel.hit_test(rect, point), AgentSettingsHit::Inside); +} diff --git a/crates/op-host-native/src/widget_host/agent_settings_tests.rs b/crates/op-host-native/src/widget_host/agent_settings_tests.rs index 3767d09f8..335fa1079 100644 --- a/crates/op-host-native/src/widget_host/agent_settings_tests.rs +++ b/crates/op-host-native/src/widget_host/agent_settings_tests.rs @@ -1,5 +1,5 @@ use super::WidgetHostNative; -use op_editor_core::agent_settings::{BuiltinAgentField, SettingsFocus}; +use op_editor_core::agent_settings::{AgentSettingsTab, BuiltinAgentField, SettingsFocus}; use op_editor_ui::widgets::agent_settings_panel::AgentSettingsPanel; #[test] @@ -109,3 +109,31 @@ fn builtin_agent_compact_edit_focuses_display_name_form() { "MiniMax" ); } + +#[test] +fn starting_mcp_server_commits_port_draft_and_clears_focus() { + let mut host = WidgetHostNative::new(); + host.editor_state_mut().editor_ui.agent_settings.tab = AgentSettingsTab::Mcp; + host.editor_state_mut().editor_ui.agent_settings.focus = Some(SettingsFocus::McpPort); + host.editor_state_mut().editor_ui.settings_input_draft = "3101".into(); + + let panel = AgentSettingsPanel::for_editor(host.editor_state()); + let rect = panel.rect(1200.0, 800.0); + let content_x = rect.origin.x + 200.0 + 24.0; + let content_y = rect.origin.y + 24.0; + let content_w = rect.size.x - 200.0 - 48.0; + let server_card_top = content_y + 36.0; + let button_x = content_x + content_w - 16.0 - 72.0; + assert!(host.dispatch_agent_settings_press( + button_x + 36.0, + server_card_top + 26.0, + 1200.0, + 800.0 + )); + + let state = host.editor_state(); + assert!(state.editor_ui.agent_settings.mcp_server.running); + assert_eq!(state.editor_ui.agent_settings.mcp_server.port, 3101); + assert!(state.editor_ui.agent_settings.focus.is_none()); + assert!(state.editor_ui.settings_input_draft.is_empty()); +} diff --git a/crates/op-host-native/src/widget_host/press_helpers.rs b/crates/op-host-native/src/widget_host/press_helpers.rs index 34eb2ea07..312d49e97 100644 --- a/crates/op-host-native/src/widget_host/press_helpers.rs +++ b/crates/op-host-native/src/widget_host/press_helpers.rs @@ -77,6 +77,7 @@ impl WidgetHostNative { self.editor_state.rebuild_chat_models(); } AgentSettingsHit::ToggleMcpServer => { + self.commit_settings_focus_if_any(); let v = &mut self .editor_state .editor_ui diff --git a/crates/op-host-web/src/widget_host/press.rs b/crates/op-host-web/src/widget_host/press.rs index 0512bb4fe..b27f4fd50 100644 --- a/crates/op-host-web/src/widget_host/press.rs +++ b/crates/op-host-web/src/widget_host/press.rs @@ -760,6 +760,7 @@ impl WidgetHost { self.editor_state.rebuild_chat_models(); } AgentSettingsHit::ToggleMcpServer => { + self.commit_settings_focus(); self.editor_state .editor_ui .agent_settings