diff --git a/crates/tui/src/dashboard/mod.rs b/crates/tui/src/dashboard/mod.rs index fb7106d7..87c6e974 100644 --- a/crates/tui/src/dashboard/mod.rs +++ b/crates/tui/src/dashboard/mod.rs @@ -136,25 +136,33 @@ pub(crate) async fn launch(runtime_command: PodRuntimeCommand) -> Result<(), Box runtime_command.clone(), ) .await; - if let Err(error) = result { - app.finish_open(&pod_name, Err(&error)); + if let Err(error) = finish_nested_console_open(&mut app, &pod_name, result) { crate::console::leave_dashboard_fullscreen(&mut terminal)?; - if crate::console::is_recoverable_dashboard_open_error(error.as_ref()) { - return Ok(()); - } return Err(error); } - app.finish_open(&pod_name, Ok(())); - app = load_app(runtime_command.clone()).await?; - let key = PanelRowKey::Pod(pod_name); - if app.panel.row(&key).is_some() { - app.select_panel_key(key); - } } } } } +fn finish_nested_console_open( + app: &mut DashboardApp, + pod_name: &str, + result: Result<(), Box>, +) -> Result<(), Box> { + match result { + Ok(()) => { + app.finish_open(pod_name, Ok(())); + Ok(()) + } + Err(error) if crate::console::is_recoverable_dashboard_open_error(error.as_ref()) => { + app.finish_open(pod_name, Err(error.as_ref())); + Ok(()) + } + Err(error) => Err(error), + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct OpenPodRequest { pub(crate) pod_name: String, diff --git a/crates/tui/src/dashboard/tests.rs b/crates/tui/src/dashboard/tests.rs index dd911f36..c8e67469 100644 --- a/crates/tui/src/dashboard/tests.rs +++ b/crates/tui/src/dashboard/tests.rs @@ -2174,18 +2174,35 @@ fn dashboard_loading_app_defers_initial_snapshot_to_enter_reload() { } #[test] -fn dashboard_open_success_requests_background_reload_without_dropping_state() { - let mut app = test_app(vec![live_info("alpha", PodStatus::Idle)]); +fn dashboard_nested_console_success_continues_without_dropping_state() { + let mut app = test_app(vec![ + live_info("alpha", PodStatus::Idle), + live_info("beta", PodStatus::Idle), + ]); + app.select_next(); app.input.insert_str("keep this draft"); + app.panel_diagnostic = Some(PanelDiagnostic { + title: "diagnostic stays".to_string(), + details: "details stay".to_string(), + }); + app.panel_diagnostic_open = true; - app.finish_open("alpha", Ok(())); + finish_nested_console_open(&mut app, "beta", Ok(())).unwrap(); assert_eq!(input_text(&app), "keep this draft"); + assert_eq!(app.list.selected_entry().unwrap().name, "beta"); + assert_eq!( + app.panel_diagnostic + .as_ref() + .map(|diagnostic| (diagnostic.title.as_str(), diagnostic.details.as_str(),)), + Some(("diagnostic stays", "details stay")) + ); + assert!(app.panel_diagnostic_open); assert!( app.notice .as_deref() .unwrap() - .contains("Refreshing workspace") + .contains("Returned from beta. Refreshing workspace") ); assert!(app.refreshing); assert!(matches!( @@ -2194,6 +2211,61 @@ fn dashboard_open_success_requests_background_reload_without_dropping_state() { )); } +#[test] +fn dashboard_nested_console_recoverable_failure_continues_without_dropping_state() { + let mut app = test_app(vec![ + live_info("alpha", PodStatus::Idle), + live_info("beta", PodStatus::Idle), + ]); + app.select_next(); + app.input.insert_str("keep this draft"); + app.panel_diagnostic = Some(PanelDiagnostic { + title: "diagnostic stays".to_string(), + details: "details stay".to_string(), + }); + app.panel_diagnostic_open = true; + let error = crate::spawn::SpawnError::Io(io::Error::other("spawn failed")); + + finish_nested_console_open(&mut app, "beta", Err(Box::new(error))).unwrap(); + + assert_eq!(input_text(&app), "keep this draft"); + assert_eq!(app.list.selected_entry().unwrap().name, "beta"); + assert_eq!( + app.panel_diagnostic + .as_ref() + .map(|diagnostic| (diagnostic.title.as_str(), diagnostic.details.as_str(),)), + Some(("diagnostic stays", "details stay")) + ); + assert!(app.panel_diagnostic_open); + assert!( + app.notice + .as_deref() + .unwrap() + .contains("Open failed for beta: io error: spawn failed. Refreshing workspace") + ); + assert!(app.refreshing); + assert!(matches!( + app.enter_reload, + Some(OrchestratorLifecycleMode::Observe) + )); +} + +#[test] +fn dashboard_nested_console_nonrecoverable_failure_bubbles_without_state_finish() { + let mut app = test_app(vec![live_info("alpha", PodStatus::Idle)]); + app.input.insert_str("keep this draft"); + app.notice = Some("opening alpha".to_string()); + let error = io::Error::other("fatal console error"); + + let err = finish_nested_console_open(&mut app, "alpha", Err(Box::new(error))).unwrap_err(); + + assert_eq!(err.to_string(), "fatal console error"); + assert_eq!(input_text(&app), "keep this draft"); + assert_eq!(app.notice.as_deref(), Some("opening alpha")); + assert!(!app.refreshing); + assert!(app.enter_reload.is_none()); +} + #[test] fn dashboard_open_disabled_target_stays_in_dashboard() { let mut live = live_info("unreachable", PodStatus::Idle);