╭ changed ─────────╮ │src/event/Repo.zig│ ╰──────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │37 │ │38 // if this event_id already maps to a repo under a different key (its │ │39 // name or owner changed), drop the stale name->id entry first │ │40 - if (try event_id_to_repo.getCursor(repo_key)) |existing_cursor| { │ │40 + const existing_cursor_maybe = try event_id_to_repo.getCursor(repo_key); │ │41 + if (existing_cursor_maybe) |existing_cursor| { │ │42 const existing_repo = try DB.HashMap(.read_only).init(existing_cursor); │ │43 const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_repo); │ │44 const existing_path = try std.fmt.allocPrint(arena.allocator(), "{s}/{s}", .{ existing_event.user_id, existing_event.name });│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │53 │ │54 try name_to_repo_id.put(hash.hashInt(hash_kind, repo_path), .{ .bytes = event_id }); │ │55 │ │56 + // first time we've seen this repo: append its id to the ordered list │ │57 + // the repos view paginates through (a delete leaves a tombstone here). │ │58 + if (existing_cursor_maybe == null) { │ │59 + const repo_list_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "repo-list")); │ │60 + const repo_list = try DB.ArrayList(.read_write).init(repo_list_cursor); │ │61 + try repo_list.append(.{ .bytes = event_id }); │ │62 + } │ │63 + │ │64 const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repos"));│ │65 const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │66 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │48 │ │49 // if this event_id already maps to a user with a different name, │ │50 // drop the stale name->id entry first │ │51 - if (try event_id_to_user.getCursor(user_key)) |existing_cursor| { │ │51 + const existing_cursor_maybe = try event_id_to_user.getCursor(user_key); │ │52 + if (existing_cursor_maybe) |existing_cursor| { │ │53 const existing_user = try DB.HashMap(.read_only).init(existing_cursor); │ │54 const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_user);│ │55 if (!std.mem.eql(u8, existing_event.name, event.name)) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │62 try evt.upsert(@This(), DB, hash_kind, user, event); │ │63 │ │64 try name_to_user_id.put(hash.hashInt(hash_kind, event.name), .{ .bytes = event_id }); │ │65 + │ │66 + // first time we've seen this user: append its id to the ordered list │ │67 + // the users view paginates through (a delete leaves a tombstone here). │ │68 + if (existing_cursor_maybe == null) { │ │69 + const user_list_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-list"));│ │70 + const user_list = try DB.ArrayList(.read_write).init(user_list_cursor); │ │71 + try user_list.append(.{ .bytes = event_id }); │ │72 + } │ │73 } else { │ │74 // read the user's name so we can drop its name->id index entry │ │75 if (try event_id_to_user.getCursor(user_key)) |existing_cursor| { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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()), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯