Skip to content

Commit e3bb33d

Browse files
zlice devclaude
andcommitted
Address code review feedback: active_tab init, scroll cleanup, sweep, WouldBlock
- Set active_tab to first valid open tab on .hello (codeant-ai: crash if tab 0 was closed before new client attaches) - Clean up scroll_offsets for all panes when closing a tab (coderabbitai: stale entries leak and affect reused pane IDs) - Run sweepFailedClients after composeForClient, not just composeAll (coderabbitai: single-client render paths left failed clients connected) - Only mark write_failed on WouldBlock after partial frame send; transient backpressure with no bytes sent just skips the frame (codex/gemini: too aggressive disconnection of briefly slow clients) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ef553e7 commit e3bb33d

1 file changed

Lines changed: 41 additions & 2 deletions

File tree

src/server.zig

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,12 +449,18 @@ pub const Server = struct {
449449
cs.rows = hp.rows;
450450
cs.screen.resize(hp.cols, hp.rows) catch {};
451451
cs.screen.invalidate();
452-
// Initialize active_panes for all existing tabs
452+
// Initialize active_panes for all existing tabs and pick a valid active_tab
453+
var first_open_tab: ?u8 = null;
453454
for (self.tab_manager.tabs, 0..) |maybe_tab, i| {
454455
if (maybe_tab) |t| {
455-
cs.active_panes[@intCast(i)] = pane_mod.firstLeafId(t.pane_tree.root);
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;
456459
}
457460
}
461+
if (first_open_tab) |tab_idx| {
462+
cs.active_tab = tab_idx;
463+
}
458464
if (self.active_client == null) {
459465
self.active_client = client_id;
460466
}
@@ -576,6 +582,8 @@ pub const Server = struct {
576582
// Reject closing the last tab before destroying any state
577583
if (self.tab_manager.tabCount() <= 1) return;
578584
const closed_tab = cs.active_tab;
585+
// Clean up per-pane client state (scroll_offsets) before destroying
586+
self.cleanupClientStateForTab(closed_tab);
579587
self.destroyAllPanesInTab(closed_tab);
580588
const nearest = self.tab_manager.closeTab(closed_tab) orelse return;
581589
// Update ALL clients viewing the closed tab
@@ -1004,6 +1012,11 @@ pub const Server = struct {
10041012

10051013
// Swap buffers
10061014
cs.screen.swapBuffers();
1015+
1016+
// Disconnect clients that failed to write during this render
1017+
if (cs.write_failed) {
1018+
self.sweepFailedClients();
1019+
}
10071020
}
10081021

10091022
// ── sendDirtyRegionsTo ───────────────────────────────────────────────
@@ -1201,6 +1214,9 @@ pub const Server = struct {
12011214
var sent: usize = 0;
12021215
while (sent < n) {
12031216
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;
12041220
cs.write_failed = true;
12051221
return err;
12061222
};
@@ -1255,6 +1271,29 @@ pub const Server = struct {
12551271
}
12561272
}
12571273

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+
12581297
// ── destroyAllPanesInTab ─────────────────────────────────────────────
12591298

12601299
fn destroyAllPanesInTab(self: *Server, tab_idx: u8) void {

0 commit comments

Comments
 (0)