╭ changed ────────────╮ │src/ui/Home/Repos.zig│ ╰─────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │10 const Grid = xitui.grid.Grid; │ │11 const Focus = xitui.focus.Focus; │ │12 │ │13 -// how many repos a page shows before the "load more" row appears.│ │14 -pub const page_size = 20; │ │13 +pub const page_size = 20; // how many repos one window shows │ │14 │ │15 repos: []const evt.Repo, │ │16 owner_names: []const []const u8, │ ╰──────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │18 -// the window start this page was built with, mirrored into the url. │ │19 -after: usize, │ │20 -// the `after` for the "load more" link, or null when this is the last page. │ │21 -next_after: ?usize, │ │17 +after: usize, // the window start this page was built with, mirrored into the url │ │18 +next_after: ?usize, // the `after` for the "next" row, or null when this is the last window│ │19 │ │20 const Self = @This(); │ │21 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 86 defer arena.deinit(); │ │ 87 const aa = arena.allocator(); │ │ 88 │ │ 92 - // one row per repo, plus a trailing "load more" row when more remain. │ │ 93 - const extra: usize = if (data.next_after != null) 1 else 0; │ │ 94 - const lines = try aa.alloc([]const u8, data.repos.len + extra); │ │ 95 - const links = try aa.alloc([]const u8, data.repos.len + extra); │ │ 89 + // a leading "previous" row off the first window, one row per repo, then │ │ 90 + // a trailing "next" row when more remain. each window row navigates to the │ │ 91 + // adjacent window (full reload on web, Nav rebuild on the TUI). │ │ 92 + const lead: usize = if (data.after > 0) 1 else 0; │ │ 93 + const trail: usize = if (data.next_after != null) 1 else 0; │ │ 94 + const lines = try aa.alloc([]const u8, lead + data.repos.len + trail); │ │ 95 + const links = try aa.alloc([]const u8, lead + data.repos.len + trail); │ │ 96 + if (data.after > 0) { │ │ 97 + lines[0] = "← previous"; │ │ 98 + links[0] = try std.fmt.allocPrint(aa, "a:/repos?after={d}", .{data.after -| page_size}); │ │ 99 + } │ │100 for (data.repos, 0..) |repo, i| { │ │ 97 - lines[i] = try std.fmt.allocPrint(aa, "{s} - {s}", .{ repo.name, repo.description }); │ │101 + lines[lead + i] = try std.fmt.allocPrint(aa, "{s} - {s}", .{ repo.name, repo.description });│ │102 // clicking a repo opens its page; the "a:" prefix makes the web │ │103 // renderer emit an <a href="/repo/alice/foo"> anchor. skip the link │ │104 // when the owner is unknown so it isn't a dead route. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │105 const owner = data.owner_names[i]; │ │102 - links[i] = if (owner.len > 0) try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}", .{ owner, repo.name }) else ""; │ │106 + links[lead + i] = if (owner.len > 0) try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}", .{ owner, repo.name }) else "";│ │107 } │ │108 if (data.next_after) |next_after| { │ │105 - // the load-more row navigates to the next window (full reload on web, │ │106 - // Nav rebuild on the TUI), exactly like the commits "load more". │ │107 - lines[data.repos.len] = "load more"; │ │108 - links[data.repos.len] = try std.fmt.allocPrint(aa, "a:/repos?after={d}", .{next_after}); │ │109 + lines[lead + data.repos.len] = "next →"; │ │110 + links[lead + data.repos.len] = try std.fmt.allocPrint(aa, "a:/repos?after={d}", .{next_after}); │ │111 } │ │112 try self.list.setItems(allocator, lines, links); │ │113 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/ui/Home/Users.zig│ ╰─────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │10 const Grid = xitui.grid.Grid; │ │11 const Focus = xitui.focus.Focus; │ │12 │ │13 -// how many users a page shows before the "load more" row appears. │ │14 -pub const page_size = 20; │ │13 +pub const page_size = 20; // how many users one window shows │ │14 │ │15 users: []const evt.User.Safe, │ │17 -// the window start this page was built with, mirrored into the url. │ │18 -after: usize, │ │19 -// the `after` for the "load more" link, or null when this is the last page. │ │20 -next_after: ?usize, │ │16 +after: usize, // the window start this page was built with, mirrored into the url │ │17 +next_after: ?usize, // the `after` for the "next" row, or null when this is the last window│ │18 │ │19 const Self = @This(); │ │20 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 80 defer arena.deinit(); │ │ 81 const aa = arena.allocator(); │ │ 82 │ │ 86 - // one row per user, plus a trailing "load more" row when more remain. │ │ 87 - const extra: usize = if (data.next_after != null) 1 else 0; │ │ 88 - const lines = try aa.alloc([]const u8, data.users.len + extra); │ │ 89 - const links = try aa.alloc([]const u8, data.users.len + extra); │ │ 83 + // a leading "previous" row off the first window, one row per user, then │ │ 84 + // a trailing "next" row when more remain. each window row navigates to the │ │ 85 + // adjacent window (full reload on web, Nav rebuild on the TUI). │ │ 86 + const lead: usize = if (data.after > 0) 1 else 0; │ │ 87 + const trail: usize = if (data.next_after != null) 1 else 0; │ │ 88 + const lines = try aa.alloc([]const u8, lead + data.users.len + trail); │ │ 89 + const links = try aa.alloc([]const u8, lead + data.users.len + trail); │ │ 90 + if (data.after > 0) { │ │ 91 + lines[0] = "← previous"; │ │ 92 + links[0] = try std.fmt.allocPrint(aa, "a:/users?after={d}", .{data.after -| page_size}); │ │ 93 + } │ │ 94 for (data.users, 0..) |user, i| { │ │ 91 - lines[i] = try std.fmt.allocPrint(aa, "{s} ({s})", .{ user.name, user.display_name }); │ │ 95 + lines[lead + i] = try std.fmt.allocPrint(aa, "{s} ({s})", .{ user.name, user.display_name }); │ │ 96 // clicking a user opens their page; the "a:" prefix makes the web │ │ 97 // renderer emit an <a href="/user/foo"> anchor. │ │ 94 - links[i] = try std.fmt.allocPrint(aa, "a:/user/{s}", .{user.name}); │ │ 98 + links[lead + i] = try std.fmt.allocPrint(aa, "a:/user/{s}", .{user.name}); │ │ 99 } │ │100 if (data.next_after) |next_after| { │ │ 97 - // the load-more row navigates to the next window (full reload on web, │ │ 98 - // Nav rebuild on the TUI), exactly like the commits "load more". │ │ 99 - lines[data.users.len] = "load more"; │ │100 - links[data.users.len] = try std.fmt.allocPrint(aa, "a:/users?after={d}", .{next_after}); │ │101 + lines[lead + data.users.len] = "next →"; │ │102 + links[lead + data.users.len] = try std.fmt.allocPrint(aa, "a:/users?after={d}", .{next_after});│ │103 } │ │104 try self.list.setItems(allocator, lines, links); │ │105 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │14 │ │15 // how many commits a page shows before a "next" link appears. │ │16 const page_size = 20; │ │17 -// how many diff hunks a commit shows per page; "load more" reveals another page.│ │17 +// how many diff hunks one window of a commit's diff shows; "next"/"previous" │ │18 +// move to the adjacent window. │ │19 const diff_page = 10; │ │20 │ │21 pub const Hunk = struct { │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │30 date: []const u8, // "YYYY-MM-DD" │ │31 message: []const u8, // first line only │ │32 hunks: []const Hunk, │ │32 - // how many hunks `hunks` covers; the resume cursor for "load more". │ │33 - shown_hunks: usize, │ │34 - // true when the diff was truncated at the budget and more hunks remain.│ │33 + // the hunk index this window starts at (0 = the first window). │ │34 + window_start: usize, │ │35 + // whether hunks exist before this window / after it. │ │36 + has_prev: bool, │ │37 has_more: bool, │ │38 }; │ │39 │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │51 event_id: *const [evt.event_id_size]u8, │ │52 identity: []const u8, │ │53 start_oid: []const u8, │ │52 - // how many hunks the selected commit (the first one, == start_oid) shows;│ │53 - // 0 means the default page. raised by "load more" via the url. │ │54 + // the hunk index the selected commit's (the first one, == start_oid) diff│ │55 + // window starts at; 0 is the first window. moved by "next"/"previous". │ │56 after: usize, │ │57 ) !Self { │ │58 const aa = arena.allocator(); │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │107 .date = try formatDate(aa, md.timestamp), │ │108 .message = try aa.dupe(u8, firstLine(md.message orelse "")),│ │109 .hunks = &.{}, │ │108 - .shown_hunks = 0, │ │110 + .window_start = 0, │ │111 + .has_prev = false, │ │112 .has_more = false, │ │113 }; │ │114 count += 1; │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯