diff --git a/crates/tty7-cli/src/cli.rs b/crates/tty7-cli/src/cli.rs index 56d0b234..a0a814e9 100644 --- a/crates/tty7-cli/src/cli.rs +++ b/crates/tty7-cli/src/cli.rs @@ -95,7 +95,18 @@ pub enum Command { #[command( about = "Block until a pane's agent needs input, finishes its turn, or the pane \ - exits — the orchestration primitive: `tty7 wait %3 && tty7 capture %3 --plain`" + exits — the orchestration primitive: `tty7 wait %3 && tty7 capture %3 \ + --plain`", + long_about = "Block until a pane's agent needs input, finishes its turn, or the \ + pane exits — the orchestration primitive:\n\n \ + tty7 wait %3 && tty7 capture %3 --plain\n\n\ + That pairs with an agent, whose pane is still alive in `waiting` and \ + `done`. For a plain command, wait for `free` instead:\n\n \ + tty7 wait %3 --until free && tty7 capture %3 --plain\n\n\ + `exit` is the one end state with nothing left to read: the pane's \ + output goes with the pane, and the workspace keeps only a leaf the \ + GUI can revive into a fresh shell. Capture before the shell exits, \ + or run the command under `tty7 run`, which streams it." )] Wait(WaitArgs), diff --git a/crates/tty7-cli/src/commands.rs b/crates/tty7-cli/src/commands.rs index 20e0b8bf..8bb9ede0 100644 --- a/crates/tty7-cli/src/commands.rs +++ b/crates/tty7-cli/src/commands.rs @@ -1517,8 +1517,11 @@ fn doctor(ctx: &Context, backend: &mut dyn Backend) -> Result { rows.push(vec![ "status".to_string(), format!( - "pid {}, up {}s, {} panes", - status.pid, status.uptime_secs, status.panes + "pid {}, up {}s, {} pane{}", + status.pid, + status.uptime_secs, + status.panes, + if status.panes == 1 { "" } else { "s" } ), ]); let routes = match backend.control(ControlRequest::Routes)? { @@ -3853,6 +3856,45 @@ mod tests { assert!(out.contains("set (/cfg/tty7)"), "{out}"); } + /// A server running one pane said "1 panes". + /// + /// The count comes straight off the status reply, so every fresh server + /// says it — `doctor` is the first thing a new user runs, and the first + /// line it shows them was ungrammatical. + #[test] + fn doctor_counts_one_pane_in_the_singular() { + use tty7_core::daemon::control::ServerStatus; + + let status = |panes: u64| ServerStatus { + pid: 4242, + uptime_secs: 61, + panes, + control_version: CONTROL_VERSION, + protocol_version: PROTOCOL_VERSION, + build: "26.7.5".into(), + socket: "127.0.0.1:5555".into(), + }; + let line = |panes: u64| { + let mut backend = mock(); + backend.replies.push_back(ReplyOk::Status(status(panes))); + backend.replies.push_back(ReplyOk::Routes(Vec::new())); + human(run_cli( + &["tty7", "doctor"], + &Context::default(), + &mut backend, + )) + }; + + assert!( + line(1).contains("1 pane\n") || line(1).contains("1 pane "), + "{}", + line(1) + ); + assert!(!line(1).contains("1 panes"), "{}", line(1)); + assert!(line(0).contains("0 panes"), "{}", line(0)); + assert!(line(2).contains("2 panes"), "{}", line(2)); + } + /// Missing hooks are the reason a perfectly healthy-looking agent never /// reports and `tty7 wait` sits there until it times out. `doctor` is the /// verb people run when something is not working, so it is where that has