From ce9d19c194cd7a55d56c5e1276d6ee56424b428d Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 07:57:08 +0100 Subject: [PATCH 1/9] fix(tests): use proper error return instead of panic for null checks Replace `.?` with `orelse return error.TestUnexpectedResult` in tests that validate parsing functions return non-null for valid inputs. This provides proper test failure messages instead of panics, and is more idiomatic Zig test code. --- src/config.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.zig b/src/config.zig index b476e8d..65a42ef 100644 --- a/src/config.zig +++ b/src/config.zig @@ -748,17 +748,17 @@ pub const SaveError = error{ } || fs.File.OpenError || fs.File.WriteError || fs.Dir.MakeError; test "Color.fromHex - valid hex colors" { - const white = Color.fromHex("#FFFFFF").?; + const white = Color.fromHex("#FFFFFF") orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(u8, 255), white.r); try std.testing.expectEqual(@as(u8, 255), white.g); try std.testing.expectEqual(@as(u8, 255), white.b); - const red = Color.fromHex("E06C75").?; + const red = Color.fromHex("E06C75") orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(u8, 224), red.r); try std.testing.expectEqual(@as(u8, 108), red.g); try std.testing.expectEqual(@as(u8, 117), red.b); - const one_dark_bg = Color.fromHex("#0E1116").?; + const one_dark_bg = Color.fromHex("#0E1116") orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(u8, 14), one_dark_bg.r); try std.testing.expectEqual(@as(u8, 17), one_dark_bg.g); try std.testing.expectEqual(@as(u8, 22), one_dark_bg.b); @@ -901,7 +901,7 @@ test "Config - parse with all theme palette colors" { } test "parseTerminalKey decodes 1-based coordinates" { - const parsed = parseTerminalKey("terminal_2_3").?; + const parsed = parseTerminalKey("terminal_2_3") orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(usize, 1), parsed.row); try std.testing.expectEqual(@as(usize, 2), parsed.col); try std.testing.expect(parseTerminalKey("terminal_x") == null); From 2510c4ae1fffe722ce9d2a4c7a87def60b6ab28e Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 08:16:07 +0100 Subject: [PATCH 2/9] refactor: use defensive null checks instead of forced unwraps Replace `.?` force unwraps with `orelse return`/`orelse continue` patterns for texture rendering and optional field access. This makes the code more defensive and eliminates static analysis warnings about potentially unsafe unwraps. Changes: - Render functions now unwrap textures with orelse return early - Field access uses orelse continue in loops - font_cache uses getOrPut to avoid put-then-getPtr pattern - state.zig uses local variable instead of immediate unwrap after assignment --- src/app/runtime.zig | 5 +++-- src/font_cache.zig | 5 +++-- src/session/state.zig | 5 +++-- src/ui/components/confirm_dialog.zig | 14 ++++++++++---- src/ui/components/quit_confirm.zig | 7 +++++-- src/ui/components/restart_buttons.zig | 4 +++- src/ui/components/session_interaction.zig | 7 ++++--- 7 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/app/runtime.zig b/src/app/runtime.zig index 8dcbd32..a95c1af 100644 --- a/src/app/runtime.zig +++ b/src/app/runtime.zig @@ -1339,9 +1339,10 @@ pub fn run() !void { const key = scaled_event.key.key; if (key == c.SDLK_ESCAPE and input.canHandleEscapePress(anim_state.mode)) { const focused = sessions[anim_state.focused_session]; - if (focused.spawned and !focused.dead and focused.shell != null) { + const shell = focused.shell orelse continue; + if (focused.spawned and !focused.dead) { const esc_byte: [1]u8 = .{27}; - _ = focused.shell.?.write(&esc_byte) catch |err| { + _ = shell.write(&esc_byte) catch |err| { log.warn("session {d}: failed to send escape key: {}", .{ anim_state.focused_session, err }); }; } diff --git a/src/font_cache.zig b/src/font_cache.zig index 55075b6..e529fe9 100644 --- a/src/font_cache.zig +++ b/src/font_cache.zig @@ -143,8 +143,9 @@ pub const FontCache = struct { }; errdefer fonts.close(); - try self.fonts.put(size, fonts); - return self.fonts.getPtr(size).?; + const gop = try self.fonts.getOrPut(size); + gop.value_ptr.* = fonts; + return gop.value_ptr; } fn releaseFonts(self: *FontCache) void { diff --git a/src/session/state.zig b/src/session/state.zig index fe065ff..ea6bbb1 100644 --- a/src/session/state.zig +++ b/src/session/state.zig @@ -481,8 +481,9 @@ pub const SessionState = struct { self.allocator.free(old); } - self.cwd_path = try self.allocator.dupe(u8, path); - self.cwd_basename = basenameForDisplay(self.cwd_path.?); + const new_path = try self.allocator.dupe(u8, path); + self.cwd_path = new_path; + self.cwd_basename = basenameForDisplay(new_path); self.markDirty(); } diff --git a/src/ui/components/confirm_dialog.zig b/src/ui/components/confirm_dialog.zig index d4cecb7..bd48c3a 100644 --- a/src/ui/components/confirm_dialog.zig +++ b/src/ui/components/confirm_dialog.zig @@ -219,6 +219,9 @@ pub const ConfirmDialogComponent = struct { } fn renderText(self: *ConfirmDialogComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32) void { + const title_tex = self.title_tex orelse return; + const message_tex = self.message_tex orelse return; + const scaled_padding = dpi.scale(padding, ui_scale); const title_x = modal.x + scaled_padding; const title_y = modal.y + scaled_padding; @@ -228,7 +231,7 @@ pub const ConfirmDialogComponent = struct { .w = @floatFromInt(self.title_w), .h = @floatFromInt(self.title_h), }; - _ = c.SDL_RenderTexture(renderer, self.title_tex.?, null, &title_rect); + _ = c.SDL_RenderTexture(renderer, title_tex, null, &title_rect); const message_y = title_y + self.title_h + dpi.scale(12, ui_scale); const message_rect = c.SDL_FRect{ @@ -237,10 +240,13 @@ pub const ConfirmDialogComponent = struct { .w = @floatFromInt(self.message_w), .h = @floatFromInt(self.message_h), }; - _ = c.SDL_RenderTexture(renderer, self.message_tex.?, null, &message_rect); + _ = c.SDL_RenderTexture(renderer, message_tex, null, &message_rect); } fn renderButtons(self: *ConfirmDialogComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32, theme: *const colors.Theme) void { + const cancel_tex = self.cancel_tex orelse return; + const confirm_tex = self.confirm_tex orelse return; + const buttons = self.buttonRects(modal, ui_scale); const cancel_rect = c.SDL_FRect{ @@ -277,7 +283,7 @@ pub const ConfirmDialogComponent = struct { .w = cancel_w, .h = cancel_h, }; - _ = c.SDL_RenderTexture(renderer, self.cancel_tex.?, null, &cancel_text_rect); + _ = c.SDL_RenderTexture(renderer, cancel_tex, null, &cancel_text_rect); const confirm_text_rect = c.SDL_FRect{ .x = @floatFromInt(buttons.confirm.x + @divFloor(buttons.confirm.w - self.confirm_w, 2)), @@ -285,7 +291,7 @@ pub const ConfirmDialogComponent = struct { .w = @floatFromInt(self.confirm_w), .h = @floatFromInt(self.confirm_h), }; - _ = c.SDL_RenderTexture(renderer, self.confirm_tex.?, null, &confirm_text_rect); + _ = c.SDL_RenderTexture(renderer, confirm_tex, null, &confirm_text_rect); } fn modalRect(self: *ConfirmDialogComponent, host: *const types.UiHost) geom.Rect { diff --git a/src/ui/components/quit_confirm.zig b/src/ui/components/quit_confirm.zig index a03feb3..557fd02 100644 --- a/src/ui/components/quit_confirm.zig +++ b/src/ui/components/quit_confirm.zig @@ -196,6 +196,9 @@ pub const QuitConfirmComponent = struct { } fn renderText(self: *QuitConfirmComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32) void { + const title_tex = self.title_tex orelse return; + const message_tex = self.message_tex orelse return; + const scaled_padding = dpi.scale(padding, ui_scale); const title_x = modal.x + scaled_padding; const title_y = modal.y + scaled_padding; @@ -205,7 +208,7 @@ pub const QuitConfirmComponent = struct { .w = @floatFromInt(self.title_w), .h = @floatFromInt(self.title_h), }; - _ = c.SDL_RenderTexture(renderer, self.title_tex.?, null, &title_rect); + _ = c.SDL_RenderTexture(renderer, title_tex, null, &title_rect); const message_y = title_y + self.title_h + dpi.scale(12, ui_scale); const message_rect = c.SDL_FRect{ @@ -214,7 +217,7 @@ pub const QuitConfirmComponent = struct { .w = @floatFromInt(self.message_w), .h = @floatFromInt(self.message_h), }; - _ = c.SDL_RenderTexture(renderer, self.message_tex.?, null, &message_rect); + _ = c.SDL_RenderTexture(renderer, message_tex, null, &message_rect); } fn renderButtons(self: *QuitConfirmComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32, theme: *const colors.Theme, font: *c.TTF_Font) void { diff --git a/src/ui/components/restart_buttons.zig b/src/ui/components/restart_buttons.zig index 5c20ee9..7c706a8 100644 --- a/src/ui/components/restart_buttons.zig +++ b/src/ui/components/restart_buttons.zig @@ -132,6 +132,8 @@ pub const RestartButtonsComponent = struct { fn renderButton(self: *RestartButtonsComponent, renderer: *c.SDL_Renderer, rect: geom.Rect, theme: *const colors.Theme, cache: *font_cache.FontCache) void { self.ensureTexture(renderer, theme, cache) catch return; + const texture = self.texture orelse return; + const text_width = self.tex_w; const text_height = self.tex_h; const button_w = text_width + restart_button_padding * 2; @@ -170,7 +172,7 @@ pub const RestartButtonsComponent = struct { .w = @floatFromInt(text_width), .h = @floatFromInt(text_height), }; - _ = c.SDL_RenderTexture(renderer, self.texture.?, null, &dest_rect); + _ = c.SDL_RenderTexture(renderer, texture, null, &dest_rect); } fn restartButtonRect(self: *RestartButtonsComponent, rect: geom.Rect) geom.Rect { diff --git a/src/ui/components/session_interaction.zig b/src/ui/components/session_interaction.zig index 21b48ff..d39f0c5 100644 --- a/src/ui/components/session_interaction.zig +++ b/src/ui/components/session_interaction.zig @@ -163,7 +163,8 @@ pub const SessionInteractionComponent = struct { const focused = self.sessions[focused_idx]; const view = &self.views[focused_idx]; - if (focused.spawned and focused.terminal != null) { + const terminal = focused.terminal orelse continue; + if (focused.spawned) { if (fullViewPinFromMouse(focused, view, mouse_x, mouse_y, host.window_w, host.window_h, self.font, host.term_cols, host.term_rows)) |pin| { const clicks = event.button.clicks; if (clicks >= 3) { @@ -174,7 +175,7 @@ pub const SessionInteractionComponent = struct { const mod = c.SDL_GetModState(); const cmd_held = (mod & c.SDL_KMOD_GUI) != 0; if (cmd_held) { - if (getLinkAtPin(self.allocator, &focused.terminal.?, pin, view.is_viewing_scrollback)) |uri| { + if (getLinkAtPin(self.allocator, &terminal, pin, view.is_viewing_scrollback)) |uri| { defer self.allocator.free(uri); open_url.openUrl(self.allocator, uri) catch |err| { log.err("failed to open URL: {}", .{err}); @@ -306,7 +307,7 @@ pub const SessionInteractionComponent = struct { if (should_forward) { if (fullViewCellFromMouse(mouse_x, mouse_y, host.window_w, host.window_h, self.font, host.term_cols, host.term_rows)) |cell| { - const terminal = session.terminal.?; + const terminal = session.terminal orelse continue; const sgr_format = terminal.modes.get(.mouse_format_sgr); const direction: input.MouseScrollDirection = if (scroll_delta < 0) .up else .down; const count = @abs(scroll_delta); From ffe5139b5f9cd4af3e0baa2f0cd64d7ce691b01a Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 08:32:13 +0100 Subject: [PATCH 3/9] revert: restore original null check pattern in runtime.zig Reverts the defensive null check change since zwanzig now properly recognizes the `if (x != null) { x.? }` pattern inside switch arms. The original idiomatic Zig pattern is now correctly handled by the linter without false positives. --- src/app/runtime.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/runtime.zig b/src/app/runtime.zig index a95c1af..8dcbd32 100644 --- a/src/app/runtime.zig +++ b/src/app/runtime.zig @@ -1339,10 +1339,9 @@ pub fn run() !void { const key = scaled_event.key.key; if (key == c.SDLK_ESCAPE and input.canHandleEscapePress(anim_state.mode)) { const focused = sessions[anim_state.focused_session]; - const shell = focused.shell orelse continue; - if (focused.spawned and !focused.dead) { + if (focused.spawned and !focused.dead and focused.shell != null) { const esc_byte: [1]u8 = .{27}; - _ = shell.write(&esc_byte) catch |err| { + _ = focused.shell.?.write(&esc_byte) catch |err| { log.warn("session {d}: failed to send escape key: {}", .{ anim_state.focused_session, err }); }; } From f7b3a958895a5807c586e564133221c977641667 Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 08:33:39 +0100 Subject: [PATCH 4/9] revert: restore original null check pattern in session_interaction.zig Reverts the defensive null check change for the terminal access pattern since zwanzig now properly recognizes `if (x != null) { x.? }` guards. The line 310 case remains as defensive code since it relies on a labeled block establishing an invariant that zwanzig can't track. --- src/ui/components/session_interaction.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ui/components/session_interaction.zig b/src/ui/components/session_interaction.zig index d39f0c5..6de40cb 100644 --- a/src/ui/components/session_interaction.zig +++ b/src/ui/components/session_interaction.zig @@ -163,8 +163,7 @@ pub const SessionInteractionComponent = struct { const focused = self.sessions[focused_idx]; const view = &self.views[focused_idx]; - const terminal = focused.terminal orelse continue; - if (focused.spawned) { + if (focused.spawned and focused.terminal != null) { if (fullViewPinFromMouse(focused, view, mouse_x, mouse_y, host.window_w, host.window_h, self.font, host.term_cols, host.term_rows)) |pin| { const clicks = event.button.clicks; if (clicks >= 3) { @@ -175,7 +174,7 @@ pub const SessionInteractionComponent = struct { const mod = c.SDL_GetModState(); const cmd_held = (mod & c.SDL_KMOD_GUI) != 0; if (cmd_held) { - if (getLinkAtPin(self.allocator, &terminal, pin, view.is_viewing_scrollback)) |uri| { + if (getLinkAtPin(self.allocator, &focused.terminal.?, pin, view.is_viewing_scrollback)) |uri| { defer self.allocator.free(uri); open_url.openUrl(self.allocator, uri) catch |err| { log.err("failed to open URL: {}", .{err}); From 9c0373e9b78fbfda52a926c2725d128a5f28f06e Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 08:42:50 +0100 Subject: [PATCH 5/9] revert: restore original pattern in restart_buttons.zig Zwanzig now handles the `self.method() catch return; ... self.field.?` pattern via interprocedural analysis, so defensive code is no longer needed. --- src/ui/components/restart_buttons.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ui/components/restart_buttons.zig b/src/ui/components/restart_buttons.zig index 7c706a8..5c20ee9 100644 --- a/src/ui/components/restart_buttons.zig +++ b/src/ui/components/restart_buttons.zig @@ -132,8 +132,6 @@ pub const RestartButtonsComponent = struct { fn renderButton(self: *RestartButtonsComponent, renderer: *c.SDL_Renderer, rect: geom.Rect, theme: *const colors.Theme, cache: *font_cache.FontCache) void { self.ensureTexture(renderer, theme, cache) catch return; - const texture = self.texture orelse return; - const text_width = self.tex_w; const text_height = self.tex_h; const button_w = text_width + restart_button_padding * 2; @@ -172,7 +170,7 @@ pub const RestartButtonsComponent = struct { .w = @floatFromInt(text_width), .h = @floatFromInt(text_height), }; - _ = c.SDL_RenderTexture(renderer, texture, null, &dest_rect); + _ = c.SDL_RenderTexture(renderer, self.texture.?, null, &dest_rect); } fn restartButtonRect(self: *RestartButtonsComponent, rect: geom.Rect) geom.Rect { From c812e5bf8529ca67c9b098febddbe042cc30eae5 Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 08:59:50 +0100 Subject: [PATCH 6/9] refactor: pass textures as parameters instead of cross-function access Instead of having ensureTextures() set fields that are later unwrapped in separate render functions, pass the textures as explicit parameters. This makes the data flow clear and avoids fragile cross-function assumptions about field state. Also fix continue -> return false in session_interaction.zig where there was no enclosing loop. --- src/ui/components/confirm_dialog.zig | 18 ++++++++---------- src/ui/components/quit_confirm.zig | 9 ++++----- src/ui/components/session_interaction.zig | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ui/components/confirm_dialog.zig b/src/ui/components/confirm_dialog.zig index bd48c3a..20bb87c 100644 --- a/src/ui/components/confirm_dialog.zig +++ b/src/ui/components/confirm_dialog.zig @@ -189,6 +189,10 @@ pub const ConfirmDialogComponent = struct { } self.ensureTextures(renderer, host.theme, cache) catch return; + const title_tex = self.title_tex orelse return; + const message_tex = self.message_tex orelse return; + const cancel_tex = self.cancel_tex orelse return; + const confirm_tex = self.confirm_tex orelse return; _ = c.SDL_SetRenderDrawBlendMode(renderer, c.SDL_BLENDMODE_BLEND); _ = c.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 170); @@ -214,14 +218,11 @@ pub const ConfirmDialogComponent = struct { _ = c.SDL_SetRenderDrawColor(renderer, acc.r, acc.g, acc.b, 255); primitives.drawRoundedBorder(renderer, modal, dpi.scale(modal_radius, host.ui_scale)); - self.renderText(renderer, modal, host.ui_scale); - self.renderButtons(renderer, modal, host.ui_scale, host.theme); + self.renderText(renderer, modal, host.ui_scale, title_tex, message_tex); + self.renderButtons(renderer, modal, host.ui_scale, host.theme, cancel_tex, confirm_tex); } - fn renderText(self: *ConfirmDialogComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32) void { - const title_tex = self.title_tex orelse return; - const message_tex = self.message_tex orelse return; - + fn renderText(self: *ConfirmDialogComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32, title_tex: *c.SDL_Texture, message_tex: *c.SDL_Texture) void { const scaled_padding = dpi.scale(padding, ui_scale); const title_x = modal.x + scaled_padding; const title_y = modal.y + scaled_padding; @@ -243,10 +244,7 @@ pub const ConfirmDialogComponent = struct { _ = c.SDL_RenderTexture(renderer, message_tex, null, &message_rect); } - fn renderButtons(self: *ConfirmDialogComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32, theme: *const colors.Theme) void { - const cancel_tex = self.cancel_tex orelse return; - const confirm_tex = self.confirm_tex orelse return; - + fn renderButtons(self: *ConfirmDialogComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32, theme: *const colors.Theme, cancel_tex: *c.SDL_Texture, confirm_tex: *c.SDL_Texture) void { const buttons = self.buttonRects(modal, ui_scale); const cancel_rect = c.SDL_FRect{ diff --git a/src/ui/components/quit_confirm.zig b/src/ui/components/quit_confirm.zig index 557fd02..066754a 100644 --- a/src/ui/components/quit_confirm.zig +++ b/src/ui/components/quit_confirm.zig @@ -165,6 +165,8 @@ pub const QuitConfirmComponent = struct { } self.ensureTextures(renderer, host.theme, cache) catch return; + const title_tex = self.title_tex orelse return; + const message_tex = self.message_tex orelse return; const body_fonts = cache.get(self.body_font_size) catch return; _ = c.SDL_SetRenderDrawBlendMode(renderer, c.SDL_BLENDMODE_BLEND); @@ -191,14 +193,11 @@ pub const QuitConfirmComponent = struct { _ = c.SDL_SetRenderDrawColor(renderer, acc.r, acc.g, acc.b, 255); primitives.drawRoundedBorder(renderer, modal, dpi.scale(modal_radius, host.ui_scale)); - self.renderText(renderer, modal, host.ui_scale); + self.renderText(renderer, modal, host.ui_scale, title_tex, message_tex); self.renderButtons(renderer, modal, host.ui_scale, host.theme, body_fonts.regular); } - fn renderText(self: *QuitConfirmComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32) void { - const title_tex = self.title_tex orelse return; - const message_tex = self.message_tex orelse return; - + fn renderText(self: *QuitConfirmComponent, renderer: *c.SDL_Renderer, modal: geom.Rect, ui_scale: f32, title_tex: *c.SDL_Texture, message_tex: *c.SDL_Texture) void { const scaled_padding = dpi.scale(padding, ui_scale); const title_x = modal.x + scaled_padding; const title_y = modal.y + scaled_padding; diff --git a/src/ui/components/session_interaction.zig b/src/ui/components/session_interaction.zig index 6de40cb..2802ba0 100644 --- a/src/ui/components/session_interaction.zig +++ b/src/ui/components/session_interaction.zig @@ -306,7 +306,7 @@ pub const SessionInteractionComponent = struct { if (should_forward) { if (fullViewCellFromMouse(mouse_x, mouse_y, host.window_w, host.window_h, self.font, host.term_cols, host.term_rows)) |cell| { - const terminal = session.terminal orelse continue; + const terminal = session.terminal orelse return false; const sgr_format = terminal.modes.get(.mouse_format_sgr); const direction: input.MouseScrollDirection = if (scroll_delta < 0) .up else .down; const count = @abs(scroll_delta); From 2241dd3dd6992ef5d1d7d7d634acb5d6440ae7b1 Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 09:05:11 +0100 Subject: [PATCH 7/9] revert: restore original try-assign pattern in state.zig Zwanzig now handles `self.field = try expr; self.field.?` pattern. --- src/session/state.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/session/state.zig b/src/session/state.zig index ea6bbb1..fe065ff 100644 --- a/src/session/state.zig +++ b/src/session/state.zig @@ -481,9 +481,8 @@ pub const SessionState = struct { self.allocator.free(old); } - const new_path = try self.allocator.dupe(u8, path); - self.cwd_path = new_path; - self.cwd_basename = basenameForDisplay(new_path); + self.cwd_path = try self.allocator.dupe(u8, path); + self.cwd_basename = basenameForDisplay(self.cwd_path.?); self.markDirty(); } From 2403557d078b61233f14cb2da5222557018cc9ec Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 09:21:06 +0100 Subject: [PATCH 8/9] refactor(session-interaction): revert defensive null handling Revert the defensive `orelse return false` to the original `.?` unwrap. Zwanzig now correctly handles the labeled block invariant pattern where `should_forward` being true implies `session.terminal` is non-null. --- src/ui/components/session_interaction.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/session_interaction.zig b/src/ui/components/session_interaction.zig index 2802ba0..21b48ff 100644 --- a/src/ui/components/session_interaction.zig +++ b/src/ui/components/session_interaction.zig @@ -306,7 +306,7 @@ pub const SessionInteractionComponent = struct { if (should_forward) { if (fullViewCellFromMouse(mouse_x, mouse_y, host.window_w, host.window_h, self.font, host.term_cols, host.term_rows)) |cell| { - const terminal = session.terminal orelse return false; + const terminal = session.terminal.?; const sgr_format = terminal.modes.get(.mouse_format_sgr); const direction: input.MouseScrollDirection = if (scroll_delta < 0) .up else .down; const count = @abs(scroll_delta); From fae40029feaa9b8d53cfff1e0c72d61123a31875 Mon Sep 17 00:00:00 2001 From: Forketyfork Date: Mon, 2 Feb 2026 11:23:18 +0100 Subject: [PATCH 9/9] chore(deps): bump zwanzig to v0.4.0 Update zwanzig dependency from v0.3.0 to v0.4.0 --- build.zig.zon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 97dbb69..33ff85c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -17,8 +17,8 @@ .hash = "toml-0.3.0-bV14Bd-EAQBKoXhpYft303BtA2vgLNlxntUCIWgRUl46", }, .zwanzig = .{ - .url = "https://github.com/forketyfork/zwanzig/archive/refs/tags/v0.3.0.tar.gz", - .hash = "zwanzig-0.3.0-oiXZluvfEACK3n9_G5gPK2m__rwk3W0AHYubEA1DVv0g", + .url = "https://github.com/forketyfork/zwanzig/archive/refs/tags/v0.4.0.tar.gz", + .hash = "zwanzig-0.4.0-oiXZlh-WEgAX1c_4KkZinBAxMRVxW_P4Qan2C6s70pt6", }, }, .paths = .{