aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-02 02:45:53 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-02 02:45:53 +0300
commit1257c1e5cbb6487448ab10f2b6d153af7059e65e (patch)
treef63e9f5bcf32d6ceb1a01c14efd180d9b65eec17 /src
parentcc527d45fe4821cb11c484372a9f25c51c02e078 (diff)
fix: preserve toasts during retained renders
Diffstat (limited to 'src')
-rw-r--r--src/server/headless.rs55
1 files changed, 55 insertions, 0 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs
index 75195441..30ee2439 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -2514,6 +2514,7 @@ impl HeadlessServer {
&& self.app.state.selection.is_none()
&& self.app.state.copy_mode.is_none()
&& self.app.state.context_menu.is_none()
+ && self.app.state.toast.is_none()
&& !self.app.full_redraw_pending
}
@@ -3391,6 +3392,19 @@ mod tests {
}
}
+ fn frame_text(frame: &FrameData) -> String {
+ frame
+ .cells
+ .chunks(usize::from(frame.width))
+ .map(|row| {
+ row.iter()
+ .map(|cell| cell.symbol.as_str())
+ .collect::<String>()
+ })
+ .collect::<Vec<_>>()
+ .join("\n")
+ }
+
fn read_server_shutdown_reason(bytes: Vec<u8>) -> Option<String> {
match read_server_message(bytes) {
ServerMessage::ServerShutdown { reason } => reason,
@@ -5557,6 +5571,47 @@ next_tab = ""
}
#[tokio::test]
+ async fn retained_pty_update_declines_while_toast_is_visible() {
+ let (mut server, client_rx, pane_id) = retained_test_server(b"aaaa");
+ server.app.state.toast = Some(crate::app::state::ToastNotification {
+ kind: crate::app::state::ToastKind::NeedsAttention,
+ title: "pi needs attention".to_owned(),
+ context: "background ยท 2".to_owned(),
+ target: None,
+ });
+ server.render_and_stream();
+ let initial = read_server_frame(
+ client_rx
+ .recv_timeout(Duration::from_millis(100))
+ .expect("initial frame"),
+ );
+ assert!(
+ frame_text(&initial).contains("pi needs attention"),
+ "expected initial full frame to include toast text"
+ );
+
+ let toast_row = server.app.state.view.toast_hit_area.y;
+ let inner_rect = server.app.state.view.pane_infos[0].inner_rect;
+ let pane_row = toast_row
+ .checked_sub(inner_rect.y)
+ .expect("toast should overlap the pane")
+ + 1;
+ assert!(pane_row <= inner_rect.height);
+ let runtime = server
+ .app
+ .state
+ .runtime_for_pane_in_workspace(&server.app.terminal_runtimes, 0, pane_id)
+ .expect("runtime");
+ runtime.test_process_pty_bytes(format!("\x1b[{pane_row};1Hzzzz").as_bytes());
+
+ assert!(!server.render_retained_pty_update_and_stream());
+ assert!(
+ client_rx.recv_timeout(Duration::from_millis(50)).is_err(),
+ "retained path should not stream a frame that can overwrite toast cells"
+ );
+ }
+
+ #[tokio::test]
async fn retained_pty_update_matches_full_render_frame() {
let initial = b"\x1b[6 qleft \xe4\xb8\xad";
let update = b"\r\x1b[44mZ\x1b[0m";