From 207be3c771d281baae6e5fa0fb74be9a056e97a2 Mon Sep 17 00:00:00 2001 From: Can Celik Date: Tue, 1 Sep 2026 15:48:22 +0300 Subject: refactor: render the shell in the client (#3487) * feat: add opt-in client-rendered shell * feat: add client-owned shell mouse controls * feat: complete client-owned shell chrome * feat: add client-owned shell settings and notifications * feat: run client shell custom commands * feat: render popup terminals in client shell * feat: add client-owned scrollback and copy mode * fix: preserve client worktree trust defaults * feat: render startup diagnostics in client shell * feat: render onboarding in client shell * feat: render product announcements in client shell * feat: render release notes in client shell * feat: render mobile switcher in client shell * feat: render kitty graphics in client shell * fix: preserve client shell lifecycle coherence * refactor: route client shell commands through endpoint connections * refactor: make the client-rendered shell the default * refactor: remove monolithic launch mode * refactor: complete client-rendered shell cutover * perf: retain client pane surface updates * perf: scale retained pane surface updates * perf: remove remote pane input latency * test: make client shell checks platform-independent * fix: prevent client timer starvation * test: fix client shell platform gates * test: accept retained surfaces in cross-area checks * test: make client mode assertions platform-neutral * fix: badge only outdated integrations --- src/client/errors.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/client/errors.rs (limited to 'src/client/errors.rs') diff --git a/src/client/errors.rs b/src/client/errors.rs new file mode 100644 index 00000000..460ba21f --- /dev/null +++ b/src/client/errors.rs @@ -0,0 +1,99 @@ +use std::io; + +use crate::protocol; +use crate::server::socket_paths::client_socket_path; + +/// Errors that can occur during client operation. +#[derive(Debug)] +pub enum ClientError { + /// Could not connect to the server's client socket. + ConnectionFailed(io::Error), + /// Server rejected our handshake. + HandshakeRejected { version: u32, error: String }, + /// Server shut down. + ServerShutdown { reason: Option }, + /// Lost connection to the server. + ConnectionLost(io::Error), + /// Protocol error (framing, deserialization). + Protocol(protocol::FramingError), +} + +impl std::fmt::Display for ClientError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ClientError::ConnectionFailed(err) => { + write!(f, "failed to connect to server: {err}")?; + let path = client_socket_path(); + write!( + f, + "\nIs herdr server running? Start it with `herdr server`." + )?; + write!(f, "\nSocket path: {}", path.display()) + } + ClientError::HandshakeRejected { version, error } => { + write!(f, "server rejected handshake (version {version}): {error}") + } + ClientError::ServerShutdown { reason } => { + match reason.as_deref() { + Some("detached") => { + if let Ok(reattach_command) = + std::env::var(crate::remote::REATTACH_COMMAND_ENV_VAR) + { + write!(f, "detached from remote server")?; + write!(f, "\nRun `{reattach_command}` to reattach")?; + } else { + write!(f, "detached from server")?; + write!( + f, + "\nRun `{}` to reattach", + crate::session::local_attach_command() + )?; + } + } + _ => { + write!(f, "server shut down")?; + if let Some(reason) = reason { + write!(f, ": {reason}")?; + } + } + } + Ok(()) + } + ClientError::ConnectionLost(err) => { + if let Ok(reattach_command) = std::env::var(crate::remote::REATTACH_COMMAND_ENV_VAR) + { + write!(f, "lost connection to remote Herdr: {err}")?; + write!(f, "\nIf the remote server survived the SSH or network drop, its panes may still be running.")?; + write!(f, "\nRun `{reattach_command}` to reattach") + } else { + write!(f, "lost connection to server: {err}") + } + } + ClientError::Protocol(err) => write!(f, "protocol error: {err}"), + } + } +} + +impl std::error::Error for ClientError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ClientError::ConnectionFailed(err) => Some(err), + ClientError::ConnectionLost(err) => Some(err), + ClientError::Protocol(err) => Some(err), + _ => None, + } + } +} + +impl From for ClientError { + fn from(err: protocol::FramingError) -> Self { + match err { + protocol::FramingError::UnexpectedEof => ClientError::ConnectionLost(io::Error::new( + io::ErrorKind::UnexpectedEof, + "server closed connection", + )), + protocol::FramingError::Io(err) => ClientError::ConnectionLost(err), + err => ClientError::Protocol(err), + } + } +} -- cgit v1.2.3