From 7966f2129262c240fde6a963772927c2204f3529 Mon Sep 17 00:00:00 2001 From: OneNoted Date: Thu, 19 Mar 2026 12:56:43 +0100 Subject: [PATCH 1/2] fix: restore backup list scrolling --- tui/src/views/backups.zig | 40 +++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/tui/src/views/backups.zig b/tui/src/views/backups.zig index e3ea0ac..a07fae1 100644 --- a/tui/src/views/backups.zig +++ b/tui/src/views/backups.zig @@ -136,6 +136,8 @@ pub const BackupView = struct { self.total_rows = pve_count + k8s_count; if (self.total_rows == 0) { + self.selected = 0; + self.scroll = 0; if (filter.len > 0) { drawCentered(win, "No backups matching filter"); } else { @@ -148,6 +150,19 @@ pub const BackupView = struct { // Clamp selection if (self.selected >= self.total_rows) self.selected = self.total_rows - 1; + const header_rows = calcHeaderRows(pve_count, k8s_count); + const visible_rows = win.height -| header_rows -| 1; + if (visible_rows == 0) { + self.scroll = 0; + } else if (self.selected < self.scroll) { + self.scroll = self.selected; + } else if (self.selected >= self.scroll + visible_rows) { + self.scroll = self.selected - visible_rows + 1; + } + + if (self.scroll >= self.total_rows) self.scroll = self.total_rows - 1; + const end_idx = self.scroll +| visible_rows; + var current_row: u16 = 0; // PVE Backups section @@ -171,11 +186,14 @@ pub const BackupView = struct { var pve_idx: u16 = 0; for (backups) |b| { if (!self.matchesFilter(b, filter)) continue; + const logical_idx = pve_idx; + pve_idx += 1; + if (logical_idx < self.scroll) continue; + if (logical_idx >= end_idx) continue; if (current_row >= win.height -| 1) break; - const is_selected = (pve_idx == self.selected); + const is_selected = (logical_idx == self.selected); drawBackupRow(win, current_row, b, is_selected, self.stale_days); current_row += 1; - pve_idx += 1; } } @@ -209,12 +227,14 @@ pub const BackupView = struct { var k8s_idx: u16 = 0; for (k8s_backups) |b| { if (!self.matchesK8sFilter(b, filter)) continue; - if (current_row >= win.height -| 1) break; const logical_idx = pve_count + k8s_idx; + k8s_idx += 1; + if (logical_idx < self.scroll) continue; + if (logical_idx >= end_idx) continue; + if (current_row >= win.height -| 1) break; const is_selected = (logical_idx == self.selected); drawK8sRow(win, current_row, b, is_selected); current_row += 1; - k8s_idx += 1; } } else if (pve_count > 0 and current_row < win.height -| 2) { // Show "no K8s providers" hint @@ -412,6 +432,18 @@ pub const BackupView = struct { return if (s.len > max) s[0..max] else s; } + fn calcHeaderRows(pve_count: u16, k8s_count: u16) u16 { + var rows: u16 = 0; + if (pve_count > 0) rows += 2; + if (k8s_count > 0) { + if (pve_count > 0) rows += 1; + rows += 2; + } else if (pve_count > 0) { + rows += 1; + } + return rows; + } + fn filteredBackupIndex(self: *BackupView, backups: []const poll.BackupRow, filtered_idx: u16) ?u16 { const filter = if (self.filter_len > 0) self.filter_buf[0..self.filter_len] else ""; var matched: u16 = 0; From 97b6dc26725d65c98b94574cb8e0081f69963cec Mon Sep 17 00:00:00 2001 From: OneNoted Date: Thu, 19 Mar 2026 12:59:02 +0100 Subject: [PATCH 2/2] fix: preserve backups rows in short viewports --- tui/src/views/backups.zig | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/tui/src/views/backups.zig b/tui/src/views/backups.zig index a07fae1..ec28ea8 100644 --- a/tui/src/views/backups.zig +++ b/tui/src/views/backups.zig @@ -150,11 +150,16 @@ pub const BackupView = struct { // Clamp selection if (self.selected >= self.total_rows) self.selected = self.total_rows - 1; - const header_rows = calcHeaderRows(pve_count, k8s_count); - const visible_rows = win.height -| header_rows -| 1; - if (visible_rows == 0) { + const footer_rows = self.filterBarRows(); + const content_height = win.height -| footer_rows; + if (content_height == 0) { self.scroll = 0; - } else if (self.selected < self.scroll) { + self.drawFilterBar(win); + return; + } + + const visible_rows = calcVisibleRows(content_height, pve_count, k8s_count); + if (self.selected < self.scroll) { self.scroll = self.selected; } else if (self.selected >= self.scroll + visible_rows) { self.scroll = self.selected - visible_rows + 1; @@ -190,7 +195,7 @@ pub const BackupView = struct { pve_idx += 1; if (logical_idx < self.scroll) continue; if (logical_idx >= end_idx) continue; - if (current_row >= win.height -| 1) break; + if (current_row >= content_height) break; const is_selected = (logical_idx == self.selected); drawBackupRow(win, current_row, b, is_selected, self.stale_days); current_row += 1; @@ -199,14 +204,14 @@ pub const BackupView = struct { // K8s Backups section if (k8s_count > 0) { - if (pve_count > 0 and current_row < win.height -| 3) { + if (pve_count > 0 and current_row < content_height -| 3) { // Separator current_row += 1; } var k8s_header_buf: [48]u8 = undefined; const k8s_header = std.fmt.bufPrint(&k8s_header_buf, " K8s Backups ({d})", .{k8s_count}) catch " K8s Backups"; - if (current_row < win.height -| 1) { + if (current_row < content_height -| 1) { const hdr_style: vaxis.Style = .{ .fg = .{ .index = 5 }, .bg = .{ .index = 8 }, .bold = true }; _ = win.print(&.{.{ .text = k8s_header, .style = hdr_style }}, .{ .row_offset = current_row, @@ -215,7 +220,7 @@ pub const BackupView = struct { current_row += 1; } - if (current_row < win.height -| 1) { + if (current_row < content_height) { const col_hdr_style: vaxis.Style = .{ .fg = .{ .index = 7 }, .bold = true }; _ = win.print(&.{.{ .text = k8s_col_header, .style = col_hdr_style }}, .{ .row_offset = current_row, @@ -231,12 +236,12 @@ pub const BackupView = struct { k8s_idx += 1; if (logical_idx < self.scroll) continue; if (logical_idx >= end_idx) continue; - if (current_row >= win.height -| 1) break; + if (current_row >= content_height) break; const is_selected = (logical_idx == self.selected); drawK8sRow(win, current_row, b, is_selected); current_row += 1; } - } else if (pve_count > 0 and current_row < win.height -| 2) { + } else if (pve_count > 0 and current_row < content_height -| 1) { // Show "no K8s providers" hint current_row += 1; const hint_style: vaxis.Style = .{ .fg = .{ .index = 8 } }; @@ -438,12 +443,20 @@ pub const BackupView = struct { if (k8s_count > 0) { if (pve_count > 0) rows += 1; rows += 2; - } else if (pve_count > 0) { - rows += 1; } return rows; } + fn filterBarRows(self: *BackupView) u16 { + return if (self.filter_active or self.filter_len > 0) 1 else 0; + } + + fn calcVisibleRows(content_height: u16, pve_count: u16, k8s_count: u16) u16 { + if (content_height == 0) return 0; + const header_rows = calcHeaderRows(pve_count, k8s_count); + return @max(@as(u16, 1), content_height -| header_rows); + } + fn filteredBackupIndex(self: *BackupView, backups: []const poll.BackupRow, filtered_idx: u16) ?u16 { const filter = if (self.filter_len > 0) self.filter_buf[0..self.filter_len] else ""; var matched: u16 = 0;