╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │308 ca.commits_after != cb.commits_after; │ │309 } │ │310 │ │311 + // true when `a` and `b` are the same home list tab paginated to a different │ │312 + // window. switching between the users/repos tabs is in-page (the home page │ │313 + // holds both lists), so only a changed `after` on the same tab navigates. │ │314 + pub fn homePageChanged(a: RoutablePage, b: RoutablePage) bool { │ │315 + return switch (a) { │ │316 + .home_users => |aa| switch (b) { │ │317 + .home_users => |bb| aa != bb, │ │318 + else => false, │ │319 + }, │ │320 + .home_repos => |aa| switch (b) { │ │321 + .home_repos => |bb| aa != bb, │ │322 + else => false, │ │323 + }, │ │324 + else => false, │ │325 + }; │ │326 + } │ │327 + │ │328 // serialize as { "kind": <tag>, "name"?: <name>, "after"?: <n> }. the default│ │329 // union(enum) codec doesn't round-trip here: Stringify emits a void │ │330 // variant as a bare tag string, but the parser expects an object — so the │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────╮ │348 try jw.objectField("after"); │ │349 try jw.write(c.after); │ │350 }, │ │351 + .home_users, .home_repos => |after| {│ │352 + try jw.objectField("after"); │ │353 + try jw.write(after); │ │354 + }, │ │355 else => {}, │ │356 } │ │357 try jw.endObject(); │ ╰──────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │368 } │ │369 }.f; │ │370 return switch (helper.kind) { │ │340 - .home_users => .home_users, │ │341 - .home_repos => .home_repos, │ │371 + .home_users => .{ .home_users = helper.after }, │ │372 + .home_repos => .{ .home_repos = helper.after }, │ │373 .home_settings => .home_settings, │ │374 .home_auth => .home_auth, │ │375 .user => .{ .user = try parseName(evt.User.name_max_len, helper.name) },│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │625 const url = custom[a_prefix.len..]; │ │626 const q = std.mem.indexOfScalar(u8, url, '?'); │ │627 const route = RoutablePage.fromUrl(if (q) |i| url[0..i] else url, if (q) |i| url[i + 1 ..] else null) orelse return null; │ │597 - // a link to a different parent page always navigates; within the repo page, │ │598 - // a files-directory link navigates while settings/auth tab links stay │ │599 - // in-page (header-handled). │ │600 - if (route.parent() != current.parent() or RoutablePage.repoPageChanged(route, current)) return route; │ │628 + // a link to a different parent page always navigates; within a page, a │ │629 + // files-directory / commits-page / list-window change navigates while tab │ │630 + // links stay in-page (header-handled). │ │631 + if (route.parent() != current.parent() or RoutablePage.repoPageChanged(route, current) or RoutablePage.homePageChanged(route, current)) return route;│ │632 return null; │ │633 } │ │634 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Home.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │28 pub fn init( │ │29 arena: *std.heap.ArenaAllocator, │ │30 haxy_moment: evt.AdminDB.HashMap(.read_only), │ │31 + // pagination window start for each list tab; the inactive tab gets 0.│ │32 + users_after: usize, │ │33 + repos_after: usize, │ │34 ) !Self { │ │35 return .{ │ │36 .header = try Header.init(arena), │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │34 - .users = try Users.init(arena, haxy_moment), │ │35 - .repos = try Repos.init(arena, haxy_moment), │ │37 + .users = try Users.init(arena, haxy_moment, users_after),│ │38 + .repos = try Repos.init(arena, haxy_moment, repos_after),│ │39 .settings = Settings.init(), │ │40 .auth = Auth.init(), │ │41 .quit = Quit.init(), │ ╰─────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │123 if (header.getSelectedIndex()) |index| { │ │124 stack.getFocus().child_id = stack.children.keys()[index]; │ │125 switch (index) { │ │123 - 0 => self.session.data.current_page = .home_users, │ │124 - 1 => self.session.data.current_page = .home_repos, │ │126 + // mirror each list tab's built pagination window so the url stays │ │127 + // consistent with what's shown (and so a build doesn't reset it). │ │128 + 0 => self.session.data.current_page = .{ .home_users = self.data.users.after },│ │129 + 1 => self.session.data.current_page = .{ .home_repos = self.data.repos.after },│ │130 2 => self.session.data.current_page = .home_settings, │ │131 3 => self.session.data.current_page = .home_auth, │ │132 // the quit tab is tty-only and not a route, so leave current_page │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/ui/Home/Repos.zig│ ╰─────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │ 9 const inp = xitui.input; │ │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; │ │15 │ │16 repos: []const evt.Repo, │ │17 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, │ │22 │ │23 const Self = @This(); │ │24 │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────╮ │25 pub fn init( │ │26 arena: *std.heap.ArenaAllocator, │ │27 haxy_moment: evt.AdminDB.HashMap(.read_only),│ │28 + after: usize, │ │29 ) !Self { │ │30 const DB = evt.AdminDB; │ │31 const hash_kind = evt.admin_repo_opts.hash; │ ╰─────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯