Fix ui jitter during remote server initialization (#9342)
## Description * Fixes [APP-4307](https://linear.app/warpdotdev/issue/APP-4307/fix-blocklist-ui-flicker-when-initializing-remote-server) * The issue: during the initialization flow there would be significant ui jitter where the entire blocklist is moving up and down - this is because there is a gap between when the command/init shell finishes and when we render the loading footer (which looks like the output grid cursor appearing, then disappearing, then the loading footer appearing) * The fix is to render the footer as we're checking for if the binary is installed so that there is no gap for when the cursor disappears and the loading footer appears * The original reason this was done was to ensure that the loading footer didn't appear before or with the choice block - I've updated it to have an explicit check instead * This also updates where we're handling `RemoteServerSetupStateChanged` such that `session` is able to handle and update itself, and the terminal view is only in charge of rerendering the footer ## Testing https://www.loom.com/share/d4f157b4afd94f648f61ab1afbcee249 ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode
This commit is contained in:
@@ -112,13 +112,6 @@ pub enum Event {
|
||||
is_tagged_in: bool,
|
||||
},
|
||||
Handler(HandlerEvent),
|
||||
/// Emitted by the async remote server setup task to report intermediate
|
||||
/// state changes (e.g. Checking → Installing → Initializing) so the
|
||||
/// prompt can show stage-specific messages.
|
||||
RemoteServerSetupStateChanged {
|
||||
session_id: SessionId,
|
||||
state: RemoteServerSetupState,
|
||||
},
|
||||
/// Emitted when the remote server binary has been successfully checked or
|
||||
/// installed and is ready. The session is initialized independently on
|
||||
/// `Bootstrapped`; when the remote server later connects, the client is
|
||||
@@ -468,15 +461,6 @@ impl Debug for Event {
|
||||
write!(f, "AgentTaggedInChanged(is_tagged_in: {is_tagged_in})")
|
||||
}
|
||||
Event::Handler(handler_event) => write!(f, "Handler({handler_event:?}))"),
|
||||
Event::RemoteServerSetupStateChanged {
|
||||
session_id,
|
||||
ref state,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"RemoteServerSetupStateChanged(session: {session_id:?}, state: {state:?})"
|
||||
)
|
||||
}
|
||||
Event::RemoteServerReady { session_id } => {
|
||||
write!(f, "RemoteServerReady(session: {session_id:?})")
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ impl Sessions {
|
||||
#[cfg(feature = "local_tty")]
|
||||
if FeatureFlag::SshRemoteServer.is_enabled() {
|
||||
let mgr = RemoteServerManager::handle(ctx);
|
||||
ctx.subscribe_to_model(&mgr, |sessions, event, _ctx| match event {
|
||||
ctx.subscribe_to_model(&mgr, |sessions, event, ctx| match event {
|
||||
RemoteServerManagerEvent::SessionConnected {
|
||||
session_id: sid,
|
||||
host_id,
|
||||
@@ -161,6 +161,10 @@ impl Sessions {
|
||||
session.set_remote_host_id(None);
|
||||
}
|
||||
}
|
||||
RemoteServerManagerEvent::SetupStateChanged { session_id, state } => {
|
||||
sessions.set_remote_server_setup_state(*session_id, state.clone());
|
||||
ctx.notify();
|
||||
}
|
||||
RemoteServerManagerEvent::SessionConnecting { .. }
|
||||
| RemoteServerManagerEvent::SessionDeregistered { .. }
|
||||
| RemoteServerManagerEvent::SessionConnectionFailed { .. }
|
||||
@@ -170,7 +174,6 @@ impl Sessions {
|
||||
| RemoteServerManagerEvent::RepoMetadataSnapshot { .. }
|
||||
| RemoteServerManagerEvent::RepoMetadataUpdated { .. }
|
||||
| RemoteServerManagerEvent::RepoMetadataDirectoryLoaded { .. }
|
||||
| RemoteServerManagerEvent::SetupStateChanged { .. }
|
||||
| RemoteServerManagerEvent::BinaryCheckComplete { .. }
|
||||
| RemoteServerManagerEvent::BinaryInstallComplete { .. }
|
||||
| RemoteServerManagerEvent::ClientRequestFailed { .. }
|
||||
|
||||
@@ -117,13 +117,6 @@ impl ModelEventDispatcher {
|
||||
is_subshell,
|
||||
})
|
||||
}
|
||||
Event::RemoteServerSetupStateChanged { session_id, state } => {
|
||||
self.sessions.update(ctx, |sessions, ctx| {
|
||||
sessions.set_remote_server_setup_state(session_id, state.clone());
|
||||
ctx.notify();
|
||||
});
|
||||
return;
|
||||
}
|
||||
Event::RemoteServerReady { session_id } => {
|
||||
log::info!("Remote server ready for session {session_id:?}");
|
||||
return;
|
||||
|
||||
+19
-12
@@ -131,6 +131,9 @@ use crate::code_review::git_status_update::{
|
||||
};
|
||||
use crate::code_review::telemetry_event::CodeReviewPaneEntrypoint;
|
||||
use crate::projects::ProjectManagementModel;
|
||||
use crate::remote_server::manager::{
|
||||
RemoteServerInitPhase, RemoteServerManager, RemoteServerManagerEvent,
|
||||
};
|
||||
use crate::settings::ai::FocusedTerminalInfo;
|
||||
use crate::settings_view::mcp_servers_page::MCPServersSettingsPage;
|
||||
use crate::terminal::cli_agent_sessions::event::{
|
||||
@@ -4168,17 +4171,14 @@ impl TerminalView {
|
||||
|
||||
// Forward RemoteServerManager setup events into the terminal event stream
|
||||
// so the ModelEventDispatcher can gate session initialization on them.
|
||||
if crate::features::FeatureFlag::SshRemoteServer.is_enabled() {
|
||||
use crate::remote_server::manager::{RemoteServerManager, RemoteServerManagerEvent};
|
||||
if FeatureFlag::SshRemoteServer.is_enabled() {
|
||||
let mgr_handle = RemoteServerManager::handle(ctx);
|
||||
ctx.subscribe_to_model(&mgr_handle, |me, _, event, ctx| match event {
|
||||
RemoteServerManagerEvent::SetupStateChanged { session_id, state } => {
|
||||
me.model.lock().event_proxy.send_terminal_event(
|
||||
crate::terminal::event::Event::RemoteServerSetupStateChanged {
|
||||
session_id: *session_id,
|
||||
state: state.clone(),
|
||||
},
|
||||
);
|
||||
RemoteServerManagerEvent::SetupStateChanged { .. } => {
|
||||
// Sessions handles the state update directly via its own
|
||||
// subscription to the manager. Notify the view so the
|
||||
// loading footer re-renders with the updated message.
|
||||
ctx.notify();
|
||||
}
|
||||
RemoteServerManagerEvent::SessionConnected { session_id, .. } => {
|
||||
me.model.lock().event_proxy.send_terminal_event(
|
||||
@@ -4198,7 +4198,7 @@ impl TerminalView {
|
||||
.unwrap_or((None, None));
|
||||
send_telemetry_from_ctx!(
|
||||
TelemetryEvent::RemoteServerInitialization {
|
||||
phase: remote_server::manager::RemoteServerInitPhase::Initialize,
|
||||
phase: RemoteServerInitPhase::Initialize,
|
||||
error: None,
|
||||
remote_os,
|
||||
remote_arch,
|
||||
@@ -11359,18 +11359,24 @@ impl TerminalView {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` when the pending session has a connecting remote-server setup state.
|
||||
/// Returns `true` when the loading footer should be shown in place of the
|
||||
/// input editor during the SSH remote-server setup flow.
|
||||
fn show_remote_server_loading_footer(&self, model: &TerminalModel, app: &AppContext) -> bool {
|
||||
if !FeatureFlag::SshRemoteServer.is_enabled() {
|
||||
return false;
|
||||
}
|
||||
// Don't show the loading footer while the choice block is visible;
|
||||
// the choice block replaces it.
|
||||
if self.active_ssh_remote_server_choice_block().is_some() {
|
||||
return false;
|
||||
}
|
||||
let Some(pending_sid) = model.pending_session_id() else {
|
||||
return false;
|
||||
};
|
||||
self.sessions
|
||||
.as_ref(app)
|
||||
.remote_server_setup_state(pending_sid)
|
||||
.is_some_and(|state| state.is_connecting())
|
||||
.is_some_and(|state| state.is_in_progress())
|
||||
}
|
||||
|
||||
/// Renders a shimmering loading footer in place of the input editor
|
||||
@@ -11388,6 +11394,7 @@ impl TerminalView {
|
||||
.as_ref(app)
|
||||
.remote_server_setup_state(sid)
|
||||
.map(|state| match state {
|
||||
RemoteServerSetupState::Checking => "Checking...".to_string(),
|
||||
RemoteServerSetupState::Installing {
|
||||
progress_percent: Some(p),
|
||||
} => format!("Installing... ({p}%)"),
|
||||
|
||||
Reference in New Issue
Block a user