@@ -134,6 +134,7 @@ pub const ClientState = struct {
134134 mode : mode_mod.Mode = .normal ,
135135 recv_buf : [RECV_BUF_SIZE ]u8 = undefined ,
136136 recv_len : usize = 0 ,
137+ write_failed : bool = false ,
137138 allocator : std.mem.Allocator ,
138139
139140 pub fn init (allocator : std.mem.Allocator , id : u16 , fd : posix.fd_t ) ! * ClientState {
@@ -347,7 +348,7 @@ pub const Server = struct {
347348
348349 if (tag == TAG_LISTEN ) {
349350 // Accept a new client connection
350- const new_fd = posix .accept (self .listen_fd , null , null , posix .SOCK .CLOEXEC ) catch continue ;
351+ const new_fd = posix .accept (self .listen_fd , null , null , posix .SOCK .CLOEXEC | posix . SOCK . NONBLOCK ) catch continue ;
351352 if (self .clients .count () >= MAX_CLIENTS ) {
352353 posix .close (new_fd );
353354 continue ;
@@ -448,6 +449,18 @@ pub const Server = struct {
448449 cs .rows = hp .rows ;
449450 cs .screen .resize (hp .cols , hp .rows ) catch {};
450451 cs .screen .invalidate ();
452+ // Initialize active_panes for all existing tabs and pick a valid active_tab
453+ var first_open_tab : ? u8 = null ;
454+ for (self .tab_manager .tabs , 0.. ) | maybe_tab , i | {
455+ if (maybe_tab ) | t | {
456+ const tab_idx : u8 = @intCast (i );
457+ cs .active_panes [tab_idx ] = pane_mod .firstLeafId (t .pane_tree .root );
458+ if (first_open_tab == null ) first_open_tab = tab_idx ;
459+ }
460+ }
461+ if (first_open_tab ) | tab_idx | {
462+ cs .active_tab = tab_idx ;
463+ }
451464 if (self .active_client == null ) {
452465 self .active_client = client_id ;
453466 }
@@ -557,15 +570,23 @@ pub const Server = struct {
557570 const new_tab_idx = self .tab_manager .createTab (new_pane_id ) catch return ;
558571 self .spawnPaneState (new_pane_id ) catch return ;
559572 cs .active_tab = new_tab_idx ;
560- cs .active_panes [new_tab_idx ] = new_pane_id ;
573+ // Propagate new tab's focused pane to ALL clients
574+ var it = self .clients .valueIterator ();
575+ while (it .next ()) | other_cs | {
576+ other_cs .* .active_panes [new_tab_idx ] = new_pane_id ;
577+ }
561578 self .invalidateAllClients ();
562579 self .composeAll ();
563580 },
564581 .close_tab = > {
565- self .destroyAllPanesInTab (cs .active_tab );
566- const nearest = self .tab_manager .closeTab (cs .active_tab ) orelse return ;
567- // Update ALL clients viewing the closed tab
582+ // Reject closing the last tab before destroying any state
583+ if (self .tab_manager .tabCount () <= 1 ) return ;
568584 const closed_tab = cs .active_tab ;
585+ // Clean up per-pane client state (scroll_offsets) before destroying
586+ self .cleanupClientStateForTab (closed_tab );
587+ self .destroyAllPanesInTab (closed_tab );
588+ const nearest = self .tab_manager .closeTab (closed_tab ) orelse return ;
589+ // Update ALL clients viewing the closed tab
569590 var it = self .clients .valueIterator ();
570591 while (it .next ()) | other_cs | {
571592 if (other_cs .* .active_tab == closed_tab ) {
@@ -822,6 +843,8 @@ pub const Server = struct {
822843 while (it .next ()) | cs | {
823844 self .composeForClient (cs .* );
824845 }
846+ // Disconnect clients that failed to write (corrupted protocol stream)
847+ self .sweepFailedClients ();
825848 }
826849
827850 // ── resizePtysForClient ──────────────────────────────────────────────
@@ -989,6 +1012,11 @@ pub const Server = struct {
9891012
9901013 // Swap buffers
9911014 cs .screen .swapBuffers ();
1015+
1016+ // Disconnect clients that failed to write during this render
1017+ if (cs .write_failed ) {
1018+ self .sweepFailedClients ();
1019+ }
9921020 }
9931021
9941022 // ── sendDirtyRegionsTo ───────────────────────────────────────────────
@@ -1185,8 +1213,17 @@ pub const Server = struct {
11851213 const n = try protocol .encodeFrame (& buf , msg_type , payload );
11861214 var sent : usize = 0 ;
11871215 while (sent < n ) {
1188- const w = try posix .write (cs .fd , buf [sent .. n ]);
1189- if (w == 0 ) return error .BrokenPipe ;
1216+ const w = posix .write (cs .fd , buf [sent .. n ]) catch | err | {
1217+ // WouldBlock before any data sent = transient backpressure, skip frame
1218+ // WouldBlock after partial send = protocol stream corrupted, must disconnect
1219+ if (err == error .WouldBlock and sent == 0 ) return err ;
1220+ cs .write_failed = true ;
1221+ return err ;
1222+ };
1223+ if (w == 0 ) {
1224+ cs .write_failed = true ;
1225+ return error .BrokenPipe ;
1226+ }
11901227 sent += w ;
11911228 }
11921229 }
@@ -1213,6 +1250,50 @@ pub const Server = struct {
12131250 }
12141251 }
12151252
1253+ // ── sweepFailedClients ─────────────────────────────────────────────
1254+
1255+ /// Disconnect clients whose write_failed flag is set (protocol stream corrupted).
1256+ /// Safe to call after iteration since it collects IDs first.
1257+ fn sweepFailedClients (self : * Server ) void {
1258+ var to_remove : [MAX_CLIENTS ]u16 = undefined ;
1259+ var count : u8 = 0 ;
1260+ var it = self .clients .iterator ();
1261+ while (it .next ()) | entry | {
1262+ if (entry .value_ptr .* .write_failed ) {
1263+ if (count < MAX_CLIENTS ) {
1264+ to_remove [count ] = entry .key_ptr .* ;
1265+ count += 1 ;
1266+ }
1267+ }
1268+ }
1269+ for (to_remove [0.. count ]) | cid | {
1270+ self .disconnectClient (cid );
1271+ }
1272+ }
1273+
1274+ // ── cleanupClientStateForTab ────────────────────────────────────────
1275+
1276+ /// Remove scroll_offsets entries for all panes in the given tab from every client.
1277+ fn cleanupClientStateForTab (self : * Server , tab_idx : u8 ) void {
1278+ const tab = self .tab_manager .activeTab (tab_idx );
1279+ var cit = self .clients .valueIterator ();
1280+ while (cit .next ()) | client_cs | {
1281+ self .removeScrollOffsetsInNode (client_cs .* , tab .pane_tree .root );
1282+ }
1283+ }
1284+
1285+ fn removeScrollOffsetsInNode (self : * Server , cs_ptr : * ClientState , node : * pane_mod.LayoutNode ) void {
1286+ switch (node .* ) {
1287+ .leaf = > | l | {
1288+ _ = cs_ptr .scroll_offsets .remove (l .id );
1289+ },
1290+ .split = > | s | {
1291+ self .removeScrollOffsetsInNode (cs_ptr , s .first );
1292+ self .removeScrollOffsetsInNode (cs_ptr , s .second );
1293+ },
1294+ }
1295+ }
1296+
12161297 // ── destroyAllPanesInTab ─────────────────────────────────────────────
12171298
12181299 fn destroyAllPanesInTab (self : * Server , tab_idx : u8 ) void {
0 commit comments