╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │30 pub fn init(arena: *std.heap.ArenaAllocator, session: *Session, route: RoutablePage) !Page { │ │31 const haxy_moment = session.haxy_moment orelse return error.NoMoment; │ │32 return switch (route.parent()) { │ │33 - .home => .{ .home = try Home.init(arena, haxy_moment) }, │ │33 + .home => .{ .home = try Home.init(arena, haxy_moment, switch (route) { │ │34 + .home_users => |after| after, │ │35 + else => 0, │ │36 + }, switch (route) { │ │37 + .home_repos => |after| after, │ │38 + else => 0, │ │39 + }) }, │ │40 .user => switch (route) { │ │41 .user, .user_settings, .user_auth => |name| .{ .user = try User.init(arena, haxy_moment, name) },│ │42 else => return error.UnexpectedRoute, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │61 // .user parent page, so switching between them stays on that page (and just│ │62 // updates the url) rather than navigating away. │ │63 pub const RoutablePage = union(enum) { │ │58 - home_users, │ │59 - home_repos, │ │64 + home_users: usize, // 0 = first page │ │65 + home_repos: usize, // 0 = first page │ │66 home_settings, │ │67 home_auth, │ │68 user: Array(evt.User.name_max_len), │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │73 repo_settings: Array(repo_route_max_len), │ │74 repo_auth: Array(repo_route_max_len), │ │75 │ │70 - pub const default: RoutablePage = .home_users; │ │76 + pub const default: RoutablePage = .{ .home_users = 0 };│ │77 │ │78 const user_segment = "/user/"; │ │79 const repo_segment = "/repo/"; │ ╰───────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │172 │ │173 pub fn urlAlloc(self: RoutablePage, arena: *std.heap.ArenaAllocator) ![]const u8 { │ │174 return switch (self) { │ │175 + .home_users => |after| if (after == 0) @as([]const u8, "/users") else try std.fmt.allocPrint(arena.allocator(), "/users?after={d}", .{after}),│ │176 + .home_repos => |after| if (after == 0) @as([]const u8, "/repos") else try std.fmt.allocPrint(arena.allocator(), "/repos?after={d}", .{after}),│ │177 .user => |name| try std.fmt.allocPrint(arena.allocator(), user_segment ++ "{s}", .{name.slice()}), │ │178 .user_settings => |name| try std.fmt.allocPrint(arena.allocator(), user_segment ++ "{s}/settings", .{name.slice()}), │ │179 .user_auth => |name| try std.fmt.allocPrint(arena.allocator(), user_segment ++ "{s}/auth", .{name.slice()}), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │200 pub fn fromUrl(path: []const u8, query: ?[]const u8) ?RoutablePage { │ │201 const after = uintParam(query, "after") orelse 0; │ │202 if (std.mem.eql(u8, path, "/")) return default; │ │195 - if (std.mem.eql(u8, path, "/users")) return .home_users; │ │196 - if (std.mem.eql(u8, path, "/repos")) return .home_repos; │ │203 + if (std.mem.eql(u8, path, "/users")) return .{ .home_users = after };│ │204 + if (std.mem.eql(u8, path, "/repos")) return .{ .home_repos = after };│ │205 if (std.mem.eql(u8, path, "/settings")) return .home_settings; │ │206 if (std.mem.eql(u8, path, "/auth")) return .home_auth; │ │207 // /user/<name>[/settings|/auth] │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │262 pub fn eql(a: RoutablePage, b: RoutablePage) bool { │ │263 if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false; │ │264 return switch (a) { │ │265 + .home_users => |a_after| a_after == b.home_users, │ │266 + .home_repos => |a_after| a_after == b.home_repos, │ │267 .user => |a_name| std.mem.eql(u8, a_name.slice(), b.user.slice()), │ │268 .user_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.user_settings.slice()),│ │269 .user_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.user_auth.slice()), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │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 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯