Add non-destructive disconnect status line and tick-driven prediction policy

Item 1: mosh-style disconnect status line. When UDP packets stop arriving,
paint a single reverse-video cyan "[dosh] last contact Ns ago — reconnecting…"
on the terminal's bottom row using save/restore cursor (ESC7/ESC8) so the app
cursor never moves and full-screen TUIs are not corrupted. Cleared the instant a
packet resumes and refreshed on the status timer tick. Configurable via
client.toml `disconnect_status` (default on) and `DOSH_DISCONNECT_STATUS` env
override. State machine and rendered escape sequences are pure and unit-tested.

Item 2: re-evaluate the prediction display policy on the select-loop status tick
via `Predictor::refresh_policy`, so a latency spike flips speculation on (or off
on recovery) promptly instead of waiting for the next keystroke. Repaints only
when the on-screen result would change, so idle ticks are no-ops.

Tests: 8 new client unit tests (status state machine + overlay bytes, predictor
spike/recovery/idle). fmt + full test suite green. No wire-format change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DuProcess
2026-06-14 10:57:40 -04:00
parent 8da98c45e7
commit 6b2933eb05
2 changed files with 402 additions and 6 deletions
+7
View File
@@ -114,6 +114,12 @@ pub struct ClientConfig {
pub use_ssh_agent: bool,
#[serde(default)]
pub forward_agent: bool,
/// Show a non-destructive, mosh-style status line on the bottom screen row
/// when UDP packets stop arriving ("[dosh] last contact Ns ago …"). Drawn
/// with save/restore cursor so it never moves the app cursor or corrupts a
/// full-screen TUI, and cleared the instant packets resume. Default on.
#[serde(default = "default_true")]
pub disconnect_status: bool,
#[serde(default = "default_send_env")]
pub send_env: Vec<String>,
#[serde(default)]
@@ -165,6 +171,7 @@ impl Default for ClientConfig {
identity_files: default_identity_files(),
use_ssh_agent: true,
forward_agent: false,
disconnect_status: true,
send_env: default_send_env(),
set_env: HashMap::new(),
}