Skip to content

Commit dd508e3

Browse files
claudesinelaw
authored andcommitted
fix(daemon): refit the shared grid when a client disconnects
The primary client sizes the session, but nothing recomputed that when a client went away — so killing the primary terminal left the survivor being rendered at the dead client's size, showing a clipped screen until it happened to send a resize of its own. Found by driving a real session through connect/disconnect orderings with two terminals and two browsers attached at once. Flag a disconnect like any other resize so the main loop recomputes the fit and repaints. When the fit turns out unchanged the recompute is a no-op and the remaining clients just get a fresh frame. The shared-session test now covers it: a second terminal must not resize the session, and dropping the primary must hand the fit to the survivor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TjGX8SFLLvHLESAw2NDBUs
1 parent 612cf6b commit dd508e3

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

crates/fresh-editor/src/server/editor_server.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1411,7 +1411,17 @@ impl EditorServer {
14111411
disconnected.sort_unstable();
14121412
disconnected.dedup();
14131413

1414-
// Remove disconnected clients
1414+
// Remove disconnected clients. Losing one changes the shared fit: the
1415+
// primary terminal sizes the session, so when it goes away the next
1416+
// client inherits that role and the grid has to be recomputed for it —
1417+
// otherwise a survivor keeps being rendered at the dead client's size
1418+
// and shows a clipped screen until it happens to send a resize. Flag it
1419+
// like any other resize so the main loop refits and repaints; when the
1420+
// fit turns out unchanged the recompute is a no-op and the clients just
1421+
// get a fresh frame.
1422+
if !disconnected.is_empty() {
1423+
resize_occurred = true;
1424+
}
14151425
for idx in disconnected.into_iter().rev() {
14161426
let client = self.clients.remove(idx);
14171427
// Clean up --wait tracking if this client was waiting

crates/fresh-editor/src/server/tests.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,6 +1808,30 @@ mod integration_tests {
18081808
"both transports write to the same buffer"
18091809
);
18101810

1811+
// (5) The primary terminal sizes the session, so when it goes away the
1812+
// next client inherits that role and the grid refits to IT. Without
1813+
// the refit on disconnect the survivor keeps being rendered at the
1814+
// dead client's size and shows a clipped screen until it happens to
1815+
// send a resize of its own.
1816+
let second = ClientConnection::connect(&socket_paths).expect("connect second terminal");
1817+
let hello = ClientHello::new(TermSize::new(60, 18));
1818+
second
1819+
.write_control(&serde_json::to_string(&ClientControl::Hello(hello)).unwrap())
1820+
.unwrap();
1821+
drop(second.read_control().unwrap());
1822+
// A second client is not the primary — it must not resize the session.
1823+
thread::sleep(Duration::from_millis(300));
1824+
let still = state_until(port, |v| v["w"].as_u64().is_some());
1825+
assert_eq!(
1826+
(still["w"].clone(), still["h"].clone()),
1827+
(serde_json::json!(80), serde_json::json!(24)),
1828+
"a non-primary terminal must not resize the shared grid"
1829+
);
1830+
// Drop the primary; the survivor's 60x18 becomes the fit.
1831+
drop(conn);
1832+
let refitted = state_until(port, |v| v["w"] == 60 && v["h"] == 18);
1833+
assert_eq!(refitted["h"], 18);
1834+
18111835
shutdown_handle.store(true, Ordering::SeqCst);
18121836
drop(server_handle.join());
18131837
drop(socket_paths.cleanup());

0 commit comments

Comments
 (0)